summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
l---------src/anvil/.indent.pro1
-rw-r--r--src/anvil/.printfck25
-rw-r--r--src/anvil/Makefile.in82
-rw-r--r--src/anvil/anvil.c1047
l---------src/bounce/.indent.pro1
-rw-r--r--src/bounce/.printfck25
-rw-r--r--src/bounce/2template_test.in136
-rw-r--r--src/bounce/Makefile.in695
-rwxr-xr-xsrc/bounce/annotate.sh120
-rw-r--r--src/bounce/bounce.c713
-rw-r--r--src/bounce/bounce_append_service.c168
-rw-r--r--src/bounce/bounce_cleanup.c177
-rw-r--r--src/bounce/bounce_notify_service.c327
-rw-r--r--src/bounce/bounce_notify_util.c1010
-rw-r--r--src/bounce/bounce_notify_util_tester.c167
-rw-r--r--src/bounce/bounce_notify_verp.c267
-rw-r--r--src/bounce/bounce_one_service.c266
-rw-r--r--src/bounce/bounce_service.h121
-rw-r--r--src/bounce/bounce_template.c548
-rw-r--r--src/bounce/bounce_template.h99
-rw-r--r--src/bounce/bounce_templates.c399
-rw-r--r--src/bounce/bounce_trace_service.c220
-rw-r--r--src/bounce/bounce_warn_service.c295
-rw-r--r--src/bounce/logfile-no-msgid-no-eoh-event13
-rw-r--r--src/bounce/logfile-no-msgid-with-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-no-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-with-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-with-filter13
-rw-r--r--src/bounce/logfile-with-msgid-with-long-line13
-rwxr-xr-xsrc/bounce/msgfile-no-msgid-no-eoh-eventbin0 -> 522 bytes
-rwxr-xr-xsrc/bounce/msgfile-no-msgid-with-eoh-eventbin0 -> 536 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-no-eoh-eventbin0 -> 606 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-eoh-eventbin0 -> 621 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-filterbin0 -> 581 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-long-linebin0 -> 4624 bytes
-rw-r--r--src/bounce/no-msgid-no-eoh-event-no-thread.ref47
-rw-r--r--src/bounce/no-msgid-no-eoh-event-with-thread.ref47
-rw-r--r--src/bounce/no-msgid-with-eoh-event-no-thread.ref49
-rw-r--r--src/bounce/no-msgid-with-eoh-event-with-thread.ref49
-rw-r--r--src/bounce/obs_template_test.ref68
-rw-r--r--src/bounce/template_test.ref68
-rw-r--r--src/bounce/with-msgid-no-eoh-event-no-thread.ref49
-rw-r--r--src/bounce/with-msgid-no-eoh-event-with-thread.ref51
-rw-r--r--src/bounce/with-msgid-with-eoh-event-no-thread.ref51
-rw-r--r--src/bounce/with-msgid-with-eoh-event-with-thread.ref53
-rw-r--r--src/bounce/with-msgid-with-filter-no-thread.ref48
-rw-r--r--src/bounce/with-msgid-with-filter-with-thread.ref50
-rw-r--r--src/bounce/with-msgid-with-long-line-no-thread.ref50
-rw-r--r--src/bounce/with-msgid-with-long-line-with-thread.ref52
l---------src/cleanup/.indent.pro1
-rw-r--r--src/cleanup/.printfck25
-rw-r--r--src/cleanup/Makefile.in1394
-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.c656
-rw-r--r--src/cleanup/cleanup.h370
-rw-r--r--src/cleanup/cleanup_addr.c286
-rw-r--r--src/cleanup/cleanup_api.c395
-rw-r--r--src/cleanup/cleanup_body_edit.c243
-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.c476
-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.c1111
-rw-r--r--src/cleanup/cleanup_milter.c2775
-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.in17a9
-rw-r--r--src/cleanup/cleanup_milter.in17b10
-rw-r--r--src/cleanup/cleanup_milter.in17c12
-rw-r--r--src/cleanup/cleanup_milter.in17d18
-rw-r--r--src/cleanup/cleanup_milter.in17e12
-rw-r--r--src/cleanup/cleanup_milter.in17f15
-rw-r--r--src/cleanup/cleanup_milter.in17g23
-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.ref17a11
-rw-r--r--src/cleanup/cleanup_milter.ref17a230
-rw-r--r--src/cleanup/cleanup_milter.ref17b11
-rw-r--r--src/cleanup/cleanup_milter.ref17b231
-rw-r--r--src/cleanup/cleanup_milter.ref17c11
-rw-r--r--src/cleanup/cleanup_milter.ref17c231
-rw-r--r--src/cleanup/cleanup_milter.ref17d11
-rw-r--r--src/cleanup/cleanup_milter.ref17d235
-rw-r--r--src/cleanup/cleanup_milter.ref17e12
-rw-r--r--src/cleanup/cleanup_milter.ref17e230
-rw-r--r--src/cleanup/cleanup_milter.ref17f12
-rw-r--r--src/cleanup/cleanup_milter.ref17f230
-rw-r--r--src/cleanup/cleanup_milter.ref17g12
-rw-r--r--src/cleanup/cleanup_milter.ref17g233
-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.c233
-rw-r--r--src/cleanup/cleanup_out_recipient.c266
-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-file17bin0 -> 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
l---------src/discard/.indent.pro1
-rw-r--r--src/discard/.printfck25
-rw-r--r--src/discard/Makefile.in86
-rw-r--r--src/discard/discard.c253
l---------src/dns/.indent.pro1
-rw-r--r--src/dns/.printfck25
-rw-r--r--src/dns/Makefile.in408
-rw-r--r--src/dns/dns.h357
-rw-r--r--src/dns/dns_lookup.c1272
-rw-r--r--src/dns/dns_rr.c347
-rw-r--r--src/dns/dns_rr_eq_sa.c157
-rw-r--r--src/dns/dns_rr_eq_sa.in4
-rw-r--r--src/dns/dns_rr_eq_sa.ref24
-rw-r--r--src/dns/dns_rr_filter.c150
-rw-r--r--src/dns/dns_rr_to_pa.c113
-rw-r--r--src/dns/dns_rr_to_pa.in2
-rw-r--r--src/dns/dns_rr_to_pa.ref2
-rw-r--r--src/dns/dns_rr_to_sa.c163
-rw-r--r--src/dns/dns_rr_to_sa.in2
-rw-r--r--src/dns/dns_rr_to_sa.ref2
-rw-r--r--src/dns/dns_sa_to_rr.c138
-rw-r--r--src/dns/dns_sa_to_rr.in1
-rw-r--r--src/dns/dns_sa_to_rr.ref2
-rw-r--r--src/dns/dns_sec.c144
-rw-r--r--src/dns/dns_str_resflags.c128
-rw-r--r--src/dns/dns_strerror.c69
-rw-r--r--src/dns/dns_strrecord.c117
-rw-r--r--src/dns/dns_strtype.c211
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref12
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref6
-rw-r--r--src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref10
-rw-r--r--src/dns/error.ref13
-rw-r--r--src/dns/error.reg1
-rw-r--r--src/dns/mxonly_test.ref11
-rw-r--r--src/dns/no-a.ref13
-rw-r--r--src/dns/no-a.reg1
-rw-r--r--src/dns/no-aaaa.ref13
-rw-r--r--src/dns/no-aaaa.reg1
-rw-r--r--src/dns/no-mx.ref15
-rw-r--r--src/dns/no-mx.reg1
-rw-r--r--src/dns/no-txt.reg1
-rw-r--r--src/dns/nullmx_test.ref8
-rw-r--r--src/dns/nxdomain_test.ref5
-rw-r--r--src/dns/test_dns_lookup.c131
l---------src/dnsblog/.indent.pro1
-rw-r--r--src/dnsblog/Makefile.in84
-rw-r--r--src/dnsblog/dnsblog.c319
l---------src/error/.indent.pro1
-rw-r--r--src/error/.printfck25
-rw-r--r--src/error/Makefile.in89
-rw-r--r--src/error/error.c266
l---------src/flush/.indent.pro1
-rw-r--r--src/flush/.printfck25
-rw-r--r--src/flush/Makefile.in96
-rw-r--r--src/flush/flush.c865
l---------src/fsstone/.indent.pro1
-rw-r--r--src/fsstone/.printfck25
-rw-r--r--src/fsstone/Makefile.in69
-rw-r--r--src/fsstone/fsstone.c242
l---------src/global/.indent.pro1
-rw-r--r--src/global/.printfck25
-rw-r--r--src/global/Makefile.in3086
-rw-r--r--src/global/abounce.c466
-rw-r--r--src/global/abounce.h43
-rw-r--r--src/global/addr_match_list.c143
-rw-r--r--src/global/addr_match_list.h41
-rw-r--r--src/global/anvil_clnt.c527
-rw-r--r--src/global/anvil_clnt.h83
-rw-r--r--src/global/attr_override.c190
-rw-r--r--src/global/attr_override.h70
-rw-r--r--src/global/been_here.c335
-rw-r--r--src/global/been_here.h57
-rw-r--r--src/global/bounce.c549
-rw-r--r--src/global/bounce.h99
-rw-r--r--src/global/bounce_log.c321
-rw-r--r--src/global/bounce_log.h55
-rw-r--r--src/global/canon_addr.c67
-rw-r--r--src/global/canon_addr.h36
-rw-r--r--src/global/cfg_parser.c322
-rw-r--r--src/global/cfg_parser.h56
-rw-r--r--src/global/cleanup_strerror.c113
-rw-r--r--src/global/cleanup_strflags.c89
-rw-r--r--src/global/cleanup_user.h117
-rw-r--r--src/global/clnt_stream.c278
-rw-r--r--src/global/clnt_stream.h48
-rw-r--r--src/global/compat_level.c461
-rw-r--r--src/global/compat_level.h43
-rw-r--r--src/global/compat_level_convert.in22
-rw-r--r--src/global/compat_level_convert.ref29
-rw-r--r--src/global/compat_level_expand.in27
-rw-r--r--src/global/compat_level_expand.ref55
-rw-r--r--src/global/config.h59
-rw-r--r--src/global/config_known_tcp_ports.c257
-rw-r--r--src/global/config_known_tcp_ports.h30
-rw-r--r--src/global/config_known_tcp_ports.ref8
-rw-r--r--src/global/conv_time.c113
-rw-r--r--src/global/conv_time.h30
-rw-r--r--src/global/data_redirect.c247
-rw-r--r--src/global/data_redirect.h31
-rw-r--r--src/global/db_common.c575
-rw-r--r--src/global/db_common.h58
-rw-r--r--src/global/debug_peer.c133
-rw-r--r--src/global/debug_peer.h31
-rw-r--r--src/global/debug_process.c62
-rw-r--r--src/global/debug_process.h30
-rw-r--r--src/global/defer.c383
-rw-r--r--src/global/defer.h54
-rw-r--r--src/global/deliver_completed.c60
-rw-r--r--src/global/deliver_completed.h35
-rw-r--r--src/global/deliver_flock.c82
-rw-r--r--src/global/deliver_flock.h36
-rw-r--r--src/global/deliver_pass.c247
-rw-r--r--src/global/deliver_pass.h37
-rw-r--r--src/global/deliver_request.c484
-rw-r--r--src/global/deliver_request.h156
-rw-r--r--src/global/delivered_hdr.c266
-rw-r--r--src/global/delivered_hdr.h43
-rw-r--r--src/global/delivered_hdr.ref3
-rw-r--r--src/global/dict_ldap.c1994
-rw-r--r--src/global/dict_ldap.h33
-rw-r--r--src/global/dict_memcache.c599
-rw-r--r--src/global/dict_memcache.h38
-rw-r--r--src/global/dict_mysql.c963
-rw-r--r--src/global/dict_mysql.h40
-rw-r--r--src/global/dict_pgsql.c924
-rw-r--r--src/global/dict_pgsql.h41
-rw-r--r--src/global/dict_proxy.c532
-rw-r--r--src/global/dict_proxy.h53
-rw-r--r--src/global/dict_sqlite.c349
-rw-r--r--src/global/dict_sqlite.h32
-rw-r--r--src/global/domain_list.c132
-rw-r--r--src/global/domain_list.h40
-rw-r--r--src/global/dot_lockfile.c173
-rw-r--r--src/global/dot_lockfile.h36
-rw-r--r--src/global/dot_lockfile_as.c99
-rw-r--r--src/global/dot_lockfile_as.h36
-rw-r--r--src/global/dsb_scan.c73
-rw-r--r--src/global/dsb_scan.h46
-rw-r--r--src/global/dsn.c189
-rw-r--r--src/global/dsn.h84
-rw-r--r--src/global/dsn_buf.c342
-rw-r--r--src/global/dsn_buf.h89
-rw-r--r--src/global/dsn_filter.c194
-rw-r--r--src/global/dsn_filter.h34
-rw-r--r--src/global/dsn_mask.c123
-rw-r--r--src/global/dsn_mask.h91
-rw-r--r--src/global/dsn_print.c73
-rw-r--r--src/global/dsn_print.h46
-rw-r--r--src/global/dsn_util.c183
-rw-r--r--src/global/dsn_util.h77
-rw-r--r--src/global/dynamicmaps.c367
-rw-r--r--src/global/dynamicmaps.h38
-rw-r--r--src/global/ehlo_mask.c145
-rw-r--r--src/global/ehlo_mask.h53
-rw-r--r--src/global/ehlo_mask.in3
-rw-r--r--src/global/ehlo_mask.ref3
-rw-r--r--src/global/ext_prop.c78
-rw-r--r--src/global/ext_prop.h37
-rw-r--r--src/global/file_id.c101
-rw-r--r--src/global/file_id.h38
-rw-r--r--src/global/flush_clnt.c269
-rw-r--r--src/global/flush_clnt.h53
-rw-r--r--src/global/fold_addr.c172
-rw-r--r--src/global/fold_addr.h35
-rw-r--r--src/global/fold_addr_test.in19
-rw-r--r--src/global/fold_addr_test.ref36
-rw-r--r--src/global/haproxy_srvr.c891
-rw-r--r--src/global/haproxy_srvr.h52
-rw-r--r--src/global/header_body_checks.c655
-rw-r--r--src/global/header_body_checks.h83
-rw-r--r--src/global/header_body_checks_ignore.ref18
-rw-r--r--src/global/header_body_checks_null.ref42
-rw-r--r--src/global/header_body_checks_prepend.ref88
-rw-r--r--src/global/header_body_checks_replace.ref64
-rw-r--r--src/global/header_body_checks_strip.ref41
-rw-r--r--src/global/header_body_checks_warn.ref65
-rw-r--r--src/global/header_opts.c179
-rw-r--r--src/global/header_opts.h84
-rw-r--r--src/global/header_token.c266
-rw-r--r--src/global/header_token.h47
-rw-r--r--src/global/hfrom_format.c281
-rw-r--r--src/global/hfrom_format.h34
-rw-r--r--src/global/hfrom_format.ref8
-rw-r--r--src/global/info_log_addr_form.c124
-rw-r--r--src/global/info_log_addr_form.h31
-rw-r--r--src/global/input_transp.c102
-rw-r--r--src/global/input_transp.h36
-rw-r--r--src/global/int_filt.c80
-rw-r--r--src/global/int_filt.h34
-rw-r--r--src/global/is_header.c92
-rw-r--r--src/global/is_header.h32
-rw-r--r--src/global/lex_822.h36
-rw-r--r--src/global/log_adhoc.c221
-rw-r--r--src/global/log_adhoc.h43
-rw-r--r--src/global/login_sender_match.c364
-rw-r--r--src/global/login_sender_match.h49
-rw-r--r--src/global/login_sender_match.ref35
-rw-r--r--src/global/mail_addr.c96
-rw-r--r--src/global/mail_addr.h36
-rw-r--r--src/global/mail_addr_crunch.c231
-rw-r--r--src/global/mail_addr_crunch.h52
-rw-r--r--src/global/mail_addr_crunch.in51
-rw-r--r--src/global/mail_addr_crunch.ref22
-rw-r--r--src/global/mail_addr_find.c671
-rw-r--r--src/global/mail_addr_find.h82
-rw-r--r--src/global/mail_addr_find.in77
-rw-r--r--src/global/mail_addr_find.ref63
-rw-r--r--src/global/mail_addr_form.c65
-rw-r--r--src/global/mail_addr_form.h36
-rw-r--r--src/global/mail_addr_map.c527
-rw-r--r--src/global/mail_addr_map.h51
-rw-r--r--src/global/mail_addr_map.ref26
-rw-r--r--src/global/mail_command_client.c108
-rw-r--r--src/global/mail_command_server.c67
-rw-r--r--src/global/mail_conf.c278
-rw-r--r--src/global/mail_conf.h249
-rw-r--r--src/global/mail_conf_bool.c153
-rw-r--r--src/global/mail_conf_int.c223
-rw-r--r--src/global/mail_conf_long.c213
-rw-r--r--src/global/mail_conf_nbool.c158
-rw-r--r--src/global/mail_conf_nint.c232
-rw-r--r--src/global/mail_conf_raw.c145
-rw-r--r--src/global/mail_conf_str.c199
-rw-r--r--src/global/mail_conf_time.c258
-rw-r--r--src/global/mail_conf_time.ref5
-rw-r--r--src/global/mail_connect.c127
-rw-r--r--src/global/mail_copy.c315
-rw-r--r--src/global/mail_copy.h63
-rw-r--r--src/global/mail_date.c141
-rw-r--r--src/global/mail_date.h35
-rw-r--r--src/global/mail_dict.c121
-rw-r--r--src/global/mail_dict.h25
-rw-r--r--src/global/mail_error.c80
-rw-r--r--src/global/mail_error.h44
-rw-r--r--src/global/mail_flush.c80
-rw-r--r--src/global/mail_flush.h30
-rw-r--r--src/global/mail_open_ok.c127
-rw-r--r--src/global/mail_open_ok.h33
-rw-r--r--src/global/mail_params.c1038
-rw-r--r--src/global/mail_params.h4396
-rw-r--r--src/global/mail_parm_split.c128
-rw-r--r--src/global/mail_parm_split.h38
-rw-r--r--src/global/mail_parm_split.in6
-rw-r--r--src/global/mail_parm_split.ref25
-rw-r--r--src/global/mail_pathname.c44
-rw-r--r--src/global/mail_proto.h323
-rw-r--r--src/global/mail_queue.c439
-rw-r--r--src/global/mail_queue.h193
-rw-r--r--src/global/mail_run.c150
-rw-r--r--src/global/mail_run.h31
-rw-r--r--src/global/mail_scan_dir.c62
-rw-r--r--src/global/mail_scan_dir.h35
-rw-r--r--src/global/mail_stream.c627
-rw-r--r--src/global/mail_stream.h91
-rw-r--r--src/global/mail_task.c77
-rw-r--r--src/global/mail_task.h29
-rw-r--r--src/global/mail_trigger.c98
-rw-r--r--src/global/mail_version.c258
-rw-r--r--src/global/mail_version.h109
-rw-r--r--src/global/mail_version.in8
-rw-r--r--src/global/mail_version.ref16
-rw-r--r--src/global/maillog_client.c304
-rw-r--r--src/global/maillog_client.h33
-rw-r--r--src/global/map_search.c397
-rw-r--r--src/global/map_search.h71
-rw-r--r--src/global/map_search.ref29
-rw-r--r--src/global/maps.c337
-rw-r--r--src/global/maps.h44
-rw-r--r--src/global/maps.in4
-rw-r--r--src/global/maps.ref8
-rw-r--r--src/global/mark_corrupt.c77
-rw-r--r--src/global/mark_corrupt.h35
-rw-r--r--src/global/match_parent_style.c74
-rw-r--r--src/global/match_parent_style.h35
-rw-r--r--src/global/match_service.c176
-rw-r--r--src/global/match_service.h32
-rw-r--r--src/global/mbox_conf.c100
-rw-r--r--src/global/mbox_conf.h41
-rw-r--r--src/global/mbox_open.c256
-rw-r--r--src/global/mbox_open.h50
-rw-r--r--src/global/memcache_proto.c207
-rw-r--r--src/global/memcache_proto.h34
-rw-r--r--src/global/midna_adomain.c119
-rw-r--r--src/global/midna_adomain.h36
-rw-r--r--src/global/mime_8bit.in3
-rw-r--r--src/global/mime_8bit.ref9
-rw-r--r--src/global/mime_cvt.in83
-rw-r--r--src/global/mime_cvt.in283
-rw-r--r--src/global/mime_cvt.in383
-rw-r--r--src/global/mime_cvt.ref93
-rw-r--r--src/global/mime_cvt.ref293
-rw-r--r--src/global/mime_cvt.ref393
-rw-r--r--src/global/mime_dom.in2
-rw-r--r--src/global/mime_dom.ref8
-rw-r--r--src/global/mime_garb1.in27
-rw-r--r--src/global/mime_garb1.ref40
-rw-r--r--src/global/mime_garb2.in27
-rw-r--r--src/global/mime_garb2.ref38
-rw-r--r--src/global/mime_garb3.in29
-rw-r--r--src/global/mime_garb3.ref45
-rw-r--r--src/global/mime_garb4.in34
-rw-r--r--src/global/mime_garb4.ref50
-rw-r--r--src/global/mime_global.in96
-rw-r--r--src/global/mime_nest.in69
-rw-r--r--src/global/mime_nest.ref163
-rw-r--r--src/global/mime_state.c1300
-rw-r--r--src/global/mime_state.h96
-rw-r--r--src/global/mime_test.in38
-rw-r--r--src/global/mime_test.ref52
-rw-r--r--src/global/mime_trunc.in1790
-rw-r--r--src/global/mime_trunc.ref32
-rw-r--r--src/global/mkmap.h64
-rw-r--r--src/global/mkmap_cdb.c65
-rw-r--r--src/global/mkmap_db.c191
-rw-r--r--src/global/mkmap_dbm.c116
-rw-r--r--src/global/mkmap_fail.c53
-rw-r--r--src/global/mkmap_lmdb.c84
-rw-r--r--src/global/mkmap_open.c313
-rw-r--r--src/global/mkmap_proxy.c58
-rw-r--r--src/global/mkmap_sdbm.c113
-rw-r--r--src/global/msg_stats.h104
-rw-r--r--src/global/msg_stats_print.c69
-rw-r--r--src/global/msg_stats_scan.c91
-rw-r--r--src/global/mynetworks.c336
-rw-r--r--src/global/mynetworks.h31
-rw-r--r--src/global/mypwd.c369
-rw-r--r--src/global/mypwd.h45
-rw-r--r--src/global/namadr_list.c141
-rw-r--r--src/global/namadr_list.h40
-rw-r--r--src/global/namadr_list.in42
-rw-r--r--src/global/namadr_list.ref53
-rw-r--r--src/global/normalize_mailhost_addr.c259
-rw-r--r--src/global/normalize_mailhost_addr.h30
-rw-r--r--src/global/off_cvt.c158
-rw-r--r--src/global/off_cvt.h37
-rw-r--r--src/global/off_cvt.in9
-rw-r--r--src/global/off_cvt.ref5
-rw-r--r--src/global/opened.c94
-rw-r--r--src/global/opened.h38
-rw-r--r--src/global/own_inet_addr.c315
-rw-r--r--src/global/own_inet_addr.h39
-rw-r--r--src/global/pipe_command.c683
-rw-r--r--src/global/pipe_command.h94
-rw-r--r--src/global/post_mail.c571
-rw-r--r--src/global/post_mail.h61
-rw-r--r--src/global/qmgr_user.h54
-rw-r--r--src/global/qmqp_proto.h27
-rw-r--r--src/global/quote_821_local.c179
-rw-r--r--src/global/quote_821_local.h37
-rw-r--r--src/global/quote_822_local.c294
-rw-r--r--src/global/quote_822_local.h48
-rw-r--r--src/global/quote_822_local.in6
-rw-r--r--src/global/quote_822_local.ref6
-rw-r--r--src/global/quote_flags.c89
-rw-r--r--src/global/quote_flags.h43
-rw-r--r--src/global/rcpt_buf.c141
-rw-r--r--src/global/rcpt_buf.h67
-rw-r--r--src/global/rcpt_print.c76
-rw-r--r--src/global/rcpt_print.h46
-rw-r--r--src/global/rec2stream.c47
-rw-r--r--src/global/rec_attr_map.c54
-rw-r--r--src/global/rec_attr_map.h30
-rw-r--r--src/global/rec_streamlf.c109
-rw-r--r--src/global/rec_streamlf.h45
-rw-r--r--src/global/rec_type.c87
-rw-r--r--src/global/rec_type.h198
-rw-r--r--src/global/recdump.c57
-rw-r--r--src/global/recipient_list.c191
-rw-r--r--src/global/recipient_list.h76
-rw-r--r--src/global/record.c415
-rw-r--r--src/global/record.h82
-rw-r--r--src/global/reject_deliver_request.c105
-rw-r--r--src/global/remove.c72
-rw-r--r--src/global/resolve_clnt.c408
-rw-r--r--src/global/resolve_clnt.h83
-rw-r--r--src/global/resolve_clnt.in49
-rw-r--r--src/global/resolve_clnt.ref343
-rw-r--r--src/global/resolve_local.c192
-rw-r--r--src/global/resolve_local.h36
-rw-r--r--src/global/resolve_local.in5
-rw-r--r--src/global/resolve_local.ref6
-rw-r--r--src/global/rewrite_clnt.c280
-rw-r--r--src/global/rewrite_clnt.h44
-rw-r--r--src/global/rewrite_clnt.in26
-rw-r--r--src/global/rewrite_clnt.ref104
-rw-r--r--src/global/safe_ultostr.c256
-rw-r--r--src/global/safe_ultostr.h36
-rw-r--r--src/global/safe_ultostr.in4
-rw-r--r--src/global/safe_ultostr.ref4
-rw-r--r--src/global/sasl_mech_filter.c113
-rw-r--r--src/global/sasl_mech_filter.h35
-rw-r--r--src/global/scache.c407
-rw-r--r--src/global/scache.h165
-rw-r--r--src/global/scache_clnt.c443
-rw-r--r--src/global/scache_multi.c493
-rw-r--r--src/global/scache_multi.in52
-rw-r--r--src/global/scache_multi.ref52
-rw-r--r--src/global/scache_single.c312
-rw-r--r--src/global/sent.c176
-rw-r--r--src/global/sent.h45
-rw-r--r--src/global/server_acl.c305
-rw-r--r--src/global/server_acl.h49
-rw-r--r--src/global/server_acl.in10
-rw-r--r--src/global/server_acl.ref18
-rw-r--r--src/global/smtp_reply_footer.c288
-rw-r--r--src/global/smtp_reply_footer.h42
-rw-r--r--src/global/smtp_reply_footer.ref15
-rw-r--r--src/global/smtp_stream.c562
-rw-r--r--src/global/smtp_stream.h74
-rw-r--r--src/global/smtputf8.c95
-rw-r--r--src/global/smtputf8.h113
-rw-r--r--src/global/split_addr.c103
-rw-r--r--src/global/split_addr.h38
-rw-r--r--src/global/stream2rec.c47
-rw-r--r--src/global/string_list.c124
-rw-r--r--src/global/string_list.h40
-rw-r--r--src/global/strip_addr.c252
-rw-r--r--src/global/strip_addr.h36
-rw-r--r--src/global/strip_addr.ref12
-rw-r--r--src/global/surrogate.ref36
-rw-r--r--src/global/sys_exits.c143
-rw-r--r--src/global/sys_exits.h60
-rw-r--r--src/global/test_main.c224
-rw-r--r--src/global/test_main.h64
-rw-r--r--src/global/timed_ipc.c55
-rw-r--r--src/global/timed_ipc.h35
-rw-r--r--src/global/tok822.h123
-rw-r--r--src/global/tok822_find.c69
-rw-r--r--src/global/tok822_limit.in1
-rw-r--r--src/global/tok822_limit.ref91
-rw-r--r--src/global/tok822_node.c79
-rw-r--r--src/global/tok822_parse.c726
-rw-r--r--src/global/tok822_parse.in47
-rw-r--r--src/global/tok822_parse.ref417
-rw-r--r--src/global/tok822_resolve.c74
-rw-r--r--src/global/tok822_rewrite.c68
-rw-r--r--src/global/tok822_tree.c310
-rw-r--r--src/global/trace.c168
-rw-r--r--src/global/trace.h38
-rw-r--r--src/global/user_acl.c118
-rw-r--r--src/global/user_acl.h39
-rw-r--r--src/global/uxtext.c278
-rw-r--r--src/global/uxtext.h40
-rw-r--r--src/global/valid_mailhost_addr.c152
-rw-r--r--src/global/valid_mailhost_addr.h38
-rw-r--r--src/global/verify.c130
-rw-r--r--src/global/verify.h41
-rw-r--r--src/global/verify_clnt.c309
-rw-r--r--src/global/verify_clnt.h54
-rw-r--r--src/global/verify_sender_addr.c341
-rw-r--r--src/global/verify_sender_addr.h31
-rw-r--r--src/global/verify_sender_addr.ref24
-rw-r--r--src/global/verp_sender.c113
-rw-r--r--src/global/verp_sender.h41
-rw-r--r--src/global/wildcard_inet_addr.c68
-rw-r--r--src/global/wildcard_inet_addr.h33
-rw-r--r--src/global/xtext.c196
-rw-r--r--src/global/xtext.h38
l---------src/local/.indent.pro1
-rw-r--r--src/local/.printfck25
-rw-r--r--src/local/Makefile.in686
-rw-r--r--src/local/Musings39
-rw-r--r--src/local/alias.c386
-rw-r--r--src/local/biff_notify.c98
-rw-r--r--src/local/biff_notify.h30
-rw-r--r--src/local/bounce_workaround.c159
-rw-r--r--src/local/command.c251
-rw-r--r--src/local/deliver_attr.c105
-rw-r--r--src/local/dotforward.c304
-rw-r--r--src/local/file.c195
-rw-r--r--src/local/forward.c393
-rw-r--r--src/local/include.c223
-rw-r--r--src/local/indirect.c94
-rw-r--r--src/local/local.c988
-rw-r--r--src/local/local.h251
-rw-r--r--src/local/local_expand.c180
-rw-r--r--src/local/mailbox.c373
-rw-r--r--src/local/maildir.c257
-rw-r--r--src/local/recipient.c307
-rw-r--r--src/local/resolve.c170
-rw-r--r--src/local/token.c222
-rw-r--r--src/local/unknown.c187
l---------src/master/.indent.pro1
-rw-r--r--src/master/.printfck25
-rw-r--r--src/master/Makefile.in477
-rw-r--r--src/master/dgram_server.c665
-rw-r--r--src/master/event_server.c968
-rw-r--r--src/master/mail_flow.c142
-rw-r--r--src/master/mail_flow.h32
-rw-r--r--src/master/mail_server.h155
-rw-r--r--src/master/master.c598
-rw-r--r--src/master/master.h246
-rw-r--r--src/master/master_avail.c251
-rw-r--r--src/master/master_conf.c152
-rw-r--r--src/master/master_ent.c648
-rw-r--r--src/master/master_flow.c33
-rw-r--r--src/master/master_listen.c186
-rw-r--r--src/master/master_monitor.c106
-rw-r--r--src/master/master_proto.c89
-rw-r--r--src/master/master_proto.h75
-rw-r--r--src/master/master_service.c113
-rw-r--r--src/master/master_sig.c275
-rw-r--r--src/master/master_spawn.c371
-rw-r--r--src/master/master_status.c198
-rw-r--r--src/master/master_vars.c98
-rw-r--r--src/master/master_wakeup.c192
-rw-r--r--src/master/master_watch.c151
-rw-r--r--src/master/multi_server.c931
-rw-r--r--src/master/single_server.c822
-rw-r--r--src/master/trigger_server.c809
l---------src/milter/.indent.pro1
-rw-r--r--src/milter/Makefile.in145
-rw-r--r--src/milter/milter.c1121
-rw-r--r--src/milter/milter.h223
-rw-r--r--src/milter/milter8.c2911
-rw-r--r--src/milter/milter_macros.c303
-rw-r--r--src/milter/test-list49
-rw-r--r--src/milter/test-milter.c840
l---------src/oqmgr/.indent.pro1
-rw-r--r--src/oqmgr/.printfck25
-rw-r--r--src/oqmgr/Makefile.in362
-rw-r--r--src/oqmgr/qmgr.c736
-rw-r--r--src/oqmgr/qmgr.h432
-rw-r--r--src/oqmgr/qmgr_active.c607
-rw-r--r--src/oqmgr/qmgr_bounce.c71
-rw-r--r--src/oqmgr/qmgr_defer.c158
-rw-r--r--src/oqmgr/qmgr_deliver.c453
-rw-r--r--src/oqmgr/qmgr_enable.c107
-rw-r--r--src/oqmgr/qmgr_entry.c391
-rw-r--r--src/oqmgr/qmgr_error.c121
-rw-r--r--src/oqmgr/qmgr_feedback.c182
-rw-r--r--src/oqmgr/qmgr_message.c1497
-rw-r--r--src/oqmgr/qmgr_move.c104
-rw-r--r--src/oqmgr/qmgr_queue.c442
-rw-r--r--src/oqmgr/qmgr_scan.c185
-rw-r--r--src/oqmgr/qmgr_transport.c472
l---------src/pickup/.indent.pro1
-rw-r--r--src/pickup/.printfck25
-rw-r--r--src/pickup/Makefile.in94
-rw-r--r--src/pickup/pickup.c631
l---------src/pipe/.indent.pro1
-rw-r--r--src/pipe/.printfck25
-rw-r--r--src/pipe/Makefile.in107
-rw-r--r--src/pipe/pipe.c1397
l---------src/postalias/.indent.pro1
-rw-r--r--src/postalias/.printfck25
-rw-r--r--src/postalias/Makefile.in121
-rw-r--r--src/postalias/aliases1
-rw-r--r--src/postalias/fail_test.in7
-rw-r--r--src/postalias/fail_test.ref7
-rw-r--r--src/postalias/map-abc1.ref1
-rw-r--r--src/postalias/map-abc2.ref1
-rw-r--r--src/postalias/map-ghi1.ref1
-rw-r--r--src/postalias/map-ghi2.ref1
-rw-r--r--src/postalias/map-uABC1.ref1
-rw-r--r--src/postalias/map-uABC2.ref1
-rw-r--r--src/postalias/map.in2
-rw-r--r--src/postalias/postalias.c906
l---------src/postcat/.indent.pro1
-rw-r--r--src/postcat/.printfck25
-rw-r--r--src/postcat/Makefile.in128
-rw-r--r--src/postcat/b_test.ref2
-rw-r--r--src/postcat/bh_test.ref9
-rw-r--r--src/postcat/default_test.ref21
-rw-r--r--src/postcat/e_test.ref12
-rw-r--r--src/postcat/eb_test.ref14
-rw-r--r--src/postcat/eh_test.ref19
-rw-r--r--src/postcat/h_test.ref7
-rw-r--r--src/postcat/postcat.c598
-rw-r--r--src/postcat/test-queue-filebin0 -> 573 bytes
l---------src/postconf/.indent.pro1
-rw-r--r--src/postconf/.printfck25
-rw-r--r--src/postconf/Makefile.in1304
-rw-r--r--src/postconf/extract.awk201
-rw-r--r--src/postconf/extract_cfg.sh92
-rw-r--r--src/postconf/install_table.h2
-rw-r--r--src/postconf/install_vars.h1
-rw-r--r--src/postconf/postconf.c1113
-rw-r--r--src/postconf/postconf.h327
-rw-r--r--src/postconf/postconf_builtin.c469
-rw-r--r--src/postconf/postconf_dbms.c338
-rw-r--r--src/postconf/postconf_edit.c625
-rw-r--r--src/postconf/postconf_lookup.c194
-rw-r--r--src/postconf/postconf_main.c220
-rw-r--r--src/postconf/postconf_master.c1120
-rw-r--r--src/postconf/postconf_match.c188
-rw-r--r--src/postconf/postconf_misc.c60
-rw-r--r--src/postconf/postconf_node.c185
-rw-r--r--src/postconf/postconf_other.c141
-rw-r--r--src/postconf/postconf_print.c114
-rw-r--r--src/postconf/postconf_service.c199
-rw-r--r--src/postconf/postconf_unused.c129
-rw-r--r--src/postconf/postconf_user.c422
-rw-r--r--src/postconf/test1.ref4
-rw-r--r--src/postconf/test10.ref2
-rw-r--r--src/postconf/test11.ref2
-rw-r--r--src/postconf/test12.ref2
-rw-r--r--src/postconf/test13.ref3
-rw-r--r--src/postconf/test14.ref3
-rw-r--r--src/postconf/test15.ref3
-rw-r--r--src/postconf/test16.ref2
-rw-r--r--src/postconf/test17.ref1
-rw-r--r--src/postconf/test18.ref3
-rw-r--r--src/postconf/test19.ref3
-rw-r--r--src/postconf/test2.ref3
-rw-r--r--src/postconf/test20.ref4
-rw-r--r--src/postconf/test21.ref3
-rw-r--r--src/postconf/test22.ref16
-rw-r--r--src/postconf/test23.ref2
-rw-r--r--src/postconf/test24.ref1
-rw-r--r--src/postconf/test25.ref16
-rw-r--r--src/postconf/test26.ref3
-rw-r--r--src/postconf/test27.ref16
-rw-r--r--src/postconf/test28.ref10
-rw-r--r--src/postconf/test29.ref16
-rw-r--r--src/postconf/test3.ref4
-rw-r--r--src/postconf/test30.ref7
-rw-r--r--src/postconf/test31.ref3
-rw-r--r--src/postconf/test32.ref1
-rw-r--r--src/postconf/test33.ref1
-rw-r--r--src/postconf/test34.ref4
-rw-r--r--src/postconf/test35.ref3
-rw-r--r--src/postconf/test36.ref4
-rw-r--r--src/postconf/test37.ref4
-rw-r--r--src/postconf/test38.ref1
-rw-r--r--src/postconf/test39.ref2
-rw-r--r--src/postconf/test4.ref3
-rw-r--r--src/postconf/test40.ref7
-rw-r--r--src/postconf/test41.ref18
-rw-r--r--src/postconf/test42.ref14
-rw-r--r--src/postconf/test43.ref6
-rw-r--r--src/postconf/test44.ref6
-rw-r--r--src/postconf/test45.ref1
-rw-r--r--src/postconf/test46.ref1
-rw-r--r--src/postconf/test47.ref1
-rw-r--r--src/postconf/test48.ref1
-rw-r--r--src/postconf/test49.ref1
-rw-r--r--src/postconf/test4b.ref5
-rw-r--r--src/postconf/test5.ref1
-rw-r--r--src/postconf/test50.ref1
-rw-r--r--src/postconf/test51.ref1
-rw-r--r--src/postconf/test52.ref1
-rw-r--r--src/postconf/test53.ref3
-rw-r--r--src/postconf/test54.ref3
-rw-r--r--src/postconf/test55.ref3
-rw-r--r--src/postconf/test56.ref5
-rw-r--r--src/postconf/test57.ref10
-rw-r--r--src/postconf/test58.ref8
-rw-r--r--src/postconf/test59.ref10
-rw-r--r--src/postconf/test6.ref17
-rw-r--r--src/postconf/test60.ref8
-rw-r--r--src/postconf/test61.ref1
-rw-r--r--src/postconf/test62.ref8
-rw-r--r--src/postconf/test63.ref1
-rw-r--r--src/postconf/test64.ref1
-rw-r--r--src/postconf/test65.ref1
-rw-r--r--src/postconf/test66.ref5
-rw-r--r--src/postconf/test67.ref10
-rw-r--r--src/postconf/test68.ref5
-rw-r--r--src/postconf/test69.ref2
-rw-r--r--src/postconf/test7.ref1
-rw-r--r--src/postconf/test70.ref4
-rw-r--r--src/postconf/test8.ref1
-rw-r--r--src/postconf/test9.ref1
l---------src/postdrop/.indent.pro1
-rw-r--r--src/postdrop/.printfck25
-rw-r--r--src/postdrop/Makefile.in96
-rw-r--r--src/postdrop/postdrop.c628
l---------src/postfix/.indent.pro1
-rw-r--r--src/postfix/.printfck25
-rw-r--r--src/postfix/Makefile.in83
-rw-r--r--src/postfix/postfix.c660
l---------src/postkick/.indent.pro1
-rw-r--r--src/postkick/.printfck25
-rw-r--r--src/postkick/Makefile.in83
-rw-r--r--src/postkick/postkick.c221
l---------src/postlock/.indent.pro1
-rw-r--r--src/postlock/.printfck25
-rw-r--r--src/postlock/Makefile.in86
-rw-r--r--src/postlock/postlock.c282
l---------src/postlog/.indent.pro1
-rw-r--r--src/postlog/.printfck25
-rw-r--r--src/postlog/Makefile.in84
-rw-r--r--src/postlog/postlog.c372
-rw-r--r--src/postlogd/Makefile.in79
-rw-r--r--src/postlogd/postlogd.c267
l---------src/postmap/.indent.pro1
-rw-r--r--src/postmap/.printfck25
-rw-r--r--src/postmap/Makefile.in162
-rw-r--r--src/postmap/aliases1
-rw-r--r--src/postmap/fail_test.in8
-rw-r--r--src/postmap/fail_test.ref8
-rw-r--r--src/postmap/file_test.in17
-rw-r--r--src/postmap/file_test.ref14
-rw-r--r--src/postmap/lmdb_abb3
-rw-r--r--src/postmap/lmdb_abb.ref3
-rw-r--r--src/postmap/map-abc1.ref1
-rw-r--r--src/postmap/map-abc2.ref1
-rw-r--r--src/postmap/map-ghi1.ref1
-rw-r--r--src/postmap/map-ghi2.ref1
-rw-r--r--src/postmap/map-uABC1.ref1
-rw-r--r--src/postmap/map-uABC2.ref1
-rw-r--r--src/postmap/map.in2
-rw-r--r--src/postmap/postmap.c1155
-rw-r--r--src/postmap/quote_test.in8
-rw-r--r--src/postmap/quote_test.ref7
l---------src/postmulti/.indent.pro1
-rw-r--r--src/postmulti/Makefile.in87
-rw-r--r--src/postmulti/postmulti.c1843
l---------src/postqueue/.indent.pro1
-rw-r--r--src/postqueue/Makefile.in133
-rw-r--r--src/postqueue/postqueue.c727
-rw-r--r--src/postqueue/postqueue.h35
-rw-r--r--src/postqueue/showq_compat.c222
-rw-r--r--src/postqueue/showq_json.c221
l---------src/postscreen/.indent.pro1
-rw-r--r--src/postscreen/Makefile.in428
-rw-r--r--src/postscreen/postscreen.c1252
-rw-r--r--src/postscreen/postscreen.h599
-rw-r--r--src/postscreen/postscreen_dict.c182
-rw-r--r--src/postscreen/postscreen_dnsbl.c624
-rw-r--r--src/postscreen/postscreen_early.c377
-rw-r--r--src/postscreen/postscreen_endpt.c232
-rw-r--r--src/postscreen/postscreen_expand.c141
-rw-r--r--src/postscreen/postscreen_haproxy.c137
-rw-r--r--src/postscreen/postscreen_haproxy.h30
-rw-r--r--src/postscreen/postscreen_misc.c164
-rw-r--r--src/postscreen/postscreen_send.c293
-rw-r--r--src/postscreen/postscreen_smtpd.c1339
-rw-r--r--src/postscreen/postscreen_starttls.c317
-rw-r--r--src/postscreen/postscreen_state.c317
-rw-r--r--src/postscreen/postscreen_tests.c341
l---------src/postsuper/.indent.pro1
-rw-r--r--src/postsuper/.printfck25
-rw-r--r--src/postsuper/Makefile.in90
-rw-r--r--src/postsuper/postsuper.c1604
l---------src/posttls-finger/.indent.pro1
-rw-r--r--src/posttls-finger/Makefile.in117
-rw-r--r--src/posttls-finger/posttls-finger.c2165
-rw-r--r--src/posttls-finger/tlsmgrmem.c143
-rw-r--r--src/posttls-finger/tlsmgrmem.h28
l---------src/proxymap/.indent.pro1
-rw-r--r--src/proxymap/Makefile.in85
-rw-r--r--src/proxymap/proxymap.c851
l---------src/qmgr/.indent.pro1
-rw-r--r--src/qmgr/.printfck25
-rw-r--r--src/qmgr/Makefile.in390
-rw-r--r--src/qmgr/qmgr.c827
-rw-r--r--src/qmgr/qmgr.h546
-rw-r--r--src/qmgr/qmgr_active.c607
-rw-r--r--src/qmgr/qmgr_bounce.c71
-rw-r--r--src/qmgr/qmgr_defer.c163
-rw-r--r--src/qmgr/qmgr_deliver.c456
-rw-r--r--src/qmgr/qmgr_enable.c107
-rw-r--r--src/qmgr/qmgr_entry.c452
-rw-r--r--src/qmgr/qmgr_error.c121
-rw-r--r--src/qmgr/qmgr_feedback.c182
-rw-r--r--src/qmgr/qmgr_job.c978
-rw-r--r--src/qmgr/qmgr_message.c1622
-rw-r--r--src/qmgr/qmgr_move.c104
-rw-r--r--src/qmgr/qmgr_peer.c154
-rw-r--r--src/qmgr/qmgr_queue.c439
-rw-r--r--src/qmgr/qmgr_scan.c185
-rw-r--r--src/qmgr/qmgr_transport.c504
l---------src/qmqpd/.indent.pro1
-rw-r--r--src/qmqpd/.printfck25
-rw-r--r--src/qmqpd/Makefile.in139
-rw-r--r--src/qmqpd/qmqpd.c865
-rw-r--r--src/qmqpd/qmqpd.h95
-rw-r--r--src/qmqpd/qmqpd_peer.c309
-rw-r--r--src/qmqpd/qmqpd_state.c99
l---------src/scache/.indent.pro1
-rw-r--r--src/scache/Makefile.in81
-rw-r--r--src/scache/scache.c586
l---------src/sendmail/.indent.pro1
-rw-r--r--src/sendmail/.printfck25
-rw-r--r--src/sendmail/Makefile.in113
-rw-r--r--src/sendmail/sendmail.c1510
l---------src/showq/.indent.pro1
-rw-r--r--src/showq/.printfck25
-rw-r--r--src/showq/Makefile.in95
-rw-r--r--src/showq/showq.c438
l---------src/smtp/.indent.pro1
-rw-r--r--src/smtp/.printfck25
-rw-r--r--src/smtp/Makefile.in878
-rw-r--r--src/smtp/lmtp_params.c136
-rw-r--r--src/smtp/smtp-only4
-rw-r--r--src/smtp/smtp.c1652
-rw-r--r--src/smtp/smtp.h771
-rw-r--r--src/smtp/smtp_addr.c702
-rw-r--r--src/smtp/smtp_addr.h31
-rw-r--r--src/smtp/smtp_chat.c503
-rw-r--r--src/smtp/smtp_connect.c1231
-rw-r--r--src/smtp/smtp_key.c215
-rw-r--r--src/smtp/smtp_map11.c325
-rw-r--r--src/smtp/smtp_map11.in33
-rw-r--r--src/smtp/smtp_map11.ref22
-rw-r--r--src/smtp/smtp_misc.c100
-rw-r--r--src/smtp/smtp_params.c140
-rw-r--r--src/smtp/smtp_proto.c2515
-rw-r--r--src/smtp/smtp_rcpt.c226
-rw-r--r--src/smtp/smtp_reuse.c269
-rw-r--r--src/smtp/smtp_reuse.h27
-rw-r--r--src/smtp/smtp_sasl.h43
-rw-r--r--src/smtp/smtp_sasl_auth_cache.c272
-rw-r--r--src/smtp/smtp_sasl_auth_cache.h62
-rw-r--r--src/smtp/smtp_sasl_glue.c512
-rw-r--r--src/smtp/smtp_sasl_proto.c171
-rw-r--r--src/smtp/smtp_session.c447
-rw-r--r--src/smtp/smtp_state.c124
-rw-r--r--src/smtp/smtp_tls_policy.c927
-rw-r--r--src/smtp/smtp_trouble.c476
-rw-r--r--src/smtp/smtp_unalias.c142
-rw-r--r--src/smtp/tls_policy.in64
-rw-r--r--src/smtp/tls_policy.ref65
l---------src/smtpd/.indent.pro1
-rw-r--r--src/smtpd/.printfck25
-rw-r--r--src/smtpd/Makefile.in679
-rw-r--r--src/smtpd/smtpd.c6776
-rw-r--r--src/smtpd/smtpd.h446
-rw-r--r--src/smtpd/smtpd_acl.in120
-rw-r--r--src/smtpd/smtpd_acl.ref187
-rw-r--r--src/smtpd/smtpd_addr_valid.in35
-rw-r--r--src/smtpd/smtpd_addr_valid.ref57
-rw-r--r--src/smtpd/smtpd_chat.c352
-rw-r--r--src/smtpd/smtpd_chat.h45
-rw-r--r--src/smtpd/smtpd_check.c6445
-rw-r--r--src/smtpd/smtpd_check.h44
-rw-r--r--src/smtpd/smtpd_check.in182
-rw-r--r--src/smtpd/smtpd_check.in2116
-rw-r--r--src/smtpd/smtpd_check.in327
-rw-r--r--src/smtpd/smtpd_check.in419
-rw-r--r--src/smtpd/smtpd_check.ref398
-rw-r--r--src/smtpd/smtpd_check.ref2236
-rw-r--r--src/smtpd/smtpd_check.ref438
-rw-r--r--src/smtpd/smtpd_check_access91
-rw-r--r--src/smtpd/smtpd_check_backup.in20
-rw-r--r--src/smtpd/smtpd_check_backup.ref34
-rw-r--r--src/smtpd/smtpd_check_dsn.in60
-rw-r--r--src/smtpd/smtpd_check_dsn.ref163
-rw-r--r--src/smtpd/smtpd_dns_filter.in83
-rw-r--r--src/smtpd/smtpd_dns_filter.ref163
-rw-r--r--src/smtpd/smtpd_dnswl.in60
-rw-r--r--src/smtpd/smtpd_dnswl.ref94
-rw-r--r--src/smtpd/smtpd_dsn_fix.c149
-rw-r--r--src/smtpd/smtpd_dsn_fix.h44
-rw-r--r--src/smtpd/smtpd_error.in81
-rw-r--r--src/smtpd/smtpd_error.ref135
-rw-r--r--src/smtpd/smtpd_exp.in62
-rw-r--r--src/smtpd/smtpd_exp.ref111
-rw-r--r--src/smtpd/smtpd_expand.c247
-rw-r--r--src/smtpd/smtpd_expand.h40
-rw-r--r--src/smtpd/smtpd_haproxy.c135
-rw-r--r--src/smtpd/smtpd_milter.c229
-rw-r--r--src/smtpd/smtpd_milter.h27
-rw-r--r--src/smtpd/smtpd_nullmx.in58
-rw-r--r--src/smtpd/smtpd_nullmx.ref100
-rw-r--r--src/smtpd/smtpd_peer.c658
-rw-r--r--src/smtpd/smtpd_proxy.c1171
-rw-r--r--src/smtpd/smtpd_proxy.h66
-rw-r--r--src/smtpd/smtpd_resolve.c190
-rw-r--r--src/smtpd/smtpd_resolve.h43
-rw-r--r--src/smtpd/smtpd_sasl_glue.c396
-rw-r--r--src/smtpd/smtpd_sasl_glue.h41
-rw-r--r--src/smtpd/smtpd_sasl_proto.c274
-rw-r--r--src/smtpd/smtpd_sasl_proto.h37
-rw-r--r--src/smtpd/smtpd_server.in56
-rw-r--r--src/smtpd/smtpd_server.ref118
-rw-r--r--src/smtpd/smtpd_state.c248
-rw-r--r--src/smtpd/smtpd_token.c233
-rw-r--r--src/smtpd/smtpd_token.h40
-rw-r--r--src/smtpd/smtpd_token.in12
-rw-r--r--src/smtpd/smtpd_token.ref84
-rw-r--r--src/smtpd/smtpd_xforward.c114
l---------src/smtpstone/.indent.pro1
-rw-r--r--src/smtpstone/.printfck25
-rw-r--r--src/smtpstone/Makefile.in172
-rw-r--r--src/smtpstone/hashed-deferred37
-rw-r--r--src/smtpstone/hashed-incoming48
-rw-r--r--src/smtpstone/mx-deliver20
-rw-r--r--src/smtpstone/mx-explode33
-rw-r--r--src/smtpstone/mx-relay20
-rw-r--r--src/smtpstone/performance28
-rw-r--r--src/smtpstone/qmail-deliver20
-rw-r--r--src/smtpstone/qmail-explode20
-rw-r--r--src/smtpstone/qmail-relay20
-rw-r--r--src/smtpstone/qmqp-sink.c325
-rw-r--r--src/smtpstone/qmqp-source.c673
-rw-r--r--src/smtpstone/smtp-sink.c1643
-rw-r--r--src/smtpstone/smtp-source.c1188
-rw-r--r--src/smtpstone/throughput28
-rw-r--r--src/smtpstone/vmail-local43
-rw-r--r--src/smtpstone/vmail-relay57
l---------src/spawn/.indent.pro1
-rw-r--r--src/spawn/.printfck25
-rw-r--r--src/spawn/Makefile.in82
-rw-r--r--src/spawn/spawn.c373
l---------src/tls/.indent.pro1
-rw-r--r--src/tls/Makefile.in601
-rw-r--r--src/tls/bad-back-to-back-keys.pem64
-rw-r--r--src/tls/bad-back-to-back-keys.pem.ref2
-rw-r--r--src/tls/bad-ec-cert-before-key.pem36
-rw-r--r--src/tls/bad-ec-cert-before-key.pem.ref2
-rw-r--r--src/tls/bad-key-cert-mismatch.pem36
-rw-r--r--src/tls/bad-key-cert-mismatch.pem.ref2
-rw-r--r--src/tls/bad-rsa-key-last.pem64
-rw-r--r--src/tls/bad-rsa-key-last.pem.ref2
-rw-r--r--src/tls/ecca-cert.pem10
-rw-r--r--src/tls/ecca-pkey.pem5
-rw-r--r--src/tls/ecee-cert.pem11
-rw-r--r--src/tls/ecee-pkey.pem5
-rw-r--r--src/tls/ecroot-cert.pem10
-rw-r--r--src/tls/ecroot-pkey.pem5
-rw-r--r--src/tls/good-mixed-keyfirst.pem45
-rw-r--r--src/tls/good-mixed-keyfirst.pem.ref12
-rw-r--r--src/tls/good-mixed-keylast.pem45
-rw-r--r--src/tls/good-mixed-keylast.pem.ref12
-rw-r--r--src/tls/good-mixed-keymiddle.pem45
-rw-r--r--src/tls/good-mixed-keymiddle.pem.ref12
-rw-r--r--src/tls/goodchains.pem121
-rw-r--r--src/tls/goodchains.pem.ref24
-rw-r--r--src/tls/mkcert.sh264
-rw-r--r--src/tls/rsaca-cert.pem19
-rw-r--r--src/tls/rsaca-pkey.pem28
-rw-r--r--src/tls/rsaee-cert.pem20
-rw-r--r--src/tls/rsaee-pkey.pem28
-rw-r--r--src/tls/rsaroot-cert.pem18
-rw-r--r--src/tls/rsaroot-pkey.pem28
-rw-r--r--src/tls/tls.h728
-rw-r--r--src/tls/tls_bio_ops.c296
-rw-r--r--src/tls/tls_certkey.c721
-rw-r--r--src/tls/tls_client.c1250
-rw-r--r--src/tls/tls_dane.c1357
-rw-r--r--src/tls/tls_dane.sh211
-rw-r--r--src/tls/tls_dh.c384
-rw-r--r--src/tls/tls_fprint.c435
-rw-r--r--src/tls/tls_level.c95
-rw-r--r--src/tls/tls_mgr.c486
-rw-r--r--src/tls/tls_mgr.h71
-rw-r--r--src/tls/tls_misc.c1712
-rw-r--r--src/tls/tls_prng.h50
-rw-r--r--src/tls/tls_prng_dev.c155
-rw-r--r--src/tls/tls_prng_egd.c166
-rw-r--r--src/tls/tls_prng_exch.c142
-rw-r--r--src/tls/tls_prng_file.c155
-rw-r--r--src/tls/tls_proxy.h287
-rw-r--r--src/tls/tls_proxy_client_misc.c130
-rw-r--r--src/tls/tls_proxy_client_print.c294
-rw-r--r--src/tls/tls_proxy_client_scan.c496
-rw-r--r--src/tls/tls_proxy_clnt.c300
-rw-r--r--src/tls/tls_proxy_context_print.c114
-rw-r--r--src/tls/tls_proxy_context_scan.c190
-rw-r--r--src/tls/tls_proxy_server_print.c143
-rw-r--r--src/tls/tls_proxy_server_scan.c245
-rw-r--r--src/tls/tls_rsa.c0
-rw-r--r--src/tls/tls_scache.c591
-rw-r--r--src/tls/tls_scache.h73
-rw-r--r--src/tls/tls_seed.c88
-rw-r--r--src/tls/tls_server.c1051
-rw-r--r--src/tls/tls_session.c185
-rw-r--r--src/tls/tls_stream.c160
-rw-r--r--src/tls/tls_verify.c425
-rw-r--r--src/tls/warn-mixed-multi-key.pem51
-rw-r--r--src/tls/warn-mixed-multi-key.pem.ref13
l---------src/tlsmgr/.indent.pro1
-rw-r--r--src/tlsmgr/Makefile.in100
-rw-r--r--src/tlsmgr/tlsmgr.c1115
l---------src/tlsproxy/.indent.pro1
-rw-r--r--src/tlsproxy/Makefile.in117
-rw-r--r--src/tlsproxy/tlsproxy.c1968
-rw-r--r--src/tlsproxy/tlsproxy.h69
-rw-r--r--src/tlsproxy/tlsproxy_state.c169
l---------src/trivial-rewrite/.indent.pro1
-rw-r--r--src/trivial-rewrite/.printfck25
-rw-r--r--src/trivial-rewrite/Makefile.in199
-rw-r--r--src/trivial-rewrite/resolve.c828
-rw-r--r--src/trivial-rewrite/rewrite.c303
-rw-r--r--src/trivial-rewrite/transport.c438
-rw-r--r--src/trivial-rewrite/transport.h51
-rw-r--r--src/trivial-rewrite/transport.in45
-rw-r--r--src/trivial-rewrite/transport.ref22
-rw-r--r--src/trivial-rewrite/trivial-rewrite.c667
-rw-r--r--src/trivial-rewrite/trivial-rewrite.h92
l---------src/util/.indent.pro1
-rw-r--r--src/util/.printfck25
-rw-r--r--src/util/Makefile.in2756
-rw-r--r--src/util/allascii.c65
-rw-r--r--src/util/alldig.c73
-rw-r--r--src/util/allprint.c50
-rw-r--r--src/util/allspace.c50
-rw-r--r--src/util/argv.c326
-rw-r--r--src/util/argv.h69
-rw-r--r--src/util/argv_attr.h43
-rw-r--r--src/util/argv_attr_print.c73
-rw-r--r--src/util/argv_attr_scan.c93
-rw-r--r--src/util/argv_split.c112
-rw-r--r--src/util/argv_split_at.c124
-rw-r--r--src/util/argv_splitq.c118
-rw-r--r--src/util/attr.h184
-rw-r--r--src/util/attr_clnt.c300
-rw-r--r--src/util/attr_clnt.h60
-rw-r--r--src/util/attr_print0.c256
-rw-r--r--src/util/attr_print64.c297
-rw-r--r--src/util/attr_print_plain.c252
-rw-r--r--src/util/attr_scan.ref36
-rw-r--r--src/util/attr_scan0.c596
-rw-r--r--src/util/attr_scan0.ref79
-rw-r--r--src/util/attr_scan64.c665
-rw-r--r--src/util/attr_scan64.ref79
-rw-r--r--src/util/attr_scan_plain.c643
-rw-r--r--src/util/attr_scan_plain.ref79
-rw-r--r--src/util/auto_clnt.c372
-rw-r--r--src/util/auto_clnt.h51
-rw-r--r--src/util/balpar.c56
-rw-r--r--src/util/base32_code.c266
-rw-r--r--src/util/base32_code.h41
-rw-r--r--src/util/base64_code.c228
-rw-r--r--src/util/base64_code.h49
-rw-r--r--src/util/basename.c49
-rw-r--r--src/util/binhash.c447
-rw-r--r--src/util/binhash.h65
-rw-r--r--src/util/byte_mask.c306
-rw-r--r--src/util/byte_mask.h64
-rw-r--r--src/util/byte_mask.in7
-rw-r--r--src/util/byte_mask.ref014
-rw-r--r--src/util/byte_mask.ref122
-rw-r--r--src/util/byte_mask.ref210
-rw-r--r--src/util/cache.in26
-rw-r--r--src/util/casefold.c359
-rw-r--r--src/util/casefold_test.in24
-rw-r--r--src/util/casefold_test.ref47
-rw-r--r--src/util/check_arg.h157
-rw-r--r--src/util/chroot_uid.c88
-rw-r--r--src/util/chroot_uid.h29
-rw-r--r--src/util/cidr_match.c316
-rw-r--r--src/util/cidr_match.h82
-rw-r--r--src/util/clean_env.c146
-rw-r--r--src/util/clean_env.h36
-rw-r--r--src/util/close_on_exec.c60
-rw-r--r--src/util/compat_va_copy.h44
-rw-r--r--src/util/concatenate.c73
-rw-r--r--src/util/connect.h43
-rw-r--r--src/util/ctable.c321
-rw-r--r--src/util/ctable.h41
-rw-r--r--src/util/ctable.in39
-rw-r--r--src/util/ctable.ref99
-rw-r--r--src/util/dict.c664
-rw-r--r--src/util/dict.h340
-rw-r--r--src/util/dict_alloc.c196
-rw-r--r--src/util/dict_cache.c1121
-rw-r--r--src/util/dict_cache.h67
-rw-r--r--src/util/dict_cdb.c446
-rw-r--r--src/util/dict_cdb.h37
-rw-r--r--src/util/dict_cidr.c361
-rw-r--r--src/util/dict_cidr.h43
-rw-r--r--src/util/dict_cidr.in23
-rw-r--r--src/util/dict_cidr.map50
-rw-r--r--src/util/dict_cidr.ref63
-rw-r--r--src/util/dict_cidr_file.in3
-rw-r--r--src/util/dict_cidr_file.map3
-rw-r--r--src/util/dict_cidr_file.ref8
-rw-r--r--src/util/dict_db.c880
-rw-r--r--src/util/dict_db.h55
-rw-r--r--src/util/dict_dbm.c503
-rw-r--r--src/util/dict_dbm.h37
-rw-r--r--src/util/dict_debug.c150
-rw-r--r--src/util/dict_env.c112
-rw-r--r--src/util/dict_env.h37
-rw-r--r--src/util/dict_fail.c115
-rw-r--r--src/util/dict_fail.h35
-rw-r--r--src/util/dict_file.c231
-rw-r--r--src/util/dict_ht.c171
-rw-r--r--src/util/dict_ht.h38
-rw-r--r--src/util/dict_inline.c150
-rw-r--r--src/util/dict_inline.h37
-rw-r--r--src/util/dict_inline.ref24
-rw-r--r--src/util/dict_inline_cidr.ref4
-rw-r--r--src/util/dict_inline_file.ref12
-rw-r--r--src/util/dict_inline_pcre.ref4
-rw-r--r--src/util/dict_inline_regexp.ref4
-rw-r--r--src/util/dict_lmdb.c706
-rw-r--r--src/util/dict_lmdb.h43
-rw-r--r--src/util/dict_ni.c194
-rw-r--r--src/util/dict_ni.h34
-rw-r--r--src/util/dict_nis.c247
-rw-r--r--src/util/dict_nis.h37
-rw-r--r--src/util/dict_nisplus.c304
-rw-r--r--src/util/dict_nisplus.h37
-rw-r--r--src/util/dict_open.c600
-rw-r--r--src/util/dict_pcre.c1120
-rw-r--r--src/util/dict_pcre.h43
-rw-r--r--src/util/dict_pcre.in15
-rw-r--r--src/util/dict_pcre.map28
-rw-r--r--src/util/dict_pcre.ref44
-rw-r--r--src/util/dict_pcre_file.in4
-rw-r--r--src/util/dict_pcre_file.map6
-rw-r--r--src/util/dict_pcre_file.ref12
-rw-r--r--src/util/dict_pipe.c189
-rw-r--r--src/util/dict_pipe.h37
-rw-r--r--src/util/dict_pipe_test.in9
-rw-r--r--src/util/dict_pipe_test.ref14
-rw-r--r--src/util/dict_random.c179
-rw-r--r--src/util/dict_random.h37
-rw-r--r--src/util/dict_random.ref20
-rw-r--r--src/util/dict_random_file.ref10
-rw-r--r--src/util/dict_regexp.c874
-rw-r--r--src/util/dict_regexp.h42
-rw-r--r--src/util/dict_regexp.in17
-rw-r--r--src/util/dict_regexp.map27
-rw-r--r--src/util/dict_regexp.ref46
-rw-r--r--src/util/dict_regexp_file.in3
-rw-r--r--src/util/dict_regexp_file.map3
-rw-r--r--src/util/dict_regexp_file.ref8
-rw-r--r--src/util/dict_sdbm.c483
-rw-r--r--src/util/dict_sdbm.h37
-rw-r--r--src/util/dict_seq.in11
-rw-r--r--src/util/dict_seq.ref22
-rw-r--r--src/util/dict_sockmap.c384
-rw-r--r--src/util/dict_sockmap.h37
-rw-r--r--src/util/dict_static.c151
-rw-r--r--src/util/dict_static.h35
-rw-r--r--src/util/dict_static.ref14
-rw-r--r--src/util/dict_static_file.ref10
-rw-r--r--src/util/dict_stream.c274
-rw-r--r--src/util/dict_stream.ref13
-rw-r--r--src/util/dict_surrogate.c180
-rw-r--r--src/util/dict_tcp.c315
-rw-r--r--src/util/dict_tcp.h37
-rw-r--r--src/util/dict_test.c166
-rw-r--r--src/util/dict_test.in10
-rw-r--r--src/util/dict_test.ref20
-rw-r--r--src/util/dict_thash.c255
-rw-r--r--src/util/dict_thash.h37
-rw-r--r--src/util/dict_thash.in5
-rw-r--r--src/util/dict_thash.map17
-rw-r--r--src/util/dict_thash.ref6
-rw-r--r--src/util/dict_union.c202
-rw-r--r--src/util/dict_union.h37
-rw-r--r--src/util/dict_union_test.in7
-rw-r--r--src/util/dict_union_test.ref10
-rw-r--r--src/util/dict_unix.c204
-rw-r--r--src/util/dict_unix.h37
-rw-r--r--src/util/dict_utf8.c300
-rw-r--r--src/util/dict_utf8_test.in14
-rw-r--r--src/util/dict_utf8_test.ref18
-rw-r--r--src/util/dir_forest.c110
-rw-r--r--src/util/dir_forest.h35
-rw-r--r--src/util/doze.c71
-rw-r--r--src/util/dummy_read.c61
-rw-r--r--src/util/dummy_write.c61
-rw-r--r--src/util/dup2_pass_on_exec.c64
-rw-r--r--src/util/duplex_pipe.c49
-rw-r--r--src/util/edit_file.c353
-rw-r--r--src/util/edit_file.h53
-rw-r--r--src/util/environ.c156
-rw-r--r--src/util/events.c1261
-rw-r--r--src/util/events.h67
-rw-r--r--src/util/exec_command.c112
-rw-r--r--src/util/exec_command.h30
-rw-r--r--src/util/extpar.c109
-rw-r--r--src/util/fifo_listen.c111
-rw-r--r--src/util/fifo_open.c67
-rw-r--r--src/util/fifo_rdonly_bug.c127
-rw-r--r--src/util/fifo_rdwr_bug.c89
-rw-r--r--src/util/fifo_trigger.c168
-rw-r--r--src/util/file_limit.c96
-rw-r--r--src/util/find_inet.c253
-rw-r--r--src/util/find_inet.h33
-rw-r--r--src/util/find_inet.ref5
-rw-r--r--src/util/format_tv.c160
-rw-r--r--src/util/format_tv.h35
-rw-r--r--src/util/format_tv.in68
-rw-r--r--src/util/format_tv.ref120
-rw-r--r--src/util/fsspace.c127
-rw-r--r--src/util/fsspace.h33
-rw-r--r--src/util/fullname.c111
-rw-r--r--src/util/fullname.h29
-rw-r--r--src/util/gccw.c58
-rw-r--r--src/util/gccw.ref18
-rw-r--r--src/util/get_domainname.c66
-rw-r--r--src/util/get_domainname.h29
-rw-r--r--src/util/get_hostname.c84
-rw-r--r--src/util/get_hostname.h29
-rw-r--r--src/util/hash_fnv.c107
-rw-r--r--src/util/hash_fnv.h39
-rw-r--r--src/util/hex_code.c243
-rw-r--r--src/util/hex_code.h54
-rw-r--r--src/util/hex_quote.c153
-rw-r--r--src/util/hex_quote.h36
-rw-r--r--src/util/host_port.c203
-rw-r--r--src/util/host_port.h35
-rw-r--r--src/util/host_port.in16
-rw-r--r--src/util/host_port.ref30
-rw-r--r--src/util/htable.c439
-rw-r--r--src/util/htable.h70
-rw-r--r--src/util/inet_addr_host.c173
-rw-r--r--src/util/inet_addr_host.h35
-rw-r--r--src/util/inet_addr_list.c186
-rw-r--r--src/util/inet_addr_list.h44
-rw-r--r--src/util/inet_addr_list.in9
-rw-r--r--src/util/inet_addr_list.ref15
-rw-r--r--src/util/inet_addr_local.c621
-rw-r--r--src/util/inet_addr_local.h35
-rw-r--r--src/util/inet_connect.c189
-rw-r--r--src/util/inet_listen.c189
-rw-r--r--src/util/inet_proto.c328
-rw-r--r--src/util/inet_proto.h56
-rw-r--r--src/util/inet_trigger.c130
-rw-r--r--src/util/inet_windowsize.c82
-rw-r--r--src/util/iostuff.h73
-rw-r--r--src/util/ip_match.c676
-rw-r--r--src/util/ip_match.h38
-rw-r--r--src/util/ip_match.in26
-rw-r--r--src/util/ip_match.ref69
-rw-r--r--src/util/killme_after.c69
-rw-r--r--src/util/killme_after.h30
-rw-r--r--src/util/known_tcp_ports.c253
-rw-r--r--src/util/known_tcp_ports.h38
-rw-r--r--src/util/known_tcp_ports.ref6
-rw-r--r--src/util/ldseed.c138
-rw-r--r--src/util/ldseed.h30
-rw-r--r--src/util/line_number.c71
-rw-r--r--src/util/line_number.h35
-rw-r--r--src/util/line_wrap.c122
-rw-r--r--src/util/line_wrap.h31
-rw-r--r--src/util/listen.h53
-rw-r--r--src/util/lmdb_cache_test_1.sh55
-rw-r--r--src/util/lmdb_cache_test_2.sh42
-rw-r--r--src/util/load_file.c80
-rw-r--r--src/util/load_file.h32
-rw-r--r--src/util/load_lib.c146
-rw-r--r--src/util/load_lib.h46
-rw-r--r--src/util/logwriter.c124
-rw-r--r--src/util/logwriter.h38
-rw-r--r--src/util/lowercase.c43
-rw-r--r--src/util/lstat_as.c73
-rw-r--r--src/util/lstat_as.h35
-rw-r--r--src/util/mac_expand.c848
-rw-r--r--src/util/mac_expand.h81
-rw-r--r--src/util/mac_expand.in105
-rw-r--r--src/util/mac_expand.ref222
-rw-r--r--src/util/mac_parse.c199
-rw-r--r--src/util/mac_parse.h51
-rw-r--r--src/util/make_dirs.c173
-rw-r--r--src/util/make_dirs.h30
-rw-r--r--src/util/mask_addr.c68
-rw-r--r--src/util/mask_addr.h30
-rw-r--r--src/util/match_list.c281
-rw-r--r--src/util/match_list.h66
-rw-r--r--src/util/match_ops.c312
-rw-r--r--src/util/midna_domain.c419
-rw-r--r--src/util/midna_domain.h43
-rw-r--r--src/util/midna_domain_test.in21
-rw-r--r--src/util/midna_domain_test.ref89
-rw-r--r--src/util/miss_endif_cidr.map1
-rw-r--r--src/util/miss_endif_cidr.ref4
-rw-r--r--src/util/miss_endif_pcre.ref4
-rw-r--r--src/util/miss_endif_re.map1
-rw-r--r--src/util/miss_endif_regexp.ref4
-rw-r--r--src/util/msg.c340
-rw-r--r--src/util/msg.h60
-rw-r--r--src/util/msg_logger.c371
-rw-r--r--src/util/msg_logger.h62
-rw-r--r--src/util/msg_output.c174
-rw-r--r--src/util/msg_output.h51
-rw-r--r--src/util/msg_rate_delay.c138
-rw-r--r--src/util/msg_syslog.c266
-rw-r--r--src/util/msg_syslog.h41
-rw-r--r--src/util/msg_vstream.c87
-rw-r--r--src/util/msg_vstream.h34
-rw-r--r--src/util/mvect.c117
-rw-r--r--src/util/mvect.h42
-rw-r--r--src/util/myaddrinfo.c905
-rw-r--r--src/util/myaddrinfo.h229
-rw-r--r--src/util/myaddrinfo.ref8
-rw-r--r--src/util/myaddrinfo.ref25
-rw-r--r--src/util/myaddrinfo4.ref6
-rw-r--r--src/util/myaddrinfo4.ref25
-rw-r--r--src/util/myflock.c152
-rw-r--r--src/util/myflock.h52
-rw-r--r--src/util/mymalloc.c269
-rw-r--r--src/util/mymalloc.h40
-rw-r--r--src/util/myrand.c63
-rw-r--r--src/util/myrand.h35
-rw-r--r--src/util/mystrtok.c258
-rw-r--r--src/util/mystrtok.ref30
-rw-r--r--src/util/name_code.c91
-rw-r--r--src/util/name_code.h39
-rw-r--r--src/util/name_mask.c511
-rw-r--r--src/util/name_mask.h86
-rw-r--r--src/util/name_mask.in9
-rw-r--r--src/util/name_mask.ref09
-rw-r--r--src/util/name_mask.ref112
-rw-r--r--src/util/name_mask.ref212
-rw-r--r--src/util/name_mask.ref314
-rw-r--r--src/util/name_mask.ref414
-rw-r--r--src/util/name_mask.ref514
-rw-r--r--src/util/name_mask.ref614
-rw-r--r--src/util/name_mask.ref712
-rw-r--r--src/util/name_mask.ref812
-rw-r--r--src/util/name_mask.ref912
-rw-r--r--src/util/nbbio.c382
-rw-r--r--src/util/nbbio.h85
-rw-r--r--src/util/netstring.c498
-rw-r--r--src/util/netstring.h55
-rw-r--r--src/util/neuter.c56
-rw-r--r--src/util/non_blocking.c66
-rw-r--r--src/util/nvtable.c122
-rw-r--r--src/util/nvtable.h44
-rw-r--r--src/util/open_as.c70
-rw-r--r--src/util/open_as.h30
-rw-r--r--src/util/open_limit.c100
-rw-r--r--src/util/open_lock.c76
-rw-r--r--src/util/open_lock.h41
-rw-r--r--src/util/pass_accept.c106
-rw-r--r--src/util/pass_trigger.c151
-rw-r--r--src/util/peekfd.c89
-rw-r--r--src/util/poll_fd.c269
-rw-r--r--src/util/posix_signals.c126
-rw-r--r--src/util/posix_signals.h59
-rw-r--r--src/util/printable.c100
-rw-r--r--src/util/rand_sleep.c89
-rw-r--r--src/util/readlline.c138
-rw-r--r--src/util/readlline.h38
-rw-r--r--src/util/recv_pass_attr.c98
-rw-r--r--src/util/ring.c121
-rw-r--r--src/util/ring.h59
-rw-r--r--src/util/safe.h30
-rw-r--r--src/util/safe_getenv.c41
-rw-r--r--src/util/safe_open.c283
-rw-r--r--src/util/safe_open.h42
-rw-r--r--src/util/sane_accept.c125
-rw-r--r--src/util/sane_accept.h29
-rw-r--r--src/util/sane_basename.c181
-rw-r--r--src/util/sane_basename.in18
-rw-r--r--src/util/sane_basename.ref18
-rw-r--r--src/util/sane_connect.c65
-rw-r--r--src/util/sane_connect.h29
-rw-r--r--src/util/sane_fsops.h35
-rw-r--r--src/util/sane_link.c72
-rw-r--r--src/util/sane_rename.c69
-rw-r--r--src/util/sane_socketpair.c71
-rw-r--r--src/util/sane_socketpair.h34
-rw-r--r--src/util/sane_strtol.c59
-rw-r--r--src/util/sane_strtol.h26
-rw-r--r--src/util/sane_time.c127
-rw-r--r--src/util/sane_time.h34
-rw-r--r--src/util/scan_dir.c216
-rw-r--r--src/util/scan_dir.h37
-rw-r--r--src/util/select_bug.c95
-rw-r--r--src/util/set_eugid.c70
-rw-r--r--src/util/set_eugid.h43
-rw-r--r--src/util/set_ugid.c61
-rw-r--r--src/util/set_ugid.h29
-rw-r--r--src/util/sigdelay.c118
-rw-r--r--src/util/sigdelay.h31
-rw-r--r--src/util/skipblanks.c43
-rw-r--r--src/util/slmdb.c938
-rw-r--r--src/util/slmdb.h117
-rw-r--r--src/util/sock_addr.c173
-rw-r--r--src/util/sock_addr.h105
-rw-r--r--src/util/spawn_command.c313
-rw-r--r--src/util/spawn_command.h66
-rw-r--r--src/util/split_at.c71
-rw-r--r--src/util/split_at.h35
-rw-r--r--src/util/split_nameval.c97
-rw-r--r--src/util/split_qnameval.c168
-rw-r--r--src/util/stat_as.c73
-rw-r--r--src/util/stat_as.h35
-rw-r--r--src/util/strcasecmp.c66
-rw-r--r--src/util/strcasecmp_utf8.c216
-rw-r--r--src/util/strcasecmp_utf8_test.in10
-rw-r--r--src/util/strcasecmp_utf8_test.ref20
-rw-r--r--src/util/stream_connect.c109
-rw-r--r--src/util/stream_listen.c102
-rw-r--r--src/util/stream_recv_fd.c120
-rw-r--r--src/util/stream_send_fd.c115
-rw-r--r--src/util/stream_test.c111
-rw-r--r--src/util/stream_trigger.c130
-rw-r--r--src/util/stringops.h107
-rw-r--r--src/util/surrogate.ref55
-rw-r--r--src/util/sys_compat.c389
-rw-r--r--src/util/sys_defs.h1797
-rw-r--r--src/util/testdb2
-rw-r--r--src/util/timecmp.c93
-rw-r--r--src/util/timecmp.h37
-rw-r--r--src/util/timed_connect.c111
-rw-r--r--src/util/timed_connect.h31
-rw-r--r--src/util/timed_read.c86
-rw-r--r--src/util/timed_wait.c122
-rw-r--r--src/util/timed_wait.h35
-rw-r--r--src/util/timed_write.c92
-rw-r--r--src/util/translit.c86
-rw-r--r--src/util/trigger.h34
-rw-r--r--src/util/trimblanks.c50
-rw-r--r--src/util/unescape.c208
-rw-r--r--src/util/unescape.in4
-rw-r--r--src/util/unescape.ref11
-rw-r--r--src/util/unix_connect.c110
-rw-r--r--src/util/unix_dgram_connect.c91
-rw-r--r--src/util/unix_dgram_listen.c93
-rw-r--r--src/util/unix_listen.c113
-rw-r--r--src/util/unix_pass_fd_fix.c67
-rw-r--r--src/util/unix_recv_fd.c178
-rw-r--r--src/util/unix_send_fd.c193
-rw-r--r--src/util/unix_trigger.c131
-rw-r--r--src/util/unsafe.c77
-rw-r--r--src/util/uppercase.c43
-rw-r--r--src/util/username.c47
-rw-r--r--src/util/username.h29
-rw-r--r--src/util/valid_hostname.c412
-rw-r--r--src/util/valid_hostname.h41
-rw-r--r--src/util/valid_hostname.in55
-rw-r--r--src/util/valid_hostname.ref143
-rw-r--r--src/util/valid_utf8_hostname.c87
-rw-r--r--src/util/valid_utf8_hostname.h35
-rw-r--r--src/util/valid_utf8_string.c139
-rw-r--r--src/util/vbuf.c238
-rw-r--r--src/util/vbuf.h110
-rw-r--r--src/util/vbuf_print.c385
-rw-r--r--src/util/vbuf_print.h40
-rw-r--r--src/util/vbuf_print_test.in33
-rw-r--r--src/util/vbuf_print_test.ref27
-rw-r--r--src/util/vstream.c2046
-rw-r--r--src/util/vstream.h293
-rw-r--r--src/util/vstream_popen.c363
-rw-r--r--src/util/vstream_test.in3
-rw-r--r--src/util/vstream_test.ref41
-rw-r--r--src/util/vstream_tweak.c168
-rw-r--r--src/util/vstring.c719
-rw-r--r--src/util/vstring.h125
-rw-r--r--src/util/vstring_test.ref6
-rw-r--r--src/util/vstring_vstream.c267
-rw-r--r--src/util/vstring_vstream.h82
-rw-r--r--src/util/warn_stat.c101
-rw-r--r--src/util/warn_stat.h38
-rw-r--r--src/util/watchdog.c314
-rw-r--r--src/util/watchdog.h36
-rw-r--r--src/util/write_buf.c89
l---------src/verify/.indent.pro1
-rw-r--r--src/verify/Makefile.in98
-rw-r--r--src/verify/verify.c776
l---------src/virtual/.indent.pro1
-rw-r--r--src/virtual/.printfck25
-rw-r--r--src/virtual/Makefile.in234
-rw-r--r--src/virtual/deliver_attr.c90
-rw-r--r--src/virtual/mailbox.c283
-rw-r--r--src/virtual/maildir.c254
-rw-r--r--src/virtual/recipient.c94
-rw-r--r--src/virtual/unknown.c65
-rw-r--r--src/virtual/virtual.c573
-rw-r--r--src/virtual/virtual.h150
l---------src/xsasl/.indent.pro1
-rw-r--r--src/xsasl/Makefile.in165
-rw-r--r--src/xsasl/README109
-rw-r--r--src/xsasl/xsasl.h146
-rw-r--r--src/xsasl/xsasl_client.c240
-rw-r--r--src/xsasl/xsasl_cyrus.h47
-rw-r--r--src/xsasl/xsasl_cyrus_client.c585
-rw-r--r--src/xsasl/xsasl_cyrus_common.h39
-rw-r--r--src/xsasl/xsasl_cyrus_log.c104
-rw-r--r--src/xsasl/xsasl_cyrus_security.c87
-rw-r--r--src/xsasl/xsasl_cyrus_server.c640
-rw-r--r--src/xsasl/xsasl_dovecot.h41
-rw-r--r--src/xsasl/xsasl_dovecot_server.c747
-rw-r--r--src/xsasl/xsasl_server.c270
1727 files changed, 293499 insertions, 0 deletions
diff --git a/src/anvil/.indent.pro b/src/anvil/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/anvil/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/anvil/.printfck b/src/anvil/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/anvil/.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/anvil/Makefile.in b/src/anvil/Makefile.in
new file mode 100644
index 0000000..2caa0a9
--- /dev/null
+++ b/src/anvil/Makefile.in
@@ -0,0 +1,82 @@
+SHELL = /bin/sh
+SRCS = anvil.c
+OBJS = anvil.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = anvil
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+anvil.o: ../../include/anvil_clnt.h
+anvil.o: ../../include/attr.h
+anvil.o: ../../include/attr_clnt.h
+anvil.o: ../../include/check_arg.h
+anvil.o: ../../include/events.h
+anvil.o: ../../include/htable.h
+anvil.o: ../../include/iostuff.h
+anvil.o: ../../include/mail_conf.h
+anvil.o: ../../include/mail_params.h
+anvil.o: ../../include/mail_proto.h
+anvil.o: ../../include/mail_server.h
+anvil.o: ../../include/mail_version.h
+anvil.o: ../../include/msg.h
+anvil.o: ../../include/mymalloc.h
+anvil.o: ../../include/nvtable.h
+anvil.o: ../../include/stringops.h
+anvil.o: ../../include/sys_defs.h
+anvil.o: ../../include/vbuf.h
+anvil.o: ../../include/vstream.h
+anvil.o: ../../include/vstring.h
+anvil.o: anvil.c
diff --git a/src/anvil/anvil.c b/src/anvil/anvil.c
new file mode 100644
index 0000000..884be28
--- /dev/null
+++ b/src/anvil/anvil.c
@@ -0,0 +1,1047 @@
+/*++
+/* NAME
+/* anvil 8
+/* SUMMARY
+/* Postfix session count and request rate control
+/* SYNOPSIS
+/* \fBanvil\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix \fBanvil\fR(8) server maintains statistics about
+/* client connection counts or client request rates. This
+/* information can be used to defend against clients that
+/* hammer a server with either too many simultaneous sessions,
+/* or with too many successive requests within a configurable
+/* time interval. This server is designed to run under control
+/* by the Postfix \fBmaster\fR(8) server.
+/*
+/* In the following text, \fBident\fR specifies a (service,
+/* client) combination. The exact syntax of that information
+/* is application-dependent; the \fBanvil\fR(8) server does
+/* not care.
+/* CONNECTION COUNT/RATE CONTROL
+/* .ad
+/* .fi
+/* To register a new connection send the following request to
+/* the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=connect\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of
+/* simultaneous connections and the number of connections per
+/* unit time for the (service, client) combination specified
+/* with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBcount=\fInumber\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/*
+/* To register a disconnect event send the following request
+/* to the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=disconnect\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server replies with:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* .fi
+/* MESSAGE RATE CONTROL
+/* .ad
+/* .fi
+/* To register a message delivery request send the following
+/* request to the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=message\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of message
+/* delivery requests per unit time for the (service, client)
+/* combination specified with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/* RECIPIENT RATE CONTROL
+/* .ad
+/* .fi
+/* To register a recipient request send the following request
+/* to the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=recipient\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of recipient
+/* addresses per unit time for the (service, client) combination
+/* specified with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/* TLS SESSION NEGOTIATION RATE CONTROL
+/* .ad
+/* .fi
+/* The features described in this section are available with
+/* Postfix 2.3 and later.
+/*
+/* To register a request for a new (i.e. not cached) TLS session
+/* send the following request to the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=newtls\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of new
+/* TLS session requests per unit time for the (service, client)
+/* combination specified with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/*
+/* To retrieve new TLS session request rate information without
+/* updating the counter information, send:
+/*
+/* .nf
+/* \fBrequest=newtls_report\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of new
+/* TLS session requests per unit time for the (service, client)
+/* combination specified with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/* AUTH RATE CONTROL
+/* .ad
+/* .fi
+/* To register an AUTH request send the following request
+/* to the \fBanvil\fR(8) server:
+/*
+/* .nf
+/* \fBrequest=auth\fR
+/* \fBident=\fIstring\fR
+/* .fi
+/*
+/* The \fBanvil\fR(8) server answers with the number of auth
+/* requests per unit time for the (service, client) combination
+/* specified with \fBident\fR:
+/*
+/* .nf
+/* \fBstatus=0\fR
+/* \fBrate=\fInumber\fR
+/* .fi
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBanvil\fR(8) server does not talk to the network or to local
+/* users, and can run chrooted at fixed low privilege.
+/*
+/* The \fBanvil\fR(8) server maintains an in-memory table with
+/* information about recent clients requests. No persistent
+/* state is kept because standard system library routines are
+/* not sufficiently robust for update-intensive applications.
+/*
+/* Although the in-memory state is kept only temporarily, this
+/* may require a lot of memory on systems that handle connections
+/* from many remote clients. To reduce memory usage, reduce
+/* the time unit over which state is kept.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/*
+/* Upon exit, and every \fBanvil_status_update_time\fR
+/* seconds, the server logs the maximal count and rate values measured,
+/* together with (service, client) information and the time of day
+/* associated with those events.
+/* In order to avoid unnecessary overhead, no measurements
+/* are done for activity that isn't concurrency limited or
+/* rate limited.
+/* BUGS
+/* Systems behind network address translating routers or proxies
+/* appear to have the same client address and can run into connection
+/* count and/or rate limits falsely.
+/*
+/* In this preliminary implementation, a count (or rate) limited server
+/* process can have only one remote client at a time. If a
+/* server process reports
+/* multiple simultaneous clients, state is kept only for the last
+/* reported client.
+/*
+/* The \fBanvil\fR(8) server automatically discards client
+/* request information after it expires. To prevent the
+/* \fBanvil\fR(8) server from discarding client request rate
+/* information too early or too late, a rate limited service
+/* should always register connect/disconnect events even when
+/* it does not explicitly limit them.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* On low-traffic mail systems, changes to \fBmain.cf\fR are
+/* picked up automatically as \fBanvil\fR(8) processes run for
+/* only a limited amount of time. On other mail systems, 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.
+/* .IP "\fBanvil_rate_time_unit (60s)\fR"
+/* The time unit over which client connection rates and other rates
+/* are calculated.
+/* .IP "\fBanvil_status_update_time (600s)\fR"
+/* How frequently the \fBanvil\fR(8) connection and rate limiting server
+/* logs peak usage information.
+/* .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 "\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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtpd(8), Postfix SMTP server
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* TUNING_README, performance tuning
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The anvil service is available in Postfix 2.2 and later.
+/* 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/time.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <stringops.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <anvil_clnt.h>
+
+/* Server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * Configuration parameters.
+ */
+int var_anvil_time_unit;
+int var_anvil_stat_time;
+
+ /*
+ * Global dynamic state.
+ */
+static HTABLE *anvil_remote_map; /* indexed by service+ remote client */
+
+ /*
+ * Remote connection state, one instance for each (service, client) pair.
+ */
+typedef struct {
+ char *ident; /* lookup key */
+ int count; /* connection count */
+ int rate; /* connection rate */
+ int mail; /* message rate */
+ int rcpt; /* recipient rate */
+ int ntls; /* new TLS session rate */
+ int auth; /* AUTH request rate */
+ time_t start; /* time of first rate sample */
+} ANVIL_REMOTE;
+
+ /*
+ * Local server state, one instance per anvil client connection. This allows
+ * us to clean up remote connection state when a local server goes away
+ * without cleaning up.
+ */
+typedef struct {
+ ANVIL_REMOTE *anvil_remote; /* XXX should be list */
+} ANVIL_LOCAL;
+
+ /*
+ * The following operations are implemented as macros with recognizable
+ * names so that we don't lose sight of what the code is trying to do.
+ *
+ * Related operations are defined side by side so that the code implementing
+ * them isn't pages apart.
+ */
+
+/* Create new (service, client) state. */
+
+#define ANVIL_REMOTE_FIRST_CONN(remote, id) \
+ do { \
+ (remote)->ident = mystrdup(id); \
+ (remote)->count = 1; \
+ (remote)->rate = 1; \
+ (remote)->mail = 0; \
+ (remote)->rcpt = 0; \
+ (remote)->ntls = 0; \
+ (remote)->auth = 0; \
+ (remote)->start = event_time(); \
+ } while(0)
+
+/* Destroy unused (service, client) state. */
+
+#define ANVIL_REMOTE_FREE(remote) \
+ do { \
+ myfree((remote)->ident); \
+ myfree((void *) (remote)); \
+ } while(0)
+
+/* Reset or update rate information for existing (service, client) state. */
+
+#define ANVIL_REMOTE_RSET_RATE(remote, _start) \
+ do { \
+ (remote)->rate = 0; \
+ (remote)->mail = 0; \
+ (remote)->rcpt = 0; \
+ (remote)->ntls = 0; \
+ (remote)->auth = 0; \
+ (remote)->start = _start; \
+ } while(0)
+
+#define ANVIL_REMOTE_INCR_RATE(remote, _what) \
+ do { \
+ time_t _now = event_time(); \
+ if ((remote)->start + var_anvil_time_unit < _now) \
+ ANVIL_REMOTE_RSET_RATE((remote), _now); \
+ if ((remote)->_what < INT_MAX) \
+ (remote)->_what += 1; \
+ } while(0)
+
+/* Update existing (service, client) state. */
+
+#define ANVIL_REMOTE_NEXT_CONN(remote) \
+ do { \
+ ANVIL_REMOTE_INCR_RATE((remote), rate); \
+ if ((remote)->count == 0) \
+ event_cancel_timer(anvil_remote_expire, (void *) remote); \
+ (remote)->count++; \
+ } while(0)
+
+#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail)
+
+#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt)
+
+#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls)
+
+#define ANVIL_REMOTE_INCR_AUTH(remote) ANVIL_REMOTE_INCR_RATE((remote), auth)
+
+/* Drop connection from (service, client) state. */
+
+#define ANVIL_REMOTE_DROP_ONE(remote) \
+ do { \
+ if ((remote) && (remote)->count > 0) { \
+ if (--(remote)->count == 0) \
+ event_request_timer(anvil_remote_expire, (void *) remote, \
+ var_anvil_time_unit); \
+ } \
+ } while(0)
+
+/* Create local server state. */
+
+#define ANVIL_LOCAL_INIT(local) \
+ do { \
+ (local)->anvil_remote = 0; \
+ } while(0)
+
+/* Add remote connection to local server. */
+
+#define ANVIL_LOCAL_ADD_ONE(local, remote) \
+ do { \
+ /* XXX allow multiple remote clients per local server. */ \
+ if ((local)->anvil_remote) \
+ ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \
+ (local)->anvil_remote = (remote); \
+ } while(0)
+
+/* Test if this remote connection is listed for this local server. */
+
+#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
+ ((local)->anvil_remote == (remote))
+
+/* Drop specific remote connection from local server. */
+
+#define ANVIL_LOCAL_DROP_ONE(local, remote) \
+ do { \
+ /* XXX allow multiple remote clients per local server. */ \
+ if ((local)->anvil_remote == (remote)) \
+ (local)->anvil_remote = 0; \
+ } while(0)
+
+/* Drop all remote connections from local server. */
+
+#define ANVIL_LOCAL_DROP_ALL(stream, local) \
+ do { \
+ /* XXX allow multiple remote clients per local server. */ \
+ if ((local)->anvil_remote) \
+ anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \
+ } while (0)
+
+ /*
+ * Lookup table to map request names to action routines.
+ */
+typedef struct {
+ const char *name;
+ void (*action) (VSTREAM *, const char *);
+} ANVIL_REQ_TABLE;
+
+ /*
+ * Run-time statistics for maximal connection counts and event rates. These
+ * store the peak resource usage, remote connection, and time. Absent a
+ * query interface, this information is logged at process exit time and at
+ * configurable intervals.
+ */
+typedef struct {
+ int value; /* peak value */
+ char *ident; /* lookup key */
+ time_t when; /* time of peak value */
+} ANVIL_MAX;
+
+static ANVIL_MAX max_conn_count; /* peak connection count */
+static ANVIL_MAX max_conn_rate; /* peak connection rate */
+static ANVIL_MAX max_mail_rate; /* peak message rate */
+static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */
+static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */
+static ANVIL_MAX max_auth_rate; /* peak AUTH request rate */
+
+static int max_cache_size; /* peak cache size */
+static time_t max_cache_time; /* time of peak size */
+
+/* Update/report peak usage. */
+
+#define ANVIL_MAX_UPDATE(_max, _value, _ident) \
+ do { \
+ _max.value = _value; \
+ if (_max.ident == 0) { \
+ _max.ident = mystrdup(_ident); \
+ } else if (!STREQ(_max.ident, _ident)) { \
+ myfree(_max.ident); \
+ _max.ident = mystrdup(_ident); \
+ } \
+ _max.when = event_time(); \
+ } while (0)
+
+#define ANVIL_MAX_RATE_REPORT(_max, _name) \
+ do { \
+ if (_max.value > 0) { \
+ msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \
+ _max.value, var_anvil_time_unit, \
+ _max.ident, ctime(&_max.when) + 4); \
+ _max.value = 0; \
+ } \
+ } while (0);
+
+#define ANVIL_MAX_COUNT_REPORT(_max, _name) \
+ do { \
+ if (_max.value > 0) { \
+ msg_info("statistics: max " _name " count %d for (%s) at %.15s", \
+ _max.value, _max.ident, ctime(&_max.when) + 4); \
+ _max.value = 0; \
+ } \
+ } while (0);
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+/* anvil_remote_expire - purge expired connection state */
+
+static void anvil_remote_expire(int unused_event, void *context)
+{
+ ANVIL_REMOTE *anvil_remote = (ANVIL_REMOTE *) context;
+ const char *myname = "anvil_remote_expire";
+
+ if (msg_verbose)
+ msg_info("%s %s", myname, anvil_remote->ident);
+
+ if (anvil_remote->count != 0)
+ msg_panic("%s: bad connection count: %d",
+ myname, anvil_remote->count);
+
+ htable_delete(anvil_remote_map, anvil_remote->ident,
+ (void (*) (void *)) 0);
+ ANVIL_REMOTE_FREE(anvil_remote);
+
+ if (msg_verbose)
+ msg_info("%s: anvil_remote_map used=%ld",
+ myname, (long) anvil_remote_map->used);
+}
+
+/* anvil_remote_lookup - dump address status */
+
+static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+ const char *myname = "anvil_remote_lookup";
+
+ if (msg_verbose)
+ msg_info("%s fd=%d stream=0x%lx ident=%s",
+ myname, vstream_fileno(client_stream),
+ (unsigned long) client_stream, ident);
+
+ /*
+ * Look up remote client information.
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_COUNT, 0),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, 0),
+ SEND_ATTR_INT(ANVIL_ATTR_MAIL, 0),
+ SEND_ATTR_INT(ANVIL_ATTR_RCPT, 0),
+ SEND_ATTR_INT(ANVIL_ATTR_NTLS, 0),
+ SEND_ATTR_INT(ANVIL_ATTR_AUTH, 0),
+ ATTR_TYPE_END);
+ } else {
+
+ /*
+ * Do not report stale information.
+ */
+ if (anvil_remote->start != 0
+ && anvil_remote->start + var_anvil_time_unit < event_time())
+ ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate),
+ SEND_ATTR_INT(ANVIL_ATTR_MAIL, anvil_remote->mail),
+ SEND_ATTR_INT(ANVIL_ATTR_RCPT, anvil_remote->rcpt),
+ SEND_ATTR_INT(ANVIL_ATTR_NTLS, anvil_remote->ntls),
+ SEND_ATTR_INT(ANVIL_ATTR_AUTH, anvil_remote->auth),
+ ATTR_TYPE_END);
+ }
+}
+
+/* anvil_remote_conn_update - instantiate or update connection info */
+
+static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+ ANVIL_LOCAL *anvil_local;
+ const char *myname = "anvil_remote_conn_update";
+
+ if (msg_verbose)
+ msg_info("%s fd=%d stream=0x%lx ident=%s",
+ myname, vstream_fileno(client_stream),
+ (unsigned long) client_stream, ident);
+
+ /*
+ * Look up remote connection count information. Update remote connection
+ * rate information. Simply reset the counter every var_anvil_time_unit
+ * seconds. This is easier than maintaining a moving average and it gives
+ * a quicker response to tresspassers.
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
+ anvil_remote = (ANVIL_REMOTE *) mymalloc(sizeof(*anvil_remote));
+ ANVIL_REMOTE_FIRST_CONN(anvil_remote, ident);
+ htable_enter(anvil_remote_map, ident, (void *) anvil_remote);
+ if (max_cache_size < anvil_remote_map->used) {
+ max_cache_size = anvil_remote_map->used;
+ max_cache_time = event_time();
+ }
+ } else {
+ ANVIL_REMOTE_NEXT_CONN(anvil_remote);
+ }
+
+ /*
+ * Record this connection under the local server information, so that we
+ * can clean up all its connection state when the local server goes away.
+ */
+ if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) {
+ anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local));
+ ANVIL_LOCAL_INIT(anvil_local);
+ vstream_control(client_stream,
+ CA_VSTREAM_CTL_CONTEXT((void *) anvil_local),
+ CA_VSTREAM_CTL_END);
+ }
+ ANVIL_LOCAL_ADD_ONE(anvil_local, anvil_remote);
+ if (msg_verbose)
+ msg_info("%s: anvil_local 0x%lx",
+ myname, (unsigned long) anvil_local);
+
+ return (anvil_remote);
+}
+
+/* anvil_remote_connect - report connection event, query address status */
+
+static void anvil_remote_connect(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+
+ /*
+ * Update or instantiate connection info.
+ */
+ anvil_remote = anvil_remote_conn_update(client_stream, ident);
+
+ /*
+ * Respond to the local server.
+ */
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate),
+ ATTR_TYPE_END);
+
+ /*
+ * Update peak statistics.
+ */
+ if (anvil_remote->rate > max_conn_rate.value)
+ ANVIL_MAX_UPDATE(max_conn_rate, anvil_remote->rate, anvil_remote->ident);
+ if (anvil_remote->count > max_conn_count.value)
+ ANVIL_MAX_UPDATE(max_conn_count, anvil_remote->count, anvil_remote->ident);
+}
+
+/* anvil_remote_mail - register message delivery request */
+
+static void anvil_remote_mail(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+
+ /*
+ * Be prepared for "postfix reload" after "connect".
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
+ anvil_remote = anvil_remote_conn_update(client_stream, ident);
+
+ /*
+ * Update message delivery request rate and respond to local server.
+ */
+ ANVIL_REMOTE_INCR_MAIL(anvil_remote);
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->mail),
+ ATTR_TYPE_END);
+
+ /*
+ * Update peak statistics.
+ */
+ if (anvil_remote->mail > max_mail_rate.value)
+ ANVIL_MAX_UPDATE(max_mail_rate, anvil_remote->mail, anvil_remote->ident);
+}
+
+/* anvil_remote_rcpt - register recipient address event */
+
+static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+
+ /*
+ * Be prepared for "postfix reload" after "connect".
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
+ anvil_remote = anvil_remote_conn_update(client_stream, ident);
+
+ /*
+ * Update recipient address rate and respond to local server.
+ */
+ ANVIL_REMOTE_INCR_RCPT(anvil_remote);
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rcpt),
+ ATTR_TYPE_END);
+
+ /*
+ * Update peak statistics.
+ */
+ if (anvil_remote->rcpt > max_rcpt_rate.value)
+ ANVIL_MAX_UPDATE(max_rcpt_rate, anvil_remote->rcpt, anvil_remote->ident);
+}
+
+/* anvil_remote_auth - register auth request event */
+
+static void anvil_remote_auth(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+
+ /*
+ * Be prepared for "postfix reload" after "connect".
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
+ anvil_remote = anvil_remote_conn_update(client_stream, ident);
+
+ /*
+ * Update recipient address rate and respond to local server.
+ */
+ ANVIL_REMOTE_INCR_AUTH(anvil_remote);
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->auth),
+ ATTR_TYPE_END);
+
+ /*
+ * Update peak statistics.
+ */
+ if (anvil_remote->auth > max_auth_rate.value)
+ ANVIL_MAX_UPDATE(max_auth_rate, anvil_remote->auth, anvil_remote->ident);
+}
+
+/* anvil_remote_newtls - register newtls event */
+
+static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+
+ /*
+ * Be prepared for "postfix reload" after "connect".
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
+ anvil_remote = anvil_remote_conn_update(client_stream, ident);
+
+ /*
+ * Update newtls rate and respond to local server.
+ */
+ ANVIL_REMOTE_INCR_NTLS(anvil_remote);
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->ntls),
+ ATTR_TYPE_END);
+
+ /*
+ * Update peak statistics.
+ */
+ if (anvil_remote->ntls > max_ntls_rate.value)
+ ANVIL_MAX_UPDATE(max_ntls_rate, anvil_remote->ntls, anvil_remote->ident);
+}
+
+/* anvil_remote_newtls_stat - report newtls stats */
+
+static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+ int rate;
+
+ /*
+ * Be prepared for "postfix reload" after "connect".
+ */
+ if ((anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
+ rate = 0;
+ }
+
+ /*
+ * Do not report stale information.
+ */
+ else {
+ if (anvil_remote->start != 0
+ && anvil_remote->start + var_anvil_time_unit < event_time())
+ ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
+ rate = anvil_remote->ntls;
+ }
+
+ /*
+ * Respond to local server.
+ */
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ SEND_ATTR_INT(ANVIL_ATTR_RATE, rate),
+ ATTR_TYPE_END);
+}
+
+/* anvil_remote_disconnect - report disconnect event */
+
+static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident)
+{
+ ANVIL_REMOTE *anvil_remote;
+ ANVIL_LOCAL *anvil_local;
+ const char *myname = "anvil_remote_disconnect";
+
+ if (msg_verbose)
+ msg_info("%s fd=%d stream=0x%lx ident=%s",
+ myname, vstream_fileno(client_stream),
+ (unsigned long) client_stream, ident);
+
+ /*
+ * Update local and remote info if this remote connection is listed for
+ * this local server.
+ */
+ if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0
+ && (anvil_remote =
+ (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) != 0
+ && ANVIL_LOCAL_REMOTE_LINKED(anvil_local, anvil_remote)) {
+ ANVIL_REMOTE_DROP_ONE(anvil_remote);
+ ANVIL_LOCAL_DROP_ONE(anvil_local, anvil_remote);
+ }
+ if (msg_verbose)
+ msg_info("%s: anvil_local 0x%lx",
+ myname, (unsigned long) anvil_local);
+
+ /*
+ * Respond to the local server.
+ */
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK),
+ ATTR_TYPE_END);
+}
+
+/* anvil_service_done - clean up */
+
+static void anvil_service_done(VSTREAM *client_stream, char *unused_service,
+ char **unused_argv)
+{
+ ANVIL_LOCAL *anvil_local;
+ const char *myname = "anvil_service_done";
+
+ if (msg_verbose)
+ msg_info("%s fd=%d stream=0x%lx",
+ myname, vstream_fileno(client_stream),
+ (unsigned long) client_stream);
+
+ /*
+ * Look up the local server, and get rid of any remote connection state
+ * that we still have for this local server. Do not destroy remote client
+ * status information before it expires.
+ */
+ if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: anvil_local 0x%lx",
+ myname, (unsigned long) anvil_local);
+ ANVIL_LOCAL_DROP_ALL(client_stream, anvil_local);
+ myfree((void *) anvil_local);
+ } else if (msg_verbose)
+ msg_info("client socket not found for fd=%d",
+ vstream_fileno(client_stream));
+}
+
+/* anvil_status_dump - log and reset extreme usage */
+
+static void anvil_status_dump(char *unused_name, char **unused_argv)
+{
+ ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection");
+ ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection");
+ ANVIL_MAX_RATE_REPORT(max_mail_rate, "message");
+ ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient");
+ ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls");
+ ANVIL_MAX_RATE_REPORT(max_auth_rate, "auth");
+
+ if (max_cache_size > 0) {
+ msg_info("statistics: max cache size %d at %.15s",
+ max_cache_size, ctime(&max_cache_time) + 4);
+ max_cache_size = 0;
+ }
+}
+
+/* anvil_status_update - log and reset extreme usage periodically */
+
+static void anvil_status_update(int unused_event, void *context)
+{
+ anvil_status_dump((char *) 0, (char **) 0);
+ event_request_timer(anvil_status_update, context, var_anvil_stat_time);
+}
+
+/* anvil_service - perform service for client */
+
+static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv)
+{
+ static VSTRING *request;
+ static VSTRING *ident;
+ static const ANVIL_REQ_TABLE request_table[] = {
+ ANVIL_REQ_CONN, anvil_remote_connect,
+ ANVIL_REQ_MAIL, anvil_remote_mail,
+ ANVIL_REQ_RCPT, anvil_remote_rcpt,
+ ANVIL_REQ_NTLS, anvil_remote_newtls,
+ ANVIL_REQ_DISC, anvil_remote_disconnect,
+ ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat,
+ ANVIL_REQ_AUTH, anvil_remote_auth,
+ ANVIL_REQ_LOOKUP, anvil_remote_lookup,
+ 0, 0,
+ };
+ const ANVIL_REQ_TABLE *rp;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Initialize.
+ */
+ if (request == 0) {
+ request = vstring_alloc(10);
+ ident = vstring_alloc(10);
+ }
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the client connection rate management service. All
+ * connection-management stuff is handled by the common code in
+ * multi_server.c.
+ */
+ if (msg_verbose)
+ msg_info("--- start request ---");
+ if (attr_scan_plain(client_stream,
+ ATTR_FLAG_MISSING | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(ANVIL_ATTR_REQ, request),
+ RECV_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END) == 2) {
+ for (rp = request_table; /* see below */ ; rp++) {
+ if (rp->name == 0) {
+ msg_warn("unrecognized request: \"%s\", ignored", STR(request));
+ attr_print_plain(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_FAIL),
+ ATTR_TYPE_END);
+ break;
+ }
+ if (STREQ(rp->name, STR(request))) {
+ rp->action(client_stream, STR(ident));
+ break;
+ }
+ }
+ vstream_fflush(client_stream);
+ } else {
+ /* Note: invokes anvil_service_done() */
+ multi_server_disconnect(client_stream);
+ }
+ if (msg_verbose)
+ msg_info("--- end request ---");
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Dump and reset extreme usage every so often.
+ */
+ event_request_timer(anvil_status_update, (void *) 0, var_anvil_stat_time);
+
+ /*
+ * Initial client state tables.
+ */
+ anvil_remote_map = htable_create(1000);
+
+ /*
+ * Do not limit the number of client requests.
+ */
+ var_use_limit = 0;
+
+ /*
+ * Don't exit before the sampling interval ends.
+ */
+ if (var_idle_limit < var_anvil_time_unit)
+ var_idle_limit = var_anvil_time_unit;
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* post_accept - announce our protocol */
+
+static void post_accept(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print_plain(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_ANVIL),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_ANVIL_TIME_UNIT, DEF_ANVIL_TIME_UNIT, &var_anvil_time_unit, 1, 0,
+ VAR_ANVIL_STAT_TIME, DEF_ANVIL_STAT_TIME, &var_anvil_stat_time, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, anvil_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_POST_ACCEPT(post_accept),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_PRE_DISCONN(anvil_service_done),
+ CA_MAIL_SERVER_EXIT(anvil_status_dump),
+ 0);
+}
diff --git a/src/bounce/.indent.pro b/src/bounce/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/bounce/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/bounce/.printfck b/src/bounce/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/bounce/.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/bounce/2template_test.in b/src/bounce/2template_test.in
new file mode 100644
index 0000000..e45fd32
--- /dev/null
+++ b/src/bounce/2template_test.in
@@ -0,0 +1,136 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/Makefile.in b/src/bounce/Makefile.in
new file mode 100644
index 0000000..969413a
--- /dev/null
+++ b/src/bounce/Makefile.in
@@ -0,0 +1,695 @@
+SHELL = /bin/sh
+SRCS = bounce.c bounce_append_service.c bounce_notify_service.c \
+ bounce_cleanup.c bounce_notify_util.c bounce_notify_verp.c \
+ bounce_one_service.c bounce_warn_service.c bounce_trace_service.c \
+ bounce_template.c bounce_templates.c
+OBJS = bounce.o bounce_append_service.o bounce_notify_service.o \
+ bounce_cleanup.o bounce_notify_util.o bounce_notify_verp.o \
+ bounce_one_service.o bounce_warn_service.o bounce_trace_service.o \
+ bounce_template.o bounce_templates.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= bounce_notify_util_tester
+PROG = bounce
+SAMPLES = ../../conf/bounce.cf.default
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG) ../../conf/bounce.cf.default
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+# Eliminate dependency on installed Postfix.
+../../conf/bounce.cf.default: template_test.ref annotate.sh
+ rm -f $@
+ ./annotate.sh <template_test.ref >$@
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: update template_test obs_template_test 2template_test \
+ with-msgid-with-long-line_test \
+ with-msgid-with-eoh-event_test \
+ with-msgid-no-eoh-event_test \
+ no-msgid-with-eoh-event_test \
+ no-msgid-no-eoh-event_test \
+ with-msgid-with-filter_test
+
+root_tests:
+
+update: ../../libexec/$(PROG) $(SAMPLES)
+
+../../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 main.cf
+ rm -rf printfck
+
+tidy: clean
+
+BOUNCE_NOTIFY_UTIL_TESTER_OBJS = bounce_notify_util_tester.o \
+ bounce_notify_util.o bounce_template.o bounce_templates.o
+
+bounce_notify_util_tester: $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) $(LIBS)
+ $(CC) -DTEST $(CFLAGS) -o $@ $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) \
+ $(LIBS) $(SYSLIBS)
+
+# Avoid dependency on installed Postfix.
+# XXX This still requires that default_privs, mail_owner etc. accounts exist.
+template_test: $(PROG) template_test.ref
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ echo header_from_format=standard >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp
+ diff template_test.ref template_test.tmp
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \
+ -o bounce_template_file=template_test.ref > template_test.tmp
+ diff template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+obs_template_test: $(PROG) obs_template_test.ref
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ echo header_from_format=obsolete >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp
+ diff obs_template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+2template_test: $(PROG) template_test.ref 2template_test.in
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \
+ -o bounce_template_file=2template_test.in > template_test.tmp
+ diff template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+with-msgid-with-long-line_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-long-line logfile-with-msgid-with-long-line \
+ with-msgid-with-long-line-no-thread.ref \
+ with-msgid-with-long-line-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-long-line queue/bounce/msgid
+ cp msgfile-with-msgid-with-long-line queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-long-line-no-thread.tmp
+ diff with-msgid-with-long-line-no-thread.ref with-msgid-with-long-line-no-thread.tmp
+ rm -f with-msgid-with-long-line-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-long-line queue/bounce/msgid
+ cp msgfile-with-msgid-with-long-line queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-long-line-with-thread.tmp
+ diff with-msgid-with-long-line-with-thread.ref with-msgid-with-long-line-with-thread.tmp
+ rm -f with-msgid-with-long-line-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-with-eoh-event_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-eoh-event logfile-with-msgid-with-eoh-event \
+ with-msgid-with-eoh-event-no-thread.ref \
+ with-msgid-with-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-eoh-event-no-thread.tmp
+ diff with-msgid-with-eoh-event-no-thread.ref with-msgid-with-eoh-event-no-thread.tmp
+ rm -f with-msgid-with-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-eoh-event-with-thread.tmp
+ diff with-msgid-with-eoh-event-with-thread.ref with-msgid-with-eoh-event-with-thread.tmp
+ rm -f with-msgid-with-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-no-eoh-event_test: bounce_notify_util_tester \
+ msgfile-with-msgid-no-eoh-event logfile-with-msgid-no-eoh-event \
+ with-msgid-no-eoh-event-no-thread.ref \
+ with-msgid-no-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-no-eoh-event-no-thread.tmp
+ diff with-msgid-no-eoh-event-no-thread.ref with-msgid-no-eoh-event-no-thread.tmp
+ rm -f with-msgid-no-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-no-eoh-event-with-thread.tmp
+ diff with-msgid-no-eoh-event-with-thread.ref with-msgid-no-eoh-event-with-thread.tmp
+ rm -f with-msgid-no-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+no-msgid-with-eoh-event_test: bounce_notify_util_tester \
+ msgfile-no-msgid-with-eoh-event logfile-no-msgid-with-eoh-event \
+ no-msgid-with-eoh-event-no-thread.ref \
+ no-msgid-with-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-with-eoh-event-no-thread.tmp
+ diff no-msgid-with-eoh-event-no-thread.ref no-msgid-with-eoh-event-no-thread.tmp
+ rm -f no-msgid-with-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-with-eoh-event-with-thread.tmp
+ diff no-msgid-with-eoh-event-with-thread.ref no-msgid-with-eoh-event-with-thread.tmp
+ rm -f no-msgid-with-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+no-msgid-no-eoh-event_test: bounce_notify_util_tester \
+ msgfile-no-msgid-no-eoh-event logfile-no-msgid-no-eoh-event \
+ no-msgid-no-eoh-event-no-thread.ref \
+ no-msgid-no-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-no-eoh-event-no-thread.tmp
+ diff no-msgid-no-eoh-event-no-thread.ref no-msgid-no-eoh-event-no-thread.tmp
+ rm -f no-msgid-no-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-no-eoh-event-with-thread.tmp
+ diff no-msgid-no-eoh-event-with-thread.ref no-msgid-no-eoh-event-with-thread.tmp
+ rm -f no-msgid-no-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-with-filter_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-filter logfile-with-msgid-with-filter \
+ with-msgid-with-filter-no-thread.ref \
+ with-msgid-with-filter-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-filter queue/bounce/msgid
+ cp msgfile-with-msgid-with-filter queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-filter-no-thread.tmp
+ diff with-msgid-with-filter-no-thread.ref with-msgid-with-filter-no-thread.tmp
+ rm -f with-msgid-with-filter-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-filter queue/bounce/msgid
+ cp msgfile-with-msgid-with-filter queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-filter-with-thread.tmp
+ diff with-msgid-with-filter-with-thread.ref with-msgid-with-filter-with-thread.tmp
+ rm -f with-msgid-with-filter-with-thread.tmp
+ rm -rf queue main.cf
+
+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'
+bounce.o: ../../include/attr.h
+bounce.o: ../../include/bounce.h
+bounce.o: ../../include/bounce_log.h
+bounce.o: ../../include/check_arg.h
+bounce.o: ../../include/deliver_request.h
+bounce.o: ../../include/dsb_scan.h
+bounce.o: ../../include/dsn.h
+bounce.o: ../../include/dsn_buf.h
+bounce.o: ../../include/hfrom_format.h
+bounce.o: ../../include/htable.h
+bounce.o: ../../include/iostuff.h
+bounce.o: ../../include/load_file.h
+bounce.o: ../../include/mail_addr.h
+bounce.o: ../../include/mail_conf.h
+bounce.o: ../../include/mail_params.h
+bounce.o: ../../include/mail_proto.h
+bounce.o: ../../include/mail_queue.h
+bounce.o: ../../include/mail_server.h
+bounce.o: ../../include/mail_version.h
+bounce.o: ../../include/msg.h
+bounce.o: ../../include/msg_stats.h
+bounce.o: ../../include/mymalloc.h
+bounce.o: ../../include/nvtable.h
+bounce.o: ../../include/rcpt_buf.h
+bounce.o: ../../include/recipient_list.h
+bounce.o: ../../include/stringops.h
+bounce.o: ../../include/sys_defs.h
+bounce.o: ../../include/vbuf.h
+bounce.o: ../../include/vstream.h
+bounce.o: ../../include/vstring.h
+bounce.o: bounce.c
+bounce.o: bounce_service.h
+bounce.o: bounce_template.h
+bounce_append_service.o: ../../include/attr.h
+bounce_append_service.o: ../../include/bounce_log.h
+bounce_append_service.o: ../../include/check_arg.h
+bounce_append_service.o: ../../include/deliver_flock.h
+bounce_append_service.o: ../../include/dsn.h
+bounce_append_service.o: ../../include/dsn_buf.h
+bounce_append_service.o: ../../include/htable.h
+bounce_append_service.o: ../../include/iostuff.h
+bounce_append_service.o: ../../include/mail_params.h
+bounce_append_service.o: ../../include/mail_proto.h
+bounce_append_service.o: ../../include/mail_queue.h
+bounce_append_service.o: ../../include/msg.h
+bounce_append_service.o: ../../include/myflock.h
+bounce_append_service.o: ../../include/mymalloc.h
+bounce_append_service.o: ../../include/nvtable.h
+bounce_append_service.o: ../../include/quote_822_local.h
+bounce_append_service.o: ../../include/quote_flags.h
+bounce_append_service.o: ../../include/rcpt_buf.h
+bounce_append_service.o: ../../include/recipient_list.h
+bounce_append_service.o: ../../include/stringops.h
+bounce_append_service.o: ../../include/sys_defs.h
+bounce_append_service.o: ../../include/vbuf.h
+bounce_append_service.o: ../../include/vstream.h
+bounce_append_service.o: ../../include/vstring.h
+bounce_append_service.o: bounce_append_service.c
+bounce_append_service.o: bounce_service.h
+bounce_append_service.o: bounce_template.h
+bounce_cleanup.o: ../../include/attr.h
+bounce_cleanup.o: ../../include/bounce_log.h
+bounce_cleanup.o: ../../include/check_arg.h
+bounce_cleanup.o: ../../include/dsn.h
+bounce_cleanup.o: ../../include/dsn_buf.h
+bounce_cleanup.o: ../../include/htable.h
+bounce_cleanup.o: ../../include/mail_queue.h
+bounce_cleanup.o: ../../include/msg.h
+bounce_cleanup.o: ../../include/mymalloc.h
+bounce_cleanup.o: ../../include/nvtable.h
+bounce_cleanup.o: ../../include/rcpt_buf.h
+bounce_cleanup.o: ../../include/recipient_list.h
+bounce_cleanup.o: ../../include/sys_defs.h
+bounce_cleanup.o: ../../include/vbuf.h
+bounce_cleanup.o: ../../include/vstream.h
+bounce_cleanup.o: ../../include/vstring.h
+bounce_cleanup.o: bounce_cleanup.c
+bounce_cleanup.o: bounce_service.h
+bounce_cleanup.o: bounce_template.h
+bounce_notify_service.o: ../../include/attr.h
+bounce_notify_service.o: ../../include/bounce.h
+bounce_notify_service.o: ../../include/bounce_log.h
+bounce_notify_service.o: ../../include/check_arg.h
+bounce_notify_service.o: ../../include/cleanup_user.h
+bounce_notify_service.o: ../../include/deliver_request.h
+bounce_notify_service.o: ../../include/dsn.h
+bounce_notify_service.o: ../../include/dsn_buf.h
+bounce_notify_service.o: ../../include/dsn_mask.h
+bounce_notify_service.o: ../../include/htable.h
+bounce_notify_service.o: ../../include/int_filt.h
+bounce_notify_service.o: ../../include/iostuff.h
+bounce_notify_service.o: ../../include/mail_addr.h
+bounce_notify_service.o: ../../include/mail_error.h
+bounce_notify_service.o: ../../include/mail_params.h
+bounce_notify_service.o: ../../include/mail_proto.h
+bounce_notify_service.o: ../../include/mail_queue.h
+bounce_notify_service.o: ../../include/msg.h
+bounce_notify_service.o: ../../include/msg_stats.h
+bounce_notify_service.o: ../../include/mymalloc.h
+bounce_notify_service.o: ../../include/name_mask.h
+bounce_notify_service.o: ../../include/nvtable.h
+bounce_notify_service.o: ../../include/post_mail.h
+bounce_notify_service.o: ../../include/rcpt_buf.h
+bounce_notify_service.o: ../../include/rec_type.h
+bounce_notify_service.o: ../../include/recipient_list.h
+bounce_notify_service.o: ../../include/smtputf8.h
+bounce_notify_service.o: ../../include/stringops.h
+bounce_notify_service.o: ../../include/sys_defs.h
+bounce_notify_service.o: ../../include/vbuf.h
+bounce_notify_service.o: ../../include/vstream.h
+bounce_notify_service.o: ../../include/vstring.h
+bounce_notify_service.o: bounce_notify_service.c
+bounce_notify_service.o: bounce_service.h
+bounce_notify_service.o: bounce_template.h
+bounce_notify_util.o: ../../include/attr.h
+bounce_notify_util.o: ../../include/bounce_log.h
+bounce_notify_util.o: ../../include/check_arg.h
+bounce_notify_util.o: ../../include/cleanup_user.h
+bounce_notify_util.o: ../../include/deliver_completed.h
+bounce_notify_util.o: ../../include/dsn.h
+bounce_notify_util.o: ../../include/dsn_buf.h
+bounce_notify_util.o: ../../include/dsn_mask.h
+bounce_notify_util.o: ../../include/events.h
+bounce_notify_util.o: ../../include/header_opts.h
+bounce_notify_util.o: ../../include/htable.h
+bounce_notify_util.o: ../../include/int_filt.h
+bounce_notify_util.o: ../../include/iostuff.h
+bounce_notify_util.o: ../../include/is_header.h
+bounce_notify_util.o: ../../include/lex_822.h
+bounce_notify_util.o: ../../include/line_wrap.h
+bounce_notify_util.o: ../../include/mail_addr.h
+bounce_notify_util.o: ../../include/mail_date.h
+bounce_notify_util.o: ../../include/mail_error.h
+bounce_notify_util.o: ../../include/mail_params.h
+bounce_notify_util.o: ../../include/mail_proto.h
+bounce_notify_util.o: ../../include/mail_queue.h
+bounce_notify_util.o: ../../include/msg.h
+bounce_notify_util.o: ../../include/myflock.h
+bounce_notify_util.o: ../../include/mymalloc.h
+bounce_notify_util.o: ../../include/name_mask.h
+bounce_notify_util.o: ../../include/nvtable.h
+bounce_notify_util.o: ../../include/post_mail.h
+bounce_notify_util.o: ../../include/quote_822_local.h
+bounce_notify_util.o: ../../include/quote_flags.h
+bounce_notify_util.o: ../../include/rcpt_buf.h
+bounce_notify_util.o: ../../include/rec_type.h
+bounce_notify_util.o: ../../include/recipient_list.h
+bounce_notify_util.o: ../../include/record.h
+bounce_notify_util.o: ../../include/smtputf8.h
+bounce_notify_util.o: ../../include/stringops.h
+bounce_notify_util.o: ../../include/sys_defs.h
+bounce_notify_util.o: ../../include/vbuf.h
+bounce_notify_util.o: ../../include/vstream.h
+bounce_notify_util.o: ../../include/vstring.h
+bounce_notify_util.o: bounce_notify_util.c
+bounce_notify_util.o: bounce_service.h
+bounce_notify_util.o: bounce_template.h
+bounce_notify_util_tester.o: ../../include/attr.h
+bounce_notify_util_tester.o: ../../include/bounce_log.h
+bounce_notify_util_tester.o: ../../include/check_arg.h
+bounce_notify_util_tester.o: ../../include/dsn.h
+bounce_notify_util_tester.o: ../../include/dsn_buf.h
+bounce_notify_util_tester.o: ../../include/dsn_mask.h
+bounce_notify_util_tester.o: ../../include/hfrom_format.h
+bounce_notify_util_tester.o: ../../include/htable.h
+bounce_notify_util_tester.o: ../../include/mail_conf.h
+bounce_notify_util_tester.o: ../../include/mail_params.h
+bounce_notify_util_tester.o: ../../include/msg.h
+bounce_notify_util_tester.o: ../../include/mymalloc.h
+bounce_notify_util_tester.o: ../../include/nvtable.h
+bounce_notify_util_tester.o: ../../include/rcpt_buf.h
+bounce_notify_util_tester.o: ../../include/rec_type.h
+bounce_notify_util_tester.o: ../../include/recipient_list.h
+bounce_notify_util_tester.o: ../../include/record.h
+bounce_notify_util_tester.o: ../../include/sys_defs.h
+bounce_notify_util_tester.o: ../../include/test_main.h
+bounce_notify_util_tester.o: ../../include/vbuf.h
+bounce_notify_util_tester.o: ../../include/vstream.h
+bounce_notify_util_tester.o: ../../include/vstring.h
+bounce_notify_util_tester.o: bounce_notify_util_tester.c
+bounce_notify_util_tester.o: bounce_service.h
+bounce_notify_util_tester.o: bounce_template.h
+bounce_notify_verp.o: ../../include/attr.h
+bounce_notify_verp.o: ../../include/bounce.h
+bounce_notify_verp.o: ../../include/bounce_log.h
+bounce_notify_verp.o: ../../include/check_arg.h
+bounce_notify_verp.o: ../../include/cleanup_user.h
+bounce_notify_verp.o: ../../include/deliver_request.h
+bounce_notify_verp.o: ../../include/dsn.h
+bounce_notify_verp.o: ../../include/dsn_buf.h
+bounce_notify_verp.o: ../../include/dsn_mask.h
+bounce_notify_verp.o: ../../include/htable.h
+bounce_notify_verp.o: ../../include/int_filt.h
+bounce_notify_verp.o: ../../include/iostuff.h
+bounce_notify_verp.o: ../../include/mail_addr.h
+bounce_notify_verp.o: ../../include/mail_error.h
+bounce_notify_verp.o: ../../include/mail_params.h
+bounce_notify_verp.o: ../../include/mail_proto.h
+bounce_notify_verp.o: ../../include/mail_queue.h
+bounce_notify_verp.o: ../../include/msg.h
+bounce_notify_verp.o: ../../include/msg_stats.h
+bounce_notify_verp.o: ../../include/mymalloc.h
+bounce_notify_verp.o: ../../include/name_mask.h
+bounce_notify_verp.o: ../../include/nvtable.h
+bounce_notify_verp.o: ../../include/post_mail.h
+bounce_notify_verp.o: ../../include/rcpt_buf.h
+bounce_notify_verp.o: ../../include/rec_type.h
+bounce_notify_verp.o: ../../include/recipient_list.h
+bounce_notify_verp.o: ../../include/smtputf8.h
+bounce_notify_verp.o: ../../include/stringops.h
+bounce_notify_verp.o: ../../include/sys_defs.h
+bounce_notify_verp.o: ../../include/vbuf.h
+bounce_notify_verp.o: ../../include/verp_sender.h
+bounce_notify_verp.o: ../../include/vstream.h
+bounce_notify_verp.o: ../../include/vstring.h
+bounce_notify_verp.o: bounce_notify_verp.c
+bounce_notify_verp.o: bounce_service.h
+bounce_notify_verp.o: bounce_template.h
+bounce_one_service.o: ../../include/attr.h
+bounce_one_service.o: ../../include/bounce.h
+bounce_one_service.o: ../../include/bounce_log.h
+bounce_one_service.o: ../../include/check_arg.h
+bounce_one_service.o: ../../include/cleanup_user.h
+bounce_one_service.o: ../../include/deliver_request.h
+bounce_one_service.o: ../../include/dsn.h
+bounce_one_service.o: ../../include/dsn_buf.h
+bounce_one_service.o: ../../include/dsn_mask.h
+bounce_one_service.o: ../../include/htable.h
+bounce_one_service.o: ../../include/int_filt.h
+bounce_one_service.o: ../../include/iostuff.h
+bounce_one_service.o: ../../include/mail_addr.h
+bounce_one_service.o: ../../include/mail_error.h
+bounce_one_service.o: ../../include/mail_params.h
+bounce_one_service.o: ../../include/mail_proto.h
+bounce_one_service.o: ../../include/msg.h
+bounce_one_service.o: ../../include/msg_stats.h
+bounce_one_service.o: ../../include/mymalloc.h
+bounce_one_service.o: ../../include/name_mask.h
+bounce_one_service.o: ../../include/nvtable.h
+bounce_one_service.o: ../../include/post_mail.h
+bounce_one_service.o: ../../include/rcpt_buf.h
+bounce_one_service.o: ../../include/rec_type.h
+bounce_one_service.o: ../../include/recipient_list.h
+bounce_one_service.o: ../../include/smtputf8.h
+bounce_one_service.o: ../../include/stringops.h
+bounce_one_service.o: ../../include/sys_defs.h
+bounce_one_service.o: ../../include/vbuf.h
+bounce_one_service.o: ../../include/vstream.h
+bounce_one_service.o: ../../include/vstring.h
+bounce_one_service.o: bounce_one_service.c
+bounce_one_service.o: bounce_service.h
+bounce_one_service.o: bounce_template.h
+bounce_template.o: ../../include/attr.h
+bounce_template.o: ../../include/bounce_log.h
+bounce_template.o: ../../include/check_arg.h
+bounce_template.o: ../../include/dsn.h
+bounce_template.o: ../../include/dsn_buf.h
+bounce_template.o: ../../include/hfrom_format.h
+bounce_template.o: ../../include/htable.h
+bounce_template.o: ../../include/iostuff.h
+bounce_template.o: ../../include/is_header.h
+bounce_template.o: ../../include/mac_expand.h
+bounce_template.o: ../../include/mac_parse.h
+bounce_template.o: ../../include/mail_conf.h
+bounce_template.o: ../../include/mail_params.h
+bounce_template.o: ../../include/mail_proto.h
+bounce_template.o: ../../include/midna_domain.h
+bounce_template.o: ../../include/msg.h
+bounce_template.o: ../../include/mymalloc.h
+bounce_template.o: ../../include/nvtable.h
+bounce_template.o: ../../include/rcpt_buf.h
+bounce_template.o: ../../include/recipient_list.h
+bounce_template.o: ../../include/split_at.h
+bounce_template.o: ../../include/stringops.h
+bounce_template.o: ../../include/sys_defs.h
+bounce_template.o: ../../include/vbuf.h
+bounce_template.o: ../../include/vstream.h
+bounce_template.o: ../../include/vstring.h
+bounce_template.o: bounce_service.h
+bounce_template.o: bounce_template.c
+bounce_template.o: bounce_template.h
+bounce_templates.o: ../../include/attr.h
+bounce_templates.o: ../../include/check_arg.h
+bounce_templates.o: ../../include/htable.h
+bounce_templates.o: ../../include/iostuff.h
+bounce_templates.o: ../../include/mail_addr.h
+bounce_templates.o: ../../include/mail_proto.h
+bounce_templates.o: ../../include/msg.h
+bounce_templates.o: ../../include/mymalloc.h
+bounce_templates.o: ../../include/nvtable.h
+bounce_templates.o: ../../include/stringops.h
+bounce_templates.o: ../../include/sys_defs.h
+bounce_templates.o: ../../include/vbuf.h
+bounce_templates.o: ../../include/vstream.h
+bounce_templates.o: ../../include/vstring.h
+bounce_templates.o: ../../include/vstring_vstream.h
+bounce_templates.o: bounce_template.h
+bounce_templates.o: bounce_templates.c
+bounce_trace_service.o: ../../include/attr.h
+bounce_trace_service.o: ../../include/bounce_log.h
+bounce_trace_service.o: ../../include/check_arg.h
+bounce_trace_service.o: ../../include/cleanup_user.h
+bounce_trace_service.o: ../../include/deliver_request.h
+bounce_trace_service.o: ../../include/dsn.h
+bounce_trace_service.o: ../../include/dsn_buf.h
+bounce_trace_service.o: ../../include/dsn_mask.h
+bounce_trace_service.o: ../../include/htable.h
+bounce_trace_service.o: ../../include/int_filt.h
+bounce_trace_service.o: ../../include/iostuff.h
+bounce_trace_service.o: ../../include/mail_addr.h
+bounce_trace_service.o: ../../include/mail_error.h
+bounce_trace_service.o: ../../include/mail_params.h
+bounce_trace_service.o: ../../include/mail_proto.h
+bounce_trace_service.o: ../../include/mail_queue.h
+bounce_trace_service.o: ../../include/msg.h
+bounce_trace_service.o: ../../include/msg_stats.h
+bounce_trace_service.o: ../../include/mymalloc.h
+bounce_trace_service.o: ../../include/name_mask.h
+bounce_trace_service.o: ../../include/nvtable.h
+bounce_trace_service.o: ../../include/post_mail.h
+bounce_trace_service.o: ../../include/rcpt_buf.h
+bounce_trace_service.o: ../../include/rec_type.h
+bounce_trace_service.o: ../../include/recipient_list.h
+bounce_trace_service.o: ../../include/smtputf8.h
+bounce_trace_service.o: ../../include/stringops.h
+bounce_trace_service.o: ../../include/sys_defs.h
+bounce_trace_service.o: ../../include/vbuf.h
+bounce_trace_service.o: ../../include/vstream.h
+bounce_trace_service.o: ../../include/vstring.h
+bounce_trace_service.o: bounce_service.h
+bounce_trace_service.o: bounce_template.h
+bounce_trace_service.o: bounce_trace_service.c
+bounce_warn_service.o: ../../include/attr.h
+bounce_warn_service.o: ../../include/bounce_log.h
+bounce_warn_service.o: ../../include/check_arg.h
+bounce_warn_service.o: ../../include/cleanup_user.h
+bounce_warn_service.o: ../../include/dsn.h
+bounce_warn_service.o: ../../include/dsn_buf.h
+bounce_warn_service.o: ../../include/dsn_mask.h
+bounce_warn_service.o: ../../include/htable.h
+bounce_warn_service.o: ../../include/int_filt.h
+bounce_warn_service.o: ../../include/iostuff.h
+bounce_warn_service.o: ../../include/mail_addr.h
+bounce_warn_service.o: ../../include/mail_error.h
+bounce_warn_service.o: ../../include/mail_params.h
+bounce_warn_service.o: ../../include/mail_proto.h
+bounce_warn_service.o: ../../include/mail_queue.h
+bounce_warn_service.o: ../../include/msg.h
+bounce_warn_service.o: ../../include/mymalloc.h
+bounce_warn_service.o: ../../include/name_mask.h
+bounce_warn_service.o: ../../include/nvtable.h
+bounce_warn_service.o: ../../include/post_mail.h
+bounce_warn_service.o: ../../include/rcpt_buf.h
+bounce_warn_service.o: ../../include/rec_type.h
+bounce_warn_service.o: ../../include/recipient_list.h
+bounce_warn_service.o: ../../include/smtputf8.h
+bounce_warn_service.o: ../../include/stringops.h
+bounce_warn_service.o: ../../include/sys_defs.h
+bounce_warn_service.o: ../../include/vbuf.h
+bounce_warn_service.o: ../../include/vstream.h
+bounce_warn_service.o: ../../include/vstring.h
+bounce_warn_service.o: bounce_service.h
+bounce_warn_service.o: bounce_template.h
+bounce_warn_service.o: bounce_warn_service.c
diff --git a/src/bounce/annotate.sh b/src/bounce/annotate.sh
new file mode 100755
index 0000000..c2acaa8
--- /dev/null
+++ b/src/bounce/annotate.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+
+cat <<'EOF'
+#
+# Do not edit this file. This file shows the default delivery status
+# notification (DSN) messages that are built into Postfix.
+#
+# To change Postfix DSN messages, perhaps to add non-English text,
+# follow instructions in the bounce(5) manual page.
+#
+EOF
+
+# QUICK INSTRUCTIONS:
+#
+#-Edit a temporary copy of this file, and preview the result of $name
+# expansions with "postconf -b temporary_file". If there are any
+# problems, Postfix will log "warning" or "fatal" messages to the
+# maillog file.
+#
+#-The template file can specify bounce message templates for
+# failed mail, for delayed mail, for successful delivery, or for
+# verbose delivery. You don't have to specify all templates.
+#
+#-Each template starts with "template_name = <<EOF" and ends
+# with a line that contains the word "EOF" only. You can change the
+# word EOF if you like, but you can't do shell/perl/etc like things
+# such as enclosing it in quotes (template_name = <<'EOF').
+#
+#-Each template consists of a few headers and message text. The
+# headers control what the recipient sees as From: and Subject:, and
+# what MIME information Postfix will generate.
+#
+#-Template message headers must not span multiple lines.
+#
+#-Template message headers must not contain main.cf $parameters.
+#
+#-Template message headers must contain ASCII characters only.
+#
+#-The template message text is not sent in Postmaster copies of
+# delivery status notifications.
+#
+#-Template message text may contain main.cf $parameters. Some
+# parameters have additional features as described below with the
+# delayed mail message template.
+#
+#-Template message text may contain non-ASCII text. In that case you
+# MUST change the character set value in the CHARSET: template header,
+# otherwise Postfix will not use your template. You must specify a
+# character set that is a superset of US-ASCII, because Postfix
+# appends ASCII text after the message template when it sends a
+# delivery status notification.
+#
+#-When previewing the result with "postconf -b temporary_file", be
+# sure to pay particular attention to the time values that appear
+# in the delayed mail notification text.
+#
+#-Once you're satisfied with the result, and once Postfix stops
+# logging warning messages, copy the template to the Postfix
+# configuration directory and specify in main.cf something like:
+#
+# /etc/postfix/main.cf:
+# bounce_template_file = $config_directory/bounce.cf
+#
+#EOF
+
+IFS=
+while read line; do
+ case "$line" in
+ failure_template*) cat <<'EOF'
+
+#
+# The failure template is used when mail is returned to the sender;
+# either the destination rejected the message, or the destination
+# could not be reached before the message expired in the queue.
+#
+
+EOF
+ ;;
+ delay_template*) cat <<'EOF'
+
+#
+# The delay template is used when mail is delayed. Note a neat trick:
+# the default template displays the delay_warning_time value as hours
+# by appending the _hours suffix to the parameter name; it displays
+# the maximal_queue_lifetime value as days by appending the _days
+# suffix.
+#
+# Other suffixes are: _seconds, _minutes, _weeks. There are no other
+# main.cf parameters that have this special behavior.
+#
+# You need to adjust these suffixes (and the surrounding text) if
+# you have very different settings for these time parameters.
+#
+
+EOF
+ ;;
+ success_template*) cat <<'EOF'
+
+#
+# The success template is used when mail is delivered to mailbox,
+# when an alias or list is expanded, or when mail is delivered to a
+# system that does not announce DSN support. It is an error to specify
+# a Postmaster-Subject: here.
+#
+
+EOF
+ ;;
+ verify_template*) cat <<'EOF'
+
+#
+# The verify template is used for address verification (sendmail -bv
+# address...) or for verbose mail delivery (sendmail -v address...).
+# It is an error to specify a Postmaster-Subject: here.
+#
+
+EOF
+ ;;
+ esac
+ echo "$line";
+done
diff --git a/src/bounce/bounce.c b/src/bounce/bounce.c
new file mode 100644
index 0000000..2393929
--- /dev/null
+++ b/src/bounce/bounce.c
@@ -0,0 +1,713 @@
+/*++
+/* NAME
+/* bounce 8
+/* SUMMARY
+/* Postfix delivery status reports
+/* SYNOPSIS
+/* \fBbounce\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBbounce\fR(8) daemon maintains per-message log files with
+/* delivery status information. Each log file is named after the
+/* queue file that it corresponds to, and is kept in a queue subdirectory
+/* named after the service name in the \fBmaster.cf\fR file (either
+/* \fBbounce\fR, \fBdefer\fR or \fBtrace\fR).
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The \fBbounce\fR(8) daemon processes two types of service requests:
+/* .IP \(bu
+/* Append a recipient (non-)delivery status record to a per-message
+/* log file.
+/* .IP \(bu
+/* Enqueue a delivery status notification message, with a copy
+/* of a per-message log file and of the corresponding message.
+/* When the delivery status notification message is
+/* enqueued successfully, the per-message log file is deleted.
+/* .PP
+/* The software does a best notification effort. A non-delivery
+/* notification is sent even when the log file or the original
+/* message cannot be read.
+/*
+/* Optionally, a bounce (defer, trace) client can request that the
+/* per-message log file be deleted when the requested operation fails.
+/* This is used by clients that cannot retry transactions by
+/* themselves, and that depend on retry logic in their own client.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 2045 (Format of Internet Message Bodies)
+/* RFC 2822 (Internet Message Format)
+/* RFC 3462 (Delivery Status Notifications)
+/* RFC 3464 (Delivery Status Notifications)
+/* RFC 3834 (Auto-Submitted: message header)
+/* RFC 5322 (Internet Message Format)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6532 (Internationalized Message Format)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBbounce\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.
+/* .IP "\fB2bounce_notice_recipient (postmaster)\fR"
+/* The recipient of undeliverable mail that cannot be returned to
+/* the sender.
+/* .IP "\fBbackwards_bounce_logfile_compatibility (yes)\fR"
+/* Produce additional \fBbounce\fR(8) logfile records that can be read by
+/* Postfix versions before 2.0.
+/* .IP "\fBbounce_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications with the message headers
+/* of mail that Postfix did not deliver and of SMTP conversation
+/* transcripts of mail that Postfix did not receive.
+/* .IP "\fBbounce_size_limit (50000)\fR"
+/* The maximal amount of original message text that is sent in a
+/* non-delivery notification.
+/* .IP "\fBbounce_template_file (empty)\fR"
+/* Pathname of a configuration file with bounce message templates.
+/* .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_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications with the message headers
+/* of mail that cannot be delivered within $delay_warning_time time
+/* units.
+/* .IP "\fBdeliver_lock_attempts (20)\fR"
+/* The maximal number of attempts to acquire an exclusive lock on a
+/* mailbox file or \fBbounce\fR(8) logfile.
+/* .IP "\fBdeliver_lock_delay (1s)\fR"
+/* The time between attempts to acquire an exclusive lock on a mailbox
+/* file or \fBbounce\fR(8) logfile.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBinternal_mail_filter_classes (empty)\fR"
+/* What categories of Postfix-generated mail are subject to
+/* before-queue content inspection by non_smtpd_milters, header_checks
+/* and body_checks.
+/* .IP "\fBmail_name (Postfix)\fR"
+/* The mail system name that is displayed in Received: headers, in
+/* the SMTP greeting banner, and in bounced mail.
+/* .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 "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* .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 "\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 3.0 and later:
+/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
+/* Detect that a message requires SMTPUTF8 support for the specified
+/* mail origin classes.
+/* .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.6 and later:
+/* .IP "\fBenable_threaded_bounces (no)\fR"
+/* Enable non-delivery, success, and delay notifications that link
+/* to the original message by including a References: and In-Reply-To:
+/* header with the original Message-ID value.
+/* .PP
+/* Available in Postfix 3.7 and later:
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* FILES
+/* /var/spool/postfix/bounce/* non-delivery records
+/* /var/spool/postfix/defer/* non-delivery records
+/* /var/spool/postfix/trace/* delivery status records
+/* SEE ALSO
+/* bounce(5), bounce message template format
+/* qmgr(8), queue manager
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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 <stringops.h>
+#include <load_file.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <bounce.h>
+#include <mail_addr.h>
+#include <rcpt_buf.h>
+#include <dsb_scan.h>
+#include <hfrom_format.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <bounce_service.h>
+
+ /*
+ * Tunables.
+ */
+int var_bounce_limit;
+int var_max_queue_time;
+int var_delay_warn_time;
+char *var_notify_classes;
+char *var_bounce_rcpt;
+char *var_2bounce_rcpt;
+char *var_delay_rcpt;
+char *var_bounce_tmpl;
+bool var_threaded_bounce;
+char *var_hfrom_format; /* header_from_format */
+
+ /*
+ * We're single threaded, so we can avoid some memory allocation overhead.
+ */
+static VSTRING *queue_id;
+static VSTRING *queue_name;
+static RCPT_BUF *rcpt_buf;
+static VSTRING *encoding;
+static VSTRING *sender;
+static VSTRING *dsn_envid;
+static VSTRING *verp_delims;
+static DSN_BUF *dsn_buf;
+
+ /*
+ * Templates.
+ */
+BOUNCE_TEMPLATES *bounce_templates;
+
+ /*
+ * From: header format.
+ */
+int bounce_hfrom_format;
+
+#define STR vstring_str
+
+#define VS_NEUTER(s) printable(vstring_str(s), '?')
+
+/* bounce_append_proto - bounce_append server protocol */
+
+static int bounce_append_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_append_proto";
+ int flags;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf),
+ ATTR_TYPE_END) != 4) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(rcpt_buf->address);
+ VS_NEUTER(rcpt_buf->orig_addr);
+ VS_NEUTER(rcpt_buf->dsn_orcpt);
+ VS_NEUTER(dsn_buf->status);
+ VS_NEUTER(dsn_buf->action);
+ VS_NEUTER(dsn_buf->reason);
+ VS_NEUTER(dsn_buf->dtype);
+ VS_NEUTER(dsn_buf->dtext);
+ VS_NEUTER(dsn_buf->mtype);
+ VS_NEUTER(dsn_buf->mname);
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+
+ /*
+ * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and
+ * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and
+ * RECIPIENT_FROM_RCPT_BUF().
+ */
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s id=%s org_to=%s to=%s off=%ld dsn_org=%s, notif=0x%x stat=%s act=%s why=%s",
+ myname, flags, service_name, STR(queue_id),
+ STR(rcpt_buf->orig_addr), STR(rcpt_buf->address),
+ rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify, STR(dsn_buf->status),
+ STR(dsn_buf->action), STR(dsn_buf->reason));
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request.
+ */
+ return (bounce_append_service(flags, service_name, STR(queue_id),
+ &rcpt_buf->rcpt, &dsn_buf->dsn));
+}
+
+/* bounce_notify_proto - bounce_notify server protocol */
+
+static int bounce_notify_proto(char *service_name, VSTREAM *client,
+ int (*service) (int, char *, char *, char *,
+ char *, int, char *, char *, int,
+ BOUNCE_TEMPLATES *))
+{
+ const char *myname = "bounce_notify_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ ATTR_TYPE_END) != 8) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x",
+ myname, flags, service_name, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender), STR(dsn_envid),
+ dsn_ret);
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request.
+ */
+ return (service(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ bounce_templates));
+}
+
+/* bounce_verp_proto - bounce_notify server protocol, VERP style */
+
+static int bounce_verp_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_verp_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims),
+ ATTR_TYPE_END) != 9) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ VS_NEUTER(verp_delims);
+ if (strlen(STR(verp_delims)) != 2) {
+ msg_warn("malformed verp delimiter string: %s", STR(verp_delims));
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x delim=%s",
+ myname, flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8, STR(sender),
+ STR(dsn_envid), dsn_ret, STR(verp_delims));
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request. Fall back to traditional notification if a bounce
+ * was returned as undeliverable, because we don't want to VERPify those.
+ */
+ if (!*STR(sender) || !strcasecmp_utf8(STR(sender),
+ mail_addr_double_bounce())) {
+ msg_warn("request to send VERP-style notification of bounced mail");
+ return (bounce_notify_service(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ bounce_templates));
+ } else
+ return (bounce_notify_verp(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ STR(verp_delims), bounce_templates));
+}
+
+/* bounce_one_proto - bounce_one server protocol */
+
+static int bounce_one_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_one_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf),
+ ATTR_TYPE_END) != 10) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (strcmp(service_name, MAIL_SERVICE_BOUNCE) != 0) {
+ msg_warn("wrong service name \"%s\" for one-recipient bouncing",
+ service_name);
+ return (-1);
+ }
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ VS_NEUTER(rcpt_buf->address);
+ VS_NEUTER(rcpt_buf->orig_addr);
+ VS_NEUTER(rcpt_buf->dsn_orcpt);
+ VS_NEUTER(dsn_buf->status);
+ VS_NEUTER(dsn_buf->action);
+ VS_NEUTER(dsn_buf->reason);
+ VS_NEUTER(dsn_buf->dtype);
+ VS_NEUTER(dsn_buf->dtext);
+ VS_NEUTER(dsn_buf->mtype);
+ VS_NEUTER(dsn_buf->mname);
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+
+ /*
+ * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and
+ * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and
+ * RECIPIENT_FROM_RCPT_BUF().
+ */
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s dsn_ret=0x%x orig_to=%s to=%s off=%ld dsn_orig=%s notif=0x%x stat=%s act=%s why=%s",
+ myname, flags, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender), STR(dsn_envid),
+ dsn_ret, STR(rcpt_buf->orig_addr), STR(rcpt_buf->address),
+ rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify, STR(dsn_buf->status),
+ STR(dsn_buf->action), STR(dsn_buf->reason));
+
+ /*
+ * Execute the request.
+ */
+ return (bounce_one_service(flags, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender),
+ STR(dsn_envid), dsn_ret, rcpt_buf,
+ dsn_buf, bounce_templates));
+}
+
+/* bounce_service - parse bounce command type and delegate */
+
+static void bounce_service(VSTREAM *client, char *service_name, char **argv)
+{
+ int command;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments. The
+ * service name should be usable as a subdirectory name.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+ if (mail_queue_name_ok(service_name) == 0)
+ msg_fatal("malformed service name: %s", service_name);
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(client, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_BOUNCE),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(client);
+
+ /*
+ * Read and validate the first parameter of the client request. Let the
+ * request-specific protocol routines take care of the remainder.
+ */
+ if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(MAIL_ATTR_NREQ, &command), 0) != 1) {
+ msg_warn("malformed request");
+ status = -1;
+ } else if (command == BOUNCE_CMD_VERP) {
+ status = bounce_verp_proto(service_name, client);
+ } else if (command == BOUNCE_CMD_FLUSH) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_notify_service);
+ } else if (command == BOUNCE_CMD_WARN) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_warn_service);
+ } else if (command == BOUNCE_CMD_TRACE) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_trace_service);
+ } else if (command == BOUNCE_CMD_APPEND) {
+ status = bounce_append_proto(service_name, client);
+ } else if (command == BOUNCE_CMD_ONE) {
+ status = bounce_one_proto(service_name, client);
+ } else {
+ msg_warn("unknown command: %d", command);
+ status = -1;
+ }
+
+ /*
+ * When the request has completed, send the completion status to the
+ * client.
+ */
+ attr_print(client, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ vstream_fflush(client);
+
+ /*
+ * When a cleanup trap was set, delete the log file in case of error.
+ * This includes errors while sending the completion status to the
+ * client.
+ */
+ if (bounce_cleanup_path) {
+ if (status || vstream_ferror(client))
+ bounce_cleanup_log();
+ bounce_cleanup_unregister();
+ }
+}
+
+static void load_helper(VSTREAM *stream, void *context)
+{
+ BOUNCE_TEMPLATES *templates = (BOUNCE_TEMPLATES *) context;
+
+ bounce_templates_load(stream, templates);
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Bundle up a bunch of bounce template information.
+ */
+ bounce_templates = bounce_templates_create();
+
+ /*
+ * Load the alternate message files (if specified) before entering the
+ * chroot jail.
+ */
+ if (*var_bounce_tmpl)
+ load_file(var_bounce_tmpl, load_helper, (void *) bounce_templates);
+}
+
+/* post_jail_init - initialize after entering chroot jail */
+
+static void post_jail_init(char *service_name, char **unused_argv)
+{
+ bounce_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+
+ /*
+ * Special case: dump bounce templates. This is not part of the master(5)
+ * public interface. This internal interface is used by the postconf
+ * command. It was implemented before bounce templates were isolated into
+ * modules that could have been called directly.
+ */
+ if (strcmp(service_name, "dump_templates") == 0) {
+ bounce_templates_dump(VSTREAM_OUT, bounce_templates);
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+ }
+ if (strcmp(service_name, "expand_templates") == 0) {
+ bounce_templates_expand(VSTREAM_OUT, bounce_templates);
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+ }
+
+ /*
+ * Initialize. We're single threaded so we can reuse some memory upon
+ * successive requests.
+ */
+ queue_id = vstring_alloc(10);
+ queue_name = vstring_alloc(10);
+ rcpt_buf = rcpb_create();
+ encoding = vstring_alloc(10);
+ sender = vstring_alloc(10);
+ dsn_envid = vstring_alloc(10);
+ verp_delims = vstring_alloc(10);
+ dsn_buf = dsb_create();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0,
+ VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0,
+ VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, bounce_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_UNLIMITED,
+ 0);
+}
diff --git a/src/bounce/bounce_append_service.c b/src/bounce/bounce_append_service.c
new file mode 100644
index 0000000..c3cea0b
--- /dev/null
+++ b/src/bounce/bounce_append_service.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* bounce_append_service 3
+/* SUMMARY
+/* append record to bounce log, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_append_service(flags, service, queue_id, rcpt, dsn),
+/* int flags;
+/* char *service;
+/* char *queue_id;
+/* RECIPIENT *rcpt;
+/* DSN *dsn;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_append()
+/* (append bounce log) request. This routine either succeeds or
+/* it raises a fatal error.
+/* DIAGNOSTICS
+/* Fatal errors: all file access errors; memory allocation errors.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <quote_822_local.h>
+#include <deliver_flock.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+/* bounce_append_service - append bounce log */
+
+int bounce_append_service(int unused_flags, char *service, char *queue_id,
+ RECIPIENT *rcpt, DSN *dsn)
+{
+ VSTRING *in_buf = vstring_alloc(100);
+ VSTREAM *log;
+ long orig_length;
+
+ /*
+ * This code is paranoid for a good reason. Once the bounce service takes
+ * responsibility, the mail system will make no further attempts to
+ * deliver this recipient. Whenever file access fails, assume that the
+ * system is under stress or that something has been mis-configured, and
+ * force a backoff by raising a fatal run-time error.
+ */
+ log = mail_queue_open(service, queue_id,
+ O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if (log == 0)
+ msg_fatal("open file %s %s: %m", service, queue_id);
+
+ /*
+ * Lock out other processes to avoid truncating someone else's data in
+ * case of trouble.
+ */
+ if (deliver_flock(vstream_fileno(log), INTERNAL_LOCK, (VSTRING *) 0) < 0)
+ msg_fatal("lock file %s %s: %m", service, queue_id);
+
+ /*
+ * Now, go for it. Append a record. Truncate the log to the original
+ * length when the append operation fails. We use the plain stream-lf
+ * file format because we do not need anything more complicated. As a
+ * benefit, we can still recover some data when the file is a little
+ * garbled.
+ *
+ * XXX addresses in defer logfiles are in printable quoted form, while
+ * addresses in message envelope records are in raw unquoted form. This
+ * may change once we replace the present ad-hoc bounce/defer logfile
+ * format by one that is transparent for control etc. characters. See
+ * also: showq/showq.c.
+ *
+ * While migrating from old format to new format, allow backwards
+ * compatibility by writing an old-style record before the new-style
+ * records.
+ */
+ if ((orig_length = vstream_fseek(log, 0L, SEEK_END)) < 0)
+ msg_fatal("seek file %s %s: %m", service, queue_id);
+
+#define NOT_NULL_EMPTY(s) ((s) != 0 && *(s) != 0)
+#define STR(x) vstring_str(x)
+
+ vstream_fputs("\n", log);
+ if (var_oldlog_compat) {
+ vstream_fprintf(log, "<%s>: %s\n", *rcpt->address == 0 ? "" :
+ STR(quote_822_local(in_buf, rcpt->address)),
+ dsn->reason);
+ }
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_RECIP, *rcpt->address ?
+ STR(quote_822_local(in_buf, rcpt->address)) : "<>");
+ if (NOT_NULL_EMPTY(rcpt->orig_addr)
+ && strcasecmp_utf8(rcpt->address, rcpt->orig_addr) != 0)
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_ORCPT,
+ STR(quote_822_local(in_buf, rcpt->orig_addr)));
+ if (rcpt->offset > 0)
+ vstream_fprintf(log, "%s=%ld\n", MAIL_ATTR_OFFSET, rcpt->offset);
+ if (NOT_NULL_EMPTY(rcpt->dsn_orcpt))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt);
+ if (rcpt->dsn_notify != 0)
+ vstream_fprintf(log, "%s=%d\n", MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify);
+
+ if (NOT_NULL_EMPTY(dsn->status))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_STATUS, dsn->status);
+ if (NOT_NULL_EMPTY(dsn->action))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ACTION, dsn->action);
+ if (NOT_NULL_EMPTY(dsn->dtype) && NOT_NULL_EMPTY(dsn->dtext)) {
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTYPE, dsn->dtype);
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTEXT, dsn->dtext);
+ }
+ if (NOT_NULL_EMPTY(dsn->mtype) && NOT_NULL_EMPTY(dsn->mname)) {
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MTYPE, dsn->mtype);
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MNAME, dsn->mname);
+ }
+ if (NOT_NULL_EMPTY(dsn->reason))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_WHY, dsn->reason);
+ vstream_fputs("\n", log);
+
+ if (vstream_fflush(log) != 0 || fsync(vstream_fileno(log)) < 0) {
+#ifndef NO_TRUNCATE
+ if (ftruncate(vstream_fileno(log), (off_t) orig_length) < 0)
+ msg_fatal("truncate file %s %s: %m", service, queue_id);
+#endif
+ msg_fatal("append file %s %s: %m", service, queue_id);
+ }
+
+ /*
+ * Darn. If closing the log detects a problem, the only way to undo the
+ * damage is to open the log once more, and to truncate the log to the
+ * original length. But, this could happen only when the log is kept on a
+ * remote file system, and that is not recommended practice anyway.
+ */
+ if (vstream_fclose(log) != 0)
+ msg_warn("append file %s %s: %m", service, queue_id);
+
+ vstring_free(in_buf);
+ return (0);
+}
diff --git a/src/bounce/bounce_cleanup.c b/src/bounce/bounce_cleanup.c
new file mode 100644
index 0000000..9fb900b
--- /dev/null
+++ b/src/bounce/bounce_cleanup.c
@@ -0,0 +1,177 @@
+/*++
+/* NAME
+/* bounce_cleanup 3
+/* SUMMARY
+/* cleanup logfile upon error
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_cleanup_registered()
+/*
+/* void bounce_cleanup_register(queue_id)
+/* char *queue_id;
+/*
+/* void bounce_cleanup_log(void)
+/*
+/* void bounce_cleanup_unregister(void)
+/* DESCRIPTION
+/* This module implements support for deleting the current
+/* bounce logfile in case of errors, and upon the arrival
+/* of a SIGTERM signal (shutdown).
+/*
+/* bounce_cleanup_register() registers a callback routine with the
+/* run-time error handler, for automatic logfile removal in case
+/* of a fatal run-time error.
+/*
+/* bounce_cleanup_unregister() cleans up storage used by
+/* bounce_cleanup_register().
+/*
+/* In-between bounce_cleanup_register() and bounce_cleanup_unregister()
+/* calls, a call of bounce_cleanup_log() will delete the registered
+/* bounce logfile.
+/*
+/* bounce_cleanup_registered() returns non-zero when a cleanup
+/* trap has been set.
+/* DIAGNOSTICS
+/* Fatal error: all file access errors. Panic: nested calls of
+/* bounce_cleanup_register(); any calls of bounce_cleanup_unregister()
+/* or bounce_cleanup_log() without preceding bounce_cleanup_register()
+/* call.
+/* BUGS
+/* SEE ALSO
+/* master(8) process manager
+/* 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 <signal.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+ /*
+ * Support for removing a logfile when an update fails. In order to do this,
+ * we save a copy of the currently-open logfile name, and register a
+ * callback function pointer with the run-time error handler. The saved
+ * pathname is made global so that the application can see whether or not a
+ * trap was set up.
+ */
+static MSG_CLEANUP_FN bounce_cleanup_func; /* saved callback */
+VSTRING *bounce_cleanup_path; /* saved path name */
+
+/* bounce_cleanup_callback - run-time callback to cleanup logfile */
+
+static void bounce_cleanup_callback(void)
+{
+
+ /*
+ * Remove the logfile.
+ */
+ if (bounce_cleanup_path)
+ bounce_cleanup_log();
+
+ /*
+ * Execute the saved cleanup action.
+ */
+ if (bounce_cleanup_func)
+ bounce_cleanup_func();
+}
+
+/* bounce_cleanup_log - clean up the logfile */
+
+void bounce_cleanup_log(void)
+{
+ const char *myname = "bounce_cleanup_log";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path == 0)
+ msg_panic("%s: no cleanup context", myname);
+
+ /*
+ * This function may be called before a logfile is created or after it
+ * has been deleted, so do not complain.
+ */
+ (void) unlink(vstring_str(bounce_cleanup_path));
+}
+
+/* bounce_cleanup_sig - signal handler */
+
+static void bounce_cleanup_sig(int sig)
+{
+
+ /*
+ * Running as a signal handler - don't do complicated stuff.
+ */
+ if (bounce_cleanup_path)
+ (void) unlink(vstring_str(bounce_cleanup_path));
+ _exit(sig);
+}
+
+/* bounce_cleanup_register - register logfile to clean up */
+
+void bounce_cleanup_register(char *service, char *queue_id)
+{
+ const char *myname = "bounce_cleanup_register";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path)
+ msg_panic("%s: nested call", myname);
+
+ /*
+ * Save a copy of the logfile path, and of the last callback function
+ * pointer registered with the run-time error handler.
+ */
+ bounce_cleanup_path = vstring_alloc(10);
+ (void) mail_queue_path(bounce_cleanup_path, service, queue_id);
+ bounce_cleanup_func = msg_cleanup(bounce_cleanup_callback);
+ signal(SIGTERM, bounce_cleanup_sig);
+}
+
+/* bounce_cleanup_unregister - unregister logfile to clean up */
+
+void bounce_cleanup_unregister(void)
+{
+ const char *myname = "bounce_cleanup_unregister";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path == 0)
+ msg_panic("%s: no cleanup context", myname);
+
+ /*
+ * Restore the saved callback function pointer, and release storage for
+ * the saved logfile pathname.
+ */
+ signal(SIGTERM, SIG_DFL);
+ (void) msg_cleanup(bounce_cleanup_func);
+ vstring_free(bounce_cleanup_path);
+ bounce_cleanup_path = 0;
+}
diff --git a/src/bounce/bounce_notify_service.c b/src/bounce/bounce_notify_service.c
new file mode 100644
index 0000000..d6c751f
--- /dev/null
+++ b/src/bounce/bounce_notify_service.c
@@ -0,0 +1,327 @@
+/*++
+/* NAME
+/* bounce_notify_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_notify_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, dsn_envid,
+/* dsn_ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_flush()
+/* (send bounce message) request.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again. Otherwise, the logfile is removed.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_notify_service - send a bounce */
+
+int bounce_notify_service(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+ char *postmaster;
+ int count;
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN The bounce service produces RFC 3464-style "failed mail" reports
+ * from information in two following types of logfile:
+ *
+ * 1 - bounce: this file is used for RFC 3464-style reports of permanent
+ * delivery errors by the bounce(8) service. This reports to the sender
+ * all recipients that have no DSN NOTIFY information (compatibility) and
+ * all recipients that have DSN NOTIFY=FAILURE; this reports to
+ * postmaster all recipients, if postmaster notification is enabled.
+ *
+ * 2 - defer: this file is used for three types of report:
+ *
+ * 2a) RFC 3464-style "mail is too old" reports by the bounce(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=FAILURE; this reports to postmaster all recipients, if
+ * postmaster notification is enabled.
+ *
+ * Other reports that other servers produce from the defer logfile:
+ *
+ * 2b) On-demand reports of all delayed deliveries by the showq(8) service
+ * and mailq(1) command. This reports all recipients that have a
+ * transient delivery error.
+ *
+ * 2c) RFC 3464-style "delayed mail" notifications by the defer(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=DELAY; this reports to postmaster all recipients, if postmaster
+ * notification is enabled.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ ts->failure);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of sender address depends on the recipient address. For a
+ * single bounce (a non-delivery notification to the message originator),
+ * the sender address is the empty string. For a double bounce (typically
+ * a failed single bounce, or a postmaster notification that was produced
+ * by any of the mail processes) the sender address is defined by the
+ * var_double_bounce_sender configuration variable. When a double bounce
+ * cannot be delivered, the queue manager blackholes the resulting triple
+ * bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce recipient address and discard mail, or
+ * every delivery agent must recognize the double-bounce sender address
+ * and substitute something else so mail does not come back at us.
+ */
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE)
+
+ else if (*recipient == 0) {
+ if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) {
+ bounce_status = 0;
+ } else {
+ postmaster = var_2bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce to the sender, subject to DSN
+ * NOTIFY restrictions.
+ */
+ else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_FAILURE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_FAILURE) > 0) {
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Optionally, send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE
+ && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ postmaster_status = 0;
+ }
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Optionally, delete the recipients from the queue file.
+ */
+ if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT))
+ bounce_delrcpt(bounce_info);
+
+ /*
+ * Examine the completion status. Delete the bounce log file only when
+ * the bounce was posted successfully, and only if we are bouncing for
+ * real, not just warning.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_notify_util.c b/src/bounce/bounce_notify_util.c
new file mode 100644
index 0000000..781a525
--- /dev/null
+++ b/src/bounce/bounce_notify_util.c
@@ -0,0 +1,1010 @@
+/*++
+/* NAME
+/* bounce_notify_util 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* typedef struct {
+/* .in +4
+/* /* All private members... */
+/* .in -4
+/* } BOUNCE_INFO;
+/*
+/* BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, template)
+/* const char *service;
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *dsn_envid;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, dsn_notify,
+/* rcpt_buf, dsn_buf, template)
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* int dsn_notify;
+/* const char *dsn_envid;
+/* RCPT_BUF *rcpt_buf;
+/* DSN_BUF *dsn_buf;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_mail_free(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_header(fp, bounce_info, recipient, postmaster_copy)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* const char *recipient;
+/* int postmaster_copy;
+/*
+/* int bounce_boilerplate(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_log(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_log(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_header_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_dsn(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_original(fp, bounce_info, headers_only)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int headers_only;
+/*
+/* void bounce_delrcpt(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* void bounce_delrcpt_one(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/* DESCRIPTION
+/* This module implements the grunt work of sending a non-delivery
+/* notification. A bounce is sent in a form that satisfies RFC 1894
+/* (delivery status notifications).
+/*
+/* bounce_mail_init() bundles up its argument and attempts to
+/* open the corresponding logfile and message file. A BOUNCE_INFO
+/* structure contains all the necessary information about an
+/* undeliverable message.
+/*
+/* bounce_mail_one_init() provides the same function for only
+/* one recipient that is not read from bounce logfile.
+/*
+/* bounce_mail_free() releases memory allocated by bounce_mail_init()
+/* and closes any files opened by bounce_mail_init().
+/*
+/* bounce_header() produces a standard mail header with the specified
+/* recipient and starts a text/plain message segment for the
+/* human-readable problem description. postmaster_copy is either
+/* POSTMASTER_COPY or NO_POSTMASTER_COPY.
+/*
+/* bounce_boilerplate() produces the standard "sorry" text that
+/* creates the illusion that mail systems are civilized.
+/*
+/* bounce_recipient_log() sends a human-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_log() sends a human-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_header_dsn() starts a message/delivery-status message
+/* segment and sends the machine-readable information that identifies
+/* the reporting MTA.
+/*
+/* bounce_recipient_dsn() sends a machine-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_dsn() sends a machine-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_original() starts a message/rfc822 or text/rfc822-headers
+/* message segment and sends the original message, either full
+/* (DSN_RET_FULL) or message headers only (DSN_RET_HDRS).
+/*
+/* bounce_delrcpt() deletes recipients in the logfile from the original
+/* queue file.
+/*
+/* bounce_delrcpt_one() deletes one recipient from the original
+/* queue file.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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/stat.h>
+#include <stdlib.h>
+#include <stdio.h> /* sscanf() */
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <line_wrap.h>
+#include <stringops.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <quote_822_local.h>
+#include <mail_params.h>
+#include <is_header.h>
+#include <record.h>
+#include <rec_type.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce_log.h>
+#include <mail_date.h>
+#include <mail_proto.h>
+#include <lex_822.h>
+#include <deliver_completed.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+#include <header_opts.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* bounce_mail_alloc - initialize */
+
+static BOUNCE_INFO *bounce_mail_alloc(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template,
+ BOUNCE_LOG *log_handle)
+{
+ BOUNCE_INFO *bounce_info;
+ int rec_type;
+ int prev_type;
+ int all_headers_seen = 0;
+ int skip_message_segment = 0;
+ int in_envelope = 1;
+
+ /*
+ * Bundle up a bunch of parameters and initialize information that will
+ * be discovered on the fly.
+ *
+ * XXX Instead of overriding the returned-message MIME encoding, separate
+ * the returned-message MIME encoding from the (boiler plate, delivery
+ * status) MIME encoding.
+ */
+ bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
+ bounce_info->service = service;
+ bounce_info->queue_name = queue_name;
+ bounce_info->queue_id = queue_id;
+ bounce_info->smtputf8 = smtputf8;
+ /* Fix 20140708: override MIME encoding: addresses may be 8bit. */
+ /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */
+ if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
+ bounce_info->mime_encoding = "7bit";
+ } else {
+ if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
+ msg_warn("%s: unknown encoding: %.200s",
+ bounce_info->queue_id, encoding);
+ bounce_info->mime_encoding = 0;
+ }
+ if (dsn_envid && *dsn_envid)
+ bounce_info->dsn_envid = dsn_envid;
+ else
+ bounce_info->dsn_envid = 0;
+ bounce_info->template = template;
+ bounce_info->buf = vstring_alloc(100);
+ bounce_info->sender = vstring_alloc(100);
+ bounce_info->arrival_time = 0;
+ bounce_info->orig_offs = 0;
+ bounce_info->message_size = 0;
+ bounce_info->orig_msgid = vstring_alloc(100);
+ bounce_info->rcpt_buf = rcpt_buf;
+ bounce_info->dsn_buf = dsn_buf;
+ bounce_info->log_handle = log_handle;
+
+ /*
+ * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
+ * must ensure it is valid.
+ */
+ bounce_info->mail_name = mystrdup(var_mail_name);
+ translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
+ "-----------------");
+
+ /*
+ * Compute a supposedly unique boundary string. This assumes that a queue
+ * ID and a hostname contain acceptable characters for a boundary string,
+ * but the assumption is not verified.
+ */
+ vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
+ queue_id, (unsigned long) event_time(), var_myhostname);
+ bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
+
+ /*
+ * If the original message cannot be found, do not raise a run-time
+ * error. There is nothing we can do about the error, and all we are
+ * doing is to inform the sender of a delivery problem. Bouncing a
+ * message does not have to be a perfect job. But if the system IS
+ * running out of resources, raise a fatal run-time error and force a
+ * backoff.
+ */
+ if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
+ O_RDWR, 0)) == 0
+ && errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+
+ /*
+ * Get time/size/sender information from the original message envelope
+ * records. If the envelope is corrupted just send whatever we can
+ * (remember this is a best effort, it does not have to be perfect).
+ *
+ * Lock the file for shared use, so that queue manager leaves it alone after
+ * restarting.
+ */
+#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
+
+ if (bounce_info->orig_fp != 0) {
+ if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
+ DELIVER_LOCK_MODE) < 0)
+ msg_fatal("cannot get shared lock on %s: %m",
+ VSTREAM_PATH(bounce_info->orig_fp));
+ for (prev_type = 0;
+ (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0;
+ prev_type = rec_type) {
+
+ /*
+ * Postfix version dependent: data offset in SIZE record.
+ */
+ if (rec_type == REC_TYPE_SIZE) {
+ if (bounce_info->message_size == 0)
+ sscanf(STR(bounce_info->buf), "%ld %ld",
+ &bounce_info->message_size,
+ &bounce_info->orig_offs);
+ if (bounce_info->message_size < 0)
+ bounce_info->message_size = 0;
+ if (bounce_info->orig_offs < 0)
+ bounce_info->orig_offs = 0;
+ }
+
+ /*
+ * Information for the Arrival-Date: attribute.
+ */
+ else if (rec_type == REC_TYPE_TIME) {
+ if (bounce_info->arrival_time == 0
+ && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
+ bounce_info->arrival_time = 0;
+ }
+
+ /*
+ * Information for the X-Postfix-Sender: attribute.
+ */
+ else if (rec_type == REC_TYPE_FROM) {
+ quote_822_local_flags(bounce_info->sender,
+ VSTRING_LEN(bounce_info->buf) ?
+ STR(bounce_info->buf) :
+ mail_addr_mail_daemon(), 0);
+ }
+
+ /*
+ * Backwards compatibility: no data offset in SIZE record.
+ */
+ else if (rec_type == REC_TYPE_MESG) {
+ /* XXX Future: sender+recipient after message content. */
+ if (VSTRING_LEN(bounce_info->sender) == 0)
+ msg_warn("%s: no sender before message content record",
+ bounce_info->queue_id);
+ bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
+ if (var_threaded_bounce == 0)
+ skip_message_segment = 1;
+ else
+ in_envelope = 0;
+ }
+
+ /*
+ * Extract Message-ID for threaded bounces.
+ */
+ else if (in_envelope == 0
+ && (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)) {
+ const HEADER_OPTS *hdr;
+ char *cp;
+
+ /*
+ * Skip records that we cannot use. Degrade if we could not
+ * skip over the message content.
+ */
+ if (var_threaded_bounce == 0 || all_headers_seen
+ || prev_type == REC_TYPE_CONT) {
+ /* void */ ;
+ }
+
+ /*
+ * Extract message-id header value.
+ */
+ else if (is_header(STR(bounce_info->buf))) {
+ if ((hdr = header_opts_find(
+ vstring_str(bounce_info->buf))) != 0
+ && hdr->type == HDR_MESSAGE_ID) {
+ vstring_truncate(bounce_info->buf,
+ trimblanks(STR(bounce_info->buf),
+ LEN(bounce_info->buf))
+ - STR(bounce_info->buf));
+ cp = STR(bounce_info->buf) + strlen(hdr->name) + 1;
+ while (ISSPACE(*cp))
+ cp++;
+ if (*cp == '<' && vstring_end(bounce_info->buf)[-1] == '>')
+ vstring_strcpy(bounce_info->orig_msgid, cp);
+ else
+ msg_warn("%s: ignoring malformed Message-ID",
+ bounce_info->queue_id);
+ }
+ }
+
+ /*
+ * Skip remainder of multiline header.
+ */
+ else if (ISSPACE(*STR(bounce_info->buf))) {
+ /* void */ ;
+ }
+
+ /*
+ * Start of body.
+ */
+ else {
+ all_headers_seen = 1;
+ skip_message_segment = 1;
+ }
+ }
+
+ /*
+ * In case we ever want to process records from the extracted
+ * segment, and in case there was no "start of body" event.
+ */
+ else if (rec_type == REC_TYPE_XTRA) {
+ if (VSTRING_LEN(bounce_info->orig_msgid) == 0)
+ if (var_threaded_bounce)
+ all_headers_seen = 1;
+ in_envelope = 1;
+ }
+
+ /*
+ * Are we done yet?
+ */
+ if (bounce_info->orig_offs > 0
+ && bounce_info->arrival_time > 0
+ && VSTRING_LEN(bounce_info->sender) > 0
+ && (var_threaded_bounce == 0 || all_headers_seen
+ || VSTRING_LEN(bounce_info->orig_msgid) > 0)) {
+ break;
+ }
+
+ /*
+ * Skip over (the remainder of) the message segment. If that
+ * fails, degrade.
+ */
+ if (skip_message_segment) {
+ if (vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs +
+ bounce_info->message_size,
+ SEEK_SET) < 0)
+ /* void */ ;
+ skip_message_segment = 0;
+ }
+ }
+ }
+ return (bounce_info);
+}
+
+/* bounce_mail_init - initialize */
+
+BOUNCE_INFO *bounce_mail_init(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+ BOUNCE_LOG *log_handle;
+ RCPT_BUF *rcpt_buf;
+ DSN_BUF *dsn_buf;
+
+ /*
+ * Initialize the bounce_info structure. If the bounce log cannot be
+ * found, do not raise a fatal run-time error. There is nothing we can do
+ * about the error, and all we are doing is to inform the sender of a
+ * delivery problem, Bouncing a message does not have to be a perfect
+ * job. But if the system IS running out of resources, raise a fatal
+ * run-time error and force a backoff.
+ */
+ if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+ rcpt_buf = 0;
+ dsn_buf = 0;
+ } else {
+ rcpt_buf = rcpb_create();
+ dsn_buf = dsb_create();
+ }
+ bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, log_handle);
+ return (bounce_info);
+}
+
+/* bounce_mail_one_init - initialize */
+
+BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+
+ /*
+ * Initialize the bounce_info structure for just one recipient.
+ */
+ bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, (BOUNCE_LOG *) 0);
+ return (bounce_info);
+}
+
+/* bounce_mail_free - undo bounce_mail_init */
+
+void bounce_mail_free(BOUNCE_INFO *bounce_info)
+{
+ if (bounce_info->log_handle) {
+ if (bounce_log_close(bounce_info->log_handle))
+ msg_warn("%s: read bounce log %s: %m",
+ bounce_info->queue_id, bounce_info->queue_id);
+ vstring_free(bounce_info->orig_msgid);
+ rcpb_free(bounce_info->rcpt_buf);
+ dsb_free(bounce_info->dsn_buf);
+ }
+ if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
+ msg_warn("%s: read message file %s %s: %m",
+ bounce_info->queue_id, bounce_info->queue_name,
+ bounce_info->queue_id);
+ vstring_free(bounce_info->buf);
+ vstring_free(bounce_info->sender);
+ myfree(bounce_info->mail_name);
+ myfree((void *) bounce_info->mime_boundary);
+ myfree((void *) bounce_info);
+}
+
+/* bounce_header - generate bounce message header */
+
+int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *dest, int postmaster_copy)
+{
+ BOUNCE_TEMPLATE *template = bounce_info->template;
+
+ /*
+ * Print a minimal bounce header. The cleanup service will add other
+ * headers and will make all addresses fully qualified.
+ */
+#define STREQ(a, b) (strcasecmp((a), (b)) == 0)
+#define STRNE(a, b) (strcasecmp((a), (b)) != 0)
+
+ /*
+ * Generic headers.
+ */
+ bounce_template_headers(post_mail_fprintf, bounce, template,
+ STR(quote_822_local(bounce_info->buf, dest)),
+ postmaster_copy);
+
+ /*
+ * References and Reply-To header that references the original message-id
+ * for better threading in MUAs.
+ */
+ if (VSTRING_LEN(bounce_info->orig_msgid) > 0) {
+ post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid));
+ post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid));
+ }
+
+ /*
+ * Auto-Submitted header, as per RFC 3834.
+ */
+ post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
+ "auto-generated" : "auto-replied");
+
+ /*
+ * MIME header. Use 8bit encoding when either the bounced message or the
+ * template requires it.
+ */
+ post_mail_fprintf(bounce, "MIME-Version: 1.0");
+ post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
+ "multipart/report", "delivery-status");
+ post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
+ if (bounce_info->mime_encoding)
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
+ bounce_template_encoding(template) :
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
+
+ /*
+ * MIME header.
+ */
+#define NOT_US_ASCII(tp) \
+ STRNE(bounce_template_charset(template), "us-ascii")
+
+#define NOT_7BIT_MIME(bp) \
+ (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
+ /* Fix 20140718: UTF-8 address or $myhostname expansion. */
+ post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
+ "text/plain", NOT_US_ASCII(template) ?
+ bounce_template_charset(template) :
+ NOT_7BIT_MIME(bounce_info) ?
+ "utf-8" : "us-ascii");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_boilerplate - generate boiler-plate text */
+
+int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * Print the boiler-plate text.
+ */
+ bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_print - line_wrap callback */
+
+static void bounce_print(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *bounce = (VSTREAM *) context;
+
+ post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
+}
+
+/* bounce_print_wrap - print and wrap a line */
+
+static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *format,...)
+{
+ va_list ap;
+
+#define LENGTH 79
+#define INDENT 4
+
+ va_start(ap, format);
+ vstring_vsprintf(bounce_info->buf, format, ap);
+ va_end(ap);
+ line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
+ bounce_print, (void *) bounce);
+}
+
+/* bounce_recipient_log - send one bounce log report entry */
+
+int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ /*
+ * Mask control and non-ASCII characters (done in bounce_log_read()),
+ * wrap long lines and prepend one blank, so this data can safely be
+ * piped into other programs. Sort of like TCP Wrapper's safe_finger
+ * program.
+ */
+#define NON_NULL_EMPTY(s) ((s) && *(s))
+
+ post_mail_fputs(bounce, "");
+ if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
+ rcpt->address, rcpt->orig_addr, dsn->reason);
+ } else {
+ bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
+ rcpt->address, dsn->reason);
+ }
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_log - send bounce log report */
+
+int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a human-readable copy of the delivery error log. We're doing a
+ * best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
+ count = 1; /* XXX don't abort */
+ }
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_log(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_header_dsn - send per-MTA bounce DSN records */
+
+int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * MIME header.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s",
+ "Delivery report");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status",
+ (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ?
+ "global-" : "");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info)
+ /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */
+ && (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+
+ /*
+ * According to RFC 1894: The body of a message/delivery-status consists
+ * of one or more "fields" formatted according to the ABNF of RFC 822
+ * header "fields" (see [6]). The per-message fields appear first,
+ * followed by a blank line.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
+#if 0
+ post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
+#endif
+ if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
+ post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
+ bounce_info->dsn_envid);
+ }
+ post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
+ bounce_info->mail_name, bounce_info->queue_id);
+
+#define IS_UTF8_ADDRESS(str, len) \
+ ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len)))
+
+ /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
+ if (VSTRING_LEN(bounce_info->sender) > 0)
+ post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
+ bounce_info->mail_name, bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(STR(bounce_info->sender),
+ VSTRING_LEN(bounce_info->sender)) ?
+ "utf-8" : "rfc822", STR(bounce_info->sender));
+ if (bounce_info->arrival_time > 0)
+ post_mail_fprintf(bounce, "Arrival-Date: %s",
+ mail_date(bounce_info->arrival_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_recipient_dsn - send per-recipient DSN records */
+
+int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ post_mail_fputs(bounce, "");
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->address,
+ strlen(rcpt->address)) ?
+ "utf-8" : "rfc822", rcpt->address);
+
+ /*
+ * XXX DSN
+ *
+ * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
+ * recipient, the Original-Recipient field MUST NOT appear."
+ *
+ * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
+ * present in the RCPT command when the message was received, an ORCPT
+ * parameter MAY be added to the RCPT command when the message is
+ * relayed.". Postfix adds an ORCPT parameter under these conditions.
+ *
+ * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
+ * field containing this same ORCPT value. When a down-stream MTA can use
+ * that information in their DSNs, it makes no sense that an up-stream
+ * MTA can't use that same information in its own DSNs.
+ *
+ * Postfix always reports an Original-Recipient field, because it is more
+ * more useful and more consistent.
+ */
+ if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
+ post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
+ } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->orig_addr,
+ strlen(rcpt->orig_addr)) ?
+ "utf-8" : "rfc822", rcpt->orig_addr);
+ }
+ post_mail_fprintf(bounce, "Action: %s",
+ IS_FAILURE_TEMPLATE(bounce_info->template) ?
+ "failed" : dsn->action);
+ post_mail_fprintf(bounce, "Status: %s", dsn->status);
+ if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
+ bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
+ dsn->mtype, dsn->mname);
+ if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
+ dsn->dtype, dsn->dtext);
+ else
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
+ bounce_info->mail_name, dsn->reason);
+#if 0
+ if (dsn->time > 0)
+ post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
+ mail_date(dsn->time));
+#endif
+ if (IS_DELAY_TEMPLATE(bounce_info->template))
+ post_mail_fprintf(bounce, "Will-Retry-Until: %s",
+ mail_date(bounce_info->arrival_time + var_max_queue_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_dsn - send bounce log report, machine readable form */
+
+int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a machine-readable copy of the delivery error log. We're doing
+ * a best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template))
+ count = 1; /* XXX don't abort */
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_dsn(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_original - send a copy of the original to the victim */
+
+int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int headers_only)
+{
+ int status = 0;
+ int rec_type = 0;
+
+ /*
+ * When truncating a large message, don't damage the MIME structure: send
+ * the message headers only.
+ */
+ if (var_bounce_limit > 0
+ && bounce_info->orig_fp
+ && (bounce_info->message_size <= 0
+ || bounce_info->message_size > var_bounce_limit))
+ headers_only = DSN_RET_HDRS;
+
+ /*
+ * MIME headers.
+ */
+#define IS_UNDELIVERED_TEMPLATE(template) \
+ (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s%s",
+ IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
+ "Undelivered " : "",
+ headers_only == DSN_RET_HDRS ?
+ "Message Headers" : "Message");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ if (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED)
+ post_mail_fprintf(bounce, "Content-Type: message/%s",
+ headers_only == DSN_RET_HDRS ?
+ "global-headers" : "global");
+ else
+ post_mail_fprintf(bounce, "Content-Type: %s",
+ headers_only == DSN_RET_HDRS ?
+ "text/rfc822-headers" : "message/rfc822");
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ /*
+ * Send place holder if original is unavailable.
+ */
+ if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs, SEEK_SET) < 0) {
+ post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
+ return (vstream_ferror(bounce));
+ }
+
+ /*
+ * XXX The cleanup server removes Return-Path: headers. This should be
+ * done only with mail that enters via a non-SMTP channel, but changing
+ * this now could break other software. Removing Return-Path: could break
+ * digital signatures, though this is unlikely. In any case,
+ * header_checks are more effective when the Return-Path: header is
+ * present, so we prepend one to the bounce message.
+ */
+ post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
+
+ /*
+ * Copy the original message contents. We're doing raw record output here
+ * so that we don't throw away binary transparency yet.
+ */
+#define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
+
+ while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (headers_only == DSN_RET_HDRS
+ && !IS_HEADER(vstring_str(bounce_info->buf)))
+ break;
+ status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
+ }
+
+ /*
+ * Final MIME headers. These require -- at the end of the boundary
+ * string.
+ *
+ * XXX This should be a separate bounce_terminate() entry so we can be
+ * assured that the terminator will always be sent.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
+
+ return (status);
+}
+
+/* bounce_delrcpt - delete recipients from original queue file */
+
+void bounce_delrcpt(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0
+ && bounce_info->log_handle != 0
+ && bounce_log_rewind(bounce_info->log_handle) == 0)
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0)
+ if (rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}
+
+/* bounce_delrcpt_one - delete one recipient from original queue file */
+
+void bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}
diff --git a/src/bounce/bounce_notify_util_tester.c b/src/bounce/bounce_notify_util_tester.c
new file mode 100644
index 0000000..da13f47
--- /dev/null
+++ b/src/bounce/bounce_notify_util_tester.c
@@ -0,0 +1,167 @@
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn_mask.h>
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <hfrom_format.h>
+
+ /*
+ * Bounce service.
+ */
+#include <bounce_service.h>
+#include <bounce_template.h>
+
+ /*
+ * Testing library.
+ */
+#include <test_main.h>
+
+#define TEST_ENCODING "7bit"
+#define NO_SMTPUTF8 (0)
+#define TEST_DSN_ENVID "TEST-ENVID"
+#define TEST_RECIPIENT "test-recipient"
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* test_driver - test driver */
+
+static void test_driver(int argc, char **argv)
+{
+ BOUNCE_TEMPLATES *bounce_templates;
+ BOUNCE_INFO *bounce_info;
+ VSTRING *message_buf;
+ VSTREAM *message_stream;
+ VSTRING *rec_buf;
+ int rec_type;
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 4)
+ msg_fatal("usage: %s [options] service queue_name queue_id", argv[0]);
+
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ bounce_hfrom_format =
+ hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+
+ /*
+ * Write one message to VSTRING.
+ */
+ message_buf = vstring_alloc(100);
+ if ((message_stream = vstream_memopen(message_buf, O_WRONLY)) == 0)
+ msg_fatal("vstream_memopen O_WRONLY: %m");
+ bounce_templates = bounce_templates_create();
+ bounce_info = bounce_mail_init(argv[1], argv[2], argv[3],
+ TEST_ENCODING, NO_SMTPUTF8, TEST_DSN_ENVID,
+ bounce_templates->failure);
+ if (bounce_header(message_stream, bounce_info, TEST_RECIPIENT,
+ NO_POSTMASTER_COPY) < 0)
+ msg_fatal("bounce_header: %m");
+ if (bounce_diagnostic_log(message_stream, bounce_info,
+ DSN_NOTIFY_OVERRIDE) <= 0)
+ msg_fatal("bounce_diagnostic_log: %m");
+ if (bounce_header_dsn(message_stream, bounce_info) != 0)
+ msg_fatal("bounce_header_dsn: %m");
+ if (bounce_diagnostic_dsn(message_stream, bounce_info,
+ DSN_NOTIFY_OVERRIDE) <= 0)
+ msg_fatal("bounce_diagnostic_dsn: %m");
+ bounce_original(message_stream, bounce_info, DSN_RET_FULL);
+ if (vstream_fclose(message_stream) != 0)
+ msg_fatal("vstream_fclose: %m");
+ bounce_mail_free(bounce_info);
+
+ /*
+ * Render the bounce message in human-readable form.
+ */
+ if ((message_stream = vstream_memopen(message_buf, O_RDONLY)) == 0)
+ msg_fatal("vstream_memopen O_RDONLY: %m");
+ rec_buf = vstring_alloc(100);
+ while ((rec_type = rec_get(message_stream, rec_buf, 0)) > 0) {
+ switch (rec_type) {
+ case REC_TYPE_CONT:
+ vstream_printf("%.*s", (int) LEN(rec_buf), STR(rec_buf));
+ break;
+ case REC_TYPE_NORM:
+ vstream_printf("%.*s\n", (int) LEN(rec_buf), STR(rec_buf));
+ break;
+ default:
+ msg_panic("unexpected message record type %d", rec_type);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstream_fclose(message_stream) != 0)
+ msg_fatal("vstream_fclose: %m");
+ vstring_free(rec_buf);
+
+ /*
+ * Clean up.
+ */
+ exit(0);
+}
+
+int var_bounce_limit;
+int var_max_queue_time;
+int var_delay_warn_time;
+char *var_notify_classes;
+char *var_bounce_rcpt;
+char *var_2bounce_rcpt;
+char *var_delay_rcpt;
+char *var_bounce_tmpl;
+bool var_threaded_bounce;
+char *var_hfrom_format; /* header_from_format */
+
+int bounce_hfrom_format;
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0,
+ VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0,
+ VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce,
+ 0,
+ };
+
+ test_main(argc, argv, test_driver,
+ CA_TEST_MAIN_INT_TABLE(int_table),
+ CA_TEST_MAIN_STR_TABLE(str_table),
+ CA_TEST_MAIN_TIME_TABLE(time_table),
+ CA_TEST_MAIN_NBOOL_TABLE(nbool_table),
+ 0);
+
+ exit(0);
+}
diff --git a/src/bounce/bounce_notify_verp.c b/src/bounce/bounce_notify_verp.c
new file mode 100644
index 0000000..8bfd71b
--- /dev/null
+++ b/src/bounce/bounce_notify_verp.c
@@ -0,0 +1,267 @@
+/*++
+/* NAME
+/* bounce_notify_verp 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_notify_verp(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, verp_delims,
+/* templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* char *verp_delims;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_notify()
+/* (send bounce message) request. The logfile
+/* is removed after and a warning is posted.
+/* The bounce recipient address is encoded in VERP format.
+/* This routine must be used for single bounces only.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <verp_sender.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_notify_verp - send a bounce, VERP style */
+
+int bounce_notify_verp(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ char *verp_delims, BOUNCE_TEMPLATES *ts)
+{
+ const char *myname = "bounce_notify_verp";
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 0;
+ int postmaster_status;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ char *postmaster;
+ VSTRING *verp_buf;
+ VSTRING *new_id;
+
+ /*
+ * Sanity checks. We must be called only for undeliverable non-bounce
+ * messages.
+ */
+ if (*recipient == 0)
+ msg_panic("%s: attempt to bounce a single bounce", myname);
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0)
+ msg_panic("%s: attempt to bounce a double bounce", myname);
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ ts->failure);
+
+ /*
+ * If we have no recipient list then we can't send VERP replies. Send
+ * *something* anyway so that the mail is not lost in a black hole.
+ */
+ if (bounce_info->log_handle == 0) {
+ DSN_BUF *dsn_buf = dsb_create();
+ RCPT_BUF *rcpt_buf = rcpb_create();
+
+ dsb_simple(dsn_buf, "5.0.0", "(error report unavailable)");
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+ vstring_strcpy(rcpt_buf->address, "(recipient address unavailable)");
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ bounce_status = bounce_one_service(flags, queue_name, queue_id,
+ encoding, smtputf8, recipient,
+ dsn_envid, dsn_ret, rcpt_buf,
+ dsn_buf, ts);
+ rcpb_free(rcpt_buf);
+ dsb_free(dsn_buf);
+ bounce_mail_free(bounce_info);
+ return (bounce_status);
+ }
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * A non-bounce message was returned. Send a single bounce, one per
+ * recipient.
+ */
+ verp_buf = vstring_alloc(100);
+ new_id = vstring_alloc(10);
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ /*
+ * Notify the originator, subject to DSN NOTIFY restrictions.
+ *
+ * Fix 20090114: Use the Postfix original recipient, because that is
+ * what the VERP consumer expects.
+ */
+ if (rcpt->dsn_notify != 0 /* compat */
+ && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) {
+ bounce_status = 0;
+ } else {
+ verp_sender(verp_buf, verp_delims, recipient, rcpt);
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, STR(verp_buf),
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ if (bounce_header(bounce, bounce_info, STR(verp_buf),
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else
+ bounce_status = 1;
+
+ /*
+ * Stop at the first sign of trouble, instead of making the
+ * problem worse.
+ */
+ if (bounce_status != 0)
+ break;
+
+ /*
+ * Optionally, mark this recipient as done.
+ */
+ if (flags & BOUNCE_FLAG_DELRCPT)
+ bounce_delrcpt_one(bounce_info);
+ }
+
+ /*
+ * Optionally, send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else
+ postmaster_status = 1;
+
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Examine the completion status. Delete the bounce log file only when
+ * the bounce was posted successfully, and only if we are bouncing for
+ * real, not just warning.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(verp_buf);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_one_service.c b/src/bounce/bounce_one_service.c
new file mode 100644
index 0000000..29c4fc3
--- /dev/null
+++ b/src/bounce/bounce_one_service.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* bounce_one_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_one_service(flags, queue_name, queue_id, encoding,
+/* smtputf8, orig_sender, envid, ret,
+/* rcpt_buf, dsn_buf, templates)
+/* int flags;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *orig_sender;
+/* char *envid;
+/* int ret;
+/* RCPT_BUF *rcpt_buf;
+/* DSN_BUF *dsn_buf;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_one()
+/* (send bounce message for one recipient) request.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_one_service - send a bounce for one recipient */
+
+int bounce_one_service(int flags, char *queue_name, char *queue_id,
+ char *encoding, int smtputf8,
+ char *orig_sender, char *dsn_envid,
+ int dsn_ret, RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ */
+ bounce_info = bounce_mail_one_init(queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf,
+ dsn_buf, ts->failure);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of bounce sender address depends on the original sender
+ * address. For a single bounce (a non-delivery notification to the
+ * message originator), the sender address is the empty string. For a
+ * double bounce (typically a failed single bounce, or a postmaster
+ * notification that was produced by any of the mail processes) the
+ * sender address is defined by the var_double_bounce_sender
+ * configuration variable. When a double bounce cannot be delivered, the
+ * queue manager blackholes the resulting triple bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce original sender address and discard mail,
+ * or every delivery agent must recognize the double-bounce sender
+ * address and substitute something else so mail does not come back at
+ * us.
+ */
+ if (strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE)
+
+ else if (*orig_sender == 0) {
+ if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) {
+ bounce_status = 0;
+ } else {
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_2bounce_rcpt,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ if (!bounce_header(bounce, bounce_info, var_2bounce_rcpt,
+ POSTMASTER_COPY)
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY
+ * restrictions.
+ */
+ else {
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (rcpt->dsn_notify != 0 /* compat */
+ && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) {
+ bounce_status = 0;
+ } else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, orig_sender,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ if (bounce_header(bounce, bounce_info, orig_sender,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ }
+
+ /*
+ * Optionally send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE
+ && strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_bounce_rcpt,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ if (bounce_header(bounce, bounce_info, var_bounce_rcpt,
+ POSTMASTER_COPY) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, orig_sender);
+ }
+ }
+
+ /*
+ * Optionally, delete the recipient from the queue file.
+ */
+ if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT))
+ bounce_delrcpt_one(bounce_info);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_service.h b/src/bounce/bounce_service.h
new file mode 100644
index 0000000..b79542f
--- /dev/null
+++ b/src/bounce/bounce_service.h
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* bounce_service 3h
+/* SUMMARY
+/* bounce message service
+/* SYNOPSIS
+/* #include <bounce_service.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <bounce_log.h>
+
+ /*
+ * Application-specific.
+ */
+#include <bounce_template.h>
+
+ /*
+ * bounce_service.c
+ */
+extern int bounce_hfrom_format;
+
+ /*
+ * bounce_append_service.c
+ */
+extern int bounce_append_service(int, char *, char *, RECIPIENT *, DSN *);
+
+ /*
+ * bounce_notify_service.c
+ */
+extern int bounce_notify_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_warn_service.c
+ */
+extern int bounce_warn_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_trace_service.c
+ */
+extern int bounce_trace_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_notify_verp.c
+ */
+extern int bounce_notify_verp(int, char *, char *, char *, char *, int, char *, char *, int, char *, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_one_service.c
+ */
+extern int bounce_one_service(int, char *, char *, char *, int, char *, char *, int, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_cleanup.c
+ */
+extern VSTRING *bounce_cleanup_path;
+extern void bounce_cleanup_register(char *, char *);
+extern void bounce_cleanup_log(void);
+extern void bounce_cleanup_unregister(void);
+
+#define bounce_cleanup_registered() (bounce_cleanup_path != 0)
+
+ /*
+ * bounce_notify_util.c
+ */
+typedef struct {
+ const char *service; /* bounce or defer */
+ const char *queue_name; /* incoming, etc. */
+ const char *queue_id; /* base name */
+ const char *mime_encoding; /* null or encoding */
+ const char *dsn_envid; /* DSN envelope ID */
+ const char *mime_boundary; /* for MIME */
+ BOUNCE_TEMPLATE *template; /* bounce message template */
+ VSTRING *buf; /* scratch pad */
+ VSTRING *sender; /* envelope sender */
+ VSTREAM *orig_fp; /* open queue file */
+ long orig_offs; /* start of content */
+ time_t arrival_time; /* time of arrival */
+ long message_size; /* size of content */
+ VSTRING *orig_msgid; /* original message-id */
+ RCPT_BUF *rcpt_buf; /* recipient info */
+ DSN_BUF *dsn_buf; /* delivery status info */
+ BOUNCE_LOG *log_handle; /* open logfile */
+ char *mail_name; /* $mail_name, cooked */
+ int smtputf8; /* SMTPUTF8 requested */
+} BOUNCE_INFO;
+
+ /* */
+
+extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, const char *, int, const char *, BOUNCE_TEMPLATE *);
+extern BOUNCE_INFO *bounce_mail_one_init(const char *, const char *, const char *, int, const char *, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATE *);
+extern void bounce_mail_free(BOUNCE_INFO *);
+extern int bounce_header(VSTREAM *, BOUNCE_INFO *, const char *, int);
+extern int bounce_boilerplate(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_recipient_log(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_diagnostic_log(VSTREAM *, BOUNCE_INFO *, int);
+extern int bounce_header_dsn(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_recipient_dsn(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_diagnostic_dsn(VSTREAM *, BOUNCE_INFO *, int);
+extern int bounce_original(VSTREAM *, BOUNCE_INFO *, int);
+extern void bounce_delrcpt(BOUNCE_INFO *);
+extern void bounce_delrcpt_one(BOUNCE_INFO *);
+
+/* 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
+/*--*/
diff --git a/src/bounce/bounce_template.c b/src/bounce/bounce_template.c
new file mode 100644
index 0000000..67a5cf7
--- /dev/null
+++ b/src/bounce/bounce_template.c
@@ -0,0 +1,548 @@
+/*++
+/* NAME
+/* bounce_template 3
+/* SUMMARY
+/* bounce template support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/*
+/* BOUNCE_TEMPLATE *bounce_template_create(def_template)
+/* const BOUNCE_TEMPLATE *def_template;
+/*
+/* void bounce_template_free(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_load(template, stream, buffer, origin)
+/* BOUNCE_TEMPLATE *template;
+/* VSTREAM *stream;
+/* const char *buffer;
+/* const char *origin;
+/*
+/* void bounce_template_headers(out_fn, stream, template,
+/* rcpt, postmaster_copy)
+/* int (*out_fn)(VSTREAM *, const char *, ...);
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/* const char *rcpt;
+/* int postmaster_copy;
+/*
+/* const char *bounce_template_encoding(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* const char *bounce_template_charset(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_expand(out_fn, stream, template)
+/* int (*out_fn)(VSTREAM *, const char *);
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_dump(stream, template)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/*
+/* int IS_FAILURE_TEMPLATE(template)
+/* int IS_DELAY_TEMPLATE(template)
+/* int IS_SUCCESS_TEMPLATE(template)
+/* BOUNCE_TEMPLATE *template;
+/* DESCRIPTION
+/* This module implements the built-in and external bounce
+/* message template support. The content of a template are
+/* private. To access information within a template, use
+/* the API described in this document.
+/*
+/* bounce_template_create() creates a template, with the
+/* specified default settings. The template defaults are not
+/* copied.
+/*
+/* bounce_template_free() destroys a bounce message template.
+/*
+/* bounce_template_load() overrides a bounce template with the
+/* specified buffer from the specified origin. The buffer and
+/* origin are copied. Specify a null buffer and origin pointer
+/* to reset the template to the defaults specified with
+/* bounce_template_create().
+/*
+/* bounce_template_headers() sends the postmaster or non-postmaster
+/* From/Subject/To message headers to the specified stream.
+/* The recipient address is expected to be in RFC822 external
+/* form. The postmaster_copy argument is one of POSTMASTER_COPY
+/* or NO_POSTMASTER_COPY.
+/*
+/* bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT
+/* or MAIL_ATTR_ENC_8BIT) for the bounce template message text.
+/*
+/* bounce_template_charset() returns the character set for the
+/* bounce template message text.
+/*
+/* bounce_template_expand() expands the body text of the
+/* specified template and writes the result to the specified
+/* stream.
+/*
+/* bounce_template_dump() dumps the specified template to the
+/* specified stream.
+/*
+/* The IS_MUMBLE_TEMPLATE() macros are predicates that
+/* determine whether the template is of the specified type.
+/* DIAGNOSTICS
+/* Fatal error: out of memory, undefined macro name in template.
+/* SEE ALSO
+/* bounce_templates(3) bounce template group support
+/* 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 <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mac_expand.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#ifndef NO_EAI
+#include <midna_domain.h>
+#endif
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <mail_conf.h>
+#include <is_header.h>
+#include <hfrom_format.h>
+
+/* Application-specific. */
+
+#include <bounce_template.h>
+#include <bounce_service.h>
+
+ /*
+ * The following tables implement support for bounce template expansions of
+ * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of
+ * these is the actual parameter value divided by the number of seconds in a
+ * day (hour, etc.), so that we can produce nicely formatted bounce messages
+ * with time values converted into the appropriate units.
+ *
+ * Ideally, the bounce template processor would strip the _days etc. suffix
+ * from the parameter name, and use the parameter name to look up the actual
+ * parameter value and its default value (the default value specifies the
+ * default time unit of that parameter (seconds, minutes, etc.)), and use
+ * this to convert the parameter string value into the corresponding number
+ * of seconds. The bounce template processor would then use the _hours etc.
+ * suffix from the bounce template to divide this number by the number of
+ * seconds in an hour, etc. and produce the number that is needed for the
+ * template.
+ *
+ * Unfortunately, there exists no code to look up default values by parameter
+ * name. If such code existed, then we could do the _days, _hours, etc.
+ * conversion with every main.cf time parameter without having to know in
+ * advance what time parameter names exist.
+ *
+ * So we have to either maintain our own table of all time related main.cf
+ * parameter names and defaults (like the postconf command does) or we make
+ * a special case for a few parameters of special interest.
+ *
+ * We go for the second solution. There are only a few parameters that need
+ * this treatment, and there will be more special cases when individual
+ * queue files get support for individual expiration times, and when other
+ * queue file information needs to be reported in bounce template messages.
+ *
+ * A really lame implementation would simply strip the optional s, h, d, etc.
+ * suffix from the actual (string) parameter value and not do any conversion
+ * at all to hours, days or weeks. But then the information in delay warning
+ * notices could be seriously incorrect.
+ */
+typedef struct {
+ const char *suffix; /* days, hours, etc. */
+ int suffix_len; /* byte count */
+ int divisor; /* divisor */
+} BOUNCE_TIME_DIVISOR;
+
+#define STRING_AND_LEN(x) (x), (sizeof(x) - 1)
+
+static const BOUNCE_TIME_DIVISOR time_divisors[] = {
+ STRING_AND_LEN("seconds"), 1,
+ STRING_AND_LEN("minutes"), 60,
+ STRING_AND_LEN("hours"), 60 * 60,
+ STRING_AND_LEN("days"), 24 * 60 * 60,
+ STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60,
+ 0, 0,
+};
+
+ /*
+ * The few special-case main.cf parameters that have support for _days, etc.
+ * suffixes for automatic conversion when expanded into a bounce template.
+ */
+typedef struct {
+ const char *param_name; /* parameter name */
+ int param_name_len; /* name length */
+ int *value; /* parameter value */
+} BOUNCE_TIME_PARAMETER;
+
+static const BOUNCE_TIME_PARAMETER time_parameter[] = {
+ STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time,
+ STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time,
+ 0, 0,
+};
+
+ /*
+ * Parameters whose value may have to be converted to UTF-8 for presentation
+ * purposes.
+ */
+typedef struct {
+ const char *param_name; /* parameter name */
+ char **value; /* parameter value */
+} BOUNCE_STR_PARAMETER;
+
+static const BOUNCE_STR_PARAMETER str_parameter[] = {
+ VAR_MYHOSTNAME, &var_myhostname,
+ VAR_MYDOMAIN, &var_mydomain,
+ 0, 0,
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* bounce_template_create - create one template */
+
+BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp));
+ *tp = *prototype;
+ return (tp);
+}
+
+/* bounce_template_free - destroy one template */
+
+void bounce_template_free(BOUNCE_TEMPLATE *tp)
+{
+ if (tp->buffer) {
+ myfree(tp->buffer);
+ myfree((void *) tp->origin);
+ }
+ myfree((void *) tp);
+}
+
+/* bounce_template_reset - reset template to default */
+
+static void bounce_template_reset(BOUNCE_TEMPLATE *tp)
+{
+ myfree(tp->buffer);
+ myfree((void *) tp->origin);
+ *tp = *(tp->prototype);
+}
+
+/* bounce_template_load - override one template */
+
+void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin,
+ const char *buffer)
+{
+
+ /*
+ * Clean up after a previous call.
+ */
+ if (tp->buffer)
+ bounce_template_reset(tp);
+
+ /*
+ * Postpone the work of template parsing until it is really needed. Most
+ * bounce service calls never need a template.
+ */
+ if (buffer && origin) {
+ tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER;
+ tp->buffer = mystrdup(buffer);
+ tp->origin = mystrdup(origin);
+ }
+}
+
+/* bounce_template_parse_buffer - initialize template */
+
+static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp)
+{
+ char *tval = tp->buffer;
+ char *cp;
+ char **cpp;
+ int cpp_len;
+ int cpp_used;
+ int hlen;
+ char *hval;
+
+ /*
+ * Sanity check.
+ */
+ if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0)
+ msg_panic("bounce_template_parse_buffer: nothing to do here");
+ tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER;
+
+ /*
+ * Discard the unusable template and use the default one instead.
+ */
+#define CLEANUP_AND_RETURN() do { \
+ bounce_template_reset(tp); \
+ return; \
+ } while (0)
+
+ /*
+ * Parse pseudo-header labels and values.
+ *
+ * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8
+ * message. Sending SMTPUTF8 in response to non-SMTPUTF8 mail would make
+ * no sense.
+ */
+#define GETLINE(line, buf) \
+ (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0)
+
+ while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) {
+ for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++)
+ *hval = 0;
+ if (*hval == 0) {
+ msg_warn("%s: empty \"%s\" header value in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ if (!allascii(hval)) {
+ msg_warn("%s: non-ASCII \"%s\" header value in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ if (strcasecmp("charset", cp) == 0) {
+ tp->mime_charset = hval;
+ } else if (strcasecmp("from", cp) == 0) {
+ tp->std_from = tp->obs_from = hval;
+ } else if (strcasecmp("subject", cp) == 0) {
+ tp->subject = hval;
+ } else if (strcasecmp("postmaster-subject", cp) == 0) {
+ if (tp->postmaster_subject == 0) {
+ msg_warn("%s: inapplicable \"%s\" header label in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ tp->postmaster_subject = hval;
+ } else {
+ msg_warn("%s: unknown \"%s\" header label in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ }
+
+ /*
+ * Skip blank lines between header and message text.
+ */
+ while (cp && (*cp == 0 || allspace(cp)))
+ (void) GETLINE(cp, tval);
+ if (cp == 0) {
+ msg_warn("%s: missing message text in %s template "
+ "-- ignoring this template",
+ tp->origin, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+
+ /*
+ * Is this 7bit or 8bit text? If the character set is US-ASCII, then
+ * don't allow 8bit text. Don't assume 8bit when charset was changed.
+ */
+#define NON_ASCII(p) ((p) && *(p) && !allascii((p)))
+
+ if (NON_ASCII(cp) || NON_ASCII(tval)) {
+ if (strcasecmp(tp->mime_charset, "us-ascii") == 0) {
+ msg_warn("%s: 8-bit message text in %s template",
+ tp->origin, tp->class);
+ msg_warn("please specify a charset value other than us-ascii");
+ msg_warn("-- ignoring this template for now");
+ CLEANUP_AND_RETURN();
+ }
+ tp->mime_encoding = MAIL_ATTR_ENC_8BIT;
+ }
+
+ /*
+ * Collect the message text and null-terminate the result.
+ */
+ cpp_len = 10;
+ cpp_used = 0;
+ cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len);
+ while (cp) {
+ cpp[cpp_used++] = cp;
+ if (cpp_used >= cpp_len) {
+ cpp = (char **) myrealloc((void *) cpp,
+ sizeof(*cpp) * 2 * cpp_len);
+ cpp_len *= 2;
+ }
+ (void) GETLINE(cp, tval);
+ }
+ cpp[cpp_used] = 0;
+ tp->message_text = (const char **) cpp;
+}
+
+/* bounce_template_lookup - lookup $name value */
+
+static const char *bounce_template_lookup(const char *key, int unused_mode,
+ void *context)
+{
+ BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context;
+ const BOUNCE_TIME_PARAMETER *bp;
+ const BOUNCE_TIME_DIVISOR *bd;
+ const BOUNCE_STR_PARAMETER *sp;
+ static VSTRING *buf;
+ int result;
+ const char *asc_val;
+ const char *utf8_val;
+
+ /*
+ * Look for parameter names that can have a time unit suffix, and scale
+ * the time value according to the suffix.
+ */
+ for (bp = time_parameter; bp->param_name; bp++) {
+ if (strncmp(key, bp->param_name, bp->param_name_len) == 0
+ && key[bp->param_name_len] == '_') {
+ for (bd = time_divisors; bd->suffix; bd++) {
+ if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) {
+ result = bp->value[0] / bd->divisor;
+ if (result > 999 && bd->divisor < 86400) {
+ msg_warn("%s: excessive result \"%d\" in %s "
+ "template conversion of parameter \"%s\"",
+ tp->origin, result, tp->class, key);
+ msg_warn("please increase time unit \"%s\" of \"%s\" "
+ "in %s template", bd->suffix, key, tp->class);
+ msg_warn("for instructions see the bounce(5) manual");
+ } else if (result == 0 && bp->value[0] && bd->divisor > 1) {
+ msg_warn("%s: zero result in %s template "
+ "conversion of parameter \"%s\"",
+ tp->origin, tp->class, key);
+ msg_warn("please reduce time unit \"%s\" of \"%s\" "
+ "in %s template", bd->suffix, key, tp->class);
+ msg_warn("for instructions see the bounce(5) manual");
+ }
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_sprintf(buf, "%d", result);
+ return (STR(buf));
+ }
+ }
+ msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"",
+ tp->origin,
+ key + bp->param_name_len + 1, key);
+ }
+ }
+
+ /*
+ * Look for parameter names that may have to be up-converted for
+ * presentation purposes.
+ */
+#ifndef NO_EAI
+ if (var_smtputf8_enable) {
+ for (sp = str_parameter; sp->param_name; sp++) {
+ if (strcmp(key, sp->param_name) == 0) {
+ asc_val = sp->value[0];
+ if (!allascii(asc_val)) {
+ msg_warn("%s: conversion \"%s\" failed: "
+ "non-ASCII input value: \"%s\"",
+ tp->origin, key, asc_val);
+ return (asc_val);
+ } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) {
+ msg_warn("%s: conversion \"%s\" failed: "
+ "input value: \"%s\"",
+ tp->origin, key, asc_val);
+ return (asc_val);
+ } else {
+ return (utf8_val);
+ }
+ }
+ }
+ }
+#endif
+ return (mail_conf_lookup_eval(key));
+}
+
+/* bounce_template_headers - send template headers */
+
+void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp,
+ BOUNCE_TEMPLATE *tp,
+ const char *rcpt,
+ int postmaster_copy)
+{
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ out_fn(fp, "From: %s", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ?
+ tp->std_from : tp->obs_from);
+ out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ?
+ tp->postmaster_subject : tp->subject);
+ out_fn(fp, "To: %s", rcpt);
+}
+
+/* bounce_template_expand - expand template to stream */
+
+void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp,
+ BOUNCE_TEMPLATE *tp)
+{
+ VSTRING *buf = vstring_alloc(100);
+ const char **cpp;
+ int stat;
+
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ for (cpp = tp->message_text; *cpp; cpp++) {
+ stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0,
+ bounce_template_lookup, (void *) tp);
+ if (stat & MAC_PARSE_ERROR)
+ msg_fatal("%s: bad $name syntax in %s template: %s",
+ tp->origin, tp->class, *cpp);
+ if (stat & MAC_PARSE_UNDEF)
+ msg_fatal("%s: undefined $name in %s template: %s",
+ tp->origin, tp->class, *cpp);
+ out_fn(fp, STR(buf));
+ }
+ vstring_free(buf);
+}
+
+/* bounce_template_dump - dump template to stream */
+
+void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp)
+{
+ const char **cpp;
+
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset);
+ vstream_fprintf(fp, "From: %s\n", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ?
+ tp->std_from : tp->obs_from);
+ vstream_fprintf(fp, "Subject: %s\n", tp->subject);
+ if (tp->postmaster_subject)
+ vstream_fprintf(fp, "Postmaster-Subject: %s\n",
+ tp->postmaster_subject);
+ vstream_fprintf(fp, "\n");
+ for (cpp = tp->message_text; *cpp; cpp++)
+ vstream_fprintf(fp, "%s\n", *cpp);
+}
diff --git a/src/bounce/bounce_template.h b/src/bounce/bounce_template.h
new file mode 100644
index 0000000..87835fc
--- /dev/null
+++ b/src/bounce/bounce_template.h
@@ -0,0 +1,99 @@
+#ifndef _BOUNCE_TEMPLATE_H_INCLUDED_
+#define _BOUNCE_TEMPLATE_H_INCLUDED_
+
+/*++
+/* NAME
+/* bounce_template 3h
+/* SUMMARY
+/* bounce template support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * Structure of a single bounce template. Each template is manipulated by
+ * itself, without any external markers and delimiters. Applications are not
+ * supposed to access BOUNCE_TEMPLATE attributes directly.
+ */
+typedef struct BOUNCE_TEMPLATE {
+ int flags;
+ const char *class; /* for diagnostics (fixed) */
+ const char *origin; /* built-in or pathname */
+ const char *mime_charset; /* character set (configurable) */
+ const char *mime_encoding; /* 7bit or 8bit (derived) */
+ const char *obs_from; /* originator (configurable) */
+ const char *std_from; /* originator (configurable) */
+ const char *subject; /* general subject (configurable) */
+ const char *postmaster_subject; /* postmaster subject (configurable) */
+ const char **message_text; /* message text (configurable) */
+ const struct BOUNCE_TEMPLATE *prototype; /* defaults */
+ char *buffer; /* ripped text */
+} BOUNCE_TEMPLATE;
+
+#define BOUNCE_TMPL_FLAG_NEW_BUFFER (1<<0)
+
+#define BOUNCE_TMPL_CLASS_FAILURE "failure"
+#define BOUNCE_TMPL_CLASS_DELAY "delay"
+#define BOUNCE_TMPL_CLASS_SUCCESS "success"
+#define BOUNCE_TMPL_CLASS_VERIFY "verify"
+
+#define IS_FAILURE_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_FAILURE[0])
+#define IS_DELAY_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_DELAY[0])
+#define IS_SUCCESS_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_SUCCESS[0])
+
+#define bounce_template_encoding(t) ((t)->mime_encoding)
+#define bounce_template_charset(t) ((t)->mime_charset)
+
+typedef int PRINTFPTRLIKE(2, 3) (*BOUNCE_XP_PRN_FN) (VSTREAM *, const char *,...);
+typedef int (*BOUNCE_XP_PUT_FN) (VSTREAM *, const char *);
+
+extern BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *);
+extern void bounce_template_free(BOUNCE_TEMPLATE *);
+extern void bounce_template_load(BOUNCE_TEMPLATE *, const char *, const char *);
+extern void bounce_template_headers(BOUNCE_XP_PRN_FN, VSTREAM *, BOUNCE_TEMPLATE *, const char *, int);
+extern void bounce_template_expand(BOUNCE_XP_PUT_FN, VSTREAM *, BOUNCE_TEMPLATE *);
+extern void bounce_template_dump(VSTREAM *, BOUNCE_TEMPLATE *);
+
+#define POSTMASTER_COPY 1 /* postmaster copy */
+#define NO_POSTMASTER_COPY 0 /* not postmaster copy */
+
+ /*
+ * Structure of a bounce template collection. These templates are read and
+ * written in their external representation, with markers and delimiters.
+ */
+typedef struct {
+ BOUNCE_TEMPLATE *failure;
+ BOUNCE_TEMPLATE *delay;
+ BOUNCE_TEMPLATE *success;
+ BOUNCE_TEMPLATE *verify;
+} BOUNCE_TEMPLATES;
+
+BOUNCE_TEMPLATES *bounce_templates_create(void);
+void bounce_templates_free(BOUNCE_TEMPLATES *);
+void bounce_templates_load(VSTREAM *, BOUNCE_TEMPLATES *);
+void bounce_templates_expand(VSTREAM *, BOUNCE_TEMPLATES *);
+void bounce_templates_dump(VSTREAM *, BOUNCE_TEMPLATES *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/bounce/bounce_templates.c b/src/bounce/bounce_templates.c
new file mode 100644
index 0000000..f81dfd4
--- /dev/null
+++ b/src/bounce/bounce_templates.c
@@ -0,0 +1,399 @@
+/*++
+/* NAME
+/* bounce_templates 3
+/* SUMMARY
+/* bounce template group support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/*
+/* typedef struct {
+/* .in +4
+/* BOUNCE_TEMPLATE *failure;
+/* BOUNCE_TEMPLATE *delay;
+/* BOUNCE_TEMPLATE *success;
+/* BOUNCE_TEMPLATE *verify;
+/* .in -4
+/* } BOUNCE_TEMPLATES;
+/*
+/* BOUNCE_TEMPLATES *bounce_templates_create(void)
+/*
+/* void bounce_templates_free(templates)
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_load(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_expand(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_dump(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements support for bounce template groups
+/* (i.e. groups that contain one template of each type).
+/*
+/* bounce_templates_create() creates a bounce template group,
+/* with default settings.
+/*
+/* bounce_templates_free() destroys a bounce template group.
+/*
+/* bounce_templates_load() reads zero or more bounce templates
+/* from the specified file to override built-in templates.
+/*
+/* bounce_templates_expand() expands $name macros and writes
+/* the text portions of the specified bounce template group
+/* to the specified stream.
+/*
+/* bounce_templates_dump() writes the complete content of the
+/* specified bounce template group to the specified stream.
+/* The format is compatible with bounce_templates_load().
+/* DIAGNOSTICS
+/* Fatal error: out of memory, undefined macro name in template.
+/* SEE ALSO
+/* bounce_template(3) bounce template support
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+/* Global library. */
+
+#include <mail_addr.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <bounce_template.h>
+
+ /*
+ * The fail template is for permanent failure.
+ */
+static const char *def_bounce_failure_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "I'm sorry to have to inform you that your message could not",
+ "be delivered to one or more recipients. It's attached below.",
+ "",
+ "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".",
+ "",
+ "If you do so, please include this problem report. You can",
+ "delete your own text from the attached returned message.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_failure_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_FAILURE,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Undelivered Mail Returned to Sender",
+ "Postmaster Copy: Undelivered Mail",
+ def_bounce_failure_body,
+ &def_bounce_failure_template,
+};
+
+ /*
+ * The delay template is for delayed mail notifications.
+ */
+static const char *def_bounce_delay_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "####################################################################",
+ "# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #",
+ "####################################################################",
+ "",
+ "Your message could not be delivered for more than $delay_warning_time_hours hour(s).",
+ "It will be retried until it is $maximal_queue_lifetime_days day(s) old.",
+ "",
+ "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".",
+ "",
+ "If you do so, please include this problem report. You can",
+ "delete your own text from the attached returned message.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_delay_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_DELAY,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Delayed Mail (still being retried)",
+ "Postmaster Warning: Delayed Mail",
+ def_bounce_delay_body,
+ &def_bounce_delay_template
+};
+
+ /*
+ * The success template is for "delivered", "expanded" and "relayed" success
+ * notifications.
+ */
+static const char *def_bounce_success_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "Your message was successfully delivered to the destination(s)",
+ "listed below. If the message was delivered to mailbox you will",
+ "receive no further notifications. Otherwise you may still receive",
+ "notifications of mail delivery errors from other systems.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_success_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_SUCCESS,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Successful Mail Delivery Report",
+ 0,
+ def_bounce_success_body,
+ &def_bounce_success_template,
+};
+
+ /*
+ * The "verify" template is for verbose delivery (sendmail -v) and for
+ * address verification (sendmail -bv).
+ */
+static const char *def_bounce_verify_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "Enclosed is the mail delivery report that you requested.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_verify_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_VERIFY,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Mail Delivery Status Report",
+ 0,
+ def_bounce_verify_body,
+ &def_bounce_verify_template,
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* bounce_templates_create - create template group */
+
+BOUNCE_TEMPLATES *bounce_templates_create(void)
+{
+ BOUNCE_TEMPLATES *bs;
+
+ bs = (BOUNCE_TEMPLATES *) mymalloc(sizeof(*bs));
+ bs->failure = bounce_template_create(&def_bounce_failure_template);
+ bs->delay = bounce_template_create(&def_bounce_delay_template);
+ bs->success = bounce_template_create(&def_bounce_success_template);
+ bs->verify = bounce_template_create(&def_bounce_verify_template);
+ return (bs);
+}
+
+/* bounce_templates_free - destroy template group */
+
+void bounce_templates_free(BOUNCE_TEMPLATES *bs)
+{
+ bounce_template_free(bs->failure);
+ bounce_template_free(bs->delay);
+ bounce_template_free(bs->success);
+ bounce_template_free(bs->verify);
+ myfree((void *) bs);
+}
+
+/* bounce_templates_load - load template or group from stream */
+
+void bounce_templates_load(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ VSTRING *line_buf;
+ char *member_name;
+ VSTRING *multi_line_buf = 0;
+ VSTRING *saved_member_name = 0;
+ VSTRING *saved_end_marker = 0;
+ char *value;
+ int lineno;
+ const char *err;
+ char *cp;
+ int len; /* Grr... */
+
+ /*
+ * XXX That's a lot of non-reusable code to parse a configuration file.
+ * Unfortunately, much of the "name = value" infrastructure is married to
+ * the dict(3) class which doesn't really help here.
+ */
+ line_buf = vstring_alloc(100);
+ lineno = 1;
+ while (vstring_get_nonl(line_buf, fp) > 0) {
+ lineno++;
+ cp = STR(line_buf) + strspn(STR(line_buf), " \t\n\v\f\r");
+ if (*cp == 0 || *cp == '#')
+ continue;
+ if ((err = split_nameval(STR(line_buf), &member_name, &value)) != 0)
+ msg_fatal("%s, line %d: %s: \"%s\"",
+ VSTREAM_PATH(fp), lineno, err, STR(line_buf));
+ if (value[0] == '<' && value[1] == '<') {
+ value += 2;
+ while (ISSPACE(*value))
+ value++;
+ if (*value == 0)
+ msg_fatal("%s, line %d: missing end marker after <<",
+ VSTREAM_PATH(fp), lineno);
+ if (!ISALNUM(*value))
+ msg_fatal("%s, line %d: malformed end marker after <<",
+ VSTREAM_PATH(fp), lineno);
+ if (multi_line_buf == 0) {
+ saved_member_name = vstring_alloc(100);
+ saved_end_marker = vstring_alloc(100);
+ multi_line_buf = vstring_alloc(100);
+ } else
+ VSTRING_RESET(multi_line_buf);
+ vstring_strcpy(saved_member_name, member_name);
+ vstring_strcpy(saved_end_marker, value);
+ while (vstring_get_nonl(line_buf, fp) > 0) {
+ lineno++;
+ if (strcmp(STR(line_buf), STR(saved_end_marker)) == 0)
+ break;
+ if (VSTRING_LEN(multi_line_buf) > 0)
+ vstring_strcat(multi_line_buf, "\n");
+ vstring_strcat(multi_line_buf, STR(line_buf));
+ }
+ if (vstream_feof(fp))
+ msg_warn("%s, line %d: missing \"%s\" end marker",
+ VSTREAM_PATH(fp), lineno, value);
+ member_name = STR(saved_member_name);
+ value = STR(multi_line_buf);
+ }
+#define MATCH_TMPL_NAME(tname, tname_len, mname) \
+ (strncmp(tname, mname, tname_len = strlen(tname)) == 0 \
+ && strcmp(mname + tname_len, "_template") == 0)
+
+ if (MATCH_TMPL_NAME(ts->failure->class, len, member_name))
+ bounce_template_load(ts->failure, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->delay->class, len, member_name))
+ bounce_template_load(ts->delay, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->success->class, len, member_name))
+ bounce_template_load(ts->success, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->verify->class, len, member_name))
+ bounce_template_load(ts->verify, VSTREAM_PATH(fp), value);
+ else
+ msg_warn("%s, line %d: unknown template name: %s "
+ "-- ignoring this template",
+ VSTREAM_PATH(fp), lineno, member_name);
+ }
+ vstring_free(line_buf);
+ if (multi_line_buf) {
+ vstring_free(saved_member_name);
+ vstring_free(saved_end_marker);
+ vstring_free(multi_line_buf);
+ }
+}
+
+/* bounce_plain_out - output line as plain text */
+
+static int bounce_plain_out(VSTREAM *fp, const char *text)
+{
+ vstream_fprintf(fp, "%s\n", text);
+ return (0);
+}
+
+/* bounce_templates_expand - dump expanded template group text to stream */
+
+void bounce_templates_expand(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = ts->failure;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->delay;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->success;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->verify;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n");
+}
+
+/* bounce_templates_dump - dump bounce template group to stream */
+
+void bounce_templates_dump(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = ts->failure;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->delay;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->success;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->verify;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n");
+}
diff --git a/src/bounce/bounce_trace_service.c b/src/bounce/bounce_trace_service.c
new file mode 100644
index 0000000..8a62305
--- /dev/null
+++ b/src/bounce/bounce_trace_service.c
@@ -0,0 +1,220 @@
+/*++
+/* NAME
+/* bounce_trace_service 3
+/* SUMMARY
+/* send status report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_trace_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, envid,
+/* ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *envid;
+/* int ret;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the trace_flush()
+/* (send delivery notice) request. The logfile
+/* is removed after the notice is posted.
+/*
+/* A status report includes a prelude with human-readable text,
+/* a DSN-style report, and the email message that was subject of
+/* the status report.
+/*
+/* When a status report is sent, the sender address is the empty
+/* address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+#include <deliver_request.h> /* USR_VRFY and RECORD flags */
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_trace_service - send a delivery status notice */
+
+int bounce_trace_service(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8,
+ char *recipient, char *dsn_envid,
+ int unused_dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id;
+ int count;
+ const char *sender;
+
+ /*
+ * For consistency with fail/delay notifications, send notification for a
+ * non-bounce message as a single-bounce message, send notification for a
+ * single-bounce message as a double-bounce message, and drop requests to
+ * send notification for a double-bounce message.
+ */
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_info("%s: not sending trace/success notification for "
+ "double-bounce message", queue_id);
+ return (0);
+ } else if (*recipient == 0) {
+ if ((notify_mask & MAIL_ERROR_2BOUNCE) != 0) {
+ recipient = var_2bounce_rcpt;
+ sender = mail_addr_double_bounce();
+ } else {
+ msg_info("%s: not sending trace/success notification "
+ "for single-bounce message", queue_id);
+ if (mail_queue_remove(service, queue_id) && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+ return (0);
+ }
+ } else {
+ /* Always send notification for non-bounce message. */
+ sender = NULL_SENDER;
+ }
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN The trace service produces information from the trace logfile
+ * which is used for three types of reports:
+ *
+ * a) "what-if" reports that show what would happen without actually
+ * delivering mail (sendmail -bv).
+ *
+ * b) A report of actual deliveries (sendmail -v).
+ *
+ * c) DSN NOTIFY=SUCCESS reports of successful delivery ("delivered",
+ * "expanded" or "relayed").
+ */
+#define NON_DSN_FLAGS (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD)
+
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ flags & NON_DSN_FLAGS ?
+ ts->verify : ts->success);
+
+ /*
+ * XXX With multi-recipient mail some queue file recipients may have
+ * NOTIFY=SUCCESS and others not. Depending on what subset of recipients
+ * are delivered, a trace file may or may not be created. Even when the
+ * last partial delivery attempt had no NOTIFY=SUCCESS recipients, a
+ * trace file may still exist from a previous partial delivery attempt.
+ * So as long as any recipient in the original queue file had
+ * NOTIFY=SUCCESS we have to always look for the trace file and be
+ * prepared for the file not to exist.
+ *
+ * See also comments in qmgr/qmgr_active.c.
+ */
+ if (bounce_info->log_handle == 0) {
+ if (msg_verbose)
+ msg_info("%s: no trace file -- not sending a notification",
+ queue_id);
+ bounce_mail_free(bounce_info);
+ return (0);
+ }
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * Send a single bounce with a template message header, some boilerplate
+ * text that pretends that we are a polite mail system, the text with
+ * per-recipient status, and a copy of the original message.
+ *
+ * XXX DSN We use the same trace file for "what-if", "verbose delivery" and
+ * "success" delivery reports. This saves file system overhead because
+ * there are fewer potential left-over files to remove up when we create
+ * a new queue file.
+ */
+ new_id = vstring_alloc(10);
+ if ((bounce = post_mail_fopen_nowait(sender, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender delivery status notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Examine the completion status. Delete the trace log file only when the
+ * status notice was posted successfully.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_warn_service.c b/src/bounce/bounce_warn_service.c
new file mode 100644
index 0000000..bbb805d
--- /dev/null
+++ b/src/bounce/bounce_warn_service.c
@@ -0,0 +1,295 @@
+/*++
+/* NAME
+/* bounce_warn_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_warn_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, envid,
+/* dsn_ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *envid;
+/* int dsn_ret;
+/* BOUNCE_TEMPLATES *ts;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_warn()
+/* (send delay notice) request. The logfile
+/* is not removed, and a warning is sent instead of a bounce.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* 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 <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_warn_service - send a delayed mail notice */
+
+int bounce_warn_service(int unused_flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+ char *postmaster;
+ int count;
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN This service produces RFC 3464-style "delayed mail" reports from
+ * information in the defer logfile. That same file is used for three
+ * different types of report:
+ *
+ * a) On-demand reports of all delayed deliveries by the mailq(1) command.
+ * This reports all recipients that have a transient delivery error.
+ *
+ * b) RFC 3464-style "delayed mail" notifications by the defer(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=DELAY; this reports to postmaster all recipients, subject to
+ * notify_classes restrictions.
+ *
+ * c) RFC 3464-style bounce reports by the bounce(8) service when mail is
+ * too old. This reports to the sender all recipients that have no DSN
+ * NOTIFY information (compatibility) and all recipients that have DSN
+ * NOTIFY=FAILURE; this reports to postmaster all recipients, subject to
+ * notify_classes restrictions.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid, ts->delay);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of sender address depends on the recipient address. For a
+ * single bounce (a non-delivery notification to the message originator),
+ * the sender address is the empty string. For a double bounce (typically
+ * a failed single bounce, or a postmaster notification that was produced
+ * by any of the mail processes) the sender address is defined by the
+ * var_double_bounce_sender configuration variable. When a double bounce
+ * cannot be delivered, the queue manager blackholes the resulting triple
+ * bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce recipient address and discard mail, or
+ * every delivery agent must recognize the double-bounce sender address
+ * and substitute something else so mail does not come back at us.
+ */
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_DELAY_NOTICE (notify_mask & MAIL_ERROR_DELAY)
+
+ else if (*recipient == 0) {
+ if (!SEND_POSTMASTER_DELAY_NOTICE) {
+ bounce_status = 0;
+ } else {
+ postmaster = var_delay_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ count = -1;
+ if (!bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY)
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY
+ * restrictions.
+ */
+ else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_DELAY)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_DELAY) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Optionally send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+ if (bounce_status == 0 && SEND_POSTMASTER_DELAY_NOTICE
+ && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_delay_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ postmaster_status = 0;
+ }
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while warning %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/logfile-no-msgid-no-eoh-event b/src/bounce/logfile-no-msgid-no-eoh-event
new file mode 100644
index 0000000..50233c8
--- /dev/null
+++ b/src/bounce/logfile-no-msgid-no-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 272
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-no-msgid-with-eoh-event b/src/bounce/logfile-no-msgid-with-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-no-msgid-with-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-no-eoh-event b/src/bounce/logfile-with-msgid-no-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-no-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-eoh-event b/src/bounce/logfile-with-msgid-with-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-filter b/src/bounce/logfile-with-msgid-with-filter
new file mode 100644
index 0000000..4d56216
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-filter
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 291
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-long-line b/src/bounce/logfile-with-msgid-with-long-line
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-long-line
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/msgfile-no-msgid-no-eoh-event b/src/bounce/msgfile-no-msgid-no-eoh-event
new file mode 100755
index 0000000..694f5db
--- /dev/null
+++ b/src/bounce/msgfile-no-msgid-no-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-no-msgid-with-eoh-event b/src/bounce/msgfile-no-msgid-with-eoh-event
new file mode 100755
index 0000000..b3b795e
--- /dev/null
+++ b/src/bounce/msgfile-no-msgid-with-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-no-eoh-event b/src/bounce/msgfile-with-msgid-no-eoh-event
new file mode 100755
index 0000000..cc16c66
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-no-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-eoh-event b/src/bounce/msgfile-with-msgid-with-eoh-event
new file mode 100755
index 0000000..ffe30c4
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-filter b/src/bounce/msgfile-with-msgid-with-filter
new file mode 100755
index 0000000..d6e77c4
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-filter
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-long-line b/src/bounce/msgfile-with-msgid-with-long-line
new file mode 100755
index 0000000..69d2801
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-long-line
Binary files differ
diff --git a/src/bounce/no-msgid-no-eoh-event-no-thread.ref b/src/bounce/no-msgid-no-eoh-event-no-thread.ref
new file mode 100644
index 0000000..630734d
--- /dev/null
+++ b/src/bounce/no-msgid-no-eoh-event-no-thread.ref
@@ -0,0 +1,47 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-no-eoh-event-with-thread.ref b/src/bounce/no-msgid-no-eoh-event-with-thread.ref
new file mode 100644
index 0000000..630734d
--- /dev/null
+++ b/src/bounce/no-msgid-no-eoh-event-with-thread.ref
@@ -0,0 +1,47 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-with-eoh-event-no-thread.ref b/src/bounce/no-msgid-with-eoh-event-no-thread.ref
new file mode 100644
index 0000000..39b5841
--- /dev/null
+++ b/src/bounce/no-msgid-with-eoh-event-no-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-with-eoh-event
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-with-eoh-event-with-thread.ref b/src/bounce/no-msgid-with-eoh-event-with-thread.ref
new file mode 100644
index 0000000..39b5841
--- /dev/null
+++ b/src/bounce/no-msgid-with-eoh-event-with-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-with-eoh-event
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/obs_template_test.ref b/src/bounce/obs_template_test.ref
new file mode 100644
index 0000000..e03bb5b
--- /dev/null
+++ b/src/bounce/obs_template_test.ref
@@ -0,0 +1,68 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/template_test.ref b/src/bounce/template_test.ref
new file mode 100644
index 0000000..381c14d
--- /dev/null
+++ b/src/bounce/template_test.ref
@@ -0,0 +1,68 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/with-msgid-no-eoh-event-no-thread.ref b/src/bounce/with-msgid-no-eoh-event-no-thread.ref
new file mode 100644
index 0000000..020d9a4
--- /dev/null
+++ b/src/bounce/with-msgid-no-eoh-event-no-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-no-eoh-event-with-thread.ref b/src/bounce/with-msgid-no-eoh-event-with-thread.ref
new file mode 100644
index 0000000..d2aadd0
--- /dev/null
+++ b/src/bounce/with-msgid-no-eoh-event-with-thread.ref
@@ -0,0 +1,51 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-eoh-event-no-thread.ref b/src/bounce/with-msgid-with-eoh-event-no-thread.ref
new file mode 100644
index 0000000..70afb5f
--- /dev/null
+++ b/src/bounce/with-msgid-with-eoh-event-no-thread.ref
@@ -0,0 +1,51 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-eoh-event-with-thread.ref b/src/bounce/with-msgid-with-eoh-event-with-thread.ref
new file mode 100644
index 0000000..e46afbe
--- /dev/null
+++ b/src/bounce/with-msgid-with-eoh-event-with-thread.ref
@@ -0,0 +1,53 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-filter-no-thread.ref b/src/bounce/with-msgid-with-filter-no-thread.ref
new file mode 100644
index 0000000..fa30ddf
--- /dev/null
+++ b/src/bounce/with-msgid-with-filter-no-thread.ref
@@ -0,0 +1,48 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-filter-with-thread.ref b/src/bounce/with-msgid-with-filter-with-thread.ref
new file mode 100644
index 0000000..14d3373
--- /dev/null
+++ b/src/bounce/with-msgid-with-filter-with-thread.ref
@@ -0,0 +1,50 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-long-line-no-thread.ref b/src/bounce/with-msgid-with-long-line-no-thread.ref
new file mode 100644
index 0000000..fa5268b
--- /dev/null
+++ b/src/bounce/with-msgid-with-long-line-no-thread.ref
@@ -0,0 +1,50 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-long-line
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-long-line-with-thread.ref b/src/bounce/with-msgid-with-long-line-with-thread.ref
new file mode 100644
index 0000000..04e96d6
--- /dev/null
+++ b/src/bounce/with-msgid-with-long-line-with-thread.ref
@@ -0,0 +1,52 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-long-line
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
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..8a3c18c
--- /dev/null
+++ b/src/cleanup/Makefile.in
@@ -0,0 +1,1394 @@
+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 cleanup_milter_test17a \
+ cleanup_milter_test17b cleanup_milter_test17c cleanup_milter_test17d \
+ cleanup_milter_test17e cleanup_milter_test17f cleanup_milter_test17g
+
+root_tests:
+
+cleanup_masquerade_test: cleanup_masquerade cleanup_masq.ref
+ rm -f cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' A.B.C,B.C xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@AA.A.B.C >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade 'xxx' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade 'yyy' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' !a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' !a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@aaa.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./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) $(VALGRIND) ./cleanup_milter <bug1.in
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov bug1.file.tmp 2>/dev/null >bug1.tmp
+ diff bug1.ref bug1.tmp
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <bug2.in
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov bug2.file.tmp 2>/dev/null >bug2.tmp
+ diff bug2.ref bug2.tmp
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <bug3.in
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov bug3.file.tmp 2>/dev/null >bug3.tmp
+ diff bug3.ref bug3.tmp
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in2
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in3
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in4a
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in4b
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in4c
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in5
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in6a
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in6b
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in6c
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in7
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in8
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in9
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in10a
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in10b
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in10c
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in10d
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in10e
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in11
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in12
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13a
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13b
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13c
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13d
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13e
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13g
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13h
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13i
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in13f
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14e 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14e1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14f 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14f1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in14g 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14g1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15e 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15e1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15f 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15f1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15g 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15g1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15h 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15h1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in15i 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15i1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in16a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref16a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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) $(VALGRIND) ./cleanup_milter <cleanup_milter.in16b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref16b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../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
+
+cleanup_milter_test17a: cleanup_milter test-queue-file17 cleanup_milter.in17a \
+ cleanup_milter.ref17a1 ../postcat/postcat cleanup_milter.ref17a2
+ cp test-queue-file17 test-queue-file17a.tmp
+ chmod u+w test-queue-file17a.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17a.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17a2 cleanup_milter.tmp2
+ rm -f test-queue-file17a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17b: cleanup_milter test-queue-file17 cleanup_milter.in17b \
+ cleanup_milter.ref17b1 ../postcat/postcat cleanup_milter.ref17b2
+ cp test-queue-file17 test-queue-file17b.tmp
+ chmod u+w test-queue-file17b.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17b.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17b2 cleanup_milter.tmp2
+ rm -f test-queue-file17b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17c: cleanup_milter test-queue-file17 cleanup_milter.in17c \
+ cleanup_milter.ref17c1 ../postcat/postcat cleanup_milter.ref17c2
+ cp test-queue-file17 test-queue-file17c.tmp
+ chmod u+w test-queue-file17c.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17c.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17c2 cleanup_milter.tmp2
+ rm -f test-queue-file17c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17d: cleanup_milter test-queue-file17 cleanup_milter.in17d \
+ cleanup_milter.ref17d1 ../postcat/postcat cleanup_milter.ref17d2
+ cp test-queue-file17 test-queue-file17d.tmp
+ chmod u+w test-queue-file17d.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17d.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17d2 cleanup_milter.tmp2
+ rm -f test-queue-file17d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17e: cleanup_milter test-queue-file17 cleanup_milter.in17e \
+ cleanup_milter.ref17e1 ../postcat/postcat cleanup_milter.ref17e2
+ cp test-queue-file17 test-queue-file17e.tmp
+ chmod u+w test-queue-file17e.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17e 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17e1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17e.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17e2 cleanup_milter.tmp2
+ rm -f test-queue-file17e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17f: cleanup_milter test-queue-file17 cleanup_milter.in17f \
+ cleanup_milter.ref17f1 ../postcat/postcat cleanup_milter.ref17f2
+ cp test-queue-file17 test-queue-file17f.tmp
+ chmod u+w test-queue-file17f.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17f 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17f1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17f.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17f2 cleanup_milter.tmp2
+ rm -f test-queue-file17f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test17g: cleanup_milter test-queue-file17 cleanup_milter.in17g \
+ cleanup_milter.ref17g1 ../postcat/postcat cleanup_milter.ref17g2
+ cp test-queue-file17 test-queue-file17g.tmp
+ chmod u+w test-queue-file17g.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in17g 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref17g1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17g.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref17g2 cleanup_milter.tmp2
+ rm -f test-queue-file17g.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/hfrom_format.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/hfrom_format.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..6fe61f8
--- /dev/null
+++ b/src/cleanup/cleanup.c
@@ -0,0 +1,656 @@
+/*++
+/* 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 performs the following transformations:
+/* .IP \(bu
+/* Insert missing message headers: (\fBResent-\fR) \fBFrom:\fR,
+/* \fBTo:\fR, \fBMessage-Id:\fR, and \fBDate:\fR.
+/* .br
+/* This is enabled with the \fBlocal_header_rewrite_clients\fR and
+/* \fBalways_add_missing_headers\fR parameter settings.
+/* .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 depends on the \fBtrivial-rewrite\fR(8) daemon.
+/* .br
+/* The header transformation is enabled with the
+/* \fBlocal_header_rewrite_clients\fR parameter setting.
+/* .IP \(bu
+/* Eliminate duplicate envelope recipient addresses.
+/* .br
+/* This is enabled with the \fBduplicate_filter_limit\fR
+/* parameter setting.
+/* .IP \(bu
+/* Remove message headers: \fBBcc\fR, \fBContent-Length\fR,
+/* \fBResent-Bcc\fR, \fBReturn-Path\fR.
+/* .br
+/* This is enabled with the message_drop_headers parameter
+/* setting.
+/* .IP \(bu
+/* Optionally, rewrite all envelope and header addresses according
+/* to the mappings specified in the \fBcanonical\fR(5) lookup tables.
+/* .br
+/* The header transformation is enabled with the
+/* \fBlocal_header_rewrite_clients\fR parameter setting.
+/* .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.
+/* .br
+/* The header transformation is enabled with the
+/* \fBlocal_header_rewrite_clients\fR parameter setting.
+/* .IP \(bu
+/* Optionally, expand envelope recipients according to information
+/* found in the \fBvirtual_alias_maps\fR 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 cleanup service 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.
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* 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) response is
+/* unavailable (for example, bad Postfix configuration or Milter
+/* failure).
+/* .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_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ 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..baecaad
--- /dev/null
+++ b/src/cleanup/cleanup.h
@@ -0,0 +1,370 @@
+/*++
+/* 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.
+ */
+extern int cleanup_hfrom_format;
+
+/* 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..738bd73
--- /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 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..3d4e866
--- /dev/null
+++ b/src/cleanup/cleanup_body_edit.c
@@ -0,0 +1,243 @@
+/*++
+/* 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);
+
+ /*
+ * Sanity check.
+ */
+ if (curr_rp->len > 0
+ && curr_rp->write_offs > curr_rp->start + curr_rp->len)
+ msg_panic("%s: write past end of body segment", myname);
+
+ 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..369a019
--- /dev/null
+++ b/src/cleanup/cleanup_init.c
@@ -0,0 +1,476 @@
+/*++
+/* 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>
+#include <hfrom_format.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 cleanup_hfrom_format;
+
+/* 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)
+{
+
+ /*
+ * 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.
+ */
+ cleanup_hfrom_format = hfrom_format_parse(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..1ee0a52
--- /dev/null
+++ b/src/cleanup/cleanup_message.c
@@ -0,0 +1,1111 @@
+/*++
+/* 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>
+#include <hfrom_format.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
+ * assumption 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 = ' ';
+
+ /*
+ * "From: phrase <route-addr>". Quote the phrase if it contains
+ * specials or the "%!" legacy address operators.
+ */
+ if (cleanup_hfrom_format == 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));
+ }
+
+ /*
+ * "From: addr-spec (ctext)". This is the obsolete form.
+ */
+ else {
+ 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);
+ }
+ }
+
+ /*
+ * "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..11510b5
--- /dev/null
+++ b/src/cleanup/cleanup_milter.c
@@ -0,0 +1,2775 @@
+/*++
+/* 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)
+ /* Matches been_here() call in cleanup_out_recipient(). */
+ 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;
+ }
+ }
+ /* Matches been_here_fixed() call in cleanup_out_recipient(). */
+ 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 const 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], "line_length_limit") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad line_length_limit argument count: %ld",
+ (long) argv->argc);
+ } else if (alldig(argv->argv[1]) == 0) {
+ msg_warn("bad line_length_limit argument count: %ld",
+ (long) argv->argc);
+ } else if ((var_line_limit = atoi(argv->argv[1])) < DEF_LINE_LIMIT) {
+ msg_warn("bad line_length_limit 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 (strcmp(argv->argv[0], "enable_original_recipient") == 0) {
+ if (argv->argc == 1) {
+ msg_info("enable_original_recipient: %d", var_enable_orcpt);
+ } else if (argv->argc != 2) {
+ msg_warn("bad enable_original_recipient argument count: %ld",
+ (long) argv->argc);
+ } else if (!alldig(argv->argv[1])) {
+ msg_warn("non-numeric enable_original_recipient argument: %s",
+ argv->argv[1]);
+ } else {
+ var_enable_orcpt = atoi(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,
+ REC_TYPE_NORM, buf);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
+ cleanup_repl_body(state, MILTER_BODY_LINE,
+ REC_TYPE_NORM, buf);
+ cleanup_repl_body(state, MILTER_BODY_END,
+ REC_TYPE_NORM, 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.in17a b/src/cleanup/cleanup_milter.in17a
new file mode 100644
index 0000000..c7eb361
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17a
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file17a.tmp
+#
+# Delete a recipient. Then add the recipient back.
+
+del_rcpt user@example.com
+add_rcpt user@example.com
+
+close
diff --git a/src/cleanup/cleanup_milter.in17b b/src/cleanup/cleanup_milter.in17b
new file mode 100644
index 0000000..654f9f5
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17b
@@ -0,0 +1,10 @@
+#verbose on
+open test-queue-file17b.tmp
+#
+# Delete a recipient. Then add the recipient back, with a DSN NOTIFY
+# record instead of a DSN original recipient.
+
+del_rcpt user@example.com
+add_rcpt_par user@example.com NOTIFY=never
+
+close
diff --git a/src/cleanup/cleanup_milter.in17c b/src/cleanup/cleanup_milter.in17c
new file mode 100644
index 0000000..8c34db4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17c
@@ -0,0 +1,12 @@
+#verbose on
+open test-queue-file17c.tmp
+#
+# Delete a recipient. Then add the recipient back, with a DSN NOTIFY
+# record instead of a DSN original recipient. Then add the same again.
+# The second command should be ignored as a duplicate.
+
+del_rcpt user@example.com
+add_rcpt_par user@example.com NOTIFY=never
+add_rcpt_par user@example.com NOTIFY=never
+
+close
diff --git a/src/cleanup/cleanup_milter.in17d b/src/cleanup/cleanup_milter.in17d
new file mode 100644
index 0000000..46ac8ff
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17d
@@ -0,0 +1,18 @@
+#verbose on
+open test-queue-file17d.tmp
+#
+# Delete a recipient. Then add the recipient back, with a DSN NOTIFY
+# record instead of a DSN original recipient. Then add the same again.
+# The second command should be ignored as a duplicate.
+
+del_rcpt user@example.com
+add_rcpt_par user@example.com NOTIFY=never
+add_rcpt_par user@example.com NOTIFY=never
+
+# The above has confirmed that recipient is in the duplicate filter.
+# Now verify that del_rcpt will delete it, and that a subsequent
+# add_rcpt_par will NOT be ignored.
+del_rcpt user@example.com
+add_rcpt_par user@example.com NOTIFY=never
+
+close
diff --git a/src/cleanup/cleanup_milter.in17e b/src/cleanup/cleanup_milter.in17e
new file mode 100644
index 0000000..e2e2546
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17e
@@ -0,0 +1,12 @@
+#verbose on
+open test-queue-file17e.tmp
+#
+# Delete a recipient. Then add the recipient back.
+
+enable_original_recipient 0
+enable_original_recipient
+
+del_rcpt user@example.com
+add_rcpt user@example.com
+
+close
diff --git a/src/cleanup/cleanup_milter.in17f b/src/cleanup/cleanup_milter.in17f
new file mode 100644
index 0000000..03d85c0
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17f
@@ -0,0 +1,15 @@
+#verbose on
+open test-queue-file17f.tmp
+#
+# Delete a recipient. Then add the recipient back.
+
+enable_original_recipient 0
+enable_original_recipient
+
+del_rcpt user@example.com
+add_rcpt user@example.com
+
+# Adding the recipient another time should be a NOOP.
+add_rcpt user@example.com
+
+close
diff --git a/src/cleanup/cleanup_milter.in17g b/src/cleanup/cleanup_milter.in17g
new file mode 100644
index 0000000..e7225ec
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in17g
@@ -0,0 +1,23 @@
+#verbose on
+open test-queue-file17g.tmp
+#
+# Delete a recipient. This leaves a deleted recipient in the queue
+# file. Then add the recipient back.
+#
+enable_original_recipient 0
+enable_original_recipient
+
+del_rcpt user@example.com
+add_rcpt user@example.com
+
+# Adding the recipient another time should be a NOOP.
+add_rcpt user@example.com
+
+# Deleting the recipient should remove it from the duplicate filter.
+# This leaves a deleted recipient in the queue file.
+# Therefore adding the recipient will not be a NOOP.
+
+del_rcpt user@example.com
+add_rcpt user@example.com
+
+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.ref17a1 b/src/cleanup/cleanup_milter.ref17a1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17a1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17a2 b/src/cleanup/cleanup_milter.ref17a2
new file mode 100644
index 0000000..18037a8
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17a2
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file17a.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: user@example.com
+ 672 recipient: user@example.com
+ 690 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17a.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-file17a.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17b1 b/src/cleanup/cleanup_milter.ref17b1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17b1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17b2 b/src/cleanup/cleanup_milter.ref17b2
new file mode 100644
index 0000000..4df043d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17b2
@@ -0,0 +1,31 @@
+*** ENVELOPE RECORDS test-queue-file17b.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: user@example.com
+ 688 recipient: user@example.com
+ 706 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17b.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-file17b.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17c1 b/src/cleanup/cleanup_milter.ref17c1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17c1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17c2 b/src/cleanup/cleanup_milter.ref17c2
new file mode 100644
index 0000000..509af41
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17c2
@@ -0,0 +1,31 @@
+*** ENVELOPE RECORDS test-queue-file17c.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: user@example.com
+ 688 recipient: user@example.com
+ 706 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17c.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-file17c.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17c.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17d1 b/src/cleanup/cleanup_milter.ref17d1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17d1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17d2 b/src/cleanup/cleanup_milter.ref17d2
new file mode 100644
index 0000000..846ac33
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17d2
@@ -0,0 +1,35 @@
+*** ENVELOPE RECORDS test-queue-file17d.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: user@example.com
+ 688 canceled_recipient: user@example.com
+ 706 pointer_record: 723
+ 723 named_attribute: notify_flags=1
+ 739 original_recipient: user@example.com
+ 757 recipient: user@example.com
+ 775 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17d.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-file17d.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17d.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17e1 b/src/cleanup/cleanup_milter.ref17e1
new file mode 100644
index 0000000..b78b1a2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17e1
@@ -0,0 +1,2 @@
+./cleanup_milter: enable_original_recipient: 0
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17e2 b/src/cleanup/cleanup_milter.ref17e2
new file mode 100644
index 0000000..7e523f6
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17e2
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file17e.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: user@example.com
+ 672 recipient: user@example.com
+ 690 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17e.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-file17e.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17e.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17f1 b/src/cleanup/cleanup_milter.ref17f1
new file mode 100644
index 0000000..b78b1a2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17f1
@@ -0,0 +1,2 @@
+./cleanup_milter: enable_original_recipient: 0
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17f2 b/src/cleanup/cleanup_milter.ref17f2
new file mode 100644
index 0000000..ce18591
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17f2
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file17f.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: user@example.com
+ 672 recipient: user@example.com
+ 690 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17f.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-file17f.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17f.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref17g1 b/src/cleanup/cleanup_milter.ref17g1
new file mode 100644
index 0000000..b78b1a2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17g1
@@ -0,0 +1,2 @@
+./cleanup_milter: enable_original_recipient: 0
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref17g2 b/src/cleanup/cleanup_milter.ref17g2
new file mode 100644
index 0000000..938eeed
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref17g2
@@ -0,0 +1,33 @@
+*** ENVELOPE RECORDS test-queue-file17g.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: user@example.com
+ 672 canceled_recipient: user@example.com
+ 690 pointer_record: 707
+ 707 original_recipient: user@example.com
+ 725 recipient: user@example.com
+ 743 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file17g.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-file17g.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 canceled_recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file17g.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..5f8ed0a
--- /dev/null
+++ b/src/cleanup/cleanup_out.c
@@ -0,0 +1,233 @@
+/*++
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, 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 (msg_verbose && !TEXT_RECORD(type))
+ msg_info("cleanup_out: %c %.*s", type, (int) len, string);
+
+ 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..5e965fa
--- /dev/null
+++ b/src/cleanup/cleanup_out_recipient.c
@@ -0,0 +1,266 @@
+/*++
+/* 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) {
+ /* Matches been_here_drop{,_fixed}() calls cleanup_del_rcpt(). */
+ 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-file17 b/src/cleanup/test-queue-file17
new file mode 100644
index 0000000..3f7c5f3
--- /dev/null
+++ b/src/cleanup/test-queue-file17
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
diff --git a/src/discard/.indent.pro b/src/discard/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/discard/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/discard/.printfck b/src/discard/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/discard/.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/discard/Makefile.in b/src/discard/Makefile.in
new file mode 100644
index 0000000..f8c3b65
--- /dev/null
+++ b/src/discard/Makefile.in
@@ -0,0 +1,86 @@
+SHELL = /bin/sh
+SRCS = discard.c
+OBJS = discard.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = discard
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+discard.o: ../../include/attr.h
+discard.o: ../../include/bounce.h
+discard.o: ../../include/check_arg.h
+discard.o: ../../include/deliver_completed.h
+discard.o: ../../include/deliver_request.h
+discard.o: ../../include/dsn.h
+discard.o: ../../include/dsn_buf.h
+discard.o: ../../include/dsn_util.h
+discard.o: ../../include/flush_clnt.h
+discard.o: ../../include/htable.h
+discard.o: ../../include/mail_conf.h
+discard.o: ../../include/mail_queue.h
+discard.o: ../../include/mail_server.h
+discard.o: ../../include/mail_version.h
+discard.o: ../../include/msg.h
+discard.o: ../../include/msg_stats.h
+discard.o: ../../include/mymalloc.h
+discard.o: ../../include/nvtable.h
+discard.o: ../../include/recipient_list.h
+discard.o: ../../include/sent.h
+discard.o: ../../include/sys_defs.h
+discard.o: ../../include/vbuf.h
+discard.o: ../../include/vstream.h
+discard.o: ../../include/vstring.h
+discard.o: discard.c
diff --git a/src/discard/discard.c b/src/discard/discard.c
new file mode 100644
index 0000000..331f96f
--- /dev/null
+++ b/src/discard/discard.c
@@ -0,0 +1,253 @@
+/*++
+/* NAME
+/* discard 8
+/* SUMMARY
+/* Postfix discard mail delivery agent
+/* SYNOPSIS
+/* \fBdiscard\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix \fBdiscard\fR(8) delivery agent processes
+/* delivery requests from
+/* the queue manager. Each request specifies a queue file, a sender
+/* address, a next-hop destination that is treated as the reason for
+/* discarding the mail, and recipient information.
+/* The reason may be prefixed with an RFC 3463-compatible detail code.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients
+/* in the delivery request, logs the "next-hop" destination
+/* as the reason for discarding the mail, updates the
+/* queue file, and either marks recipients as finished or informs the
+/* queue manager that delivery should be tried again at a later time.
+/*
+/* Delivery status reports are sent to the \fBtrace\fR(8)
+/* daemon as appropriate.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBdiscard\fR(8) mailer is not security-sensitive. It does not talk
+/* to the network, and can be run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 3463 (Enhanced Status Codes)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBdiscard\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.
+/* .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 "\fBdouble_bounce_sender (double-bounce)\fR"
+/* The sender address of postmaster notifications that are generated
+/* by the mail system.
+/* .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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* error(8), Postfix error delivery agent
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 2.2.
+/* AUTHOR(S)
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Based on code by:
+/* 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 <unistd.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <deliver_request.h>
+#include <mail_queue.h>
+#include <bounce.h>
+#include <deliver_completed.h>
+#include <flush_clnt.h>
+#include <sent.h>
+#include <dsn_util.h>
+#include <mail_version.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* deliver_message - deliver message with extreme prejudice */
+
+static int deliver_message(DELIVER_REQUEST *request)
+{
+ const char *myname = "deliver_message";
+ VSTREAM *src;
+ int result = 0;
+ int status;
+ RECIPIENT *rcpt;
+ int nrcpt;
+ DSN_SPLIT dp;
+ DSN dsn;
+
+ if (msg_verbose)
+ msg_info("deliver_message: from %s", request->sender);
+
+ /*
+ * Sanity checks.
+ */
+ if (request->nexthop[0] == 0)
+ msg_fatal("empty nexthop hostname");
+ if (request->rcpt_list.len <= 0)
+ msg_fatal("recipient count: %d", request->rcpt_list.len);
+
+ /*
+ * Open the queue file. Opening the file can fail for a variety of
+ * reasons, such as the system running out of resources. Instead of
+ * throwing away mail, we're raising a fatal error which forces the mail
+ * system to back off, and retry later.
+ */
+ src = mail_queue_open(request->queue_name, request->queue_id,
+ O_RDWR, 0);
+ if (src == 0)
+ msg_fatal("%s: open %s %s: %m", myname,
+ request->queue_name, request->queue_id);
+ if (msg_verbose)
+ msg_info("%s: file %s", myname, VSTREAM_PATH(src));
+
+ /*
+ * Discard all recipients.
+ */
+#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags)
+
+ dsn_split(&dp, "2.0.0", request->nexthop);
+ (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text);
+ for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ status = sent(BOUNCE_FLAGS(request), request->queue_id,
+ &request->msg_stats, rcpt, "none", &dsn);
+ if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
+ deliver_completed(src, rcpt->offset);
+ result |= status;
+ }
+
+ /*
+ * Clean up.
+ */
+ if (vstream_fclose(src))
+ msg_warn("close %s %s: %m", request->queue_name, request->queue_id);
+
+ return (result);
+}
+
+/* discard_service - perform service for client */
+
+static void discard_service(VSTREAM *client_stream, char *unused_service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to the discard mailer. What we see below is a little
+ * protocol to (1) tell the queue manager that we are ready, (2) read a
+ * request from the queue manager, and (3) report the completion status
+ * of that request. All connection-management stuff is handled by the
+ * common code in single_server.c.
+ */
+ if ((request = deliver_request_read(client_stream)) != 0) {
+ status = deliver_message(request);
+ deliver_request_done(client_stream, request, status);
+ }
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+ flush_init();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, discard_service,
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ 0);
+}
diff --git a/src/dns/.indent.pro b/src/dns/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/dns/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/dns/.printfck b/src/dns/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/dns/.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/dns/Makefile.in b/src/dns/Makefile.in
new file mode 100644
index 0000000..795f9ba
--- /dev/null
+++ b/src/dns/Makefile.in
@@ -0,0 +1,408 @@
+SHELL = /bin/sh
+SRCS = dns_lookup.c dns_rr.c dns_strerror.c dns_strtype.c dns_rr_to_pa.c \
+ dns_sa_to_rr.c dns_rr_eq_sa.c dns_rr_to_sa.c dns_strrecord.c \
+ dns_rr_filter.c dns_str_resflags.c dns_sec.c
+OBJS = dns_lookup.o dns_rr.o dns_strerror.o dns_strtype.o dns_rr_to_pa.o \
+ dns_sa_to_rr.o dns_rr_eq_sa.o dns_rr_to_sa.o dns_strrecord.o \
+ dns_rr_filter.o dns_str_resflags.o dns_sec.o
+HDRS = dns.h
+TESTSRC = test_dns_lookup.c test_alias_token.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)dns$(LIB_SUFFIX)
+TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
+ dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \
+ error-filter-test nullmx_test nxdomain_test mxonly_test \
+ dnsbl_tests
+
+dnsbl_tests: \
+ dnsbl_ttl_127.0.0.2_bind_plain_test \
+ dnsbl_ttl_127.0.0.2_bind_ncache_test \
+ dnsbl_ttl_127.0.0.2_priv_plain_test \
+ dnsbl_ttl_127.0.0.2_priv_ncache_test \
+ dnsbl_ttl_127.0.0.1_bind_plain_test \
+ dnsbl_ttl_127.0.0.1_bind_ncache_test \
+ dnsbl_ttl_127.0.0.1_priv_plain_test \
+ dnsbl_ttl_127.0.0.1_priv_ncache_test
+
+DNSBL_NEXIST_REPLY_FIX = \
+ sed -e 's/ [0-9][0-9]* IN SOA / TTL IN SOA /' \
+ -e 's/len=[0-9][0-9]* /len=LEN /' \
+ -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+ -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/'
+
+DNSBL_EXIST_REPLY_FIX = \
+ sed -e 's/ [0-9][0-9]* IN A / TTL IN A /' \
+ -e 's/len=[0-9][0-9]* /len=LEN /' \
+ -e 's/ancount=[1-9][0-9]*/ancount=N/' \
+ -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+ -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' \
+ -e 's/127.0.0.[0-9]*$$/127.0.0.D/' \
+ | uniq
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+test_dns_lookup: test_dns_lookup.c all $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+dns_rr_to_pa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_to_sa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_sa_to_rr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_eq_sa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+dns_rr_to_pa_test: dns_rr_to_pa dns_rr_to_pa.in dns_rr_to_pa.ref
+ $(SHLIB_ENV) ./dns_rr_to_pa `cat dns_rr_to_pa.in` >dns_rr_to_pa.tmp
+ diff dns_rr_to_pa.ref dns_rr_to_pa.tmp
+ rm -f dns_rr_to_pa.tmp
+
+dns_rr_to_sa_test: dns_rr_to_sa dns_rr_to_sa.in dns_rr_to_sa.ref
+ $(SHLIB_ENV) ./dns_rr_to_sa `cat dns_rr_to_sa.in` >dns_rr_to_sa.tmp
+ diff dns_rr_to_sa.ref dns_rr_to_sa.tmp
+ rm -f dns_rr_to_sa.tmp
+
+dns_sa_to_rr_test: dns_sa_to_rr dns_sa_to_rr.in dns_sa_to_rr.ref
+ $(SHLIB_ENV) ./dns_sa_to_rr `cat dns_sa_to_rr.in` >dns_sa_to_rr.tmp
+ diff dns_sa_to_rr.ref dns_sa_to_rr.tmp
+ rm -f dns_sa_to_rr.tmp
+
+dns_rr_eq_sa_test: dns_rr_eq_sa dns_rr_eq_sa.in dns_rr_eq_sa.ref
+ $(SHLIB_ENV) ./dns_rr_eq_sa `cat dns_rr_eq_sa.in` >dns_rr_eq_sa.tmp
+ diff dns_rr_eq_sa.ref dns_rr_eq_sa.tmp
+ rm -f dns_rr_eq_sa.tmp
+
+no-a-test: no-a.reg test_dns_lookup no-a.ref
+ $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-a.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1
+ diff no-a.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+no-aaaa-test: no-aaaa.reg test_dns_lookup no-aaaa.ref
+ $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-aaaa.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1
+ diff no-aaaa.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+no-mx-test: no-mx.reg test_dns_lookup no-mx.ref
+ set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-mx.reg mx porcupine.org 2>&1 | sort >test_dns_lookup.tmp || true
+ diff no-mx.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+error-filter-test: error.reg test_dns_lookup error.ref
+ set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:error.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1 || true
+ diff error.ref test_dns_lookup.tmp
+ rm -f test_dns_lookup.tmp
+
+nullmx_test: test_dns_lookup nullmx_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a nullmx.porcupine.org; \
+ ) >nullmx_test.tmp 2>&1 || exit 0
+ diff nullmx_test.ref nullmx_test.tmp
+ rm -f nullmx_test.tmp
+
+nxdomain_test: test_dns_lookup nxdomain_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a nxdomain.porcupine.org; \
+ ) >nxdomain_test.tmp 2>&1 || exit 0
+ diff nxdomain_test.ref nxdomain_test.tmp
+ rm -f nxdomain_test.tmp
+
+mxonly_test: test_dns_lookup mxonly_test.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup mx,a porcupine.org | sort; \
+ ) >mxonly_test.tmp 2>&1 || exit 0
+ diff mxonly_test.ref mxonly_test.tmp
+ rm -f mxonly_test.tmp
+
+# Non-existent record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_plain.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_bind_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.1_bind_plain.tmp
+
+# Non-existent record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_plain.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_priv_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.1_priv_plain.tmp
+
+# Non-existent record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+
+# Non-existent record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+ diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+
+# Existing record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_plain.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.2_bind_plain.tmp
+
+# Existing record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_plain.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_plain.tmp
+ rm -f dnsbl_ttl_127.0.0.2_priv_plain.tmp
+
+# Existing record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+
+# Existing record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+ (set -e; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.zen.spamhaus.org; \
+ $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.bl.spamcop.net; \
+ ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+ diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+ rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+
+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 $(LIB) *core $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+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'
+dns_lookup.o: ../../include/argv.h
+dns_lookup.o: ../../include/check_arg.h
+dns_lookup.o: ../../include/dict.h
+dns_lookup.o: ../../include/mail_params.h
+dns_lookup.o: ../../include/maps.h
+dns_lookup.o: ../../include/msg.h
+dns_lookup.o: ../../include/myaddrinfo.h
+dns_lookup.o: ../../include/myflock.h
+dns_lookup.o: ../../include/mymalloc.h
+dns_lookup.o: ../../include/sock_addr.h
+dns_lookup.o: ../../include/stringops.h
+dns_lookup.o: ../../include/sys_defs.h
+dns_lookup.o: ../../include/valid_hostname.h
+dns_lookup.o: ../../include/vbuf.h
+dns_lookup.o: ../../include/vstream.h
+dns_lookup.o: ../../include/vstring.h
+dns_lookup.o: dns.h
+dns_lookup.o: dns_lookup.c
+dns_rr.o: ../../include/check_arg.h
+dns_rr.o: ../../include/msg.h
+dns_rr.o: ../../include/myaddrinfo.h
+dns_rr.o: ../../include/mymalloc.h
+dns_rr.o: ../../include/myrand.h
+dns_rr.o: ../../include/sock_addr.h
+dns_rr.o: ../../include/sys_defs.h
+dns_rr.o: ../../include/vbuf.h
+dns_rr.o: ../../include/vstring.h
+dns_rr.o: dns.h
+dns_rr.o: dns_rr.c
+dns_rr_eq_sa.o: ../../include/check_arg.h
+dns_rr_eq_sa.o: ../../include/msg.h
+dns_rr_eq_sa.o: ../../include/myaddrinfo.h
+dns_rr_eq_sa.o: ../../include/sock_addr.h
+dns_rr_eq_sa.o: ../../include/sys_defs.h
+dns_rr_eq_sa.o: ../../include/vbuf.h
+dns_rr_eq_sa.o: ../../include/vstring.h
+dns_rr_eq_sa.o: dns.h
+dns_rr_eq_sa.o: dns_rr_eq_sa.c
+dns_rr_filter.o: ../../include/argv.h
+dns_rr_filter.o: ../../include/check_arg.h
+dns_rr_filter.o: ../../include/dict.h
+dns_rr_filter.o: ../../include/maps.h
+dns_rr_filter.o: ../../include/msg.h
+dns_rr_filter.o: ../../include/myaddrinfo.h
+dns_rr_filter.o: ../../include/myflock.h
+dns_rr_filter.o: ../../include/sock_addr.h
+dns_rr_filter.o: ../../include/sys_defs.h
+dns_rr_filter.o: ../../include/vbuf.h
+dns_rr_filter.o: ../../include/vstream.h
+dns_rr_filter.o: ../../include/vstring.h
+dns_rr_filter.o: dns.h
+dns_rr_filter.o: dns_rr_filter.c
+dns_rr_to_pa.o: ../../include/check_arg.h
+dns_rr_to_pa.o: ../../include/msg.h
+dns_rr_to_pa.o: ../../include/myaddrinfo.h
+dns_rr_to_pa.o: ../../include/sock_addr.h
+dns_rr_to_pa.o: ../../include/sys_defs.h
+dns_rr_to_pa.o: ../../include/vbuf.h
+dns_rr_to_pa.o: ../../include/vstring.h
+dns_rr_to_pa.o: dns.h
+dns_rr_to_pa.o: dns_rr_to_pa.c
+dns_rr_to_sa.o: ../../include/check_arg.h
+dns_rr_to_sa.o: ../../include/msg.h
+dns_rr_to_sa.o: ../../include/myaddrinfo.h
+dns_rr_to_sa.o: ../../include/sock_addr.h
+dns_rr_to_sa.o: ../../include/sys_defs.h
+dns_rr_to_sa.o: ../../include/vbuf.h
+dns_rr_to_sa.o: ../../include/vstring.h
+dns_rr_to_sa.o: dns.h
+dns_rr_to_sa.o: dns_rr_to_sa.c
+dns_sa_to_rr.o: ../../include/check_arg.h
+dns_sa_to_rr.o: ../../include/msg.h
+dns_sa_to_rr.o: ../../include/myaddrinfo.h
+dns_sa_to_rr.o: ../../include/sock_addr.h
+dns_sa_to_rr.o: ../../include/sys_defs.h
+dns_sa_to_rr.o: ../../include/vbuf.h
+dns_sa_to_rr.o: ../../include/vstring.h
+dns_sa_to_rr.o: dns.h
+dns_sa_to_rr.o: dns_sa_to_rr.c
+dns_sec.o: ../../include/check_arg.h
+dns_sec.o: ../../include/mail_params.h
+dns_sec.o: ../../include/msg.h
+dns_sec.o: ../../include/myaddrinfo.h
+dns_sec.o: ../../include/mymalloc.h
+dns_sec.o: ../../include/sock_addr.h
+dns_sec.o: ../../include/split_at.h
+dns_sec.o: ../../include/sys_defs.h
+dns_sec.o: ../../include/vbuf.h
+dns_sec.o: ../../include/vstring.h
+dns_sec.o: dns.h
+dns_sec.o: dns_sec.c
+dns_str_resflags.o: ../../include/check_arg.h
+dns_str_resflags.o: ../../include/myaddrinfo.h
+dns_str_resflags.o: ../../include/name_mask.h
+dns_str_resflags.o: ../../include/sock_addr.h
+dns_str_resflags.o: ../../include/sys_defs.h
+dns_str_resflags.o: ../../include/vbuf.h
+dns_str_resflags.o: ../../include/vstring.h
+dns_str_resflags.o: dns.h
+dns_str_resflags.o: dns_str_resflags.c
+dns_strerror.o: ../../include/check_arg.h
+dns_strerror.o: ../../include/myaddrinfo.h
+dns_strerror.o: ../../include/sock_addr.h
+dns_strerror.o: ../../include/sys_defs.h
+dns_strerror.o: ../../include/vbuf.h
+dns_strerror.o: ../../include/vstring.h
+dns_strerror.o: dns.h
+dns_strerror.o: dns_strerror.c
+dns_strrecord.o: ../../include/check_arg.h
+dns_strrecord.o: ../../include/msg.h
+dns_strrecord.o: ../../include/myaddrinfo.h
+dns_strrecord.o: ../../include/sock_addr.h
+dns_strrecord.o: ../../include/sys_defs.h
+dns_strrecord.o: ../../include/vbuf.h
+dns_strrecord.o: ../../include/vstring.h
+dns_strrecord.o: dns.h
+dns_strrecord.o: dns_strrecord.c
+dns_strtype.o: ../../include/check_arg.h
+dns_strtype.o: ../../include/myaddrinfo.h
+dns_strtype.o: ../../include/sock_addr.h
+dns_strtype.o: ../../include/sys_defs.h
+dns_strtype.o: ../../include/vbuf.h
+dns_strtype.o: ../../include/vstring.h
+dns_strtype.o: dns.h
+dns_strtype.o: dns_strtype.c
+test_dns_lookup.o: ../../include/argv.h
+test_dns_lookup.o: ../../include/check_arg.h
+test_dns_lookup.o: ../../include/mail_params.h
+test_dns_lookup.o: ../../include/msg.h
+test_dns_lookup.o: ../../include/msg_vstream.h
+test_dns_lookup.o: ../../include/myaddrinfo.h
+test_dns_lookup.o: ../../include/mymalloc.h
+test_dns_lookup.o: ../../include/sock_addr.h
+test_dns_lookup.o: ../../include/sys_defs.h
+test_dns_lookup.o: ../../include/vbuf.h
+test_dns_lookup.o: ../../include/vstream.h
+test_dns_lookup.o: ../../include/vstring.h
+test_dns_lookup.o: dns.h
+test_dns_lookup.o: test_dns_lookup.c
diff --git a/src/dns/dns.h b/src/dns/dns.h
new file mode 100644
index 0000000..5f53dbc
--- /dev/null
+++ b/src/dns/dns.h
@@ -0,0 +1,357 @@
+#ifndef _DNS_H_INCLUDED_
+#define _DNS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dns 3h
+/* SUMMARY
+/* domain name service lookup
+/* SYNOPSIS
+/* #include <dns.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef RESOLVE_H_NEEDS_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef RESOLVE_H_NEEDS_NAMESER8_COMPAT_H
+#include <nameser8_compat.h>
+#endif
+#ifdef RESOLVE_H_NEEDS_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+
+ /*
+ * Name server compatibility. These undocumented macros appear in the file
+ * <arpa/nameser.h>, but since they are undocumented we should not count on
+ * their presence, and so they are included here just in case.
+ */
+#ifndef GETSHORT
+
+#define GETSHORT(s, cp) { \
+ unsigned char *t_cp = (u_char *)(cp); \
+ (s) = ((unsigned)t_cp[0] << 8) \
+ | ((unsigned)t_cp[1]) \
+ ; \
+ (cp) += 2; \
+}
+
+#define GETLONG(l, cp) { \
+ unsigned char *t_cp = (u_char *)(cp); \
+ (l) = ((unsigned)t_cp[0] << 24) \
+ | ((unsigned)t_cp[1] << 16) \
+ | ((unsigned)t_cp[2] << 8) \
+ | ((unsigned)t_cp[3]) \
+ ; \
+ (cp) += 4; \
+}
+
+#endif
+
+ /*
+ * Provide API compatibility for systems without res_nxxx() API. Also
+ * require calling dns_get_h_errno() instead of directly accessing the
+ * global h_errno variable. We should not count on that being updated.
+ */
+#if !defined(NO_RES_NCALLS) && defined(__RES) && (__RES >= 19991006)
+#define USE_RES_NCALLS
+#undef h_errno
+#define h_errno use_dns_get_h_errno_instead_of_h_errno
+#endif
+
+/*
+ * Disable DNSSEC at compile-time even if RES_USE_DNSSEC is available
+ */
+#ifdef NO_DNSSEC
+#undef RES_USE_DNSSEC
+#undef RES_TRUSTAD
+#endif
+
+ /*
+ * Compatibility with systems that lack RES_USE_DNSSEC and RES_USE_EDNS0
+ */
+#ifndef RES_USE_DNSSEC
+#define RES_USE_DNSSEC 0
+#endif
+#ifndef RES_USE_EDNS0
+#define RES_USE_EDNS0 0
+#endif
+#ifndef RES_TRUSTAD
+#define RES_TRUSTAD 0
+#endif
+
+ /*-
+ * TLSA: https://tools.ietf.org/html/rfc6698#section-7.1
+ * RRSIG: http://tools.ietf.org/html/rfc4034#section-3
+ *
+ * We don't request RRSIG, but we get it "for free" when we send the DO-bit.
+ */
+#ifndef T_TLSA
+#define T_TLSA 52
+#endif
+#ifndef T_RRSIG
+#define T_RRSIG 46 /* Avoid unknown RR in logs */
+#endif
+#ifndef T_DNAME
+#define T_DNAME 39 /* [RFC6672] */
+#endif
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.2
+ */
+#define DNS_TLSA_USAGE_CA_CONSTRAINT 0
+#define DNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT 1
+#define DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION 2
+#define DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE 3
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.3
+ */
+#define DNS_TLSA_SELECTOR_FULL_CERTIFICATE 0
+#define DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO 1
+
+ /*
+ * https://tools.ietf.org/html/rfc6698#section-7.4
+ */
+#define DNS_TLSA_MATCHING_TYPE_NO_HASH_USED 0
+#define DNS_TLSA_MATCHING_TYPE_SHA256 1
+#define DNS_TLSA_MATCHING_TYPE_SHA512 2
+
+ /*
+ * SunOS 4 needs this.
+ */
+#ifndef T_TXT
+#define T_TXT 16
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <sock_addr.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Structure for fixed resource record data.
+ */
+typedef struct DNS_FIXED {
+ unsigned short type; /* T_A, T_CNAME, etc. */
+ unsigned short class; /* C_IN, etc. */
+ unsigned int ttl; /* always */
+ unsigned length; /* record length */
+} DNS_FIXED;
+
+ /*
+ * Structure of a DNS resource record after expansion. The components are
+ * named after the things one can expect to find in a DNS resource record.
+ */
+typedef struct DNS_RR {
+ char *qname; /* query name, mystrdup()ed */
+ char *rname; /* reply name, mystrdup()ed */
+ unsigned short type; /* T_A, T_CNAME, etc. */
+ unsigned short class; /* C_IN, etc. */
+ unsigned int ttl; /* always */
+ unsigned int dnssec_valid; /* DNSSEC validated */
+ unsigned short pref; /* T_MX only */
+ struct DNS_RR *next; /* linkage */
+ size_t data_len; /* actual data size */
+ char data[1]; /* actually a bunch of data */
+} DNS_RR;
+
+ /*
+ * dns_strerror.c
+ */
+extern const char *dns_strerror(unsigned);
+
+ /*
+ * dns_strtype.c
+ */
+extern const char *dns_strtype(unsigned);
+extern unsigned dns_type(const char *);
+
+ /*
+ * dns_strrecord.c
+ */
+extern char *dns_strrecord(VSTRING *, DNS_RR *);
+
+ /*
+ * dns_rr.c
+ */
+extern DNS_RR *dns_rr_create(const char *, const char *,
+ ushort, ushort,
+ unsigned, unsigned,
+ const char *, size_t);
+extern void dns_rr_free(DNS_RR *);
+extern DNS_RR *dns_rr_copy(DNS_RR *);
+extern DNS_RR *dns_rr_append(DNS_RR *, DNS_RR *);
+extern DNS_RR *dns_rr_sort(DNS_RR *, int (*) (DNS_RR *, DNS_RR *));
+extern int dns_rr_compare_pref_ipv6(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref_ipv4(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref_any(DNS_RR *, DNS_RR *);
+extern int dns_rr_compare_pref(DNS_RR *, DNS_RR *);
+extern DNS_RR *dns_rr_shuffle(DNS_RR *);
+extern DNS_RR *dns_rr_remove(DNS_RR *, DNS_RR *);
+
+ /*
+ * dns_rr_to_pa.c
+ */
+extern const char *dns_rr_to_pa(DNS_RR *, MAI_HOSTADDR_STR *);
+
+ /*
+ * dns_sa_to_rr.c
+ */
+extern DNS_RR *dns_sa_to_rr(const char *, unsigned, struct sockaddr *);
+
+ /*
+ * dns_rr_to_sa.c
+ */
+extern int dns_rr_to_sa(DNS_RR *, unsigned, struct sockaddr *, SOCKADDR_SIZE *);
+
+ /*
+ * dns_rr_eq_sa.c
+ */
+extern int dns_rr_eq_sa(DNS_RR *, struct sockaddr *);
+
+#ifdef HAS_IPV6
+#define DNS_RR_EQ_SA(rr, sa) \
+ ((SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr) \
+ || (SOCK_ADDR_IN_FAMILY(sa) == AF_INET6 && (rr)->type == T_AAAA \
+ && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \
+ (rr)->data, (rr)->data_len) == 0))
+#else
+#define DNS_RR_EQ_SA(rr, sa) \
+ (SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr)
+#endif
+
+ /*
+ * dns_lookup.c
+ */
+extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **,
+ VSTRING *, VSTRING *, int *, unsigned);
+extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *,
+ VSTRING *, int *, int,...);
+extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *,
+ VSTRING *, int *, int, unsigned *);
+extern int dns_get_h_errno(void);
+
+#define dns_lookup(name, type, rflags, list, fqdn, why) \
+ dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (unsigned) 0)
+#define dns_lookup_r(name, type, rflags, list, fqdn, why, rcode) \
+ dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (rcode), \
+ (unsigned) 0)
+#define dns_lookup_l(name, rflags, list, fqdn, why, lflags, ...) \
+ dns_lookup_rl((name), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (lflags), __VA_ARGS__)
+#define dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype) \
+ dns_lookup_rv((name), (rflags), (list), (fqdn), (why), (int *) 0, \
+ (lflags), (ltype))
+
+ /*
+ * The dns_lookup() rflag that requests DNSSEC validation.
+ */
+#define DNS_WANT_DNSSEC_VALIDATION(rflags) ((rflags) & RES_USE_DNSSEC)
+
+ /*
+ * lflags.
+ */
+#define DNS_REQ_FLAG_STOP_OK (1<<0)
+#define DNS_REQ_FLAG_STOP_INVAL (1<<1)
+#define DNS_REQ_FLAG_STOP_NULLMX (1<<2)
+#define DNS_REQ_FLAG_STOP_MX_POLICY (1<<3)
+#define DNS_REQ_FLAG_NCACHE_TTL (1<<4)
+#define DNS_REQ_FLAG_NONE (0)
+
+ /*
+ * Status codes. Failures must have negative codes so they will not collide
+ * with valid counts of answer records etc.
+ *
+ * When a function queries multiple record types for one name, it issues one
+ * query for each query record type. Each query returns a (status, rcode,
+ * text). Only one of these (status, rcode, text) will be returned to the
+ * caller. The selection is based on the status code precedence.
+ *
+ * - Return DNS_OK (and the corresponding rcode) as long as any query returned
+ * DNS_OK. If this is changed, then code needs to be added to prevent memory
+ * leaks.
+ *
+ * - Return DNS_RETRY (and the corresponding rcode and text) instead of any
+ * hard negative result.
+ *
+ * - Return DNS_NOTFOUND (and the corresponding rcode and text) only when all
+ * queries returned DNS_NOTFOUND.
+ *
+ * DNS_POLICY ranks higher than DNS_RETRY because there was a DNS_OK result,
+ * but the reply filter dropped it. This is a very soft error.
+ *
+ * Below is the precedence order. The order between DNS_RETRY and DNS_NOTFOUND
+ * is arbitrary.
+ */
+#define DNS_RECURSE (-7) /* internal only: recursion needed */
+#define DNS_NOTFOUND (-6) /* query ok, data not found */
+#define DNS_NULLMX (-5) /* query ok, service unavailable */
+#define DNS_FAIL (-4) /* query failed, don't retry */
+#define DNS_INVAL (-3) /* query ok, malformed reply */
+#define DNS_RETRY (-2) /* query failed, try again */
+#define DNS_POLICY (-1) /* query ok, all records dropped */
+#define DNS_OK 0 /* query succeeded */
+
+ /*
+ * How long can a DNS name or single text value be?
+ */
+#define DNS_NAME_LEN 1024
+
+ /*
+ * dns_rr_filter.c.
+ */
+extern void dns_rr_filter_compile(const char *, const char *);
+
+#ifdef LIBDNS_INTERNAL
+#include <maps.h>
+extern MAPS *dns_rr_filter_maps;
+extern int dns_rr_filter_execute(DNS_RR **);
+
+#endif
+
+ /*
+ * dns_str_resflags.c
+ */
+const char *dns_str_resflags(unsigned long);
+
+ /*
+ * dns_sec.c.
+ */
+#define DNS_SEC_FLAG_AVAILABLE (1<<0) /* got some DNSSEC validated reply */
+#define DNS_SEC_FLAG_DONT_PROBE (1<<1) /* probe already sent, or disabled */
+
+#define DNS_SEC_STATS_SET(flags) (dns_sec_stats |= (flags))
+#define DNS_SEC_STATS_TEST(flags) (dns_sec_stats & (flags))
+
+extern int dns_sec_stats; /* See DNS_SEC_FLAG_XXX above */
+extern void dns_sec_probe(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/dns/dns_lookup.c b/src/dns/dns_lookup.c
new file mode 100644
index 0000000..615902d
--- /dev/null
+++ b/src/dns/dns_lookup.c
@@ -0,0 +1,1272 @@
+/*++
+/* NAME
+/* dns_lookup 3
+/* SUMMARY
+/* domain name service lookup
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_lookup(name, type, rflags, list, fqdn, why)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/*
+/* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int lflags;
+/* unsigned ltype;
+/*
+/* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int lflags;
+/* unsigned *ltype;
+/*
+/* int dns_get_h_errno()
+/* AUXILIARY FUNCTIONS
+/* extern int var_dns_ncache_ttl_fix;
+/*
+/* int dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/*
+/* int dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags,
+/* ltype, ...)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* int lflags;
+/* unsigned ltype;
+/*
+/* int dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
+/* ltype)
+/* const char *name;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* int lflags;
+/* unsigned *ltype;
+/*
+/* int dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags)
+/* const char *name;
+/* unsigned type;
+/* unsigned rflags;
+/* DNS_RR **list;
+/* VSTRING *fqdn;
+/* VSTRING *why;
+/* int *rcode;
+/* unsigned lflags;
+/* DESCRIPTION
+/* dns_lookup() looks up DNS resource records. When requested to
+/* look up data other than type CNAME, it will follow a limited
+/* number of CNAME indirections. All result names (including
+/* null terminator) will fit a buffer of size DNS_NAME_LEN.
+/* All name results are validated by \fIvalid_hostname\fR();
+/* an invalid name is reported as a DNS_INVAL result, while
+/* malformed replies are reported as transient errors.
+/*
+/* dns_get_h_errno() returns the last error. This deprecates
+/* usage of the global h_errno variable. We should not rely
+/* on that being updated.
+/*
+/* dns_lookup_l() and dns_lookup_v() allow the user to specify
+/* a list of resource types.
+/*
+/* dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv()
+/* accept or return additional information.
+/*
+/* The var_dns_ncache_ttl_fix variable controls a workaround
+/* for res_search(3) implementations that break the
+/* DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not
+/* support EDNS0 or DNSSEC, but it should be sufficient for
+/* DNSBL/DNSWL lookups.
+/* INPUTS
+/* .ad
+/* .fi
+/* .IP name
+/* The name to be looked up in the domain name system.
+/* This name must pass the valid_hostname() test; it
+/* must not be an IP address.
+/* .IP type
+/* The resource record type to be looked up (T_A, T_MX etc.).
+/* .IP rflags
+/* Resolver flags. These are a bitwise OR of:
+/* .RS
+/* .IP RES_DEBUG
+/* Print debugging information.
+/* .IP RES_DNSRCH
+/* Search local domain and parent domains.
+/* .IP RES_DEFNAMES
+/* Append local domain to unqualified names.
+/* .IP RES_USE_DNSSEC
+/* Request DNSSEC validation. This flag is silently ignored
+/* when the system stub resolver API, resolver(3), does not
+/* implement DNSSEC.
+/* Automatically turns on the RES_TRUSTAD flag on systems that
+/* support this flag (this behavior will be more configurable
+/* in a later release).
+/* .RE
+/* .IP lflags
+/* Flags that control the operation of the dns_lookup*()
+/* functions. DNS_REQ_FLAG_NONE requests no special processing.
+/* Otherwise, specify one or more of the following:
+/* .RS
+/* .IP DNS_REQ_FLAG_STOP_INVAL
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_INVAL.
+/* .IP DNS_REQ_FLAG_STOP_NULLMX
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_NULLMX.
+/* .IP DNS_REQ_FLAG_STOP_MX_POLICY
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_POLICY
+/* for an MX query.
+/* .IP DNS_REQ_FLAG_STOP_OK
+/* This flag is used by dns_lookup_l() and dns_lookup_v().
+/* Invoke dns_lookup() for the resource types in the order as
+/* specified, and return when dns_lookup() returns DNS_OK.
+/* .IP DNS_REQ_FLAG_NCACHE_TTL
+/* When the lookup result status is DNS_NOTFOUND, return the
+/* SOA record(s) from the authority section in the reply, if
+/* available. The per-record reply TTL specifies how long the
+/* DNS_NOTFOUND answer is valid. The caller should pass the
+/* record(s) to dns_rr_free().
+/* Logs a warning if the RES_DNSRCH or RES_DEFNAMES resolver
+/* flags are set, and disables those flags.
+/* .RE
+/* .IP ltype
+/* The resource record types to be looked up. In the case of
+/* dns_lookup_l(), this is a null-terminated argument list.
+/* In the case of dns_lookup_v(), this is a null-terminated
+/* integer array.
+/* OUTPUTS
+/* .ad
+/* .fi
+/* .IP list
+/* A null pointer, or a pointer to a variable that receives a
+/* list of requested resource records.
+/* .IP fqdn
+/* A null pointer, or storage for the fully-qualified domain
+/* name found for \fIname\fR.
+/* .IP why
+/* A null pointer, or storage for the reason for failure.
+/* .IP rcode
+/* Pointer to storage for the reply RCODE value. This gives
+/* more detailed information than DNS_FAIL, DNS_RETRY, etc.
+/* DIAGNOSTICS
+/* If DNSSEC validation is requested but the response is not
+/* DNSSEC validated, dns_lookup() will send a one-time probe
+/* query as configured with the \fBdnssec_probe\fR configuration
+/* parameter, and will log a warning when the probe response
+/* was not DNSSEC validated.
+/* .PP
+/* dns_lookup() returns one of the following codes and sets the
+/* \fIwhy\fR argument accordingly:
+/* .IP DNS_OK
+/* The DNS query succeeded.
+/* .IP DNS_POLICY
+/* The DNS query succeeded, but the answer did not pass the
+/* policy filter.
+/* .IP DNS_NOTFOUND
+/* The DNS query succeeded; the requested information was not found.
+/* .IP DNS_NULLMX
+/* The DNS query succeeded; the requested service is unavailable.
+/* This is returned when the list argument is not a null
+/* pointer, and an MX lookup result contains a null server
+/* name (so-called "nullmx" record).
+/* .IP DNS_INVAL
+/* The DNS query succeeded; the result failed the valid_hostname() test.
+/*
+/* NOTE: the valid_hostname() test is skipped for results that
+/* the caller suppresses explicitly. For example, when the
+/* caller requests MX record lookup but specifies a null
+/* resource record list argument, no syntax check will be done
+/* for MX server names.
+/* .IP DNS_RETRY
+/* The query failed, or the reply was malformed.
+/* The problem is considered transient.
+/* .IP DNS_FAIL
+/* The query failed.
+/* BUGS
+/* dns_lookup() implements a subset of all possible resource types:
+/* CNAME, MX, A, and some records with similar formatting requirements.
+/* It is unwise to specify the T_ANY wildcard resource type.
+/*
+/* It takes a surprising amount of code to accomplish what appears
+/* to be a simple task. Later versions of the mail system may implement
+/* their own DNS client software.
+/* SEE ALSO
+/* dns_rr(3) resource record memory and list management
+/* 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 <netdb.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+#include <valid_hostname.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#define LIBDNS_INTERNAL
+#include "dns.h"
+
+/* Local stuff. */
+
+ /*
+ * Structure to keep track of things while decoding a name server reply.
+ */
+#define DEF_DNS_REPLY_SIZE 4096 /* in case we're using TCP */
+#define MAX_DNS_REPLY_SIZE 65536 /* in case we're using TCP */
+#define MAX_DNS_QUERY_SIZE 2048 /* XXX */
+
+typedef struct DNS_REPLY {
+ unsigned char *buf; /* raw reply data */
+ size_t buf_len; /* reply buffer length */
+ int rcode; /* unfiltered reply code */
+ int dnssec_ad; /* DNSSEC AD bit */
+ int query_count; /* number of queries */
+ int answer_count; /* number of answers */
+ int auth_count; /* number of authority records */
+ unsigned char *query_start; /* start of query data */
+ unsigned char *answer_start; /* start of answer data */
+ unsigned char *end; /* first byte past reply */
+} DNS_REPLY;
+
+ /*
+ * Test/set primitives to determine if the reply buffer contains a server
+ * response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL,
+ * and the DNS server replies that the requested record does not exist.
+ */
+#define TEST_HAVE_DNS_REPLY_PACKET(r) ((r)->end > (r)->buf)
+#define SET_HAVE_DNS_REPLY_PACKET(r, l) ((r)->end = (r)->buf + (l))
+#define SET_NO_DNS_REPLY_PACKET(r) ((r)->end = (r)->buf)
+
+#define INET_ADDR_LEN 4 /* XXX */
+#define INET6_ADDR_LEN 16 /* XXX */
+
+ /*
+ * Use the threadsafe resolver API if available, not because it is theadsafe,
+ * but because it has more functionality.
+ */
+#ifdef USE_RES_NCALLS
+static struct __res_state dns_res_state;
+
+#define DNS_RES_NINIT res_ninit
+#define DNS_RES_NMKQUERY res_nmkquery
+#define DNS_RES_NSEARCH res_nsearch
+#define DNS_RES_NSEND res_nsend
+#define DNS_GET_H_ERRNO(statp) ((statp)->res_h_errno)
+
+ /*
+ * Alias new resolver API calls to the legacy resolver API which stores
+ * resolver and error state in global variables.
+ */
+#else
+#define dns_res_state _res
+#define DNS_RES_NINIT(statp) res_init()
+#define DNS_RES_NMKQUERY(statp, op, dname, class, type, data, datalen, \
+ newrr, buf, buflen) \
+ res_mkquery((op), (dname), (class), (type), (data), (datalen), \
+ (newrr), (buf), (buflen))
+#define DNS_RES_NSEARCH(statp, dname, class, type, answer, anslen) \
+ res_search((dname), (class), (type), (answer), (anslen))
+#define DNS_RES_NSEND(statp, msg, msglen, answer, anslen) \
+ res_send((msg), (msglen), (answer), (anslen))
+#define DNS_GET_H_ERRNO(statp) (h_errno)
+#endif
+
+#ifdef USE_SET_H_ERRNO
+#define DNS_SET_H_ERRNO(statp, err) (set_h_errno(err))
+#else
+#define DNS_SET_H_ERRNO(statp, err) (DNS_GET_H_ERRNO(statp) = (err))
+#endif
+
+ /*
+ * To improve postscreen's allowlisting support, we need to know how long a
+ * DNSBL "not found" answer is valid. The 2010 implementation assumed it was
+ * valid for 3600 seconds. That is too long by 2015 standards.
+ *
+ * Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE),
+ * where a DNS server provides the TTL of a "not found" response as the TTL
+ * of an SOA record in the authority section.
+ *
+ * Unfortunately, the res_search() and res_query() API gets in the way. These
+ * functions overload their result value, the server reply length, and
+ * return -1 when the requested record does not exist. With libbind-based
+ * implementations, the server response is still available in an application
+ * buffer, thanks to the promise that res_query() and res_search() invoke
+ * res_send(), which returns the full server response even if the requested
+ * record does not exist.
+ *
+ * If this promise is broken (for example, res_search() does not call
+ * res_send(), but some non-libbind implementation that updates the
+ * application buffer only when the requested record exists), then we have a
+ * way out by setting the var_dns_ncache_ttl_fix variable. This enables a
+ * limited res_query() clone that should be sufficient for DNSBL / DNSWL
+ * lookups.
+ *
+ * The libunbound API does not comingle the reply length and reply status
+ * information, but that will have to wait until it is safe to make
+ * libunbound a mandatory dependency for Postfix.
+ */
+#ifdef HAVE_RES_SEND
+
+/* dns_neg_query - a res_query() clone that can return negative replies */
+
+static int dns_neg_query(const char *name, int class, int type,
+ unsigned char *answer, int anslen)
+{
+ unsigned char msg_buf[MAX_DNS_QUERY_SIZE];
+ HEADER *reply_header = (HEADER *) answer;
+ int len;
+
+ /*
+ * Differences with res_query() from libbind:
+ *
+ * - This function returns a positive server reply length not only in case
+ * of success, but in all cases where a server reply is available that
+ * passes the preliminary checks in res_send().
+ *
+ * - This function clears h_errno in case of success. The caller must use
+ * h_errno instead of the return value to decide if the lookup was
+ * successful.
+ *
+ * - No support for EDNS0 and DNSSEC (including turning off EDNS0 after
+ * error). That should be sufficient for DNS reputation lookups where the
+ * reply contains a small number of IP addresses. TXT records are out of
+ * scope for this workaround.
+ */
+ reply_header->rcode = NOERROR;
+
+#define NO_MKQUERY_DATA_BUF ((unsigned char *) 0)
+#define NO_MKQUERY_DATA_LEN ((int) 0)
+#define NO_MKQUERY_NEWRR ((unsigned char *) 0)
+
+ if ((len = DNS_RES_NMKQUERY(&dns_res_state,
+ QUERY, name, class, type, NO_MKQUERY_DATA_BUF,
+ NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR,
+ msg_buf, sizeof(msg_buf))) < 0) {
+ DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY);
+ if (msg_verbose)
+ msg_info("res_nmkquery() failed");
+ return (len);
+ } else if ((len = DNS_RES_NSEND(&dns_res_state,
+ msg_buf, len, answer, anslen)) < 0) {
+ DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
+ if (msg_verbose)
+ msg_info("res_nsend() failed");
+ return (len);
+ } else {
+ switch (reply_header->rcode) {
+ case NXDOMAIN:
+ DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
+ break;
+ case NOERROR:
+ if (reply_header->ancount != 0)
+ DNS_SET_H_ERRNO(&dns_res_state, 0);
+ else
+ DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
+ break;
+ case SERVFAIL:
+ DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
+ break;
+ default:
+ DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY);
+ break;
+ }
+ return (len);
+ }
+}
+
+#endif
+
+/* dns_neg_search - res_search() that can return negative replies */
+
+static int dns_neg_search(const char *name, int class, int type,
+ unsigned char *answer, int anslen, int keep_notfound)
+{
+ int len;
+
+ /*
+ * Differences with res_search() from libbind:
+ *
+ * - With a non-zero keep_notfound argument, this function returns a
+ * positive server reply length not only in case of success, but also in
+ * case of a "notfound" reply status. The keep_notfound argument is
+ * usually zero, which allows us to avoid an unnecessary memset() call in
+ * the most common use case.
+ *
+ * - This function clears h_errno in case of success. The caller must use
+ * h_errno instead of the return value to decide if a lookup was
+ * successful.
+ */
+#define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA)
+
+ if (keep_notfound)
+ /* Prepare for returning a null-padded server reply. */
+ memset(answer, 0, anslen);
+ len = DNS_RES_NSEARCH(&dns_res_state, name, class, type, answer, anslen);
+ /* Begin API creep workaround. */
+ if (len < 0 && DNS_GET_H_ERRNO(&dns_res_state) == 0) {
+ DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
+ msg_warn("res_nsearch(state, \"%s\", %d, %d, %p, %d) returns %d"
+ " with h_errno==0 -- setting h_errno=TRY_AGAIN",
+ name, class, type, answer, anslen, len);
+ }
+ /* End API creep workaround. */
+ if (len > 0) {
+ DNS_SET_H_ERRNO(&dns_res_state, 0);
+ } else if (keep_notfound
+ && NOT_FOUND_H_ERRNO(DNS_GET_H_ERRNO(&dns_res_state))) {
+ /* Expect to return a null-padded server reply. */
+ len = anslen;
+ }
+ return (len);
+}
+
+/* dns_query - query name server and pre-parse the reply */
+
+static int dns_query(const char *name, int type, unsigned flags,
+ DNS_REPLY *reply, VSTRING *why, unsigned lflags)
+{
+ HEADER *reply_header;
+ int len;
+ unsigned long saved_options;
+ int keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL);
+
+ /*
+ * Initialize the reply buffer.
+ */
+ if (reply->buf == 0) {
+ reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
+ reply->buf_len = DEF_DNS_REPLY_SIZE;
+ }
+
+ /*
+ * Initialize the name service.
+ */
+ if ((dns_res_state.options & RES_INIT) == 0
+ && DNS_RES_NINIT(&dns_res_state) < 0) {
+ if (why)
+ vstring_strcpy(why, "Name service initialization failure");
+ return (DNS_FAIL);
+ }
+
+ /*
+ * Set search options: debugging, parent domain search, append local
+ * domain. Do not allow the user to control other features.
+ */
+#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC)
+
+ if ((flags & USER_FLAGS) != flags)
+ msg_panic("dns_query: bad flags: %d", flags);
+
+ /*
+ * Set extra options that aren't exposed to the application.
+ */
+#define XTRA_FLAGS (RES_USE_EDNS0 | RES_TRUSTAD)
+
+ if (DNS_WANT_DNSSEC_VALIDATION(flags))
+ flags |= (RES_USE_EDNS0 | RES_TRUSTAD);
+
+ /*
+ * Can't append domains: we need the right SOA TTL.
+ */
+#define APPEND_DOMAIN_FLAGS (RES_DNSRCH | RES_DEFNAMES)
+
+ if (keep_notfound && (flags & APPEND_DOMAIN_FLAGS)) {
+ msg_warn("negative caching disables RES_DEFNAMES and RES_DNSRCH");
+ flags &= ~APPEND_DOMAIN_FLAGS;
+ }
+
+ /*
+ * Save and restore resolver options that we overwrite, to avoid
+ * surprising behavior in other code that also invokes the resolver.
+ */
+#define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS)
+
+ saved_options = (dns_res_state.options & SAVE_FLAGS);
+
+ /*
+ * Perform the lookup. Claim that the information cannot be found if and
+ * only if the name server told us so.
+ */
+ for (;;) {
+ dns_res_state.options &= ~saved_options;
+ dns_res_state.options |= flags;
+ if (keep_notfound && var_dns_ncache_ttl_fix) {
+#ifdef HAVE_RES_SEND
+ len = dns_neg_query((char *) name, C_IN, type, reply->buf,
+ reply->buf_len);
+#else
+ var_dns_ncache_ttl_fix = 0;
+ msg_warn("system library does not support %s=yes"
+ " -- ignoring this setting", VAR_DNS_NCACHE_TTL_FIX);
+ len = dns_neg_search((char *) name, C_IN, type, reply->buf,
+ reply->buf_len, keep_notfound);
+#endif
+ } else {
+ len = dns_neg_search((char *) name, C_IN, type, reply->buf,
+ reply->buf_len, keep_notfound);
+ }
+ dns_res_state.options &= ~flags;
+ dns_res_state.options |= saved_options;
+ reply_header = (HEADER *) reply->buf;
+ reply->rcode = reply_header->rcode;
+ if ((reply->dnssec_ad = !!reply_header->ad) != 0)
+ DNS_SEC_STATS_SET(DNS_SEC_FLAG_AVAILABLE);
+ if (DNS_GET_H_ERRNO(&dns_res_state) != 0) {
+ if (why)
+ vstring_sprintf(why, "Host or domain name not found. "
+ "Name service error for name=%s type=%s: %s",
+ name, dns_strtype(type),
+ dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
+ if (msg_verbose)
+ msg_info("dns_query: %s (%s): %s",
+ name, dns_strtype(type),
+ dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
+ switch (DNS_GET_H_ERRNO(&dns_res_state)) {
+ case NO_RECOVERY:
+ return (DNS_FAIL);
+ case HOST_NOT_FOUND:
+ case NO_DATA:
+ if (keep_notfound)
+ break;
+ SET_NO_DNS_REPLY_PACKET(reply);
+ return (DNS_NOTFOUND);
+ default:
+ return (DNS_RETRY);
+ }
+ } else {
+ if (msg_verbose)
+ msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
+ }
+
+ if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
+ break;
+ reply->buf = (unsigned char *)
+ myrealloc((void *) reply->buf, 2 * reply->buf_len);
+ reply->buf_len *= 2;
+ }
+
+ /*
+ * Future proofing. If this reaches the panic call, then some code change
+ * introduced a bug.
+ */
+ if (len < 0)
+ msg_panic("dns_query: bad length %d (h_errno=%s)",
+ len, dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
+
+ /*
+ * Paranoia.
+ */
+ if (len > reply->buf_len) {
+ msg_warn("reply length %d > buffer length %d for name=%s type=%s",
+ len, (int) reply->buf_len, name, dns_strtype(type));
+ len = reply->buf_len;
+ }
+
+ /*
+ * Initialize the reply structure. Some structure members are filled on
+ * the fly while the reply is being parsed.
+ */
+ SET_HAVE_DNS_REPLY_PACKET(reply, len);
+ reply->query_start = reply->buf + sizeof(HEADER);
+ reply->answer_start = 0;
+ reply->query_count = ntohs(reply_header->qdcount);
+ reply->answer_count = ntohs(reply_header->ancount);
+ reply->auth_count = ntohs(reply_header->nscount);
+ if (msg_verbose > 1)
+ msg_info("dns_query: reply len=%d ancount=%d nscount=%d",
+ len, reply->answer_count, reply->auth_count);
+
+ /*
+ * Future proofing. If this reaches the panic call, then some code change
+ * introduced a bug.
+ */
+ if (DNS_GET_H_ERRNO(&dns_res_state) == 0) {
+ return (DNS_OK);
+ } else if (keep_notfound) {
+ return (DNS_NOTFOUND);
+ } else {
+ msg_panic("dns_query: unexpected reply status: %s",
+ dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
+ }
+}
+
+/* dns_skip_query - skip query data in name server reply */
+
+static int dns_skip_query(DNS_REPLY *reply)
+{
+ int query_count = reply->query_count;
+ unsigned char *pos = reply->query_start;
+ int len;
+
+ /*
+ * For each query, skip over the domain name and over the fixed query
+ * data.
+ */
+ while (query_count-- > 0) {
+ if (pos >= reply->end)
+ return DNS_RETRY;
+ len = dn_skipname(pos, reply->end);
+ if (len < 0)
+ return (DNS_RETRY);
+ pos += len + QFIXEDSZ;
+ }
+ reply->answer_start = pos;
+ return (DNS_OK);
+}
+
+/* dns_get_fixed - extract fixed data from resource record */
+
+static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
+{
+ GETSHORT(fixed->type, pos);
+ GETSHORT(fixed->class, pos);
+ GETLONG(fixed->ttl, pos);
+ GETSHORT(fixed->length, pos);
+
+ if (fixed->class != C_IN) {
+ msg_warn("dns_get_fixed: bad class: %u", fixed->class);
+ return (DNS_RETRY);
+ }
+ return (DNS_OK);
+}
+
+/* valid_rr_name - validate hostname in resource record */
+
+static int valid_rr_name(const char *name, const char *location,
+ unsigned type, DNS_REPLY *reply)
+{
+ char temp[DNS_NAME_LEN];
+ char *query_name;
+ int len;
+ char *gripe;
+ int result;
+
+ /*
+ * People aren't supposed to specify numeric names where domain names are
+ * required, but it "works" with some mailers anyway, so people complain
+ * when software doesn't bend over backwards.
+ */
+#define PASS_NAME 1
+#define REJECT_NAME 0
+
+ if (valid_hostaddr(name, DONT_GRIPE)) {
+ result = PASS_NAME;
+ gripe = "numeric domain name";
+ } else if (!valid_hostname(name, DO_GRIPE | DO_WILDCARD)) {
+ result = REJECT_NAME;
+ gripe = "malformed domain name";
+ } else {
+ result = PASS_NAME;
+ gripe = 0;
+ }
+
+ /*
+ * If we have a gripe, show some context, including the name used in the
+ * query and the type of reply that we're looking at.
+ */
+ if (gripe) {
+ len = dn_expand(reply->buf, reply->end, reply->query_start,
+ temp, DNS_NAME_LEN);
+ query_name = (len < 0 ? "*unparsable*" : temp);
+ msg_warn("%s in %s of %s record for %s: %.100s",
+ gripe, location, dns_strtype(type), query_name, name);
+ }
+ return (result);
+}
+
+/* dns_get_rr - extract resource record from name server reply */
+
+static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
+ unsigned char *pos, char *rr_name,
+ DNS_FIXED *fixed)
+{
+ char temp[DNS_NAME_LEN];
+ char *tempbuf = temp;
+ UINT32_TYPE soa_buf[5];
+ int comp_len;
+ ssize_t data_len;
+ unsigned pref = 0;
+ unsigned char *src;
+ unsigned char *dst;
+ int ch;
+
+#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
+
+ *list = 0;
+
+ switch (fixed->type) {
+ default:
+ msg_panic("dns_get_rr: don't know how to extract resource type %s",
+ dns_strtype(fixed->type));
+ case T_CNAME:
+ case T_DNAME:
+ case T_MB:
+ case T_MG:
+ case T_MR:
+ case T_NS:
+ case T_PTR:
+ if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
+ return (DNS_RETRY);
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ data_len = strlen(temp) + 1;
+ break;
+ case T_MX:
+ GETSHORT(pref, pos);
+ if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
+ return (DNS_RETRY);
+ /* Don't even think of returning an invalid hostname to the caller. */
+ if (*temp == 0)
+ return (DNS_NULLMX); /* TODO: descriptive text */
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ data_len = strlen(temp) + 1;
+ break;
+ case T_A:
+ if (fixed->length != INET_ADDR_LEN) {
+ msg_warn("extract_answer: bad address length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ if (fixed->length > sizeof(temp))
+ msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
+ fixed->length);
+ memcpy(temp, pos, fixed->length);
+ data_len = fixed->length;
+ break;
+#ifdef T_AAAA
+ case T_AAAA:
+ if (fixed->length != INET6_ADDR_LEN) {
+ msg_warn("extract_answer: bad address length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ if (fixed->length > sizeof(temp))
+ msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
+ fixed->length);
+ memcpy(temp, pos, fixed->length);
+ data_len = fixed->length;
+ break;
+#endif
+
+ /*
+ * We impose the same length limit here as for DNS names. However,
+ * see T_TLSA discussion below.
+ */
+ case T_TXT:
+ data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
+ for (src = pos + 1, dst = (unsigned char *) (temp);
+ dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
+ ch = *src++;
+ *dst++ = (ISPRINT(ch) ? ch : ' ');
+ }
+ *dst = 0;
+ break;
+
+ /*
+ * For a full certificate, fixed->length may be longer than
+ * sizeof(tmpbuf) == DNS_NAME_LEN. Since we don't need a decode
+ * buffer, just copy the raw data into the rr.
+ *
+ * XXX Reject replies with bogus length < 3.
+ *
+ * XXX What about enforcing a sane upper bound? The RFC 1035 hard
+ * protocol limit is the RRDATA length limit of 65535.
+ */
+ case T_TLSA:
+ data_len = fixed->length;
+ tempbuf = (char *) pos;
+ break;
+
+ /*
+ * We use the SOA record TTL to determine the negative reply TTL. We
+ * save the time fields in the SOA record for debugging, but for now
+ * we don't bother saving the source host and mailbox information, as
+ * that would require changes to the DNS_RR structure and APIs. See
+ * also code in dns_strrecord().
+ */
+ case T_SOA:
+ comp_len = dn_skipname(pos, reply->end);
+ if (comp_len < 0)
+ return (DNS_RETRY);
+ pos += comp_len;
+ comp_len = dn_skipname(pos, reply->end);
+ if (comp_len < 0)
+ return (DNS_RETRY);
+ pos += comp_len;
+ if (reply->end - pos < sizeof(soa_buf)) {
+ msg_warn("extract_answer: bad SOA length: %d", fixed->length);
+ return (DNS_RETRY);
+ }
+ GETLONG(soa_buf[0], pos); /* Serial */
+ GETLONG(soa_buf[1], pos); /* Refresh */
+ GETLONG(soa_buf[2], pos); /* Retry */
+ GETLONG(soa_buf[3], pos); /* Expire */
+ GETLONG(soa_buf[4], pos); /* Ncache TTL */
+ tempbuf = (char *) soa_buf;
+ data_len = sizeof(soa_buf);
+ break;
+ }
+ *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
+ fixed->ttl, pref, tempbuf, data_len);
+ return (DNS_OK);
+}
+
+/* dns_get_alias - extract CNAME from name server reply */
+
+static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
+ DNS_FIXED *fixed, char *cname, int c_len)
+{
+ if (fixed->type != T_CNAME)
+ msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
+ if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
+ return (DNS_RETRY);
+ if (!valid_rr_name(cname, "resource data", fixed->type, reply))
+ return (DNS_INVAL);
+ return (DNS_OK);
+}
+
+/* dns_get_answer - extract answers from name server reply */
+
+static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
+ DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len,
+ int *maybe_secure)
+{
+ char rr_name[DNS_NAME_LEN];
+ unsigned char *pos;
+ int answer_count = reply->answer_count;
+ int len;
+ DNS_FIXED fixed;
+ DNS_RR *rr;
+ int resource_found = 0;
+ int cname_found = 0;
+ int not_found_status = DNS_NOTFOUND; /* can't happen */
+ int status;
+
+ /*
+ * Initialize. Skip over the name server query if we haven't yet.
+ */
+ if (reply->answer_start == 0)
+ if ((status = dns_skip_query(reply)) < 0)
+ return (status);
+ pos = reply->answer_start;
+
+ /*
+ * Either this, or use a GOTO for emergency exits. The purpose is to
+ * prevent incomplete answers from being passed back to the caller.
+ */
+#define CORRUPT(status) { \
+ if (rrlist && *rrlist) { \
+ dns_rr_free(*rrlist); \
+ *rrlist = 0; \
+ } \
+ return (status); \
+ }
+
+ /*
+ * Iterate over all answers.
+ */
+ while (answer_count-- > 0) {
+
+ /*
+ * Optionally extract the fully-qualified domain name.
+ */
+ if (pos >= reply->end)
+ CORRUPT(DNS_RETRY);
+ len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
+ if (len < 0)
+ CORRUPT(DNS_RETRY);
+ pos += len;
+
+ /*
+ * Extract the fixed reply data: type, class, ttl, length.
+ */
+ if (pos + RRFIXEDSZ > reply->end)
+ CORRUPT(DNS_RETRY);
+ if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
+ CORRUPT(status);
+ if (strcmp(orig_name, ".") == 0 && *rr_name == 0)
+ /* Allow empty response name for root queries. */ ;
+ else if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
+ CORRUPT(DNS_INVAL);
+ if (fqdn)
+ vstring_strcpy(fqdn, rr_name);
+ if (msg_verbose)
+ msg_info("dns_get_answer: type %s for %s",
+ dns_strtype(fixed.type), rr_name);
+ pos += RRFIXEDSZ;
+
+ /*
+ * Optionally extract the requested resource or CNAME data.
+ */
+ if (pos + fixed.length > reply->end)
+ CORRUPT(DNS_RETRY);
+ if (type == fixed.type || type == T_ANY) { /* requested type */
+ if (rrlist) {
+ if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
+ &fixed)) == DNS_OK) {
+ resource_found++;
+ rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
+ *rrlist = dns_rr_append(*rrlist, rr);
+ } else if (status == DNS_NULLMX) {
+ CORRUPT(status); /* TODO: use better name */
+ } else if (not_found_status != DNS_RETRY)
+ not_found_status = status;
+ } else
+ resource_found++;
+ } else if (fixed.type == T_CNAME) { /* cname resource */
+ cname_found++;
+ if (cname && c_len > 0)
+ if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
+ CORRUPT(status);
+ if (!reply->dnssec_ad)
+ *maybe_secure = 0;
+ }
+ pos += fixed.length;
+ }
+
+ /*
+ * See what answer we came up with. Report success when the requested
+ * information was found. Otherwise, when a CNAME was found, report that
+ * more recursion is needed. Otherwise report failure.
+ */
+ if (resource_found)
+ return (DNS_OK);
+ if (cname_found)
+ return (DNS_RECURSE);
+ return (not_found_status);
+}
+
+/* dns_lookup_x - DNS lookup user interface */
+
+int dns_lookup_x(const char *name, unsigned type, unsigned flags,
+ DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
+ int *rcode, unsigned lflags)
+{
+ char cname[DNS_NAME_LEN];
+ int c_len = sizeof(cname);
+ static DNS_REPLY reply;
+ int count;
+ int status;
+ int maybe_secure = 1; /* Query name presumed secure */
+ const char *orig_name = name;
+
+ /*
+ * Reset results early. DNS_OK is not the only status that returns
+ * resource records; DNS_NOTFOUND will do that too, if requested.
+ */
+ if (rrlist)
+ *rrlist = 0;
+
+ /*
+ * DJBDNS produces a bogus A record when given a numerical hostname.
+ */
+ if (valid_hostaddr(name, DONT_GRIPE)) {
+ if (why)
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
+ if (rcode)
+ *rcode = NXDOMAIN;
+ DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
+ return (DNS_NOTFOUND);
+ }
+
+ /*
+ * The Linux resolver misbehaves when given an invalid domain name.
+ */
+ if (strcmp(name, ".") && !valid_hostname(name, DONT_GRIPE | DO_WILDCARD)) {
+ if (why)
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
+ if (rcode)
+ *rcode = NXDOMAIN;
+ DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
+ return (DNS_NOTFOUND);
+ }
+
+ /*
+ * Perform the lookup. Follow CNAME chains, but only up to a
+ * pre-determined maximum.
+ */
+ for (count = 0; count < 10; count++) {
+
+ /*
+ * Perform the DNS lookup, and pre-parse the name server reply.
+ */
+ status = dns_query(name, type, flags, &reply, why, lflags);
+ if (rcode)
+ *rcode = reply.rcode;
+ if (status != DNS_OK) {
+
+ /*
+ * If the record does not exist, and we have a copy of the server
+ * response, try to extract the negative caching TTL for the SOA
+ * record in the authority section. DO NOT return an error if an
+ * SOA record is malformed.
+ */
+ if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
+ && reply.auth_count > 0) {
+ reply.answer_count = reply.auth_count; /* XXX TODO: Fix API */
+ (void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
+ cname, c_len, &maybe_secure);
+ }
+ if (DNS_WANT_DNSSEC_VALIDATION(flags)
+ && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
+ DNS_SEC_FLAG_DONT_PROBE))
+ dns_sec_probe(flags); /* XXX Clobbers 'reply' */
+ return (status);
+ }
+
+ /*
+ * Extract resource records of the requested type. Pick up CNAME
+ * information just in case the requested data is not found.
+ */
+ status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
+ cname, c_len, &maybe_secure);
+ if (DNS_WANT_DNSSEC_VALIDATION(flags)
+ && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
+ DNS_SEC_FLAG_DONT_PROBE))
+ dns_sec_probe(flags); /* XXX Clobbers 'reply' */
+ switch (status) {
+ default:
+ if (why)
+ vstring_sprintf(why, "Name service error for name=%s type=%s: "
+ "Malformed or unexpected name server reply",
+ name, dns_strtype(type));
+ return (status);
+ case DNS_NULLMX:
+ if (why)
+ vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
+ name);
+ DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
+ return (status);
+ case DNS_OK:
+ if (rrlist && dns_rr_filter_maps) {
+ if (dns_rr_filter_execute(rrlist) < 0) {
+ if (why)
+ vstring_sprintf(why,
+ "Error looking up name=%s type=%s: "
+ "Invalid DNS reply filter syntax",
+ name, dns_strtype(type));
+ dns_rr_free(*rrlist);
+ *rrlist = 0;
+ status = DNS_RETRY;
+ } else if (*rrlist == 0) {
+ if (why)
+ vstring_sprintf(why,
+ "Error looking up name=%s type=%s: "
+ "DNS reply filter drops all results",
+ name, dns_strtype(type));
+ status = DNS_POLICY;
+ }
+ }
+ return (status);
+ case DNS_RECURSE:
+ if (msg_verbose)
+ msg_info("dns_lookup: %s aliased to %s", name, cname);
+#if RES_USE_DNSSEC
+
+ /*
+ * Once an intermediate CNAME reply is not validated, all
+ * consequent RRs are deemed not validated, so we don't ask for
+ * further DNSSEC replies.
+ */
+ if (maybe_secure == 0)
+ flags &= ~RES_USE_DNSSEC;
+#endif
+ name = cname;
+ }
+ }
+ if (why)
+ vstring_sprintf(why, "Name server loop for %s", name);
+ msg_warn("dns_lookup: Name server loop for %s", name);
+ return (DNS_NOTFOUND);
+}
+
+/* dns_lookup_rl - DNS lookup interface with types list */
+
+int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
+ VSTRING *fqdn, VSTRING *why, int *rcode,
+ int lflags,...)
+{
+ va_list ap;
+ unsigned type, next;
+ int status = DNS_NOTFOUND;
+ int hpref_status = INT_MIN;
+ VSTRING *hpref_rtext = 0;
+ int hpref_rcode;
+ int hpref_h_errno;
+ DNS_RR *rr;
+
+ /* Save intermediate highest-priority result. */
+#define SAVE_HPREF_STATUS() do { \
+ hpref_status = status; \
+ if (rcode) \
+ hpref_rcode = *rcode; \
+ if (why && status != DNS_OK) \
+ vstring_strcpy(hpref_rtext ? hpref_rtext : \
+ (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
+ vstring_str(why)); \
+ hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \
+ } while (0)
+
+ /* Restore intermediate highest-priority result. */
+#define RESTORE_HPREF_STATUS() do { \
+ status = hpref_status; \
+ if (rcode) \
+ *rcode = hpref_rcode; \
+ if (why && status != DNS_OK) \
+ vstring_strcpy(why, vstring_str(hpref_rtext)); \
+ DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \
+ } while (0)
+
+ if (rrlist)
+ *rrlist = 0;
+ va_start(ap, lflags);
+ for (type = va_arg(ap, unsigned); type != 0; type = next) {
+ next = va_arg(ap, unsigned);
+ if (msg_verbose)
+ msg_info("lookup %s type %s flags %s",
+ name, dns_strtype(type), dns_str_resflags(flags));
+ status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+ fqdn, why, rcode, lflags);
+ if (rrlist && rr)
+ *rrlist = dns_rr_append(*rrlist, rr);
+ if (status == DNS_OK) {
+ if (lflags & DNS_REQ_FLAG_STOP_OK)
+ break;
+ } else if (status == DNS_INVAL) {
+ if (lflags & DNS_REQ_FLAG_STOP_INVAL)
+ break;
+ } else if (status == DNS_POLICY) {
+ if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
+ break;
+ } else if (status == DNS_NULLMX) {
+ if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
+ break;
+ }
+ /* XXX Stop after NXDOMAIN error. */
+ if (next == 0)
+ break;
+ if (status >= hpref_status)
+ SAVE_HPREF_STATUS(); /* save last info */
+ }
+ va_end(ap);
+ if (status < hpref_status)
+ RESTORE_HPREF_STATUS(); /* else report last info */
+ if (hpref_rtext)
+ vstring_free(hpref_rtext);
+ return (status);
+}
+
+/* dns_lookup_rv - DNS lookup interface with types vector */
+
+int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
+ VSTRING *fqdn, VSTRING *why, int *rcode,
+ int lflags, unsigned *types)
+{
+ unsigned type, next;
+ int status = DNS_NOTFOUND;
+ int hpref_status = INT_MIN;
+ VSTRING *hpref_rtext = 0;
+ int hpref_rcode;
+ int hpref_h_errno;
+ DNS_RR *rr;
+
+ if (rrlist)
+ *rrlist = 0;
+ for (type = *types++; type != 0; type = next) {
+ next = *types++;
+ if (msg_verbose)
+ msg_info("lookup %s type %s flags %s",
+ name, dns_strtype(type), dns_str_resflags(flags));
+ status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+ fqdn, why, rcode, lflags);
+ if (rrlist && rr)
+ *rrlist = dns_rr_append(*rrlist, rr);
+ if (status == DNS_OK) {
+ if (lflags & DNS_REQ_FLAG_STOP_OK)
+ break;
+ } else if (status == DNS_INVAL) {
+ if (lflags & DNS_REQ_FLAG_STOP_INVAL)
+ break;
+ } else if (status == DNS_POLICY) {
+ if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
+ break;
+ } else if (status == DNS_NULLMX) {
+ if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
+ break;
+ }
+ /* XXX Stop after NXDOMAIN error. */
+ if (next == 0)
+ break;
+ if (status >= hpref_status)
+ SAVE_HPREF_STATUS(); /* save last info */
+ }
+ if (status < hpref_status)
+ RESTORE_HPREF_STATUS(); /* else report last info */
+ if (hpref_rtext)
+ vstring_free(hpref_rtext);
+ return (status);
+}
+
+/* dns_get_h_errno - get the last lookup status */
+
+int dns_get_h_errno(void)
+{
+ return (DNS_GET_H_ERRNO(&dns_res_state));
+}
diff --git a/src/dns/dns_rr.c b/src/dns/dns_rr.c
new file mode 100644
index 0000000..b550788
--- /dev/null
+++ b/src/dns/dns_rr.c
@@ -0,0 +1,347 @@
+/*++
+/* NAME
+/* dns_rr 3
+/* SUMMARY
+/* resource record memory and list management
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_RR *dns_rr_create(qname, rname, type, class, ttl, preference,
+/* data, data_len)
+/* const char *qname;
+/* const char *rname;
+/* unsigned short type;
+/* unsigned short class;
+/* unsigned int ttl;
+/* unsigned preference;
+/* const char *data;
+/* size_t data_len;
+/*
+/* void dns_rr_free(list)
+/* DNS_RR *list;
+/*
+/* DNS_RR *dns_rr_copy(record)
+/* DNS_RR *record;
+/*
+/* DNS_RR *dns_rr_append(list, record)
+/* DNS_RR *list;
+/* DNS_RR *record;
+/*
+/* DNS_RR *dns_rr_sort(list, compar)
+/* DNS_RR *list
+/* int (*compar)(DNS_RR *, DNS_RR *);
+/*
+/* int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b)
+/* DNS_RR *list
+/* DNS_RR *list
+/*
+/* DNS_RR *dns_rr_shuffle(list)
+/* DNS_RR *list;
+/*
+/* DNS_RR *dns_rr_remove(list, record)
+/* DNS_RR *list;
+/* DNS_RR *record;
+/* DESCRIPTION
+/* The routines in this module maintain memory for DNS resource record
+/* information, and maintain lists of DNS resource records.
+/*
+/* dns_rr_create() creates and initializes one resource record.
+/* The \fIqname\fR field specifies the query name.
+/* The \fIrname\fR field specifies the reply name.
+/* \fIpreference\fR is used for MX records; \fIdata\fR is a null
+/* pointer or specifies optional resource-specific data;
+/* \fIdata_len\fR is the amount of resource-specific data.
+/*
+/* dns_rr_free() releases the resource used by of zero or more
+/* resource records.
+/*
+/* dns_rr_copy() makes a copy of a resource record.
+/*
+/* dns_rr_append() appends a resource record to a (list of) resource
+/* record(s).
+/* A null input list is explicitly allowed.
+/*
+/* dns_rr_sort() sorts a list of resource records into ascending
+/* order according to a user-specified criterion. The result is the
+/* sorted list.
+/*
+/* dns_rr_compare_pref_XXX() are dns_rr_sort() helpers to sort
+/* records by their MX preference and by their address family.
+/*
+/* dns_rr_shuffle() randomly permutes a list of resource records.
+/*
+/* dns_rr_remove() removes the specified record from the specified list.
+/* The updated list is the result value.
+/* The record MUST be a list member.
+/* 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>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <myrand.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+/* dns_rr_create - fill in resource record structure */
+
+DNS_RR *dns_rr_create(const char *qname, const char *rname,
+ ushort type, ushort class,
+ unsigned int ttl, unsigned pref,
+ const char *data, size_t data_len)
+{
+ DNS_RR *rr;
+
+ rr = (DNS_RR *) mymalloc(sizeof(*rr) + data_len - 1);
+ rr->qname = mystrdup(qname);
+ rr->rname = mystrdup(rname);
+ rr->type = type;
+ rr->class = class;
+ rr->ttl = ttl;
+ rr->dnssec_valid = 0;
+ rr->pref = pref;
+ if (data && data_len > 0)
+ memcpy(rr->data, data, data_len);
+ rr->data_len = data_len;
+ rr->next = 0;
+ return (rr);
+}
+
+/* dns_rr_free - destroy resource record structure */
+
+void dns_rr_free(DNS_RR *rr)
+{
+ if (rr) {
+ if (rr->next)
+ dns_rr_free(rr->next);
+ myfree(rr->qname);
+ myfree(rr->rname);
+ myfree((void *) rr);
+ }
+}
+
+/* dns_rr_copy - copy resource record */
+
+DNS_RR *dns_rr_copy(DNS_RR *src)
+{
+ ssize_t len = sizeof(*src) + src->data_len - 1;
+ DNS_RR *dst;
+
+ /*
+ * Combine struct assignment and data copy in one block copy operation.
+ */
+ dst = (DNS_RR *) mymalloc(len);
+ memcpy((void *) dst, (void *) src, len);
+ dst->qname = mystrdup(src->qname);
+ dst->rname = mystrdup(src->rname);
+ dst->next = 0;
+ return (dst);
+}
+
+/* dns_rr_append - append resource record to list */
+
+DNS_RR *dns_rr_append(DNS_RR *list, DNS_RR *rr)
+{
+ if (list == 0) {
+ list = rr;
+ } else {
+ list->next = dns_rr_append(list->next, rr);
+ }
+ return (list);
+}
+
+/* dns_rr_compare_pref_ipv6 - compare records by preference, ipv6 preferred */
+
+int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+#ifdef HAS_IPV6
+ if (a->type == b->type) /* 200412 */
+ return 0;
+ if (a->type == T_AAAA)
+ return (-1);
+ if (b->type == T_AAAA)
+ return (+1);
+#endif
+ return 0;
+}
+
+/* dns_rr_compare_pref_ipv4 - compare records by preference, ipv4 preferred */
+
+int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+#ifdef HAS_IPV6
+ if (a->type == b->type)
+ return 0;
+ if (a->type == T_AAAA)
+ return (+1);
+ if (b->type == T_AAAA)
+ return (-1);
+#endif
+ return 0;
+}
+
+/* dns_rr_compare_pref_any - compare records by preference, protocol-neutral */
+
+int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b)
+{
+ if (a->pref != b->pref)
+ return (a->pref - b->pref);
+ return 0;
+}
+
+/* dns_rr_compare_pref - binary compatibility helper after name change */
+
+int dns_rr_compare_pref(DNS_RR *a, DNS_RR *b)
+{
+ return (dns_rr_compare_pref_ipv6(a, b));
+}
+
+/* dns_rr_sort_callback - glue function */
+
+static int (*dns_rr_sort_user) (DNS_RR *, DNS_RR *);
+
+static int dns_rr_sort_callback(const void *a, const void *b)
+{
+ DNS_RR *aa = *(DNS_RR **) a;
+ DNS_RR *bb = *(DNS_RR **) b;
+
+ return (dns_rr_sort_user(aa, bb));
+}
+
+/* dns_rr_sort - sort resource record list */
+
+DNS_RR *dns_rr_sort(DNS_RR *list, int (*compar) (DNS_RR *, DNS_RR *))
+{
+ int (*saved_user) (DNS_RR *, DNS_RR *);
+ DNS_RR **rr_array;
+ DNS_RR *rr;
+ int len;
+ int i;
+
+ /*
+ * Save state and initialize.
+ */
+ saved_user = dns_rr_sort_user;
+ dns_rr_sort_user = compar;
+
+ /*
+ * Build linear array with pointers to each list element.
+ */
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ /* void */ ;
+ rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array));
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ rr_array[len] = rr;
+
+ /*
+ * Sort by user-specified criterion.
+ */
+ qsort((void *) rr_array, len, sizeof(*rr_array), dns_rr_sort_callback);
+
+ /*
+ * Fix the links.
+ */
+ for (i = 0; i < len - 1; i++)
+ rr_array[i]->next = rr_array[i + 1];
+ rr_array[i]->next = 0;
+ list = rr_array[0];
+
+ /*
+ * Cleanup.
+ */
+ myfree((void *) rr_array);
+ dns_rr_sort_user = saved_user;
+ return (list);
+}
+
+/* dns_rr_shuffle - shuffle resource record list */
+
+DNS_RR *dns_rr_shuffle(DNS_RR *list)
+{
+ DNS_RR **rr_array;
+ DNS_RR *rr;
+ int len;
+ int i;
+ int r;
+
+ /*
+ * Build linear array with pointers to each list element.
+ */
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ /* void */ ;
+ rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array));
+ for (len = 0, rr = list; rr != 0; len++, rr = rr->next)
+ rr_array[len] = rr;
+
+ /*
+ * Shuffle resource records. Every element has an equal chance of landing
+ * in slot 0. After that every remaining element has an equal chance of
+ * landing in slot 1, ... This is exactly n! states for n! permutations.
+ */
+ for (i = 0; i < len - 1; i++) {
+ r = i + (myrand() % (len - i)); /* Victor&Son */
+ rr = rr_array[i];
+ rr_array[i] = rr_array[r];
+ rr_array[r] = rr;
+ }
+
+ /*
+ * Fix the links.
+ */
+ for (i = 0; i < len - 1; i++)
+ rr_array[i]->next = rr_array[i + 1];
+ rr_array[i]->next = 0;
+ list = rr_array[0];
+
+ /*
+ * Cleanup.
+ */
+ myfree((void *) rr_array);
+ return (list);
+}
+
+/* dns_rr_remove - remove record from list, return new list */
+
+DNS_RR *dns_rr_remove(DNS_RR *list, DNS_RR *record)
+{
+ if (list == 0)
+ msg_panic("dns_rr_remove: record not found");
+
+ if (list == record) {
+ list = record->next;
+ record->next = 0;
+ dns_rr_free(record);
+ } else {
+ list->next = dns_rr_remove(list->next, record);
+ }
+ return (list);
+}
diff --git a/src/dns/dns_rr_eq_sa.c b/src/dns/dns_rr_eq_sa.c
new file mode 100644
index 0000000..8113d6b
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.c
@@ -0,0 +1,157 @@
+/*++
+/* NAME
+/* dns_rr_eq_sa 3
+/* SUMMARY
+/* compare resource record with socket address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa)
+/* DNS_RR *rr;
+/* struct sockaddr *sa;
+/*
+/* int DNS_RR_EQ_SA(DNS_RR *rr, struct sockaddr *sa)
+/* DNS_RR *rr;
+/* struct sockaddr *sa;
+/* DESCRIPTION
+/* dns_rr_eq_sa() compares a DNS resource record with a socket
+/* address. The result is non-zero when the resource type
+/* matches the socket address family, and when the network
+/* address information is identical.
+/*
+/* DNS_RR_EQ_SA() is an unsafe macro version for those who live fast.
+/*
+/* Arguments:
+/* .IP rr
+/* DNS resource record pointer.
+/* .IP sa
+/* Binary address pointer.
+/* DIAGNOSTICS
+/* Panic: unknown socket address family.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <sock_addr.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_eq_sa - compare resource record with socket address */
+
+int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa)
+{
+ const char *myname = "dns_rr_eq_sa";
+
+ if (sa->sa_family == AF_INET) {
+ return (rr->type == T_A
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR(rr->data).s_addr);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (rr->type == T_AAAA
+ && memcmp((void *) &SOCK_ADDR_IN6_ADDR(sa),
+ rr->data, rr->data_len) == 0);
+#endif
+ } else {
+ msg_panic("%s: unsupported socket address family type: %d",
+ myname, sa->sa_family);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <mymalloc.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s hostname address", myname);
+}
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *rr;
+ struct addrinfo *res0;
+ struct addrinfo *res1;
+ struct addrinfo *res;
+ struct addrinfo **resv;
+ size_t len, n;
+ int aierr;
+
+ myname = argv[0];
+
+ if (argc < 3)
+ usage();
+
+ inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+
+ while (*++argv) {
+ if (argv[1] == 0)
+ usage();
+
+ if ((aierr = hostaddr_to_sockaddr(argv[1], (char *) 0, 0, &res1)) != 0)
+ msg_fatal("host address %s: %s", argv[1], MAI_STRERROR(aierr));
+ if ((rr = dns_sa_to_rr(argv[1], 0, res1->ai_addr)) == 0)
+ msg_fatal("dns_sa_to_rr: %m");
+ freeaddrinfo(res1);
+
+ if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0)
+ msg_fatal("host name %s: %s", argv[0], MAI_STRERROR(aierr));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ resv[len++] = res;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ SOCKADDR_TO_HOSTADDR(resv[n]->ai_addr, resv[n]->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s =?= %s\n", hostaddr.buf, argv[1]);
+ vstream_printf("tested by function: %s\n",
+ dns_rr_eq_sa(rr, resv[n]->ai_addr) ?
+ "yes" : "no");
+ vstream_printf("tested by macro: %s\n",
+ DNS_RR_EQ_SA(rr, resv[n]->ai_addr) ?
+ "yes" : "no");
+ }
+ dns_rr_free(rr);
+ freeaddrinfo(res0);
+ myfree((void *) resv);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 1;
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_eq_sa.in b/src/dns/dns_rr_eq_sa.in
new file mode 100644
index 0000000..e8b6f83
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.in
@@ -0,0 +1,4 @@
+spike.porcupine.org 168.100.3.2
+spike.porcupine.org 168.100.3.3
+spike.porcupine.org 2604:8d00:189::2
+spike.porcupine.org 2604:8d00:189::3
diff --git a/src/dns/dns_rr_eq_sa.ref b/src/dns/dns_rr_eq_sa.ref
new file mode 100644
index 0000000..45e6b78
--- /dev/null
+++ b/src/dns/dns_rr_eq_sa.ref
@@ -0,0 +1,24 @@
+168.100.3.2 =?= 168.100.3.2
+tested by function: yes
+tested by macro: yes
+2604:8d00:189::2 =?= 168.100.3.2
+tested by function: no
+tested by macro: no
+168.100.3.2 =?= 168.100.3.3
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 168.100.3.3
+tested by function: no
+tested by macro: no
+168.100.3.2 =?= 2604:8d00:189::2
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 2604:8d00:189::2
+tested by function: yes
+tested by macro: yes
+168.100.3.2 =?= 2604:8d00:189::3
+tested by function: no
+tested by macro: no
+2604:8d00:189::2 =?= 2604:8d00:189::3
+tested by function: no
+tested by macro: no
diff --git a/src/dns/dns_rr_filter.c b/src/dns/dns_rr_filter.c
new file mode 100644
index 0000000..a02d3de
--- /dev/null
+++ b/src/dns/dns_rr_filter.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dns_rr_filter 3
+/* SUMMARY
+/* DNS resource record filter
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* void dns_rr_filter_compile(title, map_names)
+/* const char *title;
+/* const char *map_names;
+/* INTERNAL INTERFACES
+/* int dns_rr_filter_execute(rrlist)
+/* DNS_RR **rrlist;
+/*
+/* MAPS *dns_rr_filter_maps;
+/* DESCRIPTION
+/* This module implements a simple filter for dns_lookup*()
+/* results.
+/*
+/* dns_rr_filter_compile() initializes a result filter. The
+/* title and map_names arguments are as with maps_create().
+/* This function may be invoked more than once; only the last
+/* filter takes effect.
+/*
+/* dns_rr_filter_execute() converts each resource record in the
+/* specified list with dns_strrecord to ASCII form and matches
+/* that against the specified maps. If a match is found it
+/* executes the corresponding action. Currently, only the
+/* "ignore" action is implemented. This removes the matched
+/* record from the list. The result is 0 in case of success,
+/* -1 in case of error.
+/*
+/* dns_rr_filter_maps is updated by dns_rr_filter_compile().
+/* 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 <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+
+ /*
+ * DNS library.
+ */
+#define LIBDNS_INTERNAL
+#include <dns.h>
+
+ /*
+ * Application-specific.
+ */
+MAPS *dns_rr_filter_maps;
+
+static DNS_RR dns_rr_filter_error[1];
+
+#define STR vstring_str
+
+/* dns_rr_filter_compile - compile dns result filter */
+
+void dns_rr_filter_compile(const char *title, const char *map_names)
+{
+ if (dns_rr_filter_maps != 0)
+ maps_free(dns_rr_filter_maps);
+ dns_rr_filter_maps = maps_create(title, map_names,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+}
+
+/* dns_rr_action - execute action from filter map */
+
+static DNS_RR *dns_rr_action(const char *cmd, DNS_RR *rr, const char *rr_text)
+{
+ const char *cmd_args = cmd + strcspn(cmd, " \t");
+ int cmd_len = cmd_args - cmd;
+
+ while (*cmd_args && ISSPACE(*cmd_args))
+ cmd_args++;
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ if (STREQUAL(cmd, "IGNORE", cmd_len)) {
+ msg_info("ignoring DNS RR: %s", rr_text);
+ return (0);
+ } else {
+ msg_warn("%s: unknown DNS filter action: \"%s\"",
+ dns_rr_filter_maps->title, cmd);
+ return (dns_rr_filter_error);
+ }
+ return (rr);
+}
+
+/* dns_rr_filter_execute - filter DNS lookup result */
+
+int dns_rr_filter_execute(DNS_RR **rrlist)
+{
+ static VSTRING *buf = 0;
+ DNS_RR **rrp;
+ DNS_RR *rr;
+ const char *map_res;
+ DNS_RR *act_res;
+
+ /*
+ * Convert the resource record to string form, then search the maps for a
+ * matching action.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ for (rrp = rrlist; (rr = *rrp) != 0; /* see below */ ) {
+ map_res = maps_find(dns_rr_filter_maps, dns_strrecord(buf, rr),
+ DICT_FLAG_NONE);
+ if (map_res != 0) {
+ if ((act_res = dns_rr_action(map_res, rr, STR(buf))) == 0) {
+ *rrp = rr->next; /* do not advance in the list */
+ rr->next = 0;
+ dns_rr_free(rr);
+ continue;
+ } else if (act_res == dns_rr_filter_error) {
+ return (-1);
+ }
+ } else if (dns_rr_filter_maps->error) {
+ return (-1);
+ }
+ rrp = &(rr->next); /* do advance in the list */
+ }
+ return (0);
+}
diff --git a/src/dns/dns_rr_to_pa.c b/src/dns/dns_rr_to_pa.c
new file mode 100644
index 0000000..bfd93a0
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* dns_rr_to_pa 3
+/* SUMMARY
+/* resource record to printable address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_rr_to_pa(rr, hostaddr)
+/* DNS_RR *rr;
+/* MAI_HOSTADDR_STR *hostaddr;
+/* DESCRIPTION
+/* dns_rr_to_pa() converts the address in a DNS resource record
+/* into printable form and returns a pointer to the result.
+/*
+/* Arguments:
+/* .IP rr
+/* The DNS resource record.
+/* .IP hostaddr
+/* Storage for the printable address.
+/* DIAGNOSTICS
+/* The result is null in case of problems, with errno set
+/* to indicate the nature of the problem.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_to_pa - resource record to printable address */
+
+const char *dns_rr_to_pa(DNS_RR *rr, MAI_HOSTADDR_STR *hostaddr)
+{
+ if (rr->type == T_A) {
+ return (inet_ntop(AF_INET, rr->data, hostaddr->buf,
+ sizeof(hostaddr->buf)));
+#ifdef HAS_IPV6
+ } else if (rr->type == T_AAAA) {
+ return (inet_ntop(AF_INET6, rr->data, hostaddr->buf,
+ sizeof(hostaddr->buf)));
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <vstream.h>
+#include <myaddrinfo.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s dnsaddrtype hostname", myname);
+}
+
+int main(int argc, char **argv)
+{
+ DNS_RR *rr;
+ MAI_HOSTADDR_STR hostaddr;
+ VSTRING *why;
+ int type;
+
+ myname = argv[0];
+ if (argc < 3)
+ usage();
+ why = vstring_alloc(1);
+
+ while (*++argv) {
+ if (argv[1] == 0)
+ usage();
+ if ((type = dns_type(argv[0])) == 0)
+ usage();
+ if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK)
+ msg_fatal("%s: %s", argv[1], vstring_str(why));
+ if (dns_rr_to_pa(rr, &hostaddr) == 0)
+ msg_fatal("dns_rr_to_sa: %m");
+ vstream_printf("%s -> %s\n", argv[1], hostaddr.buf);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 1;
+ dns_rr_free(rr);
+ }
+ vstring_free(why);
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_to_pa.in b/src/dns/dns_rr_to_pa.in
new file mode 100644
index 0000000..28d0e77
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.in
@@ -0,0 +1,2 @@
+a spike.porcupine.org
+aaaa spike.porcupine.org
diff --git a/src/dns/dns_rr_to_pa.ref b/src/dns/dns_rr_to_pa.ref
new file mode 100644
index 0000000..db1c7af
--- /dev/null
+++ b/src/dns/dns_rr_to_pa.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org -> 168.100.3.2
+spike.porcupine.org -> 2604:8d00:189::2
diff --git a/src/dns/dns_rr_to_sa.c b/src/dns/dns_rr_to_sa.c
new file mode 100644
index 0000000..f264260
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.c
@@ -0,0 +1,163 @@
+/*++
+/* NAME
+/* dns_rr_to_sa 3
+/* SUMMARY
+/* resource record to socket address
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* int dns_rr_to_sa(rr, port, sa, sa_length)
+/* DNS_RR *rr;
+/* unsigned port;
+/* struct sockaddr *sa;
+/* SOCKADDR_SIZE *sa_length;
+/* DESCRIPTION
+/* dns_rr_to_sa() converts the address in a DNS resource record into
+/* a socket address of the corresponding type.
+/*
+/* Arguments:
+/* .IP rr
+/* DNS resource record pointer.
+/* .IP port
+/* TCP or UDP port, network byte order.
+/* .IP sa
+/* Socket address pointer.
+/* .IP sa_length
+/* On input, the available socket address storage space.
+/* On output, the amount of space actually used.
+/* DIAGNOSTICS
+/* The result is non-zero in case of problems, with the
+/* error type returned via the errno variable.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_rr_to_sa - resource record to socket address */
+
+int dns_rr_to_sa(DNS_RR *rr, unsigned port, struct sockaddr *sa,
+ SOCKADDR_SIZE *sa_length)
+{
+ SOCKADDR_SIZE sock_addr_len;
+
+ if (rr->type == T_A) {
+ if (rr->data_len != sizeof(SOCK_ADDR_IN_ADDR(sa))) {
+ errno = EINVAL;
+ return (-1);
+ } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN_PTR(sa))) > *sa_length) {
+ errno = ENOSPC;
+ return (-1);
+ } else {
+ memset((void *) SOCK_ADDR_IN_PTR(sa), 0, sock_addr_len);
+ SOCK_ADDR_IN_FAMILY(sa) = AF_INET;
+ SOCK_ADDR_IN_PORT(sa) = port;
+ SOCK_ADDR_IN_ADDR(sa) = IN_ADDR(rr->data);
+#ifdef HAS_SA_LEN
+ sa->sa_len = sock_addr_len;
+#endif
+ *sa_length = sock_addr_len;
+ return (0);
+ }
+#ifdef HAS_IPV6
+ } else if (rr->type == T_AAAA) {
+ if (rr->data_len != sizeof(SOCK_ADDR_IN6_ADDR(sa))) {
+ errno = EINVAL;
+ return (-1);
+ } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN6_PTR(sa))) > *sa_length) {
+ errno = ENOSPC;
+ return (-1);
+ } else {
+ memset((void *) SOCK_ADDR_IN6_PTR(sa), 0, sock_addr_len);
+ SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6;
+ SOCK_ADDR_IN6_PORT(sa) = port;
+ SOCK_ADDR_IN6_ADDR(sa) = IN6_ADDR(rr->data);
+#ifdef HAS_SA_LEN
+ sa->sa_len = sock_addr_len;
+#endif
+ *sa_length = sock_addr_len;
+ return (0);
+ }
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+
+#include <stringops.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s dnsaddrtype hostname portnumber", myname);
+}
+
+int main(int argc, char **argv)
+{
+ DNS_RR *rr;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_SERVPORT_STR portnum;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE sa_length = sizeof(ss);
+ VSTRING *why;
+ int type;
+ int port;
+
+ myname = argv[0];
+ if (argc < 4)
+ usage();
+ why = vstring_alloc(1);
+
+ while (*++argv) {
+ if (argv[1] == 0 || argv[2] == 0)
+ usage();
+ if ((type = dns_type(argv[0])) == 0)
+ usage();
+ if (!alldig(argv[2]) || (port = atoi(argv[2])) > 65535)
+ usage();
+ if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK)
+ msg_fatal("%s: %s", argv[1], vstring_str(why));
+ sa_length = sizeof(ss);
+ if (dns_rr_to_sa(rr, htons(port), sa, &sa_length) != 0)
+ msg_fatal("dns_rr_to_sa: %m");
+ SOCKADDR_TO_HOSTADDR(sa, sa_length, &hostaddr, &portnum, 0);
+ vstream_printf("%s %s -> %s %s\n",
+ argv[1], argv[2], hostaddr.buf, portnum.buf);
+ vstream_fflush(VSTREAM_OUT);
+ argv += 2;
+ dns_rr_free(rr);
+ }
+ vstring_free(why);
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_rr_to_sa.in b/src/dns/dns_rr_to_sa.in
new file mode 100644
index 0000000..1fff6c0
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.in
@@ -0,0 +1,2 @@
+a spike.porcupine.org 25
+aaaa spike.porcupine.org 25
diff --git a/src/dns/dns_rr_to_sa.ref b/src/dns/dns_rr_to_sa.ref
new file mode 100644
index 0000000..8a114b5
--- /dev/null
+++ b/src/dns/dns_rr_to_sa.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org 25 -> 168.100.3.2 25
+spike.porcupine.org 25 -> 2604:8d00:189::2 25
diff --git a/src/dns/dns_sa_to_rr.c b/src/dns/dns_sa_to_rr.c
new file mode 100644
index 0000000..6b9efcc
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* dns_sa_to_rr 3
+/* SUMMARY
+/* socket address to resource record
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_RR *dns_sa_to_rr(hostname, pref, sa)
+/* const char *hostname;
+/* unsigned pref;
+/* struct sockaddr *sa;
+/* DESCRIPTION
+/* dns_sa_to_rr() converts a socket address into a DNS resource record.
+/*
+/* Arguments:
+/* .IP hostname
+/* The resource record host name. This will be both the qname
+/* and the rname in the synthetic DNS resource record.
+/* .IP pref
+/* The resource record MX host preference, if applicable.
+/* .IP sa
+/* Binary address.
+/* DIAGNOSTICS
+/* The result is a null pointer in case of problems, with the
+/* errno variable set to indicate the problem type.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_sa_to_rr - socket address to resource record */
+
+DNS_RR *dns_sa_to_rr(const char *hostname, unsigned pref, struct sockaddr *sa)
+{
+#define DUMMY_TTL 0
+
+ if (sa->sa_family == AF_INET) {
+ return (dns_rr_create(hostname, hostname, T_A, C_IN, DUMMY_TTL, pref,
+ (char *) &SOCK_ADDR_IN_ADDR(sa),
+ sizeof(SOCK_ADDR_IN_ADDR(sa))));
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (dns_rr_create(hostname, hostname, T_AAAA, C_IN, DUMMY_TTL, pref,
+ (char *) &SOCK_ADDR_IN6_ADDR(sa),
+ sizeof(SOCK_ADDR_IN6_ADDR(sa))));
+#endif
+ } else {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <mymalloc.h>
+
+static const char *myname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s hostname", myname);
+}
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ struct addrinfo **resv;
+ size_t len, n;
+ DNS_RR *rr;
+ int aierr;
+
+ myname = argv[0];
+ if (argc < 2)
+ usage();
+
+ inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+
+ while (*++argv) {
+ if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0)
+ msg_fatal("%s: %s", argv[0], MAI_STRERROR(aierr));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, res = res0; res != 0; res = res->ai_next)
+ resv[len++] = res;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ if ((rr = dns_sa_to_rr(argv[0], 0, resv[n]->ai_addr)) == 0)
+ msg_fatal("dns_sa_to_rr: %m");
+ if (dns_rr_to_pa(rr, &hostaddr) == 0)
+ msg_fatal("dns_rr_to_pa: %m");
+ vstream_printf("%s -> %s\n", argv[0], hostaddr.buf);
+ vstream_fflush(VSTREAM_OUT);
+ dns_rr_free(rr);
+ }
+ freeaddrinfo(res0);
+ myfree((void *) resv);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/dns/dns_sa_to_rr.in b/src/dns/dns_sa_to_rr.in
new file mode 100644
index 0000000..4f83a7d
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.in
@@ -0,0 +1 @@
+spike.porcupine.org
diff --git a/src/dns/dns_sa_to_rr.ref b/src/dns/dns_sa_to_rr.ref
new file mode 100644
index 0000000..db1c7af
--- /dev/null
+++ b/src/dns/dns_sa_to_rr.ref
@@ -0,0 +1,2 @@
+spike.porcupine.org -> 168.100.3.2
+spike.porcupine.org -> 2604:8d00:189::2
diff --git a/src/dns/dns_sec.c b/src/dns/dns_sec.c
new file mode 100644
index 0000000..849627e
--- /dev/null
+++ b/src/dns/dns_sec.c
@@ -0,0 +1,144 @@
+/*++
+/* NAME
+/* dns_sec 3
+/* SUMMARY
+/* DNSSEC validation availability
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* DNS_SEC_STATS_SET(
+/* int flags)
+/*
+/* DNS_SEC_STATS_TEST(
+/* int flags)
+/*
+/* void dns_sec_probe(
+/* int rflags)
+/* DESCRIPTION
+/* This module maintains information about the availability of
+/* DNSSEC validation, in global flags that summarize
+/* process-lifetime history.
+/* .IP DNS_SEC_FLAG_AVAILABLE
+/* The process has received at least one DNSSEC validated
+/* response to a query that requested DNSSEC validation.
+/* .IP DNS_SEC_FLAG_DONT_PROBE
+/* The process has sent a DNSSEC probe (see below), or DNSSEC
+/* probing is disabled by configuration.
+/* .PP
+/* DNS_SEC_STATS_SET() sets one or more DNS_SEC_FLAG_* flags,
+/* and DNS_SEC_STATS_TEST() returns non-zero if any of the
+/* specified flags is set.
+/*
+/* dns_sec_probe() generates a query to the target specified
+/* with the \fBdnssec_probe\fR configuration parameter. It
+/* sets the DNS_SEC_FLAG_DONT_PROBE flag, and it calls
+/* dns_lookup() which sets DNS_SEC_FLAG_AVAILABLE if it receives
+/* a DNSSEC validated response. Preconditions:
+/* .IP \(bu
+/* The rflags argument must request DNSSEC validation (in the
+/* same manner as dns_lookup() rflags argument).
+/* .IP \(bu
+/* The DNS_SEC_FLAG_AVAILABLE and DNS_SEC_FLAG_DONT_PROBE
+/* flags must be false.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+int dns_sec_stats;
+
+/* dns_sec_probe - send a probe to establish DNSSEC viability */
+
+void dns_sec_probe(int rflags)
+{
+ const char myname[] = "dns_sec_probe";
+ char *saved_dnssec_probe;
+ char *qname;
+ int qtype;
+ DNS_RR *rrlist = 0;
+ int dns_status;
+ VSTRING *why;
+
+ /*
+ * Sanity checks.
+ */
+ if (!DNS_WANT_DNSSEC_VALIDATION(rflags))
+ msg_panic("%s: DNSSEC is not requested", myname);
+ if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_DONT_PROBE))
+ msg_panic("%s: DNSSEC probe was already sent, or probing is disabled",
+ myname);
+ if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_panic("%s: already have validated DNS response", myname);
+
+ /*
+ * Don't recurse.
+ */
+ DNS_SEC_STATS_SET(DNS_SEC_FLAG_DONT_PROBE);
+
+ /*
+ * Don't probe.
+ */
+ if (*var_dnssec_probe == 0)
+ return;
+
+ /*
+ * Parse the probe spec. Format is type:resource.
+ */
+ saved_dnssec_probe = mystrdup(var_dnssec_probe);
+ if ((qname = split_at(saved_dnssec_probe, ':')) == 0 || *qname == 0
+ || (qtype = dns_type(saved_dnssec_probe)) == 0)
+ msg_fatal("malformed %s value: %s format is qtype:qname",
+ VAR_DNSSEC_PROBE, var_dnssec_probe);
+
+ why = vstring_alloc(100);
+ dns_status = dns_lookup(qname, qtype, rflags, &rrlist, (VSTRING *) 0, why);
+ if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_warn("DNSSEC validation may be unavailable");
+ else if (msg_verbose)
+ msg_info(VAR_DNSSEC_PROBE
+ " '%s' received a response that is DNSSEC validated",
+ var_dnssec_probe);
+ switch (dns_status) {
+ default:
+ if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE))
+ msg_warn("reason: " VAR_DNSSEC_PROBE
+ " '%s' received a response that is not DNSSEC validated",
+ var_dnssec_probe);
+ if (rrlist)
+ dns_rr_free(rrlist);
+ break;
+ case DNS_RETRY:
+ case DNS_FAIL:
+ msg_warn("reason: " VAR_DNSSEC_PROBE " '%s' received no response: %s",
+ var_dnssec_probe, vstring_str(why));
+ break;
+ }
+ myfree(saved_dnssec_probe);
+ vstring_free(why);
+}
diff --git a/src/dns/dns_str_resflags.c b/src/dns/dns_str_resflags.c
new file mode 100644
index 0000000..472394c
--- /dev/null
+++ b/src/dns/dns_str_resflags.c
@@ -0,0 +1,128 @@
+/*++
+/* NAME
+/* dns_str_resflags 3
+/* SUMMARY
+/* convert resolver flags to printable form
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_str_resflags(mask)
+/* unsigned long mask;
+/* DESCRIPTION
+/* dns_str_resflags() converts RES_* resolver(5) flags from internal
+/* form to printable string. Individual flag names are separated
+/* with '|'. The result is overwritten with each call.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_mask.h>
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+ /*
+ * Application-specific.
+ */
+
+ /*
+ * This list overlaps with dns_res_opt_masks[] in smtp.c, but there we
+ * permit only a small subset of all possible flags.
+ */
+static const LONG_NAME_MASK resflag_table[] = {
+ "RES_INIT", RES_INIT,
+ "RES_DEBUG", RES_DEBUG,
+#ifdef RES_AAONLY
+ "RES_AAONLY", RES_AAONLY,
+#endif
+ "RES_USEVC", RES_USEVC,
+#ifdef RES_PRIMARY
+ "RES_PRIMARY", RES_PRIMARY,
+#endif
+ "RES_IGNTC", RES_IGNTC,
+ "RES_RECURSE", RES_RECURSE,
+ "RES_DEFNAMES", RES_DEFNAMES,
+ "RES_STAYOPEN", RES_STAYOPEN,
+ "RES_DNSRCH", RES_DNSRCH,
+#ifdef RES_INSECURE1
+ "RES_INSECURE1", RES_INSECURE1,
+#endif
+#ifdef RES_INSECURE2
+ "RES_INSECURE2", RES_INSECURE2,
+#endif
+ "RES_NOALIASES", RES_NOALIASES,
+#ifdef RES_USE_INET6
+ "RES_USE_INET6", RES_USE_INET6,
+#endif
+#ifdef RES_ROTATE
+ "RES_ROTATE", RES_ROTATE,
+#endif
+#ifdef RES_NOCHECKNAME
+ "RES_NOCHECKNAME", RES_NOCHECKNAME,
+#endif
+ "RES_USE_EDNS0", RES_USE_EDNS0,
+ "RES_USE_DNSSEC", RES_USE_DNSSEC,
+#ifdef RES_KEEPTSIG
+ "RES_KEEPTSIG", RES_KEEPTSIG,
+#endif
+#ifdef RES_BLAST
+ "RES_BLAST", RES_BLAST,
+#endif
+#ifdef RES_USEBSTRING
+ "RES_USEBSTRING", RES_USEBSTRING,
+#endif
+#ifdef RES_NSID
+ "RES_NSID", RES_NSID,
+#endif
+#ifdef RES_NOIP6DOTINT
+ "RES_NOIP6DOTINT", RES_NOIP6DOTINT,
+#endif
+#ifdef RES_USE_DNAME
+ "RES_USE_DNAME", RES_USE_DNAME,
+#endif
+#ifdef RES_NO_NIBBLE2
+ "RES_NO_NIBBLE2", RES_NO_NIBBLE2,
+#endif
+#ifdef RES_SNGLKUP
+ "RES_SNGLKUP", RES_SNGLKUP,
+#endif
+#ifdef RES_SNGLKUPREOP
+ "RES_SNGLKUPREOP", RES_SNGLKUPREOP,
+#endif
+#ifdef RES_NOTLDQUERY
+ "RES_NOTLDQUERY", RES_NOTLDQUERY,
+#endif
+ 0,
+};
+
+/* dns_str_resflags - convert RES_* resolver flags to printable form */
+
+const char *dns_str_resflags(unsigned long mask)
+{
+ static VSTRING *buf;
+
+ if (buf == 0)
+ buf = vstring_alloc(20);
+ return (str_long_name_mask_opt(buf, "dsns_str_resflags", resflag_table,
+ mask, NAME_MASK_NUMBER | NAME_MASK_PIPE));
+}
diff --git a/src/dns/dns_strerror.c b/src/dns/dns_strerror.c
new file mode 100644
index 0000000..9e56d3b
--- /dev/null
+++ b/src/dns/dns_strerror.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* dns_strerror 3
+/* SUMMARY
+/* name service lookup error code to string
+/* SYNOPSIS
+/* #include <dhs.h>
+/*
+/* const char *dns_strerror(code)
+/* int code;
+/* DESCRIPTION
+/* dns_strerror() maps a name service lookup error to printable string.
+/* The result is for read-only purposes, and unknown codes share a
+/* common string buffer.
+/* 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 <netdb.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+ /*
+ * Mapping from error code to printable string. The herror() routine does
+ * something similar, but has output only to the stderr stream.
+ */
+struct dns_error_map {
+ unsigned error;
+ const char *text;
+};
+
+static struct dns_error_map dns_error_map[] = {
+ HOST_NOT_FOUND, "Host not found",
+ TRY_AGAIN, "Host not found, try again",
+ NO_RECOVERY, "Non-recoverable error",
+ NO_DATA, "Host found but no data record of requested type",
+};
+
+/* dns_strerror - map resolver error code to printable string */
+
+const char *dns_strerror(unsigned error)
+{
+ static VSTRING *unknown = 0;
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_error_map) / sizeof(dns_error_map[0]); i++)
+ if (dns_error_map[i].error == error)
+ return (dns_error_map[i].text);
+ if (unknown == 0)
+ unknown = vstring_alloc(sizeof("Unknown error XXXXXX"));
+ vstring_sprintf(unknown, "Unknown error %u", error);
+ return (vstring_str(unknown));
+}
diff --git a/src/dns/dns_strrecord.c b/src/dns/dns_strrecord.c
new file mode 100644
index 0000000..6b8e989
--- /dev/null
+++ b/src/dns/dns_strrecord.c
@@ -0,0 +1,117 @@
+/*++
+/* NAME
+/* dns_strrecord 3
+/* SUMMARY
+/* name service resource record printable forms
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* char *dns_strrecord(buf, record)
+/* VSTRING *buf;
+/* DNS_RR *record;
+/* DESCRIPTION
+/* dns_strrecord() formats a DNS resource record as "name ttl
+/* class type preference value", where the class field is
+/* always "IN", the preference field exists only for MX records,
+/* and all names end in ".". The result value is the payload
+/* of the buffer argument.
+/* 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> /* memcpy */
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* dns_strrecord - format resource record as generic string */
+
+char *dns_strrecord(VSTRING *buf, DNS_RR *rr)
+{
+ const char myname[] = "dns_strrecord";
+ MAI_HOSTADDR_STR host;
+ UINT32_TYPE soa_buf[5];
+
+ vstring_sprintf(buf, "%s. %u IN %s ",
+ rr->rname, rr->ttl, dns_strtype(rr->type));
+ switch (rr->type) {
+ case T_A:
+#ifdef T_AAAA
+ case T_AAAA:
+#endif
+ if (dns_rr_to_pa(rr, &host) == 0)
+ msg_fatal("%s: conversion error for resource record type %s: %m",
+ myname, dns_strtype(rr->type));
+ vstring_sprintf_append(buf, "%s", host.buf);
+ break;
+ case T_CNAME:
+ case T_DNAME:
+ case T_MB:
+ case T_MG:
+ case T_MR:
+ case T_NS:
+ case T_PTR:
+ vstring_sprintf_append(buf, "%s.", rr->data);
+ break;
+ case T_TXT:
+ vstring_sprintf_append(buf, "%s", rr->data);
+ break;
+ case T_MX:
+ vstring_sprintf_append(buf, "%u %s.", rr->pref, rr->data);
+ break;
+ case T_TLSA:
+ if (rr->data_len >= 3) {
+ uint8_t *ip = (uint8_t *) rr->data;
+ uint8_t usage = *ip++;
+ uint8_t selector = *ip++;
+ uint8_t mtype = *ip++;
+ unsigned i;
+
+ /* /\.example\. \d+ IN TLSA \d+ \d+ \d+ [\da-f]*$/ IGNORE */
+ vstring_sprintf_append(buf, "%d %d %d ", usage, selector, mtype);
+ for (i = 3; i < rr->data_len; ++i)
+ vstring_sprintf_append(buf, "%02x", *ip++);
+ } else {
+ vstring_sprintf_append(buf, "[truncated record]");
+ }
+
+ /*
+ * We use the SOA record TTL to determine the negative reply TTL. We
+ * save the time fields in the SOA record for debugging, but for now
+ * we don't bother saving the source host and mailbox information, as
+ * that would require changes to the DNS_RR structure. See also code
+ * in dns_get_rr().
+ */
+ case T_SOA:
+ memcpy(soa_buf, rr->data, sizeof(soa_buf));
+ vstring_sprintf_append(buf, "- - %u %u %u %u %u",
+ soa_buf[0], soa_buf[1], soa_buf[2],
+ soa_buf[3], soa_buf[4]);
+ break;
+ default:
+ msg_fatal("%s: don't know how to print type %s",
+ myname, dns_strtype(rr->type));
+ }
+ return (vstring_str(buf));
+}
diff --git a/src/dns/dns_strtype.c b/src/dns/dns_strtype.c
new file mode 100644
index 0000000..70e59ac
--- /dev/null
+++ b/src/dns/dns_strtype.c
@@ -0,0 +1,211 @@
+/*++
+/* NAME
+/* dns_strtype 3
+/* SUMMARY
+/* name service lookup type codes and printable forms
+/* SYNOPSIS
+/* #include <dns.h>
+/*
+/* const char *dns_strtype(code)
+/* int code;
+/*
+/* int dns_type(strval)
+/* const char *strval;
+/* DESCRIPTION
+/* dns_strtype() maps a name service lookup type to printable string.
+/* The result is for read-only purposes, and unknown codes share a
+/* common string buffer.
+/*
+/* dns_type() converts a name service lookup string value to a numeric
+/* code. A null result means the code was not found. The input can be
+/* in lower case, upper case or mixed case.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* DNS library. */
+
+#include "dns.h"
+
+ /*
+ * Mapping from type code to printable string. Some names are possibly not
+ * defined on every platform, so I have #ifdef-ed them all just to be safe.
+ */
+struct dns_type_map {
+ unsigned type;
+ const char *text;
+};
+
+static struct dns_type_map dns_type_map[] = {
+#ifdef T_A
+ T_A, "A",
+#endif
+#ifdef T_AAAA
+ T_AAAA, "AAAA",
+#endif
+#ifdef T_NS
+ T_NS, "NS",
+#endif
+#ifdef T_MD
+ T_MD, "MD",
+#endif
+#ifdef T_MF
+ T_MF, "MF",
+#endif
+#ifdef T_CNAME
+ T_CNAME, "CNAME",
+#endif
+#ifdef T_SOA
+ T_SOA, "SOA",
+#endif
+#ifdef T_MB
+ T_MB, "MB",
+#endif
+#ifdef T_MG
+ T_MG, "MG",
+#endif
+#ifdef T_MR
+ T_MR, "MR",
+#endif
+#ifdef T_NULL
+ T_NULL, "NULL",
+#endif
+#ifdef T_WKS
+ T_WKS, "WKS",
+#endif
+#ifdef T_PTR
+ T_PTR, "PTR",
+#endif
+#ifdef T_HINFO
+ T_HINFO, "HINFO",
+#endif
+#ifdef T_MINFO
+ T_MINFO, "MINFO",
+#endif
+#ifdef T_MX
+ T_MX, "MX",
+#endif
+#ifdef T_TXT
+ T_TXT, "TXT",
+#endif
+#ifdef T_RP
+ T_RP, "RP",
+#endif
+#ifdef T_AFSDB
+ T_AFSDB, "AFSDB",
+#endif
+#ifdef T_X25
+ T_X25, "X25",
+#endif
+#ifdef T_ISDN
+ T_ISDN, "ISDN",
+#endif
+#ifdef T_RT
+ T_RT, "RT",
+#endif
+#ifdef T_NSAP
+ T_NSAP, "NSAP",
+#endif
+#ifdef T_NSAP_PTR
+ T_NSAP_PTR, "NSAP_PTR",
+#endif
+#ifdef T_SIG
+ T_SIG, "SIG",
+#endif
+#ifdef T_KEY
+ T_KEY, "KEY",
+#endif
+#ifdef T_PX
+ T_PX, "PX",
+#endif
+#ifdef T_GPOS
+ T_GPOS, "GPOS",
+#endif
+#ifdef T_AAAA
+ T_AAAA, "AAAA",
+#endif
+#ifdef T_LOC
+ T_LOC, "LOC",
+#endif
+#ifdef T_UINFO
+ T_UINFO, "UINFO",
+#endif
+#ifdef T_UID
+ T_UID, "UID",
+#endif
+#ifdef T_GID
+ T_GID, "GID",
+#endif
+#ifdef T_UNSPEC
+ T_UNSPEC, "UNSPEC",
+#endif
+#ifdef T_AXFR
+ T_AXFR, "AXFR",
+#endif
+#ifdef T_MAILB
+ T_MAILB, "MAILB",
+#endif
+#ifdef T_MAILA
+ T_MAILA, "MAILA",
+#endif
+#ifdef T_TLSA
+ T_TLSA, "TLSA",
+#endif
+#ifdef T_RRSIG
+ T_RRSIG, "RRSIG",
+#endif
+#ifdef T_DNAME
+ T_DNAME, "DNAME",
+#endif
+#ifdef T_ANY
+ T_ANY, "ANY",
+#endif
+};
+
+/* dns_strtype - translate DNS query type to string */
+
+const char *dns_strtype(unsigned type)
+{
+ static VSTRING *unknown = 0;
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++)
+ if (dns_type_map[i].type == type)
+ return (dns_type_map[i].text);
+ if (unknown == 0)
+ unknown = vstring_alloc(sizeof("Unknown type XXXXXX"));
+ vstring_sprintf(unknown, "Unknown type %u", type);
+ return (vstring_str(unknown));
+}
+
+/* dns_type - translate string to DNS query type */
+
+unsigned dns_type(const char *text)
+{
+ unsigned i;
+
+ for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++)
+ if (strcasecmp(dns_type_map[i].text, text) == 0)
+ return (dns_type_map[i].type);
+ return (0);
+}
diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref
new file mode 100644
index 0000000..af7b6bc
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref
@@ -0,0 +1,12 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for zen.spamhaus.org
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+1.0.0.127.zen.spamhaus.org: fqdn: zen.spamhaus.org
+ad: 0, rr: zen.spamhaus.org. TTL IN SOA - - D D D D D
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for bl.spamcop.net
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
+1.0.0.127.bl.spamcop.net: fqdn: bl.spamcop.net
+ad: 0, rr: bl.spamcop.net. TTL IN SOA - - D D D D D
diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref
new file mode 100644
index 0000000..b84554a
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref
@@ -0,0 +1,6 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
diff --git a/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref
new file mode 100644
index 0000000..81dd845
--- /dev/null
+++ b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref
@@ -0,0 +1,10 @@
+./test_dns_lookup: lookup 2.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 2.0.0.127.zen.spamhaus.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.zen.spamhaus.org
+2.0.0.127.zen.spamhaus.org: fqdn: 2.0.0.127.zen.spamhaus.org
+ad: 0, rr: 2.0.0.127.zen.spamhaus.org. TTL IN A 127.0.0.D
+./test_dns_lookup: lookup 2.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: 2.0.0.127.bl.spamcop.net (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.bl.spamcop.net
+2.0.0.127.bl.spamcop.net: fqdn: 2.0.0.127.bl.spamcop.net
+ad: 0, rr: 2.0.0.127.bl.spamcop.net. TTL IN A 127.0.0.D
diff --git a/src/dns/error.ref b/src/dns/error.ref
new file mode 100644
index 0000000..c535153
--- /dev/null
+++ b/src/dns/error.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN A 168.100.3.2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.3.2 = oops
+./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops"
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = oops
+./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops"
+./test_dns_lookup: warning: Error looking up name=spike.porcupine.org type=AAAA: Invalid DNS reply filter syntax (rcode=0)
diff --git a/src/dns/error.reg b/src/dns/error.reg
new file mode 100644
index 0000000..4e553e8
--- /dev/null
+++ b/src/dns/error.reg
@@ -0,0 +1 @@
+/./ oops
diff --git a/src/dns/mxonly_test.ref b/src/dns/mxonly_test.ref
new file mode 100644
index 0000000..44f22d6
--- /dev/null
+++ b/src/dns/mxonly_test.ref
@@ -0,0 +1,11 @@
+./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: porcupine.org (MX): OK
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: lookup porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: porcupine.org (A): Host found but no data record of requested type
+ad: 0, rr: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+ad: 0, rr: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+ad: 0, rr: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+porcupine.org: fqdn: porcupine.org
diff --git a/src/dns/no-a.ref b/src/dns/no-a.ref
new file mode 100644
index 0000000..5dff824
--- /dev/null
+++ b/src/dns/no-a.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN A 168.100.3.2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-a.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.3.2 = ignore
+./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN A 168.100.3.2
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2: not found
+spike.porcupine.org: fqdn: spike.porcupine.org
+ad: 0, rr: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
diff --git a/src/dns/no-a.reg b/src/dns/no-a.reg
new file mode 100644
index 0000000..69e05e5
--- /dev/null
+++ b/src/dns/no-a.reg
@@ -0,0 +1 @@
+/ +a +/ ignore
diff --git a/src/dns/no-aaaa.ref b/src/dns/no-aaaa.ref
new file mode 100644
index 0000000..657c69b
--- /dev/null
+++ b/src/dns/no-aaaa.ref
@@ -0,0 +1,13 @@
+./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN A 168.100.3.2
+./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN A 168.100.3.2: not found
+./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK
+./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org
+./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-aaaa.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = ignore
+./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
+spike.porcupine.org: fqdn: spike.porcupine.org
+ad: 0, rr: spike.porcupine.org. 3600 IN A 168.100.3.2
diff --git a/src/dns/no-aaaa.reg b/src/dns/no-aaaa.reg
new file mode 100644
index 0000000..962adda
--- /dev/null
+++ b/src/dns/no-aaaa.reg
@@ -0,0 +1 @@
+/ +aaaa +/ ignore
diff --git a/src/dns/no-mx.ref b/src/dns/no-mx.ref
new file mode 100644
index 0000000..5adc7bf
--- /dev/null
+++ b/src/dns/no-mx.ref
@@ -0,0 +1,15 @@
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_get_answer: type MX for porcupine.org
+./test_dns_lookup: dns_query: porcupine.org (MX): OK
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 10 spike.porcupine.org. = ignore
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 m1.porcupine.org. = ignore
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 vz.porcupine.org. = ignore
+./test_dns_lookup: warning: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0)
diff --git a/src/dns/no-mx.reg b/src/dns/no-mx.reg
new file mode 100644
index 0000000..69cf05d
--- /dev/null
+++ b/src/dns/no-mx.reg
@@ -0,0 +1 @@
+/ +mx +/ ignore
diff --git a/src/dns/no-txt.reg b/src/dns/no-txt.reg
new file mode 100644
index 0000000..175600b
--- /dev/null
+++ b/src/dns/no-txt.reg
@@ -0,0 +1 @@
+/ +txt +/ ignore
diff --git a/src/dns/nullmx_test.ref b/src/dns/nullmx_test.ref
new file mode 100644
index 0000000..1a9cab2
--- /dev/null
+++ b/src/dns/nullmx_test.ref
@@ -0,0 +1,8 @@
+./test_dns_lookup: lookup nullmx.porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nullmx.porcupine.org (MX): OK
+./test_dns_lookup: dns_get_answer: type MX for nullmx.porcupine.org
+./test_dns_lookup: lookup nullmx.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nullmx.porcupine.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for nullmx.porcupine.org
+nullmx.porcupine.org: fqdn: nullmx.porcupine.org
+ad: 0, rr: nullmx.porcupine.org. 3600 IN A 168.100.3.13
diff --git a/src/dns/nxdomain_test.ref b/src/dns/nxdomain_test.ref
new file mode 100644
index 0000000..15be203
--- /dev/null
+++ b/src/dns/nxdomain_test.ref
@@ -0,0 +1,5 @@
+./test_dns_lookup: lookup nxdomain.porcupine.org type MX flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nxdomain.porcupine.org (MX): Host not found
+./test_dns_lookup: lookup nxdomain.porcupine.org type A flags RES_USE_DNSSEC
+./test_dns_lookup: dns_query: nxdomain.porcupine.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=nxdomain.porcupine.org type=A: Host not found (rcode=3)
diff --git a/src/dns/test_dns_lookup.c b/src/dns/test_dns_lookup.c
new file mode 100644
index 0000000..e25f523
--- /dev/null
+++ b/src/dns/test_dns_lookup.c
@@ -0,0 +1,131 @@
+/*++
+/* NAME
+/* test_dns_lookup 1
+/* SUMMARY
+/* DNS lookup test program
+/* SYNOPSIS
+/* test_dns_lookup query-type domain-name
+/* DESCRIPTION
+/* test_dns_lookup performs a DNS query of the specified resource
+/* type for the specified resource name.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <argv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "dns.h"
+
+static void print_rr(VSTRING *buf, DNS_RR *rr)
+{
+ while (rr) {
+ vstream_printf("ad: %u, rr: %s\n",
+ rr->dnssec_valid, dns_strrecord(buf, rr));
+ rr = rr->next;
+ }
+}
+
+static NORETURN usage(char **argv)
+{
+ msg_fatal("usage: %s [-npv] [-f filter] types name", argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+ ARGV *types_argv;
+ unsigned *types;
+ char *name;
+ VSTRING *fqdn = vstring_alloc(100);
+ VSTRING *why = vstring_alloc(100);
+ VSTRING *buf;
+ int rcode;
+ DNS_RR *rr;
+ int i;
+ int ch;
+ int lflags = DNS_REQ_FLAG_NONE;
+
+ var_dnssec_probe = "";
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "f:npvs")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'f':
+ dns_rr_filter_compile("DNS reply filter", optarg);
+ break;
+ case 'n':
+ lflags |= DNS_REQ_FLAG_NCACHE_TTL;
+ break;
+ case 'p':
+ var_dns_ncache_ttl_fix = 1;
+ break;
+ case 's':
+ var_dnssec_probe = DEF_DNSSEC_PROBE;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+ if (argc != optind + 2)
+ usage(argv);
+ types_argv = argv_split(argv[optind], CHARS_COMMA_SP);
+ types = (unsigned *) mymalloc(sizeof(*types) * (types_argv->argc + 1));
+ for (i = 0; i < types_argv->argc; i++)
+ if ((types[i] = dns_type(types_argv->argv[i])) == 0)
+ msg_fatal("invalid query type: %s", types_argv->argv[i]);
+ types[i] = 0;
+ argv_free(types_argv);
+ name = argv[optind + 1];
+ msg_verbose = 1;
+ switch (dns_lookup_rv(name, RES_USE_DNSSEC, &rr, fqdn, why,
+ &rcode, lflags, types)) {
+ default:
+ msg_warn("%s (rcode=%d)", vstring_str(why), rcode);
+ case DNS_OK:
+ if (rr) {
+ vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn));
+ buf = vstring_alloc(100);
+ print_rr(buf, rr);
+ dns_rr_free(rr);
+ vstring_free(buf);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ myfree((void *) types);
+ exit(0);
+}
diff --git a/src/dnsblog/.indent.pro b/src/dnsblog/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/dnsblog/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/dnsblog/Makefile.in b/src/dnsblog/Makefile.in
new file mode 100644
index 0000000..c2ed848
--- /dev/null
+++ b/src/dnsblog/Makefile.in
@@ -0,0 +1,84 @@
+SHELL = /bin/sh
+SRCS = dnsblog.c
+OBJS = dnsblog.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = dnsblog
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+dnsblog.o: ../../include/argv.h
+dnsblog.o: ../../include/attr.h
+dnsblog.o: ../../include/check_arg.h
+dnsblog.o: ../../include/dns.h
+dnsblog.o: ../../include/htable.h
+dnsblog.o: ../../include/iostuff.h
+dnsblog.o: ../../include/mail_conf.h
+dnsblog.o: ../../include/mail_params.h
+dnsblog.o: ../../include/mail_proto.h
+dnsblog.o: ../../include/mail_server.h
+dnsblog.o: ../../include/mail_version.h
+dnsblog.o: ../../include/msg.h
+dnsblog.o: ../../include/myaddrinfo.h
+dnsblog.o: ../../include/mymalloc.h
+dnsblog.o: ../../include/nvtable.h
+dnsblog.o: ../../include/sock_addr.h
+dnsblog.o: ../../include/sys_defs.h
+dnsblog.o: ../../include/valid_hostname.h
+dnsblog.o: ../../include/vbuf.h
+dnsblog.o: ../../include/vstream.h
+dnsblog.o: ../../include/vstring.h
+dnsblog.o: dnsblog.c
diff --git a/src/dnsblog/dnsblog.c b/src/dnsblog/dnsblog.c
new file mode 100644
index 0000000..bc87c4b
--- /dev/null
+++ b/src/dnsblog/dnsblog.c
@@ -0,0 +1,319 @@
+/*++
+/* NAME
+/* dnsblog 8
+/* SUMMARY
+/* Postfix DNS allow/denylist logger
+/* SYNOPSIS
+/* \fBdnsblog\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBdnsblog\fR(8) server implements an ad-hoc DNS
+/* allow/denylist lookup service. This may eventually be
+/* replaced by an UDP client that is built directly into the
+/* \fBpostscreen\fR(8) server.
+/* PROTOCOL
+/* .ad
+/* .fi
+/* With each connection, the \fBdnsblog\fR(8) server receives
+/* a DNS allow/denylist domain name, an IP address, and an ID.
+/* If the IP address is listed under the DNS allow/denylist, the
+/* \fBdnsblog\fR(8) server logs the match and replies with the
+/* query arguments plus an address list with the resulting IP
+/* addresses, separated by whitespace, and the reply TTL.
+/* Otherwise it replies with the query arguments plus an empty
+/* address list and the reply TTL; the reply TTL is -1 if there
+/* is no reply, or a negative reply that contains no SOA record.
+/* Finally, the \fBdnsblog\fR(8) server closes the connection.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as
+/* \fBdnsblog\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.
+/* .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 "\fBpostscreen_dnsbl_sites (empty)\fR"
+/* Optional list of DNS allow/denylist domains, filters and weight
+/* factors.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtpd(8), Postfix SMTP server
+/* postconf(5), configuration parameters
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 2.8.
+/* 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 <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <myaddrinfo.h>
+#include <valid_hostname.h>
+#include <sock_addr.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+int var_dnsblog_delay;
+
+ /*
+ * Static so we don't allocate and free on every request.
+ */
+static VSTRING *rbl_domain;
+static VSTRING *addr;
+static VSTRING *query;
+static VSTRING *why;
+static VSTRING *result;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* static void dnsblog_query - query DNSBL for client address */
+
+static VSTRING *dnsblog_query(VSTRING *result, int *result_ttl,
+ const char *dnsbl_domain,
+ const char *addr)
+{
+ const char *myname = "dnsblog_query";
+ ARGV *octets;
+ int i;
+ struct addrinfo *res;
+ unsigned char *ipv6_addr;
+ int dns_status;
+ DNS_RR *addr_list;
+ DNS_RR *rr;
+ MAI_HOSTADDR_STR hostaddr;
+
+ if (msg_verbose)
+ msg_info("%s: addr %s dnsbl_domain %s",
+ myname, addr, dnsbl_domain);
+
+ VSTRING_RESET(query);
+
+ /*
+ * Reverse the client IPV6 address, represented as 32 hexadecimal
+ * nibbles. We use the binary address to avoid tricky code. Asking for an
+ * AAAA record makes no sense here. Just like with IPv4 we use the lookup
+ * result as a bit mask, not as an IP address.
+ */
+#ifdef HAS_IPV6
+ if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) {
+ if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0
+ || res->ai_family != PF_INET6)
+ msg_fatal("%s: unable to convert address %s", myname, addr);
+ ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr);
+ for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--)
+ vstring_sprintf_append(query, "%x.%x.",
+ ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4);
+ freeaddrinfo(res);
+ } else
+#endif
+
+ /*
+ * Reverse the client IPV4 address, represented as four decimal octet
+ * values. We use the textual address for convenience.
+ */
+ {
+ octets = argv_split(addr, ".");
+ for (i = octets->argc - 1; i >= 0; i--) {
+ vstring_strcat(query, octets->argv[i]);
+ vstring_strcat(query, ".");
+ }
+ argv_free(octets);
+ }
+
+ /*
+ * Tack on the RBL domain name and query the DNS for an A record.
+ */
+ vstring_strcat(query, dnsbl_domain);
+ dns_status = dns_lookup_x(STR(query), T_A, 0, &addr_list, (VSTRING *) 0,
+ why, (int *) 0, DNS_REQ_FLAG_NCACHE_TTL);
+
+ /*
+ * We return the lowest TTL in the response from the A record(s) if
+ * found, or from the SOA record(s) if available. If the reply specifies
+ * no TTL, or if the query fails, we return a TTL of -1.
+ */
+ VSTRING_RESET(result);
+ *result_ttl = -1;
+ if (dns_status == DNS_OK) {
+ for (rr = addr_list; rr != 0; rr = rr->next) {
+ if (dns_rr_to_pa(rr, &hostaddr) == 0) {
+ msg_warn("%s: skipping reply record type %s for query %s: %m",
+ myname, dns_strtype(rr->type), STR(query));
+ } else {
+ msg_info("addr %s listed by domain %s as %s",
+ addr, dnsbl_domain, hostaddr.buf);
+ if (LEN(result) > 0)
+ vstring_strcat(result, " ");
+ vstring_strcat(result, hostaddr.buf);
+ /* Grab the positive reply TTL. */
+ if (*result_ttl < 0 || *result_ttl > rr->ttl)
+ *result_ttl = rr->ttl;
+ }
+ }
+ dns_rr_free(addr_list);
+ } else if (dns_status == DNS_NOTFOUND) {
+ if (msg_verbose)
+ msg_info("%s: addr %s not listed by domain %s",
+ myname, addr, dnsbl_domain);
+ /* Grab the negative reply TTL. */
+ for (rr = addr_list; rr != 0; rr = rr->next) {
+ if (rr->type == T_SOA && (*result_ttl < 0 || *result_ttl > rr->ttl))
+ *result_ttl = rr->ttl;
+ }
+ dns_rr_free(addr_list);
+ } else {
+ msg_warn("%s: lookup error for DNS query %s: %s",
+ myname, STR(query), STR(why));
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* dnsblog_service - perform service for client */
+
+static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+ int request_id;
+ int result_ttl;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the dnsblog service. All connection-management stuff is handled by
+ * the common code in single_server.c.
+ */
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, rbl_domain),
+ RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, addr),
+ RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
+ ATTR_TYPE_END) == 3) {
+ (void) dnsblog_query(result, &result_ttl, STR(rbl_domain), STR(addr));
+ if (var_dnsblog_delay > 0)
+ sleep(var_dnsblog_delay);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain)),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr)),
+ SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
+ SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, STR(result)),
+ SEND_ATTR_INT(MAIL_ATTR_TTL, result_ttl),
+ ATTR_TYPE_END);
+ vstream_fflush(client_stream);
+ }
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+ rbl_domain = vstring_alloc(100);
+ addr = vstring_alloc(100);
+ query = vstring_alloc(100);
+ why = vstring_alloc(100);
+ result = vstring_alloc(100);
+ var_use_limit = 0;
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_DNSBLOG_DELAY, DEF_DNSBLOG_DELAY, &var_dnsblog_delay, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, dnsblog_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_UNLIMITED,
+ CA_MAIL_SERVER_RETIRE_ME,
+ 0);
+}
diff --git a/src/error/.indent.pro b/src/error/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/error/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/error/.printfck b/src/error/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/error/.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/error/Makefile.in b/src/error/Makefile.in
new file mode 100644
index 0000000..c98a267
--- /dev/null
+++ b/src/error/Makefile.in
@@ -0,0 +1,89 @@
+SHELL = /bin/sh
+SRCS = error.c
+OBJS = error.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = error
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+error.o: ../../include/attr.h
+error.o: ../../include/bounce.h
+error.o: ../../include/check_arg.h
+error.o: ../../include/defer.h
+error.o: ../../include/deliver_completed.h
+error.o: ../../include/deliver_request.h
+error.o: ../../include/dsn.h
+error.o: ../../include/dsn_buf.h
+error.o: ../../include/dsn_util.h
+error.o: ../../include/flush_clnt.h
+error.o: ../../include/htable.h
+error.o: ../../include/iostuff.h
+error.o: ../../include/mail_conf.h
+error.o: ../../include/mail_proto.h
+error.o: ../../include/mail_queue.h
+error.o: ../../include/mail_server.h
+error.o: ../../include/mail_version.h
+error.o: ../../include/msg.h
+error.o: ../../include/msg_stats.h
+error.o: ../../include/mymalloc.h
+error.o: ../../include/nvtable.h
+error.o: ../../include/recipient_list.h
+error.o: ../../include/sys_defs.h
+error.o: ../../include/sys_exits.h
+error.o: ../../include/vbuf.h
+error.o: ../../include/vstream.h
+error.o: ../../include/vstring.h
+error.o: error.c
diff --git a/src/error/error.c b/src/error/error.c
new file mode 100644
index 0000000..61e805b
--- /dev/null
+++ b/src/error/error.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* error 8
+/* SUMMARY
+/* Postfix error/retry mail delivery agent
+/* SYNOPSIS
+/* \fBerror\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix \fBerror\fR(8) delivery agent processes delivery
+/* requests from
+/* the queue manager. Each request specifies a queue file, a sender
+/* address, the reason for non-delivery (specified as the
+/* next-hop destination), and recipient information.
+/* The reason may be prefixed with an RFC 3463-compatible detail code;
+/* if none is specified a default 4.0.0 or 5.0.0 code is used instead.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* Depending on the service name in master.cf, \fBerror\fR
+/* or \fBretry\fR, the server bounces or defers all recipients
+/* in the delivery request using the "next-hop" information
+/* as the reason for non-delivery. The \fBretry\fR service name is
+/* supported as of Postfix 2.4.
+/*
+/* Delivery status reports are sent to the \fBbounce\fR(8),
+/* \fBdefer\fR(8) or \fBtrace\fR(8) daemon as appropriate.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBerror\fR(8) mailer is not security-sensitive. It does not talk
+/* to the network, and can be run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 3463 (Enhanced Status Codes)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBerror\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.
+/* .IP "\fB2bounce_notice_recipient (postmaster)\fR"
+/* The recipient of undeliverable mail that cannot be returned to
+/* the sender.
+/* .IP "\fBbounce_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications with the message headers
+/* of mail that Postfix did not deliver and of SMTP conversation
+/* transcripts of mail that Postfix did not receive.
+/* .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 "\fBdouble_bounce_sender (double-bounce)\fR"
+/* The sender address of postmaster notifications that are generated
+/* by the mail system.
+/* .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 "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* .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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* discard(8), Postfix discard delivery agent
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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 <unistd.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <deliver_request.h>
+#include <mail_queue.h>
+#include <bounce.h>
+#include <defer.h>
+#include <deliver_completed.h>
+#include <flush_clnt.h>
+#include <dsn_util.h>
+#include <sys_exits.h>
+#include <mail_proto.h>
+#include <mail_version.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* deliver_message - deliver message with extreme prejudice */
+
+static int deliver_message(DELIVER_REQUEST *request, const char *def_dsn,
+ int (*append) (int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *))
+{
+ const char *myname = "deliver_message";
+ VSTREAM *src;
+ int result = 0;
+ int status;
+ RECIPIENT *rcpt;
+ int nrcpt;
+ DSN_SPLIT dp;
+ DSN dsn;
+
+ if (msg_verbose)
+ msg_info("deliver_message: from %s", request->sender);
+
+ /*
+ * Sanity checks.
+ */
+ if (request->nexthop[0] == 0)
+ msg_fatal("empty nexthop hostname");
+ if (request->rcpt_list.len <= 0)
+ msg_fatal("recipient count: %d", request->rcpt_list.len);
+
+ /*
+ * Open the queue file. Opening the file can fail for a variety of
+ * reasons, such as the system running out of resources. Instead of
+ * throwing away mail, we're raising a fatal error which forces the mail
+ * system to back off, and retry later.
+ */
+ src = mail_queue_open(request->queue_name, request->queue_id,
+ O_RDWR, 0);
+ if (src == 0)
+ msg_fatal("%s: open %s %s: %m", myname,
+ request->queue_name, request->queue_id);
+ if (msg_verbose)
+ msg_info("%s: file %s", myname, VSTREAM_PATH(src));
+
+ /*
+ * Bounce/defer/whatever all recipients.
+ */
+#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags)
+
+ dsn_split(&dp, def_dsn, request->nexthop);
+ (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text);
+ for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ status = append(BOUNCE_FLAGS(request), request->queue_id,
+ &request->msg_stats, rcpt, "none", &dsn);
+ if (status == 0)
+ deliver_completed(src, rcpt->offset);
+ result |= status;
+ }
+
+ /*
+ * Clean up.
+ */
+ if (vstream_fclose(src))
+ msg_warn("close %s %s: %m", request->queue_name, request->queue_id);
+
+ return (result);
+}
+
+/* error_service - perform service for client */
+
+static void error_service(VSTREAM *client_stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to the error mailer. What we see below is a little protocol
+ * to (1) tell the queue manager that we are ready, (2) read a request
+ * from the queue manager, and (3) report the completion status of that
+ * request. All connection-management stuff is handled by the common code
+ * in single_server.c.
+ */
+ if ((request = deliver_request_read(client_stream)) != 0) {
+ if (strcmp(service, MAIL_SERVICE_ERROR) == 0)
+ status = deliver_message(request, "5.0.0", bounce_append);
+ else if (strcmp(service, MAIL_SERVICE_RETRY) == 0)
+ status = deliver_message(request, "4.0.0", defer_append);
+ else
+ msg_fatal("bad error service name: %s", service);
+ deliver_request_done(client_stream, request, status);
+ }
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+ flush_init();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, error_service,
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ 0);
+}
diff --git a/src/flush/.indent.pro b/src/flush/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/flush/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/flush/.printfck b/src/flush/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/flush/.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/flush/Makefile.in b/src/flush/Makefile.in
new file mode 100644
index 0000000..8d490bf
--- /dev/null
+++ b/src/flush/Makefile.in
@@ -0,0 +1,96 @@
+SHELL = /bin/sh
+SRCS = flush.c
+OBJS = flush.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = flush
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+flush.o: ../../include/argv.h
+flush.o: ../../include/attr.h
+flush.o: ../../include/check_arg.h
+flush.o: ../../include/dict.h
+flush.o: ../../include/domain_list.h
+flush.o: ../../include/events.h
+flush.o: ../../include/flush_clnt.h
+flush.o: ../../include/htable.h
+flush.o: ../../include/iostuff.h
+flush.o: ../../include/mail_conf.h
+flush.o: ../../include/mail_flush.h
+flush.o: ../../include/mail_params.h
+flush.o: ../../include/mail_proto.h
+flush.o: ../../include/mail_queue.h
+flush.o: ../../include/mail_scan_dir.h
+flush.o: ../../include/mail_server.h
+flush.o: ../../include/mail_version.h
+flush.o: ../../include/maps.h
+flush.o: ../../include/match_list.h
+flush.o: ../../include/match_parent_style.h
+flush.o: ../../include/midna_domain.h
+flush.o: ../../include/msg.h
+flush.o: ../../include/myflock.h
+flush.o: ../../include/mymalloc.h
+flush.o: ../../include/nvtable.h
+flush.o: ../../include/safe_open.h
+flush.o: ../../include/scan_dir.h
+flush.o: ../../include/stringops.h
+flush.o: ../../include/sys_defs.h
+flush.o: ../../include/vbuf.h
+flush.o: ../../include/vstream.h
+flush.o: ../../include/vstring.h
+flush.o: ../../include/vstring_vstream.h
+flush.o: ../../include/warn_stat.h
+flush.o: flush.c
diff --git a/src/flush/flush.c b/src/flush/flush.c
new file mode 100644
index 0000000..b8fae77
--- /dev/null
+++ b/src/flush/flush.c
@@ -0,0 +1,865 @@
+/*++
+/* NAME
+/* flush 8
+/* SUMMARY
+/* Postfix fast flush server
+/* SYNOPSIS
+/* \fBflush\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBflush\fR(8) server maintains a record of deferred
+/* mail by destination.
+/* This information is used to improve the performance of the SMTP
+/* \fBETRN\fR request, and of its command-line equivalent,
+/* "\fBsendmail -qR\fR" or "\fBpostqueue -f\fR".
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The record is implemented as a per-destination logfile with
+/* as contents the queue IDs of deferred mail. A logfile is
+/* append-only, and is truncated when delivery is requested
+/* for the corresponding destination. A destination is the
+/* part on the right-hand side of the right-most \fB@\fR in
+/* an email address.
+/*
+/* Per-destination logfiles of deferred mail are maintained only for
+/* eligible destinations. The list of eligible destinations is
+/* specified with the \fBfast_flush_domains\fR configuration parameter,
+/* which defaults to \fB$relay_domains\fR.
+/*
+/* This server implements the following requests:
+/* .IP "\fBadd\fI sitename queueid\fR"
+/* Inform the \fBflush\fR(8) server that the message with the specified
+/* queue ID is queued for the specified destination.
+/* .IP "\fBsend_site\fI sitename\fR"
+/* Request delivery of mail that is queued for the specified
+/* destination.
+/* .IP "\fBsend_file\fI queueid\fR"
+/* Request delivery of the specified deferred message.
+/* .IP \fBrefresh\fR
+/* Refresh non-empty per-destination logfiles that were not read in
+/* \fB$fast_flush_refresh_time\fR hours, by simulating
+/* send requests (see above) for the corresponding destinations.
+/* .sp
+/* Delete empty per-destination logfiles that were not updated in
+/* \fB$fast_flush_purge_time\fR days.
+/* .sp
+/* This request completes in the background.
+/* .IP \fBpurge\fR
+/* Do a \fBrefresh\fR for all per-destination logfiles.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBflush\fR(8) server is not security-sensitive. It does not
+/* talk to the network, and it does not talk to local users.
+/* The fast flush server can run chrooted at fixed low privilege.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* Fast flush logfiles are truncated only after a "send"
+/* request, not when mail is actually delivered, and therefore can
+/* accumulate outdated or redundant data. In order to maintain sanity,
+/* "refresh" must be executed periodically. This can
+/* be automated with a suitable wakeup timer setting in the
+/* \fBmaster.cf\fR configuration file.
+/*
+/* Upon receipt of a request to deliver mail for an eligible
+/* destination, the \fBflush\fR(8) server requests delivery of all messages
+/* that are listed in that destination's logfile, regardless of the
+/* recipients of those messages. This is not an issue for mail
+/* that is sent to a \fBrelay_domains\fR destination because
+/* such mail typically only has recipients in one domain.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBflush\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.
+/* .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 "\fBfast_flush_domains ($relay_domains)\fR"
+/* Optional list of destinations that are eligible for per-destination
+/* logfiles with mail that is queued to those destinations.
+/* .IP "\fBfast_flush_refresh_time (12h)\fR"
+/* The time after which a non-empty but unread per-destination "fast
+/* flush" logfile needs to be refreshed.
+/* .IP "\fBfast_flush_purge_time (7d)\fR"
+/* The time after which an empty per-destination "fast flush" logfile
+/* is deleted.
+/* .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 "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
+/* A list of Postfix features where the pattern "example.com" also
+/* matches subdomains of example.com,
+/* instead of requiring an explicit ".example.com" pattern.
+/* .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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* FILES
+/* /var/spool/postfix/flush, "fast flush" logfiles.
+/* SEE ALSO
+/* smtpd(8), SMTP server
+/* qmgr(8), queue manager
+/* 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
+/* ETRN_README, Postfix ETRN howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 1.0.
+/* 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/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <utime.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <myflock.h>
+#include <htable.h>
+#include <dict.h>
+#include <scan_dir.h>
+#include <stringops.h>
+#include <safe_open.h>
+#include <warn_stat.h>
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <mail_flush.h>
+#include <flush_clnt.h>
+#include <mail_conf.h>
+#include <mail_scan_dir.h>
+#include <maps.h>
+#include <domain_list.h>
+#include <match_parent_style.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters. The fast_flush_domains parameter is not defined here,
+ * because it is also used by the global library, and therefore is owned by
+ * the library.
+ */
+int var_fflush_refresh;
+int var_fflush_purge;
+
+ /*
+ * Flush policy stuff.
+ */
+static DOMAIN_LIST *flush_domains;
+
+ /*
+ * Some hard-wired policy: how many queue IDs we remember while we're
+ * flushing a logfile (duplicate elimination). Sites with 1000+ emails
+ * queued should arrange for permanent connectivity.
+ */
+#define FLUSH_DUP_FILTER_SIZE 10000 /* graceful degradation */
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define STREQ(x,y) (STRREF(x) == STRREF(y) || strcmp(x,y) == 0)
+
+ /*
+ * Forward declarations resulting from breaking up routines according to
+ * name space: domain names versus safe-to-use pathnames.
+ */
+static int flush_add_path(const char *, const char *);
+static int flush_send_path(const char *, int);
+
+ /*
+ * Do we only refresh the per-destination logfile, or do we really request
+ * mail delivery as if someone sent ETRN? If the latter, we must override
+ * information about unavailable hosts or unavailable transports.
+ *
+ * When selectively flushing deferred mail, we need to override the queue
+ * manager's "dead destination" information and unthrottle transports and
+ * queues. There are two options:
+ *
+ * - Unthrottle all transports and queues before we move mail to the incoming
+ * queue. This is less accurate, but has the advantage when flushing lots of
+ * mail, because Postfix can skip delivery of flushed messages after it
+ * discovers that a destination is (still) unavailable.
+ *
+ * - Unthrottle some transports and queues after the queue manager moves mail
+ * to the active queue. This is more accurate, but has the disadvantage when
+ * flushing lots of mail, because Postfix cannot skip delivery of flushed
+ * messages after it discovers that a destination is (still) unavailable.
+ */
+#define REFRESH_ONLY 0
+#define UNTHROTTLE_BEFORE (1<<0)
+#define UNTHROTTLE_AFTER (1<<1)
+
+/* flush_site_to_path - convert domain or [addr] to harmless string */
+
+static VSTRING *flush_site_to_path(VSTRING *path, const char *site)
+{
+ const char *ptr;
+ int ch;
+
+ /*
+ * Convert the name to ASCII, so that we don't to end up with non-ASCII
+ * names in the file system. The IDNA library functions fold case.
+ */
+#ifndef NO_EAI
+ if ((site = midna_domain_to_ascii(site)) == 0)
+ return (0);
+#endif
+
+ /*
+ * Allocate buffer on the fly; caller still needs to clean up.
+ */
+ if (path == 0)
+ path = vstring_alloc(10);
+
+ /*
+ * Mask characters that could upset the name-to-queue-file mapping code.
+ */
+ for (ptr = site; (ch = *(unsigned const char *) ptr) != 0; ptr++)
+ if (ISALNUM(ch))
+ VSTRING_ADDCH(path, tolower(ch));
+ else
+ VSTRING_ADDCH(path, '_');
+ VSTRING_TERMINATE(path);
+
+ if (msg_verbose)
+ msg_info("site %s to path %s", site, STR(path));
+
+ return (path);
+}
+
+/* flush_add_service - append queue ID to per-site fast flush logfile */
+
+static int flush_add_service(const char *site, const char *queue_id)
+{
+ const char *myname = "flush_add_service";
+ VSTRING *site_path;
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: site %s queue_id %s", myname, site, queue_id);
+
+ /*
+ * If this site is not eligible for logging, deny the request.
+ */
+ if (domain_list_match(flush_domains, site) == 0)
+ return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY);
+
+ /*
+ * Map site to path and update log.
+ */
+ if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0)
+ return (FLUSH_STAT_DENY);
+ status = flush_add_path(STR(site_path), queue_id);
+ vstring_free(site_path);
+
+ return (status);
+}
+
+/* flush_add_path - add record to log */
+
+static int flush_add_path(const char *path, const char *queue_id)
+{
+ const char *myname = "flush_add_path";
+ VSTREAM *log;
+
+ /*
+ * Sanity check.
+ */
+ if (!mail_queue_id_ok(path))
+ return (FLUSH_STAT_BAD);
+
+ /*
+ * Open the logfile or bust.
+ */
+ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path,
+ O_CREAT | O_APPEND | O_WRONLY, 0600)) == 0)
+ msg_fatal("%s: open fast flush logfile %s: %m", myname, path);
+
+ /*
+ * We must lock the logfile, so that we don't lose information due to
+ * concurrent access. If the lock takes too long, the Postfix watchdog
+ * will eventually take care of the problem, but it will take a while.
+ */
+ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock fast flush logfile %s: %m", myname, path);
+
+ /*
+ * Append the queue ID. With 15 bits of microsecond time, a queue ID is
+ * not recycled often enough for false hits to be a problem. If it does,
+ * then we could add other signature information, such as the file size
+ * in bytes.
+ */
+ vstream_fprintf(log, "%s\n", queue_id);
+ if (vstream_fflush(log))
+ msg_warn("write fast flush logfile %s: %m", path);
+
+ /*
+ * Clean up.
+ */
+ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path);
+ if (vstream_fclose(log) != 0)
+ msg_warn("write fast flush logfile %s: %m", path);
+
+ return (FLUSH_STAT_OK);
+}
+
+/* flush_send_service - flush mail queued for site */
+
+static int flush_send_service(const char *site, int how)
+{
+ const char *myname = "flush_send_service";
+ VSTRING *site_path;
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: site %s", myname, site);
+
+ /*
+ * If this site is not eligible for logging, deny the request.
+ */
+ if (domain_list_match(flush_domains, site) == 0)
+ return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY);
+
+ /*
+ * Map site name to path name and flush the log.
+ */
+ if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0)
+ return (FLUSH_STAT_DENY);
+ status = flush_send_path(STR(site_path), how);
+ vstring_free(site_path);
+
+ return (status);
+}
+
+/* flush_one_file - move one queue file to incoming queue */
+
+static int flush_one_file(const char *queue_id, VSTRING *queue_file,
+ struct utimbuf * tbuf, int how)
+{
+ const char *myname = "flush_one_file";
+ const char *queue_name;
+ const char *path;
+
+ /*
+ * Some other instance of this program may flush some logfile and may
+ * just have moved this queue file to the incoming queue.
+ */
+ for (queue_name = MAIL_QUEUE_DEFERRED; /* see below */ ;
+ queue_name = MAIL_QUEUE_INCOMING) {
+ path = mail_queue_path(queue_file, queue_name, queue_id);
+ if (utime(path, tbuf) == 0)
+ break;
+ if (errno != ENOENT)
+ msg_warn("%s: update %s time stamps: %m", myname, path);
+ if (STREQ(queue_name, MAIL_QUEUE_INCOMING))
+ return (0);
+ }
+
+ /*
+ * With the UNTHROTTLE_AFTER strategy, we leave it up to the queue
+ * manager to unthrottle transports and queues as it reads recipients
+ * from a queue file. We request this unthrottle operation by setting the
+ * group read permission bit.
+ *
+ * Note: we must avoid using chmod(). It is not only slower than fchmod()
+ * but it is also less secure. With chmod(), an attacker could repeatedly
+ * send requests to the flush server and trick it into changing
+ * permissions of non-queue files, by exploiting a race condition.
+ *
+ * We use safe_open() because we don't validate the file content before
+ * modifying the file status.
+ */
+ if (how & UNTHROTTLE_AFTER) {
+ VSTRING *why;
+ struct stat st;
+ VSTREAM *fp;
+
+ for (why = vstring_alloc(1); /* see below */ ;
+ queue_name = MAIL_QUEUE_INCOMING,
+ path = mail_queue_path(queue_file, queue_name, queue_id)) {
+ if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) != 0)
+ break;
+ if (errno != ENOENT)
+ msg_warn("%s: open %s: %s", myname, path, STR(why));
+ if (errno != ENOENT || STREQ(queue_name, MAIL_QUEUE_INCOMING)) {
+ vstring_free(why);
+ return (0);
+ }
+ }
+ vstring_free(why);
+ if ((st.st_mode & MAIL_QUEUE_STAT_READY) != MAIL_QUEUE_STAT_READY) {
+ (void) vstream_fclose(fp);
+ return (0);
+ }
+ if (fchmod(vstream_fileno(fp), st.st_mode | MAIL_QUEUE_STAT_UNTHROTTLE) < 0)
+ msg_warn("%s: fchmod %s: %m", myname, path);
+ (void) vstream_fclose(fp);
+ }
+
+ /*
+ * Move the file to the incoming queue, if it isn't already there.
+ */
+ if (STREQ(queue_name, MAIL_QUEUE_INCOMING) == 0
+ && mail_queue_rename(queue_id, queue_name, MAIL_QUEUE_INCOMING) < 0
+ && errno != ENOENT)
+ msg_warn("%s: rename from %s to %s: %m",
+ path, queue_name, MAIL_QUEUE_INCOMING);
+
+ /*
+ * If we got here, we achieved something, so let's claim success.
+ */
+ return (1);
+}
+
+/* flush_send_path - flush logfile file */
+
+static int flush_send_path(const char *path, int how)
+{
+ const char *myname = "flush_send_path";
+ VSTRING *queue_id;
+ VSTRING *queue_file;
+ VSTREAM *log;
+ struct utimbuf tbuf;
+ static char qmgr_flush_trigger[] = {
+ QMGR_REQ_FLUSH_DEAD, /* flush dead site/transport cache */
+ };
+ static char qmgr_scan_trigger[] = {
+ QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */
+ };
+ HTABLE *dup_filter;
+ int count;
+
+ /*
+ * Sanity check.
+ */
+ if (!mail_queue_id_ok(path))
+ return (FLUSH_STAT_BAD);
+
+ /*
+ * Open the logfile. If the file does not exist, then there is no queued
+ * mail for this destination.
+ */
+ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("%s: open fast flush logfile %s: %m", myname, path);
+ return (FLUSH_STAT_OK);
+ }
+
+ /*
+ * We must lock the logfile, so that we don't lose information when it is
+ * truncated. Unfortunately, this means that the file can be locked for a
+ * significant amount of time. If things really get stuck the Postfix
+ * watchdog will take care of it.
+ */
+ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock fast flush logfile %s: %m", myname, path);
+
+ /*
+ * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to
+ * unthrottle all transports and queues before we move a deferred queue
+ * file to the incoming queue. This minimizes a race condition where the
+ * queue manager seizes a queue file before it knows that we want to
+ * flush that message.
+ *
+ * This reduces the race condition time window to a very small amount (the
+ * flush server does not really know when the queue manager reads its
+ * command fifo). But there is a worse race, where the queue manager
+ * moves a deferred queue file to the active queue before we have a
+ * chance to expedite its delivery.
+ */
+ if (how & UNTHROTTLE_BEFORE)
+ mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
+ qmgr_flush_trigger, sizeof(qmgr_flush_trigger));
+
+ /*
+ * This is the part that dominates running time: schedule the listed
+ * queue files for delivery by updating their file time stamps and by
+ * moving them from the deferred queue to the incoming queue. This should
+ * take no more than a couple seconds under normal conditions. Filter out
+ * duplicate queue file names to avoid hammering the file system, with
+ * some finite limit on the amount of memory that we are willing to
+ * sacrifice for duplicate filtering. Graceful degradation.
+ *
+ * By moving selected queue files from the deferred queue to the incoming
+ * queue we optimize for the case where most deferred mail is for other
+ * sites. If that assumption does not hold, i.e. all deferred mail is for
+ * the same site, then doing a "fast flush" will cost more disk I/O than
+ * a "slow flush" that delivers the entire deferred queue. This penalty
+ * is only temporary - it will go away after we unite the active queue
+ * and the incoming queue.
+ */
+ queue_id = vstring_alloc(10);
+ queue_file = vstring_alloc(10);
+ dup_filter = htable_create(10);
+ tbuf.actime = tbuf.modtime = event_time();
+ for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) {
+ if (!mail_queue_id_ok(STR(queue_id))) {
+ msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s",
+ STR(queue_id), path);
+ continue;
+ }
+ if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE
+ || htable_find(dup_filter, STR(queue_id)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: logfile %s: update queue file %s time stamps",
+ myname, path, STR(queue_id));
+ if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE)
+ htable_enter(dup_filter, STR(queue_id), 0);
+ count += flush_one_file(STR(queue_id), queue_file, &tbuf, how);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: logfile %s: skip queue file %s as duplicate",
+ myname, path, STR(queue_file));
+ }
+ }
+ htable_free(dup_filter, (void (*) (void *)) 0);
+ vstring_free(queue_file);
+ vstring_free(queue_id);
+
+ /*
+ * Truncate the fast flush log.
+ */
+ if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0)
+ msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path);
+
+ /*
+ * Workaround for noatime mounts. Use futimes() if available.
+ */
+ (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0);
+
+ /*
+ * Request delivery and clean up.
+ */
+ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path);
+ if (vstream_fclose(log) != 0)
+ msg_warn("%s: read fast flush logfile %s: %m", myname, path);
+ if (count > 0) {
+ if (msg_verbose)
+ msg_info("%s: requesting delivery for logfile %s", myname, path);
+ mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
+ qmgr_scan_trigger, sizeof(qmgr_scan_trigger));
+ }
+ return (FLUSH_STAT_OK);
+}
+
+/* flush_send_file_service - flush one queue file */
+
+static int flush_send_file_service(const char *queue_id)
+{
+ const char *myname = "flush_send_file_service";
+ VSTRING *queue_file;
+ struct utimbuf tbuf;
+ static char qmgr_scan_trigger[] = {
+ QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */
+ };
+
+ /*
+ * Sanity check.
+ */
+ if (!mail_queue_id_ok(queue_id))
+ return (FLUSH_STAT_BAD);
+
+ if (msg_verbose)
+ msg_info("%s: requesting delivery for queue_id %s", myname, queue_id);
+
+ queue_file = vstring_alloc(30);
+ tbuf.actime = tbuf.modtime = event_time();
+ if (flush_one_file(queue_id, queue_file, &tbuf, UNTHROTTLE_AFTER) > 0)
+ mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
+ qmgr_scan_trigger, sizeof(qmgr_scan_trigger));
+ vstring_free(queue_file);
+
+ return (FLUSH_STAT_OK);
+}
+
+/* flush_refresh_service - refresh logfiles beyond some age */
+
+static int flush_refresh_service(int max_age)
+{
+ const char *myname = "flush_refresh_service";
+ SCAN_DIR *scan;
+ char *site_path;
+ struct stat st;
+ VSTRING *path = vstring_alloc(10);
+
+ scan = scan_dir_open(MAIL_QUEUE_FLUSH);
+ while ((site_path = mail_scan_dir_next(scan)) != 0) {
+ if (!mail_queue_id_ok(site_path))
+ continue; /* XXX grumble. */
+ mail_queue_path(path, MAIL_QUEUE_FLUSH, site_path);
+ if (stat(STR(path), &st) < 0) {
+ if (errno != ENOENT)
+ msg_warn("%s: stat %s: %m", myname, STR(path));
+ else if (msg_verbose)
+ msg_info("%s: %s: %m", myname, STR(path));
+ continue;
+ }
+ if (st.st_size == 0) {
+ if (st.st_mtime + var_fflush_purge < event_time()) {
+ if (unlink(STR(path)) < 0)
+ msg_warn("remove logfile %s: %m", STR(path));
+ else if (msg_verbose)
+ msg_info("%s: unlink %s, empty and unchanged for %d days",
+ myname, STR(path), var_fflush_purge / 86400);
+ } else if (msg_verbose)
+ msg_info("%s: skip logfile %s - empty log", myname, site_path);
+ } else if (st.st_atime + max_age < event_time()) {
+ if (msg_verbose)
+ msg_info("%s: flush logfile %s", myname, site_path);
+ flush_send_path(site_path, REFRESH_ONLY);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: skip logfile %s, unread for <%d hours(s) ",
+ myname, site_path, max_age / 3600);
+ }
+ }
+ scan_dir_close(scan);
+ vstring_free(path);
+
+ return (FLUSH_STAT_OK);
+}
+
+/* flush_request_receive - receive request */
+
+static int flush_request_receive(VSTREAM *client_stream, VSTRING *request)
+{
+ int count;
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_FLUSH),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(client_stream);
+
+ /*
+ * Kluge: choose the protocol depending on the request size.
+ */
+ if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
+ msg_warn("timeout while waiting for data from %s",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
+ msg_warn("cannot examine read buffer of %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+
+ /*
+ * Short request: master trigger. Use the string+null protocol.
+ */
+ if (count <= 2) {
+ if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
+ msg_warn("end-of-input while reading request from %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ }
+
+ /*
+ * Long request: real flush client. Use the attribute list protocol.
+ */
+ else {
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, request),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* flush_service - perform service for client */
+
+static void flush_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+ VSTRING *request = vstring_alloc(10);
+ VSTRING *site = 0;
+ VSTRING *queue_id = 0;
+ static char wakeup[] = { /* master wakeup request */
+ TRIGGER_REQ_WAKEUP,
+ 0,
+ };
+ int status = FLUSH_STAT_BAD;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to the fast flush service. What we see below is a little
+ * protocol to (1) read a request from the client (the name of the site)
+ * and (2) acknowledge that we have received the request.
+ *
+ * All connection-management stuff is handled by the common code in
+ * single_server.c.
+ */
+ if (flush_request_receive(client_stream, request) == 0) {
+ if (STREQ(STR(request), FLUSH_REQ_ADD)) {
+ site = vstring_alloc(10);
+ queue_id = vstring_alloc(10);
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_SITE, site),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ ATTR_TYPE_END) == 2
+ && mail_queue_id_ok(STR(queue_id)))
+ status = flush_add_service(STR(site), STR(queue_id));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ } else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) {
+ site = vstring_alloc(10);
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_SITE, site),
+ ATTR_TYPE_END) == 1)
+ status = flush_send_service(STR(site), UNTHROTTLE_BEFORE);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ } else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) {
+ queue_id = vstring_alloc(10);
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ ATTR_TYPE_END) == 1)
+ status = flush_send_file_service(STR(queue_id));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ } else if (STREQ(STR(request), FLUSH_REQ_REFRESH)
+ || STREQ(STR(request), wakeup)) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
+ ATTR_TYPE_END);
+ vstream_fflush(client_stream);
+ (void) flush_refresh_service(var_fflush_refresh);
+ } else if (STREQ(STR(request), FLUSH_REQ_PURGE)) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
+ ATTR_TYPE_END);
+ vstream_fflush(client_stream);
+ (void) flush_refresh_service(0);
+ }
+ } else
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ vstring_free(request);
+ if (site)
+ vstring_free(site);
+ if (queue_id)
+ vstring_free(queue_id);
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_FFLUSH_DOMAINS),
+ var_fflush_domains);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_FFLUSH_REFRESH, DEF_FFLUSH_REFRESH, &var_fflush_refresh, 1, 0,
+ VAR_FFLUSH_PURGE, DEF_FFLUSH_PURGE, &var_fflush_purge, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, flush_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_UNLIMITED,
+ 0);
+}
diff --git a/src/fsstone/.indent.pro b/src/fsstone/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/fsstone/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/fsstone/.printfck b/src/fsstone/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/fsstone/.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/fsstone/Makefile.in b/src/fsstone/Makefile.in
new file mode 100644
index 0000000..d34b0a2
--- /dev/null
+++ b/src/fsstone/Makefile.in
@@ -0,0 +1,69 @@
+SHELL = /bin/sh
+SRCS = fsstone.c
+OBJS = fsstone.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = fsstone
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+fsstone: fsstone.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ fsstone.o $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/fsstone
+
+../../libexec/fsstone: fsstone
+ cp $? $@
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+fsstone.o: ../../include/check_arg.h
+fsstone.o: ../../include/mail_version.h
+fsstone.o: ../../include/msg.h
+fsstone.o: ../../include/msg_vstream.h
+fsstone.o: ../../include/sys_defs.h
+fsstone.o: ../../include/vbuf.h
+fsstone.o: ../../include/vstream.h
+fsstone.o: fsstone.c
diff --git a/src/fsstone/fsstone.c b/src/fsstone/fsstone.c
new file mode 100644
index 0000000..217cb04
--- /dev/null
+++ b/src/fsstone/fsstone.c
@@ -0,0 +1,242 @@
+/*++
+/* NAME
+/* fsstone 1
+/* SUMMARY
+/* measure directory operation overhead
+/* SYNOPSIS
+/* .fi
+/* \fBfsstone\fR [\fB-cr\fR] [\fB-s \fIsize\fR]
+/* \fImsg_count files_per_dir\fR
+/* DESCRIPTION
+/* The \fBfsstone\fR command measures the cost of creating, renaming
+/* and deleting queue files versus appending messages to existing
+/* files and truncating them after use.
+/*
+/* The program simulates the arrival of \fImsg_count\fR short messages,
+/* and arranges for at most \fIfiles_per_dir\fR simultaneous files
+/* in the same directory.
+/*
+/* Options:
+/* .IP \fB-c\fR
+/* Create and delete files.
+/* .IP \fB-r\fR
+/* Rename files twice (requires \fB-c\fR).
+/* .IP \fB-s \fIsize\fR
+/* Specify the file size in kbytes.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* BUGS
+/* The \fB-r\fR option renames files within the same directory.
+/* For a more realistic simulation, the program should rename files
+/* <i>between</i> directories, and should also have an option to use
+/* <i>hashed</i> directories as implemented with, for example, the
+/* \fBdir_forest\fR(3) module.
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+
+/* Global directory. */
+
+#include <mail_version.h>
+
+/* rename_file - rename a file */
+
+static void rename_file(int old, int new)
+{
+ char new_path[BUFSIZ];
+ char old_path[BUFSIZ];
+
+ sprintf(new_path, "%06d", new);
+ sprintf(old_path, "%06d", old);
+ if (rename(old_path, new_path))
+ msg_fatal("rename %s to %s: %m", old_path, new_path);
+}
+
+/* make_file - create a little file and use it */
+
+static void make_file(int seqno, int size)
+{
+ char path[BUFSIZ];
+ char buf[1024];
+ FILE *fp;
+ int i;
+
+ sprintf(path, "%06d", seqno);
+ if ((fp = fopen(path, "w")) == 0)
+ msg_fatal("open %s: %m", path);
+ memset(buf, 'x', sizeof(buf));
+ for (i = 0; i < size; i++)
+ if (fwrite(buf, 1, sizeof(buf), fp) != sizeof(buf))
+ msg_fatal("fwrite: %m");
+ if (fsync(fileno(fp)))
+ msg_fatal("fsync: %m");
+ if (fclose(fp))
+ msg_fatal("fclose: %m");
+ if ((fp = fopen(path, "r")) == 0)
+ msg_fatal("open %s: %m", path);
+ while (fgets(path, sizeof(path), fp))
+ /* void */ ;
+ if (fclose(fp))
+ msg_fatal("fclose: %m");
+}
+
+/* use_file - use existing file */
+
+static void use_file(int seqno)
+{
+ char path[BUFSIZ];
+ FILE *fp;
+ int i;
+
+ sprintf(path, "%06d", seqno);
+ if ((fp = fopen(path, "w")) == 0)
+ msg_fatal("open %s: %m", path);
+ for (i = 0; i < 400; i++)
+ fprintf(fp, "hello");
+ if (fsync(fileno(fp)))
+ msg_fatal("fsync: %m");
+ if (fclose(fp))
+ msg_fatal("fclose: %m");
+ if ((fp = fopen(path, "r+")) == 0)
+ msg_fatal("open %s: %m", path);
+ while (fgets(path, sizeof(path), fp))
+ /* void */ ;
+ if (ftruncate(fileno(fp), (off_t) 0))
+ msg_fatal("ftruncate: %m");;
+ if (fclose(fp))
+ msg_fatal("fclose: %m");
+}
+
+/* remove_file - delete specified file */
+
+static void remove_file(int seq)
+{
+ char path[BUFSIZ];
+
+ sprintf(path, "%06d", seq);
+ if (remove(path))
+ msg_fatal("remove %s: %m", path);
+}
+
+/* remove_silent - delete specified file, silently */
+
+static void remove_silent(int seq)
+{
+ char path[BUFSIZ];
+
+ sprintf(path, "%06d", seq);
+ (void) remove(path);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-cr] [-s size] messages directory_entries", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int op_count;
+ int max_file;
+ struct timeval start, end;
+ int do_rename = 0;
+ int do_create = 0;
+ int seq;
+ int ch;
+ int size = 2;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "crs:")) != EOF) {
+ switch (ch) {
+ case 'c':
+ do_create++;
+ break;
+ case 'r':
+ do_rename++;
+ break;
+ case 's':
+ if ((size = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (argc - optind != 2 || (do_rename && !do_create))
+ usage(argv[0]);
+ if ((op_count = atoi(argv[optind])) <= 0)
+ usage(argv[0]);
+ if ((max_file = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+
+ /*
+ * Populate the directory with little files.
+ */
+ for (seq = 0; seq < max_file; seq++)
+ make_file(seq, size);
+
+ /*
+ * Simulate arrival and delivery of mail messages.
+ */
+ GETTIMEOFDAY(&start);
+ while (op_count > 0) {
+ seq %= max_file;
+ if (do_create) {
+ remove_file(seq);
+ make_file(seq, size);
+ if (do_rename) {
+ rename_file(seq, seq + max_file);
+ rename_file(seq + max_file, seq);
+ }
+ } else {
+ use_file(seq);
+ }
+ seq++;
+ op_count--;
+ }
+ GETTIMEOFDAY(&end);
+ if (end.tv_usec < start.tv_usec) {
+ end.tv_sec--;
+ end.tv_usec += 1000000;
+ }
+ printf("elapsed time: %ld.%06ld\n",
+ (long) (end.tv_sec - start.tv_sec),
+ (long) (end.tv_usec - start.tv_usec));
+
+ /*
+ * Clean up directory fillers.
+ */
+ for (seq = 0; seq < max_file; seq++)
+ remove_silent(seq);
+ return (0);
+}
diff --git a/src/global/.indent.pro b/src/global/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/global/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/global/.printfck b/src/global/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/global/.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/global/Makefile.in b/src/global/Makefile.in
new file mode 100644
index 0000000..0944a75
--- /dev/null
+++ b/src/global/Makefile.in
@@ -0,0 +1,3086 @@
+SHELL = /bin/sh
+SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
+ canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \
+ clnt_stream.c conv_time.c db_common.c debug_peer.c debug_process.c \
+ defer.c deliver_completed.c deliver_flock.c deliver_pass.c \
+ deliver_request.c dict_ldap.c dict_mysql.c dict_pgsql.c \
+ dict_proxy.c dict_sqlite.c domain_list.c dot_lockfile.c dot_lockfile_as.c \
+ dsb_scan.c dsn.c dsn_buf.c dsn_mask.c dsn_print.c dsn_util.c \
+ ehlo_mask.c ext_prop.c file_id.c flush_clnt.c header_opts.c \
+ header_token.c input_transp.c int_filt.c is_header.c log_adhoc.c \
+ mail_addr.c mail_addr_crunch.c mail_addr_find.c mail_addr_map.c \
+ mail_command_client.c mail_command_server.c mail_conf.c \
+ mail_conf_bool.c mail_conf_int.c mail_conf_long.c mail_conf_raw.c \
+ mail_conf_str.c mail_conf_time.c mail_connect.c mail_copy.c \
+ mail_date.c mail_dict.c mail_error.c mail_flush.c mail_open_ok.c \
+ mail_params.c mail_pathname.c mail_queue.c mail_run.c \
+ mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c maps.c \
+ mark_corrupt.c match_parent_style.c mbox_conf.c mbox_open.c \
+ mime_state.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c mkmap_lmdb.c mkmap_open.c \
+ mkmap_sdbm.c msg_stats_print.c msg_stats_scan.c mynetworks.c \
+ mypwd.c namadr_list.c off_cvt.c opened.c own_inet_addr.c \
+ pipe_command.c post_mail.c quote_821_local.c quote_822_local.c \
+ rcpt_buf.c rcpt_print.c rec_attr_map.c rec_streamlf.c rec_type.c \
+ recipient_list.c record.c remove.c resolve_clnt.c resolve_local.c \
+ rewrite_clnt.c scache_clnt.c scache_multi.c scache_single.c \
+ sent.c smtp_stream.c split_addr.c string_list.c strip_addr.c \
+ sys_exits.c timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \
+ tok822_resolve.c tok822_rewrite.c tok822_tree.c trace.c \
+ user_acl.c valid_mailhost_addr.c verify.c verify_clnt.c \
+ verp_sender.c wildcard_inet_addr.c xtext.c delivered_hdr.c \
+ fold_addr.c header_body_checks.c mkmap_proxy.c data_redirect.c \
+ match_service.c mail_conf_nint.c addr_match_list.c mail_conf_nbool.c \
+ smtp_reply_footer.c safe_ultostr.c verify_sender_addr.c \
+ dict_memcache.c mail_version.c memcache_proto.c server_acl.c \
+ mkmap_fail.c haproxy_srvr.c dsn_filter.c dynamicmaps.c uxtext.c \
+ smtputf8.c mail_conf_over.c mail_parm_split.c midna_adomain.c \
+ mail_addr_form.c quote_flags.c maillog_client.c \
+ normalize_mailhost_addr.c map_search.c reject_deliver_request.c \
+ info_log_addr_form.c sasl_mech_filter.c login_sender_match.c \
+ test_main.c compat_level.c config_known_tcp_ports.c \
+ hfrom_format.c
+OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
+ canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \
+ clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \
+ defer.o deliver_completed.o deliver_flock.o deliver_pass.o \
+ deliver_request.o \
+ dict_proxy.o domain_list.o dot_lockfile.o dot_lockfile_as.o \
+ dsb_scan.o dsn.o dsn_buf.o dsn_mask.o dsn_print.o dsn_util.o \
+ ehlo_mask.o ext_prop.o file_id.o flush_clnt.o header_opts.o \
+ header_token.o input_transp.o int_filt.o is_header.o log_adhoc.o \
+ mail_addr.o mail_addr_crunch.o mail_addr_find.o mail_addr_map.o \
+ mail_command_client.o mail_command_server.o mail_conf.o \
+ mail_conf_bool.o mail_conf_int.o mail_conf_long.o mail_conf_raw.o \
+ mail_conf_str.o mail_conf_time.o mail_connect.o mail_copy.o \
+ mail_date.o mail_dict.o mail_error.o mail_flush.o mail_open_ok.o \
+ mail_params.o mail_pathname.o mail_queue.o mail_run.o \
+ mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o maps.o \
+ mark_corrupt.o match_parent_style.o mbox_conf.o mbox_open.o \
+ mime_state.o mkmap_db.o mkmap_dbm.o mkmap_open.o \
+ msg_stats_print.o msg_stats_scan.o mynetworks.o \
+ mypwd.o namadr_list.o off_cvt.o opened.o own_inet_addr.o \
+ pipe_command.o post_mail.o quote_821_local.o quote_822_local.o \
+ rcpt_buf.o rcpt_print.o rec_attr_map.o rec_streamlf.o rec_type.o \
+ recipient_list.o record.o remove.o resolve_clnt.o resolve_local.o \
+ rewrite_clnt.o scache_clnt.o scache_multi.o scache_single.o \
+ sent.o smtp_stream.o split_addr.o string_list.o strip_addr.o \
+ sys_exits.o timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \
+ tok822_resolve.o tok822_rewrite.o tok822_tree.o trace.o \
+ user_acl.o valid_mailhost_addr.o verify.o verify_clnt.o \
+ verp_sender.o wildcard_inet_addr.o xtext.o delivered_hdr.o \
+ fold_addr.o header_body_checks.o mkmap_proxy.o data_redirect.o \
+ match_service.o mail_conf_nint.o addr_match_list.o mail_conf_nbool.o \
+ smtp_reply_footer.o safe_ultostr.o verify_sender_addr.o \
+ dict_memcache.o mail_version.o memcache_proto.o server_acl.o \
+ mkmap_fail.o haproxy_srvr.o dsn_filter.o dynamicmaps.o uxtext.o \
+ smtputf8.o attr_override.o mail_parm_split.o midna_adomain.o \
+ $(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \
+ normalize_mailhost_addr.o map_search.o reject_deliver_request.o \
+ info_log_addr_form.o sasl_mech_filter.o login_sender_match.o \
+ test_main.o compat_level.o config_known_tcp_ports.o \
+ hfrom_format.o
+# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
+# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
+# otherwise it sets the PLUGIN_* macros.
+MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o mkmap_cdb.o \
+ mkmap_lmdb.o mkmap_sdbm.o
+HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
+ canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \
+ conv_time.h db_common.h debug_peer.h debug_process.h defer.h \
+ deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \
+ dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \
+ dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \
+ dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \
+ file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \
+ int_filt.h is_header.h lex_822.h log_adhoc.h mail_addr.h \
+ mail_addr_crunch.h mail_addr_find.h mail_addr_map.h mail_conf.h \
+ mail_copy.h mail_date.h mail_dict.h mail_error.h mail_flush.h \
+ mail_open_ok.h mail_params.h mail_proto.h mail_queue.h mail_run.h \
+ mail_scan_dir.h mail_stream.h mail_task.h mail_version.h maps.h \
+ mark_corrupt.h match_parent_style.h mbox_conf.h mbox_open.h \
+ mime_state.h mkmap.h msg_stats.h mynetworks.h mypwd.h namadr_list.h \
+ off_cvt.h opened.h own_inet_addr.h pipe_command.h post_mail.h \
+ qmgr_user.h qmqp_proto.h quote_821_local.h quote_822_local.h \
+ quote_flags.h rcpt_buf.h rcpt_print.h rec_attr_map.h rec_streamlf.h \
+ rec_type.h recipient_list.h record.h resolve_clnt.h resolve_local.h \
+ rewrite_clnt.h scache.h sent.h smtp_stream.h split_addr.h \
+ string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \
+ trace.h user_acl.h valid_mailhost_addr.h verify.h verify_clnt.h \
+ verp_sender.h wildcard_inet_addr.h xtext.h delivered_hdr.h \
+ fold_addr.h header_body_checks.h data_redirect.h match_service.h \
+ addr_match_list.h smtp_reply_footer.h safe_ultostr.h \
+ verify_sender_addr.h dict_memcache.h memcache_proto.h server_acl.h \
+ haproxy_srvr.h dsn_filter.h dynamicmaps.h uxtext.h smtputf8.h \
+ attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \
+ maillog_client.h normalize_mailhost_addr.h map_search.h \
+ info_log_addr_form.h sasl_mech_filter.h login_sender_match.h \
+ test_main.h compat_level.h config_known_tcp_ports.h \
+ hfrom_format.h
+TESTSRC = rec2stream.c stream2rec.c recdump.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)global$(LIB_SUFFIX)
+TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
+ mail_addr_map mail_date maps mynetworks mypwd namadr_list \
+ off_cvt quote_822_local rec2stream recdump resolve_clnt \
+ resolve_local rewrite_clnt stream2rec string_list tok822_parse \
+ quote_821_local mail_conf_time mime_state strip_addr \
+ verify_clnt xtext anvil_clnt scache ehlo_mask \
+ valid_mailhost_addr own_inet_addr header_body_checks \
+ data_redirect addr_match_list safe_ultostr verify_sender_addr \
+ mail_version mail_dict server_acl uxtext mail_parm_split \
+ fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
+ haproxy_srvr map_search delivered_hdr login_sender_match \
+ compat_level config_known_tcp_ports hfrom_format
+
+LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \
+ $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX) \
+ $(LIB_PREFIX)lmdb$(LIB_SUFFIX) $(LIB_PREFIX)cdb$(LIB_SUFFIX) \
+ $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
+MAKES =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ)
+
+$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+plugin_map_so_make: $(PLUGIN_MAP_SO)
+
+$(LIB_PREFIX)ldap$(LIB_SUFFIX): dict_ldap.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_ldap.o $(AUXLIBS_LDAP)
+
+$(LIB_PREFIX)mysql$(LIB_SUFFIX): dict_mysql.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_mysql.o $(AUXLIBS_MYSQL)
+
+$(LIB_PREFIX)pgsql$(LIB_SUFFIX): dict_pgsql.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_pgsql.o $(AUXLIBS_PGSQL)
+
+$(LIB_PREFIX)sqlite$(LIB_SUFFIX): dict_sqlite.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_sqlite.o $(AUXLIBS_SQLITE)
+
+$(LIB_PREFIX)cdb$(LIB_SUFFIX): mkmap_cdb.o $(LIB_DIR)/dict_cdb.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_cdb.o \
+ $(LIB_DIR)/dict_cdb.o $(AUXLIBS_CDB)
+
+$(LIB_PREFIX)lmdb$(LIB_SUFFIX): mkmap_lmdb.o $(LIB_DIR)/dict_lmdb.o \
+ $(LIB_DIR)/slmdb.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_lmdb.o $(LIB_DIR)/dict_lmdb.o \
+ $(LIB_DIR)/slmdb.o $(AUXLIBS_LMDB)
+
+$(LIB_PREFIX)sdbm$(LIB_SUFFIX): mkmap_sdbm.o $(LIB_DIR)/dict_sdbm.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_sdbm.o \
+ $(LIB_DIR)/dict_sdbm.o $(AUXLIBS_SDBM)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+plugin_map_so_update: $(PLUGIN_MAP_SO)
+ -for i in $(PLUGIN_MAP_SO); \
+ do \
+ for type in $(DEFINED_MAP_TYPES); do \
+ case $$i in $(LIB_PREFIX)$$type$(LIB_SUFFIX)) \
+ cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+ continue 2;; \
+ esac; \
+ done; \
+ rm -f $(LIB_DIR)/$$i; \
+ done
+
+dot_lockfile: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tok822_parse: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+rec2stream: rec2stream.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+stream2rec: stream2rec.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+recdump: recdump.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+namadr_list: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+domain_list: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mynetworks: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+resolve_clnt: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+rewrite_clnt: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+quote_822_local: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+off_cvt: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mail_addr_map: mail_addr_map.c $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mail_addr_find: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+maps: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mypwd: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mail_date: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+resolve_local: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mail_addr_crunch: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+string_list: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+local_transport: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+quote_821_local: quote_821_local.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+mail_conf_time: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+mime_state: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+strip_addr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+uxtext: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+verify_clnt: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+xtext: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+anvil_clnt: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+scache: scache.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+ehlo_mask: ehlo_mask.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+valid_mailhost_addr: valid_mailhost_addr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+own_inet_addr: own_inet_addr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+header_body_checks: header_body_checks.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+data_redirect: data_redirect.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+addr_match_list: addr_match_list.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+safe_ultostr: safe_ultostr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+verify_sender_addr: verify_sender_addr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+mail_version: mail_version.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+mail_dict: mail_dict.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+server_acl: server_acl.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+mail_parm_split: mail_parm_split.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+fold_addr: fold_addr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+smtp_reply_footer: smtp_reply_footer.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+normalize_mailhost_addr: normalize_mailhost_addr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+haproxy_srvr: haproxy_srvr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+map_search: map_search.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+delivered_hdr: delivered_hdr.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+login_sender_match: login_sender_match.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+compat_level: compat_level.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+hfrom_format: hfrom_format.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
+tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
+ xtext_test scache_multi_test ehlo_mask_test \
+ namadr_list_test mail_conf_time_test header_body_checks_tests \
+ mail_version_test server_acl_test resolve_local_test maps_test \
+ safe_ultostr_test mail_parm_split_test fold_addr_test \
+ smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \
+ mail_addr_find_test mail_addr_map_test quote_822_local_test \
+ normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
+ delivered_hdr_test login_sender_match_test compat_level_test \
+ config_known_tcp_ports_test hfrom_format_test
+
+mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \
+ mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4
+
+header_body_checks_tests: header_body_checks_null_test \
+ header_body_checks_warn_test header_body_checks_prepend_test \
+ header_body_checks_ignore_test header_body_checks_replace_test \
+ header_body_checks_strip_test
+
+root_tests: rewrite_clnt_test resolve_clnt_test verify_sender_addr_test
+
+tok822_test: tok822_parse tok822_parse.in tok822_parse.ref
+ $(SHLIB_ENV) $(VALGRIND) ./tok822_parse <tok822_parse.in >tok822_parse.tmp 2>&1
+ diff tok822_parse.ref tok822_parse.tmp
+ rm -f tok822_parse.tmp
+
+mime_test: mime_state mime_test.in mime_test.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_test.in >mime_test.tmp
+ diff mime_test.ref mime_test.tmp
+ rm -f mime_test.tmp
+
+mime_nest: mime_state mime_nest.in mime_nest.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_nest.in >mime_nest.tmp
+ diff mime_nest.ref mime_nest.tmp
+ rm -f mime_nest.tmp
+
+mime_8bit: mime_state mime_8bit.in mime_8bit.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_8bit.in >mime_8bit.tmp
+ diff mime_8bit.ref mime_8bit.tmp
+ rm -f mime_8bit.tmp
+
+mime_dom: mime_state mime_dom.in mime_dom.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_dom.in >mime_dom.tmp
+ diff mime_dom.ref mime_dom.tmp
+ rm -f mime_dom.tmp
+
+mime_trunc: mime_state mime_trunc.in mime_trunc.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_trunc.in >mime_trunc.tmp
+ diff mime_trunc.ref mime_trunc.tmp
+ rm -f mime_trunc.tmp
+
+mime_cvt: mime_state mime_cvt.in mime_cvt.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_cvt.in >mime_cvt.tmp
+ diff mime_cvt.ref mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_cvt2: mime_state mime_cvt.in2 mime_cvt.ref2
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_cvt.in2 >mime_cvt.tmp
+ diff mime_cvt.ref2 mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_cvt3: mime_state mime_cvt.in3 mime_cvt.ref3
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_cvt.in3 >mime_cvt.tmp
+ diff mime_cvt.ref3 mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_garb1: mime_state mime_garb1.in mime_garb1.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_garb1.in >mime_cvt.tmp
+ diff mime_garb1.ref mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_garb2: mime_state mime_garb2.in mime_garb2.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_garb2.in >mime_cvt.tmp
+ diff mime_garb2.ref mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_garb3: mime_state mime_garb3.in mime_garb3.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_garb3.in >mime_cvt.tmp
+ diff mime_garb3.ref mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+mime_garb4: mime_state mime_garb4.in mime_garb4.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mime_state <mime_garb4.in >mime_cvt.tmp
+ diff mime_garb4.ref mime_cvt.tmp
+ rm -f mime_cvt.tmp
+
+tok822_limit_test: tok822_parse tok822_limit.in tok822_limit.ref
+ $(SHLIB_ENV) $(VALGRIND) ./tok822_parse <tok822_limit.in >tok822_limit.tmp
+ diff tok822_limit.ref tok822_limit.tmp
+ rm -f tok822_limit.tmp
+
+strip_addr_test: strip_addr strip_addr.ref
+ $(SHLIB_ENV) $(VALGRIND) ./strip_addr 2>strip_addr.tmp
+ diff strip_addr.ref strip_addr.tmp
+ rm -f strip_addr.tmp
+
+xtext_test: xtext
+ $(SHLIB_ENV) $(VALGRIND) ./xtext <xtext.c | od -cb >xtext.tmp
+ od -cb <xtext.c >xtext.ref
+ cmp xtext.ref xtext.tmp
+ rm -f xtext.ref xtext.tmp
+
+mail_version_test: mail_version mail_version.in mail_version.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mail_version <mail_version.in >mail_version.tmp
+ diff mail_version.ref mail_version.tmp
+ rm -f mail_version.tmp
+
+server_acl_test: server_acl server_acl.in server_acl.ref
+ $(SHLIB_ENV) $(VALGRIND) ./server_acl <server_acl.in >server_acl.tmp 2>&1
+ diff server_acl.ref server_acl.tmp
+ rm -f server_acl.tmp
+
+resolve_local_test: resolve_local resolve_local.in resolve_local.ref
+ $(SHLIB_ENV) sh resolve_local.in >resolve_local.tmp 2>&1
+ diff resolve_local.ref resolve_local.tmp
+ rm -f resolve_local.tmp
+
+maps_test: maps maps.in maps.ref
+ $(SHLIB_ENV) sh maps.in >maps.tmp 2>&1
+ diff maps.ref maps.tmp
+ rm -f maps.tmp
+
+surrogate_test: mail_dict surrogate.ref
+ cp /dev/null surrogate.tmp
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict ldap:/xx write >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict ldap:/xx read >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict mysql:/xx write >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict mysql:/xx read >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict pgsql:/xx write >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict pgsql:/xx read >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict sqlite:/xx write >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict sqlite:/xx read >>surrogate.tmp 2>&1
+ echo get foo| $(SHLIB_ENV) $(VALGRIND) ./mail_dict memcache:/xx read >>surrogate.tmp 2>&1
+ diff surrogate.ref surrogate.tmp
+ rm -f surrogate.tmp
+
+# Requires: Postfix running, root privileges
+
+rewrite_clnt_test: rewrite_clnt rewrite_clnt.in rewrite_clnt.ref
+ @set -- `id`; case "$$1" in \
+ *"(root)") ;; \
+ *) echo 'This test requires root privilege'; exit 1;; \
+ esac
+ @test -n "`postconf -h remote_header_rewrite_domain`" || { \
+ echo 'This test requires non-empty remote_header_rewrite_domain'; exit 1; }
+ $(SHLIB_ENV) $(VALGRIND) ./rewrite_clnt <rewrite_clnt.in >rewrite_clnt.tmp
+ sed -e "s/MYDOMAIN/`postconf -h mydomain`/" \
+ -e "s/INVALID_DOMAIN/`postconf -h remote_header_rewrite_domain`/" \
+ rewrite_clnt.ref | diff - rewrite_clnt.tmp
+ rm -f rewrite_clnt.tmp
+
+# Requires: Postfix, root, relayhost=$mydomain, no transport map
+
+resolve_clnt_test: resolve_clnt resolve_clnt.in resolve_clnt.ref
+ @set -- `id`; case "$$1" in \
+ *"(root)") ;; \
+ *) echo 'This test requires root privilege'; exit 1;; \
+ esac
+ @test "`postconf -h relayhost`" = '$$mydomain' || { \
+ echo 'This test requires relayhost=$$mydomain'; exit 1; }
+ @test "`postconf -h transport_maps`" = "" || { \
+ echo 'This test requires no transport map'; exit 1; }
+ sed -e "s/MYDOMAIN/`postconf -h mydomain`/g" \
+ -e "s/MYHOSTNAME/`postconf -h myhostname`/g" \
+ resolve_clnt.in | $(SHLIB_ENV) $(VALGRIND) ./resolve_clnt >resolve_clnt.tmp
+ sed -e "s/MYDOMAIN/`postconf -h mydomain`/g" \
+ -e "s/MYHOSTNAME/`postconf -h myhostname`/g" \
+ -e "s/RELAYHOST/`postconf -h mydomain`/g" \
+ resolve_clnt.ref | diff - resolve_clnt.tmp
+ rm -f resolve_clnt.tmp
+
+# Requires: Postfix, root, append_dot_mydomain=yes
+
+verify_sender_addr_test: verify_sender_addr verify_sender_addr.ref
+ @set -- `id`; case "$$1" in \
+ *"(root)") ;; \
+ *) echo 'This test requires root privilege'; exit 1;; \
+ esac
+ @test "X`postconf -h append_dot_mydomain`" = Xyes || { \
+ echo 'This test requires append_dot_mydomain=yes'; exit 1; }
+ ($(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr aa@bb 0; \
+ $(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr aa@bb 1; \
+ $(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr aa 0; \
+ $(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr aa 1; \
+ $(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr '' 0; \
+ $(SHLIB_ENV) $(VALGRIND) ./verify_sender_addr '' 1) | \
+ sed 's/[A-Z0-9][A-Z0-9]*@/STAMP@/' > verify_sender_addr.tmp
+ sed -e "s/MYDOMAIN/`postconf -h mydomain`/g" \
+ -e "s/MYORIGIN/`postconf -h myorigin`/g" \
+ -e "s/@.myhostname/@`postconf -h myhostname`/g" \
+ -e "s/@.mydomain/@`postconf -h mydomain`/g" \
+ -e "s;CONFIGDIR;`postconf -h config_directory`;" \
+ verify_sender_addr.ref | diff - verify_sender_addr.tmp
+ rm -f verify_sender_addr.tmp
+
+scache_multi_test: scache scache_multi.in scache_multi.ref
+ $(SHLIB_ENV) $(VALGRIND) ./scache <scache_multi.in >scache_multi.tmp
+ diff scache_multi.ref scache_multi.tmp
+ rm -f scache_multi.tmp
+
+ehlo_mask_test: ehlo_mask ehlo_mask.in ehlo_mask.ref
+ $(SHLIB_ENV) $(VALGRIND) ./ehlo_mask <ehlo_mask.in >ehlo_mask.tmp
+ diff ehlo_mask.ref ehlo_mask.tmp
+ rm -f ehlo_mask.tmp
+
+namadr_list_test: namadr_list namadr_list.in namadr_list.ref
+ -$(SHLIB_ENV) sh namadr_list.in >namadr_list.tmp 2>&1
+ diff namadr_list.ref namadr_list.tmp
+ rm -f namadr_list.tmp
+
+mail_conf_time_test: mail_conf_time mail_conf_time.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mail_conf_time >mail_conf_time.tmp
+ diff mail_conf_time.ref mail_conf_time.tmp
+ rm -f mail_conf_time.tmp
+
+safe_ultostr_test: safe_ultostr safe_ultostr.in safe_ultostr.ref
+ $(SHLIB_ENV) $(VALGRIND) ./safe_ultostr <safe_ultostr.in >safe_ultostr.tmp 2>&1
+ diff safe_ultostr.ref safe_ultostr.tmp
+ rm -f safe_ultostr.tmp
+
+header_body_checks_null_test: header_body_checks header_body_checks_null.ref
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks "" "" "" "" \
+ <mime_test.in >header_body_checks_null.tmp 2>&1
+ cmp header_body_checks_null.ref header_body_checks_null.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks static:dunno static:dunno static:dunno static:dunno \
+ <mime_test.in >header_body_checks_null.tmp 2>&1
+ cmp header_body_checks_null.ref header_body_checks_null.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks static:ok static:ok static:ok static:ok \
+ <mime_test.in >header_body_checks_null.tmp 2>&1
+ cmp header_body_checks_null.ref header_body_checks_null.tmp
+ rm -f header_body_checks_null.tmp
+
+header_body_checks_warn_test: header_body_checks header_body_checks_warn.ref
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks static:warn static:warn static:warn static:warn \
+ <mime_test.in >header_body_checks_warn.tmp 2>&1
+ cmp header_body_checks_warn.ref header_body_checks_warn.tmp
+ rm -f header_body_checks_warn.tmp
+
+header_body_checks_prepend_test: header_body_checks header_body_checks_prepend.ref
+ echo /./ prepend header: head >header_body_checks_head
+ echo /./ prepend header: mime >header_body_checks_mime
+ echo /./ prepend header: nest >header_body_checks_nest
+ echo /./ prepend body >header_body_checks_body
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks regexp:header_body_checks_head regexp:header_body_checks_mime \
+ regexp:header_body_checks_nest regexp:header_body_checks_body \
+ <mime_test.in >header_body_checks_prepend.tmp 2>&1
+ cmp header_body_checks_prepend.ref header_body_checks_prepend.tmp
+ rm -f header_body_checks_prepend.tmp header_body_checks_head header_body_checks_mime header_body_checks_nest header_body_checks_body
+
+# Note: the IGNORE action will not strip empty lines. Postfix maps
+# currently never see null query strings because some map types raise
+# errors. We can eliminate this restriction by allowing individual
+# map types to advertise whether they can handle null queries.
+header_body_checks_ignore_test: header_body_checks header_body_checks_ignore.ref
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks static:ignore static:ignore static:ignore static:ignore \
+ <mime_test.in >header_body_checks_ignore.tmp 2>&1
+ cmp header_body_checks_ignore.ref header_body_checks_ignore.tmp
+ rm -f header_body_checks_ignore.tmp header_body_checks_head header_body_checks_mime header_body_checks_nest header_body_checks_body
+
+header_body_checks_replace_test: header_body_checks header_body_checks_replace.ref
+ echo /./ replace header: head >header_body_checks_head
+ echo /./ replace header: mime >header_body_checks_mime
+ echo /./ replace header: nest >header_body_checks_nest
+ echo /./ replace body >header_body_checks_body
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks regexp:header_body_checks_head regexp:header_body_checks_mime \
+ regexp:header_body_checks_nest regexp:header_body_checks_body \
+ <mime_test.in >header_body_checks_replace.tmp 2>&1
+ cmp header_body_checks_replace.ref header_body_checks_replace.tmp
+ rm -f header_body_checks_replace.tmp header_body_checks_head header_body_checks_mime header_body_checks_nest header_body_checks_body
+
+header_body_checks_strip_test: header_body_checks header_body_checks_strip.ref
+ echo /./ strip header line >header_body_checks_head
+ echo /./ strip mime header line >header_body_checks_mime
+ echo /./ strip nested header >header_body_checks_nest
+ echo /./ strip body line >header_body_checks_body
+ $(SHLIB_ENV) $(VALGRIND) ./header_body_checks regexp:header_body_checks_head regexp:header_body_checks_mime \
+ regexp:header_body_checks_nest regexp:header_body_checks_body \
+ <mime_test.in >header_body_checks_strip.tmp 2>&1
+ cmp header_body_checks_strip.ref header_body_checks_strip.tmp
+ rm -f header_body_checks_strip.tmp header_body_checks_head header_body_checks_mime header_body_checks_nest header_body_checks_body
+
+mail_parm_split_test: mail_parm_split mail_parm_split.in mail_parm_split.ref
+ $(SHLIB_ENV) $(VALGRIND) ./mail_parm_split <mail_parm_split.in >mail_parm_split.tmp 2>&1
+ diff mail_parm_split.ref mail_parm_split.tmp
+ rm -f mail_parm_split.tmp
+
+fold_addr_test: fold_addr fold_addr_test.in fold_addr_test.ref
+ $(SHLIB_ENV) $(VALGRIND) ./fold_addr <fold_addr_test.in >fold_addr_test.tmp 2>&1
+ diff fold_addr_test.ref fold_addr_test.tmp
+ rm -f fold_addr_test.tmp
+
+smtp_reply_footer_test: smtp_reply_footer smtp_reply_footer.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtp_reply_footer >smtp_reply_footer.tmp 2>&1
+ diff smtp_reply_footer.ref smtp_reply_footer.tmp
+ rm -f smtp_reply_footer.tmp
+
+off_cvt_test: off_cvt off_cvt.in off_cvt.ref
+ $(SHLIB_ENV) $(VALGRIND) ./off_cvt <off_cvt.in >off_cvt.tmp 2>&1
+ diff off_cvt.ref off_cvt.tmp
+ rm -f off_cvt.tmp
+
+mail_addr_crunch_test: update mail_addr_crunch mail_addr_crunch.in mail_addr_crunch.ref
+ -$(SHLIB_ENV) sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1
+ diff mail_addr_crunch.ref mail_addr_crunch.tmp
+ rm -f mail_addr_crunch.tmp
+
+mail_addr_find_test: update mail_addr_find mail_addr_find.in mail_addr_find.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_find <mail_addr_find.in >mail_addr_find.tmp 2>&1
+ diff mail_addr_find.ref mail_addr_find.tmp
+ rm -f mail_addr_find.tmp
+
+mail_addr_map_test: update mail_addr_map mail_addr_map.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_map pass_tests
+ -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_map fail_tests >mail_addr_map.tmp 2>&1
+ diff mail_addr_map.ref mail_addr_map.tmp
+ rm -f mail_addr_map.tmp
+
+quote_822_local_test: update quote_822_local quote_822_local.in quote_822_local.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./quote_822_local < quote_822_local.in >quote_822_local.tmp 2>&1
+ diff quote_822_local.ref quote_822_local.tmp
+ rm -f quote_822_local.tmp
+
+normalize_mailhost_addr_test: update normalize_mailhost_addr
+ -$(SHLIB_ENV) $(VALGRIND) ./normalize_mailhost_addr >normalize_mailhost_addr.tmp 2>&1
+ diff /dev/null normalize_mailhost_addr.tmp
+ rm -f normalize_mailhost_addr.tmp
+
+haproxy_srvr_test: update haproxy_srvr
+ -$(SHLIB_ENV) $(VALGRIND) ./haproxy_srvr >haproxy_srvr.tmp 2>&1
+ diff /dev/null haproxy_srvr.tmp
+ rm -f haproxy_srvr.tmp
+
+map_search_test: update map_search map_search.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./map_search >map_search.tmp 2>&1
+ diff map_search.ref map_search.tmp
+ rm -f map_search.tmp
+
+delivered_hdr_test: update delivered_hdr delivered_hdr.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./delivered_hdr >delivered_hdr.tmp 2>&1
+ diff delivered_hdr.ref delivered_hdr.tmp
+ rm -f delivered_hdr.tmp
+
+login_sender_match_test: update login_sender_match login_sender_match.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./login_sender_match >login_sender_match.tmp 2>&1
+ diff login_sender_match.ref login_sender_match.tmp
+ rm -f login_sender_match.tmp
+
+compat_level_test: compat_level_expand_test compat_level_convert_test
+
+compat_level_expand_test: update compat_level compat_level_expand.in \
+ compat_level_expand.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./compat_level -x <compat_level_expand.in >compat_level_expand.tmp 2>&1
+ diff compat_level_expand.ref compat_level_expand.tmp
+ rm -f compat_level_expand.tmp
+
+compat_level_convert_test: update compat_level compat_level_convert.in \
+ compat_level_convert.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./compat_level -c <compat_level_convert.in >compat_level_convert.tmp 2>&1
+ diff compat_level_convert.ref compat_level_convert.tmp
+ rm -f compat_level_convert.tmp
+
+config_known_tcp_ports_test: update config_known_tcp_ports \
+ config_known_tcp_ports.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./config_known_tcp_ports \
+ >config_known_tcp_ports.tmp 2>&1
+ diff config_known_tcp_ports.ref config_known_tcp_ports.tmp
+ rm -f config_known_tcp_ports.tmp
+
+hfrom_format_test: update hfrom_format \
+ hfrom_format.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./hfrom_format \
+ >hfrom_format.tmp 2>&1
+ diff hfrom_format.ref hfrom_format.tmp
+ rm -f hfrom_format.tmp
+
+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 $(LIB) *core $(TESTPROG) junk $(MAPS)
+ rm -rf printfck
+
+tidy: clean
+
+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'
+abounce.o: ../../include/attr.h
+abounce.o: ../../include/check_arg.h
+abounce.o: ../../include/events.h
+abounce.o: ../../include/htable.h
+abounce.o: ../../include/iostuff.h
+abounce.o: ../../include/msg.h
+abounce.o: ../../include/mymalloc.h
+abounce.o: ../../include/nvtable.h
+abounce.o: ../../include/sys_defs.h
+abounce.o: ../../include/vbuf.h
+abounce.o: ../../include/vstream.h
+abounce.o: ../../include/vstring.h
+abounce.o: abounce.c
+abounce.o: abounce.h
+abounce.o: bounce.h
+abounce.o: deliver_request.h
+abounce.o: dsn.h
+abounce.o: dsn_buf.h
+abounce.o: mail_params.h
+abounce.o: mail_proto.h
+abounce.o: msg_stats.h
+abounce.o: recipient_list.h
+addr_match_list.o: ../../include/argv.h
+addr_match_list.o: ../../include/check_arg.h
+addr_match_list.o: ../../include/match_list.h
+addr_match_list.o: ../../include/sys_defs.h
+addr_match_list.o: ../../include/vbuf.h
+addr_match_list.o: ../../include/vstring.h
+addr_match_list.o: addr_match_list.c
+addr_match_list.o: addr_match_list.h
+anvil_clnt.o: ../../include/attr.h
+anvil_clnt.o: ../../include/attr_clnt.h
+anvil_clnt.o: ../../include/check_arg.h
+anvil_clnt.o: ../../include/htable.h
+anvil_clnt.o: ../../include/iostuff.h
+anvil_clnt.o: ../../include/msg.h
+anvil_clnt.o: ../../include/mymalloc.h
+anvil_clnt.o: ../../include/nvtable.h
+anvil_clnt.o: ../../include/stringops.h
+anvil_clnt.o: ../../include/sys_defs.h
+anvil_clnt.o: ../../include/vbuf.h
+anvil_clnt.o: ../../include/vstream.h
+anvil_clnt.o: ../../include/vstring.h
+anvil_clnt.o: anvil_clnt.c
+anvil_clnt.o: anvil_clnt.h
+anvil_clnt.o: mail_params.h
+anvil_clnt.o: mail_proto.h
+attr_override.o: ../../include/check_arg.h
+attr_override.o: ../../include/msg.h
+attr_override.o: ../../include/stringops.h
+attr_override.o: ../../include/sys_defs.h
+attr_override.o: ../../include/vbuf.h
+attr_override.o: ../../include/vstring.h
+attr_override.o: attr_override.c
+attr_override.o: attr_override.h
+attr_override.o: conv_time.h
+attr_override.o: mail_conf.h
+been_here.o: ../../include/check_arg.h
+been_here.o: ../../include/htable.h
+been_here.o: ../../include/msg.h
+been_here.o: ../../include/mymalloc.h
+been_here.o: ../../include/stringops.h
+been_here.o: ../../include/sys_defs.h
+been_here.o: ../../include/vbuf.h
+been_here.o: ../../include/vstring.h
+been_here.o: been_here.c
+been_here.o: been_here.h
+bounce.o: ../../include/attr.h
+bounce.o: ../../include/check_arg.h
+bounce.o: ../../include/htable.h
+bounce.o: ../../include/iostuff.h
+bounce.o: ../../include/msg.h
+bounce.o: ../../include/mymalloc.h
+bounce.o: ../../include/nvtable.h
+bounce.o: ../../include/sys_defs.h
+bounce.o: ../../include/vbuf.h
+bounce.o: ../../include/vstream.h
+bounce.o: ../../include/vstring.h
+bounce.o: bounce.c
+bounce.o: bounce.h
+bounce.o: defer.h
+bounce.o: deliver_request.h
+bounce.o: dsn.h
+bounce.o: dsn_buf.h
+bounce.o: dsn_filter.h
+bounce.o: dsn_print.h
+bounce.o: dsn_util.h
+bounce.o: log_adhoc.h
+bounce.o: mail_params.h
+bounce.o: mail_proto.h
+bounce.o: msg_stats.h
+bounce.o: rcpt_print.h
+bounce.o: recipient_list.h
+bounce.o: trace.h
+bounce.o: verify.h
+bounce_log.o: ../../include/attr.h
+bounce_log.o: ../../include/check_arg.h
+bounce_log.o: ../../include/htable.h
+bounce_log.o: ../../include/iostuff.h
+bounce_log.o: ../../include/msg.h
+bounce_log.o: ../../include/mymalloc.h
+bounce_log.o: ../../include/nvtable.h
+bounce_log.o: ../../include/stringops.h
+bounce_log.o: ../../include/sys_defs.h
+bounce_log.o: ../../include/vbuf.h
+bounce_log.o: ../../include/vstream.h
+bounce_log.o: ../../include/vstring.h
+bounce_log.o: ../../include/vstring_vstream.h
+bounce_log.o: bounce_log.c
+bounce_log.o: bounce_log.h
+bounce_log.o: dsn.h
+bounce_log.o: dsn_buf.h
+bounce_log.o: dsn_mask.h
+bounce_log.o: mail_params.h
+bounce_log.o: mail_proto.h
+bounce_log.o: mail_queue.h
+bounce_log.o: rcpt_buf.h
+bounce_log.o: recipient_list.h
+canon_addr.o: ../../include/attr.h
+canon_addr.o: ../../include/check_arg.h
+canon_addr.o: ../../include/htable.h
+canon_addr.o: ../../include/iostuff.h
+canon_addr.o: ../../include/mymalloc.h
+canon_addr.o: ../../include/nvtable.h
+canon_addr.o: ../../include/sys_defs.h
+canon_addr.o: ../../include/vbuf.h
+canon_addr.o: ../../include/vstream.h
+canon_addr.o: ../../include/vstring.h
+canon_addr.o: canon_addr.c
+canon_addr.o: canon_addr.h
+canon_addr.o: mail_proto.h
+canon_addr.o: rewrite_clnt.h
+cfg_parser.o: ../../include/argv.h
+cfg_parser.o: ../../include/check_arg.h
+cfg_parser.o: ../../include/dict.h
+cfg_parser.o: ../../include/msg.h
+cfg_parser.o: ../../include/myflock.h
+cfg_parser.o: ../../include/mymalloc.h
+cfg_parser.o: ../../include/sys_defs.h
+cfg_parser.o: ../../include/vbuf.h
+cfg_parser.o: ../../include/vstream.h
+cfg_parser.o: ../../include/vstring.h
+cfg_parser.o: cfg_parser.c
+cfg_parser.o: cfg_parser.h
+cfg_parser.o: mail_conf.h
+cleanup_strerror.o: ../../include/check_arg.h
+cleanup_strerror.o: ../../include/msg.h
+cleanup_strerror.o: ../../include/sys_defs.h
+cleanup_strerror.o: ../../include/vbuf.h
+cleanup_strerror.o: ../../include/vstring.h
+cleanup_strerror.o: cleanup_strerror.c
+cleanup_strerror.o: cleanup_user.h
+cleanup_strflags.o: ../../include/check_arg.h
+cleanup_strflags.o: ../../include/msg.h
+cleanup_strflags.o: ../../include/sys_defs.h
+cleanup_strflags.o: ../../include/vbuf.h
+cleanup_strflags.o: ../../include/vstring.h
+cleanup_strflags.o: cleanup_strflags.c
+cleanup_strflags.o: cleanup_user.h
+clnt_stream.o: ../../include/attr.h
+clnt_stream.o: ../../include/check_arg.h
+clnt_stream.o: ../../include/events.h
+clnt_stream.o: ../../include/htable.h
+clnt_stream.o: ../../include/iostuff.h
+clnt_stream.o: ../../include/msg.h
+clnt_stream.o: ../../include/mymalloc.h
+clnt_stream.o: ../../include/nvtable.h
+clnt_stream.o: ../../include/sys_defs.h
+clnt_stream.o: ../../include/vbuf.h
+clnt_stream.o: ../../include/vstream.h
+clnt_stream.o: ../../include/vstring.h
+clnt_stream.o: clnt_stream.c
+clnt_stream.o: clnt_stream.h
+clnt_stream.o: mail_params.h
+clnt_stream.o: mail_proto.h
+compat_level.o: ../../include/check_arg.h
+compat_level.o: ../../include/mac_expand.h
+compat_level.o: ../../include/mac_parse.h
+compat_level.o: ../../include/msg.h
+compat_level.o: ../../include/sane_strtol.h
+compat_level.o: ../../include/sys_defs.h
+compat_level.o: ../../include/vbuf.h
+compat_level.o: ../../include/vstring.h
+compat_level.o: compat_level.c
+compat_level.o: compat_level.h
+config_known_tcp_ports.o: ../../include/argv.h
+config_known_tcp_ports.o: ../../include/check_arg.h
+config_known_tcp_ports.o: ../../include/known_tcp_ports.h
+config_known_tcp_ports.o: ../../include/msg.h
+config_known_tcp_ports.o: ../../include/mymalloc.h
+config_known_tcp_ports.o: ../../include/stringops.h
+config_known_tcp_ports.o: ../../include/sys_defs.h
+config_known_tcp_ports.o: ../../include/vbuf.h
+config_known_tcp_ports.o: ../../include/vstring.h
+config_known_tcp_ports.o: config_known_tcp_ports.c
+config_known_tcp_ports.o: config_known_tcp_ports.h
+conv_time.o: ../../include/msg.h
+conv_time.o: ../../include/sys_defs.h
+conv_time.o: conv_time.c
+conv_time.o: conv_time.h
+data_redirect.o: ../../include/argv.h
+data_redirect.o: ../../include/check_arg.h
+data_redirect.o: ../../include/dict.h
+data_redirect.o: ../../include/dict_cdb.h
+data_redirect.o: ../../include/dict_db.h
+data_redirect.o: ../../include/dict_dbm.h
+data_redirect.o: ../../include/dict_lmdb.h
+data_redirect.o: ../../include/msg.h
+data_redirect.o: ../../include/myflock.h
+data_redirect.o: ../../include/name_code.h
+data_redirect.o: ../../include/split_at.h
+data_redirect.o: ../../include/stringops.h
+data_redirect.o: ../../include/sys_defs.h
+data_redirect.o: ../../include/vbuf.h
+data_redirect.o: ../../include/vstream.h
+data_redirect.o: ../../include/vstring.h
+data_redirect.o: ../../include/warn_stat.h
+data_redirect.o: data_redirect.c
+data_redirect.o: data_redirect.h
+data_redirect.o: dict_proxy.h
+data_redirect.o: mail_params.h
+db_common.o: ../../include/argv.h
+db_common.o: ../../include/check_arg.h
+db_common.o: ../../include/dict.h
+db_common.o: ../../include/match_list.h
+db_common.o: ../../include/msg.h
+db_common.o: ../../include/myflock.h
+db_common.o: ../../include/mymalloc.h
+db_common.o: ../../include/sys_defs.h
+db_common.o: ../../include/vbuf.h
+db_common.o: ../../include/vstream.h
+db_common.o: ../../include/vstring.h
+db_common.o: cfg_parser.h
+db_common.o: db_common.c
+db_common.o: db_common.h
+db_common.o: string_list.h
+debug_peer.o: ../../include/argv.h
+debug_peer.o: ../../include/check_arg.h
+debug_peer.o: ../../include/match_list.h
+debug_peer.o: ../../include/msg.h
+debug_peer.o: ../../include/sys_defs.h
+debug_peer.o: ../../include/vbuf.h
+debug_peer.o: ../../include/vstring.h
+debug_peer.o: debug_peer.c
+debug_peer.o: debug_peer.h
+debug_peer.o: mail_params.h
+debug_peer.o: match_parent_style.h
+debug_peer.o: namadr_list.h
+debug_process.o: ../../include/msg.h
+debug_process.o: ../../include/sys_defs.h
+debug_process.o: debug_process.c
+debug_process.o: debug_process.h
+debug_process.o: mail_conf.h
+debug_process.o: mail_params.h
+defer.o: ../../include/attr.h
+defer.o: ../../include/check_arg.h
+defer.o: ../../include/htable.h
+defer.o: ../../include/iostuff.h
+defer.o: ../../include/msg.h
+defer.o: ../../include/mymalloc.h
+defer.o: ../../include/nvtable.h
+defer.o: ../../include/sys_defs.h
+defer.o: ../../include/vbuf.h
+defer.o: ../../include/vstream.h
+defer.o: ../../include/vstring.h
+defer.o: bounce.h
+defer.o: defer.c
+defer.o: defer.h
+defer.o: deliver_request.h
+defer.o: dsn.h
+defer.o: dsn_buf.h
+defer.o: dsn_filter.h
+defer.o: dsn_print.h
+defer.o: dsn_util.h
+defer.o: flush_clnt.h
+defer.o: log_adhoc.h
+defer.o: mail_params.h
+defer.o: mail_proto.h
+defer.o: mail_queue.h
+defer.o: msg_stats.h
+defer.o: rcpt_print.h
+defer.o: recipient_list.h
+defer.o: trace.h
+defer.o: verify.h
+deliver_completed.o: ../../include/check_arg.h
+deliver_completed.o: ../../include/msg.h
+deliver_completed.o: ../../include/sys_defs.h
+deliver_completed.o: ../../include/vbuf.h
+deliver_completed.o: ../../include/vstream.h
+deliver_completed.o: ../../include/vstring.h
+deliver_completed.o: deliver_completed.c
+deliver_completed.o: deliver_completed.h
+deliver_completed.o: rec_type.h
+deliver_completed.o: record.h
+deliver_flock.o: ../../include/check_arg.h
+deliver_flock.o: ../../include/iostuff.h
+deliver_flock.o: ../../include/myflock.h
+deliver_flock.o: ../../include/sys_defs.h
+deliver_flock.o: ../../include/vbuf.h
+deliver_flock.o: ../../include/vstring.h
+deliver_flock.o: deliver_flock.c
+deliver_flock.o: deliver_flock.h
+deliver_flock.o: mail_params.h
+deliver_pass.o: ../../include/attr.h
+deliver_pass.o: ../../include/check_arg.h
+deliver_pass.o: ../../include/htable.h
+deliver_pass.o: ../../include/iostuff.h
+deliver_pass.o: ../../include/msg.h
+deliver_pass.o: ../../include/mymalloc.h
+deliver_pass.o: ../../include/nvtable.h
+deliver_pass.o: ../../include/split_at.h
+deliver_pass.o: ../../include/sys_defs.h
+deliver_pass.o: ../../include/vbuf.h
+deliver_pass.o: ../../include/vstream.h
+deliver_pass.o: ../../include/vstring.h
+deliver_pass.o: bounce.h
+deliver_pass.o: defer.h
+deliver_pass.o: deliver_pass.c
+deliver_pass.o: deliver_pass.h
+deliver_pass.o: deliver_request.h
+deliver_pass.o: dsb_scan.h
+deliver_pass.o: dsn.h
+deliver_pass.o: dsn_buf.h
+deliver_pass.o: info_log_addr_form.h
+deliver_pass.o: mail_params.h
+deliver_pass.o: mail_proto.h
+deliver_pass.o: msg_stats.h
+deliver_pass.o: rcpt_print.h
+deliver_pass.o: recipient_list.h
+deliver_request.o: ../../include/attr.h
+deliver_request.o: ../../include/check_arg.h
+deliver_request.o: ../../include/htable.h
+deliver_request.o: ../../include/iostuff.h
+deliver_request.o: ../../include/msg.h
+deliver_request.o: ../../include/myflock.h
+deliver_request.o: ../../include/mymalloc.h
+deliver_request.o: ../../include/nvtable.h
+deliver_request.o: ../../include/sys_defs.h
+deliver_request.o: ../../include/vbuf.h
+deliver_request.o: ../../include/vstream.h
+deliver_request.o: ../../include/vstring.h
+deliver_request.o: deliver_request.c
+deliver_request.o: deliver_request.h
+deliver_request.o: dsn.h
+deliver_request.o: dsn_print.h
+deliver_request.o: mail_open_ok.h
+deliver_request.o: mail_proto.h
+deliver_request.o: mail_queue.h
+deliver_request.o: msg_stats.h
+deliver_request.o: rcpt_buf.h
+deliver_request.o: recipient_list.h
+delivered_hdr.o: ../../include/check_arg.h
+delivered_hdr.o: ../../include/htable.h
+delivered_hdr.o: ../../include/msg.h
+delivered_hdr.o: ../../include/mymalloc.h
+delivered_hdr.o: ../../include/stringops.h
+delivered_hdr.o: ../../include/sys_defs.h
+delivered_hdr.o: ../../include/vbuf.h
+delivered_hdr.o: ../../include/vstream.h
+delivered_hdr.o: ../../include/vstring.h
+delivered_hdr.o: ../../include/vstring_vstream.h
+delivered_hdr.o: delivered_hdr.c
+delivered_hdr.o: delivered_hdr.h
+delivered_hdr.o: fold_addr.h
+delivered_hdr.o: header_opts.h
+delivered_hdr.o: is_header.h
+delivered_hdr.o: quote_822_local.h
+delivered_hdr.o: quote_flags.h
+delivered_hdr.o: rec_type.h
+delivered_hdr.o: record.h
+dict_ldap.o: ../../include/argv.h
+dict_ldap.o: ../../include/binhash.h
+dict_ldap.o: ../../include/check_arg.h
+dict_ldap.o: ../../include/dict.h
+dict_ldap.o: ../../include/match_list.h
+dict_ldap.o: ../../include/msg.h
+dict_ldap.o: ../../include/myflock.h
+dict_ldap.o: ../../include/mymalloc.h
+dict_ldap.o: ../../include/name_code.h
+dict_ldap.o: ../../include/stringops.h
+dict_ldap.o: ../../include/sys_defs.h
+dict_ldap.o: ../../include/vbuf.h
+dict_ldap.o: ../../include/vstream.h
+dict_ldap.o: ../../include/vstring.h
+dict_ldap.o: cfg_parser.h
+dict_ldap.o: db_common.h
+dict_ldap.o: dict_ldap.c
+dict_ldap.o: dict_ldap.h
+dict_ldap.o: mail_conf.h
+dict_ldap.o: string_list.h
+dict_memcache.o: ../../include/argv.h
+dict_memcache.o: ../../include/auto_clnt.h
+dict_memcache.o: ../../include/check_arg.h
+dict_memcache.o: ../../include/dict.h
+dict_memcache.o: ../../include/match_list.h
+dict_memcache.o: ../../include/msg.h
+dict_memcache.o: ../../include/myflock.h
+dict_memcache.o: ../../include/mymalloc.h
+dict_memcache.o: ../../include/stringops.h
+dict_memcache.o: ../../include/sys_defs.h
+dict_memcache.o: ../../include/vbuf.h
+dict_memcache.o: ../../include/vstream.h
+dict_memcache.o: ../../include/vstring.h
+dict_memcache.o: cfg_parser.h
+dict_memcache.o: db_common.h
+dict_memcache.o: dict_memcache.c
+dict_memcache.o: dict_memcache.h
+dict_memcache.o: memcache_proto.h
+dict_memcache.o: string_list.h
+dict_mysql.o: ../../include/argv.h
+dict_mysql.o: ../../include/check_arg.h
+dict_mysql.o: ../../include/dict.h
+dict_mysql.o: ../../include/events.h
+dict_mysql.o: ../../include/find_inet.h
+dict_mysql.o: ../../include/match_list.h
+dict_mysql.o: ../../include/msg.h
+dict_mysql.o: ../../include/myflock.h
+dict_mysql.o: ../../include/mymalloc.h
+dict_mysql.o: ../../include/myrand.h
+dict_mysql.o: ../../include/split_at.h
+dict_mysql.o: ../../include/stringops.h
+dict_mysql.o: ../../include/sys_defs.h
+dict_mysql.o: ../../include/vbuf.h
+dict_mysql.o: ../../include/vstream.h
+dict_mysql.o: ../../include/vstring.h
+dict_mysql.o: cfg_parser.h
+dict_mysql.o: db_common.h
+dict_mysql.o: dict_mysql.c
+dict_mysql.o: dict_mysql.h
+dict_mysql.o: string_list.h
+dict_pgsql.o: ../../include/argv.h
+dict_pgsql.o: ../../include/check_arg.h
+dict_pgsql.o: ../../include/dict.h
+dict_pgsql.o: ../../include/events.h
+dict_pgsql.o: ../../include/match_list.h
+dict_pgsql.o: ../../include/msg.h
+dict_pgsql.o: ../../include/myflock.h
+dict_pgsql.o: ../../include/mymalloc.h
+dict_pgsql.o: ../../include/myrand.h
+dict_pgsql.o: ../../include/split_at.h
+dict_pgsql.o: ../../include/stringops.h
+dict_pgsql.o: ../../include/sys_defs.h
+dict_pgsql.o: ../../include/vbuf.h
+dict_pgsql.o: ../../include/vstream.h
+dict_pgsql.o: ../../include/vstring.h
+dict_pgsql.o: cfg_parser.h
+dict_pgsql.o: db_common.h
+dict_pgsql.o: dict_pgsql.c
+dict_pgsql.o: dict_pgsql.h
+dict_pgsql.o: string_list.h
+dict_proxy.o: ../../include/argv.h
+dict_proxy.o: ../../include/attr.h
+dict_proxy.o: ../../include/check_arg.h
+dict_proxy.o: ../../include/dict.h
+dict_proxy.o: ../../include/htable.h
+dict_proxy.o: ../../include/iostuff.h
+dict_proxy.o: ../../include/msg.h
+dict_proxy.o: ../../include/myflock.h
+dict_proxy.o: ../../include/mymalloc.h
+dict_proxy.o: ../../include/nvtable.h
+dict_proxy.o: ../../include/stringops.h
+dict_proxy.o: ../../include/sys_defs.h
+dict_proxy.o: ../../include/vbuf.h
+dict_proxy.o: ../../include/vstream.h
+dict_proxy.o: ../../include/vstring.h
+dict_proxy.o: clnt_stream.h
+dict_proxy.o: dict_proxy.c
+dict_proxy.o: dict_proxy.h
+dict_proxy.o: mail_params.h
+dict_proxy.o: mail_proto.h
+dict_sqlite.o: ../../include/argv.h
+dict_sqlite.o: ../../include/check_arg.h
+dict_sqlite.o: ../../include/dict.h
+dict_sqlite.o: ../../include/match_list.h
+dict_sqlite.o: ../../include/msg.h
+dict_sqlite.o: ../../include/myflock.h
+dict_sqlite.o: ../../include/mymalloc.h
+dict_sqlite.o: ../../include/stringops.h
+dict_sqlite.o: ../../include/sys_defs.h
+dict_sqlite.o: ../../include/vbuf.h
+dict_sqlite.o: ../../include/vstream.h
+dict_sqlite.o: ../../include/vstring.h
+dict_sqlite.o: cfg_parser.h
+dict_sqlite.o: db_common.h
+dict_sqlite.o: dict_sqlite.c
+dict_sqlite.o: dict_sqlite.h
+dict_sqlite.o: string_list.h
+domain_list.o: ../../include/argv.h
+domain_list.o: ../../include/check_arg.h
+domain_list.o: ../../include/match_list.h
+domain_list.o: ../../include/sys_defs.h
+domain_list.o: ../../include/vbuf.h
+domain_list.o: ../../include/vstring.h
+domain_list.o: domain_list.c
+domain_list.o: domain_list.h
+dot_lockfile.o: ../../include/check_arg.h
+dot_lockfile.o: ../../include/iostuff.h
+dot_lockfile.o: ../../include/mymalloc.h
+dot_lockfile.o: ../../include/stringops.h
+dot_lockfile.o: ../../include/sys_defs.h
+dot_lockfile.o: ../../include/vbuf.h
+dot_lockfile.o: ../../include/vstring.h
+dot_lockfile.o: ../../include/warn_stat.h
+dot_lockfile.o: dot_lockfile.c
+dot_lockfile.o: dot_lockfile.h
+dot_lockfile.o: mail_params.h
+dot_lockfile_as.o: ../../include/check_arg.h
+dot_lockfile_as.o: ../../include/msg.h
+dot_lockfile_as.o: ../../include/set_eugid.h
+dot_lockfile_as.o: ../../include/sys_defs.h
+dot_lockfile_as.o: ../../include/vbuf.h
+dot_lockfile_as.o: ../../include/vstring.h
+dot_lockfile_as.o: dot_lockfile.h
+dot_lockfile_as.o: dot_lockfile_as.c
+dot_lockfile_as.o: dot_lockfile_as.h
+dsb_scan.o: ../../include/attr.h
+dsb_scan.o: ../../include/check_arg.h
+dsb_scan.o: ../../include/htable.h
+dsb_scan.o: ../../include/iostuff.h
+dsb_scan.o: ../../include/mymalloc.h
+dsb_scan.o: ../../include/nvtable.h
+dsb_scan.o: ../../include/sys_defs.h
+dsb_scan.o: ../../include/vbuf.h
+dsb_scan.o: ../../include/vstream.h
+dsb_scan.o: ../../include/vstring.h
+dsb_scan.o: dsb_scan.c
+dsb_scan.o: dsb_scan.h
+dsb_scan.o: dsn.h
+dsb_scan.o: dsn_buf.h
+dsb_scan.o: mail_proto.h
+dsn.o: ../../include/msg.h
+dsn.o: ../../include/mymalloc.h
+dsn.o: ../../include/sys_defs.h
+dsn.o: dsn.c
+dsn.o: dsn.h
+dsn_buf.o: ../../include/check_arg.h
+dsn_buf.o: ../../include/msg.h
+dsn_buf.o: ../../include/mymalloc.h
+dsn_buf.o: ../../include/sys_defs.h
+dsn_buf.o: ../../include/vbuf.h
+dsn_buf.o: ../../include/vstring.h
+dsn_buf.o: dsn.h
+dsn_buf.o: dsn_buf.c
+dsn_buf.o: dsn_buf.h
+dsn_filter.o: ../../include/argv.h
+dsn_filter.o: ../../include/check_arg.h
+dsn_filter.o: ../../include/dict.h
+dsn_filter.o: ../../include/msg.h
+dsn_filter.o: ../../include/myflock.h
+dsn_filter.o: ../../include/mymalloc.h
+dsn_filter.o: ../../include/sys_defs.h
+dsn_filter.o: ../../include/vbuf.h
+dsn_filter.o: ../../include/vstream.h
+dsn_filter.o: ../../include/vstring.h
+dsn_filter.o: dsn.h
+dsn_filter.o: dsn_filter.c
+dsn_filter.o: dsn_filter.h
+dsn_filter.o: dsn_util.h
+dsn_filter.o: maps.h
+dsn_mask.o: ../../include/check_arg.h
+dsn_mask.o: ../../include/msg.h
+dsn_mask.o: ../../include/name_code.h
+dsn_mask.o: ../../include/name_mask.h
+dsn_mask.o: ../../include/sys_defs.h
+dsn_mask.o: ../../include/vbuf.h
+dsn_mask.o: ../../include/vstring.h
+dsn_mask.o: dsn_mask.c
+dsn_mask.o: dsn_mask.h
+dsn_print.o: ../../include/attr.h
+dsn_print.o: ../../include/check_arg.h
+dsn_print.o: ../../include/htable.h
+dsn_print.o: ../../include/iostuff.h
+dsn_print.o: ../../include/mymalloc.h
+dsn_print.o: ../../include/nvtable.h
+dsn_print.o: ../../include/sys_defs.h
+dsn_print.o: ../../include/vbuf.h
+dsn_print.o: ../../include/vstream.h
+dsn_print.o: ../../include/vstring.h
+dsn_print.o: dsn.h
+dsn_print.o: dsn_print.c
+dsn_print.o: dsn_print.h
+dsn_print.o: mail_proto.h
+dsn_util.o: ../../include/check_arg.h
+dsn_util.o: ../../include/msg.h
+dsn_util.o: ../../include/mymalloc.h
+dsn_util.o: ../../include/stringops.h
+dsn_util.o: ../../include/sys_defs.h
+dsn_util.o: ../../include/vbuf.h
+dsn_util.o: ../../include/vstring.h
+dsn_util.o: dsn_util.c
+dsn_util.o: dsn_util.h
+dynamicmaps.o: ../../include/argv.h
+dynamicmaps.o: ../../include/check_arg.h
+dynamicmaps.o: ../../include/dict.h
+dynamicmaps.o: ../../include/htable.h
+dynamicmaps.o: ../../include/load_lib.h
+dynamicmaps.o: ../../include/msg.h
+dynamicmaps.o: ../../include/myflock.h
+dynamicmaps.o: ../../include/mymalloc.h
+dynamicmaps.o: ../../include/scan_dir.h
+dynamicmaps.o: ../../include/split_at.h
+dynamicmaps.o: ../../include/stringops.h
+dynamicmaps.o: ../../include/sys_defs.h
+dynamicmaps.o: ../../include/vbuf.h
+dynamicmaps.o: ../../include/vstream.h
+dynamicmaps.o: ../../include/vstring.h
+dynamicmaps.o: ../../include/vstring_vstream.h
+dynamicmaps.o: dynamicmaps.c
+dynamicmaps.o: dynamicmaps.h
+dynamicmaps.o: mkmap.h
+ehlo_mask.o: ../../include/check_arg.h
+ehlo_mask.o: ../../include/name_mask.h
+ehlo_mask.o: ../../include/sys_defs.h
+ehlo_mask.o: ../../include/vbuf.h
+ehlo_mask.o: ../../include/vstring.h
+ehlo_mask.o: ehlo_mask.c
+ehlo_mask.o: ehlo_mask.h
+ext_prop.o: ../../include/check_arg.h
+ext_prop.o: ../../include/name_mask.h
+ext_prop.o: ../../include/sys_defs.h
+ext_prop.o: ../../include/vbuf.h
+ext_prop.o: ../../include/vstring.h
+ext_prop.o: ext_prop.c
+ext_prop.o: ext_prop.h
+ext_prop.o: mail_params.h
+file_id.o: ../../include/check_arg.h
+file_id.o: ../../include/msg.h
+file_id.o: ../../include/sys_defs.h
+file_id.o: ../../include/vbuf.h
+file_id.o: ../../include/vstream.h
+file_id.o: ../../include/vstring.h
+file_id.o: ../../include/warn_stat.h
+file_id.o: file_id.c
+file_id.o: file_id.h
+file_id.o: mail_queue.h
+file_id.o: safe_ultostr.h
+flush_clnt.o: ../../include/argv.h
+flush_clnt.o: ../../include/attr.h
+flush_clnt.o: ../../include/check_arg.h
+flush_clnt.o: ../../include/htable.h
+flush_clnt.o: ../../include/iostuff.h
+flush_clnt.o: ../../include/match_list.h
+flush_clnt.o: ../../include/msg.h
+flush_clnt.o: ../../include/mymalloc.h
+flush_clnt.o: ../../include/nvtable.h
+flush_clnt.o: ../../include/sys_defs.h
+flush_clnt.o: ../../include/vbuf.h
+flush_clnt.o: ../../include/vstream.h
+flush_clnt.o: ../../include/vstring.h
+flush_clnt.o: domain_list.h
+flush_clnt.o: flush_clnt.c
+flush_clnt.o: flush_clnt.h
+flush_clnt.o: mail_flush.h
+flush_clnt.o: mail_params.h
+flush_clnt.o: mail_proto.h
+flush_clnt.o: match_parent_style.h
+fold_addr.o: ../../include/check_arg.h
+fold_addr.o: ../../include/stringops.h
+fold_addr.o: ../../include/sys_defs.h
+fold_addr.o: ../../include/vbuf.h
+fold_addr.o: ../../include/vstring.h
+fold_addr.o: fold_addr.c
+fold_addr.o: fold_addr.h
+haproxy_srvr.o: ../../include/check_arg.h
+haproxy_srvr.o: ../../include/inet_proto.h
+haproxy_srvr.o: ../../include/msg.h
+haproxy_srvr.o: ../../include/myaddrinfo.h
+haproxy_srvr.o: ../../include/mymalloc.h
+haproxy_srvr.o: ../../include/sock_addr.h
+haproxy_srvr.o: ../../include/split_at.h
+haproxy_srvr.o: ../../include/stringops.h
+haproxy_srvr.o: ../../include/sys_defs.h
+haproxy_srvr.o: ../../include/valid_hostname.h
+haproxy_srvr.o: ../../include/vbuf.h
+haproxy_srvr.o: ../../include/vstring.h
+haproxy_srvr.o: haproxy_srvr.c
+haproxy_srvr.o: haproxy_srvr.h
+header_body_checks.o: ../../include/argv.h
+header_body_checks.o: ../../include/check_arg.h
+header_body_checks.o: ../../include/dict.h
+header_body_checks.o: ../../include/msg.h
+header_body_checks.o: ../../include/myflock.h
+header_body_checks.o: ../../include/mymalloc.h
+header_body_checks.o: ../../include/sys_defs.h
+header_body_checks.o: ../../include/vbuf.h
+header_body_checks.o: ../../include/vstream.h
+header_body_checks.o: ../../include/vstring.h
+header_body_checks.o: cleanup_user.h
+header_body_checks.o: dsn_util.h
+header_body_checks.o: header_body_checks.c
+header_body_checks.o: header_body_checks.h
+header_body_checks.o: header_opts.h
+header_body_checks.o: is_header.h
+header_body_checks.o: maps.h
+header_body_checks.o: mime_state.h
+header_body_checks.o: rec_type.h
+header_opts.o: ../../include/argv.h
+header_opts.o: ../../include/check_arg.h
+header_opts.o: ../../include/htable.h
+header_opts.o: ../../include/msg.h
+header_opts.o: ../../include/mymalloc.h
+header_opts.o: ../../include/stringops.h
+header_opts.o: ../../include/sys_defs.h
+header_opts.o: ../../include/vbuf.h
+header_opts.o: ../../include/vstring.h
+header_opts.o: header_opts.c
+header_opts.o: header_opts.h
+header_opts.o: mail_params.h
+header_token.o: ../../include/check_arg.h
+header_token.o: ../../include/msg.h
+header_token.o: ../../include/sys_defs.h
+header_token.o: ../../include/vbuf.h
+header_token.o: ../../include/vstring.h
+header_token.o: header_token.c
+header_token.o: header_token.h
+header_token.o: lex_822.h
+hfrom_format.o: ../../include/msg.h
+hfrom_format.o: ../../include/name_code.h
+hfrom_format.o: ../../include/sys_defs.h
+hfrom_format.o: hfrom_format.c
+hfrom_format.o: hfrom_format.h
+hfrom_format.o: mail_params.h
+info_log_addr_form.o: ../../include/check_arg.h
+info_log_addr_form.o: ../../include/msg.h
+info_log_addr_form.o: ../../include/name_code.h
+info_log_addr_form.o: ../../include/sys_defs.h
+info_log_addr_form.o: ../../include/vbuf.h
+info_log_addr_form.o: ../../include/vstring.h
+info_log_addr_form.o: info_log_addr_form.c
+info_log_addr_form.o: info_log_addr_form.h
+info_log_addr_form.o: mail_addr_form.h
+info_log_addr_form.o: mail_params.h
+info_log_addr_form.o: quote_822_local.h
+info_log_addr_form.o: quote_flags.h
+input_transp.o: ../../include/check_arg.h
+input_transp.o: ../../include/msg.h
+input_transp.o: ../../include/name_mask.h
+input_transp.o: ../../include/sys_defs.h
+input_transp.o: ../../include/vbuf.h
+input_transp.o: ../../include/vstring.h
+input_transp.o: cleanup_user.h
+input_transp.o: input_transp.c
+input_transp.o: input_transp.h
+input_transp.o: mail_params.h
+int_filt.o: ../../include/attr.h
+int_filt.o: ../../include/check_arg.h
+int_filt.o: ../../include/htable.h
+int_filt.o: ../../include/iostuff.h
+int_filt.o: ../../include/msg.h
+int_filt.o: ../../include/mymalloc.h
+int_filt.o: ../../include/name_mask.h
+int_filt.o: ../../include/nvtable.h
+int_filt.o: ../../include/sys_defs.h
+int_filt.o: ../../include/vbuf.h
+int_filt.o: ../../include/vstream.h
+int_filt.o: ../../include/vstring.h
+int_filt.o: cleanup_user.h
+int_filt.o: int_filt.c
+int_filt.o: int_filt.h
+int_filt.o: mail_params.h
+int_filt.o: mail_proto.h
+is_header.o: ../../include/sys_defs.h
+is_header.o: is_header.c
+is_header.o: is_header.h
+log_adhoc.o: ../../include/attr.h
+log_adhoc.o: ../../include/check_arg.h
+log_adhoc.o: ../../include/format_tv.h
+log_adhoc.o: ../../include/htable.h
+log_adhoc.o: ../../include/msg.h
+log_adhoc.o: ../../include/mymalloc.h
+log_adhoc.o: ../../include/nvtable.h
+log_adhoc.o: ../../include/stringops.h
+log_adhoc.o: ../../include/sys_defs.h
+log_adhoc.o: ../../include/vbuf.h
+log_adhoc.o: ../../include/vstream.h
+log_adhoc.o: ../../include/vstring.h
+log_adhoc.o: dsn.h
+log_adhoc.o: info_log_addr_form.h
+log_adhoc.o: log_adhoc.c
+log_adhoc.o: log_adhoc.h
+log_adhoc.o: mail_params.h
+log_adhoc.o: msg_stats.h
+log_adhoc.o: recipient_list.h
+login_sender_match.o: ../../include/argv.h
+login_sender_match.o: ../../include/check_arg.h
+login_sender_match.o: ../../include/dict.h
+login_sender_match.o: ../../include/msg.h
+login_sender_match.o: ../../include/myflock.h
+login_sender_match.o: ../../include/mymalloc.h
+login_sender_match.o: ../../include/stringops.h
+login_sender_match.o: ../../include/sys_defs.h
+login_sender_match.o: ../../include/vbuf.h
+login_sender_match.o: ../../include/vstream.h
+login_sender_match.o: ../../include/vstring.h
+login_sender_match.o: login_sender_match.c
+login_sender_match.o: login_sender_match.h
+login_sender_match.o: mail_params.h
+login_sender_match.o: maps.h
+login_sender_match.o: quote_822_local.h
+login_sender_match.o: quote_flags.h
+login_sender_match.o: strip_addr.h
+mail_addr.o: ../../include/check_arg.h
+mail_addr.o: ../../include/stringops.h
+mail_addr.o: ../../include/sys_defs.h
+mail_addr.o: ../../include/vbuf.h
+mail_addr.o: ../../include/vstring.h
+mail_addr.o: mail_addr.c
+mail_addr.o: mail_addr.h
+mail_addr.o: mail_params.h
+mail_addr_crunch.o: ../../include/argv.h
+mail_addr_crunch.o: ../../include/check_arg.h
+mail_addr_crunch.o: ../../include/mymalloc.h
+mail_addr_crunch.o: ../../include/sys_defs.h
+mail_addr_crunch.o: ../../include/vbuf.h
+mail_addr_crunch.o: ../../include/vstring.h
+mail_addr_crunch.o: canon_addr.h
+mail_addr_crunch.o: mail_addr_crunch.c
+mail_addr_crunch.o: mail_addr_crunch.h
+mail_addr_crunch.o: mail_addr_form.h
+mail_addr_crunch.o: quote_822_local.h
+mail_addr_crunch.o: quote_flags.h
+mail_addr_crunch.o: resolve_clnt.h
+mail_addr_crunch.o: tok822.h
+mail_addr_find.o: ../../include/argv.h
+mail_addr_find.o: ../../include/check_arg.h
+mail_addr_find.o: ../../include/dict.h
+mail_addr_find.o: ../../include/msg.h
+mail_addr_find.o: ../../include/myflock.h
+mail_addr_find.o: ../../include/mymalloc.h
+mail_addr_find.o: ../../include/name_mask.h
+mail_addr_find.o: ../../include/stringops.h
+mail_addr_find.o: ../../include/sys_defs.h
+mail_addr_find.o: ../../include/vbuf.h
+mail_addr_find.o: ../../include/vstream.h
+mail_addr_find.o: ../../include/vstring.h
+mail_addr_find.o: mail_addr_find.c
+mail_addr_find.o: mail_addr_find.h
+mail_addr_find.o: mail_addr_form.h
+mail_addr_find.o: mail_params.h
+mail_addr_find.o: maps.h
+mail_addr_find.o: quote_822_local.h
+mail_addr_find.o: quote_flags.h
+mail_addr_find.o: resolve_local.h
+mail_addr_find.o: strip_addr.h
+mail_addr_form.o: ../../include/name_code.h
+mail_addr_form.o: ../../include/sys_defs.h
+mail_addr_form.o: mail_addr_form.c
+mail_addr_form.o: mail_addr_form.h
+mail_addr_map.o: ../../include/argv.h
+mail_addr_map.o: ../../include/check_arg.h
+mail_addr_map.o: ../../include/dict.h
+mail_addr_map.o: ../../include/msg.h
+mail_addr_map.o: ../../include/myflock.h
+mail_addr_map.o: ../../include/mymalloc.h
+mail_addr_map.o: ../../include/sys_defs.h
+mail_addr_map.o: ../../include/vbuf.h
+mail_addr_map.o: ../../include/vstream.h
+mail_addr_map.o: ../../include/vstring.h
+mail_addr_map.o: mail_addr_crunch.h
+mail_addr_map.o: mail_addr_find.h
+mail_addr_map.o: mail_addr_form.h
+mail_addr_map.o: mail_addr_map.c
+mail_addr_map.o: mail_addr_map.h
+mail_addr_map.o: maps.h
+mail_addr_map.o: quote_822_local.h
+mail_addr_map.o: quote_flags.h
+mail_command_client.o: ../../include/attr.h
+mail_command_client.o: ../../include/check_arg.h
+mail_command_client.o: ../../include/htable.h
+mail_command_client.o: ../../include/iostuff.h
+mail_command_client.o: ../../include/msg.h
+mail_command_client.o: ../../include/mymalloc.h
+mail_command_client.o: ../../include/nvtable.h
+mail_command_client.o: ../../include/sys_defs.h
+mail_command_client.o: ../../include/vbuf.h
+mail_command_client.o: ../../include/vstream.h
+mail_command_client.o: ../../include/vstring.h
+mail_command_client.o: mail_command_client.c
+mail_command_client.o: mail_proto.h
+mail_command_server.o: ../../include/attr.h
+mail_command_server.o: ../../include/check_arg.h
+mail_command_server.o: ../../include/htable.h
+mail_command_server.o: ../../include/iostuff.h
+mail_command_server.o: ../../include/mymalloc.h
+mail_command_server.o: ../../include/nvtable.h
+mail_command_server.o: ../../include/sys_defs.h
+mail_command_server.o: ../../include/vbuf.h
+mail_command_server.o: ../../include/vstream.h
+mail_command_server.o: ../../include/vstring.h
+mail_command_server.o: mail_command_server.c
+mail_command_server.o: mail_proto.h
+mail_conf.o: ../../include/argv.h
+mail_conf.o: ../../include/check_arg.h
+mail_conf.o: ../../include/dict.h
+mail_conf.o: ../../include/msg.h
+mail_conf.o: ../../include/myflock.h
+mail_conf.o: ../../include/mymalloc.h
+mail_conf.o: ../../include/readlline.h
+mail_conf.o: ../../include/safe.h
+mail_conf.o: ../../include/stringops.h
+mail_conf.o: ../../include/sys_defs.h
+mail_conf.o: ../../include/vbuf.h
+mail_conf.o: ../../include/vstream.h
+mail_conf.o: ../../include/vstring.h
+mail_conf.o: mail_conf.c
+mail_conf.o: mail_conf.h
+mail_conf.o: mail_params.h
+mail_conf_bool.o: ../../include/argv.h
+mail_conf_bool.o: ../../include/check_arg.h
+mail_conf_bool.o: ../../include/dict.h
+mail_conf_bool.o: ../../include/msg.h
+mail_conf_bool.o: ../../include/myflock.h
+mail_conf_bool.o: ../../include/sys_defs.h
+mail_conf_bool.o: ../../include/vbuf.h
+mail_conf_bool.o: ../../include/vstream.h
+mail_conf_bool.o: ../../include/vstring.h
+mail_conf_bool.o: mail_conf.h
+mail_conf_bool.o: mail_conf_bool.c
+mail_conf_int.o: ../../include/argv.h
+mail_conf_int.o: ../../include/check_arg.h
+mail_conf_int.o: ../../include/dict.h
+mail_conf_int.o: ../../include/msg.h
+mail_conf_int.o: ../../include/myflock.h
+mail_conf_int.o: ../../include/mymalloc.h
+mail_conf_int.o: ../../include/stringops.h
+mail_conf_int.o: ../../include/sys_defs.h
+mail_conf_int.o: ../../include/vbuf.h
+mail_conf_int.o: ../../include/vstream.h
+mail_conf_int.o: ../../include/vstring.h
+mail_conf_int.o: mail_conf.h
+mail_conf_int.o: mail_conf_int.c
+mail_conf_long.o: ../../include/argv.h
+mail_conf_long.o: ../../include/check_arg.h
+mail_conf_long.o: ../../include/dict.h
+mail_conf_long.o: ../../include/msg.h
+mail_conf_long.o: ../../include/myflock.h
+mail_conf_long.o: ../../include/mymalloc.h
+mail_conf_long.o: ../../include/stringops.h
+mail_conf_long.o: ../../include/sys_defs.h
+mail_conf_long.o: ../../include/vbuf.h
+mail_conf_long.o: ../../include/vstream.h
+mail_conf_long.o: ../../include/vstring.h
+mail_conf_long.o: mail_conf.h
+mail_conf_long.o: mail_conf_long.c
+mail_conf_nbool.o: ../../include/argv.h
+mail_conf_nbool.o: ../../include/check_arg.h
+mail_conf_nbool.o: ../../include/dict.h
+mail_conf_nbool.o: ../../include/msg.h
+mail_conf_nbool.o: ../../include/myflock.h
+mail_conf_nbool.o: ../../include/sys_defs.h
+mail_conf_nbool.o: ../../include/vbuf.h
+mail_conf_nbool.o: ../../include/vstream.h
+mail_conf_nbool.o: ../../include/vstring.h
+mail_conf_nbool.o: mail_conf.h
+mail_conf_nbool.o: mail_conf_nbool.c
+mail_conf_nint.o: ../../include/argv.h
+mail_conf_nint.o: ../../include/check_arg.h
+mail_conf_nint.o: ../../include/dict.h
+mail_conf_nint.o: ../../include/msg.h
+mail_conf_nint.o: ../../include/myflock.h
+mail_conf_nint.o: ../../include/mymalloc.h
+mail_conf_nint.o: ../../include/stringops.h
+mail_conf_nint.o: ../../include/sys_defs.h
+mail_conf_nint.o: ../../include/vbuf.h
+mail_conf_nint.o: ../../include/vstream.h
+mail_conf_nint.o: ../../include/vstring.h
+mail_conf_nint.o: mail_conf.h
+mail_conf_nint.o: mail_conf_nint.c
+mail_conf_raw.o: ../../include/msg.h
+mail_conf_raw.o: ../../include/mymalloc.h
+mail_conf_raw.o: ../../include/sys_defs.h
+mail_conf_raw.o: mail_conf.h
+mail_conf_raw.o: mail_conf_raw.c
+mail_conf_str.o: ../../include/check_arg.h
+mail_conf_str.o: ../../include/msg.h
+mail_conf_str.o: ../../include/mymalloc.h
+mail_conf_str.o: ../../include/stringops.h
+mail_conf_str.o: ../../include/sys_defs.h
+mail_conf_str.o: ../../include/vbuf.h
+mail_conf_str.o: ../../include/vstring.h
+mail_conf_str.o: mail_conf.h
+mail_conf_str.o: mail_conf_str.c
+mail_conf_time.o: ../../include/argv.h
+mail_conf_time.o: ../../include/check_arg.h
+mail_conf_time.o: ../../include/dict.h
+mail_conf_time.o: ../../include/msg.h
+mail_conf_time.o: ../../include/myflock.h
+mail_conf_time.o: ../../include/mymalloc.h
+mail_conf_time.o: ../../include/stringops.h
+mail_conf_time.o: ../../include/sys_defs.h
+mail_conf_time.o: ../../include/vbuf.h
+mail_conf_time.o: ../../include/vstream.h
+mail_conf_time.o: ../../include/vstring.h
+mail_conf_time.o: conv_time.h
+mail_conf_time.o: mail_conf.h
+mail_conf_time.o: mail_conf_time.c
+mail_connect.o: ../../include/attr.h
+mail_connect.o: ../../include/check_arg.h
+mail_connect.o: ../../include/connect.h
+mail_connect.o: ../../include/htable.h
+mail_connect.o: ../../include/iostuff.h
+mail_connect.o: ../../include/msg.h
+mail_connect.o: ../../include/mymalloc.h
+mail_connect.o: ../../include/nvtable.h
+mail_connect.o: ../../include/stringops.h
+mail_connect.o: ../../include/sys_defs.h
+mail_connect.o: ../../include/vbuf.h
+mail_connect.o: ../../include/vstream.h
+mail_connect.o: ../../include/vstring.h
+mail_connect.o: mail_connect.c
+mail_connect.o: mail_proto.h
+mail_connect.o: timed_ipc.h
+mail_copy.o: ../../include/check_arg.h
+mail_copy.o: ../../include/htable.h
+mail_copy.o: ../../include/iostuff.h
+mail_copy.o: ../../include/msg.h
+mail_copy.o: ../../include/safe_open.h
+mail_copy.o: ../../include/stringops.h
+mail_copy.o: ../../include/sys_defs.h
+mail_copy.o: ../../include/vbuf.h
+mail_copy.o: ../../include/vstream.h
+mail_copy.o: ../../include/vstring.h
+mail_copy.o: ../../include/vstring_vstream.h
+mail_copy.o: ../../include/warn_stat.h
+mail_copy.o: dsn.h
+mail_copy.o: dsn_buf.h
+mail_copy.o: mail_addr.h
+mail_copy.o: mail_copy.c
+mail_copy.o: mail_copy.h
+mail_copy.o: mail_params.h
+mail_copy.o: mail_queue.h
+mail_copy.o: mark_corrupt.h
+mail_copy.o: mbox_open.h
+mail_copy.o: quote_822_local.h
+mail_copy.o: quote_flags.h
+mail_copy.o: rec_type.h
+mail_copy.o: record.h
+mail_copy.o: sys_exits.h
+mail_date.o: ../../include/check_arg.h
+mail_date.o: ../../include/msg.h
+mail_date.o: ../../include/sys_defs.h
+mail_date.o: ../../include/vbuf.h
+mail_date.o: ../../include/vstring.h
+mail_date.o: mail_date.c
+mail_date.o: mail_date.h
+mail_dict.o: ../../include/argv.h
+mail_dict.o: ../../include/check_arg.h
+mail_dict.o: ../../include/dict.h
+mail_dict.o: ../../include/msg.h
+mail_dict.o: ../../include/myflock.h
+mail_dict.o: ../../include/mymalloc.h
+mail_dict.o: ../../include/stringops.h
+mail_dict.o: ../../include/sys_defs.h
+mail_dict.o: ../../include/vbuf.h
+mail_dict.o: ../../include/vstream.h
+mail_dict.o: ../../include/vstring.h
+mail_dict.o: dict_ldap.h
+mail_dict.o: dict_memcache.h
+mail_dict.o: dict_mysql.h
+mail_dict.o: dict_pgsql.h
+mail_dict.o: dict_proxy.h
+mail_dict.o: dict_sqlite.h
+mail_dict.o: dynamicmaps.h
+mail_dict.o: mail_dict.c
+mail_dict.o: mail_dict.h
+mail_dict.o: mail_params.h
+mail_error.o: ../../include/check_arg.h
+mail_error.o: ../../include/name_mask.h
+mail_error.o: ../../include/sys_defs.h
+mail_error.o: ../../include/vbuf.h
+mail_error.o: ../../include/vstring.h
+mail_error.o: mail_error.c
+mail_error.o: mail_error.h
+mail_flush.o: ../../include/attr.h
+mail_flush.o: ../../include/check_arg.h
+mail_flush.o: ../../include/htable.h
+mail_flush.o: ../../include/iostuff.h
+mail_flush.o: ../../include/mymalloc.h
+mail_flush.o: ../../include/nvtable.h
+mail_flush.o: ../../include/sys_defs.h
+mail_flush.o: ../../include/vbuf.h
+mail_flush.o: ../../include/vstream.h
+mail_flush.o: ../../include/vstring.h
+mail_flush.o: mail_flush.c
+mail_flush.o: mail_flush.h
+mail_flush.o: mail_params.h
+mail_flush.o: mail_proto.h
+mail_open_ok.o: ../../include/check_arg.h
+mail_open_ok.o: ../../include/msg.h
+mail_open_ok.o: ../../include/sys_defs.h
+mail_open_ok.o: ../../include/vbuf.h
+mail_open_ok.o: ../../include/vstream.h
+mail_open_ok.o: ../../include/vstring.h
+mail_open_ok.o: ../../include/warn_stat.h
+mail_open_ok.o: mail_open_ok.c
+mail_open_ok.o: mail_open_ok.h
+mail_open_ok.o: mail_queue.h
+mail_params.o: ../../include/argv.h
+mail_params.o: ../../include/attr.h
+mail_params.o: ../../include/check_arg.h
+mail_params.o: ../../include/dict.h
+mail_params.o: ../../include/dict_db.h
+mail_params.o: ../../include/dict_lmdb.h
+mail_params.o: ../../include/get_hostname.h
+mail_params.o: ../../include/htable.h
+mail_params.o: ../../include/inet_addr_list.h
+mail_params.o: ../../include/inet_proto.h
+mail_params.o: ../../include/iostuff.h
+mail_params.o: ../../include/midna_domain.h
+mail_params.o: ../../include/msg.h
+mail_params.o: ../../include/msg_syslog.h
+mail_params.o: ../../include/myaddrinfo.h
+mail_params.o: ../../include/myflock.h
+mail_params.o: ../../include/mymalloc.h
+mail_params.o: ../../include/nvtable.h
+mail_params.o: ../../include/safe.h
+mail_params.o: ../../include/safe_open.h
+mail_params.o: ../../include/stringops.h
+mail_params.o: ../../include/sys_defs.h
+mail_params.o: ../../include/valid_hostname.h
+mail_params.o: ../../include/vbuf.h
+mail_params.o: ../../include/vstream.h
+mail_params.o: ../../include/vstring.h
+mail_params.o: ../../include/vstring_vstream.h
+mail_params.o: config_known_tcp_ports.h
+mail_params.o: compat_level.h
+mail_params.o: mail_conf.h
+mail_params.o: mail_params.c
+mail_params.o: mail_params.h
+mail_params.o: mail_proto.h
+mail_params.o: mail_version.h
+mail_params.o: mynetworks.h
+mail_params.o: own_inet_addr.h
+mail_params.o: recipient_list.h
+mail_params.o: verp_sender.h
+mail_parm_split.o: ../../include/argv.h
+mail_parm_split.o: ../../include/check_arg.h
+mail_parm_split.o: ../../include/msg.h
+mail_parm_split.o: ../../include/mymalloc.h
+mail_parm_split.o: ../../include/stringops.h
+mail_parm_split.o: ../../include/sys_defs.h
+mail_parm_split.o: ../../include/vbuf.h
+mail_parm_split.o: ../../include/vstring.h
+mail_parm_split.o: mail_params.h
+mail_parm_split.o: mail_parm_split.c
+mail_parm_split.o: mail_parm_split.h
+mail_pathname.o: ../../include/attr.h
+mail_pathname.o: ../../include/check_arg.h
+mail_pathname.o: ../../include/htable.h
+mail_pathname.o: ../../include/iostuff.h
+mail_pathname.o: ../../include/mymalloc.h
+mail_pathname.o: ../../include/nvtable.h
+mail_pathname.o: ../../include/stringops.h
+mail_pathname.o: ../../include/sys_defs.h
+mail_pathname.o: ../../include/vbuf.h
+mail_pathname.o: ../../include/vstream.h
+mail_pathname.o: ../../include/vstring.h
+mail_pathname.o: mail_pathname.c
+mail_pathname.o: mail_proto.h
+mail_queue.o: ../../include/argv.h
+mail_queue.o: ../../include/check_arg.h
+mail_queue.o: ../../include/dir_forest.h
+mail_queue.o: ../../include/make_dirs.h
+mail_queue.o: ../../include/msg.h
+mail_queue.o: ../../include/mymalloc.h
+mail_queue.o: ../../include/sane_fsops.h
+mail_queue.o: ../../include/split_at.h
+mail_queue.o: ../../include/sys_defs.h
+mail_queue.o: ../../include/valid_hostname.h
+mail_queue.o: ../../include/vbuf.h
+mail_queue.o: ../../include/vstream.h
+mail_queue.o: ../../include/vstring.h
+mail_queue.o: file_id.h
+mail_queue.o: mail_params.h
+mail_queue.o: mail_queue.c
+mail_queue.o: mail_queue.h
+mail_queue.o: safe_ultostr.h
+mail_run.o: ../../include/check_arg.h
+mail_run.o: ../../include/msg.h
+mail_run.o: ../../include/mymalloc.h
+mail_run.o: ../../include/stringops.h
+mail_run.o: ../../include/sys_defs.h
+mail_run.o: ../../include/vbuf.h
+mail_run.o: ../../include/vstring.h
+mail_run.o: mail_params.h
+mail_run.o: mail_run.c
+mail_run.o: mail_run.h
+mail_scan_dir.o: ../../include/scan_dir.h
+mail_scan_dir.o: ../../include/sys_defs.h
+mail_scan_dir.o: mail_scan_dir.c
+mail_scan_dir.o: mail_scan_dir.h
+mail_stream.o: ../../include/argv.h
+mail_stream.o: ../../include/attr.h
+mail_stream.o: ../../include/check_arg.h
+mail_stream.o: ../../include/htable.h
+mail_stream.o: ../../include/iostuff.h
+mail_stream.o: ../../include/msg.h
+mail_stream.o: ../../include/mymalloc.h
+mail_stream.o: ../../include/nvtable.h
+mail_stream.o: ../../include/sane_fsops.h
+mail_stream.o: ../../include/stringops.h
+mail_stream.o: ../../include/sys_defs.h
+mail_stream.o: ../../include/vbuf.h
+mail_stream.o: ../../include/vstream.h
+mail_stream.o: ../../include/vstring.h
+mail_stream.o: ../../include/warn_stat.h
+mail_stream.o: cleanup_user.h
+mail_stream.o: mail_params.h
+mail_stream.o: mail_parm_split.h
+mail_stream.o: mail_proto.h
+mail_stream.o: mail_queue.h
+mail_stream.o: mail_stream.c
+mail_stream.o: mail_stream.h
+mail_stream.o: opened.h
+mail_task.o: ../../include/check_arg.h
+mail_task.o: ../../include/safe.h
+mail_task.o: ../../include/sys_defs.h
+mail_task.o: ../../include/vbuf.h
+mail_task.o: ../../include/vstring.h
+mail_task.o: mail_conf.h
+mail_task.o: mail_params.h
+mail_task.o: mail_task.c
+mail_task.o: mail_task.h
+mail_trigger.o: ../../include/attr.h
+mail_trigger.o: ../../include/check_arg.h
+mail_trigger.o: ../../include/htable.h
+mail_trigger.o: ../../include/iostuff.h
+mail_trigger.o: ../../include/msg.h
+mail_trigger.o: ../../include/mymalloc.h
+mail_trigger.o: ../../include/nvtable.h
+mail_trigger.o: ../../include/sys_defs.h
+mail_trigger.o: ../../include/trigger.h
+mail_trigger.o: ../../include/vbuf.h
+mail_trigger.o: ../../include/vstream.h
+mail_trigger.o: ../../include/vstring.h
+mail_trigger.o: ../../include/warn_stat.h
+mail_trigger.o: mail_params.h
+mail_trigger.o: mail_proto.h
+mail_trigger.o: mail_trigger.c
+mail_version.o: ../../include/check_arg.h
+mail_version.o: ../../include/msg.h
+mail_version.o: ../../include/mymalloc.h
+mail_version.o: ../../include/split_at.h
+mail_version.o: ../../include/stringops.h
+mail_version.o: ../../include/sys_defs.h
+mail_version.o: ../../include/vbuf.h
+mail_version.o: ../../include/vstring.h
+mail_version.o: mail_version.c
+mail_version.o: mail_version.h
+maillog_client.o: ../../include/argv.h
+maillog_client.o: ../../include/attr.h
+maillog_client.o: ../../include/check_arg.h
+maillog_client.o: ../../include/htable.h
+maillog_client.o: ../../include/iostuff.h
+maillog_client.o: ../../include/logwriter.h
+maillog_client.o: ../../include/msg.h
+maillog_client.o: ../../include/msg_logger.h
+maillog_client.o: ../../include/msg_syslog.h
+maillog_client.o: ../../include/mymalloc.h
+maillog_client.o: ../../include/nvtable.h
+maillog_client.o: ../../include/safe.h
+maillog_client.o: ../../include/stringops.h
+maillog_client.o: ../../include/sys_defs.h
+maillog_client.o: ../../include/vbuf.h
+maillog_client.o: ../../include/vstream.h
+maillog_client.o: ../../include/vstring.h
+maillog_client.o: mail_params.h
+maillog_client.o: mail_proto.h
+maillog_client.o: maillog_client.c
+maillog_client.o: maillog_client.h
+map_search.o: ../../include/check_arg.h
+map_search.o: ../../include/htable.h
+map_search.o: ../../include/msg.h
+map_search.o: ../../include/mymalloc.h
+map_search.o: ../../include/name_code.h
+map_search.o: ../../include/stringops.h
+map_search.o: ../../include/sys_defs.h
+map_search.o: ../../include/vbuf.h
+map_search.o: ../../include/vstring.h
+map_search.o: map_search.c
+map_search.o: map_search.h
+maps.o: ../../include/argv.h
+maps.o: ../../include/check_arg.h
+maps.o: ../../include/dict.h
+maps.o: ../../include/msg.h
+maps.o: ../../include/myflock.h
+maps.o: ../../include/mymalloc.h
+maps.o: ../../include/split_at.h
+maps.o: ../../include/stringops.h
+maps.o: ../../include/sys_defs.h
+maps.o: ../../include/vbuf.h
+maps.o: ../../include/vstream.h
+maps.o: ../../include/vstring.h
+maps.o: mail_conf.h
+maps.o: maps.c
+maps.o: maps.h
+mark_corrupt.o: ../../include/attr.h
+mark_corrupt.o: ../../include/check_arg.h
+mark_corrupt.o: ../../include/htable.h
+mark_corrupt.o: ../../include/msg.h
+mark_corrupt.o: ../../include/mymalloc.h
+mark_corrupt.o: ../../include/nvtable.h
+mark_corrupt.o: ../../include/set_eugid.h
+mark_corrupt.o: ../../include/sys_defs.h
+mark_corrupt.o: ../../include/vbuf.h
+mark_corrupt.o: ../../include/vstream.h
+mark_corrupt.o: ../../include/vstring.h
+mark_corrupt.o: deliver_request.h
+mark_corrupt.o: dsn.h
+mark_corrupt.o: mail_params.h
+mark_corrupt.o: mail_queue.h
+mark_corrupt.o: mark_corrupt.c
+mark_corrupt.o: mark_corrupt.h
+mark_corrupt.o: msg_stats.h
+mark_corrupt.o: recipient_list.h
+match_parent_style.o: ../../include/argv.h
+match_parent_style.o: ../../include/check_arg.h
+match_parent_style.o: ../../include/match_list.h
+match_parent_style.o: ../../include/sys_defs.h
+match_parent_style.o: ../../include/vbuf.h
+match_parent_style.o: ../../include/vstring.h
+match_parent_style.o: mail_params.h
+match_parent_style.o: match_parent_style.c
+match_parent_style.o: match_parent_style.h
+match_parent_style.o: string_list.h
+match_service.o: ../../include/argv.h
+match_service.o: ../../include/check_arg.h
+match_service.o: ../../include/msg.h
+match_service.o: ../../include/mymalloc.h
+match_service.o: ../../include/stringops.h
+match_service.o: ../../include/sys_defs.h
+match_service.o: ../../include/vbuf.h
+match_service.o: ../../include/vstring.h
+match_service.o: match_service.c
+match_service.o: match_service.h
+mbox_conf.o: ../../include/argv.h
+mbox_conf.o: ../../include/check_arg.h
+mbox_conf.o: ../../include/name_mask.h
+mbox_conf.o: ../../include/sys_defs.h
+mbox_conf.o: ../../include/vbuf.h
+mbox_conf.o: ../../include/vstring.h
+mbox_conf.o: mail_params.h
+mbox_conf.o: mbox_conf.c
+mbox_conf.o: mbox_conf.h
+mbox_open.o: ../../include/argv.h
+mbox_open.o: ../../include/check_arg.h
+mbox_open.o: ../../include/iostuff.h
+mbox_open.o: ../../include/msg.h
+mbox_open.o: ../../include/myflock.h
+mbox_open.o: ../../include/mymalloc.h
+mbox_open.o: ../../include/safe_open.h
+mbox_open.o: ../../include/sys_defs.h
+mbox_open.o: ../../include/vbuf.h
+mbox_open.o: ../../include/vstream.h
+mbox_open.o: ../../include/vstring.h
+mbox_open.o: ../../include/warn_stat.h
+mbox_open.o: deliver_flock.h
+mbox_open.o: dot_lockfile.h
+mbox_open.o: dsn.h
+mbox_open.o: dsn_buf.h
+mbox_open.o: mbox_conf.h
+mbox_open.o: mbox_open.c
+mbox_open.o: mbox_open.h
+memcache_proto.o: ../../include/check_arg.h
+memcache_proto.o: ../../include/compat_va_copy.h
+memcache_proto.o: ../../include/msg.h
+memcache_proto.o: ../../include/sys_defs.h
+memcache_proto.o: ../../include/vbuf.h
+memcache_proto.o: ../../include/vstream.h
+memcache_proto.o: ../../include/vstring.h
+memcache_proto.o: ../../include/vstring_vstream.h
+memcache_proto.o: memcache_proto.c
+memcache_proto.o: memcache_proto.h
+midna_adomain.o: ../../include/check_arg.h
+midna_adomain.o: ../../include/midna_domain.h
+midna_adomain.o: ../../include/stringops.h
+midna_adomain.o: ../../include/sys_defs.h
+midna_adomain.o: ../../include/vbuf.h
+midna_adomain.o: ../../include/vstring.h
+midna_adomain.o: midna_adomain.c
+midna_adomain.o: midna_adomain.h
+mime_state.o: ../../include/check_arg.h
+mime_state.o: ../../include/msg.h
+mime_state.o: ../../include/mymalloc.h
+mime_state.o: ../../include/sys_defs.h
+mime_state.o: ../../include/vbuf.h
+mime_state.o: ../../include/vstring.h
+mime_state.o: header_opts.h
+mime_state.o: header_token.h
+mime_state.o: is_header.h
+mime_state.o: lex_822.h
+mime_state.o: mail_params.h
+mime_state.o: mime_state.c
+mime_state.o: mime_state.h
+mime_state.o: rec_type.h
+mkmap_cdb.o: ../../include/argv.h
+mkmap_cdb.o: ../../include/check_arg.h
+mkmap_cdb.o: ../../include/dict.h
+mkmap_cdb.o: ../../include/dict_cdb.h
+mkmap_cdb.o: ../../include/myflock.h
+mkmap_cdb.o: ../../include/mymalloc.h
+mkmap_cdb.o: ../../include/sys_defs.h
+mkmap_cdb.o: ../../include/vbuf.h
+mkmap_cdb.o: ../../include/vstream.h
+mkmap_cdb.o: ../../include/vstring.h
+mkmap_cdb.o: mkmap.h
+mkmap_cdb.o: mkmap_cdb.c
+mkmap_db.o: ../../include/argv.h
+mkmap_db.o: ../../include/check_arg.h
+mkmap_db.o: ../../include/dict.h
+mkmap_db.o: ../../include/dict_db.h
+mkmap_db.o: ../../include/msg.h
+mkmap_db.o: ../../include/myflock.h
+mkmap_db.o: ../../include/mymalloc.h
+mkmap_db.o: ../../include/stringops.h
+mkmap_db.o: ../../include/sys_defs.h
+mkmap_db.o: ../../include/vbuf.h
+mkmap_db.o: ../../include/vstream.h
+mkmap_db.o: ../../include/vstring.h
+mkmap_db.o: ../../include/warn_stat.h
+mkmap_db.o: mail_params.h
+mkmap_db.o: mkmap.h
+mkmap_db.o: mkmap_db.c
+mkmap_dbm.o: ../../include/argv.h
+mkmap_dbm.o: ../../include/check_arg.h
+mkmap_dbm.o: ../../include/dict.h
+mkmap_dbm.o: ../../include/dict_dbm.h
+mkmap_dbm.o: ../../include/msg.h
+mkmap_dbm.o: ../../include/myflock.h
+mkmap_dbm.o: ../../include/mymalloc.h
+mkmap_dbm.o: ../../include/stringops.h
+mkmap_dbm.o: ../../include/sys_defs.h
+mkmap_dbm.o: ../../include/vbuf.h
+mkmap_dbm.o: ../../include/vstream.h
+mkmap_dbm.o: ../../include/vstring.h
+mkmap_dbm.o: mkmap.h
+mkmap_dbm.o: mkmap_dbm.c
+mkmap_fail.o: ../../include/argv.h
+mkmap_fail.o: ../../include/check_arg.h
+mkmap_fail.o: ../../include/dict.h
+mkmap_fail.o: ../../include/dict_fail.h
+mkmap_fail.o: ../../include/myflock.h
+mkmap_fail.o: ../../include/mymalloc.h
+mkmap_fail.o: ../../include/sys_defs.h
+mkmap_fail.o: ../../include/vbuf.h
+mkmap_fail.o: ../../include/vstream.h
+mkmap_fail.o: ../../include/vstring.h
+mkmap_fail.o: mkmap.h
+mkmap_fail.o: mkmap_fail.c
+mkmap_lmdb.o: ../../include/argv.h
+mkmap_lmdb.o: ../../include/check_arg.h
+mkmap_lmdb.o: ../../include/dict.h
+mkmap_lmdb.o: ../../include/dict_lmdb.h
+mkmap_lmdb.o: ../../include/msg.h
+mkmap_lmdb.o: ../../include/myflock.h
+mkmap_lmdb.o: ../../include/mymalloc.h
+mkmap_lmdb.o: ../../include/stringops.h
+mkmap_lmdb.o: ../../include/sys_defs.h
+mkmap_lmdb.o: ../../include/vbuf.h
+mkmap_lmdb.o: ../../include/vstream.h
+mkmap_lmdb.o: ../../include/vstring.h
+mkmap_lmdb.o: ../../include/warn_stat.h
+mkmap_lmdb.o: mail_conf.h
+mkmap_lmdb.o: mail_params.h
+mkmap_lmdb.o: mkmap.h
+mkmap_lmdb.o: mkmap_lmdb.c
+mkmap_open.o: ../../include/argv.h
+mkmap_open.o: ../../include/check_arg.h
+mkmap_open.o: ../../include/dict.h
+mkmap_open.o: ../../include/dict_cdb.h
+mkmap_open.o: ../../include/dict_db.h
+mkmap_open.o: ../../include/dict_dbm.h
+mkmap_open.o: ../../include/dict_fail.h
+mkmap_open.o: ../../include/dict_lmdb.h
+mkmap_open.o: ../../include/dict_sdbm.h
+mkmap_open.o: ../../include/htable.h
+mkmap_open.o: ../../include/msg.h
+mkmap_open.o: ../../include/myflock.h
+mkmap_open.o: ../../include/mymalloc.h
+mkmap_open.o: ../../include/sigdelay.h
+mkmap_open.o: ../../include/stringops.h
+mkmap_open.o: ../../include/sys_defs.h
+mkmap_open.o: ../../include/vbuf.h
+mkmap_open.o: ../../include/vstream.h
+mkmap_open.o: ../../include/vstring.h
+mkmap_open.o: dict_proxy.h
+mkmap_open.o: mkmap.h
+mkmap_open.o: mkmap_open.c
+mkmap_proxy.o: ../../include/argv.h
+mkmap_proxy.o: ../../include/check_arg.h
+mkmap_proxy.o: ../../include/dict.h
+mkmap_proxy.o: ../../include/myflock.h
+mkmap_proxy.o: ../../include/mymalloc.h
+mkmap_proxy.o: ../../include/sys_defs.h
+mkmap_proxy.o: ../../include/vbuf.h
+mkmap_proxy.o: ../../include/vstream.h
+mkmap_proxy.o: ../../include/vstring.h
+mkmap_proxy.o: dict_proxy.h
+mkmap_proxy.o: mkmap.h
+mkmap_proxy.o: mkmap_proxy.c
+mkmap_sdbm.o: ../../include/argv.h
+mkmap_sdbm.o: ../../include/check_arg.h
+mkmap_sdbm.o: ../../include/dict.h
+mkmap_sdbm.o: ../../include/dict_sdbm.h
+mkmap_sdbm.o: ../../include/msg.h
+mkmap_sdbm.o: ../../include/myflock.h
+mkmap_sdbm.o: ../../include/mymalloc.h
+mkmap_sdbm.o: ../../include/stringops.h
+mkmap_sdbm.o: ../../include/sys_defs.h
+mkmap_sdbm.o: ../../include/vbuf.h
+mkmap_sdbm.o: ../../include/vstream.h
+mkmap_sdbm.o: ../../include/vstring.h
+mkmap_sdbm.o: mkmap.h
+mkmap_sdbm.o: mkmap_sdbm.c
+msg_stats_print.o: ../../include/attr.h
+msg_stats_print.o: ../../include/check_arg.h
+msg_stats_print.o: ../../include/htable.h
+msg_stats_print.o: ../../include/iostuff.h
+msg_stats_print.o: ../../include/mymalloc.h
+msg_stats_print.o: ../../include/nvtable.h
+msg_stats_print.o: ../../include/sys_defs.h
+msg_stats_print.o: ../../include/vbuf.h
+msg_stats_print.o: ../../include/vstream.h
+msg_stats_print.o: ../../include/vstring.h
+msg_stats_print.o: mail_proto.h
+msg_stats_print.o: msg_stats.h
+msg_stats_print.o: msg_stats_print.c
+msg_stats_scan.o: ../../include/attr.h
+msg_stats_scan.o: ../../include/check_arg.h
+msg_stats_scan.o: ../../include/htable.h
+msg_stats_scan.o: ../../include/iostuff.h
+msg_stats_scan.o: ../../include/msg.h
+msg_stats_scan.o: ../../include/mymalloc.h
+msg_stats_scan.o: ../../include/nvtable.h
+msg_stats_scan.o: ../../include/sys_defs.h
+msg_stats_scan.o: ../../include/vbuf.h
+msg_stats_scan.o: ../../include/vstream.h
+msg_stats_scan.o: ../../include/vstring.h
+msg_stats_scan.o: mail_proto.h
+msg_stats_scan.o: msg_stats.h
+msg_stats_scan.o: msg_stats_scan.c
+mynetworks.o: ../../include/argv.h
+mynetworks.o: ../../include/check_arg.h
+mynetworks.o: ../../include/inet_addr_list.h
+mynetworks.o: ../../include/inet_proto.h
+mynetworks.o: ../../include/mask_addr.h
+mynetworks.o: ../../include/msg.h
+mynetworks.o: ../../include/myaddrinfo.h
+mynetworks.o: ../../include/mymalloc.h
+mynetworks.o: ../../include/name_mask.h
+mynetworks.o: ../../include/sock_addr.h
+mynetworks.o: ../../include/sys_defs.h
+mynetworks.o: ../../include/vbuf.h
+mynetworks.o: ../../include/vstring.h
+mynetworks.o: been_here.h
+mynetworks.o: mail_params.h
+mynetworks.o: mynetworks.c
+mynetworks.o: mynetworks.h
+mynetworks.o: own_inet_addr.h
+mypwd.o: ../../include/binhash.h
+mypwd.o: ../../include/htable.h
+mypwd.o: ../../include/msg.h
+mypwd.o: ../../include/mymalloc.h
+mypwd.o: ../../include/sys_defs.h
+mypwd.o: mypwd.c
+mypwd.o: mypwd.h
+namadr_list.o: ../../include/argv.h
+namadr_list.o: ../../include/check_arg.h
+namadr_list.o: ../../include/match_list.h
+namadr_list.o: ../../include/sys_defs.h
+namadr_list.o: ../../include/vbuf.h
+namadr_list.o: ../../include/vstring.h
+namadr_list.o: namadr_list.c
+namadr_list.o: namadr_list.h
+normalize_mailhost_addr.o: ../../include/check_arg.h
+normalize_mailhost_addr.o: ../../include/inet_proto.h
+normalize_mailhost_addr.o: ../../include/msg.h
+normalize_mailhost_addr.o: ../../include/myaddrinfo.h
+normalize_mailhost_addr.o: ../../include/mymalloc.h
+normalize_mailhost_addr.o: ../../include/stringops.h
+normalize_mailhost_addr.o: ../../include/sys_defs.h
+normalize_mailhost_addr.o: ../../include/valid_hostname.h
+normalize_mailhost_addr.o: ../../include/vbuf.h
+normalize_mailhost_addr.o: ../../include/vstring.h
+normalize_mailhost_addr.o: normalize_mailhost_addr.c
+normalize_mailhost_addr.o: normalize_mailhost_addr.h
+normalize_mailhost_addr.o: valid_mailhost_addr.h
+off_cvt.o: ../../include/check_arg.h
+off_cvt.o: ../../include/msg.h
+off_cvt.o: ../../include/sys_defs.h
+off_cvt.o: ../../include/vbuf.h
+off_cvt.o: ../../include/vstring.h
+off_cvt.o: off_cvt.c
+off_cvt.o: off_cvt.h
+opened.o: ../../include/check_arg.h
+opened.o: ../../include/msg.h
+opened.o: ../../include/sys_defs.h
+opened.o: ../../include/vbuf.h
+opened.o: ../../include/vstring.h
+opened.o: info_log_addr_form.h
+opened.o: opened.c
+opened.o: opened.h
+own_inet_addr.o: ../../include/check_arg.h
+own_inet_addr.o: ../../include/inet_addr_host.h
+own_inet_addr.o: ../../include/inet_addr_list.h
+own_inet_addr.o: ../../include/inet_addr_local.h
+own_inet_addr.o: ../../include/inet_proto.h
+own_inet_addr.o: ../../include/msg.h
+own_inet_addr.o: ../../include/myaddrinfo.h
+own_inet_addr.o: ../../include/mymalloc.h
+own_inet_addr.o: ../../include/sock_addr.h
+own_inet_addr.o: ../../include/stringops.h
+own_inet_addr.o: ../../include/sys_defs.h
+own_inet_addr.o: ../../include/vbuf.h
+own_inet_addr.o: ../../include/vstring.h
+own_inet_addr.o: mail_params.h
+own_inet_addr.o: own_inet_addr.c
+own_inet_addr.o: own_inet_addr.h
+pipe_command.o: ../../include/argv.h
+pipe_command.o: ../../include/check_arg.h
+pipe_command.o: ../../include/chroot_uid.h
+pipe_command.o: ../../include/clean_env.h
+pipe_command.o: ../../include/exec_command.h
+pipe_command.o: ../../include/iostuff.h
+pipe_command.o: ../../include/msg.h
+pipe_command.o: ../../include/msg_vstream.h
+pipe_command.o: ../../include/set_eugid.h
+pipe_command.o: ../../include/set_ugid.h
+pipe_command.o: ../../include/stringops.h
+pipe_command.o: ../../include/sys_defs.h
+pipe_command.o: ../../include/timed_wait.h
+pipe_command.o: ../../include/vbuf.h
+pipe_command.o: ../../include/vstream.h
+pipe_command.o: ../../include/vstring.h
+pipe_command.o: dsn.h
+pipe_command.o: dsn_buf.h
+pipe_command.o: dsn_util.h
+pipe_command.o: mail_copy.h
+pipe_command.o: mail_params.h
+pipe_command.o: pipe_command.c
+pipe_command.o: pipe_command.h
+pipe_command.o: sys_exits.h
+post_mail.o: ../../include/attr.h
+post_mail.o: ../../include/check_arg.h
+post_mail.o: ../../include/events.h
+post_mail.o: ../../include/htable.h
+post_mail.o: ../../include/iostuff.h
+post_mail.o: ../../include/msg.h
+post_mail.o: ../../include/mymalloc.h
+post_mail.o: ../../include/nvtable.h
+post_mail.o: ../../include/sys_defs.h
+post_mail.o: ../../include/vbuf.h
+post_mail.o: ../../include/vstream.h
+post_mail.o: ../../include/vstring.h
+post_mail.o: cleanup_user.h
+post_mail.o: int_filt.h
+post_mail.o: mail_date.h
+post_mail.o: mail_params.h
+post_mail.o: mail_proto.h
+post_mail.o: post_mail.c
+post_mail.o: post_mail.h
+post_mail.o: rec_type.h
+post_mail.o: record.h
+post_mail.o: smtputf8.h
+quote_821_local.o: ../../include/check_arg.h
+quote_821_local.o: ../../include/sys_defs.h
+quote_821_local.o: ../../include/vbuf.h
+quote_821_local.o: ../../include/vstring.h
+quote_821_local.o: quote_821_local.c
+quote_821_local.o: quote_821_local.h
+quote_821_local.o: quote_flags.h
+quote_822_local.o: ../../include/check_arg.h
+quote_822_local.o: ../../include/sys_defs.h
+quote_822_local.o: ../../include/vbuf.h
+quote_822_local.o: ../../include/vstring.h
+quote_822_local.o: quote_822_local.c
+quote_822_local.o: quote_822_local.h
+quote_822_local.o: quote_flags.h
+quote_flags.o: ../../include/check_arg.h
+quote_flags.o: ../../include/name_mask.h
+quote_flags.o: ../../include/sys_defs.h
+quote_flags.o: ../../include/vbuf.h
+quote_flags.o: ../../include/vstring.h
+quote_flags.o: quote_flags.c
+quote_flags.o: quote_flags.h
+rcpt_buf.o: ../../include/attr.h
+rcpt_buf.o: ../../include/check_arg.h
+rcpt_buf.o: ../../include/htable.h
+rcpt_buf.o: ../../include/iostuff.h
+rcpt_buf.o: ../../include/mymalloc.h
+rcpt_buf.o: ../../include/nvtable.h
+rcpt_buf.o: ../../include/sys_defs.h
+rcpt_buf.o: ../../include/vbuf.h
+rcpt_buf.o: ../../include/vstream.h
+rcpt_buf.o: ../../include/vstring.h
+rcpt_buf.o: mail_proto.h
+rcpt_buf.o: rcpt_buf.c
+rcpt_buf.o: rcpt_buf.h
+rcpt_buf.o: recipient_list.h
+rcpt_print.o: ../../include/attr.h
+rcpt_print.o: ../../include/check_arg.h
+rcpt_print.o: ../../include/htable.h
+rcpt_print.o: ../../include/iostuff.h
+rcpt_print.o: ../../include/mymalloc.h
+rcpt_print.o: ../../include/nvtable.h
+rcpt_print.o: ../../include/sys_defs.h
+rcpt_print.o: ../../include/vbuf.h
+rcpt_print.o: ../../include/vstream.h
+rcpt_print.o: ../../include/vstring.h
+rcpt_print.o: mail_proto.h
+rcpt_print.o: rcpt_print.c
+rcpt_print.o: rcpt_print.h
+rcpt_print.o: recipient_list.h
+rec2stream.o: ../../include/check_arg.h
+rec2stream.o: ../../include/sys_defs.h
+rec2stream.o: ../../include/vbuf.h
+rec2stream.o: ../../include/vstream.h
+rec2stream.o: ../../include/vstring.h
+rec2stream.o: rec2stream.c
+rec2stream.o: rec_streamlf.h
+rec2stream.o: rec_type.h
+rec2stream.o: record.h
+rec_attr_map.o: ../../include/attr.h
+rec_attr_map.o: ../../include/check_arg.h
+rec_attr_map.o: ../../include/htable.h
+rec_attr_map.o: ../../include/iostuff.h
+rec_attr_map.o: ../../include/mymalloc.h
+rec_attr_map.o: ../../include/nvtable.h
+rec_attr_map.o: ../../include/sys_defs.h
+rec_attr_map.o: ../../include/vbuf.h
+rec_attr_map.o: ../../include/vstream.h
+rec_attr_map.o: ../../include/vstring.h
+rec_attr_map.o: mail_proto.h
+rec_attr_map.o: rec_attr_map.c
+rec_attr_map.o: rec_attr_map.h
+rec_attr_map.o: rec_type.h
+rec_streamlf.o: ../../include/check_arg.h
+rec_streamlf.o: ../../include/sys_defs.h
+rec_streamlf.o: ../../include/vbuf.h
+rec_streamlf.o: ../../include/vstream.h
+rec_streamlf.o: ../../include/vstring.h
+rec_streamlf.o: rec_streamlf.c
+rec_streamlf.o: rec_streamlf.h
+rec_streamlf.o: rec_type.h
+rec_streamlf.o: record.h
+rec_type.o: rec_type.c
+rec_type.o: rec_type.h
+recdump.o: ../../include/check_arg.h
+recdump.o: ../../include/msg_vstream.h
+recdump.o: ../../include/sys_defs.h
+recdump.o: ../../include/vbuf.h
+recdump.o: ../../include/vstream.h
+recdump.o: ../../include/vstring.h
+recdump.o: rec_streamlf.h
+recdump.o: rec_type.h
+recdump.o: recdump.c
+recdump.o: record.h
+recipient_list.o: ../../include/msg.h
+recipient_list.o: ../../include/mymalloc.h
+recipient_list.o: ../../include/sys_defs.h
+recipient_list.o: recipient_list.c
+recipient_list.o: recipient_list.h
+record.o: ../../include/check_arg.h
+record.o: ../../include/msg.h
+record.o: ../../include/mymalloc.h
+record.o: ../../include/stringops.h
+record.o: ../../include/sys_defs.h
+record.o: ../../include/vbuf.h
+record.o: ../../include/vstream.h
+record.o: ../../include/vstring.h
+record.o: off_cvt.h
+record.o: rec_type.h
+record.o: record.c
+record.o: record.h
+reject_deliver_request.o: ../../include/attr.h
+reject_deliver_request.o: ../../include/check_arg.h
+reject_deliver_request.o: ../../include/htable.h
+reject_deliver_request.o: ../../include/msg.h
+reject_deliver_request.o: ../../include/mymalloc.h
+reject_deliver_request.o: ../../include/nvtable.h
+reject_deliver_request.o: ../../include/sys_defs.h
+reject_deliver_request.o: ../../include/vbuf.h
+reject_deliver_request.o: ../../include/vstream.h
+reject_deliver_request.o: ../../include/vstring.h
+reject_deliver_request.o: bounce.h
+reject_deliver_request.o: defer.h
+reject_deliver_request.o: deliver_completed.h
+reject_deliver_request.o: deliver_request.h
+reject_deliver_request.o: dsn.h
+reject_deliver_request.o: dsn_buf.h
+reject_deliver_request.o: msg_stats.h
+reject_deliver_request.o: recipient_list.h
+reject_deliver_request.o: reject_deliver_request.c
+remove.o: ../../include/check_arg.h
+remove.o: ../../include/sys_defs.h
+remove.o: ../../include/vbuf.h
+remove.o: ../../include/vstring.h
+remove.o: ../../include/warn_stat.h
+remove.o: mail_params.h
+remove.o: remove.c
+resolve_clnt.o: ../../include/attr.h
+resolve_clnt.o: ../../include/check_arg.h
+resolve_clnt.o: ../../include/events.h
+resolve_clnt.o: ../../include/htable.h
+resolve_clnt.o: ../../include/iostuff.h
+resolve_clnt.o: ../../include/msg.h
+resolve_clnt.o: ../../include/mymalloc.h
+resolve_clnt.o: ../../include/nvtable.h
+resolve_clnt.o: ../../include/sys_defs.h
+resolve_clnt.o: ../../include/vbuf.h
+resolve_clnt.o: ../../include/vstream.h
+resolve_clnt.o: ../../include/vstring.h
+resolve_clnt.o: ../../include/vstring_vstream.h
+resolve_clnt.o: clnt_stream.h
+resolve_clnt.o: mail_params.h
+resolve_clnt.o: mail_proto.h
+resolve_clnt.o: resolve_clnt.c
+resolve_clnt.o: resolve_clnt.h
+resolve_local.o: ../../include/argv.h
+resolve_local.o: ../../include/check_arg.h
+resolve_local.o: ../../include/dict.h
+resolve_local.o: ../../include/inet_addr_list.h
+resolve_local.o: ../../include/match_list.h
+resolve_local.o: ../../include/msg.h
+resolve_local.o: ../../include/myaddrinfo.h
+resolve_local.o: ../../include/myflock.h
+resolve_local.o: ../../include/mymalloc.h
+resolve_local.o: ../../include/sys_defs.h
+resolve_local.o: ../../include/valid_hostname.h
+resolve_local.o: ../../include/vbuf.h
+resolve_local.o: ../../include/vstream.h
+resolve_local.o: ../../include/vstring.h
+resolve_local.o: mail_params.h
+resolve_local.o: own_inet_addr.h
+resolve_local.o: resolve_local.c
+resolve_local.o: resolve_local.h
+resolve_local.o: string_list.h
+resolve_local.o: valid_mailhost_addr.h
+rewrite_clnt.o: ../../include/attr.h
+rewrite_clnt.o: ../../include/check_arg.h
+rewrite_clnt.o: ../../include/events.h
+rewrite_clnt.o: ../../include/htable.h
+rewrite_clnt.o: ../../include/iostuff.h
+rewrite_clnt.o: ../../include/msg.h
+rewrite_clnt.o: ../../include/mymalloc.h
+rewrite_clnt.o: ../../include/nvtable.h
+rewrite_clnt.o: ../../include/sys_defs.h
+rewrite_clnt.o: ../../include/vbuf.h
+rewrite_clnt.o: ../../include/vstream.h
+rewrite_clnt.o: ../../include/vstring.h
+rewrite_clnt.o: ../../include/vstring_vstream.h
+rewrite_clnt.o: clnt_stream.h
+rewrite_clnt.o: mail_params.h
+rewrite_clnt.o: mail_proto.h
+rewrite_clnt.o: quote_822_local.h
+rewrite_clnt.o: quote_flags.h
+rewrite_clnt.o: rewrite_clnt.c
+rewrite_clnt.o: rewrite_clnt.h
+safe_ultostr.o: ../../include/check_arg.h
+safe_ultostr.o: ../../include/msg.h
+safe_ultostr.o: ../../include/mymalloc.h
+safe_ultostr.o: ../../include/sys_defs.h
+safe_ultostr.o: ../../include/vbuf.h
+safe_ultostr.o: ../../include/vstring.h
+safe_ultostr.o: safe_ultostr.c
+safe_ultostr.o: safe_ultostr.h
+sasl_mech_filter.o: ../../include/argv.h
+sasl_mech_filter.o: ../../include/check_arg.h
+sasl_mech_filter.o: ../../include/match_list.h
+sasl_mech_filter.o: ../../include/msg.h
+sasl_mech_filter.o: ../../include/mymalloc.h
+sasl_mech_filter.o: ../../include/stringops.h
+sasl_mech_filter.o: ../../include/sys_defs.h
+sasl_mech_filter.o: ../../include/vbuf.h
+sasl_mech_filter.o: ../../include/vstring.h
+sasl_mech_filter.o: sasl_mech_filter.c
+sasl_mech_filter.o: sasl_mech_filter.h
+sasl_mech_filter.o: string_list.h
+scache.o: ../../include/argv.h
+scache.o: ../../include/check_arg.h
+scache.o: ../../include/events.h
+scache.o: ../../include/msg.h
+scache.o: ../../include/sys_defs.h
+scache.o: ../../include/vbuf.h
+scache.o: ../../include/vstream.h
+scache.o: ../../include/vstring.h
+scache.o: ../../include/vstring_vstream.h
+scache.o: scache.c
+scache.o: scache.h
+scache_clnt.o: ../../include/attr.h
+scache_clnt.o: ../../include/auto_clnt.h
+scache_clnt.o: ../../include/check_arg.h
+scache_clnt.o: ../../include/htable.h
+scache_clnt.o: ../../include/iostuff.h
+scache_clnt.o: ../../include/msg.h
+scache_clnt.o: ../../include/mymalloc.h
+scache_clnt.o: ../../include/nvtable.h
+scache_clnt.o: ../../include/stringops.h
+scache_clnt.o: ../../include/sys_defs.h
+scache_clnt.o: ../../include/vbuf.h
+scache_clnt.o: ../../include/vstream.h
+scache_clnt.o: ../../include/vstring.h
+scache_clnt.o: mail_params.h
+scache_clnt.o: mail_proto.h
+scache_clnt.o: scache.h
+scache_clnt.o: scache_clnt.c
+scache_multi.o: ../../include/check_arg.h
+scache_multi.o: ../../include/events.h
+scache_multi.o: ../../include/htable.h
+scache_multi.o: ../../include/msg.h
+scache_multi.o: ../../include/mymalloc.h
+scache_multi.o: ../../include/ring.h
+scache_multi.o: ../../include/sys_defs.h
+scache_multi.o: ../../include/vbuf.h
+scache_multi.o: ../../include/vstring.h
+scache_multi.o: scache.h
+scache_multi.o: scache_multi.c
+scache_single.o: ../../include/check_arg.h
+scache_single.o: ../../include/events.h
+scache_single.o: ../../include/msg.h
+scache_single.o: ../../include/mymalloc.h
+scache_single.o: ../../include/sys_defs.h
+scache_single.o: ../../include/vbuf.h
+scache_single.o: ../../include/vstring.h
+scache_single.o: scache.h
+scache_single.o: scache_single.c
+sent.o: ../../include/attr.h
+sent.o: ../../include/check_arg.h
+sent.o: ../../include/htable.h
+sent.o: ../../include/msg.h
+sent.o: ../../include/mymalloc.h
+sent.o: ../../include/nvtable.h
+sent.o: ../../include/sys_defs.h
+sent.o: ../../include/vbuf.h
+sent.o: ../../include/vstream.h
+sent.o: ../../include/vstring.h
+sent.o: bounce.h
+sent.o: defer.h
+sent.o: deliver_request.h
+sent.o: dsn.h
+sent.o: dsn_buf.h
+sent.o: dsn_filter.h
+sent.o: dsn_mask.h
+sent.o: dsn_util.h
+sent.o: log_adhoc.h
+sent.o: mail_params.h
+sent.o: msg_stats.h
+sent.o: recipient_list.h
+sent.o: sent.c
+sent.o: sent.h
+sent.o: trace.h
+sent.o: verify.h
+server_acl.o: ../../include/argv.h
+server_acl.o: ../../include/check_arg.h
+server_acl.o: ../../include/dict.h
+server_acl.o: ../../include/match_list.h
+server_acl.o: ../../include/msg.h
+server_acl.o: ../../include/myflock.h
+server_acl.o: ../../include/mymalloc.h
+server_acl.o: ../../include/stringops.h
+server_acl.o: ../../include/sys_defs.h
+server_acl.o: ../../include/vbuf.h
+server_acl.o: ../../include/vstream.h
+server_acl.o: ../../include/vstring.h
+server_acl.o: addr_match_list.h
+server_acl.o: mail_params.h
+server_acl.o: match_parent_style.h
+server_acl.o: mynetworks.h
+server_acl.o: server_acl.c
+server_acl.o: server_acl.h
+smtp_reply_footer.o: ../../include/check_arg.h
+smtp_reply_footer.o: ../../include/mac_expand.h
+smtp_reply_footer.o: ../../include/mac_parse.h
+smtp_reply_footer.o: ../../include/msg.h
+smtp_reply_footer.o: ../../include/mymalloc.h
+smtp_reply_footer.o: ../../include/sys_defs.h
+smtp_reply_footer.o: ../../include/vbuf.h
+smtp_reply_footer.o: ../../include/vstring.h
+smtp_reply_footer.o: dsn_util.h
+smtp_reply_footer.o: smtp_reply_footer.c
+smtp_reply_footer.o: smtp_reply_footer.h
+smtp_stream.o: ../../include/check_arg.h
+smtp_stream.o: ../../include/iostuff.h
+smtp_stream.o: ../../include/msg.h
+smtp_stream.o: ../../include/sys_defs.h
+smtp_stream.o: ../../include/vbuf.h
+smtp_stream.o: ../../include/vstream.h
+smtp_stream.o: ../../include/vstring.h
+smtp_stream.o: ../../include/vstring_vstream.h
+smtp_stream.o: smtp_stream.c
+smtp_stream.o: smtp_stream.h
+smtputf8.o: ../../include/attr.h
+smtputf8.o: ../../include/check_arg.h
+smtputf8.o: ../../include/htable.h
+smtputf8.o: ../../include/iostuff.h
+smtputf8.o: ../../include/msg.h
+smtputf8.o: ../../include/mymalloc.h
+smtputf8.o: ../../include/name_mask.h
+smtputf8.o: ../../include/nvtable.h
+smtputf8.o: ../../include/sys_defs.h
+smtputf8.o: ../../include/vbuf.h
+smtputf8.o: ../../include/vstream.h
+smtputf8.o: ../../include/vstring.h
+smtputf8.o: cleanup_user.h
+smtputf8.o: mail_params.h
+smtputf8.o: mail_proto.h
+smtputf8.o: smtputf8.c
+smtputf8.o: smtputf8.h
+split_addr.o: ../../include/check_arg.h
+split_addr.o: ../../include/split_at.h
+split_addr.o: ../../include/stringops.h
+split_addr.o: ../../include/sys_defs.h
+split_addr.o: ../../include/vbuf.h
+split_addr.o: ../../include/vstring.h
+split_addr.o: mail_addr.h
+split_addr.o: mail_params.h
+split_addr.o: split_addr.c
+split_addr.o: split_addr.h
+stream2rec.o: ../../include/check_arg.h
+stream2rec.o: ../../include/sys_defs.h
+stream2rec.o: ../../include/vbuf.h
+stream2rec.o: ../../include/vstream.h
+stream2rec.o: ../../include/vstring.h
+stream2rec.o: rec_streamlf.h
+stream2rec.o: rec_type.h
+stream2rec.o: record.h
+stream2rec.o: stream2rec.c
+string_list.o: ../../include/argv.h
+string_list.o: ../../include/check_arg.h
+string_list.o: ../../include/match_list.h
+string_list.o: ../../include/sys_defs.h
+string_list.o: ../../include/vbuf.h
+string_list.o: ../../include/vstring.h
+string_list.o: string_list.c
+string_list.o: string_list.h
+strip_addr.o: ../../include/mymalloc.h
+strip_addr.o: ../../include/sys_defs.h
+strip_addr.o: split_addr.h
+strip_addr.o: strip_addr.c
+strip_addr.o: strip_addr.h
+sys_exits.o: ../../include/check_arg.h
+sys_exits.o: ../../include/msg.h
+sys_exits.o: ../../include/sys_defs.h
+sys_exits.o: ../../include/vbuf.h
+sys_exits.o: ../../include/vstring.h
+sys_exits.o: sys_exits.c
+sys_exits.o: sys_exits.h
+test_main.o: ../../include/argv.h
+test_main.o: ../../include/check_arg.h
+test_main.o: ../../include/dict.h
+test_main.o: ../../include/msg.h
+test_main.o: ../../include/msg_vstream.h
+test_main.o: ../../include/myflock.h
+test_main.o: ../../include/mymalloc.h
+test_main.o: ../../include/stringops.h
+test_main.o: ../../include/sys_defs.h
+test_main.o: ../../include/vbuf.h
+test_main.o: ../../include/vstream.h
+test_main.o: ../../include/vstring.h
+test_main.o: mail_conf.h
+test_main.o: mail_dict.h
+test_main.o: mail_params.h
+test_main.o: mail_task.h
+test_main.o: mail_version.h
+test_main.o: test_main.c
+test_main.o: test_main.h
+timed_ipc.o: ../../include/check_arg.h
+timed_ipc.o: ../../include/msg.h
+timed_ipc.o: ../../include/sys_defs.h
+timed_ipc.o: ../../include/vbuf.h
+timed_ipc.o: ../../include/vstream.h
+timed_ipc.o: mail_params.h
+timed_ipc.o: timed_ipc.c
+timed_ipc.o: timed_ipc.h
+tok822_find.o: ../../include/check_arg.h
+tok822_find.o: ../../include/sys_defs.h
+tok822_find.o: ../../include/vbuf.h
+tok822_find.o: ../../include/vstring.h
+tok822_find.o: resolve_clnt.h
+tok822_find.o: tok822.h
+tok822_find.o: tok822_find.c
+tok822_node.o: ../../include/check_arg.h
+tok822_node.o: ../../include/mymalloc.h
+tok822_node.o: ../../include/sys_defs.h
+tok822_node.o: ../../include/vbuf.h
+tok822_node.o: ../../include/vstring.h
+tok822_node.o: resolve_clnt.h
+tok822_node.o: tok822.h
+tok822_node.o: tok822_node.c
+tok822_parse.o: ../../include/check_arg.h
+tok822_parse.o: ../../include/msg.h
+tok822_parse.o: ../../include/stringops.h
+tok822_parse.o: ../../include/sys_defs.h
+tok822_parse.o: ../../include/vbuf.h
+tok822_parse.o: ../../include/vstring.h
+tok822_parse.o: lex_822.h
+tok822_parse.o: quote_822_local.h
+tok822_parse.o: quote_flags.h
+tok822_parse.o: resolve_clnt.h
+tok822_parse.o: tok822.h
+tok822_parse.o: tok822_parse.c
+tok822_resolve.o: ../../include/check_arg.h
+tok822_resolve.o: ../../include/msg.h
+tok822_resolve.o: ../../include/sys_defs.h
+tok822_resolve.o: ../../include/vbuf.h
+tok822_resolve.o: ../../include/vstring.h
+tok822_resolve.o: resolve_clnt.h
+tok822_resolve.o: tok822.h
+tok822_resolve.o: tok822_resolve.c
+tok822_rewrite.o: ../../include/attr.h
+tok822_rewrite.o: ../../include/check_arg.h
+tok822_rewrite.o: ../../include/htable.h
+tok822_rewrite.o: ../../include/iostuff.h
+tok822_rewrite.o: ../../include/msg.h
+tok822_rewrite.o: ../../include/mymalloc.h
+tok822_rewrite.o: ../../include/nvtable.h
+tok822_rewrite.o: ../../include/sys_defs.h
+tok822_rewrite.o: ../../include/vbuf.h
+tok822_rewrite.o: ../../include/vstream.h
+tok822_rewrite.o: ../../include/vstring.h
+tok822_rewrite.o: mail_proto.h
+tok822_rewrite.o: resolve_clnt.h
+tok822_rewrite.o: rewrite_clnt.h
+tok822_rewrite.o: tok822.h
+tok822_rewrite.o: tok822_rewrite.c
+tok822_tree.o: ../../include/check_arg.h
+tok822_tree.o: ../../include/mymalloc.h
+tok822_tree.o: ../../include/sys_defs.h
+tok822_tree.o: ../../include/vbuf.h
+tok822_tree.o: ../../include/vstring.h
+tok822_tree.o: resolve_clnt.h
+tok822_tree.o: tok822.h
+tok822_tree.o: tok822_tree.c
+trace.o: ../../include/attr.h
+trace.o: ../../include/check_arg.h
+trace.o: ../../include/htable.h
+trace.o: ../../include/iostuff.h
+trace.o: ../../include/msg.h
+trace.o: ../../include/mymalloc.h
+trace.o: ../../include/nvtable.h
+trace.o: ../../include/sys_defs.h
+trace.o: ../../include/vbuf.h
+trace.o: ../../include/vstream.h
+trace.o: ../../include/vstring.h
+trace.o: bounce.h
+trace.o: deliver_request.h
+trace.o: dsn.h
+trace.o: dsn_buf.h
+trace.o: dsn_print.h
+trace.o: log_adhoc.h
+trace.o: mail_params.h
+trace.o: mail_proto.h
+trace.o: msg_stats.h
+trace.o: rcpt_print.h
+trace.o: recipient_list.h
+trace.o: trace.c
+trace.o: trace.h
+user_acl.o: ../../include/argv.h
+user_acl.o: ../../include/check_arg.h
+user_acl.o: ../../include/dict.h
+user_acl.o: ../../include/dict_static.h
+user_acl.o: ../../include/match_list.h
+user_acl.o: ../../include/myflock.h
+user_acl.o: ../../include/sys_defs.h
+user_acl.o: ../../include/vbuf.h
+user_acl.o: ../../include/vstream.h
+user_acl.o: ../../include/vstring.h
+user_acl.o: mypwd.h
+user_acl.o: string_list.h
+user_acl.o: user_acl.c
+user_acl.o: user_acl.h
+uxtext.o: ../../include/check_arg.h
+uxtext.o: ../../include/msg.h
+uxtext.o: ../../include/sys_defs.h
+uxtext.o: ../../include/vbuf.h
+uxtext.o: ../../include/vstring.h
+uxtext.o: uxtext.c
+uxtext.o: uxtext.h
+valid_mailhost_addr.o: ../../include/msg.h
+valid_mailhost_addr.o: ../../include/myaddrinfo.h
+valid_mailhost_addr.o: ../../include/sys_defs.h
+valid_mailhost_addr.o: ../../include/valid_hostname.h
+valid_mailhost_addr.o: valid_mailhost_addr.c
+valid_mailhost_addr.o: valid_mailhost_addr.h
+verify.o: ../../include/attr.h
+verify.o: ../../include/check_arg.h
+verify.o: ../../include/htable.h
+verify.o: ../../include/iostuff.h
+verify.o: ../../include/msg.h
+verify.o: ../../include/mymalloc.h
+verify.o: ../../include/nvtable.h
+verify.o: ../../include/stringops.h
+verify.o: ../../include/sys_defs.h
+verify.o: ../../include/vbuf.h
+verify.o: ../../include/vstream.h
+verify.o: ../../include/vstring.h
+verify.o: deliver_request.h
+verify.o: dsn.h
+verify.o: log_adhoc.h
+verify.o: mail_params.h
+verify.o: mail_proto.h
+verify.o: msg_stats.h
+verify.o: recipient_list.h
+verify.o: verify.c
+verify.o: verify.h
+verify.o: verify_clnt.h
+verify_clnt.o: ../../include/attr.h
+verify_clnt.o: ../../include/check_arg.h
+verify_clnt.o: ../../include/htable.h
+verify_clnt.o: ../../include/iostuff.h
+verify_clnt.o: ../../include/msg.h
+verify_clnt.o: ../../include/mymalloc.h
+verify_clnt.o: ../../include/nvtable.h
+verify_clnt.o: ../../include/sys_defs.h
+verify_clnt.o: ../../include/vbuf.h
+verify_clnt.o: ../../include/vstream.h
+verify_clnt.o: ../../include/vstring.h
+verify_clnt.o: clnt_stream.h
+verify_clnt.o: deliver_request.h
+verify_clnt.o: dsn.h
+verify_clnt.o: mail_params.h
+verify_clnt.o: mail_proto.h
+verify_clnt.o: msg_stats.h
+verify_clnt.o: recipient_list.h
+verify_clnt.o: verify_clnt.c
+verify_clnt.o: verify_clnt.h
+verify_sender_addr.o: ../../include/attr.h
+verify_sender_addr.o: ../../include/check_arg.h
+verify_sender_addr.o: ../../include/events.h
+verify_sender_addr.o: ../../include/htable.h
+verify_sender_addr.o: ../../include/iostuff.h
+verify_sender_addr.o: ../../include/msg.h
+verify_sender_addr.o: ../../include/mymalloc.h
+verify_sender_addr.o: ../../include/nvtable.h
+verify_sender_addr.o: ../../include/stringops.h
+verify_sender_addr.o: ../../include/sys_defs.h
+verify_sender_addr.o: ../../include/vbuf.h
+verify_sender_addr.o: ../../include/vstream.h
+verify_sender_addr.o: ../../include/vstring.h
+verify_sender_addr.o: mail_params.h
+verify_sender_addr.o: mail_proto.h
+verify_sender_addr.o: rewrite_clnt.h
+verify_sender_addr.o: safe_ultostr.h
+verify_sender_addr.o: verify_sender_addr.c
+verify_sender_addr.o: verify_sender_addr.h
+verp_sender.o: ../../include/check_arg.h
+verp_sender.o: ../../include/sys_defs.h
+verp_sender.o: ../../include/vbuf.h
+verp_sender.o: ../../include/vstring.h
+verp_sender.o: mail_params.h
+verp_sender.o: recipient_list.h
+verp_sender.o: verp_sender.c
+verp_sender.o: verp_sender.h
+wildcard_inet_addr.o: ../../include/inet_addr_host.h
+wildcard_inet_addr.o: ../../include/inet_addr_list.h
+wildcard_inet_addr.o: ../../include/msg.h
+wildcard_inet_addr.o: ../../include/myaddrinfo.h
+wildcard_inet_addr.o: ../../include/sys_defs.h
+wildcard_inet_addr.o: wildcard_inet_addr.c
+wildcard_inet_addr.o: wildcard_inet_addr.h
+xtext.o: ../../include/check_arg.h
+xtext.o: ../../include/msg.h
+xtext.o: ../../include/sys_defs.h
+xtext.o: ../../include/vbuf.h
+xtext.o: ../../include/vstring.h
+xtext.o: xtext.c
+xtext.o: xtext.h
diff --git a/src/global/abounce.c b/src/global/abounce.c
new file mode 100644
index 0000000..5522f60
--- /dev/null
+++ b/src/global/abounce.c
@@ -0,0 +1,466 @@
+/*++
+/* NAME
+/* abounce 3
+/* SUMMARY
+/* asynchronous bounce/defer/trace service client
+/* SYNOPSIS
+/* #include <abounce.h>
+/*
+/* void abounce_flush(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/*
+/* void abounce_flush_verp(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, verp, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* const char *verp;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/*
+/* void adefer_flush(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/*
+/* void adefer_flush_verp(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, verp, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* const char *verp;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/*
+/* void adefer_warn(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/*
+/* void atrace_flush(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, callback, context)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* void (*callback)(int status, void *context);
+/* void *context;
+/* DESCRIPTION
+/* This module implements an asynchronous interface to the
+/* bounce/defer/trace service for submitting sender notifications
+/* without waiting for completion of the request.
+/*
+/* abounce_flush() bounces the specified message to
+/* the specified sender, including the bounce log that was
+/* built with bounce_append().
+/*
+/* abounce_flush_verp() is like abounce_flush() but sends
+/* one VERP style notification per undeliverable recipient.
+/*
+/* adefer_flush() bounces the specified message to
+/* the specified sender, including the defer log that was
+/* built with defer_append().
+/* adefer_flush() requests that the deferred recipients are deleted
+/* from the original queue file.
+/*
+/* adefer_flush_verp() is like adefer_flush() but sends
+/* one VERP style notification per undeliverable recipient.
+/*
+/* adefer_warn() sends a "mail is delayed" notification to
+/* the specified sender, including the defer log that was
+/* built with defer_append().
+/*
+/* atrace_flush() returns the specified message to the specified
+/* sender, including the message delivery record log that was
+/* built with vtrace_append().
+/*
+/* Arguments:
+/* .IP flags
+/* The bitwise OR of zero or more of the following (specify
+/* BOUNCE_FLAG_NONE to request no special processing):
+/* .RS
+/* .IP BOUNCE_FLAG_CLEAN
+/* Delete the bounce log in case of an error (as in: pretend
+/* that we never even tried to bounce this message).
+/* .IP BOUNCE_FLAG_DELRCPT
+/* When specified with a flush operation, request that
+/* recipients be deleted from the queue file.
+/*
+/* Note: the bounce daemon ignores this request when the
+/* recipient queue file offset is <= 0.
+/* .IP BOUNCE_FLAG_COPY
+/* Request that a postmaster copy is sent.
+/* .RE
+/* .IP queue
+/* The message queue name of the original message file.
+/* .IP id
+/* The message queue id if the original message file. The bounce log
+/* file has the same name as the original message file.
+/* .IP encoding
+/* The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
+/* .IP smtputf8
+/* The level of SMTPUTF8 support (to be defined).
+/* .IP sender
+/* The sender envelope address.
+/* .IP dsn_envid
+/* Optional DSN envelope ID.
+/* .IP ret
+/* Optional DSN return full/headers option.
+/* .IP verp
+/* VERP delimiter characters.
+/* .IP callback
+/* Name of a routine that receives the notification status as
+/* documented for bounce_flush() or defer_flush().
+/* .IP context
+/* Application-specific context that is passed through to the
+/* callback routine. Use proper casts or the world will come
+/* to an end.
+/* DIAGNOSTICS
+/* In case of success, these functions log the action, and return a
+/* zero result via the callback routine. Otherwise, the functions
+/* return a non-zero result via the callback routine, and when
+/* BOUNCE_FLAG_CLEAN is disabled, log that message delivery is deferred.
+/* 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 <events.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <abounce.h>
+
+/* Application-specific. */
+
+ /*
+ * Each bounce/defer flush/warn request is implemented by sending the
+ * request to the bounce/defer server, and by creating a pseudo thread that
+ * suspends itself until the server replies (or dies). Upon wakeup, the
+ * pseudo thread delivers the request completion status to the application
+ * and destroys itself. The structure below maintains all the necessary
+ * request state while the pseudo thread is suspended.
+ */
+typedef struct {
+ int command; /* bounce request type */
+ int flags; /* bounce options */
+ char *id; /* queue ID for logging */
+ VSTRING *request; /* serialized request */
+ ABOUNCE_FN callback; /* application callback */
+ void *context; /* application context */
+ VSTREAM *fp; /* server I/O handle */
+} ABOUNCE_STATE;
+
+ /*
+ * Encapsulate common code.
+ */
+#define ABOUNCE_EVENT_ENABLE(fd, callback, context, timeout) do { \
+ event_enable_read((fd), (callback), (context)); \
+ event_request_timer((callback), (context), (timeout)); \
+ } while (0)
+
+ /*
+ * If we set the reply timeout too short, then we make the problem worse by
+ * increasing overload. With 1000s timeout mail will keep flowing, but there
+ * will be a large number of blocked bounce processes, and some resource is
+ * likely to run out.
+ */
+#define ABOUNCE_TIMEOUT 1000
+
+ /*
+ * The initial buffer size for a serialized request.
+ */
+#define ABOUNCE_BUFSIZE VSTREAM_BUFSIZE
+
+ /*
+ * We share most of the verp and non-verp code paths.
+ */
+#define ABOUNCE_NO_VERP ((char *) 0)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* abounce_done - deliver status to application and clean up pseudo thread */
+
+static void abounce_done(ABOUNCE_STATE *ap, int status)
+{
+ if (ap->fp) {
+ event_disable_readwrite(vstream_fileno(ap->fp));
+ (void) vstream_fclose(ap->fp);
+ }
+ if (status != 0 && (ap->flags & BOUNCE_FLAG_CLEAN) == 0)
+ msg_info("%s: status=deferred (%s failed)", ap->id,
+ ap->command == BOUNCE_CMD_FLUSH ? "bounce" :
+ ap->command == BOUNCE_CMD_WARN ? "delay warning" :
+ ap->command == BOUNCE_CMD_VERP ? "verp" :
+ ap->command == BOUNCE_CMD_TRACE ? "trace" :
+ "whatever");
+ ap->callback(status, ap->context);
+ myfree(ap->id);
+ vstring_free(ap->request);
+ myfree((void *) ap);
+}
+
+/* abounce_receive - receive server reply */
+
+static void abounce_receive(int event, void *context)
+{
+ ABOUNCE_STATE *ap = (ABOUNCE_STATE *) context;
+ int status;
+
+ if (event != EVENT_TIME)
+ event_cancel_timer(abounce_receive, context);
+
+ if (event == EVENT_READ
+ && attr_scan(ap->fp, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) == 1) {
+ abounce_done(ap, status);
+ } else {
+ abounce_done(ap, -1);
+ }
+}
+
+/* abounce_send - send the request and suspend until the server replies */
+
+static void abounce_send(int event, void *context)
+{
+ ABOUNCE_STATE *ap = (ABOUNCE_STATE *) context;
+
+ /*
+ * Receive the server's protocol name announcement. At this point the
+ * server is ready to receive a request without blocking the sender. Send
+ * the request and suspend until the server replies (or dies).
+ */
+ if (event != EVENT_TIME)
+ event_cancel_timer(abounce_send, context);
+
+ non_blocking(vstream_fileno(ap->fp), BLOCKING);
+ if (event == EVENT_READ
+ && attr_scan(ap->fp, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_BOUNCE),
+ ATTR_TYPE_END) == 0
+ && vstream_fwrite(ap->fp, STR(ap->request),
+ LEN(ap->request)) == LEN(ap->request)
+ && vstream_fflush(ap->fp) == 0) {
+ ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_receive,
+ (void *) ap, ABOUNCE_TIMEOUT);
+ } else {
+ abounce_done(ap, -1);
+ }
+}
+
+/* abounce_connect - connect and suspend until the server replies */
+
+static void abounce_connect(const char *class, const char *service,
+ int command, int flags,
+ const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender,
+ const char *dsn_envid, int dsn_ret,
+ const char *verp, ABOUNCE_FN callback,
+ void *context)
+{
+ ABOUNCE_STATE *ap;
+
+ /*
+ * Save pseudo thread state. Connect to the server. Prior to Postfix 3.6
+ * the asynchronous bounce flush/warn client called mail_connect_wait()
+ * which sleeps and retries several times before terminating with a fatal
+ * error. This block-and-sleep behavior was not consistent with a) the
+ * rest of the code in this module, and with b) the synchronous bounce
+ * client which gives up immediately. It should be safe to give up
+ * immediately because that leaves the bounce/defer/trace logs in the
+ * queue. In particular, this should not increase the simultaneous number
+ * of asynchronous bounce/defer/trace flush/warn requests that are in
+ * flight.
+ */
+ ap = (ABOUNCE_STATE *) mymalloc(sizeof(*ap));
+ ap->command = command;
+ ap->flags = flags;
+ ap->id = mystrdup(id);
+ ap->request = vstring_alloc(ABOUNCE_BUFSIZE);
+ ap->callback = callback;
+ ap->context = context;
+ ap->fp = mail_connect(class, service, NON_BLOCKING);
+
+ /*
+ * Format the request now, so that we don't have to save a lot of
+ * arguments now and format the request later.
+ */
+ if (ap->fp != 0) {
+ /* Note: all code paths must terminate or enable I/O events. */
+ VSTREAM *mp = vstream_memopen(ap->request, O_WRONLY);
+
+ if (attr_print(mp, ATTR_FLAG_MORE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, command),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ ATTR_TYPE_END) != 0
+ || (verp != 0
+ && attr_print(mp, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_VERPDL, verp),
+ ATTR_TYPE_END) != 0)
+ || attr_print(mp, ATTR_FLAG_NONE,
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_panic("abounce_connect: write request to memory stream: %m");
+
+ /*
+ * Suspend until the server replies (or dies).
+ */
+ ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_send,
+ (void *) ap, ABOUNCE_TIMEOUT);
+ } else {
+ abounce_done(ap, -1);
+ }
+}
+
+/* abounce_flush_verp - asynchronous bounce flush */
+
+void abounce_flush_verp(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, const char *verp,
+ ABOUNCE_FN callback,
+ void *context)
+{
+ abounce_connect(MAIL_CLASS_PRIVATE, var_bounce_service,
+ BOUNCE_CMD_VERP, flags, queue, id, encoding, smtputf8,
+ sender, dsn_envid, dsn_ret, verp, callback, context);
+}
+
+/* adefer_flush_verp - asynchronous defer flush */
+
+void adefer_flush_verp(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, const char *verp,
+ ABOUNCE_FN callback, void *context)
+{
+ flags |= BOUNCE_FLAG_DELRCPT;
+ abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service,
+ BOUNCE_CMD_VERP, flags, queue, id, encoding, smtputf8,
+ sender, dsn_envid, dsn_ret, verp, callback, context);
+}
+
+/* abounce_flush - asynchronous bounce flush */
+
+void abounce_flush(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, ABOUNCE_FN callback,
+ void *context)
+{
+ abounce_connect(MAIL_CLASS_PRIVATE, var_bounce_service, BOUNCE_CMD_FLUSH,
+ flags, queue, id, encoding, smtputf8, sender, dsn_envid,
+ dsn_ret, ABOUNCE_NO_VERP, callback, context);
+}
+
+/* adefer_flush - asynchronous defer flush */
+
+void adefer_flush(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, ABOUNCE_FN callback, void *context)
+{
+ flags |= BOUNCE_FLAG_DELRCPT;
+ abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_FLUSH,
+ flags, queue, id, encoding, smtputf8, sender, dsn_envid,
+ dsn_ret, ABOUNCE_NO_VERP, callback, context);
+}
+
+/* adefer_warn - send copy of defer log to sender as warning bounce */
+
+void adefer_warn(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, ABOUNCE_FN callback, void *context)
+{
+ abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_WARN,
+ flags, queue, id, encoding, smtputf8, sender, dsn_envid,
+ dsn_ret, ABOUNCE_NO_VERP, callback, context);
+}
+
+/* atrace_flush - asynchronous trace flush */
+
+void atrace_flush(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, ABOUNCE_FN callback, void *context)
+{
+ abounce_connect(MAIL_CLASS_PRIVATE, var_trace_service, BOUNCE_CMD_TRACE,
+ flags, queue, id, encoding, smtputf8, sender, dsn_envid,
+ dsn_ret, ABOUNCE_NO_VERP, callback, context);
+}
diff --git a/src/global/abounce.h b/src/global/abounce.h
new file mode 100644
index 0000000..ea24ed8
--- /dev/null
+++ b/src/global/abounce.h
@@ -0,0 +1,43 @@
+#ifndef _ABOUNCE_H_INCLUDED_
+#define _ABOUNCE_H_INCLUDED_
+
+/*++
+/* NAME
+/* abounce 3h
+/* SUMMARY
+/* asynchronous bounce/defer service client
+/* SYNOPSIS
+/* #include <abounce.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <bounce.h>
+
+ /*
+ * Client interface.
+ */
+typedef void (*ABOUNCE_FN) (int, void *);
+
+extern void abounce_flush(int, const char *, const char *, const char *, int, const char *, const char *, int, ABOUNCE_FN, void *);
+extern void adefer_flush(int, const char *, const char *, const char *, int, const char *, const char *, int, ABOUNCE_FN, void *);
+extern void adefer_warn(int, const char *, const char *, const char *, int, const char *, const char *, int, ABOUNCE_FN, void *);
+extern void atrace_flush(int, const char *, const char *, const char *, int, const char *, const char *, int, ABOUNCE_FN, void *);
+
+extern void abounce_flush_verp(int, const char *, const char *, const char *, int, const char *, const char *, int, const char *, ABOUNCE_FN, void *);
+extern void adefer_flush_verp(int, const char *, const char *, const char *, int, const char *, const char *, int, const char *, ABOUNCE_FN, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/addr_match_list.c b/src/global/addr_match_list.c
new file mode 100644
index 0000000..8008df2
--- /dev/null
+++ b/src/global/addr_match_list.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* addr_match_list 3
+/* SUMMARY
+/* address list membership
+/* SYNOPSIS
+/* #include <addr_match_list.h>
+/*
+/* ADDR_MATCH_LIST *addr_match_list_init(pname, flags, pattern_list)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/*
+/* int addr_match_list_match(list, addr)
+/* ADDR_MATCH_LIST *list;
+/* const char *addr;
+/*
+/* void addr_match_list_free(list)
+/* ADDR_MATCH_LIST *list;
+/* DESCRIPTION
+/* This is a convenience wrapper around the match_list module.
+/*
+/* This module implements tests for list membership of a
+/* network address.
+/*
+/* A list pattern specifies an internet address, or a network/mask
+/* pattern, where the mask specifies the number of bits in the
+/* network part. When a pattern specifies a file name, its
+/* contents are substituted for the file name; when a pattern
+/* is a type:name table specification, table lookup is used
+/* instead. Patterns are separated by whitespace and/or commas.
+/* In order to reverse the result, precede a pattern with an
+/* exclamation point (!).
+/*
+/* A host matches a list when its address matches a pattern.
+/* The matching process is case insensitive.
+/*
+/* addr_match_list_init() performs initializations. The pname
+/* argument specifies error reporting context. The flags
+/* argument is the bit-wise OR of zero or more of the following:
+/* .IP MATCH_FLAG_RETURN
+/* Request that addr_match_list_match() logs a warning and
+/* returns zero with list->error set to a non-zero dictionary
+/* error code, instead of raising a fatal error.
+/* .PP
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* The last argument is a list of patterns, or the absolute
+/* pathname of a file with patterns.
+/*
+/* addr_match_list_match() matches the specified host address
+/* against the specified list of patterns.
+/*
+/* addr_match_list_free() releases storage allocated by
+/* addr_match_list_init().
+/* DIAGNOSTICS
+/* Fatal errors: unable to open or read a pattern file; invalid
+/* pattern. Panic: interface violations.
+/* SEE ALSO
+/* match_list(3) generic list matching
+/* match_ops(3) match host by name or by address
+/* 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 <match_list.h>
+
+/* Global library. */
+
+#include "addr_match_list.h"
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+#include <dict.h>
+#include <stringops.h> /* util_utf8_enable */
+
+static void usage(char *progname)
+{
+ msg_fatal("usage: %s [-v] pattern_list address", progname);
+}
+
+int main(int argc, char **argv)
+{
+ ADDR_MATCH_LIST *list;
+ char *addr;
+ int ch;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 2)
+ usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ list = addr_match_list_init("command line", MATCH_FLAG_PARENT
+ | MATCH_FLAG_RETURN, argv[optind]);
+ addr = argv[optind + 1];
+ if (strcmp(addr, "-") == 0) {
+ VSTRING *buf = vstring_alloc(100);
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF)
+ vstream_printf("%s: %s\n", vstring_str(buf),
+ addr_match_list_match(list, vstring_str(buf)) ?
+ "YES" : list->error == 0 ? "NO" : "ERROR");
+ vstring_free(buf);
+ } else {
+ vstream_printf("%s: %s\n", addr,
+ addr_match_list_match(list, addr) > 0 ?
+ "YES" : list->error == 0 ? "NO" : "ERROR");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ addr_match_list_free(list);
+ return (0);
+}
+
+#endif
diff --git a/src/global/addr_match_list.h b/src/global/addr_match_list.h
new file mode 100644
index 0000000..f03c09d
--- /dev/null
+++ b/src/global/addr_match_list.h
@@ -0,0 +1,41 @@
+#ifndef _ADDR_MATCH_LIST_H_INCLUDED_
+#define _ADDR_MATCH_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* addr 3h
+/* SUMMARY
+/* address list membership
+/* SYNOPSIS
+/* #include <addr_match_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <match_list.h>
+
+ /*
+ * External interface.
+ */
+#define ADDR_MATCH_LIST MATCH_LIST
+
+#define addr_match_list_init(o, f, p) \
+ match_list_init((o), (f), (p), 1, match_hostaddr)
+#define addr_match_list_match(l, a) \
+ match_list_match((l), (a))
+#define addr_match_list_free match_list_free
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/anvil_clnt.c b/src/global/anvil_clnt.c
new file mode 100644
index 0000000..fff9ec7
--- /dev/null
+++ b/src/global/anvil_clnt.c
@@ -0,0 +1,527 @@
+/*++
+/* NAME
+/* anvil_clnt 3
+/* SUMMARY
+/* connection count and rate management client interface
+/* SYNOPSIS
+/* #include <anvil_clnt.h>
+/*
+/* ANVIL_CLNT *anvil_clnt_create(void)
+/*
+/* void anvil_clnt_free(anvil_clnt)
+/* ANVIL_CLNT *anvil_clnt;
+/*
+/* int anvil_clnt_connect(anvil_clnt, service, addr,
+/* count, rate)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *count;
+/* int *rate;
+/*
+/* int anvil_clnt_mail(anvil_clnt, service, addr, msgs)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *msgs;
+/*
+/* int anvil_clnt_rcpt(anvil_clnt, service, addr, rcpts)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *rcpts;
+/*
+/* int anvil_clnt_newtls(anvil_clnt, service, addr, newtls)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *newtls;
+/*
+/* int anvil_clnt_newtls_stat(anvil_clnt, service, addr, newtls)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *newtls;
+/*
+/* int anvil_clnt_auth(anvil_clnt, service, addr, auths)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *auths;
+/*
+/* int anvil_clnt_disconnect(anvil_clnt, service, addr)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/*
+/* int anvil_clnt_lookup(anvil_clnt, service, addr, count,
+/* rate, msgs, rcpts, ntls, auths)
+/* ANVIL_CLNT *anvil_clnt;
+/* const char *service;
+/* const char *addr;
+/* int *count;
+/* int *rate;
+/* int *msgs;
+/* int *rcpts;
+/* int *ntls;
+/* int *auths;
+/* DESCRIPTION
+/* anvil_clnt_create() instantiates a local anvil service
+/* client endpoint.
+/*
+/* anvil_clnt_connect() informs the anvil server that a
+/* remote client has connected, and returns the current
+/* connection count and connection rate for that remote client.
+/*
+/* anvil_clnt_mail() registers a MAIL FROM event and
+/* returns the current MAIL FROM rate for the specified remote
+/* client.
+/*
+/* anvil_clnt_rcpt() registers a RCPT TO event and
+/* returns the current RCPT TO rate for the specified remote
+/* client.
+/*
+/* anvil_clnt_newtls() registers a remote client request
+/* to negotiate a new (uncached) TLS session and returns the
+/* current newtls request rate for the specified remote client.
+/*
+/* anvil_clnt_newtls_stat() returns the current newtls request
+/* rate for the specified remote client.
+/*
+/* anvil_clnt_auth() registers an AUTH event and returns the
+/* current AUTH event rate for the specified remote client.
+/*
+/* anvil_clnt_disconnect() informs the anvil server that a remote
+/* client has disconnected.
+/*
+/* anvil_clnt_lookup() returns the current count and rate
+/* information for the specified client.
+/*
+/* anvil_clnt_free() destroys a local anvil service client
+/* endpoint.
+/*
+/* Arguments:
+/* .IP anvil_clnt
+/* Client rate control service handle.
+/* .IP service
+/* The service that the remote client is connected to.
+/* .IP addr
+/* Null terminated string that identifies the remote client.
+/* .IP count
+/* Pointer to storage for the current number of connections from
+/* this remote client.
+/* .IP rate
+/* Pointer to storage for the current connection rate for this
+/* remote client.
+/* .IP msgs
+/* Pointer to storage for the current message rate for this
+/* remote client.
+/* .IP rcpts
+/* Pointer to storage for the current recipient rate for this
+/* remote client.
+/* .IP newtls
+/* Pointer to storage for the current "new TLS session" rate
+/* for this remote client.
+/* .IP auths
+/* Pointer to storage for the current AUTH event rate for this
+/* remote client.
+/* DIAGNOSTICS
+/* The update and status query routines return
+/* ANVIL_STAT_OK in case of success, ANVIL_STAT_FAIL otherwise
+/* (either the communication with the server is broken or the
+/* server experienced a problem).
+/* SEE ALSO
+/* anvil(8), connection/rate limiting
+/* 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 <msg.h>
+#include <attr_clnt.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <anvil_clnt.h>
+
+/* Application specific. */
+
+#define ANVIL_IDENT(service, addr) \
+ printable(concatenate(service, ":", addr, (char *) 0), '?')
+
+/* anvil_clnt_handshake - receive server protocol announcement */
+
+static int anvil_clnt_handshake(VSTREAM *stream)
+{
+ return (attr_scan_plain(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_ANVIL),
+ ATTR_TYPE_END));
+}
+
+/* anvil_clnt_create - instantiate connection rate service client */
+
+ANVIL_CLNT *anvil_clnt_create(void)
+{
+ ATTR_CLNT *anvil_clnt;
+
+ /*
+ * Use whatever IPC is preferred for internal use: UNIX-domain sockets or
+ * Solaris streams.
+ */
+#ifndef VAR_ANVIL_SERVICE
+ anvil_clnt = attr_clnt_create("local:" ANVIL_CLASS "/" ANVIL_SERVICE,
+ var_ipc_timeout, 0, 0);
+#else
+ anvil_clnt = attr_clnt_create(var_anvil_service, var_ipc_timeout, 0, 0);
+#endif
+ attr_clnt_control(anvil_clnt,
+ ATTR_CLNT_CTL_HANDSHAKE, anvil_clnt_handshake,
+ ATTR_CLNT_CTL_END);
+ return ((ANVIL_CLNT *) anvil_clnt);
+}
+
+/* anvil_clnt_free - destroy connection rate service client */
+
+void anvil_clnt_free(ANVIL_CLNT *anvil_clnt)
+{
+ attr_clnt_free((ATTR_CLNT *) anvil_clnt);
+}
+
+/* anvil_clnt_lookup - status query */
+
+int anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *count, int *rate,
+ int *msgs, int *rcpts, int *newtls, int *auths)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_LOOKUP),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_COUNT, count),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, rate),
+ RECV_ATTR_INT(ANVIL_ATTR_MAIL, msgs),
+ RECV_ATTR_INT(ANVIL_ATTR_RCPT, rcpts),
+ RECV_ATTR_INT(ANVIL_ATTR_NTLS, newtls),
+ RECV_ATTR_INT(ANVIL_ATTR_AUTH, auths),
+ ATTR_TYPE_END) != 7)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_connect - heads-up and status query */
+
+int anvil_clnt_connect(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *count, int *rate)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_CONN),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_COUNT, count),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, rate),
+ ATTR_TYPE_END) != 3)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_mail - heads-up and status query */
+
+int anvil_clnt_mail(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *msgs)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_MAIL),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, msgs),
+ ATTR_TYPE_END) != 2)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_rcpt - heads-up and status query */
+
+int anvil_clnt_rcpt(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *rcpts)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_RCPT),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, rcpts),
+ ATTR_TYPE_END) != 2)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_newtls - heads-up and status query */
+
+int anvil_clnt_newtls(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *newtls)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_NTLS),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, newtls),
+ ATTR_TYPE_END) != 2)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_newtls_stat - status query */
+
+int anvil_clnt_newtls_stat(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *newtls)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_NTLS_STAT),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, newtls),
+ ATTR_TYPE_END) != 2)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_auth - heads-up and status query */
+
+int anvil_clnt_auth(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr, int *auths)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_AUTH),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(ANVIL_ATTR_RATE, auths),
+ ATTR_TYPE_END) != 2)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+/* anvil_clnt_disconnect - heads-up only */
+
+int anvil_clnt_disconnect(ANVIL_CLNT *anvil_clnt, const char *service,
+ const char *addr)
+{
+ char *ident = ANVIL_IDENT(service, addr);
+ int status;
+
+ if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_DISC),
+ SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = ANVIL_STAT_FAIL;
+ else if (status != ANVIL_STAT_OK)
+ status = ANVIL_STAT_FAIL;
+ myfree(ident);
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone client for testing.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <msg_vstream.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <vstring_vstream.h>
+
+static void usage(void)
+{
+ vstream_printf("usage: "
+ ANVIL_REQ_CONN " service addr | "
+ ANVIL_REQ_DISC " service addr | "
+ ANVIL_REQ_MAIL " service addr | "
+ ANVIL_REQ_RCPT " service addr | "
+ ANVIL_REQ_NTLS " service addr | "
+ ANVIL_REQ_NTLS_STAT " service addr | "
+ ANVIL_REQ_AUTH " service addr | "
+ ANVIL_REQ_LOOKUP " service addr\n");
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *inbuf = vstring_alloc(1);
+ char *bufp;
+ char *cmd;
+ ssize_t cmd_len;
+ char *service;
+ char *addr;
+ int count;
+ int rate;
+ int msgs;
+ int rcpts;
+ int newtls;
+ int auths;
+ ANVIL_CLNT *anvil;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ msg_verbose++;
+
+ anvil = anvil_clnt_create();
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ bufp = vstring_str(inbuf);
+ if ((cmd = mystrtok(&bufp, " ")) == 0 || *bufp == 0
+ || (service = mystrtok(&bufp, " ")) == 0 || *service == 0
+ || (addr = mystrtok(&bufp, " ")) == 0 || *addr == 0
+ || mystrtok(&bufp, " ") != 0) {
+ vstream_printf("bad command syntax\n");
+ usage();
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ cmd_len = strlen(cmd);
+ if (strncmp(cmd, ANVIL_REQ_CONN, cmd_len) == 0) {
+ if (anvil_clnt_connect(anvil, service, addr, &count, &rate) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("count=%d, rate=%d\n", count, rate);
+ } else if (strncmp(cmd, ANVIL_REQ_MAIL, cmd_len) == 0) {
+ if (anvil_clnt_mail(anvil, service, addr, &msgs) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("rate=%d\n", msgs);
+ } else if (strncmp(cmd, ANVIL_REQ_RCPT, cmd_len) == 0) {
+ if (anvil_clnt_rcpt(anvil, service, addr, &rcpts) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("rate=%d\n", rcpts);
+ } else if (strncmp(cmd, ANVIL_REQ_NTLS, cmd_len) == 0) {
+ if (anvil_clnt_newtls(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("rate=%d\n", newtls);
+ } else if (strncmp(cmd, ANVIL_REQ_AUTH, cmd_len) == 0) {
+ if (anvil_clnt_auth(anvil, service, addr, &auths) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("rate=%d\n", auths);
+ } else if (strncmp(cmd, ANVIL_REQ_NTLS_STAT, cmd_len) == 0) {
+ if (anvil_clnt_newtls_stat(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("rate=%d\n", newtls);
+ } else if (strncmp(cmd, ANVIL_REQ_DISC, cmd_len) == 0) {
+ if (anvil_clnt_disconnect(anvil, service, addr) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("OK\n");
+ } else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) {
+ if (anvil_clnt_lookup(anvil, service, addr, &count, &rate, &msgs,
+ &rcpts, &newtls, &auths) != ANVIL_STAT_OK)
+ msg_warn("error!");
+ else
+ vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d "
+ "auths=%d\n", count, rate, msgs, rcpts, newtls,
+ auths);
+ } else {
+ vstream_printf("bad command: \"%s\"\n", cmd);
+ usage();
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(inbuf);
+ anvil_clnt_free(anvil);
+ return (0);
+}
+
+#endif
diff --git a/src/global/anvil_clnt.h b/src/global/anvil_clnt.h
new file mode 100644
index 0000000..d060155
--- /dev/null
+++ b/src/global/anvil_clnt.h
@@ -0,0 +1,83 @@
+#ifndef _ANVIL_CLNT_H_INCLUDED_
+#define _ANVIL_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* anvil_clnt 3h
+/* SUMMARY
+/* connection count and rate management client interface
+/* SYNOPSIS
+/* #include <anvil_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <attr_clnt.h>
+
+ /*
+ * Protocol interface: requests and endpoints.
+ */
+#define ANVIL_SERVICE "anvil"
+#define ANVIL_CLASS "private"
+
+#define ANVIL_ATTR_REQ "request"
+#define ANVIL_REQ_CONN "connect"
+#define ANVIL_REQ_DISC "disconnect"
+#define ANVIL_REQ_MAIL "message"
+#define ANVIL_REQ_RCPT "recipient"
+#define ANVIL_REQ_NTLS "newtls"
+#define ANVIL_REQ_NTLS_STAT "newtls_status"
+#define ANVIL_REQ_AUTH "auth"
+#define ANVIL_REQ_LOOKUP "lookup"
+#define ANVIL_ATTR_IDENT "ident"
+#define ANVIL_ATTR_COUNT "count"
+#define ANVIL_ATTR_RATE "rate"
+#define ANVIL_ATTR_MAIL "mail"
+#define ANVIL_ATTR_RCPT "rcpt"
+#define ANVIL_ATTR_NTLS "newtls"
+#define ANVIL_ATTR_AUTH "auth"
+#define ANVIL_ATTR_STATUS "status"
+
+#define ANVIL_STAT_OK 0
+#define ANVIL_STAT_FAIL (-1)
+
+ /*
+ * Functional interface.
+ */
+typedef struct ANVIL_CLNT ANVIL_CLNT;
+
+extern ANVIL_CLNT *anvil_clnt_create(void);
+extern int anvil_clnt_connect(ANVIL_CLNT *, const char *, const char *, int *, int *);
+extern int anvil_clnt_mail(ANVIL_CLNT *, const char *, const char *, int *);
+extern int anvil_clnt_rcpt(ANVIL_CLNT *, const char *, const char *, int *);
+extern int anvil_clnt_newtls(ANVIL_CLNT *, const char *, const char *, int *);
+extern int anvil_clnt_newtls_stat(ANVIL_CLNT *, const char *, const char *, int *);
+extern int anvil_clnt_auth(ANVIL_CLNT *, const char *, const char *, int *);
+extern int anvil_clnt_lookup(ANVIL_CLNT *, const char *, const char *, int *, int *, int *, int *, int *, int *);
+extern int anvil_clnt_disconnect(ANVIL_CLNT *, const char *, const char *);
+extern void anvil_clnt_free(ANVIL_CLNT *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/attr_override.c b/src/global/attr_override.c
new file mode 100644
index 0000000..bff2954
--- /dev/null
+++ b/src/global/attr_override.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* attr_override 3
+/* SUMMARY
+/* apply name=value settings from string
+/* SYNOPSIS
+/* #include <attr_override.h>
+/*
+/* void attr_override(bp, delimiters, parens, ... CA_ATTR_OVER_END);
+/* char *bp;
+/* const char *delimiters;
+/* const char *parens;
+/* DESCRIPTION
+/* This routine updates the values of known in-memory variables
+/* based on the name=value specifications from an input string.
+/* The input format supports parentheses around name=value to
+/* allow whitespace around "=" and within values.
+/*
+/* This may be used, for example, with client endpoint
+/* specifications or with policy tables to allow selective
+/* overrides of global main.cf parameter settings (timeouts,
+/* fall-back policies, etc.).
+/*
+/* Arguments:
+/* .IP bp
+/* Pointer to input string. The input is modified.
+/* .IP "delimiters, parens"
+/* See mystrtok(3) for description. Typical values are
+/* CHARS_COMMA_SP and CHARS_BRACE, respectively.
+/* .PP
+/* The parens argument is followed by a list of macros
+/* with arguments. Each macro may appear only once. The list
+/* must be terminated with CA_ATTR_OVER_END which has no argument.
+/* The following describes the expected values.
+/* .IP "CA_ATTR_OVER_STR_TABLE(const ATTR_OVER_STR *)"
+/* The macro argument specifies a null-terminated table with
+/* attribute names, assignment targets, and range limits which
+/* should be the same as for the corresponding main.cf parameters.
+/* .IP "CA_ATTR_OVER_TIME_TABLE(const ATTR_OVER_TIME *)"
+/* The macro argument specifies a null-terminated table with
+/* attribute names, their default time units (leading digits
+/* are skipped), assignment targets, and range limits which
+/* should be the same as for the corresponding main.cf parameters.
+/* .IP "CA_ATTR_OVER_INT_TABLE(const ATTR_OVER_INT *)"
+/* The macro argument specifies a null-terminated table with
+/* attribute names, assignment targets, and range limits which
+/* should be the same as for the corresponding main.cf parameters.
+/* SEE ALSO
+/* mystrtok(3), safe tokenizer
+/* extpar(3), extract text from parentheses
+/* split_nameval(3), name-value splitter
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/*
+/* Fatal errors: memory allocation problem, syntax error,
+/* out-of-range error.
+/* 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 <string.h>
+#include <stdlib.h> /* strtol() */
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <stringops.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_conf.h>
+#include <conv_time.h>
+#include <attr_override.h>
+
+/* attr_override - apply settings from list of attribute=value pairs */
+
+void attr_override(char *cp, const char *sep, const char *parens,...)
+{
+ static const char myname[] = "attr_override";
+ va_list ap;
+ int idx;
+ char *nameval;
+ const ATTR_OVER_INT *int_table = 0;
+ const ATTR_OVER_STR *str_table = 0;
+ const ATTR_OVER_TIME *time_table = 0;
+
+ /*
+ * Get the lookup tables and assignment targets.
+ */
+ va_start(ap, parens);
+ while ((idx = va_arg(ap, int)) != ATTR_OVER_END) {
+ switch (idx) {
+ case ATTR_OVER_INT_TABLE:
+ if (int_table)
+ msg_panic("%s: multiple ATTR_OVER_INT_TABLE", myname);
+ int_table = va_arg(ap, const ATTR_OVER_INT *);
+ break;
+ case ATTR_OVER_STR_TABLE:
+ if (str_table)
+ msg_panic("%s: multiple ATTR_OVER_STR_TABLE", myname);
+ str_table = va_arg(ap, const ATTR_OVER_STR *);
+ break;
+ case ATTR_OVER_TIME_TABLE:
+ if (time_table)
+ msg_panic("%s: multiple ATTR_OVER_TIME_TABLE", myname);
+ time_table = va_arg(ap, const ATTR_OVER_TIME *);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, idx);
+ }
+ }
+ va_end(ap);
+
+ /*
+ * Process each attribute=value override in the input string.
+ */
+ while ((nameval = mystrtokq(&cp, sep, parens)) != 0) {
+ int found = 0;
+ char *key;
+ char *value;
+ const char *err;
+ const ATTR_OVER_INT *ip;
+ const ATTR_OVER_STR *sp;
+ const ATTR_OVER_TIME *tp;
+ int int_val;
+ int def_unit;
+ char *end;
+ long longval;
+
+ /*
+ * Split into name and value.
+ */
+ /* { name = value } */
+ if (*nameval == parens[0]
+ && (err = extpar(&nameval, parens, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("%s in \"%s\"", err, nameval);
+ if ((err = split_nameval(nameval, &key, &value)) != 0)
+ msg_fatal("malformed option: %s: \"...%s...\"", err, nameval);
+
+ /*
+ * Look up the name and apply the value.
+ */
+ for (sp = str_table; sp != 0 && found == 0 && sp->name != 0; sp++) {
+ if (strcmp(sp->name, key) != 0)
+ continue;
+ check_mail_conf_str(sp->name, value, sp->min, sp->max);
+ sp->target[0] = value;
+ found = 1;
+ }
+ for (ip = int_table; ip != 0 && found == 0 && ip->name != 0; ip++) {
+ if (strcmp(ip->name, key) != 0)
+ continue;
+ /* XXX Duplicated from mail_conf_int(3). */
+ errno = 0;
+ int_val = longval = strtol(value, &end, 10);
+ if (*value == 0 || *end != 0 || errno == ERANGE
+ || longval != int_val)
+ msg_fatal("bad numerical configuration: %s = %s", key, value);
+ check_mail_conf_int(key, int_val, ip->min, ip->max);
+ ip->target[0] = int_val;
+ found = 1;
+ }
+ for (tp = time_table; tp != 0 && found == 0 && tp->name != 0; tp++) {
+ if (strcmp(tp->name, key) != 0)
+ continue;
+ def_unit = tp->defval[strspn(tp->defval, "0123456789")];
+ if (conv_time(value, &int_val, def_unit) == 0)
+ msg_fatal("%s: bad time value or unit: %s", key, value);
+ check_mail_conf_time(key, int_val, tp->min, tp->max);
+ tp->target[0] = int_val;
+ found = 1;
+ }
+ if (found == 0)
+ msg_fatal("unknown option: \"%s = %s\"", key, value);
+ }
+}
diff --git a/src/global/attr_override.h b/src/global/attr_override.h
new file mode 100644
index 0000000..9f06162
--- /dev/null
+++ b/src/global/attr_override.h
@@ -0,0 +1,70 @@
+#ifndef _ATTR_OVERRIDE_H_INCLUDED_
+#define _ATTR_OVERRIDE_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr_override 3h
+/* SUMMARY
+/* apply name=value settings from string
+/* SYNOPSIS
+/* #include <attr_override.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#include <check_arg.h>
+
+extern void attr_override(char *, const char *, const char *,...);
+
+typedef struct {
+ const char *name;
+ CONST_CHAR_STAR *target;
+ int min;
+ int max;
+} ATTR_OVER_STR;
+
+typedef struct {
+ const char *name;
+ const char *defval;
+ int *target;
+ int min;
+ int max;
+} ATTR_OVER_TIME;
+
+typedef struct {
+ const char *name;
+ int *target;
+ int min;
+ int max;
+} ATTR_OVER_INT;
+
+/* Type-unchecked API, internal use only. */
+#define ATTR_OVER_END 0
+#define ATTR_OVER_STR_TABLE 1
+#define ATTR_OVER_TIME_TABLE 2
+#define ATTR_OVER_INT_TABLE 3
+
+/* Type-checked API, external use only. */
+#define CA_ATTR_OVER_END 0
+#define CA_ATTR_OVER_STR_TABLE(v) ATTR_OVER_STR_TABLE, CHECK_CPTR(ATTR_OVER, ATTR_OVER_STR, (v))
+#define CA_ATTR_OVER_TIME_TABLE(v) ATTR_OVER_TIME_TABLE, CHECK_CPTR(ATTR_OVER, ATTR_OVER_TIME, (v))
+#define CA_ATTR_OVER_INT_TABLE(v) ATTR_OVER_INT_TABLE, CHECK_CPTR(ATTR_OVER, ATTR_OVER_INT, (v))
+
+CHECK_CPTR_HELPER_DCL(ATTR_OVER, ATTR_OVER_TIME);
+CHECK_CPTR_HELPER_DCL(ATTR_OVER, ATTR_OVER_STR);
+CHECK_CPTR_HELPER_DCL(ATTR_OVER, ATTR_OVER_INT);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/been_here.c b/src/global/been_here.c
new file mode 100644
index 0000000..d0c6820
--- /dev/null
+++ b/src/global/been_here.c
@@ -0,0 +1,335 @@
+/*++
+/* NAME
+/* been_here 3
+/* SUMMARY
+/* detect repeated occurrence of string
+/* SYNOPSIS
+/* #include <been_here.h>
+/*
+/* BH_TABLE *been_here_init(size, flags)
+/* int size;
+/* int flags;
+/*
+/* int been_here_fixed(dup_filter, string)
+/* BH_TABLE *dup_filter;
+/* char *string;
+/*
+/* int been_here(dup_filter, format, ...)
+/* BH_TABLE *dup_filter;
+/* char *format;
+/*
+/* int been_here_check_fixed(dup_filter, string)
+/* BH_TABLE *dup_filter;
+/* char *string;
+/*
+/* int been_here_check(dup_filter, format, ...)
+/* BH_TABLE *dup_filter;
+/* char *format;
+/*
+/* int been_here_drop_fixed(dup_filter, string)
+/* BH_TABLE *dup_filter;
+/* char *string;
+/*
+/* int been_here_drop(dup_filter, format, ...)
+/* BH_TABLE *dup_filter;
+/* char *format;
+/*
+/* void been_here_free(dup_filter)
+/* BH_TABLE *dup_filter;
+/* DESCRIPTION
+/* This module implements a simple filter to detect repeated
+/* occurrences of character strings.
+/*
+/* been_here_init() creates an empty duplicate filter.
+/*
+/* been_here_fixed() looks up a fixed string in the given table, and
+/* makes an entry in the table if the string was not found. The result
+/* is non-zero (true) if the string was found, zero (false) otherwise.
+/*
+/* been_here() formats its arguments, looks up the result in the
+/* given table, and makes an entry in the table if the string was
+/* not found. The result is non-zero (true) if the formatted result was
+/* found, zero (false) otherwise.
+/*
+/* been_here_check_fixed() and been_here_check() are similar
+/* but do not update the duplicate filter.
+/*
+/* been_here_drop_fixed() looks up a fixed string in the given
+/* table, and deletes the entry if the string was found. The
+/* result is non-zero (true) if the string was found, zero
+/* (false) otherwise.
+/*
+/* been_here_drop() formats its arguments, looks up the result
+/* in the given table, and removes the entry if the formatted
+/* result was found. The result is non-zero (true) if the
+/* formatted result was found, zero (false) otherwise.
+/*
+/* been_here_free() releases storage for a duplicate filter.
+/*
+/* Arguments:
+/* .IP size
+/* Upper bound on the table size; at most \fIsize\fR strings will
+/* be remembered. Specify BH_BOUND_NONE to disable the upper bound.
+/* .IP flags
+/* Requests for special processing. Specify the bitwise OR of zero
+/* or more flags:
+/* .RS
+/* .IP BH_FLAG_FOLD
+/* Enable case-insensitive lookup.
+/* .IP BH_FLAG_NONE
+/* A manifest constant that requests no special processing.
+/* .RE
+/* .IP dup_filter
+/* The table with remembered names
+/* .IP string
+/* Fixed search string.
+/* .IP format
+/* Format for building the search string.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "been_here.h"
+
+#define STR(x) vstring_str(x)
+
+/* been_here_init - initialize duplicate filter */
+
+BH_TABLE *been_here_init(int limit, int flags)
+{
+ BH_TABLE *dup_filter;
+
+ dup_filter = (BH_TABLE *) mymalloc(sizeof(*dup_filter));
+ dup_filter->limit = limit;
+ dup_filter->flags = flags;
+ dup_filter->table = htable_create(0);
+ return (dup_filter);
+}
+
+/* been_here_free - destroy duplicate filter */
+
+void been_here_free(BH_TABLE *dup_filter)
+{
+ htable_free(dup_filter->table, (void (*) (void *)) 0);
+ myfree((void *) dup_filter);
+}
+
+/* been_here - duplicate detector with finer control */
+
+int been_here(BH_TABLE *dup_filter, const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int status;
+ va_list ap;
+
+ /*
+ * Construct the string to be checked.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Do the duplicate check.
+ */
+ status = been_here_fixed(dup_filter, vstring_str(buf));
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ return (status);
+}
+
+/* been_here_fixed - duplicate detector */
+
+int been_here_fixed(BH_TABLE *dup_filter, const char *string)
+{
+ VSTRING *folded_string;
+ const char *lookup_key;
+ int status;
+
+ /*
+ * Special processing: case insensitive lookup.
+ */
+ if (dup_filter->flags & BH_FLAG_FOLD) {
+ folded_string = vstring_alloc(100);
+ lookup_key = casefold(folded_string, string);
+ } else {
+ folded_string = 0;
+ lookup_key = string;
+ }
+
+ /*
+ * Do the duplicate check.
+ */
+ if (htable_locate(dup_filter->table, lookup_key) != 0) {
+ status = 1;
+ } else {
+ if (dup_filter->limit <= 0
+ || dup_filter->limit > dup_filter->table->used)
+ htable_enter(dup_filter->table, lookup_key, (void *) 0);
+ status = 0;
+ }
+ if (msg_verbose)
+ msg_info("been_here: %s: %d", string, status);
+
+ /*
+ * Cleanup.
+ */
+ if (folded_string)
+ vstring_free(folded_string);
+
+ return (status);
+}
+
+/* been_here_check - query duplicate detector with finer control */
+
+int been_here_check(BH_TABLE *dup_filter, const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int status;
+ va_list ap;
+
+ /*
+ * Construct the string to be checked.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Do the duplicate check.
+ */
+ status = been_here_check_fixed(dup_filter, vstring_str(buf));
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ return (status);
+}
+
+/* been_here_check_fixed - query duplicate detector */
+
+int been_here_check_fixed(BH_TABLE *dup_filter, const char *string)
+{
+ VSTRING *folded_string;
+ const char *lookup_key;
+ int status;
+
+ /*
+ * Special processing: case insensitive lookup.
+ */
+ if (dup_filter->flags & BH_FLAG_FOLD) {
+ folded_string = vstring_alloc(100);
+ lookup_key = casefold(folded_string, string);
+ } else {
+ folded_string = 0;
+ lookup_key = string;
+ }
+
+ /*
+ * Do the duplicate check.
+ */
+ status = (htable_locate(dup_filter->table, lookup_key) != 0);
+ if (msg_verbose)
+ msg_info("been_here_check: %s: %d", string, status);
+
+ /*
+ * Cleanup.
+ */
+ if (folded_string)
+ vstring_free(folded_string);
+
+ return (status);
+}
+
+/* been_here_drop - remove filter entry with finer control */
+
+int been_here_drop(BH_TABLE *dup_filter, const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int status;
+ va_list ap;
+
+ /*
+ * Construct the string to be dropped.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Drop the filter entry.
+ */
+ status = been_here_drop_fixed(dup_filter, vstring_str(buf));
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ return (status);
+}
+
+/* been_here_drop_fixed - remove filter entry */
+
+int been_here_drop_fixed(BH_TABLE *dup_filter, const char *string)
+{
+ VSTRING *folded_string;
+ const char *lookup_key;
+ int status;
+
+ /*
+ * Special processing: case insensitive lookup.
+ */
+ if (dup_filter->flags & BH_FLAG_FOLD) {
+ folded_string = vstring_alloc(100);
+ lookup_key = casefold(folded_string, string);
+ } else {
+ folded_string = 0;
+ lookup_key = string;
+ }
+
+ /*
+ * Drop the filter entry.
+ */
+ if ((status = been_here_check_fixed(dup_filter, lookup_key)) != 0)
+ htable_delete(dup_filter->table, lookup_key, (void (*) (void *)) 0);
+
+ /*
+ * Cleanup.
+ */
+ if (folded_string)
+ vstring_free(folded_string);
+
+ return (status);
+}
diff --git a/src/global/been_here.h b/src/global/been_here.h
new file mode 100644
index 0000000..b5ac2d6
--- /dev/null
+++ b/src/global/been_here.h
@@ -0,0 +1,57 @@
+#ifndef _BEEN_HERE_H_INCLUDED_
+#define _BEEN_HERE_H_INCLUDED_
+
+/*++
+/* NAME
+/* been_here 3h
+/* SUMMARY
+/* detect repeated occurrence of string
+/* SYNOPSIS
+/* #include <been_here.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int limit; /* ceiling, zero for none */
+ int flags; /* see below */
+ struct HTABLE *table;
+} BH_TABLE;
+
+#define BH_BOUND_NONE 0 /* no upper bound */
+#define BH_FLAG_NONE 0 /* no special processing */
+#define BH_FLAG_FOLD (1<<0) /* fold case */
+
+extern BH_TABLE *been_here_init(int, int);
+extern void been_here_free(BH_TABLE *);
+extern int been_here_fixed(BH_TABLE *, const char *);
+extern int PRINTFLIKE(2, 3) been_here(BH_TABLE *, const char *,...);
+extern int been_here_check_fixed(BH_TABLE *, const char *);
+extern int PRINTFLIKE(2, 3) been_here_check(BH_TABLE *, const char *,...);
+extern int been_here_drop_fixed(BH_TABLE *, const char *);
+extern int PRINTFLIKE(2, 3) been_here_drop(BH_TABLE *, const char *,...);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/bounce.c b/src/global/bounce.c
new file mode 100644
index 0000000..072a7a7
--- /dev/null
+++ b/src/global/bounce.c
@@ -0,0 +1,549 @@
+/*++
+/* NAME
+/* bounce 3
+/* SUMMARY
+/* bounce service client
+/* SYNOPSIS
+/* #include <bounce.h>
+/*
+/* int bounce_append(flags, id, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/*
+/* int bounce_flush(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/*
+/* int bounce_flush_verp(flags, queue, id, encoding, smtputf8,
+/* sender, dsn_envid, dsn_ret, verp_delims)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* const char *verp_delims;
+/*
+/* int bounce_one(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, ret, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/*
+/* void bounce_client_init(title, maps)
+/* const char *title;
+/* const char *maps;
+/* INTERNAL API
+/* DSN_FILTER *delivery_status_filter;
+/*
+/* int bounce_append_intern(flags, id, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/*
+/* int bounce_one_intern(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, ret, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/* DESCRIPTION
+/* This module implements the client interface to the message
+/* bounce service, which maintains a per-message log of status
+/* records with recipients that were bounced, and the dsn_text why.
+/*
+/* bounce_append() appends a dsn_text for non-delivery to the
+/* bounce log for the named recipient, updates the address
+/* verification service, or updates a message delivery record
+/* on request by the sender. The flags argument determines
+/* the action.
+/*
+/* bounce_flush() actually bounces the specified message to
+/* the specified sender, including the bounce log that was
+/* built with bounce_append(). The bounce logfile is removed
+/* upon successful completion.
+/*
+/* bounce_flush_verp() is like bounce_flush(), but sends one
+/* notification per recipient, with the failed recipient encoded
+/* into the sender address.
+/*
+/* bounce_one() bounces one recipient and immediately sends a
+/* notification to the sender. This procedure does not append
+/* the recipient and dsn_text to the per-message bounce log, and
+/* should be used when a delivery agent changes the error
+/* return address in a manner that depends on the recipient
+/* address.
+/*
+/* bounce_client_init() initializes an optional DSN filter.
+/*
+/* bounce_append_intern() and bounce_one_intern() are for use
+/* after the DSN filter.
+/*
+/* Arguments:
+/* .IP flags
+/* The bitwise OR of zero or more of the following (specify
+/* BOUNCE_FLAG_NONE to request no special processing):
+/* .RS
+/* .IP BOUNCE_FLAG_CLEAN
+/* Delete the bounce log in case of an error (as in: pretend
+/* that we never even tried to bounce this message).
+/* .IP BOUNCE_FLAG_DELRCPT
+/* When specified with a flush request, request that
+/* recipients be deleted from the queue file.
+/*
+/* Note: the bounce daemon ignores this request when the
+/* recipient queue file offset is <= 0.
+/* .IP DEL_REQ_FLAG_MTA_VRFY
+/* The message is an MTA-requested address verification probe.
+/* Update the address verification database instead of bouncing
+/* mail.
+/* .IP DEL_REQ_FLAG_USR_VRFY
+/* The message is a user-requested address expansion probe.
+/* Update the message delivery record instead of bouncing mail.
+/* .IP DEL_REQ_FLAG_RECORD
+/* This is a normal message with logged delivery. Update the
+/* message delivery record and bounce the mail.
+/* .RE
+/* .IP queue
+/* The message queue name of the original message file.
+/* .IP id
+/* The message queue id if the original message file. The bounce log
+/* file has the same name as the original message file.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP rcpt
+/* Recipient information. See recipient_list(3).
+/* .IP relay
+/* Name of the host that the message could not be delivered to.
+/* This information is used for syslogging only.
+/* .IP encoding
+/* The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
+/* .IP smtputf8
+/* The level of SMTPUTF8 support (to be defined).
+/* .IP sender
+/* The sender envelope address.
+/* .IP dsn_envid
+/* Optional DSN envelope ID.
+/* .IP dsn_ret
+/* Optional DSN return full/headers option.
+/* .IP dsn
+/* Delivery status. See dsn(3). The specified action is ignored.
+/* .IP verp_delims
+/* VERP delimiter characters, used when encoding the failed
+/* sender into the envelope sender address.
+/* DIAGNOSTICS
+/* In case of success, these functions log the action, and return a
+/* zero value. Otherwise, the functions return a non-zero result,
+/* and when BOUNCE_FLAG_CLEAN is disabled, log that message
+/* delivery is deferred.
+/* .IP title
+/* The origin of the optional DSN filter lookup table names.
+/* .IP maps
+/* The optional "type:table" DSN filter lookup table names,
+/* separated by comma or whitespace.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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 <mymalloc.h>
+
+/* Global library. */
+
+#define DSN_INTERN
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <log_adhoc.h>
+#include <dsn_util.h>
+#include <rcpt_print.h>
+#include <dsn_print.h>
+#include <verify.h>
+#include <defer.h>
+#include <trace.h>
+#include <bounce.h>
+
+/* Shared internally, between bounce and defer clients. */
+
+DSN_FILTER *delivery_status_filter;
+
+/* bounce_append - append delivery status to per-message bounce log */
+
+int bounce_append(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ DSN *dsn_res;
+
+ /*
+ * Sanity check. If we're really confident, change this into msg_panic
+ * (remember, this information may be under control by a hostile server).
+ */
+ if (my_dsn.status[0] != '5' || !dsn_valid(my_dsn.status)) {
+ msg_warn("bounce_append: ignoring dsn code \"%s\"", my_dsn.status);
+ my_dsn.status = "5.0.0";
+ }
+
+ /*
+ * DSN filter (Postfix 3.0).
+ */
+ if (delivery_status_filter != 0
+ && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) {
+ if (dsn_res->status[0] == '4')
+ return (defer_append_intern(flags, id, stats, rcpt, relay, dsn_res));
+ my_dsn = *dsn_res;
+ }
+ return (bounce_append_intern(flags, id, stats, rcpt, relay, &my_dsn));
+}
+
+/* bounce_append_intern - append delivery status to per-message bounce log */
+
+int bounce_append_intern(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ int status;
+
+ /*
+ * MTA-requested address verification information is stored in the verify
+ * service database.
+ */
+ if (flags & DEL_REQ_FLAG_MTA_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = verify_append(id, stats, rcpt, relay, &my_dsn,
+ DEL_RCPT_STAT_BOUNCE);
+ return (status);
+ }
+
+ /*
+ * User-requested address verification information is logged and mailed
+ * to the requesting user.
+ */
+ if (flags & DEL_REQ_FLAG_USR_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = trace_append(flags, id, stats, rcpt, relay, &my_dsn);
+ return (status);
+ }
+
+ /*
+ * Normal (well almost) delivery. When we're pretending that we can't
+ * bounce, don't create a defer log file when we wouldn't keep the bounce
+ * log file. That's a lot of negatives in one sentence.
+ */
+ else if (var_soft_bounce && (flags & BOUNCE_FLAG_CLEAN)) {
+ return (-1);
+ }
+
+ /*
+ * Normal mail delivery. May also send a delivery record to the user.
+ *
+ * XXX DSN We write all recipients to the bounce logfile regardless of DSN
+ * NOTIFY options, because those options don't apply to postmaster
+ * notifications.
+ */
+ else {
+ char *my_status = mystrdup(my_dsn.status);
+ const char *log_status = var_soft_bounce ? "SOFTBOUNCE" : "bounced";
+
+ /*
+ * Supply default action.
+ */
+ my_dsn.status = my_status;
+ if (var_soft_bounce) {
+ my_status[0] = '4';
+ my_dsn.action = "delayed";
+ } else {
+ my_dsn.action = "failed";
+ }
+
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_soft_bounce ?
+ var_defer_service : var_bounce_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt),
+ SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn),
+ ATTR_TYPE_END) == 0
+ && ((flags & DEL_REQ_FLAG_RECORD) == 0
+ || trace_append(flags, id, stats, rcpt, relay,
+ &my_dsn) == 0)) {
+ log_adhoc(id, stats, rcpt, relay, &my_dsn, log_status);
+ status = (var_soft_bounce ? -1 : 0);
+ } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) {
+ VSTRING *junk = vstring_alloc(100);
+
+ my_dsn.status = "4.3.0";
+ vstring_sprintf(junk, "%s or %s service failure",
+ var_bounce_service, var_trace_service);
+ my_dsn.reason = vstring_str(junk);
+ status = defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn);
+ vstring_free(junk);
+ } else {
+ status = -1;
+ }
+ myfree(my_status);
+ return (status);
+ }
+}
+
+/* bounce_flush - flush the bounce log and deliver to the sender */
+
+int bounce_flush(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret)
+{
+
+ /*
+ * When we're pretending that we can't bounce, don't send a bounce
+ * message.
+ */
+ if (var_soft_bounce)
+ return (-1);
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ ATTR_TYPE_END) == 0) {
+ return (0);
+ } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) {
+ msg_info("%s: status=deferred (bounce failed)", id);
+ return (-1);
+ } else {
+ return (-1);
+ }
+}
+
+/* bounce_flush_verp - verpified notification */
+
+int bounce_flush_verp(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, const char *verp_delims)
+{
+
+ /*
+ * When we're pretending that we can't bounce, don't send a bounce
+ * message.
+ */
+ if (var_soft_bounce)
+ return (-1);
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_VERP),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ SEND_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims),
+ ATTR_TYPE_END) == 0) {
+ return (0);
+ } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) {
+ msg_info("%s: status=deferred (bounce failed)", id);
+ return (-1);
+ } else {
+ return (-1);
+ }
+}
+
+/* bounce_one - send notice for one recipient */
+
+int bounce_one(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, MSG_STATS *stats, RECIPIENT *rcpt,
+ const char *relay, DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ DSN *dsn_res;
+
+ /*
+ * Sanity check.
+ */
+ if (my_dsn.status[0] != '5' || !dsn_valid(my_dsn.status)) {
+ msg_warn("bounce_one: ignoring dsn code \"%s\"", my_dsn.status);
+ my_dsn.status = "5.0.0";
+ }
+
+ /*
+ * DSN filter (Postfix 3.0).
+ */
+ if (delivery_status_filter != 0
+ && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) {
+ if (dsn_res->status[0] == '4')
+ return (defer_append_intern(flags, id, stats, rcpt, relay, dsn_res));
+ my_dsn = *dsn_res;
+ }
+ return (bounce_one_intern(flags, queue, id, encoding, smtputf8, sender,
+ dsn_envid, dsn_ret, stats, rcpt, relay, &my_dsn));
+}
+
+/* bounce_one_intern - send notice for one recipient */
+
+int bounce_one_intern(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ int status;
+
+ /*
+ * MTA-requested address verification information is stored in the verify
+ * service database.
+ */
+ if (flags & DEL_REQ_FLAG_MTA_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = verify_append(id, stats, rcpt, relay, &my_dsn,
+ DEL_RCPT_STAT_BOUNCE);
+ return (status);
+ }
+
+ /*
+ * User-requested address verification information is logged and mailed
+ * to the requesting user.
+ */
+ if (flags & DEL_REQ_FLAG_USR_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = trace_append(flags, id, stats, rcpt, relay, &my_dsn);
+ return (status);
+ }
+
+ /*
+ * When we're not bouncing, then use the standard multi-recipient logfile
+ * based procedure.
+ */
+ else if (var_soft_bounce) {
+ return (bounce_append_intern(flags, id, stats, rcpt, relay, &my_dsn));
+ }
+
+ /*
+ * Normal mail delivery. May also send a delivery record to the user.
+ *
+ * XXX DSN We send all recipients regardless of DSN NOTIFY options, because
+ * those options don't apply to postmaster notifications.
+ */
+ else {
+
+ /*
+ * Supply default action.
+ */
+ my_dsn.action = "failed";
+
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_ONE),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt),
+ SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn),
+ ATTR_TYPE_END) == 0
+ && ((flags & DEL_REQ_FLAG_RECORD) == 0
+ || trace_append(flags, id, stats, rcpt, relay,
+ &my_dsn) == 0)) {
+ log_adhoc(id, stats, rcpt, relay, &my_dsn, "bounced");
+ status = 0;
+ } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) {
+ VSTRING *junk = vstring_alloc(100);
+
+ my_dsn.status = "4.3.0";
+ vstring_sprintf(junk, "%s or %s service failure",
+ var_bounce_service, var_trace_service);
+ my_dsn.reason = vstring_str(junk);
+ status = defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn);
+ vstring_free(junk);
+ } else {
+ status = -1;
+ }
+ return (status);
+ }
+}
+
+/* bounce_client_init - initialize bounce/defer DSN filter */
+
+void bounce_client_init(const char *title, const char *maps)
+{
+ static const char myname[] = "bounce_client_init";
+
+ if (delivery_status_filter != 0)
+ msg_panic("%s: duplicate initialization", myname);
+ if (*maps)
+ delivery_status_filter = dsn_filter_create(title, maps);
+}
diff --git a/src/global/bounce.h b/src/global/bounce.h
new file mode 100644
index 0000000..e2d67bd
--- /dev/null
+++ b/src/global/bounce.h
@@ -0,0 +1,99 @@
+#ifndef _BOUNCE_H_INCLUDED_
+#define _BOUNCE_H_INCLUDED_
+
+/*++
+/* NAME
+/* bounce 3h
+/* SUMMARY
+/* bounce service client
+/* SYNOPSIS
+/* #include <bounce.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <dsn_buf.h>
+
+ /*
+ * Client interface.
+ */
+extern int bounce_append(int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+extern int bounce_flush(int, const char *, const char *, const char *, int,
+ const char *, const char *, int);
+extern int bounce_flush_verp(int, const char *, const char *, const char *, int,
+ const char *, const char *, int, const char *);
+extern int bounce_one(int, const char *, const char *, const char *, int,
+ const char *, const char *,
+ int, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+extern void bounce_client_init(const char *, const char *);
+
+ /*
+ * Bounce/defer protocol commands.
+ */
+#define BOUNCE_CMD_APPEND 0 /* append log */
+#define BOUNCE_CMD_FLUSH 1 /* send log */
+#define BOUNCE_CMD_WARN 2 /* send warning, don't delete log */
+#define BOUNCE_CMD_VERP 3 /* send log, verp style */
+#define BOUNCE_CMD_ONE 4 /* send one recipient notice */
+#define BOUNCE_CMD_TRACE 5 /* send delivery record */
+
+ /*
+ * Macros to make obscure code more readable.
+ */
+#define NO_DSN_DCODE ((char *) 0)
+#define NO_RELAY_AGENT "none"
+#define NO_DSN_RMTA ((char *) 0)
+
+ /*
+ * Flags.
+ */
+#define BOUNCE_FLAG_NONE 0 /* no flags up */
+#define BOUNCE_FLAG_CLEAN (1<<0) /* remove log on error */
+#define BOUNCE_FLAG_DELRCPT (1<<1) /* delete recipient from queue file */
+
+ /*
+ * Backwards compatibility.
+ */
+#define BOUNCE_FLAG_KEEP BOUNCE_FLAG_NONE
+
+ /*
+ * Start of private API.
+ */
+
+#ifdef DSN_INTERN
+
+#include <dsn_filter.h>
+
+extern DSN_FILTER *delivery_status_filter;
+
+extern int bounce_append_intern(int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+extern int bounce_one_intern(int, const char *, const char *, const char *,
+ int, const char *, const char *,
+ int, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/bounce_log.c b/src/global/bounce_log.c
new file mode 100644
index 0000000..b97515e
--- /dev/null
+++ b/src/global/bounce_log.c
@@ -0,0 +1,321 @@
+/*++
+/* NAME
+/* bounce_log 3
+/* SUMMARY
+/* bounce file API
+/* SYNOPSIS
+/* #include <bounce_log.h>
+/*
+/* typedef struct {
+/* .in +4
+/* /* No public members. */
+/* .in -4
+/* } BOUNCE_LOG;
+/*
+/* BOUNCE_LOG *bounce_log_open(queue, id, flags, mode)
+/* const char *queue;
+/* const char *id;
+/* int flags;
+/* mode_t mode;
+/*
+/* BOUNCE_LOG *bounce_log_read(bp, rcpt, dsn)
+/* BOUNCE_LOG *bp;
+/* RCPT_BUF *rcpt;
+/* DSN_BUF *dsn;
+/*
+/* void bounce_log_rewind(bp)
+/* BOUNCE_LOG *bp;
+/*
+/* void bounce_log_close(bp)
+/* BOUNCE_LOG *bp;
+/* DESCRIPTION
+/* This module implements a bounce/defer logfile API. Information
+/* is sanitized for control and non-ASCII characters. Fields not
+/* present in input are represented by empty strings.
+/*
+/* bounce_log_open() opens the named bounce or defer logfile
+/* and returns a handle that must be used for further access.
+/* The result is a null pointer if the file cannot be opened.
+/* The caller is expected to inspect the errno code and deal
+/* with the problem.
+/*
+/* bounce_log_read() reads the next record from the bounce or defer
+/* logfile (skipping over and warning about malformed data)
+/* and breaks out the recipient address, the recipient status
+/* and the text that explains why the recipient was undeliverable.
+/* bounce_log_read() returns a null pointer when no recipient was read,
+/* otherwise it returns its argument.
+/*
+/* bounce_log_rewind() is a helper that seeks to the first recipient
+/* in an open bounce or defer logfile (skipping over recipients that
+/* are marked as done). The result is 0 in case of success, -1 in case
+/* of problems.
+/*
+/* bounce_log_close() closes an open bounce or defer logfile and
+/* releases memory for the specified handle. The result is non-zero
+/* in case of I/O errors.
+/*
+/* Arguments:
+/* .IP queue
+/* The bounce or defer queue name.
+/* .IP id
+/* The message queue id of bounce or defer logfile. This
+/* file has the same name as the original message file.
+/* .IP flags
+/* File open flags, as with open(2).
+/* .IP mode
+/* File permissions, as with open(2).
+/* .IP rcpt
+/* Recipient buffer. The RECIPIENT member is updated.
+/* .IP dsn
+/* Delivery status information. The DSN member is updated.
+/* 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 <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <dsn_mask.h>
+#include <bounce_log.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+
+/* bounce_log_open - open bounce read stream */
+
+BOUNCE_LOG *bounce_log_open(const char *queue_name, const char *queue_id,
+ int flags, mode_t mode)
+{
+ BOUNCE_LOG *bp;
+ VSTREAM *fp;
+
+#define STREQ(x,y) (strcmp((x),(y)) == 0)
+
+ /*
+ * Logfiles may contain a mixture of old-style (<recipient>: text) and
+ * new-style entries with multiple attributes per recipient.
+ *
+ * Kluge up default DSN status and action for old-style logfiles.
+ */
+ if ((fp = mail_queue_open(queue_name, queue_id, flags, mode)) == 0) {
+ return (0);
+ } else {
+ bp = (BOUNCE_LOG *) mymalloc(sizeof(*bp));
+ bp->fp = fp;
+ bp->buf = vstring_alloc(100);
+ if (STREQ(queue_name, MAIL_QUEUE_DEFER)) {
+ bp->compat_status = mystrdup("4.0.0");
+ bp->compat_action = mystrdup("delayed");
+ } else {
+ bp->compat_status = mystrdup("5.0.0");
+ bp->compat_action = mystrdup("failed");
+ }
+ return (bp);
+ }
+}
+
+/* bounce_log_read - read one record from bounce log file */
+
+BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf)
+{
+ char *recipient;
+ char *text;
+ char *cp;
+ int state;
+
+ /*
+ * Our trivial logfile parser state machine.
+ */
+#define START 0 /* still searching */
+#define FOUND 1 /* in logfile entry */
+
+ /*
+ * Initialize.
+ */
+ state = START;
+ rcpb_reset(rcpt_buf);
+ dsb_reset(dsn_buf);
+
+ /*
+ * Support mixed logfile formats to make migration easier. The same file
+ * can start with old-style records and end with new-style records. With
+ * backwards compatibility, we even have old format followed by new
+ * format within the same logfile entry!
+ */
+ for (;;) {
+ if ((vstring_get_nonl(bp->buf, bp->fp) == VSTREAM_EOF))
+ return (0);
+
+ /*
+ * Logfile entries are separated by blank lines. Even the old ad-hoc
+ * logfile format has a blank line after the last record. This means
+ * we can safely use blank lines to detect the start and end of
+ * logfile entries.
+ */
+ if (STR(bp->buf)[0] == 0) {
+ if (state == FOUND)
+ break;
+ state = START;
+ continue;
+ }
+
+ /*
+ * Sanitize. XXX This needs to be done more carefully with new-style
+ * logfile entries.
+ */
+ cp = printable(STR(bp->buf), '?');
+
+ if (state == START)
+ state = FOUND;
+
+ /*
+ * New style logfile entries are in "name = value" format.
+ */
+ if (ISALNUM(*cp)) {
+ const char *err;
+ char *name;
+ char *value;
+ long offset;
+ int notify;
+
+ /*
+ * Split into name and value.
+ */
+ if ((err = split_nameval(cp, &name, &value)) != 0) {
+ msg_warn("%s: malformed record: %s", VSTREAM_PATH(bp->fp), err);
+ continue;
+ }
+
+ /*
+ * Save attribute value.
+ */
+ if (STREQ(name, MAIL_ATTR_RECIP)) {
+ vstring_strcpy(rcpt_buf->address, *value ?
+ value : "(MAILER-DAEMON)");
+ } else if (STREQ(name, MAIL_ATTR_ORCPT)) {
+ vstring_strcpy(rcpt_buf->orig_addr, *value ?
+ value : "(MAILER-DAEMON)");
+ } else if (STREQ(name, MAIL_ATTR_DSN_ORCPT)) {
+ vstring_strcpy(rcpt_buf->dsn_orcpt, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_NOTIFY)) {
+ if ((notify = atoi(value)) > 0 && DSN_NOTIFY_OK(notify))
+ rcpt_buf->dsn_notify = notify;
+ } else if (STREQ(name, MAIL_ATTR_OFFSET)) {
+ if ((offset = atol(value)) > 0)
+ rcpt_buf->offset = offset;
+ } else if (STREQ(name, MAIL_ATTR_DSN_STATUS)) {
+ vstring_strcpy(dsn_buf->status, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_ACTION)) {
+ vstring_strcpy(dsn_buf->action, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_DTYPE)) {
+ vstring_strcpy(dsn_buf->dtype, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_DTEXT)) {
+ vstring_strcpy(dsn_buf->dtext, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_MTYPE)) {
+ vstring_strcpy(dsn_buf->mtype, value);
+ } else if (STREQ(name, MAIL_ATTR_DSN_MNAME)) {
+ vstring_strcpy(dsn_buf->mname, value);
+ } else if (STREQ(name, MAIL_ATTR_WHY)) {
+ vstring_strcpy(dsn_buf->reason, value);
+ } else {
+ msg_warn("%s: unknown attribute name: %s, ignored",
+ VSTREAM_PATH(bp->fp), name);
+ }
+ continue;
+ }
+
+ /*
+ * Old-style logfile record. Find the recipient address.
+ */
+ if (*cp != '<') {
+ msg_warn("%s: malformed record: %.30s...",
+ VSTREAM_PATH(bp->fp), cp);
+ continue;
+ }
+ recipient = cp + 1;
+ if ((cp = strstr(recipient, ">: ")) == 0) {
+ msg_warn("%s: malformed record: %.30s...",
+ VSTREAM_PATH(bp->fp), recipient - 1);
+ continue;
+ }
+ *cp = 0;
+ vstring_strcpy(rcpt_buf->address, *recipient ?
+ recipient : "(MAILER-DAEMON)");
+
+ /*
+ * Find the text that explains why mail was not deliverable.
+ */
+ text = cp + 2;
+ while (*text && ISSPACE(*text))
+ text++;
+ vstring_strcpy(dsn_buf->reason, text);
+ }
+
+ /*
+ * Specify place holders for missing fields. See also DSN_FROM_DSN_BUF()
+ * and RECIPIENT_FROM_RCPT_BUF() for null and non-null fields.
+ */
+#define BUF_NODATA(buf) (STR(buf)[0] == 0)
+#define BUF_ASSIGN(buf, text) vstring_strcpy((buf), (text))
+
+ if (BUF_NODATA(rcpt_buf->address))
+ BUF_ASSIGN(rcpt_buf->address, "(recipient address unavailable)");
+ if (BUF_NODATA(dsn_buf->status))
+ BUF_ASSIGN(dsn_buf->status, bp->compat_status);
+ if (BUF_NODATA(dsn_buf->action))
+ BUF_ASSIGN(dsn_buf->action, bp->compat_action);
+ if (BUF_NODATA(dsn_buf->reason))
+ BUF_ASSIGN(dsn_buf->reason, "(description unavailable)");
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+ return (bp);
+}
+
+/* bounce_log_close - close bounce reader stream */
+
+int bounce_log_close(BOUNCE_LOG *bp)
+{
+ int ret;
+
+ ret = vstream_fclose(bp->fp);
+ vstring_free(bp->buf);
+ myfree(bp->compat_status);
+ myfree(bp->compat_action);
+ myfree((void *) bp);
+
+ return (ret);
+}
diff --git a/src/global/bounce_log.h b/src/global/bounce_log.h
new file mode 100644
index 0000000..03b78b2
--- /dev/null
+++ b/src/global/bounce_log.h
@@ -0,0 +1,55 @@
+#ifndef _BOUNCE_LOG_H_INCLUDED_
+#define _BOUNCE_LOG_H_INCLUDED_
+
+/*++
+/* NAME
+/* bounce_log 3h
+/* SUMMARY
+/* bounce file reader
+/* SYNOPSIS
+/* #include <bounce_log.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+#include <rcpt_buf.h>
+#include <dsn_buf.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ VSTREAM *fp; /* open file */
+ VSTRING *buf; /* I/O buffer */
+ char *compat_status; /* old logfile compatibility */
+ char *compat_action; /* old logfile compatibility */
+} BOUNCE_LOG;
+
+extern BOUNCE_LOG *bounce_log_open(const char *, const char *, int, mode_t);
+extern BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *);
+extern BOUNCE_LOG *bounce_log_delrcpt(BOUNCE_LOG *);
+extern int bounce_log_close(BOUNCE_LOG *);
+
+#define bounce_log_rewind(bp) vstream_fseek((bp)->fp, 0L, SEEK_SET)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/canon_addr.c b/src/global/canon_addr.c
new file mode 100644
index 0000000..912ae1d
--- /dev/null
+++ b/src/global/canon_addr.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* canon_addr 3
+/* SUMMARY
+/* simple address canonicalization
+/* SYNOPSIS
+/* #include <canon_addr.h>
+/*
+/* VSTRING *canon_addr_external(result, address)
+/* VSTRING *result;
+/* const char *address;
+/*
+/* VSTRING *canon_addr_internal(result, address)
+/* VSTRING *result;
+/* const char *address;
+/* DESCRIPTION
+/* This module provides a simple interface to the address
+/* canonicalization service that is provided by the address
+/* rewriting service.
+/*
+/* canon_addr_external() transforms an address in external (i.e.
+/* quoted) RFC822 form to a fully-qualified address (user@domain).
+/*
+/* canon_addr_internal() transforms an address in internal (i.e.
+/* unquoted) RFC822 form to a fully-qualified address (user@domain).
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages).
+/* SEE ALSO
+/* rewrite_clnt(3) address rewriting client interface
+/* 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 <vstring.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include "rewrite_clnt.h"
+#include "canon_addr.h"
+
+/* canon_addr_external - make address fully qualified, external form */
+
+VSTRING *canon_addr_external(VSTRING *result, const char *addr)
+{
+ return (rewrite_clnt(REWRITE_CANON, addr, result));
+}
+
+/* canon_addr_internal - make address fully qualified, internal form */
+
+VSTRING *canon_addr_internal(VSTRING *result, const char *addr)
+{
+ return (rewrite_clnt_internal(REWRITE_CANON, addr, result));
+}
diff --git a/src/global/canon_addr.h b/src/global/canon_addr.h
new file mode 100644
index 0000000..63b7e29
--- /dev/null
+++ b/src/global/canon_addr.h
@@ -0,0 +1,36 @@
+#ifndef _CANON_ADDR_H_INCLUDED_
+#define _CANON_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* canon_addr 3h
+/* SUMMARY
+/* simple address canonicalization
+/* SYNOPSIS
+/* #include <canon_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *canon_addr_external(VSTRING *, const char *);
+extern VSTRING *canon_addr_internal(VSTRING *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/cfg_parser.c b/src/global/cfg_parser.c
new file mode 100644
index 0000000..de006c7
--- /dev/null
+++ b/src/global/cfg_parser.c
@@ -0,0 +1,322 @@
+/*++
+/* NAME
+/* cfg_parser 3
+/* SUMMARY
+/* configuration parser utilities
+/* SYNOPSIS
+/* #include "cfg_parser.h"
+/*
+/* CFG_PARSER *cfg_parser_alloc(pname)
+/* const char *pname;
+/*
+/* CFG_PARSER *cfg_parser_free(parser)
+/* CFG_PARSER *parser;
+/*
+/* char *cfg_get_str(parser, name, defval, min, max)
+/* const CFG_PARSER *parser;
+/* const char *name;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* int cfg_get_int(parser, name, defval, min, max)
+/* const CFG_PARSER *parser;
+/* const char *name;
+/* int defval;
+/* int min;
+/* int max;
+/*
+/* int cfg_get_bool(parser, name, defval)
+/* const CFG_PARSER *parser;
+/* const char *name;
+/* int defval;
+/*
+/* DICT_OWNER cfg_get_owner(parser)
+/* const CFG_PARSER *parser;
+/* DESCRIPTION
+/* This module implements utilities for parsing parameters defined
+/* either as "\fIname\fR = \fBvalue\fR" in a file pointed to by
+/* \fIpname\fR (the old MySQL style), or as "\fIpname\fR_\fIname\fR =
+/* \fBvalue\fR" in main.cf (the old LDAP style). It unifies the
+/* two styles and provides support for range checking.
+/*
+/* \fIcfg_parser_alloc\fR initializes the parser. The result
+/* is NULL if a configuration file could not be opened.
+/*
+/* \fIcfg_parser_free\fR releases the parser.
+/*
+/* \fIcfg_get_str\fR looks up a string.
+/*
+/* \fIcfg_get_int\fR looks up an integer.
+/*
+/* \fIcfg_get_bool\fR looks up a boolean value.
+/*
+/* \fIdefval\fR is returned when no value was found. \fImin\fR is
+/* zero or specifies a lower limit on the integer value or string
+/* length; \fImax\fR is zero or specifies an upper limit on the
+/* integer value or string length.
+/*
+/* Conveniently, \fIcfg_get_str\fR returns \fBNULL\fR if
+/* \fIdefval\fR is \fBNULL\fR and no value was found. The returned
+/* string has to be freed by the caller if not \fBNULL\fR.
+/*
+/* cfg_get_owner() looks up the configuration file owner.
+/* DIAGNOSTICS
+/* Fatal errors: bad string length, malformed numerical value, malformed
+/* boolean value.
+/* SEE ALSO
+/* mail_conf_str(3) string-valued global configuration parameter support
+/* mail_conf_int(3) integer-valued configuration parameter support
+/* mail_conf_bool(3) boolean-valued configuration parameter support
+/* 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
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "dict.h"
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* Application-specific. */
+
+#include "cfg_parser.h"
+
+/* get string from file */
+
+static char *get_dict_str(const struct CFG_PARSER *parser,
+ const char *name, const char *defval,
+ int min, int max)
+{
+ const char *strval;
+ int len;
+
+ if ((strval = dict_lookup(parser->name, name)) == 0)
+ strval = defval;
+
+ len = strlen(strval);
+ if (min && len < min)
+ msg_fatal("%s: bad string length %d < %d: %s = %s",
+ parser->name, len, min, name, strval);
+ if (max && len > max)
+ msg_fatal("%s: bad string length %d > %d: %s = %s",
+ parser->name, len, max, name, strval);
+ return (mystrdup(strval));
+}
+
+/* get string from main.cf */
+
+static char *get_main_str(const struct CFG_PARSER *parser,
+ const char *name, const char *defval,
+ int min, int max)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(15);
+ vstring_sprintf(buf, "%s_%s", parser->name, name);
+ return (get_mail_conf_str(vstring_str(buf), defval, min, max));
+}
+
+/* get integer from file */
+
+static int get_dict_int(const struct CFG_PARSER *parser,
+ const char *name, int defval, int min, int max)
+{
+ const char *strval;
+ char *end;
+ int intval;
+ long longval;
+
+ if ((strval = dict_lookup(parser->name, name)) != 0) {
+ errno = 0;
+ intval = longval = strtol(strval, &end, 10);
+ if (*strval == 0 || *end != 0 || errno == ERANGE || longval != intval)
+ msg_fatal("%s: bad numerical configuration: %s = %s",
+ parser->name, name, strval);
+ } else
+ intval = defval;
+ if (min && intval < min)
+ msg_fatal("%s: invalid %s parameter value %d < %d",
+ parser->name, name, intval, min);
+ if (max && intval > max)
+ msg_fatal("%s: invalid %s parameter value %d > %d",
+ parser->name, name, intval, max);
+ return (intval);
+}
+
+/* get integer from main.cf */
+
+static int get_main_int(const struct CFG_PARSER *parser,
+ const char *name, int defval, int min, int max)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(15);
+ vstring_sprintf(buf, "%s_%s", parser->name, name);
+ return (get_mail_conf_int(vstring_str(buf), defval, min, max));
+}
+
+/* get boolean option from file */
+
+static int get_dict_bool(const struct CFG_PARSER *parser,
+ const char *name, int defval)
+{
+ const char *strval;
+ int intval;
+
+ if ((strval = dict_lookup(parser->name, name)) != 0) {
+ if (strcasecmp(strval, CONFIG_BOOL_YES) == 0) {
+ intval = 1;
+ } else if (strcasecmp(strval, CONFIG_BOOL_NO) == 0) {
+ intval = 0;
+ } else {
+ msg_fatal("%s: bad boolean configuration: %s = %s",
+ parser->name, name, strval);
+ }
+ } else
+ intval = defval;
+ return (intval);
+}
+
+/* get boolean option from main.cf */
+
+static int get_main_bool(const struct CFG_PARSER *parser,
+ const char *name, int defval)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(15);
+ vstring_sprintf(buf, "%s_%s", parser->name, name);
+ return (get_mail_conf_bool(vstring_str(buf), defval));
+}
+
+/* initialize parser */
+
+CFG_PARSER *cfg_parser_alloc(const char *pname)
+{
+ const char *myname = "cfg_parser_alloc";
+ CFG_PARSER *parser;
+ DICT *dict;
+
+ if (pname == 0 || *pname == 0)
+ msg_fatal("%s: null parser name", myname);
+ parser = (CFG_PARSER *) mymalloc(sizeof(*parser));
+ parser->name = mystrdup(pname);
+ if (*parser->name == '/' || *parser->name == '.') {
+ if (dict_load_file_xt(parser->name, parser->name) == 0) {
+ myfree(parser->name);
+ myfree((void *) parser);
+ return (0);
+ }
+ parser->get_str = get_dict_str;
+ parser->get_int = get_dict_int;
+ parser->get_bool = get_dict_bool;
+ dict = dict_handle(parser->name);
+ } else {
+ parser->get_str = get_main_str;
+ parser->get_int = get_main_int;
+ parser->get_bool = get_main_bool;
+ dict = dict_handle(CONFIG_DICT); /* XXX Use proper API */
+ }
+ if (dict == 0)
+ msg_panic("%s: dict_handle failed", myname);
+ parser->owner = dict->owner;
+ return (parser);
+}
+
+/* get string */
+
+char *cfg_get_str(const CFG_PARSER *parser, const char *name,
+ const char *defval, int min, int max)
+{
+ const char *myname = "cfg_get_str";
+ char *strval;
+
+ strval = parser->get_str(parser, name, (defval ? defval : ""), min, max);
+ if (defval == 0 && *strval == 0) {
+ /* the caller wants NULL instead of "" */
+ myfree(strval);
+ strval = 0;
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: %s = %s", myname, parser->name, name,
+ (strval ? strval : "<NULL>"));
+ return (strval);
+}
+
+/* get integer */
+
+int cfg_get_int(const CFG_PARSER *parser, const char *name, int defval,
+ int min, int max)
+{
+ const char *myname = "cfg_get_int";
+ int intval;
+
+ intval = parser->get_int(parser, name, defval, min, max);
+ if (msg_verbose)
+ msg_info("%s: %s: %s = %d", myname, parser->name, name, intval);
+ return (intval);
+}
+
+/* get boolean option */
+
+int cfg_get_bool(const CFG_PARSER *parser, const char *name, int defval)
+{
+ const char *myname = "cfg_get_bool";
+ int intval;
+
+ intval = parser->get_bool(parser, name, defval);
+ if (msg_verbose)
+ msg_info("%s: %s: %s = %s", myname, parser->name, name,
+ (intval ? "on" : "off"));
+ return (intval);
+}
+
+/* release parser */
+
+CFG_PARSER *cfg_parser_free(CFG_PARSER *parser)
+{
+ const char *myname = "cfg_parser_free";
+
+ if (parser->name == 0 || *parser->name == 0)
+ msg_panic("%s: null parser name", myname);
+ if (*parser->name == '/' || *parser->name == '.') {
+ if (dict_handle(parser->name))
+ dict_unregister(parser->name);
+ }
+ myfree(parser->name);
+ myfree((void *) parser);
+ return (0);
+}
diff --git a/src/global/cfg_parser.h b/src/global/cfg_parser.h
new file mode 100644
index 0000000..c3f8d9b
--- /dev/null
+++ b/src/global/cfg_parser.h
@@ -0,0 +1,56 @@
+#ifndef _CFG_PARSER_H_INCLUDED_
+#define _CFG_PARSER_H_INCLUDED_
+
+/*++
+/* NAME
+/* cfg_parser 3h
+/* SUMMARY
+/* configuration parser utilities
+/* SYNOPSIS
+/* #include "cfg_parser.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+typedef struct CFG_PARSER {
+ char *name;
+ char *(*get_str) (const struct CFG_PARSER *, const char *, const char *,
+ int, int);
+ int (*get_int) (const struct CFG_PARSER *, const char *, int, int, int);
+ int (*get_bool) (const struct CFG_PARSER *, const char *, int);
+ DICT_OWNER owner;
+} CFG_PARSER;
+
+extern CFG_PARSER *cfg_parser_alloc(const char *);
+extern char *cfg_get_str(const CFG_PARSER *, const char *, const char *,
+ int, int);
+extern int cfg_get_int(const CFG_PARSER *, const char *, int, int, int);
+extern int cfg_get_bool(const CFG_PARSER *, const char *, int);
+extern CFG_PARSER *cfg_parser_free(CFG_PARSER *);
+
+#define cfg_get_owner(cfg) ((cfg)->owner)
+
+/* 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
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*--*/
+
+#endif
diff --git a/src/global/cleanup_strerror.c b/src/global/cleanup_strerror.c
new file mode 100644
index 0000000..09a0666
--- /dev/null
+++ b/src/global/cleanup_strerror.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* cleanup_strerror 3
+/* SUMMARY
+/* cleanup status code to string
+/* SYNOPSIS
+/* #include <cleanup_user.h>
+/*
+/* typedef struct {
+/* .in +4
+/* const unsigned status; /* cleanup status */
+/* const int smtp; /* RFC 821 */
+/* const char *dsn; /* RFC 3463 */
+/* const char *text; /* free text */
+/* .in -4
+/* } CLEANUP_STAT_DETAIL;
+/*
+/* const char *cleanup_strerror(code)
+/* int code;
+/*
+/* const CLEANUP_STAT_DETAIL *cleanup_stat_detail(code)
+/* int code;
+/* DESCRIPTION
+/* cleanup_strerror() maps a status code returned by the \fIcleanup\fR
+/* service to printable string.
+/* The result is for read purposes only.
+/*
+/* cleanup_stat_detail() returns a pointer to structure with
+/* assorted information.
+/* DIAGNOSTICS:
+/* Panic: unknown status.
+/* 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 <vstring.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+
+ /*
+ * Mapping from status code to printable string. One message may suffer from
+ * multiple errors, to it is important to list the most severe errors first,
+ * because cleanup_strerror() can report only one error.
+ */
+static const CLEANUP_STAT_DETAIL cleanup_stat_map[] = {
+ CLEANUP_STAT_DEFER, 451, "4.7.1", "service unavailable",
+ CLEANUP_STAT_PROXY, 451, "4.3.0", "queue file write error",
+ CLEANUP_STAT_BAD, 451, "4.3.0", "internal protocol error",
+ CLEANUP_STAT_RCPT, 550, "5.1.0", "no recipients specified",
+ CLEANUP_STAT_HOPS, 554, "5.4.0", "too many hops",
+ CLEANUP_STAT_SIZE, 552, "5.3.4", "message file too big",
+ CLEANUP_STAT_CONT, 550, "5.7.1", "message content rejected",
+ CLEANUP_STAT_WRITE, 451, "4.3.0", "queue file write error",
+ CLEANUP_STAT_NOPERM, 550, "5.7.1", "service denied",
+ CLEANUP_STAT_BARE_LF, 521, "5.5.2", "bare <LF> received",
+};
+
+static CLEANUP_STAT_DETAIL cleanup_stat_success = {
+ CLEANUP_STAT_OK, 250, "2.0.0", "Success",
+};
+
+/* cleanup_strerror - map status code to printable string */
+
+const char *cleanup_strerror(unsigned status)
+{
+ unsigned i;
+
+ if (status == CLEANUP_STAT_OK)
+ return ("Success");
+
+ for (i = 0; i < sizeof(cleanup_stat_map) / sizeof(cleanup_stat_map[0]); i++)
+ if (cleanup_stat_map[i].status & status)
+ return (cleanup_stat_map[i].text);
+
+ msg_panic("cleanup_strerror: unknown status %u", status);
+}
+
+/* cleanup_stat_detail - map status code to table entry with assorted data */
+
+const CLEANUP_STAT_DETAIL *cleanup_stat_detail(unsigned status)
+{
+ unsigned i;
+
+ if (status == CLEANUP_STAT_OK)
+ return (&cleanup_stat_success);
+
+ for (i = 0; i < sizeof(cleanup_stat_map) / sizeof(cleanup_stat_map[0]); i++)
+ if (cleanup_stat_map[i].status & status)
+ return (cleanup_stat_map + i);
+
+ msg_panic("cleanup_stat_detail: unknown status %u", status);
+}
diff --git a/src/global/cleanup_strflags.c b/src/global/cleanup_strflags.c
new file mode 100644
index 0000000..d281c44
--- /dev/null
+++ b/src/global/cleanup_strflags.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* cleanup_strflags 3
+/* SUMMARY
+/* cleanup flags code to string
+/* SYNOPSIS
+/* #include <cleanup_user.h>
+/*
+/* const char *cleanup_strflags(code)
+/* int code;
+/* DESCRIPTION
+/* cleanup_strflags() maps a CLEANUP_FLAGS code to printable string.
+/* The result is for read purposes only. The result is overwritten
+/* upon each call.
+/* 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 "cleanup_user.h"
+
+ /*
+ * Mapping from flags code to printable string.
+ */
+struct cleanup_flag_map {
+ unsigned flag;
+ const char *text;
+};
+
+static struct cleanup_flag_map cleanup_flag_map[] = {
+ CLEANUP_FLAG_BOUNCE, "enable_bad_mail_bounce",
+ CLEANUP_FLAG_FILTER, "enable_header_body_filter",
+ CLEANUP_FLAG_HOLD, "hold_message",
+ CLEANUP_FLAG_DISCARD, "discard_message",
+ CLEANUP_FLAG_BCC_OK, "enable_automatic_bcc",
+ CLEANUP_FLAG_MAP_OK, "enable_address_mapping",
+ CLEANUP_FLAG_MILTER, "enable_milters",
+ CLEANUP_FLAG_SMTP_REPLY, "enable_smtp_reply",
+ CLEANUP_FLAG_SMTPUTF8, "smtputf8_requested",
+ CLEANUP_FLAG_AUTOUTF8, "smtputf8_autodetect",
+};
+
+/* cleanup_strflags - map flags code to printable string */
+
+const char *cleanup_strflags(unsigned flags)
+{
+ static VSTRING *result;
+ unsigned i;
+
+ if (flags == 0)
+ return ("none");
+
+ if (result == 0)
+ result = vstring_alloc(20);
+ else
+ VSTRING_RESET(result);
+
+ for (i = 0; i < sizeof(cleanup_flag_map) / sizeof(cleanup_flag_map[0]); i++) {
+ if (cleanup_flag_map[i].flag & flags) {
+ vstring_sprintf_append(result, "%s ", cleanup_flag_map[i].text);
+ flags &= ~cleanup_flag_map[i].flag;
+ }
+ }
+
+ if (flags != 0 || VSTRING_LEN(result) == 0)
+ msg_panic("cleanup_strflags: unrecognized flag value(s) 0x%x", flags);
+
+ vstring_truncate(result, VSTRING_LEN(result) - 1);
+ VSTRING_TERMINATE(result);
+
+ return (vstring_str(result));
+}
diff --git a/src/global/cleanup_user.h b/src/global/cleanup_user.h
new file mode 100644
index 0000000..74815be
--- /dev/null
+++ b/src/global/cleanup_user.h
@@ -0,0 +1,117 @@
+#ifndef _CLEANUP_USER_H_INCLUDED_
+#define _CLEANUP_USER_H_INCLUDED_
+
+/*++
+/* NAME
+/* cleanup_user 3h
+/* SUMMARY
+/* cleanup user interface codes
+/* SYNOPSIS
+/* #include <cleanup_user.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Client processing options. Flags 16- are reserved for cleanup.h.
+ */
+#define CLEANUP_FLAG_NONE 0 /* No special features */
+#define CLEANUP_FLAG_BOUNCE (1<<0) /* Bounce bad messages */
+#define CLEANUP_FLAG_FILTER (1<<1) /* Enable header/body checks */
+#define CLEANUP_FLAG_HOLD (1<<2) /* Place message on hold */
+#define CLEANUP_FLAG_DISCARD (1<<3) /* Discard message silently */
+#define CLEANUP_FLAG_BCC_OK (1<<4) /* Ok to add auto-BCC addresses */
+#define CLEANUP_FLAG_MAP_OK (1<<5) /* Ok to map addresses */
+#define CLEANUP_FLAG_MILTER (1<<6) /* Enable Milter applications */
+#define CLEANUP_FLAG_SMTP_REPLY (1<<7) /* Enable SMTP reply */
+#define CLEANUP_FLAG_SMTPUTF8 (1<<8) /* SMTPUTF8 requested */
+#define CLEANUP_FLAG_AUTOUTF8 (1<<9) /* Autodetect SMTPUTF8 */
+
+#define CLEANUP_FLAG_FILTER_ALL (CLEANUP_FLAG_FILTER | CLEANUP_FLAG_MILTER)
+ /*
+ * These are normally set when receiving mail from outside.
+ */
+#define CLEANUP_FLAG_MASK_EXTERNAL \
+ (CLEANUP_FLAG_FILTER_ALL | CLEANUP_FLAG_BCC_OK | CLEANUP_FLAG_MAP_OK)
+
+ /*
+ * These are normally set when generating notices or when forwarding mail
+ * internally.
+ */
+#define CLEANUP_FLAG_MASK_INTERNAL CLEANUP_FLAG_MAP_OK
+
+ /*
+ * These are set on the fly while processing SMTP envelopes or message
+ * content.
+ */
+#define CLEANUP_FLAG_MASK_EXTRA \
+ (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)
+
+ /*
+ * Diagnostics.
+ *
+ * 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.
+ */
+#define CLEANUP_STAT_OK 0 /* Success. */
+#define CLEANUP_STAT_BAD (1<<0) /* Internal protocol error */
+#define CLEANUP_STAT_WRITE (1<<1) /* Error writing message file */
+#define CLEANUP_STAT_SIZE (1<<2) /* Message file too big */
+#define CLEANUP_STAT_CONT (1<<3) /* Message content rejected */
+#define CLEANUP_STAT_HOPS (1<<4) /* Too many hops */
+#define CLEANUP_STAT_RCPT (1<<6) /* No recipients found */
+#define CLEANUP_STAT_PROXY (1<<7) /* Proxy reject */
+#define CLEANUP_STAT_DEFER (1<<8) /* Temporary reject */
+#define CLEANUP_STAT_NOPERM (1<<9) /* Denied by non-content policy */
+
+ /*
+ * Non-cleanup errors that live in the same bitmask space, to centralize
+ * error handling.
+ */
+#define CLEANUP_STAT_BARE_LF (1<<16) /* Bare <LF> received */
+
+ /*
+ * These are set when we can't bounce even if we were asked to.
+ */
+#define CLEANUP_STAT_MASK_CANT_BOUNCE \
+ (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_DEFER \
+ | CLEANUP_STAT_RCPT)
+
+ /*
+ * These are set when we can't examine every record of a message.
+ */
+#define CLEANUP_STAT_MASK_INCOMPLETE \
+ (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_SIZE \
+ | CLEANUP_STAT_DEFER)
+
+ /*
+ * Mapping from status code to DSN detail and free text.
+ */
+typedef struct {
+ const unsigned status; /* CLEANUP_STAT_MUMBLE */
+ const int smtp; /* RFC 821 */
+ const char *dsn; /* RFC 3463 */
+ const char *text; /* free text */
+} CLEANUP_STAT_DETAIL;
+
+extern const char *cleanup_strerror(unsigned);
+extern const CLEANUP_STAT_DETAIL *cleanup_stat_detail(unsigned);
+extern const char *cleanup_strflags(unsigned);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/clnt_stream.c b/src/global/clnt_stream.c
new file mode 100644
index 0000000..ca3ce4f
--- /dev/null
+++ b/src/global/clnt_stream.c
@@ -0,0 +1,278 @@
+/*++
+/* NAME
+/* clnt_stream 3
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <clnt_stream.h>
+/*
+/* typedef void (*CLNT_STREAM_HANDSHAKE_FN)(VSTREAM *)
+/*
+/* CLNT_STREAM *clnt_stream_create(class, service, timeout, ttl,
+/* handshake)
+/* const char *class;
+/* const char *service;
+/* int timeout;
+/* int ttl;
+/* CLNT_STREAM_HANDSHAKE_FN *handshake;
+/*
+/* VSTREAM *clnt_stream_access(clnt_stream)
+/* CLNT_STREAM *clnt_stream;
+/*
+/* void clnt_stream_recover(clnt_stream)
+/* CLNT_STREAM *clnt_stream;
+/*
+/* void clnt_stream_free(clnt_stream)
+/* CLNT_STREAM *clnt_stream;
+/* DESCRIPTION
+/* This module maintains local IPC client endpoints that automatically
+/* disconnect after a being idle for a configurable amount of time,
+/* that disconnect after a configurable time to live,
+/* and that transparently handle most server-initiated disconnects.
+/* Server disconnect is detected by read-selecting the client endpoint.
+/* The code assumes that the server has disconnected when the endpoint
+/* becomes readable.
+/*
+/* clnt_stream_create() instantiates a client endpoint.
+/*
+/* clnt_stream_access() returns an open stream to the service specified
+/* to clnt_stream_create(). The stream instance may change between calls.
+/* This function returns null when the handshake function returned an
+/* error.
+/*
+/* clnt_stream_recover() recovers from a server-initiated disconnect
+/* that happened in the middle of an I/O operation.
+/*
+/* clnt_stream_free() destroys of the specified client endpoint.
+/*
+/* Arguments:
+/* .IP class
+/* The service class, private or public.
+/* .IP service
+/* The service endpoint name. The name is limited to local IPC
+/* over sockets or equivalent.
+/* .IP timeout
+/* Idle time after which the client disconnects.
+/* .IP ttl
+/* Upper bound on the time that a connection is allowed to persist.
+/* .IP handshake
+/* Null pointer, or pointer to function that will be called
+/* at the start of a new connection and that returns 0 in case
+/* of success.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: mail system is down,
+/* out of memory.
+/* SEE ALSO
+/* mail_proto(3h) low-level mail component glue.
+/* 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 <events.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+#include "mail_params.h"
+#include "clnt_stream.h"
+
+/* Application-specific. */
+
+ /*
+ * CLNT_STREAM is an opaque structure. None of the access methods can easily
+ * be implemented as a macro, and access is not performance critical anyway.
+ */
+struct CLNT_STREAM {
+ VSTREAM *vstream; /* buffered I/O */
+ int timeout; /* time before client disconnect */
+ int ttl; /* time before client disconnect */
+ CLNT_STREAM_HANDSHAKE_FN handshake;
+ char *class; /* server class */
+ char *service; /* server name */
+};
+
+static void clnt_stream_close(CLNT_STREAM *);
+
+/* clnt_stream_event - server-initiated disconnect or client-side timeout */
+
+static void clnt_stream_event(int unused_event, void *context)
+{
+ CLNT_STREAM *clnt_stream = (CLNT_STREAM *) context;
+
+ /*
+ * Sanity check. This routine causes the stream to be closed, so it
+ * cannot be called when the stream is already closed.
+ */
+ if (clnt_stream->vstream == 0)
+ msg_panic("clnt_stream_event: stream is closed");
+
+ clnt_stream_close(clnt_stream);
+}
+
+/* clnt_stream_ttl_event - client-side expiration */
+
+static void clnt_stream_ttl_event(int event, void *context)
+{
+
+ /*
+ * XXX This function is needed only because event_request_timer() cannot
+ * distinguish between requests that specify the same call-back routine
+ * and call-back context. The fix is obvious: specify a request ID along
+ * with the call-back routine, but there is too much code that would have
+ * to be changed.
+ *
+ * XXX Should we be concerned that an overly aggressive optimizer will
+ * eliminate this function and replace calls to clnt_stream_ttl_event()
+ * by direct calls to clnt_stream_event()? It should not, because there
+ * exists code that takes the address of both functions.
+ */
+ clnt_stream_event(event, context);
+}
+
+/* clnt_stream_open - connect to service */
+
+static void clnt_stream_open(CLNT_STREAM *clnt_stream)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (clnt_stream->vstream)
+ msg_panic("clnt_stream_open: stream is open");
+
+ /*
+ * Schedule a read event so that we can clean up when the remote side
+ * disconnects, and schedule a timer event so that we can cleanup an idle
+ * connection. Note that both events are handled by the same routine.
+ *
+ * Finally, schedule an event to force disconnection even when the
+ * connection is not idle. This is to prevent one client from clinging on
+ * to a server forever.
+ */
+ clnt_stream->vstream = mail_connect_wait(clnt_stream->class,
+ clnt_stream->service);
+ close_on_exec(vstream_fileno(clnt_stream->vstream), CLOSE_ON_EXEC);
+ event_enable_read(vstream_fileno(clnt_stream->vstream), clnt_stream_event,
+ (void *) clnt_stream);
+ event_request_timer(clnt_stream_event, (void *) clnt_stream,
+ clnt_stream->timeout);
+ event_request_timer(clnt_stream_ttl_event, (void *) clnt_stream,
+ clnt_stream->ttl);
+}
+
+/* clnt_stream_close - disconnect from service */
+
+static void clnt_stream_close(CLNT_STREAM *clnt_stream)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (clnt_stream->vstream == 0)
+ msg_panic("clnt_stream_close: stream is closed");
+
+ /*
+ * Be sure to disable read and timer events.
+ */
+ if (msg_verbose)
+ msg_info("%s stream disconnect", clnt_stream->service);
+ event_disable_readwrite(vstream_fileno(clnt_stream->vstream));
+ event_cancel_timer(clnt_stream_event, (void *) clnt_stream);
+ event_cancel_timer(clnt_stream_ttl_event, (void *) clnt_stream);
+ (void) vstream_fclose(clnt_stream->vstream);
+ clnt_stream->vstream = 0;
+}
+
+/* clnt_stream_recover - recover from server-initiated disconnect */
+
+void clnt_stream_recover(CLNT_STREAM *clnt_stream)
+{
+
+ /*
+ * Clean up. Don't re-connect until the caller needs it.
+ */
+ if (clnt_stream->vstream)
+ clnt_stream_close(clnt_stream);
+}
+
+/* clnt_stream_access - access a client stream */
+
+VSTREAM *clnt_stream_access(CLNT_STREAM *clnt_stream)
+{
+ CLNT_STREAM_HANDSHAKE_FN handshake;
+
+ /*
+ * Open a stream or restart the idle timer.
+ *
+ * Important! Do not restart the TTL timer!
+ */
+ if (clnt_stream->vstream == 0) {
+ clnt_stream_open(clnt_stream);
+ handshake = clnt_stream->handshake;
+ } else if (readable(vstream_fileno(clnt_stream->vstream))) {
+ clnt_stream_close(clnt_stream);
+ clnt_stream_open(clnt_stream);
+ handshake = clnt_stream->handshake;
+ } else {
+ event_request_timer(clnt_stream_event, (void *) clnt_stream,
+ clnt_stream->timeout);
+ handshake = 0;
+ }
+ if (handshake != 0 && handshake(clnt_stream->vstream) != 0)
+ return (0);
+ return (clnt_stream->vstream);
+}
+
+/* clnt_stream_create - create client stream connection */
+
+CLNT_STREAM *clnt_stream_create(const char *class, const char *service,
+ int timeout, int ttl,
+ CLNT_STREAM_HANDSHAKE_FN handshake)
+{
+ CLNT_STREAM *clnt_stream;
+
+ /*
+ * Don't open the stream until the caller needs it.
+ */
+ clnt_stream = (CLNT_STREAM *) mymalloc(sizeof(*clnt_stream));
+ clnt_stream->vstream = 0;
+ clnt_stream->timeout = timeout;
+ clnt_stream->ttl = ttl;
+ clnt_stream->handshake = handshake;
+ clnt_stream->class = mystrdup(class);
+ clnt_stream->service = mystrdup(service);
+ return (clnt_stream);
+}
+
+/* clnt_stream_free - destroy client stream instance */
+
+void clnt_stream_free(CLNT_STREAM *clnt_stream)
+{
+ if (clnt_stream->vstream)
+ clnt_stream_close(clnt_stream);
+ myfree(clnt_stream->class);
+ myfree(clnt_stream->service);
+ myfree((void *) clnt_stream);
+}
diff --git a/src/global/clnt_stream.h b/src/global/clnt_stream.h
new file mode 100644
index 0000000..bb92e2f
--- /dev/null
+++ b/src/global/clnt_stream.h
@@ -0,0 +1,48 @@
+#ifndef _CLNT_STREAM_H_INCLUDED_
+#define _CLNT_STREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* clnt_stream 3h
+/* SUMMARY
+/* client socket maintenance
+/* SYNOPSIS
+/* #include <clnt_stream.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct CLNT_STREAM CLNT_STREAM;
+typedef int (*CLNT_STREAM_HANDSHAKE_FN)(VSTREAM *);
+
+extern CLNT_STREAM *clnt_stream_create(const char *, const char *, int, int,
+ CLNT_STREAM_HANDSHAKE_FN);
+extern VSTREAM *clnt_stream_access(CLNT_STREAM *);
+extern const char *clnt_stream_path(CLNT_STREAM *);
+extern void clnt_stream_recover(CLNT_STREAM *);
+extern void clnt_stream_free(CLNT_STREAM *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/compat_level.c b/src/global/compat_level.c
new file mode 100644
index 0000000..7430878
--- /dev/null
+++ b/src/global/compat_level.c
@@ -0,0 +1,461 @@
+/*++
+/* NAME
+/* compat_level 3
+/* SUMMARY
+/* compatibility_level support
+/* SYNOPSIS
+/* #include <compat_level.h>
+/*
+/* void compat_level_relop_register(void)
+/*
+/* long compat_level_from_string(
+/* const char *str,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_numbers(
+/* long major,
+/* long minor,
+/* long patch,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* const char *compat_level_to_string(
+/* long compat_level,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* AUXULIARY FUNCTIONS
+/* long compat_level_from_major_minor(
+/* long major,
+/* long minor,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_major(
+/* long major,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* DESCRIPTION
+/* This module supports compatibility level syntax with
+/* "major.minor.patch" but will also accept the shorter forms
+/* "major.minor" and "major" (missing members default to zero).
+/* Compatibility levels with multiple numbers cannot be compared
+/* as strings or as floating-point numbers (for example, "3.10"
+/* would be smaller than "3.9").
+/*
+/* The major number can range from [0..2047] inclusive (11
+/* bits) or more, while the minor and patch numbers can range
+/* from [0..1023] inclusive (10 bits).
+/*
+/* compat_level_from_string() converts a compatibility level
+/* from string form to numerical form for easy comparison.
+/* Valid input results in a non-negative result. In case of
+/* error, compat_level_from_string() reports the problem with
+/* the provided function, and returns -1 if that function does
+/* not terminate execution.
+/*
+/* compat_level_from_numbers() creates an internal-form
+/* compatibility level from distinct numbers. Valid input
+/* results in a non-negative result. In case of error,
+/* compat_level_from_numbers() reports the problem with the
+/* provided function, and returns -1 if that function does not
+/* terminate execution.
+/*
+/* The functions compat_level_from_major_minor() and
+/* compat_level_from_major() are helpers that default the missing
+/* information to zeroes.
+/*
+/* compat_level_to_string() converts a compatibility level
+/* from numerical form to canonical string form. Valid input
+/* results in a non-null result. In case of error,
+/* compat_level_to_string() reports the problem with the
+/* provided function, and returns a null pointer if that
+/* function does not terminate execution.
+/*
+/* compat_level_relop_register() registers a mac_expand() callback
+/* that registers operators such as <=level, >level, that compare
+/* compatibility levels. This function should be called before
+/* loading parameter settings from main.cf.
+/* DIAGNOSTICS
+/* info, .., panic: bad compatibility_level syntax.
+/* BUGS
+/* The patch and minor fields range from 0..1023 (10 bits) while
+/* the major field ranges from 0..COMPAT_MAJOR_SHIFT47 or more
+/* (11 bits or more).
+/*
+/* This would be a great use case for functions returning
+/* StatusOr<compat_level_t> or StatusOr<string>, but is it a bit
+/* late for a port to C++.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+
+ /*
+ * Utility library.
+ */
+#include <mac_expand.h>
+#include <msg.h>
+#include <sane_strtol.h>
+
+ /*
+ * For easy comparison we convert a three-number compatibility level into
+ * just one number, using different bit ranges for the major version, minor
+ * version, and patch level.
+ *
+ * We use long integers because standard C guarantees that long has at last 32
+ * bits instead of int which may have only 16 bits (though it is unlikely
+ * that Postfix would run on such systems). That gives us 11 or more bits
+ * for the major version, and 10 bits for minor the version and patchlevel.
+ *
+ * Below are all the encoding details in one place. This is easier to verify
+ * than wading through code.
+ */
+#define COMPAT_MAJOR_SHIFT \
+ (COMPAT_MINOR_SHIFT + COMPAT_MINOR_WIDTH)
+
+#define COMPAT_MINOR_SHIFT COMPAT_PATCH_WIDTH
+#define COMPAT_MINOR_BITS 0x3ff
+#define COMPAT_MINOR_WIDTH 10
+
+#define COMPAT_PATCH_BITS 0x3ff
+#define COMPAT_PATCH_WIDTH 10
+
+#define GOOD_MAJOR(m) ((m) >= 0 && (m) <= (LONG_MAX >> COMPAT_MAJOR_SHIFT))
+#define GOOD_MINOR(m) ((m) >= 0 && (m) <= COMPAT_MINOR_BITS)
+#define GOOD_PATCH(p) ((p) >= 0 && (p) <= COMPAT_PATCH_BITS)
+
+#define ENCODE_MAJOR(m) ((m) << COMPAT_MAJOR_SHIFT)
+#define ENCODE_MINOR(m) ((m) << COMPAT_MINOR_SHIFT)
+#define ENCODE_PATCH(p) (p)
+
+#define DECODE_MAJOR(l) ((l) >> COMPAT_MAJOR_SHIFT)
+#define DECODE_MINOR(l) (((l) >> COMPAT_MINOR_SHIFT) & COMPAT_MINOR_BITS)
+#define DECODE_PATCH(l) ((l) & COMPAT_PATCH_BITS)
+
+ /*
+ * Global library.
+ */
+#include <compat_level.h>
+
+/* compat_level_from_string - convert major[.minor] to comparable type */
+
+long compat_level_from_string(const char *str,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ long major, minor, patch, res = 0;
+ const char *start;
+ char *remainder;
+
+ start = str;
+ major = sane_strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MAJOR(major)) {
+ res = ENCODE_MAJOR(major);
+ if (*remainder == 0)
+ return res;
+ start = remainder + 1;
+ minor = sane_strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MINOR(minor)) {
+ res |= ENCODE_MINOR(minor);
+ if (*remainder == 0)
+ return (res);
+ start = remainder + 1;
+ patch = sane_strtol(start, &remainder, 10);
+ if (start < remainder && *remainder == 0 && errno != ERANGE
+ && GOOD_PATCH(patch)) {
+ return (res | ENCODE_PATCH(patch));
+ }
+ }
+ }
+ msg_fn("malformed compatibility level syntax: \"%s\"", str);
+ return (-1);
+}
+
+/* compat_level_from_numbers - internal form from numbers */
+
+long compat_level_from_numbers(long major, long minor, long patch,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_from_numbers";
+
+ /*
+ * Sanity checks.
+ */
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad major version: %ld", myname, major);
+ return (-1);
+ }
+ if (!GOOD_MINOR(minor)) {
+ msg_fn("%s: bad minor version: %ld", myname, minor);
+ return (-1);
+ }
+ if (!GOOD_PATCH(patch)) {
+ msg_fn("%s: bad patch level: %ld", myname, patch);
+ return (-1);
+ }
+
+ /*
+ * Conversion.
+ */
+ return (ENCODE_MAJOR(major) | ENCODE_MINOR(minor) | ENCODE_PATCH(patch));
+}
+
+/* compat_level_to_string - pretty-print a compatibility level */
+
+const char *compat_level_to_string(long compat_level,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_to_string";
+ static VSTRING *buf;
+ long major;
+ long minor;
+ long patch;
+
+ /*
+ * Sanity check.
+ */
+ if (compat_level < 0) {
+ msg_fn("%s: bad compatibility level: %ld", myname, compat_level);
+ return (0);
+ }
+
+ /*
+ * Compatibility levels 0..2 have no minor or patch level.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ major = DECODE_MAJOR(compat_level);
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad compatibility major level: %ld", myname, compat_level);
+ return (0);
+ }
+ vstring_sprintf(buf, "%ld", major);
+ if (major > 2) {
+
+ /*
+ * Expect that major.minor will be common.
+ */
+ minor = DECODE_MINOR(compat_level);
+ vstring_sprintf_append(buf, ".%ld", minor);
+
+ /*
+ * Expect that major.minor.patch will be rare.
+ */
+ patch = DECODE_PATCH(compat_level);
+ if (patch)
+ vstring_sprintf_append(buf, ".%ld", patch);
+ }
+ return (vstring_str(buf));
+}
+
+/* compat_relop_eval - mac_expand callback */
+
+static MAC_EXP_OP_RES compat_relop_eval(const char *left_str, int relop,
+ const char *rite_str)
+{
+ const char myname[] = "compat_relop_eval";
+ long left_val, rite_val;
+
+ /*
+ * Negative result means error.
+ */
+ if ((left_val = compat_level_from_string(left_str, msg_warn)) < 0
+ || (rite_val = compat_level_from_string(rite_str, msg_warn)) < 0)
+ return (MAC_EXP_OP_RES_ERROR);
+
+ /*
+ * Valid result. The difference between non-negative numbers will no
+ * overflow.
+ */
+ long delta = left_val - rite_val;
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+/* compat_level_register - register comparison operators */
+
+void compat_level_relop_register(void)
+{
+ int compat_level_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+ static int register_done;
+
+ if (register_done++ == 0)
+ mac_expand_add_relop(compat_level_relops, "level", compat_relop_eval);
+}
+
+#ifdef TEST
+#include <unistd.h>
+
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+static void test_expand(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ compat_level_relop_register();
+
+ /*
+ * Loop over the inputs.
+ */
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+}
+
+static void test_convert(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ long compat_level;
+ const char *as_string;
+
+ /*
+ * Read compatibility level.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ if ((compat_level = compat_level_from_string(vstring_str(buf),
+ msg_warn)) < 0)
+ continue;
+ msg_info("%s -> 0x%lx", vstring_str(buf), compat_level);
+ errno = ERANGE;
+ if ((as_string = compat_level_to_string(compat_level,
+ msg_warn)) == 0)
+ continue;
+ msg_info("0x%lx->%s", compat_level, as_string);
+ }
+ vstring_free(buf);
+}
+
+static NORETURN usage(char **argv)
+{
+ msg_fatal("usage: %s option\n-c (convert)\n-c (expand)", argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ int mode = 0;
+
+#define MODE_EXPAND (1<<0)
+#define MODE_CONVERT (1<<1)
+
+ while ((ch = GETOPT(argc, argv, "cx")) > 0) {
+ switch (ch) {
+ case 'c':
+ mode |= MODE_CONVERT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ mode |= MODE_EXPAND;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+ switch (mode) {
+ case MODE_CONVERT:
+ test_convert();
+ break;
+ case MODE_EXPAND:
+ test_expand();
+ break;
+ default:
+ usage(argv);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/global/compat_level.h b/src/global/compat_level.h
new file mode 100644
index 0000000..076e887
--- /dev/null
+++ b/src/global/compat_level.h
@@ -0,0 +1,43 @@
+#ifndef _COMPAT_LEVEL_H_INCLUDED_
+#define _COMPAT_LEVEL_H_INCLUDED_
+
+/*++
+/* NAME
+/* compat_level 3h
+/* SUMMARY
+/* compatibility_level support
+/* SYNOPSIS
+/* #include <compat_level.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void compat_level_relop_register(void);
+extern long compat_level_from_string(const char *,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+extern long compat_level_from_numbers(long, long, long,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+extern const char *compat_level_to_string(long,
+ void PRINTFLIKE(1, 2) (*) (const char *,...));
+
+#define compat_level_from_major(major, msg_fn) \
+ compat_level_from_major_minor((major), 0, (msg_fn))
+#define compat_level_from_major_minor(major, minor, msg_fn) \
+ compat_level_from_numbers((major), (minor), 0, (msg_fn))
+
+#
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/compat_level_convert.in b/src/global/compat_level_convert.in
new file mode 100644
index 0000000..e7cec78
--- /dev/null
+++ b/src/global/compat_level_convert.in
@@ -0,0 +1,22 @@
+3.2.1
+3.2.1.0
+3a.2.1
+a3.2.1
+3.a2.1
+3.2a.1
+3.2a.a1
+3.2a.1a
+3
+3.
+3.2.
+3.1.1.
+2
+1
+0
+3.1023
+3.1024
+3.2.1023
+3.2.1024
+-3
+3.-2.1
+3.2.-1
diff --git a/src/global/compat_level_convert.ref b/src/global/compat_level_convert.ref
new file mode 100644
index 0000000..17f29dc
--- /dev/null
+++ b/src/global/compat_level_convert.ref
@@ -0,0 +1,29 @@
+unknown: 3.2.1 -> 0x300801
+unknown: 0x300801->3.2.1
+unknown: warning: malformed compatibility level syntax: "3.2.1.0"
+unknown: warning: malformed compatibility level syntax: "3a.2.1"
+unknown: warning: malformed compatibility level syntax: "a3.2.1"
+unknown: warning: malformed compatibility level syntax: "3.a2.1"
+unknown: warning: malformed compatibility level syntax: "3.2a.1"
+unknown: warning: malformed compatibility level syntax: "3.2a.a1"
+unknown: warning: malformed compatibility level syntax: "3.2a.1a"
+unknown: 3 -> 0x300000
+unknown: 0x300000->3.0
+unknown: warning: malformed compatibility level syntax: "3."
+unknown: warning: malformed compatibility level syntax: "3.2."
+unknown: warning: malformed compatibility level syntax: "3.1.1."
+unknown: 2 -> 0x200000
+unknown: 0x200000->2
+unknown: 1 -> 0x100000
+unknown: 0x100000->1
+unknown: 0 -> 0x0
+unknown: 0x0->0
+unknown: 3.1023 -> 0x3ffc00
+unknown: 0x3ffc00->3.1023
+unknown: warning: malformed compatibility level syntax: "3.1024"
+unknown: 3.2.1023 -> 0x300bff
+unknown: 0x300bff->3.2.1023
+unknown: warning: malformed compatibility level syntax: "3.2.1024"
+unknown: warning: malformed compatibility level syntax: "-3"
+unknown: warning: malformed compatibility level syntax: "3.-2.1"
+unknown: warning: malformed compatibility level syntax: "3.2.-1"
diff --git a/src/global/compat_level_expand.in b/src/global/compat_level_expand.in
new file mode 100644
index 0000000..0f5208b
--- /dev/null
+++ b/src/global/compat_level_expand.in
@@ -0,0 +1,27 @@
+compatibility_level = 3
+
+${ {$compatibility_level} ==level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} !=level {3.0} ? {bad} : {good} }
+${ {$compatibility_level} ==level {3.10} ? {bad} : {good} }
+${ {$compatibility_level} !=level {3.10} ? {good} : {bad} }
+${ {$compatibility_level} <level {4} ? {good} : {bad} }
+${ {$compatibility_level} <=level {4} ? {good} : {bad} }
+${ {$compatibility_level} <level {2} ? {bad} : {good} }
+${ {$compatibility_level} <=level {2} ? {bad} : {good} }
+${ {$compatibility_level} <=level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} >level {4} ? {bad} : {good} }
+${ {$compatibility_level} >=level {4} ? {bad} : {good} }
+${ {$compatibility_level} >level {2} ? {good} : {bad} }
+${ {$compatibility_level} >=level {2} ? {good} : {bad} }
+${ {$compatibility_level} >=level {3.0} ? {good} : {bad} }
+${ {$compatibility_level} >=level {3A} ? {error} : {error} }
+${ {$compatibility_level} >=level {3.} ? {error} : {error} }
+${ {$compatibility_level} >=level {.1} ? {error} : {error} }
+${ {3} > {3.2} ? {bad} : {good} }
+${ {3} >level {3.2} ? {bad} : {good} }
+${ {3} < {3.2} ? {good} : {bad} }
+${ {3} <level {3.2} ? {good} : {bad} }
+${ {3.10} > {3.2} ? {bad} : {good} }
+${ {3.10} >level {3.2} ? {good} : {bad} }
+${ {3.10} < {3.2} ? {good} : {bad} }
+${ {3.10} <level {3.2} ? {bad} : {good} }
diff --git a/src/global/compat_level_expand.ref b/src/global/compat_level_expand.ref
new file mode 100644
index 0000000..bd1836b
--- /dev/null
+++ b/src/global/compat_level_expand.ref
@@ -0,0 +1,55 @@
+<< compatibility_level = 3
+<<
+<< ${ {$compatibility_level} ==level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} !=level {3.0} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} ==level {3.10} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} !=level {3.10} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <level {4} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {4} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} <level {2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} <=level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >level {4} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {4} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {$compatibility_level} >level {2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {3.0} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {$compatibility_level} >=level {3A} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: "3A"
+stat=1 result=
+<< ${ {$compatibility_level} >=level {3.} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: "3."
+stat=1 result=
+<< ${ {$compatibility_level} >=level {.1} ? {error} : {error} }
+unknown: warning: malformed compatibility level syntax: ".1"
+stat=1 result=
+<< ${ {3} > {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3} >level {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3} < {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3} <level {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} > {3.2} ? {bad} : {good} }
+stat=0 result=good
+<< ${ {3.10} >level {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} < {3.2} ? {good} : {bad} }
+stat=0 result=good
+<< ${ {3.10} <level {3.2} ? {bad} : {good} }
+stat=0 result=good
diff --git a/src/global/config.h b/src/global/config.h
new file mode 100644
index 0000000..d03a652
--- /dev/null
+++ b/src/global/config.h
@@ -0,0 +1,59 @@
+#ifndef _CONFIG_H_INCLUDED_
+#define _CONFIG_H_INCLUDED_
+
+/*++
+/* NAME
+/* config 3h
+/* SUMMARY
+/* compatibility
+/* SYNOPSIS
+/* #include <config.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mail_conf.h>
+
+ /*
+ * Aliases.
+ */
+#define config_eval mail_conf_eval
+#define config_lookup mail_conf_lookup
+#define config_lookup_eval mail_conf_lookup_eval
+#define config_read mail_conf_read
+#define read_config mail_conf_update
+#define get_config_bool get_mail_conf_bool
+#define get_config_bool_fn get_mail_conf_bool_fn
+#define get_config_bool_fn_table get_mail_conf_bool_fn_table
+#define get_config_bool_table get_mail_conf_bool_table
+#define get_config_int get_mail_conf_int
+#define get_config_int2 get_mail_conf_int2
+#define get_config_int_fn get_mail_conf_int_fn
+#define get_config_int_fn_table get_mail_conf_int_fn_table
+#define get_config_int_table get_mail_conf_int_table
+#define get_config_raw get_mail_conf_raw
+#define get_config_raw_fn get_mail_conf_raw_fn
+#define get_config_raw_fn_table get_mail_conf_raw_fn_table
+#define get_config_raw_table get_mail_conf_raw_table
+#define get_config_str get_mail_conf_str
+#define get_config_str_fn get_mail_conf_str_fn
+#define get_config_str_fn_table get_mail_conf_str_fn_table
+#define get_config_str_table get_mail_conf_str_table
+#define set_config_bool set_mail_conf_bool
+#define set_config_int set_mail_conf_int
+#define set_config_str set_mail_conf_str
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/config_known_tcp_ports.c b/src/global/config_known_tcp_ports.c
new file mode 100644
index 0000000..563bbd3
--- /dev/null
+++ b/src/global/config_known_tcp_ports.c
@@ -0,0 +1,257 @@
+/*++
+/* NAME
+/* config_known_tcp_ports 3
+/* SUMMARY
+/* parse and store known TCP port configuration
+/* SYNOPSIS
+/* #include <config_known_tcp_ports.h>
+/*
+/* void config_known_tcp_ports(
+/* const char *source,
+/* const char *settings);
+/* DESCRIPTION
+/* config_known_tcp_ports() parses the known TCP port information
+/* in the settings argument, and reports any warnings to the standard
+/* error stream. The source argument is used to provide warning
+/* context. It typically is a configuration parameter name.
+/* .SH EXPECTED SYNTAX (ABNF)
+/* configuration = empty | name-to-port *("," name-to-port)
+/* name-to-port = 1*(name "=") port
+/* SH EXAMPLES
+/* In the example below, the whitespace is optional.
+/* smtp = 25, smtps = submissions = 465, submission = 587
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <known_tcp_ports.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Application-specific.
+ */
+#include <config_known_tcp_ports.h>
+
+/* config_known_tcp_ports - parse configuration and store associations */
+
+void config_known_tcp_ports(const char *source, const char *settings)
+{
+ ARGV *associations;
+ ARGV *association;
+ char **cpp;
+
+ clear_known_tcp_ports();
+
+ /*
+ * The settings is in the form of associations separated by comma. Split
+ * it into separate associations.
+ */
+ associations = argv_split(settings, ",");
+ if (associations->argc == 0) {
+ argv_free(associations);
+ return;
+ }
+
+ /*
+ * Each association is in the form of "1*(name =) port". We use
+ * argv_split() to carve this up, then we use mystrtok() to validate the
+ * individual fragments. But first we prepend and append space so that we
+ * get sensible results when an association starts or ends in "=".
+ */
+ for (cpp = associations->argv; *cpp != 0; cpp++) {
+ char *temp = concatenate(" ", *cpp, " ", (char *) 0);
+
+ association = argv_split_at(temp, '=');
+ myfree(temp);
+
+ if (association->argc == 0) {
+ /* empty, ignore */ ;
+ } else if (association->argc == 1) {
+ msg_warn("%s: in \"%s\" is not in \"name = value\" form",
+ source, *cpp);
+ } else {
+ char *bp;
+ char *lhs;
+ char *rhs;
+ const char *err = 0;
+ int n;
+
+ bp = association->argv[association->argc - 1];
+ if ((rhs = mystrtok(&bp, CHARS_SPACE)) == 0) {
+ err = "missing port value after \"=\"";
+ } else if (mystrtok(&bp, CHARS_SPACE) != 0) {
+ err = "whitespace in port number";
+ } else {
+ for (n = 0; n < association->argc - 1; n++) {
+ const char *new_err;
+
+ bp = association->argv[n];
+ if ((lhs = mystrtok(&bp, CHARS_SPACE)) == 0) {
+ new_err = "missing service name before \"=\"";
+ } else if (mystrtok(&bp, CHARS_SPACE) != 0) {
+ new_err = "whitespace in service name";
+ } else {
+ new_err = add_known_tcp_port(lhs, rhs);
+ }
+ if (new_err != 0 && err == 0)
+ err = new_err;
+ }
+ }
+ if (err != 0) {
+ msg_warn("%s: in \"%s\": %s", source, *cpp, err);
+ }
+ }
+ argv_free(association);
+ }
+ argv_free(associations);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <string.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+ /* TODO(wietse) make this a proper VSTREAM interface */
+
+/* vstream_swap - kludge to capture output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+struct test_case {
+ const char *label; /* identifies test case */
+ const char *config; /* configuration under test */
+ const char *exp_warning; /* expected warning or null */
+ const char *exp_export; /* expected export or null */
+};
+
+static struct test_case test_cases[] = {
+ {"good",
+ /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24",
+ /* warning */ "",
+ /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
+ },
+ {"equal-equal",
+ /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24",
+ /* warning */ "config_known_tcp_ports: warning: equal-equal: "
+ "in \" smtps == submissions = 465\": missing service name before "
+ "\"=\"\n",
+ /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
+ },
+ {"port test 1",
+ /* config */ "smtps = submission =",
+ /* warning */ "config_known_tcp_ports: warning: port test 1: "
+ "in \"smtps = submission =\": missing port value after \"=\"\n",
+ /* export */ ""
+ },
+ {"port test 2",
+ /* config */ "smtps = submission = 4 65",
+ /* warning */ "config_known_tcp_ports: warning: port test 2: "
+ "in \"smtps = submission = 4 65\": whitespace in port number\n",
+ /* export */ ""
+ },
+ {"port test 3",
+ /* config */ "lmtp = 24, smtps = submission = foo",
+ /* warning */ "config_known_tcp_ports: warning: port test 3: "
+ "in \" smtps = submission = foo\": non-numerical service port\n",
+ /* export */ "lmtp=24"
+ },
+ {"service name test 1",
+ /* config */ "smtps = sub mission = 465",
+ /* warning */ "config_known_tcp_ports: warning: service name test 1: "
+ "in \"smtps = sub mission = 465\": whitespace in service name\n",
+ /* export */ "smtps=465"
+ },
+ {"service name test 2",
+ /* config */ "lmtp = 24, smtps = 1234 = submissions = 465",
+ /* warning */ "config_known_tcp_ports: warning: service name test 2: "
+ "in \" smtps = 1234 = submissions = 465\": numerical service name\n",
+ /* export */ "lmtp=24 smtps=465 submissions=465"
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ VSTRING *export_buf;
+ struct test_case *tp;
+ int pass = 0;
+ int fail = 0;
+ int test_failed;
+ const char *export;
+ VSTRING *msg_buf;
+ VSTREAM *memory_stream;
+
+#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
+
+ msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR);
+
+ export_buf = vstring_alloc(100);
+ msg_buf = vstring_alloc(100);
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_failed = 0;
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ config_known_tcp_ports(tp->label, tp->config);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ if (vstream_fclose(memory_stream))
+ msg_fatal("close memory stream: %m");
+ if (strcmp(STR(msg_buf), tp->exp_warning) != 0) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ tp->label, STR(msg_buf),
+ STRING_OR_NULL(tp->exp_warning));
+ test_failed = 1;
+ } else {
+ export = export_known_tcp_ports(export_buf);
+ if (strcmp(export, tp->exp_export) != 0) {
+ msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
+ tp->label, export, tp->exp_export);
+ test_failed = 1;
+ }
+ clear_known_tcp_ports();
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ }
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(msg_buf);
+ vstring_free(export_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/global/config_known_tcp_ports.h b/src/global/config_known_tcp_ports.h
new file mode 100644
index 0000000..6b13e9a
--- /dev/null
+++ b/src/global/config_known_tcp_ports.h
@@ -0,0 +1,30 @@
+#ifndef _CONFIG_KNOWN_TCP_PORTS_H_INCLUDED_
+#define _CONFIG_KNOWN_TCP_PORTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* config_known_tcp_ports 3h
+/* SUMMARY
+/* parse and store known TCP port configuration
+/* SYNOPSIS
+/* #include <config_known_tcp_ports.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void config_known_tcp_ports(const char *source, const char *settings);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/config_known_tcp_ports.ref b/src/global/config_known_tcp_ports.ref
new file mode 100644
index 0000000..5a35677
--- /dev/null
+++ b/src/global/config_known_tcp_ports.ref
@@ -0,0 +1,8 @@
+config_known_tcp_ports: good: PASS
+config_known_tcp_ports: equal-equal: PASS
+config_known_tcp_ports: port test 1: PASS
+config_known_tcp_ports: port test 2: PASS
+config_known_tcp_ports: port test 3: PASS
+config_known_tcp_ports: service name test 1: PASS
+config_known_tcp_ports: service name test 2: PASS
+config_known_tcp_ports: PASS=7 FAIL=0
diff --git a/src/global/conv_time.c b/src/global/conv_time.c
new file mode 100644
index 0000000..78a40b5
--- /dev/null
+++ b/src/global/conv_time.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* conv_time 3
+/* SUMMARY
+/* time value conversion
+/* SYNOPSIS
+/* #include <conv_time.h>
+/*
+/* int conv_time(strval, timval, def_unit);
+/* const char *strval;
+/* int *timval;
+/* int def_unit;
+/* DESCRIPTION
+/* conv_time() converts a numerical time value with optional
+/* one-letter suffix that specifies an explicit time unit: s
+/* (seconds), m (minutes), h (hours), d (days) or w (weeks).
+/* Internally, time is represented in seconds.
+/*
+/* Arguments:
+/* .IP strval
+/* Input value.
+/* .IP timval
+/* Result pointer.
+/* .IP def_unit
+/* The default time unit suffix character.
+/* DIAGNOSTICS
+/* The result value is non-zero in case of success, zero in
+/* case of a bad time value or a bad time unit suffix.
+/* 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 <limits.h> /* INT_MAX */
+#include <stdlib.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <conv_time.h>
+
+#define MINUTE (60)
+#define HOUR (60 * MINUTE)
+#define DAY (24 * HOUR)
+#define WEEK (7 * DAY)
+
+/* conv_time - convert time value */
+
+int conv_time(const char *strval, int *timval, int def_unit)
+{
+ char *end;
+ int intval;
+ long longval;
+
+ errno = 0;
+ intval = longval = strtol(strval, &end, 10);
+ if (*strval == 0 || errno == ERANGE || longval != intval || intval < 0
+ /* || (*end != 0 && end[1] != 0) */)
+ return (0);
+
+ switch (*end ? *end : def_unit) {
+ case 'w':
+ if (intval < INT_MAX / WEEK) {
+ *timval = intval * WEEK;
+ return (1);
+ } else {
+ return (0);
+ }
+ case 'd':
+ if (intval < INT_MAX / DAY) {
+ *timval = intval * DAY;
+ return (1);
+ } else {
+ return (0);
+ }
+ case 'h':
+ if (intval < INT_MAX / HOUR) {
+ *timval = intval * HOUR;
+ return (1);
+ } else {
+ return (0);
+ }
+ case 'm':
+ if (intval < INT_MAX / MINUTE) {
+ *timval = intval * MINUTE;
+ return (1);
+ } else {
+ return (0);
+ }
+ case 's':
+ *timval = intval;
+ return (1);
+ }
+ return (0);
+}
diff --git a/src/global/conv_time.h b/src/global/conv_time.h
new file mode 100644
index 0000000..565ce3c
--- /dev/null
+++ b/src/global/conv_time.h
@@ -0,0 +1,30 @@
+#ifndef _CONV_TIME_INCLUDED_
+#define _CONV_TIME_INCLUDED_
+
+/*++
+/* NAME
+/* conv_time 3h
+/* SUMMARY
+/* time value conversion
+/* SYNOPSIS
+/* #include <conv_time.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int conv_time(const char *, int *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/data_redirect.c b/src/global/data_redirect.c
new file mode 100644
index 0000000..1a8fe0f
--- /dev/null
+++ b/src/global/data_redirect.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* data_redirect 3
+/* SUMMARY
+/* redirect legacy writes to Postfix-owned data directory
+/* SYNOPSIS
+/* #include <data_redirect.h>
+/*
+/* char *data_redirect_file(result, path)
+/* VSTRING *result;
+/* const char *path;
+/*
+/* char *data_redirect_map(result, map)
+/* VSTRING *result;
+/* const char *map;
+/* DESCRIPTION
+/* With Postfix version 2.5 and later, the tlsmgr(8) and
+/* verify(8) servers no longer open cache files with root
+/* privilege. This avoids a potential security loophole where
+/* the ownership of a file (or directory) does not match the
+/* trust level of the content of that file (or directory).
+/*
+/* This module implements a migration aid that allows a
+/* transition without disruption of service.
+/*
+/* data_redirect_file() detects a request to open a file in a
+/* non-Postfix directory, logs a warning, and redirects the
+/* request to the Postfix-owned data_directory.
+/*
+/* data_redirect_map() performs the same function for a limited
+/* subset of file-based lookup tables.
+/*
+/* Arguments:
+/* .IP result
+/* A possibly redirected copy of the input.
+/* .IP path
+/* The pathname that may be redirected.
+/* .IP map
+/* The "mapname" or "maptype:mapname" that may be redirected.
+/* The result is always in "maptype:mapname" form.
+/* BUGS
+/* Only a few map types are redirected. This is acceptable for
+/* a temporary migration tool.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure.
+/* CONFIGURATION PARAMETERS
+/* data_directory, location of Postfix-writable files
+/* 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>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <dict_db.h>
+#include <dict_dbm.h>
+#include <dict_cdb.h>
+#include <dict_lmdb.h>
+#include <warn_stat.h>
+
+/* Global directory. */
+
+#include <mail_params.h>
+#include <dict_proxy.h>
+#include <data_redirect.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * Redirect only these map types, so that we don't try stupid things with
+ * NIS, *SQL or LDAP. This is a transition feature for legacy TLS and verify
+ * configurations, so it does not have to cover every possible map type.
+ *
+ * XXX In this same spirit of imperfection we also use hard-coded map names,
+ * because maintainers may add map types that the official release doesn't
+ * even know about, because map types may be added dynamically on some
+ * platforms.
+ */
+static const NAME_CODE data_redirect_map_types[] = {
+ DICT_TYPE_HASH, 1,
+ DICT_TYPE_BTREE, 1,
+ DICT_TYPE_DBM, 1,
+ DICT_TYPE_LMDB, 1,
+ DICT_TYPE_CDB, 1, /* not a read-write map type */
+ "sdbm", 1, /* legacy 3rd-party TLS */
+ "dbz", 1, /* just in case */
+ 0, 0,
+};
+
+/* data_redirect_path - redirect path to Postfix-owned directory */
+
+static char *data_redirect_path(VSTRING *result, const char *path,
+ const char *log_type, const char *log_name)
+{
+ struct stat st;
+
+#define PATH_DELIMITER "/"
+
+ (void) sane_dirname(result, path);
+ if (stat(STR(result), &st) != 0 || st.st_uid == var_owner_uid) {
+ vstring_strcpy(result, path);
+ } else {
+ msg_warn("request to update %s %s in non-%s directory %s",
+ log_type, log_name, var_mail_owner, STR(result));
+ msg_warn("redirecting the request to %s-owned %s %s",
+ var_mail_owner, VAR_DATA_DIR, var_data_dir);
+ (void) sane_basename(result, path);
+ vstring_prepend(result, PATH_DELIMITER, sizeof(PATH_DELIMITER) - 1);
+ vstring_prepend(result, var_data_dir, strlen(var_data_dir));
+ }
+ return (STR(result));
+}
+
+/* data_redirect_file - redirect file to Postfix-owned directory */
+
+char *data_redirect_file(VSTRING *result, const char *path)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (path == STR(result))
+ msg_panic("data_redirect_file: result clobbers input");
+
+ return (data_redirect_path(result, path, "file", path));
+}
+
+char *data_redirect_map(VSTRING *result, const char *map)
+{
+ const char *path;
+ const char *map_type;
+ size_t map_type_len;
+
+#define MAP_DELIMITER ":"
+
+ /*
+ * Sanity check.
+ */
+ if (map == STR(result))
+ msg_panic("data_redirect_map: result clobbers input");
+
+ /*
+ * Parse the input into map type and map name.
+ */
+ path = strchr(map, MAP_DELIMITER[0]);
+ if (path != 0) {
+ map_type = map;
+ map_type_len = path - map;
+ path += 1;
+ } else {
+ map_type = var_db_type;
+ map_type_len = strlen(map_type);
+ path = map;
+ }
+
+ /*
+ * Redirect the pathname.
+ */
+ vstring_strncpy(result, map_type, map_type_len);
+ if (name_code(data_redirect_map_types, NAME_CODE_FLAG_NONE, STR(result))) {
+ data_redirect_path(result, path, "table", map);
+ } else {
+ vstring_strcpy(result, path);
+ }
+
+ /*
+ * (Re)combine the map type with the map name.
+ */
+ vstring_prepend(result, MAP_DELIMITER, sizeof(MAP_DELIMITER) - 1);
+ vstring_prepend(result, map_type, map_type_len);
+ return (STR(result));
+}
+
+ /*
+ * Proof-of-concept test program. This can't be run as automated regression
+ * test, because the result depends on main.cf information (mail_owner UID
+ * and data_directory pathname) and on local file system details.
+ */
+#ifdef TEST
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <vstring_vstream.h>
+#include <mail_conf.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *inbuf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *bufp;
+ char *cmd;
+ char *target;
+ char *junk;
+
+ mail_conf_read();
+
+ while (vstring_get_nonl(inbuf, VSTREAM_IN) != VSTREAM_EOF) {
+ bufp = STR(inbuf);
+ if (!isatty(0)) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((cmd = mystrtok(&bufp, " \t")) == 0) {
+ vstream_printf("usage: file path|map maptype:mapname\n");
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ target = mystrtokq(&bufp, " \t");
+ junk = mystrtok(&bufp, " \t");
+ if (strcmp(cmd, "file") == 0 && target && !junk) {
+ data_redirect_file(result, target);
+ vstream_printf("%s -> %s\n", target, STR(result));
+ } else if (strcmp(cmd, "map") == 0 && target && !junk) {
+ data_redirect_map(result, target);
+ vstream_printf("%s -> %s\n", target, STR(result));
+ } else {
+ vstream_printf("usage: file path|map maptype:mapname\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/data_redirect.h b/src/global/data_redirect.h
new file mode 100644
index 0000000..28b431e
--- /dev/null
+++ b/src/global/data_redirect.h
@@ -0,0 +1,31 @@
+#ifndef _DATA_REDIRECT_H_INCLUDED_
+#define _DATA_REDIRECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* data_redirect 3h
+/* SUMMARY
+/* redirect writes from legacy pathname to Postfix-owned data directory
+/* SYNOPSIS
+/* #include "data_redirect.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+char *data_redirect_file(VSTRING *, const char *);
+char *data_redirect_map(VSTRING *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/db_common.c b/src/global/db_common.c
new file mode 100644
index 0000000..15e7a1c
--- /dev/null
+++ b/src/global/db_common.c
@@ -0,0 +1,575 @@
+/*++
+/* NAME
+/* db_common 3
+/* SUMMARY
+/* utilities common to network based dictionaries
+/* SYNOPSIS
+/* #include "db_common.h"
+/*
+/* int db_common_parse(dict, ctx, format, query)
+/* DICT *dict;
+/* void **ctx;
+/* const char *format;
+/* int query;
+/*
+/* void db_common_free_context(ctx)
+/* void *ctx;
+/*
+/* int db_common_expand(ctx, format, value, key, buf, quote_func);
+/* void *ctx;
+/* const char *format;
+/* const char *value;
+/* const char *key;
+/* VSTRING *buf;
+/* void (*quote_func)(DICT *, const char *, VSTRING *);
+/*
+/* int db_common_check_domain(domain_list, addr);
+/* STRING_LIST *domain_list;
+/* const char *addr;
+/*
+/* void db_common_sql_build_query(query,parser);
+/* VSTRING *query;
+/* CFG_PARSER *parser;
+/*
+/* DESCRIPTION
+/* This module implements utilities common to network based dictionaries.
+/*
+/* \fIdb_common_parse\fR parses query and result substitution templates.
+/* It must be called for each template before any calls to
+/* \fIdb_common_expand\fR. The \fIctx\fR argument must be initialized to
+/* a reference to a (void *)0 before the first template is parsed, this
+/* causes memory for the context to be allocated and the new pointer is
+/* stored in *ctx. When the dictionary is closed, this memory must be
+/* freed with a final call to \fBdb_common_free_context\fR.
+/*
+/* Calls for additional templates associated with the same map must use the
+/* same ctx argument. The context accumulates run-time lookup key and result
+/* validation information (inapplicable keys or results are skipped) and is
+/* needed later in each call of \fIdb_common_expand\fR. A non-zero return
+/* value indicates that data-dependent '%' expansions were found in the input
+/* template.
+/*
+/* db_common_alloc() provides a way to use db_common_parse_domain()
+/* etc. without prior db_common_parse() call.
+/*
+/* \fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
+/* When the input data lacks all fields needed for the expansion, zero
+/* is returned and the query or result should be skipped. Otherwise
+/* the expansion is appended to the result buffer (after a comma if the
+/* result buffer is not empty).
+/*
+/* If not NULL, the \fBquote_func\fR callback performs database-specific
+/* quoting of each variable before expansion.
+/* \fBvalue\fR is the lookup key for query expansion and result for result
+/* expansion. \fBkey\fR is NULL for query expansion and the lookup key for
+/* result expansion.
+/* .PP
+/* The following '%' expansions are performed on \fBvalue\fR:
+/* .IP %%
+/* A literal percent character.
+/* .IP %s
+/* The entire lookup key \fIaddr\fR.
+/* .IP %u
+/* If \fBaddr\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIaddr\fR.
+/* .IP %d
+/* If \fIaddr\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/*
+/* The following '%' expansions are performed on the lookup \fBkey\fR:
+/* .IP %S
+/* The entire lookup key \fIkey\fR.
+/* .IP %U
+/* If \fBkey\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIkey\fR.
+/* .IP %D
+/* If \fIkey\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/* .PP
+/* \fIdb_common_check_domain\fR() checks the domain list so
+/* that query optimization can be performed. The result is >0
+/* (match found), 0 (no match), or <0 (dictionary error code).
+/*
+/* .PP
+/* \fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
+/* query from the 'table', 'select_field', 'where_field' and
+/* 'additional_conditions' parameters, checking for errors.
+/*
+/* DIAGNOSTICS
+/* Fatal errors: invalid substitution format, invalid string_list pattern,
+/* insufficient parameters.
+/* SEE ALSO
+/* dict(3) dictionary manager
+/* string_list(3) string list pattern matching
+/* match_ops(3) simple string or host pattern matching
+/* 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
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* Jose Luis Tallon
+/* G4 J.E. - F.I. - U.P.M.
+/* Campus de Montegancedo, S/N
+/* E-28660 Madrid, SPAIN
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+ /*
+ * System library.
+ */
+#include "sys_defs.h"
+#include <stddef.h>
+#include <string.h>
+
+ /*
+ * Global library.
+ */
+#include "cfg_parser.h"
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+#include <dict.h>
+
+ /*
+ * Application specific
+ */
+#include "db_common.h"
+
+#define DB_COMMON_KEY_DOMAIN (1 << 0)/* Need lookup key domain */
+#define DB_COMMON_KEY_USER (1 << 1)/* Need lookup key localpart */
+#define DB_COMMON_VALUE_DOMAIN (1 << 2)/* Need result domain */
+#define DB_COMMON_VALUE_USER (1 << 3)/* Need result localpart */
+#define DB_COMMON_KEY_PARTIAL (1 << 4)/* Key uses input substrings */
+
+typedef struct {
+ DICT *dict;
+ STRING_LIST *domain;
+ int flags;
+ int nparts;
+} DB_COMMON_CTX;
+
+/* db_common_alloc - allocate db_common context */
+
+void *db_common_alloc(DICT *dict)
+{
+ DB_COMMON_CTX *ctx;
+
+ ctx = (DB_COMMON_CTX *) mymalloc(sizeof *ctx);
+ ctx->dict = dict;
+ ctx->domain = 0;
+ ctx->flags = 0;
+ ctx->nparts = 0;
+ return ((void *) ctx);
+}
+
+/* db_common_parse - validate query or result template */
+
+int db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
+ const char *cp;
+ int dynamic = 0;
+
+ if (ctx == 0)
+ ctx = (DB_COMMON_CTX *) (*ctxPtr = db_common_alloc(dict));
+
+ for (cp = format; *cp; ++cp)
+ if (*cp == '%')
+ switch (*++cp) {
+ case '%':
+ break;
+ case 'u':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_USER;
+ dynamic = 1;
+ break;
+ case 'd':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_DOMAIN;
+ dynamic = 1;
+ break;
+ case 's':
+ case 'S':
+ dynamic = 1;
+ break;
+ case 'U':
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
+ dynamic = 1;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+
+ /*
+ * Find highest %[1-9] index in query template. Input keys
+ * will be constrained to those with at least this many
+ * domain components. This makes the db_common_expand() code
+ * safe from invalid inputs.
+ */
+ if (ctx->nparts < *cp - '0')
+ ctx->nparts = *cp - '0';
+ /* FALLTHROUGH */
+ case 'D':
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
+ dynamic = 1;
+ break;
+ default:
+ msg_fatal("db_common_parse: %s: Invalid %s template: %s",
+ ctx->dict->name, query ? "query" : "result", format);
+ }
+ return dynamic;
+}
+
+/* db_common_parse_domain - parse domain matchlist*/
+
+void db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+ char *domainlist;
+ const char *myname = "db_common_parse_domain";
+
+ domainlist = cfg_get_str(parser, "domain", "", 0, 0);
+ if (*domainlist) {
+ ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN,
+ domainlist);
+ if (ctx->domain == 0)
+
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, parser->name, domainlist);
+ }
+ myfree(domainlist);
+}
+
+/* db_common_dict_partial - Does query use partial lookup keys? */
+
+int db_common_dict_partial(void *ctxPtr)
+{
+#if 0 /* Breaks recipient_delimiter */
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+
+ return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
+#endif
+ return (0);
+}
+
+/* db_common_free_ctx - free parse context */
+
+void db_common_free_ctx(void *ctxPtr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+
+ if (ctx->domain)
+ string_list_free(ctx->domain);
+ myfree((void *) ctxPtr);
+}
+
+/* db_common_expand - expand query and result templates */
+
+int db_common_expand(void *ctxArg, const char *format, const char *value,
+ const char *key, VSTRING *result,
+ db_quote_callback_t quote_func)
+{
+ const char *myname = "db_common_expand";
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
+ const char *vdomain = 0;
+ const char *kdomain = 0;
+ const char *domain = 0;
+ int dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
+ char *vuser = 0;
+ char *kuser = 0;
+ ARGV *parts = 0;
+ int i;
+ const char *cp;
+
+ /* Skip NULL values, silently. */
+ if (value == 0)
+ return (0);
+
+ /* Don't silenty skip empty query string or empty lookup results. */
+ if (*value == 0) {
+ if (key)
+ msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
+ " -- ignored", ctx->dict->type, ctx->dict->name, key);
+ else
+ msg_warn("table \"%s:%s\": empty query string"
+ " -- ignored", ctx->dict->type, ctx->dict->name);
+ return (0);
+ }
+ if (key) {
+ /* This is a result template and the input value is the result */
+ if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
+ || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
+ return (0);
+
+ /* The result format may use the local or domain part of the key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((kdomain = strrchr(key, '@')) != 0)
+ ++kdomain;
+
+ /*
+ * The key should already be checked before the query. No harm if the
+ * query did not get optimized out, so we just issue a warning.
+ */
+ if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
+ || (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
+ msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
+ ctx->dict->name, value);
+ return (0);
+ }
+ } else {
+ /* This is a query template and the input value is the lookup key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
+ || (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
+ return (0);
+ }
+
+ if (ctx->nparts > 0) {
+ parts = argv_split(key ? kdomain : vdomain, ".");
+
+ /*
+ * Filter out input keys whose domains lack enough labels to fill-in
+ * the query template. See below and also db_common_parse() which
+ * initializes ctx->nparts.
+ */
+ if (parts->argc < ctx->nparts) {
+ argv_free(parts);
+ return (0);
+ }
+
+ /*
+ * Skip domains with leading, consecutive or trailing '.' separators
+ * among the required labels.
+ */
+ for (i = 0; i < ctx->nparts; i++)
+ if (*parts->argv[parts->argc - i - 1] == 0) {
+ argv_free(parts);
+ return (0);
+ }
+ }
+ if (VSTRING_LEN(result) > 0)
+ VSTRING_ADDCH(result, ',');
+
+#define QUOTE_VAL(d, q, v, buf) do { \
+ if (q) \
+ q(d, v, buf); \
+ else \
+ vstring_strcat(buf, v); \
+ } while (0)
+
+ /*
+ * Replace all instances of %s with the address to look up. Replace %u
+ * with the user portion, and %d with the domain portion. "%%" expands to
+ * "%". lowercase -> addr, uppercase -> key
+ */
+ for (cp = format; *cp; cp++) {
+ if (*cp == '%') {
+ switch (*++cp) {
+
+ case '%':
+ VSTRING_ADDCH(result, '%');
+ break;
+
+ case 's':
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'u':
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'd':
+ if (!(ctx->flags & dflag))
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if (!vdomain)
+ msg_panic("%s: %s: %s: expanding domain-less key or value",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
+ break;
+
+ case 'S':
+ if (key)
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'U':
+ if (key) {
+ if (kdomain) {
+ if (kuser == 0)
+ kuser = mystrndup(key, kdomain - key - 1);
+ QUOTE_VAL(ctx->dict, quote_func, kuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ } else {
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ }
+ break;
+
+ case 'D':
+ if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if ((domain = key ? kdomain : vdomain) == 0)
+ msg_panic("%s: %s: %s: expanding domain-less key or value",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func, domain, result);
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+
+ /*
+ * Interpolate %[1-9] components into the query string. By
+ * this point db_common_parse() has identified the highest
+ * component index, and (see above) keys with fewer
+ * components have been filtered out. The "parts" ARGV is
+ * guaranteed to be initialized and hold enough elements to
+ * satisfy the query template.
+ */
+ if (!(ctx->flags & DB_COMMON_KEY_DOMAIN)
+ || ctx->nparts < *cp - '0')
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if (!parts || parts->argc < ctx->nparts)
+ msg_panic("%s: %s: %s: key has too few domain labels",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func,
+ parts->argv[parts->argc - (*cp - '0')], result);
+ break;
+
+ default:
+ msg_fatal("%s: %s: invalid %s template '%s'", myname,
+ ctx->dict->name, key ? "result" : "query",
+ format);
+ }
+ } else
+ VSTRING_ADDCH(result, *cp);
+ }
+ VSTRING_TERMINATE(result);
+
+ if (vuser)
+ myfree(vuser);
+ if (kuser)
+ myfree(kuser);
+ if (parts)
+ argv_free(parts);
+
+ return (1);
+}
+
+
+/* db_common_check_domain - check domain list */
+
+int db_common_check_domain(void *ctxPtr, const char *addr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+ char *domain;
+
+ if (ctx->domain) {
+ if ((domain = strrchr(addr, '@')) != NULL)
+ ++domain;
+ if (domain == NULL || domain == addr + 1)
+ return (0);
+ if (match_list_match(ctx->domain, domain) == 0)
+ return (ctx->domain->error);
+ }
+ return (1);
+}
+
+/* db_common_sql_build_query -- build query for SQL maptypes */
+
+void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
+{
+ const char *myname = "db_common_sql_build_query";
+ char *table;
+ char *select_field;
+ char *where_field;
+ char *additional_conditions;
+
+ /*
+ * Build "old style" query: "select %s from %s where %s"
+ */
+ if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'table' parameter not defined", myname);
+
+ if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'select_field' parameter not defined", myname);
+
+ if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'where_field' parameter not defined", myname);
+
+ additional_conditions = cfg_get_str(parser, "additional_conditions",
+ "", 0, 0);
+
+ vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
+ select_field, table, where_field,
+ additional_conditions);
+
+ myfree(table);
+ myfree(select_field);
+ myfree(where_field);
+ myfree(additional_conditions);
+}
diff --git a/src/global/db_common.h b/src/global/db_common.h
new file mode 100644
index 0000000..26ebf97
--- /dev/null
+++ b/src/global/db_common.h
@@ -0,0 +1,58 @@
+#ifndef _DB_COMMON_H_INCLUDED_
+#define _DB_COMMON_H_INCLUDED_
+
+/*++
+/* NAME
+/* db_common 3h
+/* SUMMARY
+/* utilities common to network based dictionaries
+/* SYNOPSIS
+/* #include "db_common.h"
+/* DESCRIPTION
+/* .nf
+ */
+
+ /*
+ * External interface.
+ */
+#include "dict.h"
+#include "string_list.h"
+
+typedef void (*db_quote_callback_t)(DICT *, const char *, VSTRING *);
+
+extern int db_common_parse(DICT *, void **, const char *, int);
+extern void *db_common_alloc(DICT *);
+extern void db_common_parse_domain(CFG_PARSER *, void *);
+extern int db_common_dict_partial(void *);
+extern int db_common_expand(void *, const char *, const char *,
+ const char *, VSTRING *, db_quote_callback_t);
+extern int db_common_check_domain(void *, const char *);
+extern void db_common_free_ctx(void *);
+extern void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser);
+
+/* 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
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* Jose Luis Tallon
+/* G4 J.E. - F.I. - U.P.M.
+/* Campus de Montegancedo, S/N
+/* E-28660 Madrid, SPAIN
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+#endif
+
diff --git a/src/global/debug_peer.c b/src/global/debug_peer.c
new file mode 100644
index 0000000..ae57a8f
--- /dev/null
+++ b/src/global/debug_peer.c
@@ -0,0 +1,133 @@
+/*++
+/* NAME
+/* debug_peer 3
+/* SUMMARY
+/* increase verbose logging for specific peers
+/* SYNOPSIS
+/* #include <debug_peer.h>
+/*
+/* void debug_peer_init(void)
+/*
+/* int debug_peer_check(peer_name, peer_addr)
+/* const char *peer_name;
+/* const char *peer_addr;
+/*
+/* void debug_peer_restore()
+/* DESCRIPTION
+/* This module implements increased verbose logging for specific
+/* network peers.
+/*
+/* The \fIdebug_peer_list\fR configuration parameter
+/* specifies what peers receive this special treatment; see
+/* namadr_list(3) for a description of the matching process.
+/*
+/* The \fIdebug_peer_level\fR configuration parameter specifies
+/* by what amount the verbose logging level should increase when
+/* a peer is listed in \fIdebug_peer_list\fR.
+/*
+/* debug_peer_init() performs initializations that must be
+/* performed once at the start of the program.
+/*
+/* debug_peer_check() increases the verbose logging level when the
+/* client name or address matches the debug_peer_list pattern.
+/* The result is non-zero when the noise leven was increased.
+/*
+/* debug_peer_restore() restores the verbose logging level.
+/* This routine has no effect when debug_peer_check() had no
+/* effect; this routine can safely be called multiple times.
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/* Fatal errors: unable to access a peer_list file; invalid
+/* peer_list pattern; invalid verbosity level increment.
+/* SEE ALSO
+/* msg(3) the msg_verbose variable
+/* namadr_list(3) match host by name or by address
+/* CONFIG PARAMETERS
+/* debug_peer_list, patterns as described in namadr_list(3)
+/* debug_peer_level, verbose logging level
+/* 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>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <namadr_list.h>
+#include <debug_peer.h>
+#include <match_parent_style.h>
+
+/* Application-specific. */
+
+#define UNUSED_SAVED_LEVEL (-1)
+
+static NAMADR_LIST *debug_peer_list;
+static int saved_level = UNUSED_SAVED_LEVEL;
+
+/* debug_peer_init - initialize */
+
+void debug_peer_init(void)
+{
+ const char *myname = "debug_peer_init";
+
+ /*
+ * Sanity check.
+ */
+ if (debug_peer_list)
+ msg_panic("%s: repeated call", myname);
+ if (var_debug_peer_list == 0)
+ msg_panic("%s: uninitialized %s", myname, VAR_DEBUG_PEER_LIST);
+ if (var_debug_peer_level <= 0)
+ msg_fatal("%s: %s <= 0", myname, VAR_DEBUG_PEER_LEVEL);
+
+ /*
+ * Finally.
+ */
+ if (*var_debug_peer_list)
+ debug_peer_list =
+ namadr_list_init(VAR_DEBUG_PEER_LIST, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_DEBUG_PEER_LIST),
+ var_debug_peer_list);
+}
+
+/* debug_peer_check - see if this peer needs verbose logging */
+
+int debug_peer_check(const char *name, const char *addr)
+{
+
+ /*
+ * Crank up the noise when this peer is listed.
+ */
+ if (debug_peer_list != 0
+ && saved_level == UNUSED_SAVED_LEVEL
+ && namadr_list_match(debug_peer_list, name, addr) != 0) {
+ saved_level = msg_verbose;
+ msg_verbose += var_debug_peer_level;
+ return (1);
+ }
+ return (0);
+}
+
+/* debug_peer_restore - restore logging level */
+
+void debug_peer_restore(void)
+{
+ if (saved_level != UNUSED_SAVED_LEVEL) {
+ msg_verbose = saved_level;
+ saved_level = UNUSED_SAVED_LEVEL;
+ }
+}
diff --git a/src/global/debug_peer.h b/src/global/debug_peer.h
new file mode 100644
index 0000000..925e503
--- /dev/null
+++ b/src/global/debug_peer.h
@@ -0,0 +1,31 @@
+#ifndef _DEBUG_PEER_H_INCLUDED_
+#define _DEBUG_PEER_H_INCLUDED_
+/*++
+/* NAME
+/* debug_peer 3h
+/* SUMMARY
+/* increase verbose logging for specific peers
+/* SYNOPSIS
+/* #include <debug_peer.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void debug_peer_init(void);
+extern int debug_peer_check(const char *, const char *);
+extern void debug_peer_restore(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/debug_process.c b/src/global/debug_process.c
new file mode 100644
index 0000000..504cbd1
--- /dev/null
+++ b/src/global/debug_process.c
@@ -0,0 +1,62 @@
+/*++
+/* NAME
+/* debug_process 3
+/* SUMMARY
+/* run an external debugger
+/* SYNOPSIS
+/* #include <debug_process.h>
+/*
+/* char *debug_process()
+/* DESCRIPTION
+/* debug_process() runs a debugger, as specified in the
+/* \fIdebugger_command\fR configuration variable.
+/*
+/* Examples of non-interactive debuggers are call tracing tools
+/* such as: trace, strace or truss.
+/*
+/* Examples of interactive debuggers are xxgdb, xxdbx, and so on.
+/* In order to use an X-based debugger, the process must have a
+/* properly set up XAUTHORITY environment variable.
+/* 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>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_conf.h"
+#include "debug_process.h"
+
+/* debug_process - run a debugger on this process */
+
+void debug_process(void)
+{
+ const char *command;
+
+ /*
+ * Expand $debugger_command then run it.
+ */
+ command = mail_conf_lookup_eval(VAR_DEBUG_COMMAND);
+ if (command == 0 || *command == 0)
+ msg_fatal("no %s variable set up", VAR_DEBUG_COMMAND);
+ msg_info("running: %s", command);
+ system(command);
+}
diff --git a/src/global/debug_process.h b/src/global/debug_process.h
new file mode 100644
index 0000000..04272ab
--- /dev/null
+++ b/src/global/debug_process.h
@@ -0,0 +1,30 @@
+#ifndef _DEBUG_PROCESS_H_INCLUDED_
+#define _DEBUG_PROCESS_H_INCLUDED_
+
+/*++
+/* NAME
+/* debug_process 3h
+/* SUMMARY
+/* run an external debugger
+/* SYNOPSIS
+/* #include <unistd.h>
+/* #include <debug_process.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void debug_process(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/defer.c b/src/global/defer.c
new file mode 100644
index 0000000..8eaf082
--- /dev/null
+++ b/src/global/defer.c
@@ -0,0 +1,383 @@
+/*++
+/* NAME
+/* defer 3
+/* SUMMARY
+/* defer service client interface
+/* SYNOPSIS
+/* #include <defer.h>
+/*
+/* int defer_append(flags, id, stats, rcpt, relay, dsn)
+/* int flags;
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/*
+/* int defer_flush(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/*
+/* int defer_warn(flags, queue, id, encoding, smtputf8, sender,
+ dsn_envid, dsn_ret)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/*
+/* int defer_one(flags, queue, id, encoding, smtputf8, sender,
+/* dsn_envid, ret, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/* INTERNAL API
+/* int defer_append_intern(flags, id, stats, rcpt, relay, dsn)
+/* int flags;
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DESCRIPTION
+/* This module implements a client interface to the defer service,
+/* which maintains a per-message logfile with status records for
+/* each recipient whose delivery is deferred, and the dsn_text why.
+/*
+/* defer_append() appends a record to the per-message defer log,
+/* with the dsn_text for delayed delivery to the named rcpt,
+/* updates the address verification service, or updates a message
+/* delivery record on request by the sender. The flags argument
+/* determines the action.
+/* The result is a convenient non-zero value.
+/* When the fast flush cache is enabled, the fast flush server is
+/* notified of deferred mail.
+/*
+/* defer_flush() bounces the specified message to the specified
+/* sender, including the defer log that was built with defer_append().
+/* defer_flush() requests that the deferred recipients are deleted
+/* from the original queue file; the defer logfile is deleted after
+/* successful completion.
+/* The result is zero in case of success, non-zero otherwise.
+/*
+/* defer_warn() sends a warning message that the mail in
+/* question has been deferred. The defer log is not deleted,
+/* and no recipients are deleted from the original queue file.
+/*
+/* defer_one() implements dsn_filter(3) compatibility for the
+/* bounce_one() routine.
+/*
+/* defer_append_intern() is for use after the DSN filter.
+/*
+/* Arguments:
+/* .IP flags
+/* The bit-wise OR of zero or more of the following (specify
+/* BOUNCE_FLAG_NONE to explicitly request not special processing):
+/* .RS
+/* .IP BOUNCE_FLAG_CLEAN
+/* Delete the defer log in case of an error (as in: pretend
+/* that we never even tried to defer this message).
+/* .IP BOUNCE_FLAG_DELRCPT
+/* When specified with a flush request, request that
+/* recipients be deleted from the queue file.
+/*
+/* Note: the bounce daemon ignores this request when the
+/* recipient queue file offset is <= 0.
+/* .IP DEL_REQ_FLAG_MTA_VRFY
+/* The message is an MTA-requested address verification probe.
+/* Update the address verification database instead of deferring
+/* mail.
+/* .IP DEL_REQ_FLAG_USR_VRFY
+/* The message is a user-requested address expansion probe.
+/* Update the message delivery record instead of deferring
+/* mail.
+/* .IP DEL_REQ_FLAG_RECORD
+/* This is a normal message with logged delivery. Update the
+/* message delivery record and defer mail delivery.
+/* .RE
+/* .IP queue
+/* The message queue name of the original message file.
+/* .IP id
+/* The queue id of the original message file.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP rcpt
+/* Recipient information. See recipient_list(3).
+/* .IP relay
+/* Host we could not talk to.
+/* .IP dsn
+/* Delivery status. See dsn(3). The specified action is ignored.
+/* .IP encoding
+/* The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
+/* .IP smtputf8
+/* The level of SMTPUTF8 support (to be defined).
+/* .IP sender
+/* The sender envelope address.
+/* .IP dsn_envid
+/* Optional DSN envelope ID.
+/* .IP dsn_ret
+/* Optional DSN return full/headers option.
+/* .PP
+/* For convenience, these functions always return a non-zero result.
+/* DIAGNOSTICS
+/* Warnings: problems connecting to the defer service.
+/* Fatal: out of memory.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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>
+
+/* Global library. */
+
+#define DSN_INTERN
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <flush_clnt.h>
+#include <verify.h>
+#include <dsn_util.h>
+#include <rcpt_print.h>
+#include <dsn_print.h>
+#include <log_adhoc.h>
+#include <trace.h>
+#include <defer.h>
+
+#define STR(x) vstring_str(x)
+
+/* defer_append - defer message delivery */
+
+int defer_append(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ DSN *dsn_res;
+
+ /*
+ * Sanity check.
+ */
+ if (my_dsn.status[0] != '4' || !dsn_valid(my_dsn.status)) {
+ msg_warn("defer_append: ignoring dsn code \"%s\"", my_dsn.status);
+ my_dsn.status = "4.0.0";
+ }
+
+ /*
+ * DSN filter (Postfix 3.0).
+ */
+ if (delivery_status_filter != 0
+ && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) {
+ if (dsn_res->status[0] == '5')
+ return (bounce_append_intern(flags, id, stats, rcpt, relay, dsn_res));
+ my_dsn = *dsn_res;
+ }
+ return (defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn));
+}
+
+/* defer_append_intern - defer message delivery */
+
+int defer_append_intern(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ const char *rcpt_domain;
+ DSN my_dsn = *dsn;
+ int status;
+
+ /*
+ * MTA-requested address verification information is stored in the verify
+ * service database.
+ */
+ if (flags & DEL_REQ_FLAG_MTA_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = verify_append(id, stats, rcpt, relay, &my_dsn,
+ DEL_RCPT_STAT_DEFER);
+ return (status);
+ }
+
+ /*
+ * User-requested address verification information is logged and mailed
+ * to the requesting user.
+ */
+ if (flags & DEL_REQ_FLAG_USR_VRFY) {
+ my_dsn.action = "undeliverable";
+ status = trace_append(flags, id, stats, rcpt, relay, &my_dsn);
+ return (status);
+ }
+
+ /*
+ * Normal mail delivery. May also send a delivery record to the user.
+ *
+ * XXX DSN We write all deferred recipients to the defer logfile regardless
+ * of DSN NOTIFY options, because those options don't apply to mailq(1)
+ * reports or to postmaster notifications.
+ */
+ else {
+
+ /*
+ * Supply default action.
+ */
+ my_dsn.action = "delayed";
+
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_defer_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt),
+ SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn),
+ ATTR_TYPE_END) != 0)
+ msg_warn("%s: %s service failure", id, var_defer_service);
+ log_adhoc(id, stats, rcpt, relay, &my_dsn, "deferred");
+
+ /*
+ * Traced delivery.
+ */
+ if (flags & DEL_REQ_FLAG_RECORD)
+ if (trace_append(flags, id, stats, rcpt, relay, &my_dsn) != 0)
+ msg_warn("%s: %s service failure", id, var_trace_service);
+
+ /*
+ * Notify the fast flush service. XXX Should not this belong in the
+ * bounce/defer daemon? Well, doing it here is more robust.
+ */
+ if ((rcpt_domain = strrchr(rcpt->address, '@')) != 0
+ && *++rcpt_domain != 0)
+ switch (flush_add(rcpt_domain, id)) {
+ case FLUSH_STAT_OK:
+ case FLUSH_STAT_DENY:
+ break;
+ default:
+ msg_warn("%s: %s service failure", id, var_flush_service);
+ break;
+ }
+ return (-1);
+ }
+}
+
+/* defer_flush - flush the defer log and deliver to the sender */
+
+int defer_flush(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret)
+{
+ flags |= BOUNCE_FLAG_DELRCPT;
+
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_defer_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ ATTR_TYPE_END) == 0) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+/* defer_warn - send a copy of the defer log to the sender as a warning bounce
+ * do not flush the log */
+
+int defer_warn(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *envid, int dsn_ret)
+{
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_defer_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_WARN),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ ATTR_TYPE_END) == 0) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+/* defer_one - defer mail for one recipient */
+
+int defer_one(int flags, const char *queue, const char *id,
+ const char *encoding, int smtputf8,
+ const char *sender, const char *dsn_envid,
+ int dsn_ret, MSG_STATS *stats, RECIPIENT *rcpt,
+ const char *relay, DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ DSN *dsn_res;
+
+ /*
+ * Sanity check.
+ */
+ if (my_dsn.status[0] != '4' || !dsn_valid(my_dsn.status)) {
+ msg_warn("defer_one: ignoring dsn code \"%s\"", my_dsn.status);
+ my_dsn.status = "4.0.0";
+ }
+
+ /*
+ * DSN filter (Postfix 3.0).
+ */
+ if (delivery_status_filter != 0
+ && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) {
+ if (dsn_res->status[0] == '5')
+ return (bounce_one_intern(flags, queue, id, encoding, smtputf8,
+ sender, dsn_envid, dsn_ret, stats,
+ rcpt, relay, dsn_res));
+ my_dsn = *dsn_res;
+ }
+ return (defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn));
+}
diff --git a/src/global/defer.h b/src/global/defer.h
new file mode 100644
index 0000000..a015052
--- /dev/null
+++ b/src/global/defer.h
@@ -0,0 +1,54 @@
+#ifndef _DEFER_H_INCLUDED_
+#define _DEFER_H_INCLUDED_
+
+/*++
+/* NAME
+/* defer 3h
+/* SUMMARY
+/* defer service client interface
+/* SYNOPSIS
+/* #include <defer.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <bounce.h>
+
+ /*
+ * External interface.
+ */
+extern int defer_append(int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+extern int defer_flush(int, const char *, const char *, const char *, int,
+ const char *, const char *, int);
+extern int defer_warn(int, const char *, const char *, const char *, int,
+ const char *, const char *, int);
+extern int defer_one(int, const char *, const char *, const char *, int,
+ const char *, const char *,
+ int, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+
+ /*
+ * Start of private API.
+ */
+#ifdef DSN_INTERN
+
+extern int defer_append_intern(int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/deliver_completed.c b/src/global/deliver_completed.c
new file mode 100644
index 0000000..6fe3888
--- /dev/null
+++ b/src/global/deliver_completed.c
@@ -0,0 +1,60 @@
+/*++
+/* NAME
+/* deliver_completed 3
+/* SUMMARY
+/* recipient delivery completion
+/* SYNOPSIS
+/* #include <deliver_completed.h>
+/*
+/* void deliver_completed(stream, offset)
+/* VSTREAM *stream;
+/* long offset;
+/* DESCRIPTION
+/* deliver_completed() crosses off the specified recipient from
+/* an open queue file. A -1 offset means ignore the request -
+/* this is used for delivery requests that are passed on from
+/* one delivery agent to another.
+/* DIAGNOSTICS
+/* Fatal error: unable to update the queue file.
+/* 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 <vstream.h>
+
+/* Global library. */
+
+#include "record.h"
+#include "rec_type.h"
+#include "deliver_completed.h"
+
+/* deliver_completed - handle per-recipient delivery completion */
+
+void deliver_completed(VSTREAM *stream, long offset)
+{
+ const char *myname = "deliver_completed";
+
+ if (offset == -1)
+ return;
+
+ if (offset <= 0)
+ msg_panic("%s: bad offset %ld", myname, offset);
+
+ if (rec_put_type(stream, REC_TYPE_DONE, offset) < 0
+ || vstream_fflush(stream))
+ msg_fatal("update queue file %s: %m", VSTREAM_PATH(stream));
+}
diff --git a/src/global/deliver_completed.h b/src/global/deliver_completed.h
new file mode 100644
index 0000000..cb0b1df
--- /dev/null
+++ b/src/global/deliver_completed.h
@@ -0,0 +1,35 @@
+#ifndef _DELIVER_COMPLETED_H_INCLUDED_
+#define _DELIVER_COMPLETED_H_INCLUDED_
+
+/*++
+/* NAME
+/* deliver_completed 3h
+/* SUMMARY
+/* recipient delivery completion
+/* SYNOPSIS
+/* #include <deliver_completed.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern void deliver_completed(VSTREAM *, long);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/deliver_flock.c b/src/global/deliver_flock.c
new file mode 100644
index 0000000..35a6bcb
--- /dev/null
+++ b/src/global/deliver_flock.c
@@ -0,0 +1,82 @@
+/*++
+/* NAME
+/* deliver_flock 3
+/* SUMMARY
+/* lock open file for mail delivery
+/* SYNOPSIS
+/* #include <deliver_flock.h>
+/*
+/* int deliver_flock(fd, lock_style, why)
+/* int fd;
+/* int lock_style;
+/* VSTRING *why;
+/* DESCRIPTION
+/* deliver_flock() sets one exclusive kernel lock on an open file,
+/* for example in order to deliver mail.
+/* It performs several non-blocking attempts to acquire an exclusive
+/* lock before giving up.
+/*
+/* Arguments:
+/* .IP fd
+/* A file descriptor that is associated with an open file.
+/* .IP lock_style
+/* A locking style defined in myflock(3).
+/* .IP why
+/* A null pointer, or storage for diagnostics.
+/* DIAGNOSTICS
+/* deliver_flock() returns -1 in case of problems, 0 in case
+/* of success. The reason for failure is returned via the \fIwhy\fR
+/* parameter.
+/* CONFIGURATION PARAMETERS
+/* deliver_lock_attempts, number of locking attempts
+/* deliver_lock_delay, time in seconds between attempts
+/* sun_mailtool_compatibility, disable kernel locking
+/* 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>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <myflock.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "deliver_flock.h"
+
+/* Application-specific. */
+
+#define MILLION 1000000
+
+/* deliver_flock - lock open file for mail delivery */
+
+int deliver_flock(int fd, int lock_style, VSTRING *why)
+{
+ int i;
+
+ for (i = 1; /* void */ ; i++) {
+ if (myflock(fd, lock_style,
+ MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) == 0)
+ return (0);
+ if (i >= var_flock_tries)
+ break;
+ rand_sleep(var_flock_delay * MILLION, var_flock_delay * MILLION / 2);
+ }
+ if (why)
+ vstring_sprintf(why, "unable to lock for exclusive access: %m");
+ return (-1);
+}
diff --git a/src/global/deliver_flock.h b/src/global/deliver_flock.h
new file mode 100644
index 0000000..f167725
--- /dev/null
+++ b/src/global/deliver_flock.h
@@ -0,0 +1,36 @@
+#ifndef _DELIVER_FLOCK_H_INCLUDED_
+#define _DELIVER_FLOCK_H_INCLUDED_
+
+/*++
+/* NAME
+/* deliver_flock 3h
+/* SUMMARY
+/* lock open file for mail delivery
+/* SYNOPSIS
+/* #include <deliver_flock.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <myflock.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern int deliver_flock(int, int, VSTRING *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/deliver_pass.c b/src/global/deliver_pass.c
new file mode 100644
index 0000000..231b070
--- /dev/null
+++ b/src/global/deliver_pass.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* deliver_pass 3
+/* SUMMARY
+/* deliver request pass_through
+/* SYNOPSIS
+/* #include <deliver_request.h>
+/*
+/* int deliver_pass(class, service, request, recipient)
+/* const char *class;
+/* const char *service;
+/* DELIVER_REQUEST *request;
+/* RECIPIENT *recipient;
+/*
+/* int deliver_pass_all(class, service, request)
+/* const char *class;
+/* const char *service;
+/* DELIVER_REQUEST *request;
+/* DESCRIPTION
+/* This module implements the client side of the `queue manager
+/* to delivery agent' protocol, passing one recipient on from
+/* one delivery agent to another.
+/*
+/* deliver_pass() delegates delivery of the named recipient.
+/*
+/* deliver_pass_all() delegates an entire delivery request.
+/*
+/* Arguments:
+/* .IP class
+/* Destination delivery agent service class
+/* .IP service
+/* String of the form \fItransport\fR:\fInexthop\fR. Either transport
+/* or nexthop are optional. For details see the transport map manual page.
+/* .IP request
+/* Delivery request with queue file information.
+/* .IP recipient
+/* Recipient information. See recipient_list(3).
+/* DIAGNOSTICS
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* BUGS
+/* One recipient at a time; this is OK for mailbox deliveries.
+/*
+/* Hop status information cannot be passed back.
+/* 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 <vstring.h>
+#include <vstream.h>
+#include <split_at.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <deliver_pass.h>
+#include <dsb_scan.h>
+#include <defer.h>
+#include <rcpt_print.h>
+#include <info_log_addr_form.h>
+
+#define DELIVER_PASS_DEFER 1
+#define DELIVER_PASS_UNKNOWN 2
+
+/* deliver_pass_initial_reply - retrieve initial delivery process response */
+
+static int deliver_pass_initial_reply(VSTREAM *stream)
+{
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (-1);
+ }
+ return (0);
+}
+
+/* deliver_pass_send_request - send delivery request to delivery process */
+
+static int deliver_pass_send_request(VSTREAM *stream, DELIVER_REQUEST *request,
+ const char *nexthop,
+ RECIPIENT *rcpt)
+{
+ int stat;
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, request->flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, request->queue_name),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, request->queue_id),
+ SEND_ATTR_LONG(MAIL_ATTR_OFFSET, request->data_offset),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE, request->data_size),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, nexthop),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, request->encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, request->smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, request->sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, request->dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, request->dsn_ret),
+ SEND_ATTR_FUNC(msg_stats_print, (const void *) &request->msg_stats),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, request->client_name),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, request->client_port),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, request->client_proto),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, request->client_helo),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, request->sasl_method),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, request->sasl_username),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, request->sasl_sender),
+ /* XXX Ditto if we want to pass TLS certificate info. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_IDENT, request->log_ident),
+ SEND_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, request->rewrite_context),
+ SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, 1),
+ ATTR_TYPE_END);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream)) {
+ msg_warn("%s: bad write: %m", VSTREAM_PATH(stream));
+ stat = -1;
+ } else {
+ stat = 0;
+ }
+ return (stat);
+}
+
+/* deliver_pass_final_reply - retrieve final delivery status response */
+
+static int deliver_pass_final_reply(VSTREAM *stream, DSN_BUF *dsb)
+{
+ int stat;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsb),
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &stat),
+ ATTR_TYPE_END) != 2) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (DELIVER_PASS_UNKNOWN);
+ } else {
+ return (stat ? DELIVER_PASS_DEFER : 0);
+ }
+}
+
+/* deliver_pass - deliver one per-site queue entry */
+
+int deliver_pass(const char *class, const char *service,
+ DELIVER_REQUEST *request,
+ RECIPIENT *rcpt)
+{
+ VSTREAM *stream;
+ DSN_BUF *dsb;
+ DSN dsn;
+ int status;
+ char *saved_service;
+ char *transport;
+ char *nexthop;
+
+ /*
+ * Parse service into transport:nexthop form, and allow for omission of
+ * optional fields
+ */
+ transport = saved_service = mystrdup(service);
+ if ((nexthop = split_at(saved_service, ':')) == 0 || *nexthop == 0)
+ nexthop = request->nexthop;
+ if (*transport == 0)
+ msg_fatal("missing transport name in \"%s\"", service);
+
+ /*
+ * Initialize.
+ */
+ msg_info("%s: passing <%s> to transport=%s",
+ request->queue_id, info_log_addr_form_recipient(rcpt->address),
+ transport);
+ stream = mail_connect_wait(class, transport);
+ dsb = dsb_create();
+
+ /*
+ * Get the delivery process initial response. Send the queue file info
+ * and recipient info to the delivery process. Retrieve the delivery
+ * agent status report. The numerical status code indicates if delivery
+ * should be tried again. The reason text is sent only when a destination
+ * should be avoided for a while, so that the queue manager can log why
+ * it does not even try to schedule delivery to the affected recipients.
+ * XXX Can't pass back hop status info because the problem is with a
+ * different transport.
+ */
+ if (deliver_pass_initial_reply(stream) != 0
+ || deliver_pass_send_request(stream, request, nexthop, rcpt) != 0) {
+ (void) DSN_SIMPLE(&dsn, "4.3.0", "mail transport unavailable");
+ status = defer_append(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats,
+ rcpt, "none", &dsn);
+ } else if ((status = deliver_pass_final_reply(stream, dsb))
+ == DELIVER_PASS_UNKNOWN) {
+ (void) DSN_SIMPLE(&dsn, "4.3.0", "unknown mail transport error");
+ status = defer_append(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats,
+ rcpt, "none", &dsn);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstream_fclose(stream);
+ dsb_free(dsb);
+ myfree(saved_service);
+
+ return (status);
+}
+
+/* deliver_pass_all - pass entire delivery request */
+
+int deliver_pass_all(const char *class, const char *service,
+ DELIVER_REQUEST *request)
+{
+ RECIPIENT_LIST *list;
+ RECIPIENT *rcpt;
+ int status = 0;
+
+ /*
+ * XXX We should find out if the target transport can handle
+ * multi-recipient requests. Unfortunately such code is hard to test,
+ * rarely used, and therefore will be buggy.
+ */
+ list = &request->rcpt_list;
+ for (rcpt = list->info; rcpt < list->info + list->len; rcpt++)
+ status |= deliver_pass(class, service, request, rcpt);
+ return (status);
+}
diff --git a/src/global/deliver_pass.h b/src/global/deliver_pass.h
new file mode 100644
index 0000000..de4f6c5
--- /dev/null
+++ b/src/global/deliver_pass.h
@@ -0,0 +1,37 @@
+#ifndef _DELIVER_PASS_H_INCLUDED_
+#define _DELIVER_PASS_H_INCLUDED_
+
+/*++
+/* NAME
+/* deliver_pass 3h
+/* SUMMARY
+/* deliver request pass_through
+/* SYNOPSIS
+/* #include <deliver_pass.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <mail_proto.h>
+
+ /*
+ * External interface.
+ */
+extern int deliver_pass(const char *, const char *, DELIVER_REQUEST *, RECIPIENT *);
+extern int deliver_pass_all(const char *, const char *, DELIVER_REQUEST *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/deliver_request.c b/src/global/deliver_request.c
new file mode 100644
index 0000000..7bc5553
--- /dev/null
+++ b/src/global/deliver_request.c
@@ -0,0 +1,484 @@
+/*++
+/* NAME
+/* deliver_request 3
+/* SUMMARY
+/* mail delivery request protocol, server side
+/* SYNOPSIS
+/* #include <deliver_request.h>
+/*
+/* typedef struct DELIVER_REQUEST {
+/* .in +5
+/* VSTREAM *fp;
+/* int flags;
+/* char *queue_name;
+/* char *queue_id;
+/* long data_offset;
+/* long data_size;
+/* char *nexthop;
+/* char *encoding;
+/* char *sender;
+/* MSG_STATS msg_stats;
+/* RECIPIENT_LIST rcpt_list;
+/* DSN *hop_status;
+/* char *client_name;
+/* char *client_addr;
+/* char *client_port;
+/* char *client_proto;
+/* char *client_helo;
+/* char *sasl_method;
+/* char *sasl_username;
+/* char *sasl_sender;
+/* char *log_ident;
+/* char *rewrite_context;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* .in -5
+/* } DELIVER_REQUEST;
+/*
+/* DELIVER_REQUEST *deliver_request_read(stream)
+/* VSTREAM *stream;
+/*
+/* void deliver_request_done(stream, request, status)
+/* VSTREAM *stream;
+/* DELIVER_REQUEST *request;
+/* int status;
+/* DESCRIPTION
+/* This module implements the delivery agent side of the `queue manager
+/* to delivery agent' protocol. In this game, the queue manager is
+/* the client, while the delivery agent is the server.
+/*
+/* deliver_request_read() reads a client message delivery request,
+/* opens the queue file, and acquires a shared lock.
+/* A null result means that the client sent bad information or that
+/* it went away unexpectedly.
+/*
+/* The \fBflags\fR structure member is the bit-wise OR of zero or more
+/* of the following:
+/* .IP \fBDEL_REQ_FLAG_SUCCESS\fR
+/* Delete successful recipients from the queue file.
+/*
+/* Note: currently, this also controls whether bounced recipients
+/* are deleted.
+/*
+/* Note: the deliver_completed() function ignores this request
+/* when the recipient queue file offset is -1.
+/* .IP \fBDEL_REQ_FLAG_BOUNCE\fR
+/* Delete bounced recipients from the queue file. Currently,
+/* this flag is non-functional.
+/* .PP
+/* The \fBDEL_REQ_FLAG_DEFLT\fR constant provides a convenient shorthand
+/* for the most common case: delete successful and bounced recipients.
+/*
+/* The \fIhop_status\fR member must be updated by the caller
+/* when all delivery to the destination in \fInexthop\fR should
+/* be deferred. This member is passed to dsn_free().
+/*
+/* deliver_request_done() reports the delivery status back to the
+/* client, including the optional \fIhop_status\fR etc. information,
+/* closes the queue file,
+/* and destroys the DELIVER_REQUEST structure. The result is
+/* non-zero when the status could not be reported to the client.
+/* DIAGNOSTICS
+/* Warnings: bad data sent by the client. Fatal errors: out of
+/* memory, queue file open errors.
+/* SEE ALSO
+/* attr_scan(3) low-level intra-mail input routines
+/* 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/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include "mail_queue.h"
+#include "mail_proto.h"
+#include "mail_open_ok.h"
+#include "recipient_list.h"
+#include "dsn.h"
+#include "dsn_print.h"
+#include "deliver_request.h"
+#include "rcpt_buf.h"
+
+/* deliver_request_initial - send initial status code */
+
+static int deliver_request_initial(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * The master processes runs a finite number of delivery agent processes
+ * to handle service requests. Thus, a delivery agent process must send
+ * something to inform the queue manager that it is ready to receive a
+ * delivery request; otherwise the queue manager could block in write().
+ */
+ if (msg_verbose)
+ msg_info("deliver_request_initial: send initial response");
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
+ ATTR_TYPE_END);
+ if ((err = vstream_fflush(stream)) != 0)
+ if (msg_verbose)
+ msg_warn("send initial response: %m");
+ return (err);
+}
+
+/* deliver_request_final - send final delivery request status */
+
+static int deliver_request_final(VSTREAM *stream, DELIVER_REQUEST *request,
+ int status)
+{
+ DSN *hop_status;
+ int err;
+
+ /* XXX This DSN structure initialization bypasses integrity checks. */
+ static DSN dummy_dsn = {"", "", "", "", "", "", ""};
+
+ /*
+ * Send the status and the optional reason.
+ */
+ if ((hop_status = request->hop_status) == 0)
+ hop_status = &dummy_dsn;
+ if (msg_verbose)
+ msg_info("deliver_request_final: send: \"%s\" %d",
+ hop_status->reason, status);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(dsn_print, (const void *) hop_status),
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ if ((err = vstream_fflush(stream)) != 0)
+ if (msg_verbose)
+ msg_warn("send final status: %m");
+
+ /*
+ * With some UNIX systems, stream sockets lose data when you close them
+ * immediately after writing to them. That is not how sockets are
+ * supposed to behave! The workaround is to wait until the receiver
+ * closes the connection. Calling VSTREAM_GETC() has the benefit of using
+ * whatever timeout is specified in the ipc_timeout parameter.
+ */
+ (void) VSTREAM_GETC(stream);
+ return (err);
+}
+
+/* deliver_request_get - receive message delivery request */
+
+static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request)
+{
+ const char *myname = "deliver_request_get";
+ const char *path;
+ struct stat st;
+ static VSTRING *queue_name;
+ static VSTRING *queue_id;
+ static VSTRING *nexthop;
+ static VSTRING *encoding;
+ static VSTRING *address;
+ static VSTRING *client_name;
+ static VSTRING *client_addr;
+ static VSTRING *client_port;
+ static VSTRING *client_proto;
+ static VSTRING *client_helo;
+ static VSTRING *sasl_method;
+ static VSTRING *sasl_username;
+ static VSTRING *sasl_sender;
+ static VSTRING *log_ident;
+ static VSTRING *rewrite_context;
+ static VSTRING *dsn_envid;
+ static RCPT_BUF *rcpt_buf;
+ int rcpt_count;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Initialize. For some reason I wanted to allow for multiple instances
+ * of a deliver_request structure, thus the hoopla with string
+ * initialization and copying.
+ */
+ if (queue_name == 0) {
+ queue_name = vstring_alloc(10);
+ queue_id = vstring_alloc(10);
+ nexthop = vstring_alloc(10);
+ encoding = vstring_alloc(10);
+ address = vstring_alloc(10);
+ client_name = vstring_alloc(10);
+ client_addr = vstring_alloc(10);
+ client_port = vstring_alloc(10);
+ client_proto = vstring_alloc(10);
+ client_helo = vstring_alloc(10);
+ sasl_method = vstring_alloc(10);
+ sasl_username = vstring_alloc(10);
+ sasl_sender = vstring_alloc(10);
+ log_ident = vstring_alloc(10);
+ rewrite_context = vstring_alloc(10);
+ dsn_envid = vstring_alloc(10);
+ rcpt_buf = rcpb_create();
+ }
+
+ /*
+ * Extract the queue file name, data offset, and sender address. Abort
+ * the conversation when they send bad information.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request->flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_LONG(MAIL_ATTR_OFFSET, &request->data_offset),
+ RECV_ATTR_LONG(MAIL_ATTR_SIZE, &request->data_size),
+ RECV_ATTR_STR(MAIL_ATTR_NEXTHOP, nexthop),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, address),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_FUNC(msg_stats_scan, (void *) &request->msg_stats),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, client_name),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, client_addr),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, client_port),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, client_proto),
+ RECV_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, client_helo),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ RECV_ATTR_STR(MAIL_ATTR_SASL_METHOD, sasl_method),
+ RECV_ATTR_STR(MAIL_ATTR_SASL_USERNAME, sasl_username),
+ RECV_ATTR_STR(MAIL_ATTR_SASL_SENDER, sasl_sender),
+ /* XXX Ditto if we want to pass TLS certificate info. */
+ RECV_ATTR_STR(MAIL_ATTR_LOG_IDENT, log_ident),
+ RECV_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, rewrite_context),
+ RECV_ATTR_INT(MAIL_ATTR_RCPT_COUNT, &rcpt_count),
+ ATTR_TYPE_END) != 23) {
+ msg_warn("%s: error receiving common attributes", myname);
+ return (-1);
+ }
+ if (mail_open_ok(vstring_str(queue_name),
+ vstring_str(queue_id), &st, &path) == 0)
+ return (-1);
+
+ /* Don't override hand-off time after deliver_pass() delegation. */
+ if (request->msg_stats.agent_handoff.tv_sec == 0)
+ GETTIMEOFDAY(&request->msg_stats.agent_handoff);
+
+ request->queue_name = mystrdup(vstring_str(queue_name));
+ request->queue_id = mystrdup(vstring_str(queue_id));
+ request->nexthop = mystrdup(vstring_str(nexthop));
+ request->encoding = mystrdup(vstring_str(encoding));
+ /* Fix 20140708: dedicated smtputf8 attribute with its own flags. */
+ request->smtputf8 = smtputf8;
+ request->sender = mystrdup(vstring_str(address));
+ request->client_name = mystrdup(vstring_str(client_name));
+ request->client_addr = mystrdup(vstring_str(client_addr));
+ request->client_port = mystrdup(vstring_str(client_port));
+ request->client_proto = mystrdup(vstring_str(client_proto));
+ request->client_helo = mystrdup(vstring_str(client_helo));
+ request->sasl_method = mystrdup(vstring_str(sasl_method));
+ request->sasl_username = mystrdup(vstring_str(sasl_username));
+ request->sasl_sender = mystrdup(vstring_str(sasl_sender));
+ request->log_ident = mystrdup(vstring_str(log_ident));
+ request->rewrite_context = mystrdup(vstring_str(rewrite_context));
+ request->dsn_envid = mystrdup(vstring_str(dsn_envid));
+ request->dsn_ret = dsn_ret;
+
+ /*
+ * Extract the recipient offset and address list. Skip over any
+ * attributes from the sender that we do not understand.
+ */
+ while (rcpt_count-- > 0) {
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: error receiving recipient attributes", myname);
+ return (-1);
+ }
+ recipient_list_add(&request->rcpt_list, rcpt_buf->offset,
+ vstring_str(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify,
+ vstring_str(rcpt_buf->orig_addr),
+ vstring_str(rcpt_buf->address));
+ }
+ if (request->rcpt_list.len <= 0) {
+ msg_warn("%s: no recipients in delivery request for destination %s",
+ request->queue_id, request->nexthop);
+ return (-1);
+ }
+
+ /*
+ * Open the queue file and set a shared lock, in order to prevent
+ * duplicate deliveries when the queue is flushed immediately after queue
+ * manager restart.
+ *
+ * The queue manager locks the file exclusively when it enters the active
+ * queue, and releases the lock before starting deliveries from that
+ * file. The queue manager does not lock the file again when reading more
+ * recipients into memory. When the queue manager is restarted, the new
+ * process moves files from the active queue to the incoming queue to
+ * cool off for a while. Delivery agents should therefore never try to
+ * open a file that is locked by a queue manager process.
+ *
+ * Opening the queue file can fail for a variety of reasons, such as the
+ * system running out of resources. Instead of throwing away mail, we're
+ * raising a fatal error which forces the mail system to back off, and
+ * retry later.
+ */
+#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
+
+ request->fp =
+ mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0);
+ if (request->fp == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", request->queue_name, request->queue_id);
+ msg_warn("open %s %s: %m", request->queue_name, request->queue_id);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: file %s", myname, VSTREAM_PATH(request->fp));
+ if (myflock(vstream_fileno(request->fp), INTERNAL_LOCK, DELIVER_LOCK_MODE) < 0)
+ msg_fatal("shared lock %s: %m", VSTREAM_PATH(request->fp));
+ close_on_exec(vstream_fileno(request->fp), CLOSE_ON_EXEC);
+
+ return (0);
+}
+
+/* deliver_request_alloc - allocate delivery request structure */
+
+static DELIVER_REQUEST *deliver_request_alloc(void)
+{
+ DELIVER_REQUEST *request;
+
+ request = (DELIVER_REQUEST *) mymalloc(sizeof(*request));
+ request->fp = 0;
+ request->queue_name = 0;
+ request->queue_id = 0;
+ request->nexthop = 0;
+ request->encoding = 0;
+ request->sender = 0;
+ request->data_offset = 0;
+ request->data_size = 0;
+ recipient_list_init(&request->rcpt_list, RCPT_LIST_INIT_STATUS);
+ request->hop_status = 0;
+ request->client_name = 0;
+ request->client_addr = 0;
+ request->client_port = 0;
+ request->client_proto = 0;
+ request->client_helo = 0;
+ request->sasl_method = 0;
+ request->sasl_username = 0;
+ request->sasl_sender = 0;
+ request->log_ident = 0;
+ request->rewrite_context = 0;
+ request->dsn_envid = 0;
+ return (request);
+}
+
+/* deliver_request_free - clean up delivery request structure */
+
+static void deliver_request_free(DELIVER_REQUEST *request)
+{
+ if (request->fp)
+ vstream_fclose(request->fp);
+ if (request->queue_name)
+ myfree(request->queue_name);
+ if (request->queue_id)
+ myfree(request->queue_id);
+ if (request->nexthop)
+ myfree(request->nexthop);
+ if (request->encoding)
+ myfree(request->encoding);
+ if (request->sender)
+ myfree(request->sender);
+ recipient_list_free(&request->rcpt_list);
+ if (request->hop_status)
+ dsn_free(request->hop_status);
+ if (request->client_name)
+ myfree(request->client_name);
+ if (request->client_addr)
+ myfree(request->client_addr);
+ if (request->client_port)
+ myfree(request->client_port);
+ if (request->client_proto)
+ myfree(request->client_proto);
+ if (request->client_helo)
+ myfree(request->client_helo);
+ if (request->sasl_method)
+ myfree(request->sasl_method);
+ if (request->sasl_username)
+ myfree(request->sasl_username);
+ if (request->sasl_sender)
+ myfree(request->sasl_sender);
+ if (request->log_ident)
+ myfree(request->log_ident);
+ if (request->rewrite_context)
+ myfree(request->rewrite_context);
+ if (request->dsn_envid)
+ myfree(request->dsn_envid);
+ myfree((void *) request);
+}
+
+/* deliver_request_read - create and read delivery request */
+
+DELIVER_REQUEST *deliver_request_read(VSTREAM *stream)
+{
+ DELIVER_REQUEST *request;
+
+ /*
+ * Tell the queue manager that we are ready for this request.
+ */
+ if (deliver_request_initial(stream) != 0)
+ return (0);
+
+ /*
+ * Be prepared for the queue manager to change its mind after contacting
+ * us. This can happen when a transport or host goes bad.
+ */
+ (void) read_wait(vstream_fileno(stream), -1);
+ if (peekfd(vstream_fileno(stream)) <= 0)
+ return (0);
+
+ /*
+ * Allocate and read the queue manager's delivery request.
+ */
+#define XXX_DEFER_STATUS -1
+
+ request = deliver_request_alloc();
+ if (deliver_request_get(stream, request) < 0) {
+ deliver_request_done(stream, request, XXX_DEFER_STATUS);
+ request = 0;
+ }
+ return (request);
+}
+
+/* deliver_request_done - finish delivery request */
+
+int deliver_request_done(VSTREAM *stream, DELIVER_REQUEST *request, int status)
+{
+ int err;
+
+ err = deliver_request_final(stream, request, status);
+ deliver_request_free(request);
+ return (err);
+}
diff --git a/src/global/deliver_request.h b/src/global/deliver_request.h
new file mode 100644
index 0000000..c1c5b1d
--- /dev/null
+++ b/src/global/deliver_request.h
@@ -0,0 +1,156 @@
+#ifndef _DELIVER_REQUEST_H_INCLUDED_
+#define _DELIVER_REQUEST_H_INCLUDED_
+
+/*++
+/* NAME
+/* deliver_request 3h
+/* SUMMARY
+/* mail delivery request protocol, server side
+/* SYNOPSIS
+/* #include <deliver_request.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+#include <dsn.h>
+#include <msg_stats.h>
+
+ /*
+ * Structure of a server mail delivery request.
+ */
+typedef struct DELIVER_REQUEST {
+ VSTREAM *fp; /* stream, shared lock */
+ int flags; /* see below */
+ char *queue_name; /* message queue name */
+ char *queue_id; /* message queue id */
+ long data_offset; /* offset to message */
+ long data_size; /* message size */
+ char *nexthop; /* next hop name */
+ char *encoding; /* content encoding */
+ int smtputf8; /* SMTPUTF8 level */
+ char *sender; /* envelope sender */
+ MSG_STATS msg_stats; /* time profile */
+ RECIPIENT_LIST rcpt_list; /* envelope recipients */
+ DSN *hop_status; /* DSN status */
+ char *client_name; /* client hostname */
+ char *client_addr; /* client address */
+ char *client_port; /* client port */
+ char *client_proto; /* client protocol */
+ char *client_helo; /* helo parameter */
+ char *sasl_method; /* SASL method */
+ char *sasl_username; /* SASL user name */
+ char *sasl_sender; /* SASL sender */
+ char *log_ident; /* original queue ID */
+ char *rewrite_context; /* address rewrite context */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN full/header notification */
+} DELIVER_REQUEST;
+
+ /*
+ * Since we can't send null pointers, null strings represent unavailable
+ * attributes instead. They're less likely to explode in our face, too.
+ */
+#define DEL_REQ_ATTR_AVAIL(a) (*(a))
+
+ /*
+ * How to deliver, really?
+ */
+#define DEL_REQ_FLAG_DEFLT (DEL_REQ_FLAG_SUCCESS | DEL_REQ_FLAG_BOUNCE)
+#define DEL_REQ_FLAG_SUCCESS (1<<0) /* delete successful recipients */
+#define DEL_REQ_FLAG_BOUNCE (1<<1) /* unimplemented */
+
+#define DEL_REQ_FLAG_MTA_VRFY (1<<8) /* MTA-requested address probe */
+#define DEL_REQ_FLAG_USR_VRFY (1<<9) /* user-requested address probe */
+#define DEL_REQ_FLAG_RECORD (1<<10) /* record and deliver */
+#define DEL_REQ_FLAG_CONN_LOAD (1<<11) /* Consult opportunistic cache */
+#define DEL_REQ_FLAG_CONN_STORE (1<<12) /* Update opportunistic cache */
+#define DEL_REQ_FLAG_REC_DLY_SENT (1<<13) /* Record delayed delivery */
+
+ /*
+ * Cache Load and Store as value or mask. Use explicit _MASK for multi-bit
+ * values.
+ */
+#define DEL_REQ_FLAG_CONN_MASK \
+ (DEL_REQ_FLAG_CONN_LOAD | DEL_REQ_FLAG_CONN_STORE)
+
+ /*
+ * For compatibility, the old confusing names.
+ */
+#define DEL_REQ_FLAG_VERIFY DEL_REQ_FLAG_MTA_VRFY
+#define DEL_REQ_FLAG_EXPAND DEL_REQ_FLAG_USR_VRFY
+
+ /*
+ * Mail that uses the trace(8) service, and maybe more.
+ */
+#define DEL_REQ_TRACE_FLAGS_MASK \
+ (DEL_REQ_FLAG_MTA_VRFY | DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD \
+ | DEL_REQ_FLAG_REC_DLY_SENT)
+#define DEL_REQ_TRACE_FLAGS(f) ((f) & DEL_REQ_TRACE_FLAGS_MASK)
+
+ /*
+ * Mail that is not delivered (i.e. uses the trace(8) service only).
+ */
+#define DEL_REQ_TRACE_ONLY_MASK \
+ (DEL_REQ_FLAG_MTA_VRFY | DEL_REQ_FLAG_USR_VRFY)
+#define DEL_REQ_TRACE_ONLY(f) ((f) & DEL_REQ_TRACE_ONLY_MASK)
+
+ /*
+ * Per-recipient delivery status. Not to be confused with per-delivery
+ * request status.
+ */
+#define DEL_RCPT_STAT_OK 0
+#define DEL_RCPT_STAT_DEFER 1
+#define DEL_RCPT_STAT_BOUNCE 2
+#define DEL_RCPT_STAT_TODO 3
+
+ /*
+ * Delivery request status. Note that there are only FINAL and DEFER. This
+ * is because delivery status information can be lost when a delivery agent
+ * or queue manager process terminates prematurely. The only distinctions we
+ * can rely on are "final delivery completed" (positive confirmation that
+ * all recipients are marked as done) and "everything else". In the absence
+ * of a definitive statement the queue manager will always have to be
+ * prepared for all possibilities.
+ */
+#define DEL_STAT_FINAL 0 /* delivered or bounced */
+#define DEL_STAT_DEFER (-1) /* not delivered or bounced */
+
+typedef struct VSTREAM _deliver_vstream_;
+extern DELIVER_REQUEST *deliver_request_read(_deliver_vstream_ *);
+extern int deliver_request_done(_deliver_vstream_ *, DELIVER_REQUEST *, int);
+
+extern int PRINTFLIKE(4, 5) reject_deliver_request(const char *,
+ DELIVER_REQUEST *, const char *, const char *,...);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/delivered_hdr.c b/src/global/delivered_hdr.c
new file mode 100644
index 0000000..0aea1cc
--- /dev/null
+++ b/src/global/delivered_hdr.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* delivered_hdr 3
+/* SUMMARY
+/* process Delivered-To: headers
+/* SYNOPSIS
+/* #include <delivered_hdr.h>
+/*
+/* DELIVERED_HDR_INFO *delivered_hdr_init(stream, offset, flags)
+/* VSTREAM *stream;
+/* off_t offset;
+/* int flags;
+/*
+/* int delivered_hdr_find(info, address)
+/* DELIVERED_HDR_INFO *info;
+/* const char *address;
+/*
+/* void delivered_hdr_free(info)
+/* DELIVERED_HDR_INFO *info;
+/* DESCRIPTION
+/* This module processes addresses in Delivered-To: headers.
+/* These headers are added by some mail delivery systems, for the
+/* purpose of breaking mail forwarding loops. N.B. This solves
+/* a different problem than the Received: hop count limit. Hop
+/* counts are used to limit the impact of mail routing problems.
+/*
+/* delivered_hdr_init() extracts Delivered-To: header addresses
+/* from the specified message, and returns a table with the
+/* result. The file seek pointer is changed.
+/*
+/* delivered_hdr_find() looks up the address in the lookup table,
+/* and returns non-zero when the address was found. The
+/* address argument must be in internalized form.
+/*
+/* delivered_hdr_free() releases storage that was allocated by
+/* delivered_hdr_init().
+/*
+/* Arguments:
+/* .IP stream
+/* The open queue file.
+/* .IP offset
+/* Offset of the first message content record.
+/* .IP flags
+/* Zero, or the bit-wise OR ot:
+/* .RS
+/* .IP FOLD_ADDR_USER
+/* Case fold the address local part.
+/* .IP FOLD_ADDR_HOST
+/* Case fold the address domain part.
+/* .IP FOLD_ADDR_ALL
+/* Alias for (FOLD_ADDR_USER | FOLD_ADDR_HOST).
+/* .RE
+/* .IP info
+/* Extracted Delivered-To: addresses information.
+/* .IP address
+/* A recipient address, internal form.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* mail_copy(3), producer of Delivered-To: and other headers.
+/* 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 <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <is_header.h>
+#include <quote_822_local.h>
+#include <header_opts.h>
+#include <delivered_hdr.h>
+#include <fold_addr.h>
+
+ /*
+ * Application-specific.
+ */
+struct DELIVERED_HDR_INFO {
+ int flags;
+ VSTRING *buf;
+ VSTRING *fold;
+ HTABLE *table;
+};
+
+#define STR(x) vstring_str(x)
+
+/* delivered_hdr_init - extract delivered-to information from the message */
+
+DELIVERED_HDR_INFO *delivered_hdr_init(VSTREAM *fp, off_t offset, int flags)
+{
+ char *cp;
+ DELIVERED_HDR_INFO *info;
+ const HEADER_OPTS *hdr;
+ int curr_type;
+ int prev_type;
+
+ /*
+ * Sanity check.
+ */
+ info = (DELIVERED_HDR_INFO *) mymalloc(sizeof(*info));
+ info->flags = flags;
+ info->buf = vstring_alloc(10);
+ info->fold = vstring_alloc(10);
+ info->table = htable_create(0);
+
+ if (vstream_fseek(fp, offset, SEEK_SET) < 0)
+ msg_fatal("seek queue file %s: %m", VSTREAM_PATH(fp));
+
+ /*
+ * XXX Assume that mail_copy() produces delivered-to headers that fit in
+ * a REC_TYPE_NORM or REC_TYPE_CONT record. Lowercase the delivered-to
+ * addresses for consistency.
+ *
+ * XXX Don't get bogged down by gazillions of delivered-to headers.
+ */
+#define DELIVERED_HDR_LIMIT 1000
+
+ for (prev_type = REC_TYPE_NORM;
+ info->table->used < DELIVERED_HDR_LIMIT
+ && ((curr_type = rec_get(fp, info->buf, 0)) == REC_TYPE_NORM
+ || curr_type == REC_TYPE_CONT);
+ prev_type = curr_type) {
+ if (prev_type == REC_TYPE_CONT)
+ continue;
+ if (is_header(STR(info->buf))) {
+ if ((hdr = header_opts_find(STR(info->buf))) != 0
+ && hdr->type == HDR_DELIVERED_TO) {
+ cp = STR(info->buf) + strlen(hdr->name) + 1;
+ while (ISSPACE(*cp))
+ cp++;
+ cp = fold_addr(info->fold, cp, info->flags);
+ if (msg_verbose)
+ msg_info("delivered_hdr_init: %s", cp);
+ htable_enter(info->table, cp, (void *) 0);
+ }
+ } else if (ISSPACE(STR(info->buf)[0])) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ return (info);
+}
+
+/* delivered_hdr_find - look up recipient in delivered table */
+
+int delivered_hdr_find(DELIVERED_HDR_INFO *info, const char *address)
+{
+ HTABLE_INFO *ht;
+ const char *addr_key;
+
+ /*
+ * mail_copy() uses quote_822_local() when writing the Delivered-To:
+ * header. We must therefore apply the same transformation when looking
+ * up the recipient. Lowercase the delivered-to address for consistency.
+ */
+ quote_822_local(info->buf, address);
+ addr_key = fold_addr(info->fold, STR(info->buf), info->flags);
+ ht = htable_locate(info->table, addr_key);
+ return (ht != 0);
+}
+
+/* delivered_hdr_free - destructor */
+
+void delivered_hdr_free(DELIVERED_HDR_INFO *info)
+{
+ vstring_free(info->buf);
+ vstring_free(info->fold);
+ htable_free(info->table, (void (*) (void *)) 0);
+ myfree((void *) info);
+}
+
+#ifdef TEST
+
+#include <msg_vstream.h>
+#include <mail_params.h>
+
+char *var_drop_hdrs;
+
+int main(int arc, char **argv)
+{
+
+ /*
+ * We write test records to a VSTRING, then read with delivered_hdr_init.
+ */
+ VSTRING *mem_bp;
+ VSTREAM *mem_fp;
+ DELIVERED_HDR_INFO *dp;
+ struct test_case {
+ int rec_type;
+ const char *content;
+ int expect_find;
+ };
+ const struct test_case test_cases[] = {
+ REC_TYPE_CONT, "Delivered-To: one", 1,
+ REC_TYPE_NORM, "Delivered-To: two", 0,
+ REC_TYPE_NORM, "Delivered-To: three", 1,
+ 0,
+ };
+ const struct test_case *tp;
+ int actual_find;
+ int errors;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ var_drop_hdrs = mystrdup(DEF_DROP_HDRS);
+
+ mem_bp = vstring_alloc(VSTREAM_BUFSIZE);
+ if ((mem_fp = vstream_memopen(mem_bp, O_WRONLY)) == 0)
+ msg_panic("vstream_memopen O_WRONLY failed: %m");
+
+#define REC_PUT_LIT(fp, type, lit) rec_put((fp), (type), (lit), strlen(lit))
+
+ for (tp = test_cases; tp->content != 0; tp++)
+ REC_PUT_LIT(mem_fp, tp->rec_type, tp->content);
+
+ if (vstream_fclose(mem_fp))
+ msg_panic("vstream_fclose fail: %m");
+
+ if ((mem_fp = vstream_memopen(mem_bp, O_RDONLY)) == 0)
+ msg_panic("vstream_memopen O_RDONLY failed: %m");
+
+ dp = delivered_hdr_init(mem_fp, 0, FOLD_ADDR_ALL);
+
+ for (errors = 0, tp = test_cases; tp->content != 0; tp++) {
+ actual_find =
+ delivered_hdr_find(dp, tp->content + sizeof("Delivered-To:"));
+ msg_info("test case: %c >%s<: %s (expected: %s)",
+ tp->rec_type, tp->content,
+ actual_find == tp->expect_find ? "PASS" : "FAIL",
+ tp->expect_find ? "MATCH" : "NO MATCH");
+ errors += (actual_find != tp->expect_find);;
+ }
+ exit(errors);
+}
+
+#endif
diff --git a/src/global/delivered_hdr.h b/src/global/delivered_hdr.h
new file mode 100644
index 0000000..24e0ceb
--- /dev/null
+++ b/src/global/delivered_hdr.h
@@ -0,0 +1,43 @@
+#ifndef _DELIVERED_HDR_H_INCLUDED_
+#define _DELIVERED_HDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* delivered_hdr 3h
+/* SUMMARY
+/* process Delivered-To: headers
+/* SYNOPSIS
+/* #include <delivered_hdr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <fold_addr.h>
+
+ /*
+ * External interface.
+ */
+typedef struct DELIVERED_HDR_INFO DELIVERED_HDR_INFO;
+extern DELIVERED_HDR_INFO *delivered_hdr_init(VSTREAM *, off_t, int);
+extern int delivered_hdr_find(DELIVERED_HDR_INFO *, const char *);
+extern void delivered_hdr_free(DELIVERED_HDR_INFO *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/delivered_hdr.ref b/src/global/delivered_hdr.ref
new file mode 100644
index 0000000..1e71d57
--- /dev/null
+++ b/src/global/delivered_hdr.ref
@@ -0,0 +1,3 @@
+./delivered_hdr: test case: L >Delivered-To: one<: PASS (expected: MATCH)
+./delivered_hdr: test case: N >Delivered-To: two<: PASS (expected: NO MATCH)
+./delivered_hdr: test case: N >Delivered-To: three<: PASS (expected: MATCH)
diff --git a/src/global/dict_ldap.c b/src/global/dict_ldap.c
new file mode 100644
index 0000000..a4a4c26
--- /dev/null
+++ b/src/global/dict_ldap.c
@@ -0,0 +1,1994 @@
+/*++
+/* NAME
+/* dict_ldap 3
+/* SUMMARY
+/* dictionary manager interface to LDAP maps
+/* SYNOPSIS
+/* #include <dict_ldap.h>
+/*
+/* DICT *dict_ldap_open(attribute, dummy, dict_flags)
+/* const char *ldapsource;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_ldap_open() makes LDAP user information accessible via
+/* the generic dictionary operations described in dict_open(3).
+/*
+/* Arguments:
+/* .IP ldapsource
+/* Either the path to the LDAP configuration file (if it starts
+/* with '/' or '.'), or the prefix which will be used to obtain
+/* configuration parameters for this search.
+/*
+/* In the first case, the configuration variables below are
+/* specified in the file as \fBname\fR=\fBvalue\fR pairs.
+/*
+/* In the second case, the configuration variables are prefixed
+/* with the value of \fIldapsource\fR and an underscore,
+/* and they are specified in main.cf. For example, if this
+/* value is \fBldapone\fR, the variables would look like
+/* \fBldapone_server_host\fR, \fBldapone_search_base\fR, and so on.
+/* .IP dummy
+/* Not used; this argument exists only for compatibility with
+/* the dict_open(3) interface.
+/* .PP
+/* Configuration parameters:
+/* .IP server_host
+/* List of hosts at which all LDAP queries are directed.
+/* The host names can also be LDAP URLs if the LDAP client library used
+/* is OpenLDAP.
+/* .IP server_port
+/* The port the LDAP server listens on.
+/* .IP search_base
+/* The LDAP search base, for example: \fIO=organization name, C=country\fR.
+/* .IP domain
+/* If specified, only lookups ending in this value will be queried.
+/* This can significantly reduce the query load on the LDAP server.
+/* .IP timeout
+/* Deadline for LDAP open() and LDAP search() .
+/* .IP query_filter
+/* The search filter template used to search for directory entries,
+/* for example \fI(mailacceptinggeneralid=%s)\fR. See ldap_table(5)
+/* for details.
+/* .IP result_format
+/* The result template used to expand results from queries. Default
+/* is \fI%s\fR. See ldap_table(5) for details. Also supported under
+/* the name \fIresult_filter\fR for compatibility with older releases.
+/* .IP result_attribute
+/* The attribute(s) returned by the search, in which to find
+/* RFC822 addresses, for example \fImaildrop\fR.
+/* .IP special_result_attribute
+/* The attribute(s) of directory entries that can contain DNs or URLs.
+/* If found, a recursive subsequent search is done using their values.
+/* .IP leaf_result_attribute
+/* These are only returned for "leaf" LDAP entries, i.e. those that are
+/* not "terminal" and have no values for any of the "special" result
+/* attributes.
+/* .IP terminal_result_attribute
+/* If found, the LDAP entry is considered a terminal LDAP object, not
+/* subject to further direct or recursive expansion. Only the terminal
+/* result attributes are returned.
+/* .IP scope
+/* LDAP search scope: sub, base, or one.
+/* .IP bind
+/* Whether or not to bind to the server -- LDAP v3 implementations don't
+/* require it, which saves some overhead.
+/* .IP bind_dn
+/* If you must bind to the server, do it with this distinguished name ...
+/* .IP bind_pw
+/* \&... and this password.
+/* .IP cache (no longer supported)
+/* Whether or not to turn on client-side caching.
+/* .IP cache_expiry (no longer supported)
+/* If you do cache results, expire them after this many seconds.
+/* .IP cache_size (no longer supported)
+/* The cache size in bytes. Does nothing if the cache is off, of course.
+/* .IP recursion_limit
+/* Maximum recursion depth when expanding DN or URL references.
+/* Queries which exceed the recursion limit fail with
+/* dict->error = DICT_ERR_RETRY.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values. Lookups which
+/* exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that
+/* each value of a multivalued result attribute counts as one result.
+/* .IP size_limit
+/* Limit on the number of entries returned by individual LDAP queries.
+/* Queries which exceed the limit fail with dict->error=DICT_ERR_RETRY.
+/* This is an *entry* count, for any single query performed during the
+/* possibly recursive lookup.
+/* .IP chase_referrals
+/* Controls whether LDAP referrals are obeyed.
+/* .IP dereference
+/* How to handle LDAP aliases. See ldap.h or ldap_open(3) man page.
+/* .IP version
+/* Specifies the LDAP protocol version to use. Default is version
+/* \fI2\fR.
+/* .IP "\fBsasl_mechs (empty)\fR"
+/* Specifies a space-separated list of LDAP SASL Mechanisms.
+/* .IP "\fBsasl_realm (empty)\fR"
+/* The realm to use for SASL binds.
+/* .IP "\fBsasl_authz_id (empty)\fR"
+/* The SASL Authorization Identity to assert.
+/* .IP "\fBsasl_minssf (0)\fR"
+/* The minimum SASL SSF to allow.
+/* .IP start_tls
+/* Whether or not to issue STARTTLS upon connection to the server.
+/* At this time, STARTTLS and LDAP SSL are only available if the
+/* LDAP client library used is OpenLDAP. Default is \fIno\fR.
+/* .IP tls_ca_cert_file
+/* File containing certificates for all of the X509 Certification
+/* Authorities the client will recognize. Takes precedence over
+/* tls_ca_cert_dir.
+/* .IP tls_ca_cert_dir
+/* Directory containing X509 Certification Authority certificates
+/* in separate individual files.
+/* .IP tls_cert
+/* File containing client's X509 certificate.
+/* .IP tls_key
+/* File containing the private key corresponding to
+/* tls_cert.
+/* .IP tls_require_cert
+/* Whether or not to request server's X509 certificate and check its
+/* validity. The value "no" means don't check the cert trust chain
+/* and (OpenLDAP 2.1+) don't check the peername. The value "yes" means
+/* check both the trust chain and the peername (with OpenLDAP <= 2.0.11,
+/* the peername checks use the reverse hostname from the LDAP servers's
+/* IP address, not the user supplied servername).
+/* .IP tls_random_file
+/* Path of a file to obtain random bits from when /dev/[u]random is
+/* not available. Generally set to the name of the EGD/PRNGD socket.
+/* .IP tls_cipher_suite
+/* Cipher suite to use in SSL/TLS negotiations.
+/* .IP debuglevel
+/* Debug level. See 'loglevel' option in slapd.conf(5) man page.
+/* Currently only in openldap libraries (and derivatives).
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Prabhat K Singh
+/* VSNL, Bombay, India.
+/* prabhat@giasbm01.vsnl.net.in
+/*
+/* 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
+/*
+/* John Hensley
+/* john@sunislelodge.com
+/*
+/* Current maintainers:
+/*
+/* LaMont Jones
+/* lamont@debian.org
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/* New York, USA
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+#ifdef HAS_LDAP
+
+#include <sys/time.h>
+#include <stdio.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <lber.h>
+#include <ldap.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Older APIs have weird memory freeing behavior.
+ */
+#if !defined(LDAP_API_VERSION) || (LDAP_API_VERSION < 2000)
+#error "Your LDAP version is too old"
+#endif
+
+/* Handle differences between LDAP SDK's constant definitions */
+#ifndef LDAP_CONST
+#define LDAP_CONST const
+#endif
+#ifndef LDAP_OPT_SUCCESS
+#define LDAP_OPT_SUCCESS 0
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <dict.h>
+#include <stringops.h>
+#include <binhash.h>
+#include <name_code.h>
+
+/* Global library. */
+
+#include "cfg_parser.h"
+#include "db_common.h"
+#include "mail_conf.h"
+
+#if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
+
+ /*
+ * SASL headers, for sasl_interact_t. Either SASL v1 or v2 should be fine.
+ */
+#include <sasl.h>
+#endif
+
+/* Application-specific. */
+
+#include "dict_ldap.h"
+
+#define DICT_LDAP_BIND_NONE 0
+#define DICT_LDAP_BIND_SIMPLE 1
+#define DICT_LDAP_BIND_SASL 2
+#define DICT_LDAP_DO_BIND(d) ((d)->bind != DICT_LDAP_BIND_NONE)
+#define DICT_LDAP_DO_SASL(d) ((d)->bind == DICT_LDAP_BIND_SASL)
+
+static const NAME_CODE bindopt_table[] = {
+ CONFIG_BOOL_NO, DICT_LDAP_BIND_NONE,
+ "none", DICT_LDAP_BIND_NONE,
+ CONFIG_BOOL_YES, DICT_LDAP_BIND_SIMPLE,
+ "simple", DICT_LDAP_BIND_SIMPLE,
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+#if defined(USE_LDAP_SASL)
+ "sasl", DICT_LDAP_BIND_SASL,
+#endif
+#endif
+ 0, -1,
+};
+
+typedef struct {
+ LDAP *conn_ld;
+ int conn_refcount;
+} LDAP_CONN;
+
+/*
+ * Structure containing all the configuration parameters for a given
+ * LDAP source, plus its connection handle.
+ */
+typedef struct {
+ DICT dict; /* generic member */
+ CFG_PARSER *parser; /* common parameter parser */
+ char *query; /* db_common_expand() query */
+ char *result_format; /* db_common_expand() result_format */
+ void *ctx; /* db_common_parse() context */
+ int dynamic_base; /* Search base has substitutions? */
+ int expansion_limit;
+ char *server_host;
+ int server_port;
+ int scope;
+ char *search_base;
+ ARGV *result_attributes;
+ int num_terminal; /* Number of terminal attributes. */
+ int num_leaf; /* Number of leaf attributes */
+ int num_attributes; /* Combined # of non-special attrs */
+ int bind;
+ char *bind_dn;
+ char *bind_pw;
+ int timeout;
+ int dereference;
+ long recursion_limit;
+ long size_limit;
+ int chase_referrals;
+ int debuglevel;
+ int version;
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+#if defined(USE_LDAP_SASL)
+ int sasl;
+ char *sasl_mechs;
+ char *sasl_realm;
+ char *sasl_authz;
+ int sasl_minssf;
+#endif
+ int ldap_ssl;
+ int start_tls;
+ int tls_require_cert;
+ char *tls_ca_cert_file;
+ char *tls_ca_cert_dir;
+ char *tls_cert;
+ char *tls_key;
+ char *tls_random_file;
+ char *tls_cipher_suite;
+#endif
+ BINHASH_INFO *ht; /* hash entry for LDAP connection */
+ LDAP *ld; /* duplicated from conn->conn_ld */
+} DICT_LDAP;
+
+#define DICT_LDAP_CONN(d) ((LDAP_CONN *)((d)->ht->value))
+
+#define DICT_LDAP_UNBIND_RETURN(__ld, __err, __ret) do { \
+ dict_ldap_unbind(__ld); \
+ (__ld) = 0; \
+ dict_ldap->dict.error = (__err); \
+ return ((__ret)); \
+ } while (0)
+
+ /*
+ * Bitrot: LDAP_API 3000 and up (OpenLDAP 2.2.x) deprecated ldap_unbind()
+ */
+#if LDAP_API_VERSION >= 3000
+#define dict_ldap_unbind(ld) ldap_unbind_ext((ld), 0, 0)
+#define dict_ldap_abandon(ld, msg) ldap_abandon_ext((ld), (msg), 0, 0)
+#else
+#define dict_ldap_unbind(ld) ldap_unbind(ld)
+#define dict_ldap_abandon(ld, msg) ldap_abandon((ld), (msg))
+#endif
+
+static int dict_ldap_vendor_version(void)
+{
+ const char *myname = "dict_ldap_api_info";
+ LDAPAPIInfo api;
+
+ /*
+ * We tell the library our version, and it tells us its version and/or
+ * may return an error code if the versions are not the same.
+ */
+ api.ldapai_info_version = LDAP_API_INFO_VERSION;
+ if (ldap_get_option(0, LDAP_OPT_API_INFO, &api) != LDAP_SUCCESS
+ || api.ldapai_info_version != LDAP_API_INFO_VERSION) {
+ if (api.ldapai_info_version != LDAP_API_INFO_VERSION)
+ msg_fatal("%s: run-time API_INFO version: %d, compiled with: %d",
+ myname, api.ldapai_info_version, LDAP_API_INFO_VERSION);
+ else
+ msg_fatal("%s: ldap_get_option(API_INFO) failed", myname);
+ }
+ if (strcmp(api.ldapai_vendor_name, LDAP_VENDOR_NAME) != 0)
+ msg_fatal("%s: run-time API vendor: %s, compiled with: %s",
+ myname, api.ldapai_vendor_name, LDAP_VENDOR_NAME);
+
+ return (api.ldapai_vendor_version);
+}
+
+/*
+ * Quoting rules.
+ */
+
+/* rfc2253_quote - Quote input key for safe inclusion in the search base */
+
+static void rfc2253_quote(DICT *unused, const char *name, VSTRING *result)
+{
+ const char *sub = name;
+ size_t len;
+
+ /*
+ * The RFC only requires quoting of a leading or trailing space, but it
+ * is harmless to quote whitespace everywhere. Similarly, we quote all
+ * '#' characters, even though only the leading '#' character requires
+ * quoting per the RFC.
+ */
+ while (*sub)
+ if ((len = strcspn(sub, " \t\"#+,;<>\\")) > 0) {
+ vstring_strncat(result, sub, len);
+ sub += len;
+ } else
+ vstring_sprintf_append(result, "\\%02X",
+ *((const unsigned char *) sub++));
+}
+
+/* rfc2254_quote - Quote input key for safe inclusion in the query filter */
+
+static void rfc2254_quote(DICT *unused, const char *name, VSTRING *result)
+{
+ const char *sub = name;
+ size_t len;
+
+ /*
+ * If any characters in the supplied address should be escaped per RFC
+ * 2254, do so. Thanks to Keith Stevenson and Wietse. And thanks to
+ * Samuel Tardieu for spotting that wildcard searches were being done in
+ * the first place, which prompted the ill-conceived lookup_wildcards
+ * parameter and then this more comprehensive mechanism.
+ */
+ while (*sub)
+ if ((len = strcspn(sub, "*()\\")) > 0) {
+ vstring_strncat(result, sub, len);
+ sub += len;
+ } else
+ vstring_sprintf_append(result, "\\%02X",
+ *((const unsigned char *) sub++));
+}
+
+static BINHASH *conn_hash = 0;
+
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
+/*
+ * LDAP connection timeout support.
+ */
+static jmp_buf env;
+
+static void dict_ldap_timeout(int unused_sig)
+{
+ longjmp(env, 1);
+}
+
+#endif
+
+static void dict_ldap_logprint(LDAP_CONST char *data)
+{
+ const char *myname = "dict_ldap_debug";
+ char *buf, *p;
+
+ buf = mystrdup(data);
+ if (*buf) {
+ p = buf + strlen(buf) - 1;
+ while (p - buf >= 0 && ISSPACE(*p))
+ *p-- = 0;
+ }
+ msg_info("%s: %s", myname, buf);
+ myfree(buf);
+}
+
+static int dict_ldap_get_errno(LDAP *ld)
+{
+ int rc;
+
+ if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_OPT_SUCCESS)
+ rc = LDAP_OTHER;
+ return rc;
+}
+
+static int dict_ldap_set_errno(LDAP *ld, int rc)
+{
+ (void) ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &rc);
+ return rc;
+}
+
+#if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
+
+ /*
+ * Context structure for SASL property callback.
+ */
+typedef struct bind_props {
+ char *authcid;
+ char *passwd;
+ char *realm;
+ char *authzid;
+} bind_props;
+
+static int ldap_b2_interact(LDAP *ld, unsigned flags, void *props, void *inter)
+{
+
+ sasl_interact_t *in;
+ bind_props *ctx = (bind_props *) props;
+
+ for (in = inter; in->id != SASL_CB_LIST_END; in++) {
+ in->result = NULL;
+ switch (in->id) {
+ case SASL_CB_GETREALM:
+ in->result = ctx->realm;
+ break;
+ case SASL_CB_AUTHNAME:
+ in->result = ctx->authcid;
+ break;
+ case SASL_CB_USER:
+ in->result = ctx->authzid;
+ break;
+ case SASL_CB_PASS:
+ in->result = ctx->passwd;
+ break;
+ }
+ if (in->result)
+ in->len = strlen(in->result);
+ }
+ return LDAP_SUCCESS;
+}
+
+#endif
+
+/* dict_ldap_result - Read and parse LDAP result */
+
+static int dict_ldap_result(LDAP *ld, int msgid, int timeout, LDAPMessage **res)
+{
+ struct timeval mytimeval;
+ int err;
+
+ mytimeval.tv_sec = timeout;
+ mytimeval.tv_usec = 0;
+
+#define GET_ALL 1
+ if (ldap_result(ld, msgid, GET_ALL, &mytimeval, res) == -1)
+ return (dict_ldap_get_errno(ld));
+
+ if ((err = dict_ldap_get_errno(ld)) != LDAP_SUCCESS) {
+ if (err == LDAP_TIMEOUT) {
+ (void) dict_ldap_abandon(ld, msgid);
+ return (dict_ldap_set_errno(ld, LDAP_TIMEOUT));
+ }
+ return err;
+ }
+ return LDAP_SUCCESS;
+}
+
+#if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
+
+/* Asynchronous SASL auth if SASL is enabled */
+
+static int dict_ldap_bind_sasl(DICT_LDAP *dict_ldap)
+{
+ int rc;
+ bind_props props;
+ static VSTRING *minssf = 0;
+
+ if (minssf == 0)
+ minssf = vstring_alloc(12);
+
+ vstring_sprintf(minssf, "minssf=%d", dict_ldap->sasl_minssf);
+
+ if ((rc = ldap_set_option(dict_ldap->ld, LDAP_OPT_X_SASL_SECPROPS,
+ (char *) minssf)) != LDAP_OPT_SUCCESS)
+ return (rc);
+
+ props.authcid = dict_ldap->bind_dn;
+ props.passwd = dict_ldap->bind_pw;
+ props.realm = dict_ldap->sasl_realm;
+ props.authzid = dict_ldap->sasl_authz;
+
+ if ((rc = ldap_sasl_interactive_bind_s(dict_ldap->ld, NULL,
+ dict_ldap->sasl_mechs, NULL, NULL,
+ LDAP_SASL_QUIET, ldap_b2_interact,
+ &props)) != LDAP_SUCCESS)
+ return (rc);
+
+ return (LDAP_SUCCESS);
+}
+
+#endif
+
+/* dict_ldap_bind_st - Synchronous simple auth with timeout */
+
+static int dict_ldap_bind_st(DICT_LDAP *dict_ldap)
+{
+ int rc;
+ int err = LDAP_SUCCESS;
+ int msgid;
+ LDAPMessage *res;
+ struct berval cred;
+
+ cred.bv_val = dict_ldap->bind_pw;
+ cred.bv_len = strlen(cred.bv_val);
+ if ((rc = ldap_sasl_bind(dict_ldap->ld, dict_ldap->bind_dn,
+ LDAP_SASL_SIMPLE, &cred,
+ 0, 0, &msgid)) != LDAP_SUCCESS)
+ return (rc);
+ if ((rc = dict_ldap_result(dict_ldap->ld, msgid, dict_ldap->timeout,
+ &res)) != LDAP_SUCCESS)
+ return (rc);
+
+#define FREE_RESULT 1
+ rc = ldap_parse_result(dict_ldap->ld, res, &err, 0, 0, 0, 0, FREE_RESULT);
+ return (rc == LDAP_SUCCESS ? err : rc);
+}
+
+/* search_st - Synchronous search with timeout */
+
+static int search_st(LDAP *ld, char *base, int scope, char *query,
+ char **attrs, int timeout, LDAPMessage **res)
+{
+ struct timeval mytimeval;
+ int msgid;
+ int rc;
+ int err;
+
+ mytimeval.tv_sec = timeout;
+ mytimeval.tv_usec = 0;
+
+#define WANTVALS 0
+#define USE_SIZE_LIM_OPT -1 /* Any negative value will do */
+
+ if ((rc = ldap_search_ext(ld, base, scope, query, attrs, WANTVALS, 0, 0,
+ &mytimeval, USE_SIZE_LIM_OPT,
+ &msgid)) != LDAP_SUCCESS)
+ return rc;
+
+ if ((rc = dict_ldap_result(ld, msgid, timeout, res)) != LDAP_SUCCESS)
+ return (rc);
+
+#define DONT_FREE_RESULT 0
+ rc = ldap_parse_result(ld, *res, &err, 0, 0, 0, 0, DONT_FREE_RESULT);
+ return (err != LDAP_SUCCESS ? err : rc);
+}
+
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+static int dict_ldap_set_tls_options(DICT_LDAP *dict_ldap)
+{
+ const char *myname = "dict_ldap_set_tls_options";
+ int rc;
+
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ int am_server = 0;
+ LDAP *ld = dict_ldap->ld;
+
+#else
+ LDAP *ld = 0;
+
+#endif
+
+ if (dict_ldap->start_tls || dict_ldap->ldap_ssl) {
+ if (*dict_ldap->tls_random_file) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_RANDOM_FILE,
+ dict_ldap->tls_random_file)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_random_file to %s: %d: %s",
+ myname, dict_ldap->tls_random_file,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if (*dict_ldap->tls_ca_cert_file) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE,
+ dict_ldap->tls_ca_cert_file)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_ca_cert_file to %s: %d: %s",
+ myname, dict_ldap->tls_ca_cert_file,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if (*dict_ldap->tls_ca_cert_dir) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR,
+ dict_ldap->tls_ca_cert_dir)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_ca_cert_dir to %s: %d: %s",
+ myname, dict_ldap->tls_ca_cert_dir,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if (*dict_ldap->tls_cert) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE,
+ dict_ldap->tls_cert)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_cert to %s: %d: %s",
+ myname, dict_ldap->tls_cert,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if (*dict_ldap->tls_key) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE,
+ dict_ldap->tls_key)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_key to %s: %d: %s",
+ myname, dict_ldap->tls_key,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if (*dict_ldap->tls_cipher_suite) {
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ dict_ldap->tls_cipher_suite)) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_cipher_suite to %s: %d: %s",
+ myname, dict_ldap->tls_cipher_suite,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+ }
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &(dict_ldap->tls_require_cert))) != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to set tls_require_cert to %d: %d: %s",
+ myname, dict_ldap->tls_require_cert,
+ rc, ldap_err2string(rc));
+ return (-1);
+ }
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &am_server))
+ != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to allocate new TLS context %d: %s",
+ myname, rc, ldap_err2string(rc));
+ return (-1);
+ }
+#endif
+ }
+ return (0);
+}
+
+#endif
+
+/* Establish a connection to the LDAP server. */
+static int dict_ldap_connect(DICT_LDAP *dict_ldap)
+{
+ const char *myname = "dict_ldap_connect";
+ int rc = 0;
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ struct timeval mytimeval;
+
+#endif
+
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
+ void (*saved_alarm) (int);
+
+#endif
+
+#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
+ if (dict_ldap->debuglevel > 0 &&
+ ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN,
+ (LDAP_CONST void *) dict_ldap_logprint) != LBER_OPT_SUCCESS)
+ msg_warn("%s: Unable to set ber logprint function.", myname);
+#if defined(LBER_OPT_DEBUG_LEVEL)
+ if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL,
+ &(dict_ldap->debuglevel)) != LBER_OPT_SUCCESS)
+ msg_warn("%s: Unable to set BER debug level.", myname);
+#endif
+ if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL,
+ &(dict_ldap->debuglevel)) != LDAP_OPT_SUCCESS)
+ msg_warn("%s: Unable to set LDAP debug level.", myname);
+#endif
+
+ dict_ldap->dict.error = 0;
+
+ if (msg_verbose)
+ msg_info("%s: Connecting to server %s", myname,
+ dict_ldap->server_host);
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+ ldap_initialize(&(dict_ldap->ld), dict_ldap->server_host);
+#else
+ dict_ldap->ld = ldap_init(dict_ldap->server_host,
+ (int) dict_ldap->server_port);
+#endif
+ if (dict_ldap->ld == NULL) {
+ msg_warn("%s: Unable to init LDAP server %s",
+ myname, dict_ldap->server_host);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+ mytimeval.tv_sec = dict_ldap->timeout;
+ mytimeval.tv_usec = 0;
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &mytimeval) !=
+ LDAP_OPT_SUCCESS) {
+ msg_warn("%s: Unable to set network timeout.", myname);
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+#else
+ if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+ msg_warn("%s: Error setting signal handler for open timeout: %m",
+ myname);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+ alarm(dict_ldap->timeout);
+ if (setjmp(env) == 0)
+ dict_ldap->ld = ldap_open(dict_ldap->server_host,
+ (int) dict_ldap->server_port);
+ else
+ dict_ldap->ld = 0;
+ alarm(0);
+
+ if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+ msg_warn("%s: Error resetting signal handler after open: %m",
+ myname);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+ if (dict_ldap->ld == NULL) {
+ msg_warn("%s: Unable to connect to LDAP server %s",
+ myname, dict_ldap->server_host);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+#endif
+
+ /*
+ * v3 support is needed for referral chasing. Thanks to Sami Haahtinen
+ * for the patch.
+ */
+#ifdef LDAP_OPT_PROTOCOL_VERSION
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_PROTOCOL_VERSION,
+ &dict_ldap->version) != LDAP_OPT_SUCCESS) {
+ msg_warn("%s: Unable to set LDAP protocol version", myname);
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+ if (msg_verbose) {
+ if (ldap_get_option(dict_ldap->ld,
+ LDAP_OPT_PROTOCOL_VERSION,
+ &dict_ldap->version) != LDAP_OPT_SUCCESS)
+ msg_warn("%s: Unable to get LDAP protocol version", myname);
+ else
+ msg_info("%s: Actual Protocol version used is %d.",
+ myname, dict_ldap->version);
+ }
+#endif
+
+ /*
+ * Limit the number of entries returned by each query.
+ */
+ if (dict_ldap->size_limit) {
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT,
+ &dict_ldap->size_limit) != LDAP_OPT_SUCCESS) {
+ msg_warn("%s: %s: Unable to set query result size limit to %ld.",
+ myname, dict_ldap->parser->name, dict_ldap->size_limit);
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+ }
+
+ /*
+ * Configure alias dereferencing for this connection. Thanks to Mike
+ * Mattice for this, and to Hery Rakotoarisoa for the v3 update.
+ */
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_DEREF,
+ &(dict_ldap->dereference)) != LDAP_OPT_SUCCESS)
+ msg_warn("%s: Unable to set dereference option.", myname);
+
+ /* Chase referrals. */
+
+#ifdef LDAP_OPT_REFERRALS
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_REFERRALS,
+ dict_ldap->chase_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)
+ != LDAP_OPT_SUCCESS) {
+ msg_warn("%s: Unable to set Referral chasing.", myname);
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+#else
+ if (dict_ldap->chase_referrals) {
+ msg_warn("%s: Unable to set Referral chasing.", myname);
+ }
+#endif
+
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+ if (dict_ldap_set_tls_options(dict_ldap) != 0)
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ if (dict_ldap->start_tls) {
+ if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+ msg_warn("%s: Error setting signal handler for STARTTLS timeout: %m",
+ myname);
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+ alarm(dict_ldap->timeout);
+ if (setjmp(env) == 0)
+ rc = ldap_start_tls_s(dict_ldap->ld, NULL, NULL);
+ else {
+ rc = LDAP_TIMEOUT;
+ dict_ldap->ld = 0; /* Unknown state after
+ * longjmp() */
+ }
+ alarm(0);
+
+ if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+ msg_warn("%s: Error resetting signal handler after STARTTLS: %m",
+ myname);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+ if (rc != LDAP_SUCCESS) {
+ msg_error("%s: Unable to set STARTTLS: %d: %s", myname,
+ rc, ldap_err2string(rc));
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (-1);
+ }
+ }
+#endif
+
+#define DN_LOG_VAL(dict_ldap) \
+ ((dict_ldap)->bind_dn[0] ? (dict_ldap)->bind_dn : "empty or implicit")
+
+ /*
+ * If this server requires a bind, do so. Thanks to Sam Tardieu for
+ * noticing that the original bind call was broken.
+ */
+ if (DICT_LDAP_DO_BIND(dict_ldap)) {
+ if (msg_verbose)
+ msg_info("%s: Binding to server %s with dn %s",
+ myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap));
+
+#if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
+ if (DICT_LDAP_DO_SASL(dict_ldap)) {
+ rc = dict_ldap_bind_sasl(dict_ldap);
+ } else {
+ rc = dict_ldap_bind_st(dict_ldap);
+ }
+#else
+ rc = dict_ldap_bind_st(dict_ldap);
+#endif
+
+ if (rc != LDAP_SUCCESS) {
+ msg_warn("%s: Unable to bind to server %s with dn %s: %d (%s)",
+ myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap),
+ rc, ldap_err2string(rc));
+ DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
+ }
+ if (msg_verbose)
+ msg_info("%s: Successful bind to server %s with dn %s",
+ myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap));
+ }
+ /* Save connection handle in shared container */
+ DICT_LDAP_CONN(dict_ldap)->conn_ld = dict_ldap->ld;
+
+ if (msg_verbose)
+ msg_info("%s: Cached connection handle for LDAP source %s",
+ myname, dict_ldap->parser->name);
+
+ return (0);
+}
+
+/*
+ * Locate or allocate connection cache entry.
+ */
+static void dict_ldap_conn_find(DICT_LDAP *dict_ldap)
+{
+ VSTRING *keybuf = vstring_alloc(10);
+ char *key;
+ int len;
+
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+ int sslon = dict_ldap->start_tls || dict_ldap->ldap_ssl;
+
+#endif
+ LDAP_CONN *conn;
+
+ /*
+ * Join key fields with null characters.
+ */
+#define ADDSTR(vp, s) vstring_memcat((vp), (s), strlen((s))+1)
+#define ADDINT(vp, i) vstring_sprintf_append((vp), "%lu%c", (unsigned long)(i), 0)
+
+ ADDSTR(keybuf, dict_ldap->server_host);
+ ADDINT(keybuf, dict_ldap->server_port);
+ ADDINT(keybuf, dict_ldap->bind);
+ ADDSTR(keybuf, DICT_LDAP_DO_BIND(dict_ldap) ? dict_ldap->bind_dn : "");
+ ADDSTR(keybuf, DICT_LDAP_DO_BIND(dict_ldap) ? dict_ldap->bind_pw : "");
+ ADDINT(keybuf, dict_ldap->dereference);
+ ADDINT(keybuf, dict_ldap->chase_referrals);
+ ADDINT(keybuf, dict_ldap->debuglevel);
+ ADDINT(keybuf, dict_ldap->version);
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+#if defined(USE_LDAP_SASL)
+ ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_mechs : "");
+ ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_realm : "");
+ ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_authz : "");
+ ADDINT(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_minssf : 0);
+#endif
+ ADDINT(keybuf, dict_ldap->ldap_ssl);
+ ADDINT(keybuf, dict_ldap->start_tls);
+ ADDINT(keybuf, sslon ? dict_ldap->tls_require_cert : 0);
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_file : "");
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_dir : "");
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_cert : "");
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_key : "");
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_random_file : "");
+ ADDSTR(keybuf, sslon ? dict_ldap->tls_cipher_suite : "");
+#endif
+
+ key = vstring_str(keybuf);
+ len = VSTRING_LEN(keybuf);
+
+ if (conn_hash == 0)
+ conn_hash = binhash_create(0);
+
+ if ((dict_ldap->ht = binhash_locate(conn_hash, key, len)) == 0) {
+ conn = (LDAP_CONN *) mymalloc(sizeof(LDAP_CONN));
+ conn->conn_ld = 0;
+ conn->conn_refcount = 0;
+ dict_ldap->ht = binhash_enter(conn_hash, key, len, (void *) conn);
+ }
+ ++DICT_LDAP_CONN(dict_ldap)->conn_refcount;
+
+ vstring_free(keybuf);
+}
+
+/* attr_sub_type - Is one of two attributes a sub-type of another */
+
+static int attrdesc_subtype(const char *a1, const char *a2)
+{
+
+ /*
+ * RFC 2251 section 4.1.4: LDAP attribute names are case insensitive
+ */
+ while (*a1 && TOLOWER(*a1) == TOLOWER(*a2))
+ ++a1, ++a2;
+
+ /*
+ * Names equal to end of a1, is a2 equal or a subtype?
+ */
+ if (*a1 == 0 && (*a2 == 0 || *a2 == ';'))
+ return (1);
+
+ /*
+ * Names equal to end of a2, is a1 a subtype?
+ */
+ if (*a2 == 0 && *a1 == ';')
+ return (-1);
+
+ /*
+ * Distinct attributes
+ */
+ return (0);
+}
+
+/* url_attrs - attributes we want from LDAP URL */
+
+static char **url_attrs(DICT_LDAP *dict_ldap, LDAPURLDesc * url)
+{
+ static ARGV *attrs;
+ char **a1;
+ char **a2;
+ int arel;
+
+ /*
+ * If the LDAP URI specified no attributes, all entry attributes are
+ * returned, leading to unnecessarily large LDAP results, particularly
+ * since dynamic groups are most useful for large groups.
+ *
+ * Since we only make use of the various mumble_results attributes, we ask
+ * only for these, thus making large queries much faster.
+ *
+ * In one test case, a query returning 75K users took 16 minutes when all
+ * attributes are returned, and just under 3 minutes with only the
+ * desired result attribute.
+ */
+ if (url->lud_attrs == 0 || *url->lud_attrs == 0)
+ return (dict_ldap->result_attributes->argv);
+
+ /*
+ * When the LDAP URI explicitly specifies a set of attributes, we use the
+ * interaction of the URI attributes and our result attributes. This way
+ * LDAP URIs can hide certain attributes that should not be part of the
+ * query. There is no point in retrieving attributes not listed in our
+ * result set, we won't make any use of those.
+ */
+ if (attrs)
+ argv_truncate(attrs, 0);
+ else
+ attrs = argv_alloc(2);
+
+ /*
+ * Retrieve only those attributes that are of interest to us.
+ *
+ * If the URL attribute and the attribute we want differ only in the
+ * "options" part of the attribute descriptor, select the more specific
+ * attribute descriptor.
+ */
+ for (a1 = url->lud_attrs; *a1; ++a1) {
+ for (a2 = dict_ldap->result_attributes->argv; *a2; ++a2) {
+ arel = attrdesc_subtype(*a1, *a2);
+ if (arel > 0)
+ argv_add(attrs, *a2, ARGV_END);
+ else if (arel < 0)
+ argv_add(attrs, *a1, ARGV_END);
+ }
+ }
+
+ return ((attrs->argc > 0) ? attrs->argv : 0);
+}
+
+/*
+ * dict_ldap_get_values: for each entry returned by a search, get the values
+ * of all its attributes. Recurses to resolve any DN or URL values found.
+ *
+ * This and the rest of the handling of multiple attributes, DNs and URLs
+ * are thanks to LaMont Jones.
+ */
+static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage *res,
+ VSTRING *result, const char *name)
+{
+ static int recursion = 0;
+ static int expansion;
+ long entries = 0;
+ long i = 0;
+ int rc = 0;
+ LDAPMessage *resloop = 0;
+ LDAPMessage *entry = 0;
+ BerElement *ber;
+ char *attr;
+ char **attrs;
+ struct berval **vals;
+ int valcount;
+ LDAPURLDesc *url;
+ const char *myname = "dict_ldap_get_values";
+ int is_leaf = 1; /* No recursion via this entry */
+ int is_terminal = 0; /* No expansion via this entry */
+
+ if (++recursion == 1)
+ expansion = 0;
+
+ if (msg_verbose)
+ msg_info("%s[%d]: Search found %d match(es)", myname, recursion,
+ ldap_count_entries(dict_ldap->ld, res));
+
+ for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL;
+ entry = ldap_next_entry(dict_ldap->ld, entry)) {
+ ber = NULL;
+
+ /*
+ * LDAP should not, but may produce more than the requested maximum
+ * number of entries.
+ */
+ if (dict_ldap->dict.error == 0
+ && dict_ldap->size_limit
+ && ++entries > dict_ldap->size_limit) {
+ msg_warn("%s[%d]: %s: Query size limit (%ld) exceeded",
+ myname, recursion, dict_ldap->parser->name,
+ dict_ldap->size_limit);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ }
+
+ /*
+ * Check for terminal attributes, these preclude expansion of all
+ * other attributes, and DN/URI recursion. Any terminal attributes
+ * are listed first in the attribute array.
+ */
+ if (dict_ldap->num_terminal > 0) {
+ for (i = 0; i < dict_ldap->num_terminal; ++i) {
+ attr = dict_ldap->result_attributes->argv[i];
+ if (!(vals = ldap_get_values_len(dict_ldap->ld, entry, attr)))
+ continue;
+ is_terminal = (ldap_count_values_len(vals) > 0);
+ ldap_value_free_len(vals);
+ if (is_terminal)
+ break;
+ }
+ }
+
+ /*
+ * Check for special attributes, these preclude expansion of
+ * "leaf-only" attributes, and are at the end of the attribute array
+ * after the terminal, leaf and regular attributes.
+ */
+ if (is_terminal == 0 && dict_ldap->num_leaf > 0) {
+ for (i = dict_ldap->num_attributes;
+ dict_ldap->result_attributes->argv[i]; ++i) {
+ attr = dict_ldap->result_attributes->argv[i];
+ if (!(vals = ldap_get_values_len(dict_ldap->ld, entry, attr)))
+ continue;
+ is_leaf = (ldap_count_values_len(vals) == 0);
+ ldap_value_free_len(vals);
+ if (!is_leaf)
+ break;
+ }
+ }
+ for (attr = ldap_first_attribute(dict_ldap->ld, entry, &ber);
+ attr != NULL; ldap_memfree(attr),
+ attr = ldap_next_attribute(dict_ldap->ld, entry, ber)) {
+
+ vals = ldap_get_values_len(dict_ldap->ld, entry, attr);
+ if (vals == NULL) {
+ if (msg_verbose)
+ msg_info("%s[%d]: Entry doesn't have any values for %s",
+ myname, recursion, attr);
+ continue;
+ }
+ valcount = ldap_count_values_len(vals);
+
+ /*
+ * If we previously encountered an error, we still continue
+ * through the loop, to avoid memory leaks, but we don't waste
+ * time accumulating any further results.
+ *
+ * XXX: There may be a more efficient way to exit the loop with no
+ * leaks, but it will likely be more fragile and not worth the
+ * extra code.
+ */
+ if (dict_ldap->dict.error != 0 || valcount == 0) {
+ ldap_value_free_len(vals);
+ continue;
+ }
+
+ /*
+ * The "result_attributes" list enumerates all the requested
+ * attributes, first the ordinary result attributes and then the
+ * special result attributes that hold DN or LDAP URL values.
+ *
+ * The number of ordinary attributes is "num_attributes".
+ *
+ * We compute the attribute type (ordinary or special) from its
+ * index on the "result_attributes" list.
+ */
+ for (i = 0; dict_ldap->result_attributes->argv[i]; i++)
+ if (attrdesc_subtype(dict_ldap->result_attributes->argv[i],
+ attr) > 0)
+ break;
+
+ /*
+ * Append each returned address to the result list, possibly
+ * recursing (for dn or url attributes of non-terminal entries)
+ */
+ if (i < dict_ldap->num_attributes || is_terminal) {
+ if ((is_terminal && i >= dict_ldap->num_terminal)
+ || (!is_leaf &&
+ i < dict_ldap->num_terminal + dict_ldap->num_leaf)) {
+ if (msg_verbose)
+ msg_info("%s[%d]: skipping %d value(s) of %s "
+ "attribute %s", myname, recursion, valcount,
+ is_terminal ? "non-terminal" : "leaf-only",
+ attr);
+ } else {
+ /* Ordinary result attribute */
+ for (i = 0; i < valcount; i++) {
+ if (db_common_expand(dict_ldap->ctx,
+ dict_ldap->result_format,
+ vals[i]->bv_val,
+ name, result, 0)
+ && dict_ldap->expansion_limit > 0
+ && ++expansion > dict_ldap->expansion_limit) {
+ msg_warn("%s[%d]: %s: Expansion limit exceeded "
+ "for key: '%s'", myname, recursion,
+ dict_ldap->parser->name, name);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ break;
+ }
+ }
+ if (dict_ldap->dict.error != 0)
+ continue;
+ if (msg_verbose)
+ msg_info("%s[%d]: search returned %d value(s) for"
+ " requested result attribute %s",
+ myname, recursion, valcount, attr);
+ }
+ } else if (recursion < dict_ldap->recursion_limit
+ && dict_ldap->result_attributes->argv[i]) {
+ /* Special result attribute */
+ for (i = 0; i < valcount; i++) {
+ if (ldap_is_ldap_url(vals[i]->bv_val)) {
+ rc = ldap_url_parse(vals[i]->bv_val, &url);
+ if (rc == 0) {
+ if ((attrs = url_attrs(dict_ldap, url)) != 0) {
+ if (msg_verbose)
+ msg_info("%s[%d]: looking up URL %s",
+ myname, recursion,
+ vals[i]->bv_val);
+ rc = search_st(dict_ldap->ld, url->lud_dn,
+ url->lud_scope,
+ url->lud_filter,
+ attrs, dict_ldap->timeout,
+ &resloop);
+ }
+ ldap_free_urldesc(url);
+ if (attrs == 0) {
+ if (msg_verbose)
+ msg_info("%s[%d]: skipping URL %s: no "
+ "pertinent attributes", myname,
+ recursion, vals[i]->bv_val);
+ continue;
+ }
+ } else {
+ msg_warn("%s[%d]: malformed URL %s: %s(%d)",
+ myname, recursion, vals[i]->bv_val,
+ ldap_err2string(rc), rc);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ break;
+ }
+ } else {
+ if (msg_verbose)
+ msg_info("%s[%d]: looking up DN %s",
+ myname, recursion, vals[i]->bv_val);
+ rc = search_st(dict_ldap->ld, vals[i]->bv_val,
+ LDAP_SCOPE_BASE, "objectclass=*",
+ dict_ldap->result_attributes->argv,
+ dict_ldap->timeout, &resloop);
+ }
+ switch (rc) {
+ case LDAP_SUCCESS:
+ dict_ldap_get_values(dict_ldap, resloop, result, name);
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+
+ /*
+ * Go ahead and treat this as though the DN existed
+ * and just didn't have any result attributes.
+ */
+ msg_warn("%s[%d]: DN %s not found, skipping ", myname,
+ recursion, vals[i]->bv_val);
+ break;
+ default:
+ msg_warn("%s[%d]: search error %d: %s ", myname,
+ recursion, rc, ldap_err2string(rc));
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ break;
+ }
+
+ if (resloop != 0)
+ ldap_msgfree(resloop);
+
+ if (dict_ldap->dict.error != 0)
+ break;
+ }
+ if (msg_verbose && dict_ldap->dict.error == 0)
+ msg_info("%s[%d]: search returned %d value(s) for"
+ " special result attribute %s",
+ myname, recursion, valcount, attr);
+ } else if (recursion >= dict_ldap->recursion_limit
+ && dict_ldap->result_attributes->argv[i]) {
+ msg_warn("%s[%d]: %s: Recursion limit exceeded"
+ " for special attribute %s=%s", myname, recursion,
+ dict_ldap->parser->name, attr, vals[0]->bv_val);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ }
+ ldap_value_free_len(vals);
+ }
+ if (ber)
+ ber_free(ber, 0);
+ }
+
+ if (msg_verbose)
+ msg_info("%s[%d]: Leaving %s", myname, recursion, myname);
+ --recursion;
+}
+
+/* dict_ldap_lookup - find database entry */
+
+static const char *dict_ldap_lookup(DICT *dict, const char *name)
+{
+ const char *myname = "dict_ldap_lookup";
+ DICT_LDAP *dict_ldap = (DICT_LDAP *) dict;
+ LDAPMessage *res = 0;
+ static VSTRING *base;
+ static VSTRING *query;
+ static VSTRING *result;
+ int rc = 0;
+ int sizelimit;
+ int domain_rc;
+
+ dict_ldap->dict.error = 0;
+
+ if (msg_verbose)
+ msg_info("%s: In dict_ldap_lookup", myname);
+
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_ldap->parser->name, name);
+ return (0);
+ }
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * If they specified a domain list for this map, then only search for
+ * addresses in domains on the list. This can significantly reduce the
+ * load on the LDAP server.
+ */
+ if ((domain_rc = db_common_check_domain(dict_ldap->ctx, name)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of key '%s': domain mismatch",
+ myname, dict_ldap->parser->name, name);
+ return (0);
+ }
+ if (domain_rc < 0)
+ DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(base, 10);
+ INIT_VSTR(query, 10);
+ INIT_VSTR(result, 10);
+
+ /*
+ * Because the connection may be shared and invalidated via queries for
+ * another map, update private copy of "ld" from shared connection
+ * container.
+ */
+ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld;
+
+ /*
+ * Connect to the LDAP server, if necessary.
+ */
+ if (dict_ldap->ld == NULL) {
+ if (msg_verbose)
+ msg_info
+ ("%s: No existing connection for LDAP source %s, reopening",
+ myname, dict_ldap->parser->name);
+
+ dict_ldap_connect(dict_ldap);
+
+ /*
+ * if dict_ldap_connect() set dict_ldap->dict.error, abort.
+ */
+ if (dict_ldap->dict.error)
+ return (0);
+ } else if (msg_verbose)
+ msg_info("%s: Using existing connection for LDAP source %s",
+ myname, dict_ldap->parser->name);
+
+ /*
+ * Connection caching, means that the connection handle may have the
+ * wrong size limit. Re-adjust before each query. This is cheap, just
+ * sets a field in the ldap connection handle. We also do this in the
+ * connect code, because we sometimes reconnect (below) in the middle of
+ * a query.
+ */
+ sizelimit = dict_ldap->size_limit ? dict_ldap->size_limit : LDAP_NO_LIMIT;
+ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit)
+ != LDAP_OPT_SUCCESS) {
+ msg_warn("%s: %s: Unable to set query result size limit to %ld.",
+ myname, dict_ldap->parser->name, dict_ldap->size_limit);
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ /*
+ * Expand the search base and query. Skip lookup when the input key lacks
+ * sufficient domain components to satisfy all the requested
+ * %-substitutions.
+ *
+ * When the search base is not static, LDAP_NO_SUCH_OBJECT is expected and
+ * is therefore treated as a non-error: the lookup returns no results
+ * rather than a soft error.
+ */
+ if (!db_common_expand(dict_ldap->ctx, dict_ldap->search_base,
+ name, 0, base, rfc2253_quote)) {
+ if (msg_verbose > 1)
+ msg_info("%s: %s: Empty expansion for %s", myname,
+ dict_ldap->parser->name, dict_ldap->search_base);
+ return (0);
+ }
+ if (!db_common_expand(dict_ldap->ctx, dict_ldap->query,
+ name, 0, query, rfc2254_quote)) {
+ if (msg_verbose > 1)
+ msg_info("%s: %s: Empty expansion for %s", myname,
+ dict_ldap->parser->name, dict_ldap->query);
+ return (0);
+ }
+
+ /*
+ * On to the search.
+ */
+ if (msg_verbose)
+ msg_info("%s: %s: Searching with filter %s", myname,
+ dict_ldap->parser->name, vstring_str(query));
+
+ rc = search_st(dict_ldap->ld, vstring_str(base), dict_ldap->scope,
+ vstring_str(query), dict_ldap->result_attributes->argv,
+ dict_ldap->timeout, &res);
+
+ if (rc == LDAP_SERVER_DOWN) {
+ if (msg_verbose)
+ msg_info("%s: Lost connection for LDAP source %s, reopening",
+ myname, dict_ldap->parser->name);
+
+ dict_ldap_unbind(dict_ldap->ld);
+ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0;
+ dict_ldap_connect(dict_ldap);
+
+ /*
+ * if dict_ldap_connect() set dict_ldap->dict.error, abort.
+ */
+ if (dict_ldap->dict.error)
+ return (0);
+
+ rc = search_st(dict_ldap->ld, vstring_str(base), dict_ldap->scope,
+ vstring_str(query), dict_ldap->result_attributes->argv,
+ dict_ldap->timeout, &res);
+
+ }
+ switch (rc) {
+
+ case LDAP_SUCCESS:
+
+ /*
+ * Search worked; extract the requested result_attribute.
+ */
+
+ dict_ldap_get_values(dict_ldap, res, result, name);
+
+ /*
+ * OpenLDAP's ldap_next_attribute returns a bogus
+ * LDAP_DECODING_ERROR; I'm ignoring that for now.
+ */
+
+ rc = dict_ldap_get_errno(dict_ldap->ld);
+ if (rc != LDAP_SUCCESS && rc != LDAP_DECODING_ERROR)
+ msg_warn
+ ("%s: Had some trouble with entries returned by search: %s",
+ myname, ldap_err2string(rc));
+
+ if (msg_verbose)
+ msg_info("%s: Search returned %s", myname,
+ VSTRING_LEN(result) >
+ 0 ? vstring_str(result) : "nothing");
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+
+ /*
+ * If the search base is input key dependent, then not finding it, is
+ * equivalent to not finding the input key. Sadly, we cannot detect
+ * misconfiguration in this case.
+ */
+ if (dict_ldap->dynamic_base)
+ break;
+
+ msg_warn("%s: %s: Search base '%s' not found: %d: %s",
+ myname, dict_ldap->parser->name,
+ vstring_str(base), rc, ldap_err2string(rc));
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ break;
+
+ default:
+
+ /*
+ * Rats. The search didn't work.
+ */
+ msg_warn("%s: Search error %d: %s ", myname, rc,
+ ldap_err2string(rc));
+
+ /*
+ * Tear down the connection so it gets set up from scratch on the
+ * next lookup.
+ */
+ dict_ldap_unbind(dict_ldap->ld);
+ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0;
+
+ /*
+ * And tell the caller to try again later.
+ */
+ dict_ldap->dict.error = DICT_ERR_RETRY;
+ break;
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (res != 0)
+ ldap_msgfree(res);
+
+ /*
+ * If we had an error, return nothing, Otherwise, return the result, if
+ * any.
+ */
+ return (VSTRING_LEN(result) > 0 && !dict_ldap->dict.error ? vstring_str(result) : 0);
+}
+
+/* dict_ldap_close - disassociate from data base */
+
+static void dict_ldap_close(DICT *dict)
+{
+ const char *myname = "dict_ldap_close";
+ DICT_LDAP *dict_ldap = (DICT_LDAP *) dict;
+ LDAP_CONN *conn = DICT_LDAP_CONN(dict_ldap);
+ BINHASH_INFO *ht = dict_ldap->ht;
+
+ if (--conn->conn_refcount == 0) {
+ if (conn->conn_ld) {
+ if (msg_verbose)
+ msg_info("%s: Closed connection handle for LDAP source %s",
+ myname, dict_ldap->parser->name);
+ dict_ldap_unbind(conn->conn_ld);
+ }
+ binhash_delete(conn_hash, ht->key, ht->key_len, myfree);
+ }
+ cfg_parser_free(dict_ldap->parser);
+ myfree(dict_ldap->server_host);
+ myfree(dict_ldap->search_base);
+ myfree(dict_ldap->query);
+ if (dict_ldap->result_format)
+ myfree(dict_ldap->result_format);
+ argv_free(dict_ldap->result_attributes);
+ myfree(dict_ldap->bind_dn);
+ myfree(dict_ldap->bind_pw);
+ if (dict_ldap->ctx)
+ db_common_free_ctx(dict_ldap->ctx);
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+#if defined(USE_LDAP_SASL)
+ if (DICT_LDAP_DO_SASL(dict_ldap)) {
+ myfree(dict_ldap->sasl_mechs);
+ myfree(dict_ldap->sasl_realm);
+ myfree(dict_ldap->sasl_authz);
+ }
+#endif
+ myfree(dict_ldap->tls_ca_cert_file);
+ myfree(dict_ldap->tls_ca_cert_dir);
+ myfree(dict_ldap->tls_cert);
+ myfree(dict_ldap->tls_key);
+ myfree(dict_ldap->tls_random_file);
+ myfree(dict_ldap->tls_cipher_suite);
+#endif
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_ldap_open - create association with data base */
+
+DICT *dict_ldap_open(const char *ldapsource, int open_flags, int dict_flags)
+{
+ const char *myname = "dict_ldap_open";
+ DICT_LDAP *dict_ldap;
+ VSTRING *url_list;
+ char *s;
+ char *h;
+ char *server_host;
+ char *scope;
+ char *attr;
+ char *bindopt;
+ int tmp;
+ int vendor_version = dict_ldap_vendor_version();
+ CFG_PARSER *parser;
+
+ if (msg_verbose)
+ msg_info("%s: Using LDAP source %s", myname, ldapsource);
+
+ /*
+ * Sanity check.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_LDAP, ldapsource));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((parser = cfg_parser_alloc(ldapsource)) == 0)
+ return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags,
+ "open %s: %m", ldapsource));
+
+ dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource,
+ sizeof(*dict_ldap));
+ dict_ldap->dict.lookup = dict_ldap_lookup;
+ dict_ldap->dict.close = dict_ldap_close;
+ dict_ldap->dict.flags = dict_flags;
+
+ dict_ldap->ld = NULL;
+ dict_ldap->parser = parser;
+
+ server_host = cfg_get_str(dict_ldap->parser, "server_host",
+ "localhost", 1, 0);
+
+ /*
+ * get configured value of "server_port"; default to LDAP_PORT (389)
+ */
+ dict_ldap->server_port =
+ cfg_get_int(dict_ldap->parser, "server_port", LDAP_PORT, 0, 0);
+
+ /*
+ * Define LDAP Protocol Version.
+ */
+ dict_ldap->version = cfg_get_int(dict_ldap->parser, "version", 2, 2, 0);
+ switch (dict_ldap->version) {
+ case 2:
+ dict_ldap->version = LDAP_VERSION2;
+ break;
+ case 3:
+ dict_ldap->version = LDAP_VERSION3;
+ break;
+ default:
+ msg_warn("%s: %s Unknown version %d, using 2.", myname, ldapsource,
+ dict_ldap->version);
+ dict_ldap->version = LDAP_VERSION2;
+ }
+
+#if defined(LDAP_API_FEATURE_X_OPENLDAP)
+ dict_ldap->ldap_ssl = 0;
+#endif
+
+ url_list = vstring_alloc(32);
+ s = server_host;
+ while ((h = mystrtok(&s, CHARS_COMMA_SP)) != NULL) {
+#if defined(LDAP_API_FEATURE_X_OPENLDAP)
+
+ /*
+ * Convert (host, port) pairs to LDAP URLs
+ */
+ if (ldap_is_ldap_url(h)) {
+ LDAPURLDesc *url_desc;
+ int rc;
+
+ if ((rc = ldap_url_parse(h, &url_desc)) != 0) {
+ msg_error("%s: error parsing URL %s: %d: %s; skipping", myname,
+ h, rc, ldap_err2string(rc));
+ continue;
+ }
+ if (strcasecmp(url_desc->lud_scheme, "ldap") != 0 &&
+ dict_ldap->version != LDAP_VERSION3) {
+ msg_warn("%s: URL scheme %s requires protocol version 3", myname,
+ url_desc->lud_scheme);
+ dict_ldap->version = LDAP_VERSION3;
+ }
+ if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0)
+ dict_ldap->ldap_ssl = 1;
+ ldap_free_urldesc(url_desc);
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
+ vstring_strcat(url_list, h);
+ } else {
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
+ if (strrchr(h, ':'))
+ vstring_sprintf_append(url_list, "ldap://%s", h);
+ else
+ vstring_sprintf_append(url_list, "ldap://%s:%d", h,
+ dict_ldap->server_port);
+ }
+#else
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
+ vstring_strcat(url_list, h);
+#endif
+ }
+ VSTRING_TERMINATE(url_list);
+ dict_ldap->server_host = vstring_export(url_list);
+
+#if defined(LDAP_API_FEATURE_X_OPENLDAP)
+
+ /*
+ * With URL scheme, clear port to normalize connection cache key
+ */
+ dict_ldap->server_port = LDAP_PORT;
+ if (msg_verbose)
+ msg_info("%s: %s server_host URL is %s", myname, ldapsource,
+ dict_ldap->server_host);
+#endif
+ myfree(server_host);
+
+ /*
+ * Scope handling thanks to Carsten Hoeger of SuSE.
+ */
+ scope = cfg_get_str(dict_ldap->parser, "scope", "sub", 1, 0);
+
+ if (strcasecmp(scope, "one") == 0) {
+ dict_ldap->scope = LDAP_SCOPE_ONELEVEL;
+ } else if (strcasecmp(scope, "base") == 0) {
+ dict_ldap->scope = LDAP_SCOPE_BASE;
+ } else if (strcasecmp(scope, "sub") == 0) {
+ dict_ldap->scope = LDAP_SCOPE_SUBTREE;
+ } else {
+ msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub",
+ myname, ldapsource, scope);
+ dict_ldap->scope = LDAP_SCOPE_SUBTREE;
+ }
+
+ myfree(scope);
+
+ dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base",
+ "", 0, 0);
+
+ /*
+ * get configured value of "timeout"; default to 10 seconds
+ *
+ * Thanks to Manuel Guesdon for spotting that this wasn't really getting
+ * set.
+ */
+ dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout", 10, 0, 0);
+ dict_ldap->query =
+ cfg_get_str(dict_ldap->parser, "query_filter",
+ "(mailacceptinggeneralid=%s)", 0, 0);
+ if ((dict_ldap->result_format =
+ cfg_get_str(dict_ldap->parser, "result_format", 0, 0, 0)) == 0)
+ dict_ldap->result_format =
+ cfg_get_str(dict_ldap->parser, "result_filter", "%s", 1, 0);
+
+ /*
+ * Must parse all templates before we can use db_common_expand() If data
+ * dependent substitutions are found in the search base, treat
+ * NO_SUCH_OBJECT search errors as a non-matching key, rather than a
+ * fatal run-time error.
+ */
+ dict_ldap->ctx = 0;
+ dict_ldap->dynamic_base =
+ db_common_parse(&dict_ldap->dict, &dict_ldap->ctx,
+ dict_ldap->search_base, 1);
+ if (!db_common_parse(0, &dict_ldap->ctx, dict_ldap->query, 1)) {
+ msg_warn("%s: %s: Fixed query_filter %s is probably useless",
+ myname, ldapsource, dict_ldap->query);
+ }
+ (void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format, 0);
+ db_common_parse_domain(dict_ldap->parser, dict_ldap->ctx);
+
+ /*
+ * Maps that use substring keys should only be used with the full input
+ * key.
+ */
+ if (db_common_dict_partial(dict_ldap->ctx))
+ dict_ldap->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_ldap->dict.flags |= DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_ldap->dict.fold_buf = vstring_alloc(10);
+
+ /* Order matters, first the terminal attributes: */
+ attr = cfg_get_str(dict_ldap->parser, "terminal_result_attribute", "", 0, 0);
+ dict_ldap->result_attributes = argv_split(attr, CHARS_COMMA_SP);
+ dict_ldap->num_terminal = dict_ldap->result_attributes->argc;
+ myfree(attr);
+
+ /* Order matters, next the leaf-only attributes: */
+ attr = cfg_get_str(dict_ldap->parser, "leaf_result_attribute", "", 0, 0);
+ if (*attr)
+ argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
+ dict_ldap->num_leaf =
+ dict_ldap->result_attributes->argc - dict_ldap->num_terminal;
+ myfree(attr);
+
+ /* Order matters, next the regular attributes: */
+ attr = cfg_get_str(dict_ldap->parser, "result_attribute", "maildrop", 0, 0);
+ if (*attr)
+ argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
+ dict_ldap->num_attributes = dict_ldap->result_attributes->argc;
+ myfree(attr);
+
+ /* Order matters, finally the special attributes: */
+ attr = cfg_get_str(dict_ldap->parser, "special_result_attribute", "", 0, 0);
+ if (*attr)
+ argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
+ myfree(attr);
+
+ /*
+ * get configured value of "bind"; default to simple bind
+ */
+ bindopt = cfg_get_str(dict_ldap->parser, "bind", CONFIG_BOOL_YES, 1, 0);
+ dict_ldap->bind = name_code(bindopt_table, NAME_CODE_FLAG_NONE, bindopt);
+ if (dict_ldap->bind < 0)
+ msg_fatal("%s: unsupported parameter value: %s = %s",
+ dict_ldap->parser->name, "bind", bindopt);
+ myfree(bindopt);
+
+ /*
+ * get configured value of "bind_dn"; default to ""
+ */
+ dict_ldap->bind_dn = cfg_get_str(dict_ldap->parser, "bind_dn", "", 0, 0);
+
+ /*
+ * get configured value of "bind_pw"; default to ""
+ */
+ dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0);
+
+ /*
+ * LDAP message caching never worked and is no longer supported.
+ */
+ tmp = cfg_get_bool(dict_ldap->parser, "cache", 0);
+ if (tmp)
+ msg_warn("%s: %s ignoring cache", myname, ldapsource);
+
+ tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0);
+ if (tmp >= 0)
+ msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource);
+
+ tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0);
+ if (tmp >= 0)
+ msg_warn("%s: %s ignoring cache_size", myname, ldapsource);
+
+ dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser,
+ "recursion_limit", 1000, 1, 0);
+
+ /*
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
+ */
+ dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser,
+ "expansion_limit", 0, 0, 0);
+
+ dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit",
+ dict_ldap->expansion_limit, 0, 0);
+
+ /*
+ * Alias dereferencing suggested by Mike Mattice.
+ */
+ dict_ldap->dereference = cfg_get_int(dict_ldap->parser, "dereference",
+ 0, 0, 0);
+ if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) {
+ msg_warn("%s: %s Unrecognized value %d specified for dereference; using 0",
+ myname, ldapsource, dict_ldap->dereference);
+ dict_ldap->dereference = 0;
+ }
+ /* Referral chasing */
+ dict_ldap->chase_referrals = cfg_get_bool(dict_ldap->parser,
+ "chase_referrals", 0);
+
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+#if defined(USE_LDAP_SASL)
+
+ /*
+ * SASL options
+ */
+ if (DICT_LDAP_DO_SASL(dict_ldap)) {
+ dict_ldap->sasl_mechs =
+ cfg_get_str(dict_ldap->parser, "sasl_mechs", "", 0, 0);
+ dict_ldap->sasl_realm =
+ cfg_get_str(dict_ldap->parser, "sasl_realm", "", 0, 0);
+ dict_ldap->sasl_authz =
+ cfg_get_str(dict_ldap->parser, "sasl_authz_id", "", 0, 0);
+ dict_ldap->sasl_minssf =
+ cfg_get_int(dict_ldap->parser, "sasl_minssf", 0, 0, 4096);
+ } else {
+ dict_ldap->sasl_mechs = 0;
+ dict_ldap->sasl_realm = 0;
+ dict_ldap->sasl_authz = 0;
+ }
+#endif
+
+ /*
+ * TLS options
+ */
+ /* get configured value of "start_tls"; default to no */
+ dict_ldap->start_tls = cfg_get_bool(dict_ldap->parser, "start_tls", 0);
+ if (dict_ldap->start_tls) {
+ if (dict_ldap->version < LDAP_VERSION3) {
+ msg_warn("%s: %s start_tls requires protocol version 3",
+ myname, ldapsource);
+ dict_ldap->version = LDAP_VERSION3;
+ }
+ /* Binary incompatibility in the OpenLDAP API from 2.0.11 to 2.0.12 */
+ if (((LDAP_VENDOR_VERSION <= 20011) && !(vendor_version <= 20011))
+ || (!(LDAP_VENDOR_VERSION <= 20011) && (vendor_version <= 20011)))
+ msg_fatal("%s: incompatible TLS support: "
+ "compile-time OpenLDAP version %d, "
+ "run-time OpenLDAP version %d",
+ myname, LDAP_VENDOR_VERSION, vendor_version);
+ }
+ /* get configured value of "tls_require_cert"; default to no */
+ dict_ldap->tls_require_cert =
+ cfg_get_bool(dict_ldap->parser, "tls_require_cert", 0) ?
+ LDAP_OPT_X_TLS_DEMAND : LDAP_OPT_X_TLS_NEVER;
+
+ /* get configured value of "tls_ca_cert_file"; default "" */
+ dict_ldap->tls_ca_cert_file = cfg_get_str(dict_ldap->parser,
+ "tls_ca_cert_file", "", 0, 0);
+
+ /* get configured value of "tls_ca_cert_dir"; default "" */
+ dict_ldap->tls_ca_cert_dir = cfg_get_str(dict_ldap->parser,
+ "tls_ca_cert_dir", "", 0, 0);
+
+ /* get configured value of "tls_cert"; default "" */
+ dict_ldap->tls_cert = cfg_get_str(dict_ldap->parser, "tls_cert",
+ "", 0, 0);
+
+ /* get configured value of "tls_key"; default "" */
+ dict_ldap->tls_key = cfg_get_str(dict_ldap->parser, "tls_key",
+ "", 0, 0);
+
+ /* get configured value of "tls_random_file"; default "" */
+ dict_ldap->tls_random_file = cfg_get_str(dict_ldap->parser,
+ "tls_random_file", "", 0, 0);
+
+ /* get configured value of "tls_cipher_suite"; default "" */
+ dict_ldap->tls_cipher_suite = cfg_get_str(dict_ldap->parser,
+ "tls_cipher_suite", "", 0, 0);
+#endif
+
+ /*
+ * Debug level.
+ */
+#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
+ dict_ldap->debuglevel = cfg_get_int(dict_ldap->parser, "debuglevel",
+ 0, 0, 0);
+#endif
+
+ /*
+ * Find or allocate shared LDAP connection container.
+ */
+ dict_ldap_conn_find(dict_ldap);
+
+ /*
+ * Return the new dict_ldap structure.
+ */
+ dict_ldap->dict.owner = cfg_get_owner(dict_ldap->parser);
+ return (DICT_DEBUG (&dict_ldap->dict));
+}
+
+#endif
diff --git a/src/global/dict_ldap.h b/src/global/dict_ldap.h
new file mode 100644
index 0000000..465766f
--- /dev/null
+++ b/src/global/dict_ldap.h
@@ -0,0 +1,33 @@
+#ifndef _DICT_LDAP_H_INCLUDED_
+#define _DICT_LDAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_ldap 3h
+/* SUMMARY
+/* dictionary manager interface to LDAP maps
+/* SYNOPSIS
+/* #include <dict_ldap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_LDAP "ldap"
+
+extern DICT *dict_ldap_open(const char *, int, int);
+
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/global/dict_memcache.c b/src/global/dict_memcache.c
new file mode 100644
index 0000000..3210d7d
--- /dev/null
+++ b/src/global/dict_memcache.c
@@ -0,0 +1,599 @@
+/*++
+/* NAME
+/* dict_memcache 3
+/* SUMMARY
+/* dictionary interface to memcaches
+/* SYNOPSIS
+/* #include <dict_memcache.h>
+/*
+/* DICT *dict_memcache_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_memcache_open() opens a memcache, providing
+/* a dictionary interface for Postfix key->value mappings.
+/* The result is a pointer to the installed dictionary.
+/*
+/* Configuration parameters are described in memcache_table(5).
+/*
+/* Arguments:
+/* .IP name
+/* The path to the Postfix memcache configuration file.
+/* .IP open_flags
+/* O_RDONLY or O_RDWR. This function ignores flags that don't
+/* specify a read, write or append mode.
+/* .IP dict_flags
+/* See dict_open(3).
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* HISTORY
+/* .ad
+/* .fi
+/* The first memcache client for Postfix was written by Omar
+/* Kilani, and was based on libmemcache. The current
+/* implementation implements the memcache protocol directly,
+/* and bears no resemblance to earlier work.
+/* 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 <string.h>
+#include <ctype.h>
+#include <stdio.h> /* XXX sscanf() */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <auto_clnt.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <cfg_parser.h>
+#include <db_common.h>
+#include <memcache_proto.h>
+
+/* Application-specific. */
+
+#include <dict_memcache.h>
+
+ /*
+ * Structure of one memcache dictionary handle.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ CFG_PARSER *parser; /* common parameter parser */
+ void *dbc_ctxt; /* db_common context */
+ char *key_format; /* query key translation */
+ int timeout; /* client timeout */
+ int mc_ttl; /* memcache update expiration */
+ int mc_flags; /* memcache update flags */
+ int err_pause; /* delay between errors */
+ int max_tries; /* number of tries */
+ int max_line; /* reply line limit */
+ int max_data; /* reply data limit */
+ char *memcache; /* memcache server spec */
+ AUTO_CLNT *clnt; /* memcache client stream */
+ VSTRING *clnt_buf; /* memcache client buffer */
+ VSTRING *key_buf; /* lookup key */
+ VSTRING *res_buf; /* lookup result */
+ int error; /* memcache dict_errno */
+ DICT *backup; /* persistent backup */
+} DICT_MC;
+
+ /*
+ * Memcache option defaults and names.
+ */
+#define DICT_MC_DEF_HOST "localhost"
+#define DICT_MC_DEF_PORT "11211"
+#define DICT_MC_DEF_MEMCACHE "inet:" DICT_MC_DEF_HOST ":" DICT_MC_DEF_PORT
+#define DICT_MC_DEF_KEY_FMT "%s"
+#define DICT_MC_DEF_MC_TTL 3600
+#define DICT_MC_DEF_MC_TIMEOUT 2
+#define DICT_MC_DEF_MC_FLAGS 0
+#define DICT_MC_DEF_MAX_TRY 2
+#define DICT_MC_DEF_MAX_LINE 1024
+#define DICT_MC_DEF_MAX_DATA 10240
+#define DICT_MC_DEF_ERR_PAUSE 1
+
+#define DICT_MC_NAME_MEMCACHE "memcache"
+#define DICT_MC_NAME_BACKUP "backup"
+#define DICT_MC_NAME_KEY_FMT "key_format"
+#define DICT_MC_NAME_MC_TTL "ttl"
+#define DICT_MC_NAME_MC_TIMEOUT "timeout"
+#define DICT_MC_NAME_MC_FLAGS "flags"
+#define DICT_MC_NAME_MAX_TRY "max_try"
+#define DICT_MC_NAME_MAX_LINE "line_size_limit"
+#define DICT_MC_NAME_MAX_DATA "data_size_limit"
+#define DICT_MC_NAME_ERR_PAUSE "retry_pause"
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/*#define msg_verbose 1*/
+
+/* dict_memcache_set - set memcache key/value */
+
+static int dict_memcache_set(DICT_MC *dict_mc, const char *value, int ttl)
+{
+ VSTREAM *fp;
+ int count;
+ size_t data_len = strlen(value);
+
+ /*
+ * Return a permanent error if we can't store this data. This results in
+ * loss of information.
+ */
+ if (data_len > dict_mc->max_data) {
+ msg_warn("database %s:%s: data for key %s is too long (%s=%d) "
+ "-- not stored", DICT_TYPE_MEMCACHE, dict_mc->dict.name,
+ STR(dict_mc->key_buf), DICT_MC_NAME_MAX_DATA,
+ dict_mc->max_data);
+ /* Not stored! */
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL);
+ }
+ for (count = 0; count < dict_mc->max_tries; count++) {
+ if (count > 0)
+ sleep(dict_mc->err_pause);
+ if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) {
+ break;
+ } else if (memcache_printf(fp, "set %s %d %d %ld",
+ STR(dict_mc->key_buf), dict_mc->mc_flags,
+ ttl, (long) data_len) < 0
+ || memcache_fwrite(fp, value, strlen(value)) < 0
+ || memcache_get(fp, dict_mc->clnt_buf,
+ dict_mc->max_line) < 0) {
+ if (count > 0)
+ msg_warn(errno ? "database %s:%s: I/O error: %m" :
+ "database %s:%s: I/O error",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name);
+ } else if (strcmp(STR(dict_mc->clnt_buf), "STORED") != 0) {
+ if (count > 0)
+ msg_warn("database %s:%s: update failed: %.30s",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name,
+ STR(dict_mc->clnt_buf));
+ } else {
+ /* Victory! */
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ }
+ auto_clnt_recover(dict_mc->clnt);
+ }
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_memcache_get - get memcache key/value */
+
+static const char *dict_memcache_get(DICT_MC *dict_mc)
+{
+ VSTREAM *fp;
+ long todo;
+ int count;
+
+ for (count = 0; count < dict_mc->max_tries; count++) {
+ if (count > 0)
+ sleep(dict_mc->err_pause);
+ if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) {
+ break;
+ } else if (memcache_printf(fp, "get %s", STR(dict_mc->key_buf)) < 0
+ || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) {
+ if (count > 0)
+ msg_warn(errno ? "database %s:%s: I/O error: %m" :
+ "database %s:%s: I/O error",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name);
+ } else if (strcmp(STR(dict_mc->clnt_buf), "END") == 0) {
+ /* Not found. */
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, (char *) 0);
+ } else if (sscanf(STR(dict_mc->clnt_buf),
+ "VALUE %*s %*s %ld", &todo) != 1
+ || todo < 0 || todo > dict_mc->max_data) {
+ if (count > 0)
+ msg_warn("%s: unexpected memcache server reply: %.30s",
+ dict_mc->dict.name, STR(dict_mc->clnt_buf));
+ } else if (memcache_fread(fp, dict_mc->res_buf, todo) < 0) {
+ if (count > 0)
+ msg_warn("%s: EOF receiving memcache server reply",
+ dict_mc->dict.name);
+ } else {
+ /* Victory! */
+ if (memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0
+ || strcmp(STR(dict_mc->clnt_buf), "END") != 0)
+ auto_clnt_recover(dict_mc->clnt);
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, STR(dict_mc->res_buf));
+ }
+ auto_clnt_recover(dict_mc->clnt);
+ }
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, (char *) 0);
+}
+
+/* dict_memcache_del - delete memcache key/value */
+
+static int dict_memcache_del(DICT_MC *dict_mc)
+{
+ VSTREAM *fp;
+ int count;
+
+ for (count = 0; count < dict_mc->max_tries; count++) {
+ if (count > 0)
+ sleep(dict_mc->err_pause);
+ if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) {
+ break;
+ } else if (memcache_printf(fp, "delete %s", STR(dict_mc->key_buf)) < 0
+ || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) {
+ if (count > 0)
+ msg_warn(errno ? "database %s:%s: I/O error: %m" :
+ "database %s:%s: I/O error",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name);
+ } else if (strcmp(STR(dict_mc->clnt_buf), "DELETED") == 0) {
+ /* Victory! */
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ } else if (strcmp(STR(dict_mc->clnt_buf), "NOT_FOUND") == 0) {
+ /* Not found! */
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL);
+ } else {
+ if (count > 0)
+ msg_warn("database %s:%s: delete failed: %.30s",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name,
+ STR(dict_mc->clnt_buf));
+ }
+ auto_clnt_recover(dict_mc->clnt);
+ }
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_memcache_prepare_key - prepare lookup key */
+
+static ssize_t dict_memcache_prepare_key(DICT_MC *dict_mc, const char *name)
+{
+
+ /*
+ * Optionally case-fold the search string.
+ */
+ if (dict_mc->dict.flags & DICT_FLAG_FOLD_FIX) {
+ if (dict_mc->dict.fold_buf == 0)
+ dict_mc->dict.fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict_mc->dict.fold_buf, name);
+ name = lowercase(STR(dict_mc->dict.fold_buf));
+ }
+
+ /*
+ * Optionally expand the query key format.
+ */
+#define DICT_MC_NO_KEY (0)
+#define DICT_MC_NO_QUOTING ((void (*)(DICT *, const char *, VSTRING *)) 0)
+
+ if (dict_mc->key_format != 0
+ && strcmp(dict_mc->key_format, DICT_MC_DEF_KEY_FMT) != 0) {
+ VSTRING_RESET(dict_mc->key_buf);
+ if (db_common_expand(dict_mc->dbc_ctxt, dict_mc->key_format,
+ name, DICT_MC_NO_KEY, dict_mc->key_buf,
+ DICT_MC_NO_QUOTING) == 0)
+ return (0);
+ } else {
+ vstring_strcpy(dict_mc->key_buf, name);
+ }
+
+ /*
+ * The length indicates whether the expansion is empty or not.
+ */
+ return (LEN(dict_mc->key_buf));
+}
+
+/* dict_memcache_valid_key - validate key */
+
+static int dict_memcache_valid_key(DICT_MC *dict_mc,
+ const char *name,
+ const char *operation,
+ void (*log_func) (const char *,...))
+{
+ unsigned char *cp;
+ int rc;
+
+#define DICT_MC_SKIP(why) do { \
+ if (msg_verbose || log_func != msg_info) \
+ log_func("%s: skipping %s for name \"%s\": %s", \
+ dict_mc->dict.name, operation, name, (why)); \
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 0); \
+ } while (0)
+
+ if (*name == 0)
+ DICT_MC_SKIP("empty lookup key");
+ if ((rc = db_common_check_domain(dict_mc->dbc_ctxt, name)) == 0)
+ DICT_MC_SKIP("domain mismatch");
+ if (rc < 0)
+ DICT_ERR_VAL_RETURN(dict_mc, rc, 0);
+ if (dict_memcache_prepare_key(dict_mc, name) == 0)
+ DICT_MC_SKIP("empty lookup key expansion");
+ for (cp = (unsigned char *) STR(dict_mc->key_buf); *cp; cp++)
+ if (isascii(*cp) && isspace(*cp))
+ DICT_MC_SKIP("name contains space");
+
+ DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 1);
+}
+
+/* dict_memcache_update - update memcache */
+
+static int dict_memcache_update(DICT *dict, const char *name,
+ const char *value)
+{
+ const char *myname = "dict_memcache_update";
+ DICT_MC *dict_mc = (DICT_MC *) dict;
+ DICT *backup = dict_mc->backup;
+ int upd_res;
+
+ /*
+ * Skip updates with an inapplicable key, noisily. This results in loss
+ * of information.
+ */
+ if (dict_memcache_valid_key(dict_mc, name, "update", msg_warn) == 0)
+ DICT_ERR_VAL_RETURN(dict, dict_mc->error, DICT_STAT_FAIL);
+
+ /*
+ * Update the memcache first.
+ */
+ upd_res = dict_memcache_set(dict_mc, value, dict_mc->mc_ttl);
+ dict->error = dict_mc->error;
+
+ /*
+ * Update the backup database last.
+ */
+ if (backup) {
+ upd_res = backup->update(backup, name, value);
+ dict->error = backup->error;
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: update key \"%s\"(%s) => \"%s\" %s",
+ myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
+ value, dict_mc->error ? "(memcache error)" : (backup
+ && backup->error) ? "(backup error)" : "(no error)");
+
+ return (upd_res);
+}
+
+/* dict_memcache_lookup - lookup memcache */
+
+static const char *dict_memcache_lookup(DICT *dict, const char *name)
+{
+ const char *myname = "dict_memcache_lookup";
+ DICT_MC *dict_mc = (DICT_MC *) dict;
+ DICT *backup = dict_mc->backup;
+ const char *retval;
+
+ /*
+ * Skip lookups with an inapplicable key, silently. This is just asking
+ * for information that cannot exist.
+ */
+ if (dict_memcache_valid_key(dict_mc, name, "lookup", msg_info) == 0)
+ DICT_ERR_VAL_RETURN(dict, dict_mc->error, (char *) 0);
+
+ /*
+ * Search the memcache first.
+ */
+ retval = dict_memcache_get(dict_mc);
+ dict->error = dict_mc->error;
+
+ /*
+ * Search the backup database last. Update the memcache if the data is
+ * found.
+ */
+ if (backup) {
+ backup->error = 0;
+ if (retval == 0) {
+ retval = backup->lookup(backup, name);
+ dict->error = backup->error;
+ /* Update the cache. */
+ if (retval != 0)
+ dict_memcache_set(dict_mc, retval, dict_mc->mc_ttl);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: key \"%s\"(%s) => %s",
+ myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
+ retval ? retval : dict_mc->error ? "(memcache error)" :
+ (backup && backup->error) ? "(backup error)" : "(not found)");
+
+ return (retval);
+}
+
+/* dict_memcache_delete - delete memcache entry */
+
+static int dict_memcache_delete(DICT *dict, const char *name)
+{
+ const char *myname = "dict_memcache_delete";
+ DICT_MC *dict_mc = (DICT_MC *) dict;
+ DICT *backup = dict_mc->backup;
+ int del_res;
+
+ /*
+ * Skip lookups with an inapplicable key, noisily. This is just deleting
+ * information that cannot exist.
+ */
+ if (dict_memcache_valid_key(dict_mc, name, "delete", msg_info) == 0)
+ DICT_ERR_VAL_RETURN(dict, dict_mc->error, dict_mc->error ?
+ DICT_STAT_ERROR : DICT_STAT_FAIL);
+
+ /*
+ * Update the memcache first.
+ */
+ del_res = dict_memcache_del(dict_mc);
+ dict->error = dict_mc->error;
+
+ /*
+ * Update the persistent database last.
+ */
+ if (backup) {
+ del_res = backup->delete(backup, name);
+ dict->error = backup->error;
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: delete key \"%s\"(%s) => %s",
+ myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
+ dict_mc->error ? "(memcache error)" : (backup
+ && backup->error) ? "(backup error)" : "(no error)");
+
+ return (del_res);
+}
+
+/* dict_memcache_sequence - first/next lookup */
+
+static int dict_memcache_sequence(DICT *dict, int function, const char **key,
+ const char **value)
+{
+ const char *myname = "dict_memcache_sequence";
+ DICT_MC *dict_mc = (DICT_MC *) dict;
+ DICT *backup = dict_mc->backup;
+ int seq_res;
+
+ if (backup == 0) {
+ msg_warn("database %s:%s: first/next support requires backup database",
+ DICT_TYPE_MEMCACHE, dict_mc->dict.name);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ } else {
+ seq_res = backup->sequence(backup, function, key, value);
+ if (msg_verbose)
+ msg_info("%s: %s: key \"%s\" => %s",
+ myname, dict_mc->dict.name, *key ? *key : "(not found)",
+ *value ? *value : backup->error ? "(backup error)" :
+ "(not found)");
+ DICT_ERR_VAL_RETURN(dict, backup->error, seq_res);
+ }
+}
+
+/* dict_memcache_close - close memcache */
+
+static void dict_memcache_close(DICT *dict)
+{
+ DICT_MC *dict_mc = (DICT_MC *) dict;
+
+ cfg_parser_free(dict_mc->parser);
+ db_common_free_ctx(dict_mc->dbc_ctxt);
+ if (dict_mc->key_format)
+ myfree(dict_mc->key_format);
+ myfree(dict_mc->memcache);
+ auto_clnt_free(dict_mc->clnt);
+ vstring_free(dict_mc->clnt_buf);
+ vstring_free(dict_mc->key_buf);
+ vstring_free(dict_mc->res_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ if (dict_mc->backup)
+ dict_close(dict_mc->backup);
+ dict_free(dict);
+}
+
+/* dict_memcache_open - open memcache */
+
+DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_MC *dict_mc;
+ char *backup;
+ CFG_PARSER *parser;
+
+ /*
+ * Sanity checks.
+ */
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
+ "%s:%s map is not allowed for security-sensitive data",
+ DICT_TYPE_MEMCACHE, name));
+ open_flags &= (O_RDONLY | O_RDWR | O_WRONLY | O_APPEND);
+ if (open_flags != O_RDONLY && open_flags != O_RDWR)
+ return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY or O_RDWR access mode",
+ DICT_TYPE_MEMCACHE, name));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((parser = cfg_parser_alloc(name)) == 0)
+ return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
+ "open %s: %m", name));
+
+ /*
+ * Create the dictionary object.
+ */
+ dict_mc = (DICT_MC *) dict_alloc(DICT_TYPE_MEMCACHE, name,
+ sizeof(*dict_mc));
+ dict_mc->dict.lookup = dict_memcache_lookup;
+ if (open_flags == O_RDWR) {
+ dict_mc->dict.update = dict_memcache_update;
+ dict_mc->dict.delete = dict_memcache_delete;
+ }
+ dict_mc->dict.sequence = dict_memcache_sequence;
+ dict_mc->dict.close = dict_memcache_close;
+ dict_mc->dict.flags = dict_flags;
+ dict_mc->key_buf = vstring_alloc(10);
+ dict_mc->res_buf = vstring_alloc(10);
+
+ /*
+ * Parse the configuration file.
+ */
+ dict_mc->parser = parser;
+ dict_mc->key_format = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_FMT,
+ DICT_MC_DEF_KEY_FMT, 0, 0);
+ dict_mc->timeout = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TIMEOUT,
+ DICT_MC_DEF_MC_TIMEOUT, 0, 0);
+ dict_mc->mc_ttl = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TTL,
+ DICT_MC_DEF_MC_TTL, 0, 0);
+ dict_mc->mc_flags = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_FLAGS,
+ DICT_MC_DEF_MC_FLAGS, 0, 0);
+ dict_mc->err_pause = cfg_get_int(dict_mc->parser, DICT_MC_NAME_ERR_PAUSE,
+ DICT_MC_DEF_ERR_PAUSE, 1, 0);
+ dict_mc->max_tries = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_TRY,
+ DICT_MC_DEF_MAX_TRY, 1, 0);
+ dict_mc->max_line = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_LINE,
+ DICT_MC_DEF_MAX_LINE, 1, 0);
+ dict_mc->max_data = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_DATA,
+ DICT_MC_DEF_MAX_DATA, 1, 0);
+ dict_mc->memcache = cfg_get_str(dict_mc->parser, DICT_MC_NAME_MEMCACHE,
+ DICT_MC_DEF_MEMCACHE, 0, 0);
+
+ /*
+ * Initialize the memcache client.
+ */
+ dict_mc->clnt = auto_clnt_create(dict_mc->memcache, dict_mc->timeout, 0, 0);
+ dict_mc->clnt_buf = vstring_alloc(100);
+
+ /*
+ * Open the optional backup database.
+ */
+ backup = cfg_get_str(dict_mc->parser, DICT_MC_NAME_BACKUP,
+ (char *) 0, 0, 0);
+ if (backup) {
+ dict_mc->backup = dict_open(backup, open_flags, dict_flags);
+ myfree(backup);
+ } else
+ dict_mc->backup = 0;
+
+ /*
+ * Parse templates and common database parameters. Maps that use
+ * substring keys should only be used with the full input key.
+ */
+ dict_mc->dbc_ctxt = 0;
+ db_common_parse(&dict_mc->dict, &dict_mc->dbc_ctxt,
+ dict_mc->key_format, 1);
+ db_common_parse_domain(dict_mc->parser, dict_mc->dbc_ctxt);
+ if (db_common_dict_partial(dict_mc->dbc_ctxt))
+ /* Breaks recipient delimiters */
+ dict_mc->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_mc->dict.flags |= DICT_FLAG_FIXED;
+
+ dict_mc->dict.flags |= DICT_FLAG_MULTI_WRITER;
+
+ return (&dict_mc->dict);
+}
diff --git a/src/global/dict_memcache.h b/src/global/dict_memcache.h
new file mode 100644
index 0000000..3b6e7a4
--- /dev/null
+++ b/src/global/dict_memcache.h
@@ -0,0 +1,38 @@
+#ifndef _DICT_MEMCACHE_INCLUDED_
+#define _DICT_MEMCACHE_INCLUDED_
+
+/*++
+/* NAME
+/* dict_memcache 3h
+/* SUMMARY
+/* dictionary interface to memcache databases
+/* SYNOPSIS
+/* #include <dict_memcache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_MEMCACHE "memcache"
+
+extern DICT *dict_memcache_open(const char *name, int unused_flags,
+ int dict_flags);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dict_mysql.c b/src/global/dict_mysql.c
new file mode 100644
index 0000000..735e195
--- /dev/null
+++ b/src/global/dict_mysql.c
@@ -0,0 +1,963 @@
+/*++
+/* NAME
+/* dict_mysql 3
+/* SUMMARY
+/* dictionary manager interface to MySQL databases
+/* SYNOPSIS
+/* #include <dict_mysql.h>
+/*
+/* DICT *dict_mysql_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_mysql_open() creates a dictionary of type 'mysql'. This
+/* dictionary is an interface for the postfix key->value mappings
+/* to mysql. The result is a pointer to the installed dictionary,
+/* or a null pointer in case of problems.
+/*
+/* The mysql dictionary can manage multiple connections to different
+/* sql servers on different hosts. It assumes that the underlying data
+/* on each host is identical (mirrored) and maintains one connection
+/* at any given time. If any connection fails, any other available
+/* ones will be opened and used. The intent of this feature is to eliminate
+/* a single point of failure for mail systems that would otherwise rely
+/* on a single mysql server.
+/* .PP
+/* Arguments:
+/* .IP name
+/* Either the path to the MySQL configuration file (if it starts
+/* with '/' or '.'), or the prefix which will be used to obtain
+/* main.cf configuration parameters for this search.
+/*
+/* In the first case, the configuration parameters below are
+/* specified in the file as \fIname\fR=\fIvalue\fR pairs.
+/*
+/* In the second case, the configuration parameters are
+/* prefixed with the value of \fIname\fR and an underscore,
+/* and they are specified in main.cf. For example, if this
+/* value is \fImysqlsource\fR, the parameters would look like
+/* \fImysqlsource_user\fR, \fImysqlsource_table\fR, and so on.
+/*
+/* .IP other_name
+/* reference for outside use.
+/* .IP open_flags
+/* Must be O_RDONLY.
+/* .IP dict_flags
+/* See dict_open(3).
+/* .PP
+/* Configuration parameters:
+/* .IP user
+/* Username for connecting to the database.
+/* .IP password
+/* Password for the above.
+/* .IP dbname
+/* Name of the database.
+/* .IP domain
+/* List of domains the queries should be restricted to. If
+/* specified, only FQDN addresses whose domain parts matching this
+/* list will be queried against the SQL database. Lookups for
+/* partial addresses are also suppressed. This can significantly
+/* reduce the query load on the server.
+/* .IP query
+/* Query template, before the query is actually issued, variable
+/* substitutions are performed. See mysql_table(5) for details. If
+/* No query is specified, the legacy variables \fItable\fR,
+/* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR
+/* are used to construct the query template.
+/* .IP result_format
+/* The format used to expand results from queries. Substitutions
+/* are performed as described in mysql_table(5). Defaults to returning
+/* the lookup result unchanged.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values. Lookups which
+/* exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that each
+/* non-empty (and non-NULL) column of a multi-column result row counts as
+/* one result.
+/* .IP table
+/* When \fIquery\fR is not set, name of the table used to construct the
+/* query string. This provides compatibility with older releases.
+/* .IP select_field
+/* When \fIquery\fR is not set, name of the result field used to
+/* construct the query string. This provides compatibility with older
+/* releases.
+/* .IP where_field
+/* When \fIquery\fR is not set, name of the where clause field used to
+/* construct the query string. This provides compatibility with older
+/* releases.
+/* .IP additional_conditions
+/* When \fIquery\fR is not set, additional where clause conditions used
+/* to construct the query string. This provides compatibility with older
+/* releases.
+/* .IP hosts
+/* List of hosts to connect to.
+/* .IP option_file
+/* Read options from the given file instead of the default my.cnf
+/* location.
+/* .IP option_group
+/* Read options from the given group.
+/* .IP require_result_set
+/* Require that every query produces a result set.
+/* .IP tls_cert_file
+/* File containing client's X509 certificate.
+/* .IP tls_key_file
+/* File containing the private key corresponding to \fItls_cert_file\fR.
+/* .IP tls_CAfile
+/* File containing certificates for all of the X509 Certification
+/* Authorities the client will recognize. Takes precedence over
+/* \fItls_CApath\fR.
+/* .IP tls_CApath
+/* Directory containing X509 Certification Authority certificates
+/* in separate individual files.
+/* .IP tls_verify_cert
+/* Verify that the server's name matches the common name of the
+/* certificate.
+/* .PP
+/* For example, if you want the map to reference databases of
+/* the name "your_db" and execute a query like this: select
+/* forw_addr from aliases where alias like '<some username>'
+/* against any database called "vmailer_info" located on hosts
+/* host1.some.domain and host2.some.domain, logging in as user
+/* "vmailer" and password "passwd" then the configuration file
+/* should read:
+/* .PP
+/* user = vmailer
+/* .br
+/* password = passwd
+/* .br
+/* dbname = vmailer_info
+/* .br
+/* table = aliases
+/* .br
+/* select_field = forw_addr
+/* .br
+/* where_field = alias
+/* .br
+/* hosts = host1.some.domain host2.some.domain
+/* .PP
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Scott Cotton, Joshua Marcus
+/* IC Group, Inc.
+/* scott@icgroup.com
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* John Fawcett
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+#include "sys_defs.h"
+
+#ifdef HAS_MYSQL
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <time.h>
+#include <mysql.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include "dict.h"
+#include "msg.h"
+#include "mymalloc.h"
+#include "argv.h"
+#include "vstring.h"
+#include "split_at.h"
+#include "find_inet.h"
+#include "myrand.h"
+#include "events.h"
+#include "stringops.h"
+
+/* Global library. */
+
+#include "cfg_parser.h"
+#include "db_common.h"
+
+/* Application-specific. */
+
+#include "dict_mysql.h"
+
+/* MySQL 8.x API change */
+
+#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50023
+#define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_VERIFY_SERVER_CERT
+#elif MYSQL_VERSION_ID >= 80000
+#define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_MODE
+#endif
+
+/* need some structs to help organize things */
+typedef struct {
+ MYSQL *db;
+ char *hostname;
+ char *name;
+ unsigned port;
+ unsigned type; /* TYPEUNIX | TYPEINET */
+ unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */
+ time_t ts; /* used for attempting reconnection
+ * every so often if a host is down */
+} HOST;
+
+typedef struct {
+ int len_hosts; /* number of hosts */
+ HOST **db_hosts; /* the hosts on which the databases
+ * reside */
+} PLMYSQL;
+
+typedef struct {
+ DICT dict;
+ CFG_PARSER *parser;
+ char *query;
+ char *result_format;
+ char *option_file;
+ char *option_group;
+ void *ctx;
+ int expansion_limit;
+ char *username;
+ char *password;
+ char *dbname;
+ ARGV *hosts;
+ PLMYSQL *pldb;
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ HOST *active_host;
+ char *tls_cert_file;
+ char *tls_key_file;
+ char *tls_CAfile;
+ char *tls_CApath;
+ char *tls_ciphers;
+#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT)
+ int tls_verify_cert;
+#endif
+#endif
+ int require_result_set;
+} DICT_MYSQL;
+
+#define STATACTIVE (1<<0)
+#define STATFAIL (1<<1)
+#define STATUNTRIED (1<<2)
+
+#define TYPEUNIX (1<<0)
+#define TYPEINET (1<<1)
+
+#define RETRY_CONN_MAX 100
+#define RETRY_CONN_INTV 60 /* 1 minute */
+#define IDLE_CONN_INTV 60 /* 1 minute */
+
+/* internal function declarations */
+static PLMYSQL *plmysql_init(ARGV *);
+static int plmysql_query(DICT_MYSQL *, const char *, VSTRING *, MYSQL_RES **);
+static void plmysql_dealloc(PLMYSQL *);
+static void plmysql_close_host(HOST *);
+static void plmysql_down_host(HOST *);
+static void plmysql_connect_single(DICT_MYSQL *, HOST *);
+static const char *dict_mysql_lookup(DICT *, const char *);
+DICT *dict_mysql_open(const char *, int, int);
+static void dict_mysql_close(DICT *);
+static void mysql_parse_config(DICT_MYSQL *, const char *);
+static HOST *host_init(const char *);
+
+/* dict_mysql_quote - escape SQL metacharacters in input string */
+
+static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
+{
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
+ int len = strlen(name);
+ int buflen;
+
+ /*
+ * We won't get integer overflows in 2*len + 1, because Postfix input
+ * keys have reasonable size limits, better safe than sorry.
+ */
+ if (len > (INT_MAX - VSTRING_LEN(result) - 1) / 2)
+ msg_panic("dict_mysql_quote: integer overflow in %lu+2*%d+1",
+ (unsigned long) VSTRING_LEN(result), len);
+ buflen = 2 * len + 1;
+ VSTRING_SPACE(result, buflen);
+
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ if (dict_mysql->active_host)
+ mysql_real_escape_string(dict_mysql->active_host->db,
+ vstring_end(result), name, len);
+ else
+#endif
+ mysql_escape_string(vstring_end(result), name, len);
+
+ VSTRING_SKIP(result);
+}
+
+/* dict_mysql_lookup - find database entry */
+
+static const char *dict_mysql_lookup(DICT *dict, const char *name)
+{
+ const char *myname = "dict_mysql_lookup";
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
+ MYSQL_RES *query_res;
+ MYSQL_ROW row;
+ static VSTRING *result;
+ static VSTRING *query;
+ int i;
+ int j;
+ int numrows;
+ int expansion;
+ const char *r;
+ db_quote_callback_t quote_func = dict_mysql_quote;
+ int domain_rc;
+
+ dict->error = 0;
+
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+#ifdef SNAPSHOT
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_mysql->parser->name, name);
+ return (0);
+ }
+#endif
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * If there is a domain list for this map, then only search for addresses
+ * in domains on the list. This can significantly reduce the load on the
+ * server.
+ */
+ if ((domain_rc = db_common_check_domain(dict_mysql->ctx, name)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: Skipping lookup of '%s'", myname, name);
+ return (0);
+ }
+ if (domain_rc < 0) {
+ msg_warn("%s:%s 'domain' pattern match failed for '%s'",
+ dict->type, dict->name, name);
+ DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+ }
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(query, 10);
+
+ /*
+ * Suppress the lookup if the query expansion is empty
+ *
+ * This initial expansion is outside the context of any specific host
+ * connection, we just want to check the key pre-requisites, so when
+ * quoting happens separately for each connection, we don't bother with
+ * quoting...
+ */
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ quote_func = 0;
+#endif
+ if (!db_common_expand(dict_mysql->ctx, dict_mysql->query,
+ name, 0, query, quote_func))
+ return (0);
+
+ /* do the query - set dict->error & cleanup if there's an error */
+ if (plmysql_query(dict_mysql, name, query, &query_res) == 0) {
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+ if (query_res == 0)
+ return (0);
+ numrows = mysql_num_rows(query_res);
+ if (msg_verbose)
+ msg_info("%s: retrieved %d rows", myname, numrows);
+ if (numrows == 0) {
+ mysql_free_result(query_res);
+ return 0;
+ }
+ INIT_VSTR(result, 10);
+
+ for (expansion = i = 0; i < numrows && dict->error == 0; i++) {
+ row = mysql_fetch_row(query_res);
+ for (j = 0; j < mysql_num_fields(query_res); j++) {
+ if (db_common_expand(dict_mysql->ctx, dict_mysql->result_format,
+ row[j], name, result, 0)
+ && dict_mysql->expansion_limit > 0
+ && ++expansion > dict_mysql->expansion_limit) {
+ msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
+ myname, dict_mysql->parser->name, name);
+ dict->error = DICT_ERR_RETRY;
+ break;
+ }
+ }
+ }
+ mysql_free_result(query_res);
+ r = vstring_str(result);
+ return ((dict->error == 0 && *r) ? r : 0);
+}
+
+/* dict_mysql_check_stat - check the status of a host */
+
+static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type,
+ time_t t)
+{
+ if ((host->stat & stat) && (!type || host->type & type)) {
+ /* try not to hammer the dead hosts too often */
+ if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t)
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* dict_mysql_find_host - find a host with the given status */
+
+static HOST *dict_mysql_find_host(PLMYSQL *PLDB, unsigned stat, unsigned type)
+{
+ time_t t;
+ int count = 0;
+ int idx;
+ int i;
+
+ t = time((time_t *) 0);
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t))
+ count++;
+ }
+
+ if (count) {
+ idx = (count > 1) ?
+ 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
+
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
+ --idx == 0)
+ return PLDB->db_hosts[i];
+ }
+ }
+ return 0;
+}
+
+/* dict_mysql_get_active - get an active connection */
+
+static HOST *dict_mysql_get_active(DICT_MYSQL *dict_mysql)
+{
+ const char *myname = "dict_mysql_get_active";
+ PLMYSQL *PLDB = dict_mysql->pldb;
+ HOST *host;
+ int count = RETRY_CONN_MAX;
+
+ /* Try the active connections first; prefer the ones to UNIX sockets. */
+ if ((host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL ||
+ (host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL) {
+ if (msg_verbose)
+ msg_info("%s: found active connection to host %s", myname,
+ host->hostname);
+ return host;
+ }
+
+ /*
+ * Try the remaining hosts. "count" is a safety net, in case the loop
+ * takes more than RETRY_CONN_INTV and the dead hosts are no longer
+ * skipped.
+ */
+ while (--count > 0 &&
+ ((host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL,
+ TYPEUNIX)) != NULL ||
+ (host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL,
+ TYPEINET)) != NULL)) {
+ if (msg_verbose)
+ msg_info("%s: attempting to connect to host %s", myname,
+ host->hostname);
+ plmysql_connect_single(dict_mysql, host);
+ if (host->stat == STATACTIVE)
+ return host;
+ }
+
+ /* bad news... */
+ return 0;
+}
+
+/* dict_mysql_event - callback: close idle connections */
+
+static void dict_mysql_event(int unused_event, void *context)
+{
+ HOST *host = (HOST *) context;
+
+ if (host->db)
+ plmysql_close_host(host);
+}
+
+/*
+ * plmysql_query - process a MySQL query. Return 'true' on success.
+ * On failure, log failure and try other db instances.
+ * on failure of all db instances, return 'false';
+ * close unnecessary active connections
+ */
+
+static int plmysql_query(DICT_MYSQL *dict_mysql,
+ const char *name,
+ VSTRING *query,
+ MYSQL_RES **result)
+{
+ HOST *host;
+ MYSQL_RES *first_result = 0;
+ int query_error = 1;
+
+ /*
+ * Helper to avoid spamming the log with warnings.
+ */
+#define SET_ERROR_AND_WARN_ONCE(err, ...) \
+ do { \
+ if (err == 0) { \
+ err = 1; \
+ msg_warn(__VA_ARGS__); \
+ } \
+ } while (0)
+
+ while ((host = dict_mysql_get_active(dict_mysql)) != NULL) {
+
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+
+ /*
+ * The active host is used to escape strings in the context of the
+ * active connection's character encoding.
+ */
+ dict_mysql->active_host = host;
+ VSTRING_RESET(query);
+ VSTRING_TERMINATE(query);
+ db_common_expand(dict_mysql->ctx, dict_mysql->query,
+ name, 0, query, dict_mysql_quote);
+ dict_mysql->active_host = 0;
+#endif
+
+ query_error = 0;
+ errno = 0;
+
+ /*
+ * The query must complete.
+ */
+ if (mysql_query(host->db, vstring_str(query)) != 0) {
+ query_error = 1;
+ msg_warn("%s:%s: query failed: %s",
+ dict_mysql->dict.type, dict_mysql->dict.name,
+ mysql_error(host->db));
+ }
+
+ /*
+ * Collect all result sets to avoid synchronization errors.
+ */
+ else {
+ int next_res_status;
+
+ do {
+ MYSQL_RES *temp_result;
+
+ /*
+ * Keep the first result set. Reject multiple result sets.
+ */
+ if ((temp_result = mysql_store_result(host->db)) != 0) {
+ if (first_result == 0) {
+ first_result = temp_result;
+ } else {
+ SET_ERROR_AND_WARN_ONCE(query_error,
+ "%s:%s: query failed: multiple result sets "
+ "returning data are not supported",
+ dict_mysql->dict.type,
+ dict_mysql->dict.name);
+ mysql_free_result(temp_result);
+ }
+ }
+
+ /*
+ * No result: the mysql_field_count() function must return 0
+ * to indicate that mysql_store_result() completed normally.
+ */
+ else if (mysql_field_count(host->db) != 0) {
+ SET_ERROR_AND_WARN_ONCE(query_error,
+ "%s:%s: query failed (mysql_store_result): %s",
+ dict_mysql->dict.type,
+ dict_mysql->dict.name,
+ mysql_error(host->db));
+ }
+
+ /*
+ * Are there more results? -1 = no, 0 = yes, > 0 = error.
+ */
+ if ((next_res_status = mysql_next_result(host->db)) > 0) {
+ SET_ERROR_AND_WARN_ONCE(query_error,
+ "%s:%s: query failed (mysql_next_result): %s",
+ dict_mysql->dict.type,
+ dict_mysql->dict.name,
+ mysql_error(host->db));
+ }
+ } while (next_res_status == 0);
+
+ /*
+ * Enforce the require_result_set setting.
+ */
+ if (first_result == 0 && dict_mysql->require_result_set) {
+ SET_ERROR_AND_WARN_ONCE(query_error,
+ "%s:%s: query failed: query returned no result set"
+ "(require_result_set = yes)",
+ dict_mysql->dict.type,
+ dict_mysql->dict.name);
+ }
+ }
+
+ /*
+ * See what we got.
+ */
+ if (query_error) {
+ plmysql_down_host(host);
+ if (errno == 0)
+ errno = ENOTSUP;
+ if (first_result) {
+ mysql_free_result(first_result);
+ first_result = 0;
+ }
+ } else {
+ if (msg_verbose)
+ msg_info("%s:%s: successful query result from host %s",
+ dict_mysql->dict.type, dict_mysql->dict.name,
+ host->hostname);
+ event_request_timer(dict_mysql_event, (void *) host,
+ IDLE_CONN_INTV);
+ break;
+ }
+ }
+
+ *result = first_result;
+ return (query_error == 0);
+}
+
+/*
+ * plmysql_connect_single -
+ * used to reconnect to a single database when one is down or none is
+ * connected yet. Log all errors and set the stat field of host accordingly
+ */
+static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
+{
+ if ((host->db = mysql_init(NULL)) == NULL)
+ msg_fatal("dict_mysql: insufficient memory");
+ if (dict_mysql->option_file)
+ mysql_options(host->db, MYSQL_READ_DEFAULT_FILE, dict_mysql->option_file);
+ if (dict_mysql->option_group && dict_mysql->option_group[0])
+ mysql_options(host->db, MYSQL_READ_DEFAULT_GROUP, dict_mysql->option_group);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ if (dict_mysql->tls_key_file || dict_mysql->tls_cert_file ||
+ dict_mysql->tls_CAfile || dict_mysql->tls_CApath || dict_mysql->tls_ciphers)
+ mysql_ssl_set(host->db,
+ dict_mysql->tls_key_file, dict_mysql->tls_cert_file,
+ dict_mysql->tls_CAfile, dict_mysql->tls_CApath,
+ dict_mysql->tls_ciphers);
+#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT)
+ if (dict_mysql->tls_verify_cert != -1)
+ mysql_options(host->db, DICT_MYSQL_SSL_VERIFY_SERVER_CERT,
+ &dict_mysql->tls_verify_cert);
+#endif
+#endif
+ if (mysql_real_connect(host->db,
+ (host->type == TYPEINET ? host->name : 0),
+ dict_mysql->username,
+ dict_mysql->password,
+ dict_mysql->dbname,
+ host->port,
+ (host->type == TYPEUNIX ? host->name : 0),
+ CLIENT_MULTI_RESULTS)) {
+ if (msg_verbose)
+ msg_info("dict_mysql: successful connection to host %s",
+ host->hostname);
+ host->stat = STATACTIVE;
+ } else {
+ msg_warn("connect to mysql server %s: %s",
+ host->hostname, mysql_error(host->db));
+ plmysql_down_host(host);
+ }
+}
+
+/* plmysql_close_host - close an established MySQL connection */
+static void plmysql_close_host(HOST *host)
+{
+ mysql_close(host->db);
+ host->db = 0;
+ host->stat = STATUNTRIED;
+}
+
+/*
+ * plmysql_down_host - close a failed connection AND set a "stay away from
+ * this host" timer
+ */
+static void plmysql_down_host(HOST *host)
+{
+ mysql_close(host->db);
+ host->db = 0;
+ host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
+ host->stat = STATFAIL;
+ event_cancel_timer(dict_mysql_event, (void *) host);
+}
+
+/* mysql_parse_config - parse mysql configuration file */
+
+static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
+{
+ const char *myname = "mysql_parse_config";
+ CFG_PARSER *p = dict_mysql->parser;
+ VSTRING *buf;
+ char *hosts;
+
+ dict_mysql->username = cfg_get_str(p, "user", "", 0, 0);
+ dict_mysql->password = cfg_get_str(p, "password", "", 0, 0);
+ dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
+ dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0);
+ dict_mysql->option_group = cfg_get_str(p, "option_group", "client", 0, 0);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0);
+ dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0);
+ dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0);
+ dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0);
+ dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0);
+#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT)
+ dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1);
+#endif
+#endif
+ dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1);
+
+ /*
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
+ */
+ dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser,
+ "expansion_limit", 0, 0, 0);
+
+ if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) {
+
+ /*
+ * No query specified -- fallback to building it from components (old
+ * style "select %s from %s where %s")
+ */
+ buf = vstring_alloc(64);
+ db_common_sql_build_query(buf, p);
+ dict_mysql->query = vstring_export(buf);
+ }
+
+ /*
+ * Must parse all templates before we can use db_common_expand()
+ */
+ dict_mysql->ctx = 0;
+ (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx,
+ dict_mysql->query, 1);
+ (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0);
+ db_common_parse_domain(p, dict_mysql->ctx);
+
+ /*
+ * Maps that use substring keys should only be used with the full input
+ * key.
+ */
+ if (db_common_dict_partial(dict_mysql->ctx))
+ dict_mysql->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_mysql->dict.flags |= DICT_FLAG_FIXED;
+ if (dict_mysql->dict.flags & DICT_FLAG_FOLD_FIX)
+ dict_mysql->dict.fold_buf = vstring_alloc(10);
+
+ hosts = cfg_get_str(p, "hosts", "", 0, 0);
+
+ dict_mysql->hosts = argv_split(hosts, CHARS_COMMA_SP);
+ if (dict_mysql->hosts->argc == 0) {
+ argv_add(dict_mysql->hosts, "localhost", ARGV_END);
+ argv_terminate(dict_mysql->hosts);
+ if (msg_verbose)
+ msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
+ myname, mysqlcf, dict_mysql->hosts->argv[0]);
+ }
+ myfree(hosts);
+}
+
+/* dict_mysql_open - open MYSQL data base */
+
+DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_MYSQL *dict_mysql;
+ CFG_PARSER *parser;
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_MYSQL, name));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((parser = cfg_parser_alloc(name)) == 0)
+ return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags,
+ "open %s: %m", name));
+
+ dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name,
+ sizeof(DICT_MYSQL));
+ dict_mysql->dict.lookup = dict_mysql_lookup;
+ dict_mysql->dict.close = dict_mysql_close;
+ dict_mysql->dict.flags = dict_flags;
+ dict_mysql->parser = parser;
+ mysql_parse_config(dict_mysql, name);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ dict_mysql->active_host = 0;
+#endif
+ dict_mysql->pldb = plmysql_init(dict_mysql->hosts);
+ if (dict_mysql->pldb == NULL)
+ msg_fatal("couldn't initialize pldb!\n");
+ dict_mysql->dict.owner = cfg_get_owner(dict_mysql->parser);
+ return (DICT_DEBUG (&dict_mysql->dict));
+}
+
+/*
+ * plmysql_init - initialize a MYSQL database.
+ * Return NULL on failure, or a PLMYSQL * on success.
+ */
+static PLMYSQL *plmysql_init(ARGV *hosts)
+{
+ PLMYSQL *PLDB;
+ int i;
+
+ if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == 0)
+ msg_fatal("mymalloc of pldb failed");
+
+ PLDB->len_hosts = hosts->argc;
+ if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc)) == 0)
+ return (0);
+ for (i = 0; i < hosts->argc; i++)
+ PLDB->db_hosts[i] = host_init(hosts->argv[i]);
+
+ return PLDB;
+}
+
+
+/* host_init - initialize HOST structure */
+static HOST *host_init(const char *hostname)
+{
+ const char *myname = "mysql host_init";
+ HOST *host = (HOST *) mymalloc(sizeof(HOST));
+ const char *d = hostname;
+ char *s;
+
+ host->db = 0;
+ host->hostname = mystrdup(hostname);
+ host->port = 0;
+ host->stat = STATUNTRIED;
+ host->ts = 0;
+
+ /*
+ * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where
+ * both "inet:" and ":port" are optional.
+ */
+ if (strncmp(d, "unix:", 5) == 0) {
+ d += 5;
+ host->type = TYPEUNIX;
+ } else {
+ if (strncmp(d, "inet:", 5) == 0)
+ d += 5;
+ host->type = TYPEINET;
+ }
+ host->name = mystrdup(d);
+ if ((s = split_at_right(host->name, ':')) != 0)
+ host->port = ntohs(find_inet_port(s, "tcp"));
+ if (strcasecmp(host->name, "localhost") == 0) {
+ /* The MySQL way: this will actually connect over the UNIX socket */
+ myfree(host->name);
+ host->name = 0;
+ host->type = TYPEUNIX;
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: host=%s, port=%d, type=%s", myname,
+ host->name ? host->name : "localhost",
+ host->port, host->type == TYPEUNIX ? "unix" : "inet");
+ return host;
+}
+
+/* dict_mysql_close - close MYSQL database */
+
+static void dict_mysql_close(DICT *dict)
+{
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
+
+ plmysql_dealloc(dict_mysql->pldb);
+ cfg_parser_free(dict_mysql->parser);
+ myfree(dict_mysql->username);
+ myfree(dict_mysql->password);
+ myfree(dict_mysql->dbname);
+ myfree(dict_mysql->query);
+ myfree(dict_mysql->result_format);
+ if (dict_mysql->option_file)
+ myfree(dict_mysql->option_file);
+ if (dict_mysql->option_group)
+ myfree(dict_mysql->option_group);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ if (dict_mysql->tls_key_file)
+ myfree(dict_mysql->tls_key_file);
+ if (dict_mysql->tls_cert_file)
+ myfree(dict_mysql->tls_cert_file);
+ if (dict_mysql->tls_CAfile)
+ myfree(dict_mysql->tls_CAfile);
+ if (dict_mysql->tls_CApath)
+ myfree(dict_mysql->tls_CApath);
+ if (dict_mysql->tls_ciphers)
+ myfree(dict_mysql->tls_ciphers);
+#endif
+ if (dict_mysql->hosts)
+ argv_free(dict_mysql->hosts);
+ if (dict_mysql->ctx)
+ db_common_free_ctx(dict_mysql->ctx);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* plmysql_dealloc - free memory associated with PLMYSQL close databases */
+static void plmysql_dealloc(PLMYSQL *PLDB)
+{
+ int i;
+
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ event_cancel_timer(dict_mysql_event, (void *) (PLDB->db_hosts[i]));
+ if (PLDB->db_hosts[i]->db)
+ mysql_close(PLDB->db_hosts[i]->db);
+ myfree(PLDB->db_hosts[i]->hostname);
+ if (PLDB->db_hosts[i]->name)
+ myfree(PLDB->db_hosts[i]->name);
+ myfree((void *) PLDB->db_hosts[i]);
+ }
+ myfree((void *) PLDB->db_hosts);
+ myfree((void *) (PLDB));
+}
+
+#endif
diff --git a/src/global/dict_mysql.h b/src/global/dict_mysql.h
new file mode 100644
index 0000000..7fe559b
--- /dev/null
+++ b/src/global/dict_mysql.h
@@ -0,0 +1,40 @@
+#ifndef _DICT_MYSQL_H_INCLUDED_
+#define _DICT_MYSQL_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_mysql 3h
+/* SUMMARY
+/* dictionary manager interface to mysql databases
+/* SYNOPSIS
+/* #include <dict_mysql.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_MYSQL "mysql"
+
+extern DICT *dict_mysql_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Scott Cotton
+/* IC Group, Inc.
+/* scott@icgroup.com
+/*
+/* Joshua Marcus
+/* IC Group, Inc.
+/* josh@icgroup.com
+/*--*/
+
+#endif
diff --git a/src/global/dict_pgsql.c b/src/global/dict_pgsql.c
new file mode 100644
index 0000000..8eac256
--- /dev/null
+++ b/src/global/dict_pgsql.c
@@ -0,0 +1,924 @@
+/*++
+/* NAME
+/* dict_pgsql 3
+/* SUMMARY
+/* dictionary manager interface to PostgreSQL databases
+/* SYNOPSIS
+/* #include <dict_pgsql.h>
+/*
+/* DICT *dict_pgsql_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_pgsql_open() creates a dictionary of type 'pgsql'. This
+/* dictionary is an interface for the postfix key->value mappings
+/* to pgsql. The result is a pointer to the installed dictionary,
+/* or a null pointer in case of problems.
+/*
+/* The pgsql dictionary can manage multiple connections to
+/* different sql servers for the same database. It assumes that
+/* the underlying data on each server is identical (mirrored) and
+/* maintains one connection at any given time. If any connection
+/* fails, any other available ones will be opened and used.
+/* The intent of this feature is to eliminate a single point of
+/* failure for mail systems that would otherwise rely on a single
+/* pgsql server.
+/* .PP
+/* Arguments:
+/* .IP name
+/* Either the path to the PostgreSQL configuration file (if it
+/* starts with '/' or '.'), or the prefix which will be used to
+/* obtain main.cf configuration parameters for this search.
+/*
+/* In the first case, the configuration parameters below are
+/* specified in the file as \fIname\fR=\fIvalue\fR pairs.
+/*
+/* In the second case, the configuration parameters are
+/* prefixed with the value of \fIname\fR and an underscore,
+/* and they are specified in main.cf. For example, if this
+/* value is \fIpgsqlsource\fR, the parameters would look like
+/* \fIpgsqlsource_user\fR, \fIpgsqlsource_table\fR, and so on.
+/* .IP other_name
+/* reference for outside use.
+/* .IP open_flags
+/* Must be O_RDONLY.
+/* .IP dict_flags
+/* See dict_open(3).
+/*
+/* .PP
+/* Configuration parameters:
+/* .IP user
+/* Username for connecting to the database.
+/* .IP password
+/* Password for the above.
+/* .IP dbname
+/* Name of the database.
+/* .IP query
+/* Query template. If not defined a default query template is constructed
+/* from the legacy \fIselect_function\fR or failing that the \fItable\fR,
+/* \fIselect_field\fR, \fIwhere_field\fR, and \fIadditional_conditions\fR
+/* parameters. Before the query is issues, variable substitutions are
+/* performed. See pgsql_table(5).
+/* .IP domain
+/* List of domains the queries should be restricted to. If
+/* specified, only FQDN addresses whose domain parts matching this
+/* list will be queried against the SQL database. Lookups for
+/* partial addresses are also suppressed. This can significantly
+/* reduce the query load on the server.
+/* .IP result_format
+/* The format used to expand results from queries. Substitutions
+/* are performed as described in pgsql_table(5). Defaults to returning
+/* the lookup result unchanged.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values. Lookups which
+/* exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that each
+/* non-empty (and non-NULL) column of a multi-column result row counts as
+/* one result.
+/* .IP select_function
+/* When \fIquery\fR is not defined, the function to be used instead of
+/* the default query based on the legacy \fItable\fR, \fIselect_field\fR,
+/* \fIwhere_field\fR, and \fIadditional_conditions\fR parameters.
+/* .IP table
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* FROM table used to construct the default query template, see pgsql_table(5).
+/* .IP select_field
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* SELECT field used to construct the default query template, see pgsql_table(5).
+/* .IP where_field
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* WHERE field used to construct the default query template, see pgsql_table(5).
+/* .IP additional_conditions
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* additional text to add to the WHERE field in the default query template (this
+/* usually begins with "and") see pgsql_table(5).
+/* .IP hosts
+/* List of hosts to connect to.
+/* .PP
+/* For example, if you want the map to reference databases of
+/* the name "your_db" and execute a query like this: select
+/* forw_addr from aliases where alias like '<some username>'
+/* against any database called "postfix_info" located on hosts
+/* host1.some.domain and host2.some.domain, logging in as user
+/* "postfix" and password "passwd" then the configuration file
+/* should read:
+/* .PP
+/* user = postfix
+/* .br
+/* password = passwd
+/* .br
+/* dbname = postfix_info
+/* .br
+/* table = aliases
+/* .br
+/* select_field = forw_addr
+/* .br
+/* where_field = alias
+/* .br
+/* hosts = host1.some.domain host2.some.domain
+/* .PP
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Aaron Sethman
+/* androsyn@ratbox.org
+/*
+/* Based upon dict_mysql.c by
+/*
+/* Scott Cotton
+/* IC Group, Inc.
+/* scott@icgroup.com
+/*
+/* Joshua Marcus
+/* IC Group, Inc.
+/* josh@icgroup.com
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+#ifdef HAS_PGSQL
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <postgres_ext.h>
+#include <libpq-fe.h>
+
+/* Utility library. */
+
+#include "dict.h"
+#include "msg.h"
+#include "mymalloc.h"
+#include "argv.h"
+#include "vstring.h"
+#include "split_at.h"
+#include "myrand.h"
+#include "events.h"
+#include "stringops.h"
+
+/* Global library. */
+
+#include "cfg_parser.h"
+#include "db_common.h"
+
+/* Application-specific. */
+
+#include "dict_pgsql.h"
+
+#define STATACTIVE (1<<0)
+#define STATFAIL (1<<1)
+#define STATUNTRIED (1<<2)
+
+#define TYPEUNIX (1<<0)
+#define TYPEINET (1<<1)
+#define TYPECONNSTRING (1<<2)
+
+#define RETRY_CONN_MAX 100
+#define RETRY_CONN_INTV 60 /* 1 minute */
+#define IDLE_CONN_INTV 60 /* 1 minute */
+
+typedef struct {
+ PGconn *db;
+ char *hostname;
+ char *name;
+ char *port;
+ unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING */
+ unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */
+ time_t ts; /* used for attempting reconnection */
+} HOST;
+
+typedef struct {
+ int len_hosts; /* number of hosts */
+ HOST **db_hosts; /* hosts on which databases reside */
+} PLPGSQL;
+
+typedef struct {
+ DICT dict;
+ CFG_PARSER *parser;
+ char *query;
+ char *result_format;
+ void *ctx;
+ int expansion_limit;
+ char *username;
+ char *password;
+ char *dbname;
+ char *table;
+ ARGV *hosts;
+ PLPGSQL *pldb;
+ HOST *active_host;
+} DICT_PGSQL;
+
+
+/* Just makes things a little easier for me.. */
+#define PGSQL_RES PGresult
+
+/* internal function declarations */
+static PLPGSQL *plpgsql_init(ARGV *);
+static PGSQL_RES *plpgsql_query(DICT_PGSQL *, const char *, VSTRING *, char *,
+ char *, char *);
+static void plpgsql_dealloc(PLPGSQL *);
+static void plpgsql_close_host(HOST *);
+static void plpgsql_down_host(HOST *);
+static void plpgsql_connect_single(HOST *, char *, char *, char *);
+static const char *dict_pgsql_lookup(DICT *, const char *);
+DICT *dict_pgsql_open(const char *, int, int);
+static void dict_pgsql_close(DICT *);
+static HOST *host_init(const char *);
+
+/* dict_pgsql_quote - escape SQL metacharacters in input string */
+
+static void dict_pgsql_quote(DICT *dict, const char *name, VSTRING *result)
+{
+ DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
+ HOST *active_host = dict_pgsql->active_host;
+ char *myname = "dict_pgsql_quote";
+ size_t len = strlen(name);
+ size_t buflen;
+ int err = 1;
+
+ if (active_host == 0)
+ msg_panic("%s: bogus dict_pgsql->active_host", myname);
+
+ /*
+ * We won't get arithmetic overflows in 2*len + 1, because Postfix input
+ * keys have reasonable size limits, better safe than sorry.
+ */
+ if (len > (SSIZE_T_MAX - VSTRING_LEN(result) - 1) / 2)
+ msg_panic("%s: arithmetic overflow in %lu+2*%lu+1",
+ myname, (unsigned long) VSTRING_LEN(result),
+ (unsigned long) len);
+ buflen = 2 * len + 1;
+
+ /*
+ * XXX Workaround: stop further processing when PQescapeStringConn()
+ * (below) fails. A more proper fix requires invasive changes, not
+ * suitable for a stable release.
+ */
+ if (active_host->stat == STATFAIL)
+ return;
+
+ /*
+ * Escape the input string, using PQescapeStringConn(), because the older
+ * PQescapeString() is not safe anymore, as stated by the documentation.
+ *
+ * From current libpq (8.1.4) documentation:
+ *
+ * PQescapeStringConn writes an escaped version of the from string to the to
+ * buffer, escaping special characters so that they cannot cause any
+ * harm, and adding a terminating zero byte.
+ *
+ * ...
+ *
+ * The parameter from points to the first character of the string that is to
+ * be escaped, and the length parameter gives the number of bytes in this
+ * string. A terminating zero byte is not required, and should not be
+ * counted in length.
+ *
+ * ...
+ *
+ * (The parameter) to shall point to a buffer that is able to hold at least
+ * one more byte than twice the value of length, otherwise the behavior
+ * is undefined.
+ *
+ * ...
+ *
+ * If the error parameter is not NULL, then *error is set to zero on
+ * success, nonzero on error ... The output string is still generated on
+ * error, but it can be expected that the server will reject it as
+ * malformed. On error, a suitable message is stored in the conn object,
+ * whether or not error is NULL.
+ */
+ VSTRING_SPACE(result, buflen);
+ PQescapeStringConn(active_host->db, vstring_end(result), name, len, &err);
+ if (err == 0) {
+ VSTRING_SKIP(result);
+ } else {
+
+ /*
+ * PQescapeStringConn() failed. According to the docs, we still have
+ * a valid, null-terminated output string, but we need not rely on
+ * this behavior.
+ */
+ msg_warn("dict pgsql: (host %s) cannot escape input string: %s",
+ active_host->hostname, PQerrorMessage(active_host->db));
+ active_host->stat = STATFAIL;
+ VSTRING_TERMINATE(result);
+ }
+}
+
+/* dict_pgsql_lookup - find database entry */
+
+static const char *dict_pgsql_lookup(DICT *dict, const char *name)
+{
+ const char *myname = "dict_pgsql_lookup";
+ PGSQL_RES *query_res;
+ DICT_PGSQL *dict_pgsql;
+ static VSTRING *query;
+ static VSTRING *result;
+ int i;
+ int j;
+ int numrows;
+ int numcols;
+ int expansion;
+ const char *r;
+ int domain_rc;
+
+ dict_pgsql = (DICT_PGSQL *) dict;
+
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(query, 10);
+ INIT_VSTR(result, 10);
+
+ dict->error = 0;
+
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+#ifdef SNAPSHOT
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_pgsql->parser->name, name);
+ return (0);
+ }
+#endif
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * If there is a domain list for this map, then only search for addresses
+ * in domains on the list. This can significantly reduce the load on the
+ * server.
+ */
+ if ((domain_rc = db_common_check_domain(dict_pgsql->ctx, name)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: Skipping lookup of '%s'", myname, name);
+ return (0);
+ }
+ if (domain_rc < 0)
+ DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+
+ /*
+ * Suppress the actual lookup if the expansion is empty.
+ *
+ * This initial expansion is outside the context of any specific host
+ * connection, we just want to check the key pre-requisites, so when
+ * quoting happens separately for each connection, we don't bother with
+ * quoting...
+ */
+ if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
+ name, 0, query, 0))
+ return (0);
+
+ /* do the query - set dict->error & cleanup if there's an error */
+ if ((query_res = plpgsql_query(dict_pgsql, name, query,
+ dict_pgsql->dbname,
+ dict_pgsql->username,
+ dict_pgsql->password)) == 0) {
+ dict->error = DICT_ERR_RETRY;
+ return 0;
+ }
+ numrows = PQntuples(query_res);
+ if (msg_verbose)
+ msg_info("%s: retrieved %d rows", myname, numrows);
+ if (numrows == 0) {
+ PQclear(query_res);
+ return 0;
+ }
+ numcols = PQnfields(query_res);
+
+ for (expansion = i = 0; i < numrows && dict->error == 0; i++) {
+ for (j = 0; j < numcols; j++) {
+ r = PQgetvalue(query_res, i, j);
+ if (db_common_expand(dict_pgsql->ctx, dict_pgsql->result_format,
+ r, name, result, 0)
+ && dict_pgsql->expansion_limit > 0
+ && ++expansion > dict_pgsql->expansion_limit) {
+ msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
+ myname, dict_pgsql->parser->name, name);
+ dict->error = DICT_ERR_RETRY;
+ break;
+ }
+ }
+ }
+ PQclear(query_res);
+ r = vstring_str(result);
+ return ((dict->error == 0 && *r) ? r : 0);
+}
+
+/* dict_pgsql_check_stat - check the status of a host */
+
+static int dict_pgsql_check_stat(HOST *host, unsigned stat, unsigned type,
+ time_t t)
+{
+ if ((host->stat & stat) && (!type || host->type & type)) {
+ /* try not to hammer the dead hosts too often */
+ if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t)
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* dict_pgsql_find_host - find a host with the given status */
+
+static HOST *dict_pgsql_find_host(PLPGSQL *PLDB, unsigned stat, unsigned type)
+{
+ time_t t;
+ int count = 0;
+ int idx;
+ int i;
+
+ t = time((time_t *) 0);
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t))
+ count++;
+ }
+
+ if (count) {
+ idx = (count > 1) ?
+ 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
+
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
+ --idx == 0)
+ return PLDB->db_hosts[i];
+ }
+ }
+ return 0;
+}
+
+/* dict_pgsql_get_active - get an active connection */
+
+static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname,
+ char *username, char *password)
+{
+ const char *myname = "dict_pgsql_get_active";
+ HOST *host;
+ int count = RETRY_CONN_MAX;
+
+ /* try the active connections first; prefer the ones to UNIX sockets */
+ if ((host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL ||
+ (host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL ||
+ (host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPECONNSTRING)) != NULL) {
+ if (msg_verbose)
+ msg_info("%s: found active connection to host %s", myname,
+ host->hostname);
+ return host;
+ }
+
+ /*
+ * Try the remaining hosts. "count" is a safety net, in case the loop
+ * takes more than RETRY_CONN_INTV and the dead hosts are no longer
+ * skipped.
+ */
+ while (--count > 0 &&
+ ((host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
+ TYPEUNIX)) != NULL ||
+ (host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
+ TYPEINET)) != NULL ||
+ (host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
+ TYPECONNSTRING)) != NULL)) {
+ if (msg_verbose)
+ msg_info("%s: attempting to connect to host %s", myname,
+ host->hostname);
+ plpgsql_connect_single(host, dbname, username, password);
+ if (host->stat == STATACTIVE)
+ return host;
+ }
+
+ /* bad news... */
+ return 0;
+}
+
+/* dict_pgsql_event - callback: close idle connections */
+
+static void dict_pgsql_event(int unused_event, void *context)
+{
+ HOST *host = (HOST *) context;
+
+ if (host->db)
+ plpgsql_close_host(host);
+}
+
+/*
+ * plpgsql_query - process a PostgreSQL query. Return PGSQL_RES* on success.
+ * On failure, log failure and try other db instances.
+ * on failure of all db instances, return 0;
+ * close unnecessary active connections
+ */
+
+static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
+ const char *name,
+ VSTRING *query,
+ char *dbname,
+ char *username,
+ char *password)
+{
+ PLPGSQL *PLDB = dict_pgsql->pldb;
+ HOST *host;
+ PGSQL_RES *res = 0;
+ ExecStatusType status;
+
+ while ((host = dict_pgsql_get_active(PLDB, dbname, username, password)) != NULL) {
+
+ /*
+ * The active host is used to escape strings in the context of the
+ * active connection's character encoding.
+ */
+ dict_pgsql->active_host = host;
+ VSTRING_RESET(query);
+ VSTRING_TERMINATE(query);
+ db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
+ name, 0, query, dict_pgsql_quote);
+ dict_pgsql->active_host = 0;
+
+ /* Check for potential dict_pgsql_quote() failure. */
+ if (host->stat == STATFAIL) {
+ plpgsql_down_host(host);
+ continue;
+ }
+
+ /*
+ * Submit a command to the server. Be paranoid when processing the
+ * result set: try to enumerate every successful case, and reject
+ * everything else.
+ *
+ * From PostgreSQL 8.1.4 docs: (PQexec) returns a PGresult pointer or
+ * possibly a null pointer. A non-null pointer will generally be
+ * returned except in out-of-memory conditions or serious errors such
+ * as inability to send the command to the server.
+ */
+ if ((res = PQexec(host->db, vstring_str(query))) != 0) {
+
+ /*
+ * XXX Because non-null result pointer does not imply success, we
+ * need to check the command's result status.
+ *
+ * Section 28.3.1: A result of status PGRES_NONFATAL_ERROR will
+ * never be returned directly by PQexec or other query execution
+ * functions; results of this kind are instead passed to the
+ * notice processor.
+ *
+ * PGRES_EMPTY_QUERY is being sent by the server when the query
+ * string is empty. The sanity-checking done by the Postfix
+ * infrastructure makes this case impossible, so we need not
+ * handle this situation explicitly.
+ */
+ switch ((status = PQresultStatus(res))) {
+ case PGRES_TUPLES_OK:
+ case PGRES_COMMAND_OK:
+ /* Success. */
+ if (msg_verbose)
+ msg_info("dict_pgsql: successful query from host %s",
+ host->hostname);
+ event_request_timer(dict_pgsql_event, (void *) host,
+ IDLE_CONN_INTV);
+ return (res);
+ case PGRES_FATAL_ERROR:
+ msg_warn("pgsql query failed: fatal error from host %s: %s",
+ host->hostname, PQresultErrorMessage(res));
+ break;
+ case PGRES_BAD_RESPONSE:
+ msg_warn("pgsql query failed: protocol error, host %s",
+ host->hostname);
+ break;
+ default:
+ msg_warn("pgsql query failed: unknown code 0x%lx from host %s",
+ (unsigned long) status, host->hostname);
+ break;
+ }
+ } else {
+
+ /*
+ * This driver treats null pointers like fatal, non-null result
+ * pointer errors, as suggested by the PostgreSQL 8.1.4
+ * documentation.
+ */
+ msg_warn("pgsql query failed: fatal error from host %s: %s",
+ host->hostname, PQerrorMessage(host->db));
+ }
+
+ /*
+ * XXX An error occurred. Clean up memory and skip this connection.
+ */
+ if (res != 0)
+ PQclear(res);
+ plpgsql_down_host(host);
+ }
+
+ return (0);
+}
+
+/*
+ * plpgsql_connect_single -
+ * used to reconnect to a single database when one is down or none is
+ * connected yet. Log all errors and set the stat field of host accordingly
+ */
+static void plpgsql_connect_single(HOST *host, char *dbname, char *username, char *password)
+{
+ if (host->type == TYPECONNSTRING) {
+ host->db = PQconnectdb(host->name);
+ } else {
+ host->db = PQsetdbLogin(host->name, host->port, NULL, NULL,
+ dbname, username, password);
+ }
+ if (host->db == NULL || PQstatus(host->db) != CONNECTION_OK) {
+ msg_warn("connect to pgsql server %s: %s",
+ host->hostname, PQerrorMessage(host->db));
+ plpgsql_down_host(host);
+ return;
+ }
+ if (msg_verbose)
+ msg_info("dict_pgsql: successful connection to host %s",
+ host->hostname);
+
+ /*
+ * The only legitimate encodings for Internet mail are ASCII and UTF-8.
+ */
+#ifdef SNAPSHOT
+ if (PQsetClientEncoding(host->db, "UTF8") != 0) {
+ msg_warn("dict_pgsql: cannot set the encoding to UTF8, skipping %s",
+ host->hostname);
+ plpgsql_down_host(host);
+ return;
+ }
+#else
+
+ /*
+ * XXX Postfix does not send multi-byte characters. The following piece
+ * of code is an explicit statement of this fact, and the database server
+ * should not accept multi-byte information after this point.
+ */
+ if (PQsetClientEncoding(host->db, "LATIN1") != 0) {
+ msg_warn("dict_pgsql: cannot set the encoding to LATIN1, skipping %s",
+ host->hostname);
+ plpgsql_down_host(host);
+ return;
+ }
+#endif
+ /* Success. */
+ host->stat = STATACTIVE;
+}
+
+/* plpgsql_close_host - close an established PostgreSQL connection */
+
+static void plpgsql_close_host(HOST *host)
+{
+ if (host->db)
+ PQfinish(host->db);
+ host->db = 0;
+ host->stat = STATUNTRIED;
+}
+
+/*
+ * plpgsql_down_host - close a failed connection AND set a "stay away from
+ * this host" timer.
+ */
+static void plpgsql_down_host(HOST *host)
+{
+ if (host->db)
+ PQfinish(host->db);
+ host->db = 0;
+ host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
+ host->stat = STATFAIL;
+ event_cancel_timer(dict_pgsql_event, (void *) host);
+}
+
+/* pgsql_parse_config - parse pgsql configuration file */
+
+static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
+{
+ const char *myname = "pgsql_parse_config";
+ CFG_PARSER *p = dict_pgsql->parser;
+ char *hosts;
+ VSTRING *query;
+ char *select_function;
+
+ dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
+ dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
+ dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
+
+ /*
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
+ */
+ dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser,
+ "expansion_limit", 0, 0, 0);
+
+ if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) {
+
+ /*
+ * No query specified -- fallback to building it from components (
+ * old style "select %s from %s where %s" )
+ */
+ query = vstring_alloc(64);
+ select_function = cfg_get_str(p, "select_function", 0, 0, 0);
+ if (select_function != 0) {
+ vstring_sprintf(query, "SELECT %s('%%s')", select_function);
+ myfree(select_function);
+ } else
+ db_common_sql_build_query(query, p);
+ dict_pgsql->query = vstring_export(query);
+ }
+
+ /*
+ * Must parse all templates before we can use db_common_expand()
+ */
+ dict_pgsql->ctx = 0;
+ (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
+ dict_pgsql->query, 1);
+ (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
+ db_common_parse_domain(p, dict_pgsql->ctx);
+
+ /*
+ * Maps that use substring keys should only be used with the full input
+ * key.
+ */
+ if (db_common_dict_partial(dict_pgsql->ctx))
+ dict_pgsql->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_pgsql->dict.flags |= DICT_FLAG_FIXED;
+ if (dict_pgsql->dict.flags & DICT_FLAG_FOLD_FIX)
+ dict_pgsql->dict.fold_buf = vstring_alloc(10);
+
+ hosts = cfg_get_str(p, "hosts", "", 0, 0);
+
+ dict_pgsql->hosts = argv_split(hosts, CHARS_COMMA_SP);
+ if (dict_pgsql->hosts->argc == 0) {
+ argv_add(dict_pgsql->hosts, "localhost", ARGV_END);
+ argv_terminate(dict_pgsql->hosts);
+ if (msg_verbose)
+ msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
+ myname, pgsqlcf, dict_pgsql->hosts->argv[0]);
+ }
+ myfree(hosts);
+}
+
+/* dict_pgsql_open - open PGSQL data base */
+
+DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_PGSQL *dict_pgsql;
+ CFG_PARSER *parser;
+
+ /*
+ * Sanity check.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_PGSQL, name));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((parser = cfg_parser_alloc(name)) == 0)
+ return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
+ "open %s: %m", name));
+
+ dict_pgsql = (DICT_PGSQL *) dict_alloc(DICT_TYPE_PGSQL, name,
+ sizeof(DICT_PGSQL));
+ dict_pgsql->dict.lookup = dict_pgsql_lookup;
+ dict_pgsql->dict.close = dict_pgsql_close;
+ dict_pgsql->dict.flags = dict_flags;
+ dict_pgsql->parser = parser;
+ pgsql_parse_config(dict_pgsql, name);
+ dict_pgsql->active_host = 0;
+ dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
+ if (dict_pgsql->pldb == NULL)
+ msg_fatal("couldn't initialize pldb!\n");
+ dict_pgsql->dict.owner = cfg_get_owner(dict_pgsql->parser);
+ return (DICT_DEBUG (&dict_pgsql->dict));
+}
+
+/* plpgsql_init - initialize a PGSQL database */
+
+static PLPGSQL *plpgsql_init(ARGV *hosts)
+{
+ PLPGSQL *PLDB;
+ int i;
+
+ PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL));
+ PLDB->len_hosts = hosts->argc;
+ PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc);
+ for (i = 0; i < hosts->argc; i++)
+ PLDB->db_hosts[i] = host_init(hosts->argv[i]);
+
+ return PLDB;
+}
+
+
+/* host_init - initialize HOST structure */
+
+static HOST *host_init(const char *hostname)
+{
+ const char *myname = "pgsql host_init";
+ HOST *host = (HOST *) mymalloc(sizeof(HOST));
+ const char *d = hostname;
+
+ host->db = 0;
+ host->hostname = mystrdup(hostname);
+ host->stat = STATUNTRIED;
+ host->ts = 0;
+
+ /*
+ * Modern syntax: "postgresql://connection-info".
+ */
+ if (strncmp(d, "postgresql:", 11) == 0) {
+ host->type = TYPECONNSTRING;
+ host->name = mystrdup(d);
+ host->port = 0;
+ }
+
+ /*
+ * Historical syntax: "unix:/pathname" and "inet:host:port". Strip the
+ * "unix:" and "inet:" prefixes. Look at the first character, which is
+ * how PgSQL historically distinguishes between UNIX and INET.
+ */
+ else {
+ if (strncmp(d, "unix:", 5) == 0 || strncmp(d, "inet:", 5) == 0)
+ d += 5;
+ host->name = mystrdup(d);
+ if (host->name[0] && host->name[0] != '/') {
+ host->type = TYPEINET;
+ host->port = split_at_right(host->name, ':');
+ } else {
+ host->type = TYPEUNIX;
+ host->port = 0;
+ }
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: host=%s, port=%s, type=%s", myname, host->name,
+ host->port ? host->port : "",
+ host->type == TYPEUNIX ? "unix" :
+ host->type == TYPEINET ? "inet" :
+ "uri");
+ return host;
+}
+
+/* dict_pgsql_close - close PGSQL data base */
+
+static void dict_pgsql_close(DICT *dict)
+{
+ DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
+
+ plpgsql_dealloc(dict_pgsql->pldb);
+ cfg_parser_free(dict_pgsql->parser);
+ myfree(dict_pgsql->username);
+ myfree(dict_pgsql->password);
+ myfree(dict_pgsql->dbname);
+ myfree(dict_pgsql->query);
+ myfree(dict_pgsql->result_format);
+ if (dict_pgsql->hosts)
+ argv_free(dict_pgsql->hosts);
+ if (dict_pgsql->ctx)
+ db_common_free_ctx(dict_pgsql->ctx);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* plpgsql_dealloc - free memory associated with PLPGSQL close databases */
+
+static void plpgsql_dealloc(PLPGSQL *PLDB)
+{
+ int i;
+
+ for (i = 0; i < PLDB->len_hosts; i++) {
+ event_cancel_timer(dict_pgsql_event, (void *) (PLDB->db_hosts[i]));
+ if (PLDB->db_hosts[i]->db)
+ PQfinish(PLDB->db_hosts[i]->db);
+ myfree(PLDB->db_hosts[i]->hostname);
+ myfree(PLDB->db_hosts[i]->name);
+ myfree((void *) PLDB->db_hosts[i]);
+ }
+ myfree((void *) PLDB->db_hosts);
+ myfree((void *) (PLDB));
+}
+
+#endif
diff --git a/src/global/dict_pgsql.h b/src/global/dict_pgsql.h
new file mode 100644
index 0000000..f597a27
--- /dev/null
+++ b/src/global/dict_pgsql.h
@@ -0,0 +1,41 @@
+#ifndef _DICT_PGSQL_INCLUDED_
+#define _DICT_PGSQL_INCLUDED_
+
+/*++
+/* NAME
+/* dict_pgsql 3h
+/* SUMMARY
+/* dictionary manager interface to Postgresql files
+/* SYNOPSIS
+/* #include <dict_pgsql.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PGSQL "pgsql"
+
+extern DICT *dict_pgsql_open(const char *name, int unused_flags, int dict_flags);
+
+/* AUTHOR(S)
+/* Aaron Sethman
+/* androsyn@ratbox.org
+/*
+/* Based upon dict_mysql.c by
+/*
+/* Scott Cotton
+/* IC Group, Inc.
+/* scott@icgroup.com
+/*
+/* Joshua Marcus
+/* IC Group, Inc.
+/* josh@icgroup.com
+/*--*/
+
+#endif
diff --git a/src/global/dict_proxy.c b/src/global/dict_proxy.c
new file mode 100644
index 0000000..5aa9153
--- /dev/null
+++ b/src/global/dict_proxy.c
@@ -0,0 +1,532 @@
+/*++
+/* NAME
+/* dict_proxy 3
+/* SUMMARY
+/* generic dictionary proxy client
+/* SYNOPSIS
+/* #include <dict_proxy.h>
+/*
+/* DICT *dict_proxy_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_proxy_open() relays read-only or read-write operations
+/* through the Postfix proxymap server.
+/*
+/* The \fIopen_flags\fR argument must specify O_RDONLY
+/* or O_RDWR. Depending on this, the client
+/* connects to the proxymap multiserver or to the
+/* proxywrite single updater.
+/*
+/* The connection to the Postfix proxymap server is automatically
+/* closed after $ipc_idle seconds of idle time, or after $ipc_ttl
+/* seconds of activity.
+/* SECURITY
+/* The proxy map server is not meant to be a trusted process. Proxy
+/* maps must not be used to look up security sensitive information
+/* such as user/group IDs, output files, or external commands.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* clnt_stream(3) client endpoint connection management
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unimplemented operation,
+/* bad request parameter, map not approved for proxy access.
+/* 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>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <attr.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <clnt_stream.h>
+#include <dict_proxy.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ CLNT_STREAM *clnt; /* client handle (shared) */
+ const char *service; /* service name */
+ int inst_flags; /* saved dict flags */
+ VSTRING *reskey; /* result key storage */
+ VSTRING *result; /* storage */
+} DICT_PROXY;
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define VSTREQ(v,s) (strcmp(STR(v),s) == 0)
+
+ /*
+ * All proxied maps of the same type share the same query/reply socket.
+ */
+static CLNT_STREAM *proxymap_stream; /* read-only maps */
+static CLNT_STREAM *proxywrite_stream; /* read-write maps */
+
+/* dict_proxy_handshake - receive server protocol announcement */
+
+static int dict_proxy_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP),
+ ATTR_TYPE_END));
+}
+
+/* dict_proxy_sequence - find first/next entry */
+
+static int dict_proxy_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_proxy_sequence";
+ DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
+ VSTREAM *stream;
+ int status;
+ int count = 0;
+ int request_flags;
+
+ /*
+ * The client and server live in separate processes that may start and
+ * terminate independently. We cannot rely on a persistent connection,
+ * let alone on persistent state (such as a specific open table) that is
+ * associated with a specific connection. Each lookup needs to specify
+ * the table and the flags that were specified to dict_proxy_open().
+ */
+ VSTRING_RESET(dict_proxy->reskey);
+ VSTRING_TERMINATE(dict_proxy->reskey);
+ VSTRING_RESET(dict_proxy->result);
+ VSTRING_TERMINATE(dict_proxy->result);
+ request_flags = dict_proxy->inst_flags
+ | (dict->flags & DICT_FLAG_RQST_MASK);
+ for (;;) {
+ stream = clnt_stream_access(dict_proxy->clnt);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_SEQUENCE),
+ SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
+ SEND_ATTR_INT(MAIL_ATTR_FUNC, function),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_KEY, dict_proxy->reskey),
+ RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result),
+ ATTR_TYPE_END) != 3) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("%s: service %s: %m", myname, dict_proxy->service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: table=%s flags=%s func=%d -> status=%d key=%s val=%s",
+ myname, dict->name, dict_flags_str(request_flags),
+ function, status, STR(dict_proxy->reskey),
+ STR(dict_proxy->result));
+ switch (status) {
+ case PROXY_STAT_BAD:
+ msg_fatal("%s sequence failed for table \"%s\" function %d: "
+ "invalid request",
+ dict_proxy->service, dict->name, function);
+ case PROXY_STAT_DENY:
+ msg_fatal("%s service is not configured for table \"%s\"",
+ dict_proxy->service, dict->name);
+ case PROXY_STAT_OK:
+ *key = STR(dict_proxy->reskey);
+ *value = STR(dict_proxy->result);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ case PROXY_STAT_NOKEY:
+ *key = *value = 0;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ case PROXY_STAT_RETRY:
+ *key = *value = 0;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+ case PROXY_STAT_CONFIG:
+ *key = *value = 0;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR);
+ default:
+ msg_warn("%s sequence failed for table \"%s\" function %d: "
+ "unexpected reply status %d",
+ dict_proxy->service, dict->name, function, status);
+ }
+ }
+ clnt_stream_recover(dict_proxy->clnt);
+ sleep(1); /* XXX make configurable */
+ }
+}
+
+/* dict_proxy_lookup - find table entry */
+
+static const char *dict_proxy_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_proxy_lookup";
+ DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
+ VSTREAM *stream;
+ int status;
+ int count = 0;
+ int request_flags;
+
+ /*
+ * The client and server live in separate processes that may start and
+ * terminate independently. We cannot rely on a persistent connection,
+ * let alone on persistent state (such as a specific open table) that is
+ * associated with a specific connection. Each lookup needs to specify
+ * the table and the flags that were specified to dict_proxy_open().
+ */
+ VSTRING_RESET(dict_proxy->result);
+ VSTRING_TERMINATE(dict_proxy->result);
+ request_flags = dict_proxy->inst_flags
+ | (dict->flags & DICT_FLAG_RQST_MASK);
+ for (;;) {
+ stream = clnt_stream_access(dict_proxy->clnt);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_LOOKUP),
+ SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
+ SEND_ATTR_STR(MAIL_ATTR_KEY, key),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result),
+ ATTR_TYPE_END) != 2) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("%s: service %s: %m", myname, dict_proxy->service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: table=%s flags=%s key=%s -> status=%d result=%s",
+ myname, dict->name,
+ dict_flags_str(request_flags), key,
+ status, STR(dict_proxy->result));
+ switch (status) {
+ case PROXY_STAT_BAD:
+ msg_fatal("%s lookup failed for table \"%s\" key \"%s\": "
+ "invalid request",
+ dict_proxy->service, dict->name, key);
+ case PROXY_STAT_DENY:
+ msg_fatal("%s service is not configured for table \"%s\"",
+ dict_proxy->service, dict->name);
+ case PROXY_STAT_OK:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_proxy->result));
+ case PROXY_STAT_NOKEY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, (char *) 0);
+ case PROXY_STAT_RETRY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0);
+ case PROXY_STAT_CONFIG:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, (char *) 0);
+ default:
+ msg_warn("%s lookup failed for table \"%s\" key \"%s\": "
+ "unexpected reply status %d",
+ dict_proxy->service, dict->name, key, status);
+ }
+ }
+ clnt_stream_recover(dict_proxy->clnt);
+ sleep(1); /* XXX make configurable */
+ }
+}
+
+/* dict_proxy_update - update table entry */
+
+static int dict_proxy_update(DICT *dict, const char *key, const char *value)
+{
+ const char *myname = "dict_proxy_update";
+ DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
+ VSTREAM *stream;
+ int status;
+ int count = 0;
+ int request_flags;
+
+ /*
+ * The client and server live in separate processes that may start and
+ * terminate independently. We cannot rely on a persistent connection,
+ * let alone on persistent state (such as a specific open table) that is
+ * associated with a specific connection. Each lookup needs to specify
+ * the table and the flags that were specified to dict_proxy_open().
+ */
+ request_flags = dict_proxy->inst_flags
+ | (dict->flags & DICT_FLAG_RQST_MASK);
+ for (;;) {
+ stream = clnt_stream_access(dict_proxy->clnt);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_UPDATE),
+ SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
+ SEND_ATTR_STR(MAIL_ATTR_KEY, key),
+ SEND_ATTR_STR(MAIL_ATTR_VALUE, value),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("%s: service %s: %m", myname, dict_proxy->service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: table=%s flags=%s key=%s value=%s -> status=%d",
+ myname, dict->name, dict_flags_str(request_flags),
+ key, value, status);
+ switch (status) {
+ case PROXY_STAT_BAD:
+ msg_fatal("%s update failed for table \"%s\" key \"%s\": "
+ "invalid request",
+ dict_proxy->service, dict->name, key);
+ case PROXY_STAT_DENY:
+ msg_fatal("%s update access is not configured for table \"%s\"",
+ dict_proxy->service, dict->name);
+ case PROXY_STAT_OK:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ case PROXY_STAT_NOKEY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ case PROXY_STAT_RETRY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+ case PROXY_STAT_CONFIG:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR);
+ default:
+ msg_warn("%s update failed for table \"%s\" key \"%s\": "
+ "unexpected reply status %d",
+ dict_proxy->service, dict->name, key, status);
+ }
+ }
+ clnt_stream_recover(dict_proxy->clnt);
+ sleep(1); /* XXX make configurable */
+ }
+}
+
+/* dict_proxy_delete - delete table entry */
+
+static int dict_proxy_delete(DICT *dict, const char *key)
+{
+ const char *myname = "dict_proxy_delete";
+ DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
+ VSTREAM *stream;
+ int status;
+ int count = 0;
+ int request_flags;
+
+ /*
+ * The client and server live in separate processes that may start and
+ * terminate independently. We cannot rely on a persistent connection,
+ * let alone on persistent state (such as a specific open table) that is
+ * associated with a specific connection. Each lookup needs to specify
+ * the table and the flags that were specified to dict_proxy_open().
+ */
+ request_flags = dict_proxy->inst_flags
+ | (dict->flags & DICT_FLAG_RQST_MASK);
+ for (;;) {
+ stream = clnt_stream_access(dict_proxy->clnt);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_DELETE),
+ SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
+ SEND_ATTR_STR(MAIL_ATTR_KEY, key),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno !=
+ ENOENT))
+ msg_warn("%s: service %s: %m", myname, dict_proxy->service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: table=%s flags=%s key=%s -> status=%d",
+ myname, dict->name, dict_flags_str(request_flags),
+ key, status);
+ switch (status) {
+ case PROXY_STAT_BAD:
+ msg_fatal("%s delete failed for table \"%s\" key \"%s\": "
+ "invalid request",
+ dict_proxy->service, dict->name, key);
+ case PROXY_STAT_DENY:
+ msg_fatal("%s update access is not configured for table \"%s\"",
+ dict_proxy->service, dict->name);
+ case PROXY_STAT_OK:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ case PROXY_STAT_NOKEY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ case PROXY_STAT_RETRY:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+ case PROXY_STAT_CONFIG:
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR);
+ default:
+ msg_warn("%s delete failed for table \"%s\" key \"%s\": "
+ "unexpected reply status %d",
+ dict_proxy->service, dict->name, key, status);
+ }
+ }
+ clnt_stream_recover(dict_proxy->clnt);
+ sleep(1); /* XXX make configurable */
+ }
+}
+
+/* dict_proxy_close - disconnect */
+
+static void dict_proxy_close(DICT *dict)
+{
+ DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
+
+ vstring_free(dict_proxy->reskey);
+ vstring_free(dict_proxy->result);
+ dict_free(dict);
+}
+
+/* dict_proxy_open - open remote map */
+
+DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags)
+{
+ const char *myname = "dict_proxy_open";
+ DICT_PROXY *dict_proxy;
+ VSTREAM *stream;
+ int server_flags;
+ int status;
+ const char *service;
+ char *relative_path;
+ char *kludge = 0;
+ char *prefix;
+ CLNT_STREAM **pstream;
+
+ /*
+ * If this map can't be proxied then we silently do a direct open. This
+ * allows sites to benefit from proxying the virtual mailbox maps without
+ * unnecessary pain.
+ */
+ if (dict_flags & DICT_FLAG_NO_PROXY)
+ return (dict_open(map, open_flags, dict_flags));
+
+ /*
+ * Use a shared stream for proxied table lookups of the same type.
+ *
+ * XXX A complete implementation would also allow O_RDWR without O_CREAT.
+ * But we must not pass on every possible set of flags to the proxy
+ * server; only sets that make sense. For now, the flags are passed
+ * implicitly by choosing between the proxymap or proxywrite service.
+ *
+ * XXX Use absolute pathname to make this work from non-daemon processes.
+ */
+ if (open_flags == O_RDONLY) {
+ pstream = &proxymap_stream;
+ service = var_proxymap_service;
+ } else if ((open_flags & O_RDWR) == O_RDWR) {
+ pstream = &proxywrite_stream;
+ service = var_proxywrite_service;
+ } else
+ msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR mode",
+ map, DICT_TYPE_PROXY);
+
+ if (*pstream == 0) {
+ relative_path = concatenate(MAIL_CLASS_PRIVATE "/",
+ service, (char *) 0);
+ if (access(relative_path, F_OK) == 0)
+ prefix = MAIL_CLASS_PRIVATE;
+ else
+ prefix = kludge = concatenate(var_queue_dir, "/",
+ MAIL_CLASS_PRIVATE, (char *) 0);
+ *pstream = clnt_stream_create(prefix, service, var_ipc_idle_limit,
+ var_ipc_ttl_limit,
+ dict_proxy_handshake);
+ if (kludge)
+ myfree(kludge);
+ myfree(relative_path);
+ }
+
+ /*
+ * Local initialization.
+ */
+ dict_proxy = (DICT_PROXY *)
+ dict_alloc(DICT_TYPE_PROXY, map, sizeof(*dict_proxy));
+ dict_proxy->dict.lookup = dict_proxy_lookup;
+ dict_proxy->dict.update = dict_proxy_update;
+ dict_proxy->dict.delete = dict_proxy_delete;
+ dict_proxy->dict.sequence = dict_proxy_sequence;
+ dict_proxy->dict.close = dict_proxy_close;
+ dict_proxy->inst_flags = (dict_flags & DICT_FLAG_INST_MASK);
+ dict_proxy->reskey = vstring_alloc(10);
+ dict_proxy->result = vstring_alloc(10);
+ dict_proxy->clnt = *pstream;
+ dict_proxy->service = service;
+
+ /*
+ * Establish initial contact and get the map type specific flags.
+ *
+ * XXX Should retrieve flags from local instance.
+ */
+ for (;;) {
+ stream = clnt_stream_access(dict_proxy->clnt);
+ errno = 0;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_OPEN),
+ SEND_ATTR_STR(MAIL_ATTR_TABLE, dict_proxy->dict.name),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict_proxy->inst_flags),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags),
+ ATTR_TYPE_END) != 2) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("%s: service %s: %m", myname, dict_proxy->service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: connect to map=%s status=%d server_flags=%s",
+ myname, dict_proxy->dict.name, status,
+ dict_flags_str(server_flags));
+ switch (status) {
+ case PROXY_STAT_BAD:
+ msg_fatal("%s open failed for table \"%s\": invalid request",
+ dict_proxy->service, dict_proxy->dict.name);
+ case PROXY_STAT_DENY:
+ msg_fatal("%s service is not configured for table \"%s\"",
+ dict_proxy->service, dict_proxy->dict.name);
+ case PROXY_STAT_OK:
+ dict_proxy->dict.flags = (dict_flags & ~DICT_FLAG_IMPL_MASK)
+ | (server_flags & DICT_FLAG_IMPL_MASK);
+ return (DICT_DEBUG (&dict_proxy->dict));
+ default:
+ msg_warn("%s open failed for table \"%s\": unexpected status %d",
+ dict_proxy->service, dict_proxy->dict.name, status);
+ }
+ }
+ clnt_stream_recover(dict_proxy->clnt);
+ sleep(1); /* XXX make configurable */
+ }
+}
diff --git a/src/global/dict_proxy.h b/src/global/dict_proxy.h
new file mode 100644
index 0000000..a5b7924
--- /dev/null
+++ b/src/global/dict_proxy.h
@@ -0,0 +1,53 @@
+#ifndef _DICT_PROXY_H_INCLUDED_
+#define _DICT_PROXY_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_proxy 3h
+/* SUMMARY
+/* dictionary manager interface to PROXY maps
+/* SYNOPSIS
+/* #include <dict_proxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PROXY "proxy"
+
+extern DICT *dict_proxy_open(const char *, int, int);
+
+ /*
+ * Protocol interface.
+ */
+#define PROXY_REQ_OPEN "open"
+#define PROXY_REQ_LOOKUP "lookup"
+#define PROXY_REQ_UPDATE "update"
+#define PROXY_REQ_DELETE "delete"
+#define PROXY_REQ_SEQUENCE "sequence"
+
+#define PROXY_STAT_OK 0 /* operation succeeded */
+#define PROXY_STAT_NOKEY 1 /* requested key not found */
+#define PROXY_STAT_RETRY 2 /* try lookup again later */
+#define PROXY_STAT_BAD 3 /* invalid request parameter */
+#define PROXY_STAT_DENY 4 /* table not approved for proxying */
+#define PROXY_STAT_CONFIG 5 /* DICT_ERR_CONFIG error */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dict_sqlite.c b/src/global/dict_sqlite.c
new file mode 100644
index 0000000..106731a
--- /dev/null
+++ b/src/global/dict_sqlite.c
@@ -0,0 +1,349 @@
+/*++
+/* NAME
+/* dict_sqlite 3
+/* SUMMARY
+/* dictionary manager interface to SQLite3 databases
+/* SYNOPSIS
+/* #include <dict_sqlite.h>
+/*
+/* DICT *dict_sqlite_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sqlite_open() creates a dictionary of type 'sqlite'.
+/* This dictionary is an interface for the postfix key->value
+/* mappings to SQLite. The result is a pointer to the installed
+/* dictionary.
+/* .PP
+/* Arguments:
+/* .IP name
+/* Either the path to the SQLite configuration file (if it
+/* starts with '/' or '.'), or the prefix which will be used
+/* to obtain main.cf configuration parameters for this search.
+/*
+/* In the first case, the configuration parameters below are
+/* specified in the file as \fIname\fR=\fIvalue\fR pairs.
+/*
+/* In the second case, the configuration parameters are prefixed
+/* with the value of \fIname\fR and an underscore, and they
+/* are specified in main.cf. For example, if this value is
+/* \fIsqlitecon\fR, the parameters would look like
+/* \fIsqlitecon_dbpath\fR, \fIsqlitecon_query\fR, and so on.
+/* .IP open_flags
+/* Must be O_RDONLY.
+/* .IP dict_flags
+/* See dict_open(3).
+/* .PP
+/* Configuration parameters:
+/* .IP dbpath
+/* Path to SQLite database
+/* .IP query
+/* Query template. Before the query is actually issued, variable
+/* substitutions are performed. See sqlite_table(5) for details.
+/* .IP result_format
+/* The format used to expand results from queries. Substitutions
+/* are performed as described in sqlite_table(5). Defaults to
+/* returning the lookup result unchanged.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values.
+/* Lookups which exceed the limit fail with dict->error=DICT_ERR_RETRY.
+/* Note that each non-empty (and non-NULL) column of a
+/* multi-column result row counts as one result.
+/* .IP "select_field, where_field, additional_conditions"
+/* Legacy query interface.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Axel Steiner
+/* ast@treibsand.com
+/*
+/* Adopted and updated by:
+/* 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>
+
+#ifdef HAS_SQLITE
+#include <sqlite3.h>
+
+#if !defined(SQLITE_VERSION_NUMBER) || (SQLITE_VERSION_NUMBER < 3005004)
+#define sqlite3_prepare_v2 sqlite3_prepare
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <cfg_parser.h>
+#include <db_common.h>
+
+/* Application-specific. */
+
+#include <dict_sqlite.h>
+
+typedef struct {
+ DICT dict; /* generic member */
+ CFG_PARSER *parser; /* common parameter parser */
+ sqlite3 *db; /* sqlite handle */
+ char *query; /* db_common_expand() query */
+ char *result_format; /* db_common_expand() result_format */
+ void *ctx; /* db_common_parse() context */
+ char *dbpath; /* dbpath config attribute */
+ int expansion_limit; /* expansion_limit config attribute */
+} DICT_SQLITE;
+
+/* dict_sqlite_quote - escape SQL metacharacters in input string */
+
+static void dict_sqlite_quote(DICT *dict, const char *raw_text, VSTRING *result)
+{
+ char *quoted_text;
+
+ quoted_text = sqlite3_mprintf("%q", raw_text);
+ /* Fix 20100616 */
+ if (quoted_text == 0)
+ msg_fatal("dict_sqlite_quote: out of memory");
+ vstring_strcat(result, quoted_text);
+ sqlite3_free(quoted_text);
+}
+
+/* dict_sqlite_close - close the database */
+
+static void dict_sqlite_close(DICT *dict)
+{
+ const char *myname = "dict_sqlite_close";
+ DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_sqlite->parser->name);
+
+ if (sqlite3_close(dict_sqlite->db) != SQLITE_OK)
+ msg_fatal("%s: close %s failed", myname, dict_sqlite->parser->name);
+ cfg_parser_free(dict_sqlite->parser);
+ myfree(dict_sqlite->dbpath);
+ myfree(dict_sqlite->query);
+ myfree(dict_sqlite->result_format);
+ if (dict_sqlite->ctx)
+ db_common_free_ctx(dict_sqlite->ctx);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sqlite_lookup - find database entry */
+
+static const char *dict_sqlite_lookup(DICT *dict, const char *name)
+{
+ const char *myname = "dict_sqlite_lookup";
+ DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict;
+ sqlite3_stmt *sql_stmt;
+ const char *query_remainder;
+ static VSTRING *query;
+ static VSTRING *result;
+ const char *retval;
+ int expansion = 0;
+ int status;
+ int domain_rc;
+
+ /*
+ * In case of return without lookup (skipped key, etc.).
+ */
+ dict->error = 0;
+
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_sqlite->parser->name, name);
+ return (0);
+ }
+
+ /*
+ * Optionally fold the key. Folding may be enabled on-the-fly.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(100);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Apply the optional domain filter for email address lookups.
+ */
+ if ((domain_rc = db_common_check_domain(dict_sqlite->ctx, name)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of '%s'",
+ myname, dict_sqlite->parser->name, name);
+ return (0);
+ }
+ if (domain_rc < 0)
+ DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+
+ /*
+ * Expand the query and query the database.
+ */
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(query, 10);
+
+ if (!db_common_expand(dict_sqlite->ctx, dict_sqlite->query,
+ name, 0, query, dict_sqlite_quote))
+ return (0);
+
+ if (msg_verbose)
+ msg_info("%s: %s: Searching with query %s",
+ myname, dict_sqlite->parser->name, vstring_str(query));
+
+ if (sqlite3_prepare_v2(dict_sqlite->db, vstring_str(query), -1,
+ &sql_stmt, &query_remainder) != SQLITE_OK)
+ msg_fatal("%s: %s: SQL prepare failed: %s\n",
+ myname, dict_sqlite->parser->name,
+ sqlite3_errmsg(dict_sqlite->db));
+
+ if (*query_remainder && msg_verbose)
+ msg_info("%s: %s: Ignoring text at end of query: %s",
+ myname, dict_sqlite->parser->name, query_remainder);
+
+ /*
+ * Retrieve and expand the result(s).
+ */
+ INIT_VSTR(result, 10);
+ while ((status = sqlite3_step(sql_stmt)) != SQLITE_DONE) {
+ if (status == SQLITE_ROW) {
+ if (db_common_expand(dict_sqlite->ctx, dict_sqlite->result_format,
+ (const char *) sqlite3_column_text(sql_stmt, 0),
+ name, result, 0)
+ && dict_sqlite->expansion_limit > 0
+ && ++expansion > dict_sqlite->expansion_limit) {
+ msg_warn("%s: %s: Expansion limit exceeded for key '%s'",
+ myname, dict_sqlite->parser->name, name);
+ dict->error = DICT_ERR_RETRY;
+ break;
+ }
+ }
+ /* Fix 20100616 */
+ else {
+ msg_warn("%s: %s: SQL step failed for query '%s': %s\n",
+ myname, dict_sqlite->parser->name,
+ vstring_str(query), sqlite3_errmsg(dict_sqlite->db));
+ dict->error = DICT_ERR_RETRY;
+ break;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (sqlite3_finalize(sql_stmt))
+ msg_fatal("%s: %s: SQL finalize failed for query '%s': %s\n",
+ myname, dict_sqlite->parser->name,
+ vstring_str(query), sqlite3_errmsg(dict_sqlite->db));
+
+ return ((dict->error == 0 && *(retval = vstring_str(result)) != 0) ?
+ retval : 0);
+}
+
+/* sqlite_parse_config - parse sqlite configuration file */
+
+static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf)
+{
+ VSTRING *buf;
+
+ /*
+ * Parse the primary configuration parameters, and emulate the legacy
+ * query interface if necessary. This simplifies migration from one SQL
+ * database type to another.
+ */
+ dict_sqlite->dbpath = cfg_get_str(dict_sqlite->parser, "dbpath", "", 1, 0);
+ dict_sqlite->query = cfg_get_str(dict_sqlite->parser, "query", NULL, 0, 0);
+ if (dict_sqlite->query == 0) {
+ buf = vstring_alloc(100);
+ db_common_sql_build_query(buf, dict_sqlite->parser);
+ dict_sqlite->query = vstring_export(buf);
+ }
+ dict_sqlite->result_format =
+ cfg_get_str(dict_sqlite->parser, "result_format", "%s", 1, 0);
+ dict_sqlite->expansion_limit =
+ cfg_get_int(dict_sqlite->parser, "expansion_limit", 0, 0, 0);
+
+ /*
+ * Parse the query / result templates and the optional domain filter.
+ */
+ dict_sqlite->ctx = 0;
+ (void) db_common_parse(&dict_sqlite->dict, &dict_sqlite->ctx,
+ dict_sqlite->query, 1);
+ (void) db_common_parse(0, &dict_sqlite->ctx, dict_sqlite->result_format, 0);
+ db_common_parse_domain(dict_sqlite->parser, dict_sqlite->ctx);
+
+ /*
+ * Maps that use substring keys should only be used with the full input
+ * key.
+ */
+ if (db_common_dict_partial(dict_sqlite->ctx))
+ dict_sqlite->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_sqlite->dict.flags |= DICT_FLAG_FIXED;
+}
+
+/* dict_sqlite_open - open sqlite database */
+
+DICT *dict_sqlite_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_SQLITE *dict_sqlite;
+ CFG_PARSER *parser;
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_SQLITE, name, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_SQLITE, name));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((parser = cfg_parser_alloc(name)) == 0)
+ return (dict_surrogate(DICT_TYPE_SQLITE, name, open_flags, dict_flags,
+ "open %s: %m", name));
+
+ dict_sqlite = (DICT_SQLITE *) dict_alloc(DICT_TYPE_SQLITE, name,
+ sizeof(DICT_SQLITE));
+ dict_sqlite->dict.lookup = dict_sqlite_lookup;
+ dict_sqlite->dict.close = dict_sqlite_close;
+ dict_sqlite->dict.flags = dict_flags;
+
+ dict_sqlite->parser = parser;
+ sqlite_parse_config(dict_sqlite, name);
+
+ if (sqlite3_open(dict_sqlite->dbpath, &dict_sqlite->db))
+ msg_fatal("%s:%s: Can't open database: %s\n",
+ DICT_TYPE_SQLITE, name, sqlite3_errmsg(dict_sqlite->db));
+
+ dict_sqlite->dict.owner = cfg_get_owner(dict_sqlite->parser);
+
+ return (DICT_DEBUG (&dict_sqlite->dict));
+}
+
+#endif
diff --git a/src/global/dict_sqlite.h b/src/global/dict_sqlite.h
new file mode 100644
index 0000000..fb2bdf2
--- /dev/null
+++ b/src/global/dict_sqlite.h
@@ -0,0 +1,32 @@
+#ifndef _DICT_SQLITE_H_INCLUDED_
+#define _DICT_SQLITE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_sqlite 3h
+/* SUMMARY
+/* dictionary manager interface to sqlite databases
+/* SYNOPSIS
+/* #include <dict_sqlite.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_SQLITE "sqlite"
+
+extern DICT *dict_sqlite_open(const char *, int, int);
+
+
+/* AUTHOR(S)
+/* Axel Steiner
+/* ast@treibsand.com
+/*--*/
+
+#endif
diff --git a/src/global/domain_list.c b/src/global/domain_list.c
new file mode 100644
index 0000000..d79beaf
--- /dev/null
+++ b/src/global/domain_list.c
@@ -0,0 +1,132 @@
+/*++
+/* NAME
+/* domain_list 3
+/* SUMMARY
+/* match a host or domain name against a pattern list
+/* SYNOPSIS
+/* #include <domain_list.h>
+/*
+/* DOMAIN_LIST *domain_list_init(pname, flags, pattern_list)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/*
+/* int domain_list_match(list, name)
+/* DOMAIN_LIST *list;
+/* const char *name;
+/*
+/* void domain_list_free(list)
+/* DOMAIN_LIST *list;
+/* DESCRIPTION
+/* This is a convenience wrapper around the match_list module.
+/*
+/* This module implements tests for list membership of a host or
+/* domain name.
+/*
+/* Patterns are separated by whitespace and/or commas. A pattern
+/* is either a string, a file name (in which case the contents
+/* of the file are substituted for the file name) or a type:name
+/* lookup table specification.
+/*
+/* A host name matches a domain list when its name appears in the
+/* list of domain patterns, or when any of its parent domains appears
+/* in the list of domain patterns. The matching process is case
+/* insensitive. In order to reverse the result, precede a
+/* pattern with an exclamation point (!).
+/*
+/* domain_list_init() performs initializations. The pname
+/* argument specifies error reporting context. The flags argument
+/* is the bit-wise OR of zero or more of the following:
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches itself and any name below
+/* the domain foo.com. If this flag is cleared, foo.com matches itself
+/* only, and .foo.com matches any name below the domain foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Request that domain_list_match() logs a warning and returns
+/* zero, with list->error set to a non-zero dictionary error
+/* code, instead of raising a fatal error.
+/* .PP
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* The last argument is a list of domain patterns, or the name of
+/* a file containing domain patterns.
+/*
+/* domain_list_match() matches the specified host or domain name
+/* against the specified pattern list.
+/*
+/* domain_list_free() releases storage allocated by domain_list_init().
+/* DIAGNOSTICS
+/* Fatal error: unable to open or read a domain_list file; invalid
+/* domain_list pattern.
+/* SEE ALSO
+/* match_list(3) generic list matching
+/* match_ops(3) match hosts by name or by address
+/* 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 <match_list.h>
+
+/* Global library. */
+
+#include "domain_list.h"
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <dict.h>
+#include <stringops.h> /* util_utf8_enable */
+
+static void usage(char *progname)
+{
+ msg_fatal("usage: %s [-v] patterns hostname", progname);
+}
+
+int main(int argc, char **argv)
+{
+ DOMAIN_LIST *list;
+ char *host;
+ int ch;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 2)
+ usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ list = domain_list_init("command line", MATCH_FLAG_PARENT
+ | MATCH_FLAG_RETURN, argv[optind]);
+ host = argv[optind + 1];
+ vstream_printf("%s: %s\n", host, domain_list_match(list, host) ?
+ "YES" : list->error == 0 ? "NO" : "ERROR");
+ vstream_fflush(VSTREAM_OUT);
+ domain_list_free(list);
+ return (0);
+}
+
+#endif
diff --git a/src/global/domain_list.h b/src/global/domain_list.h
new file mode 100644
index 0000000..b0dfaec
--- /dev/null
+++ b/src/global/domain_list.h
@@ -0,0 +1,40 @@
+#ifndef _DOMAIN_LIST_H_INCLUDED_
+#define _DOMAIN_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* domain_list 3h
+/* SUMMARY
+/* match a host or domain name against a pattern list
+/* SYNOPSIS
+/* #include <domain_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <match_list.h>
+
+ /*
+ * External interface.
+ */
+#define DOMAIN_LIST MATCH_LIST
+
+#define domain_list_init(o, f, p)\
+ match_list_init((o), (f), (p), 1, match_hostname)
+#define domain_list_match match_list_match
+#define domain_list_free match_list_free
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dot_lockfile.c b/src/global/dot_lockfile.c
new file mode 100644
index 0000000..89a977a
--- /dev/null
+++ b/src/global/dot_lockfile.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* dot_lockfile 3
+/* SUMMARY
+/* dotlock file management
+/* SYNOPSIS
+/* #include <dot_lockfile.h>
+/*
+/* int dot_lockfile(path, why)
+/* const char *path;
+/* VSTRING *why;
+/*
+/* void dot_unlockfile(path)
+/* const char *path;
+/* DESCRIPTION
+/* dot_lockfile() constructs a lock file name by appending ".lock" to
+/* \fIpath\fR and creates the named file exclusively. It tries several
+/* times and attempts to break stale locks. A negative result value
+/* means no lock file could be created.
+/*
+/* dot_unlockfile() attempts to remove the lock file created by
+/* dot_lockfile(). The operation always succeeds, and therefore
+/* it preserves the errno value.
+/*
+/* Arguments:
+/* .IP path
+/* Name of the file to be locked or unlocked.
+/* .IP why
+/* A null pointer, or storage for the reason why a lock file could
+/* not be created.
+/* DIAGNOSTICS
+/* dot_lockfile() returns 0 upon success. In case of failure, the
+/* result is -1, and the errno variable is set appropriately:
+/* EEXIST when a "fresh" lock file already exists; other values as
+/* appropriate.
+/* CONFIGURATION PARAMETERS
+/* deliver_lock_attempts, how many times to try to create a lock
+/* deliver_lock_delay, how long to wait between attempts
+/* stale_lock_time, when to break a stale lock
+/* 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>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "dot_lockfile.h"
+
+/* Application-specific. */
+
+#define MILLION 1000000
+
+/* dot_lockfile - create user.lock file */
+
+int dot_lockfile(const char *path, VSTRING *why)
+{
+ char *lock_file;
+ int count;
+ struct stat st;
+ int fd;
+ int status = -1;
+
+ lock_file = concatenate(path, ".lock", (char *) 0);
+
+ for (count = 1; /* void */ ; count++) {
+
+ /*
+ * Attempt to create the lock. This code relies on O_EXCL | O_CREAT
+ * to not follow symlinks. With NFS file systems this operation can
+ * at the same time succeed and fail with errno of EEXIST.
+ */
+ if ((fd = open(lock_file, O_WRONLY | O_EXCL | O_CREAT, 0)) >= 0) {
+ close(fd);
+ status = 0;
+ break;
+ }
+ if (count >= var_flock_tries)
+ break;
+
+ /*
+ * We can deal only with "file exists" errors. Any other error means
+ * we better give up trying.
+ */
+ if (errno != EEXIST)
+ break;
+
+ /*
+ * Break the lock when it is too old. Give up when we are unable to
+ * remove a stale lock.
+ */
+ if (stat(lock_file, &st) == 0)
+ if (time((time_t *) 0) > st.st_ctime + var_flock_stale)
+ if (unlink(lock_file) < 0)
+ if (errno != ENOENT)
+ break;
+
+ rand_sleep(var_flock_delay * MILLION, var_flock_delay * MILLION / 2);
+ }
+ if (status && why)
+ vstring_sprintf(why, "unable to create lock file %s: %m", lock_file);
+
+ myfree(lock_file);
+ return (status);
+}
+
+/* dot_unlockfile - remove .lock file */
+
+void dot_unlockfile(const char *path)
+{
+ char *lock_file;
+ int saved_errno = errno;
+
+ lock_file = concatenate(path, ".lock", (char *) 0);
+ (void) unlink(lock_file);
+ myfree(lock_file);
+ errno = saved_errno;
+}
+
+#ifdef TEST
+
+ /*
+ * Test program for setting a .lock file.
+ *
+ * Usage: dot_lockfile filename
+ *
+ * Creates filename.lock and removes it.
+ */
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mail_conf.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *why = vstring_alloc(100);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 2)
+ msg_fatal("usage: %s file-to-be-locked", argv[0]);
+ mail_conf_read();
+ if (dot_lockfile(argv[1], why) < 0)
+ msg_fatal("%s", vstring_str(why));
+ dot_unlockfile(argv[1]);
+ vstring_free(why);
+ return (0);
+}
+
+#endif
diff --git a/src/global/dot_lockfile.h b/src/global/dot_lockfile.h
new file mode 100644
index 0000000..1c04510
--- /dev/null
+++ b/src/global/dot_lockfile.h
@@ -0,0 +1,36 @@
+#ifndef _DOT_LOCKFILE_H_INCLUDED_
+#define _DOT_LOCKFILE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dot_lockfile 3h
+/* SUMMARY
+/* dotlock file management
+/* SYNOPSIS
+/* #include <dot_lockfile.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern int dot_lockfile(const char *, VSTRING *);
+extern void dot_unlockfile(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dot_lockfile_as.c b/src/global/dot_lockfile_as.c
new file mode 100644
index 0000000..7ee84d9
--- /dev/null
+++ b/src/global/dot_lockfile_as.c
@@ -0,0 +1,99 @@
+/*++
+/* NAME
+/* dot_lockfile_as 3
+/* SUMMARY
+/* dotlock file as user
+/* SYNOPSIS
+/* #include <dot_lockfile_as.h>
+/*
+/* int dot_lockfile_as(path, why, euid, egid)
+/* const char *path;
+/* VSTRING *why;
+/* uid_t euid;
+/* gid_t egid;
+/*
+/* void dot_unlockfile_as(path, euid, egid)
+/* const char *path;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* dot_lockfile_as() and dot_unlockfile_as() are wrappers around
+/* the dot_lockfile() and dot_unlockfile() routines. The routines
+/* change privilege to the designated privilege, perform the
+/* requested operation, and restore privileges.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* dot_lockfile(3) dotlock file management
+/* set_eugid(3) switch effective rights
+/* 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>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "dot_lockfile.h"
+#include "dot_lockfile_as.h"
+
+/* dot_lockfile_as - dotlock file as user */
+
+int dot_lockfile_as(const char *path, VSTRING *why, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int result;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Lock that file.
+ */
+ result = dot_lockfile(path, why);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (result);
+}
+
+/* dot_unlockfile_as - dotlock file as user */
+
+void dot_unlockfile_as(const char *path, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Lock that file.
+ */
+ dot_unlockfile(path);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+}
diff --git a/src/global/dot_lockfile_as.h b/src/global/dot_lockfile_as.h
new file mode 100644
index 0000000..539a70a
--- /dev/null
+++ b/src/global/dot_lockfile_as.h
@@ -0,0 +1,36 @@
+#ifndef _DOT_LOCKFILE_AS_H_INCLUDED_
+#define _DOT_LOCKFILE_AS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dot_lockfile_as 3h
+/* SUMMARY
+/* dotlock file management
+/* SYNOPSIS
+/* #include <dot_lockfile_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern int dot_lockfile_as(const char *, VSTRING *, uid_t, gid_t);
+extern void dot_unlockfile_as(const char *, uid_t, gid_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsb_scan.c b/src/global/dsb_scan.c
new file mode 100644
index 0000000..cf434d1
--- /dev/null
+++ b/src/global/dsb_scan.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* dsb_scan
+/* SUMMARY
+/* read DSN_BUF from stream
+/* SYNOPSIS
+/* #include <dsb_scan.h>
+/*
+/* int dsb_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* dsb_scan() reads a DSN_BUF from the named stream using the
+/* specified attribute scan routine. dsb_scan() is meant
+/* to be passed as a call-back to attr_scan(), thusly:
+/*
+/* ... RECV_ATTR_FUNC(dsb_scan, (void *) &dsbuf), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <dsb_scan.h>
+
+/* dsb_scan - read DSN_BUF from stream */
+
+int dsb_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ DSN_BUF *dsb = (DSN_BUF *) ptr;
+ int ret;
+
+ /*
+ * The attribute order is determined by backwards compatibility. It can
+ * be sanitized after all the ad-hoc DSN read/write code is replaced.
+ */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_DSN_STATUS, dsb->status),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_DTYPE, dsb->dtype),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_DTEXT, dsb->dtext),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_MTYPE, dsb->mtype),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_MNAME, dsb->mname),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ACTION, dsb->action),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, dsb->reason),
+ ATTR_TYPE_END);
+ return (ret == 7 ? 1 : -1);
+}
diff --git a/src/global/dsb_scan.h b/src/global/dsb_scan.h
new file mode 100644
index 0000000..69340be
--- /dev/null
+++ b/src/global/dsb_scan.h
@@ -0,0 +1,46 @@
+#ifndef _DSB_SCAN_H_INCLUDED_
+#define _DSB_SCAN_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsb_scan 3h
+/* SUMMARY
+/* write DSN to stream
+/* SYNOPSIS
+/* #include <dsb_scan.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn_buf.h>
+
+ /*
+ * External interface.
+ */
+extern int dsb_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn.c b/src/global/dsn.c
new file mode 100644
index 0000000..32da936
--- /dev/null
+++ b/src/global/dsn.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* dsn
+/* SUMMARY
+/* RFC-compliant delivery status information
+/* SYNOPSIS
+/* #include <dsn.h>
+/*
+/* typedef struct {
+/* .in +4
+/* const char *status; /* RFC 3463 status */
+/* const char *action; /* null or RFC 3464 action */
+/* const char *reason; /* human-readable text */
+/* const char *dtype; /* null or diagnostic type */
+/* const char *dtext; /* null or diagnostic code */
+/* const char *mtype; /* null or MTA type */
+/* const char *mname; /* null or remote MTA */
+/* .in -4
+/* } DSN;
+/*
+/* DSN *dsn_create(status, action, reason, dtype, dtext, mtype, mname)
+/* const char *status;
+/* const char *action;
+/* const char *reason;
+/* const char *dtype;
+/* const char *dtext;
+/* const char *mtype;
+/* const char *mname;
+/*
+/* DSN *DSN_COPY(dsn)
+/* DSN *dsn;
+/*
+/* void dsn_free(dsn)
+/* DSN *dsn;
+/*
+/* DSN *DSN_ASSIGN(dsn, status, action, reason, dtype, dtext,
+/* mtype, mname)
+/* DSN *dsn;
+/* const char *status;
+/* const char *action;
+/* const char *reason;
+/* const char *dtype;
+/* const char *dtext;
+/* const char *mtype;
+/* const char *mname;
+/*
+/* DSN *DSN_SIMPLE(dsn, status, action, reason)
+/* DSN *dsn;
+/* const char *status;
+/* const char *action;
+/* const char *reason;
+/* DESCRIPTION
+/* This module maintains delivery error information. For a
+/* description of structure field members see "Arguments"
+/* below. Function-like names spelled in upper case are macros.
+/* These may evaluate some arguments more than once.
+/*
+/* dsn_create() creates a DSN structure and copies its arguments.
+/* The DSN structure should be destroyed with dsn_free().
+/*
+/* DSN_COPY() creates a deep copy of its argument.
+/*
+/* dsn_free() destroys a DSN structure and makes its storage
+/* available for reuse.
+/*
+/* DSN_ASSIGN() updates a DSN structure and DOES NOT copy
+/* arguments or free memory. The result DSN structure must
+/* NOT be passed to dsn_free(). DSN_ASSIGN() is typically used
+/* for stack-based short-lived storage.
+/*
+/* DSN_SIMPLE() takes the minimally required subset of all the
+/* attributes and sets the rest to empty strings.
+/* This is a wrapper around the DSN_ASSIGN() macro.
+/*
+/* Arguments:
+/* .IP reason
+/* Human-readable text, used for logging purposes, and for
+/* updating the message-specific \fBbounce\fR or \fIdefer\fR
+/* logfile.
+/* .IP status
+/* Enhanced status code as specified in RFC 3463.
+/* .IP action
+/* DSN_NO_ACTION, empty string, or action as defined in RFC 3464.
+/* If no action is specified, a default action is chosen.
+/* .IP dtype
+/* DSN_NO_DTYPE, empty string, or diagnostic code type as
+/* specified in RFC 3464.
+/* .IP dtext
+/* DSN_NO_DTEXT, empty string, or diagnostic code as specified
+/* in RFC 3464.
+/* .IP mtype
+/* DSN_NO_MTYPE, empty string, DSN_MTYPE_DNS or DSN_MTYPE_UNIX.
+/* .IP mname
+/* DSN_NO_MNAME, empty string, or remote MTA as specified in
+/* RFC 3464.
+/* DIAGNOSTICS
+/* Panic: null or empty status or reason.
+/* Fatal: out of memory.
+/* 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 <mymalloc.h>
+
+/* Global library. */
+
+#include <dsn.h>
+
+/* dsn_create - create DSN structure */
+
+DSN *dsn_create(const char *status, const char *action, const char *reason,
+ const char *dtype, const char *dtext,
+ const char *mtype, const char *mname)
+{
+ const char *myname = "dsn_create";
+ DSN *dsn;
+
+ dsn = (DSN *) mymalloc(sizeof(*dsn));
+
+ /*
+ * Status and reason must not be empty. Other members may be empty
+ * strings.
+ *
+ * Early implementations represented unavailable information with null
+ * pointers. This resulted in code that was difficult to maintain. We now
+ * use empty strings instead. For safety sake we keep the null pointer
+ * test for input, but we always convert to empty string on output.
+ */
+#define NULL_OR_EMPTY(s) ((s) == 0 || *(s) == 0)
+
+ if (NULL_OR_EMPTY(status))
+ msg_panic("%s: null dsn status", myname);
+ else
+ dsn->status = mystrdup(status);
+
+ if (NULL_OR_EMPTY(action))
+ dsn->action = mystrdup("");
+ else
+ dsn->action = mystrdup(action);
+
+ if (NULL_OR_EMPTY(reason))
+ msg_panic("%s: null dsn reason", myname);
+ else
+ dsn->reason = mystrdup(reason);
+
+ if (NULL_OR_EMPTY(dtype) || NULL_OR_EMPTY(dtext)) {
+ dsn->dtype = mystrdup("");
+ dsn->dtext = mystrdup("");
+ } else {
+ dsn->dtype = mystrdup(dtype);
+ dsn->dtext = mystrdup(dtext);
+ }
+ if (NULL_OR_EMPTY(mtype) || NULL_OR_EMPTY(mname)) {
+ dsn->mtype = mystrdup("");
+ dsn->mname = mystrdup("");
+ } else {
+ dsn->mtype = mystrdup(mtype);
+ dsn->mname = mystrdup(mname);
+ }
+ return (dsn);
+}
+
+/* dsn_free - destroy DSN structure */
+
+void dsn_free(DSN *dsn)
+{
+ myfree((void *) dsn->status);
+ myfree((void *) dsn->action);
+ myfree((void *) dsn->reason);
+ myfree((void *) dsn->dtype);
+ myfree((void *) dsn->dtext);
+ myfree((void *) dsn->mtype);
+ myfree((void *) dsn->mname);
+ myfree((void *) dsn);
+}
diff --git a/src/global/dsn.h b/src/global/dsn.h
new file mode 100644
index 0000000..bf49dcb
--- /dev/null
+++ b/src/global/dsn.h
@@ -0,0 +1,84 @@
+#ifndef _DSN_H_INCLUDED_
+#define _DSN_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn 3h
+/* SUMMARY
+/* RFC-compliant delivery status information
+/* SYNOPSIS
+/* #include <dsn.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *status; /* RFC 3463 status */
+ const char *action; /* Null / RFC 3464 action */
+ const char *reason; /* descriptive reason */
+ const char *dtype; /* Null / RFC 3464 diagnostic type */
+ const char *dtext; /* Null / RFC 3464 diagnostic code */
+ const char *mtype; /* Null / RFC 3464 MTA type */
+ const char *mname; /* Null / RFC 3464 remote MTA */
+} DSN;
+
+extern DSN *dsn_create(const char *, const char *, const char *, const char *,
+ const char *, const char *, const char *);
+extern void dsn_free(DSN *);
+
+#define DSN_ASSIGN(dsn, _status, _action, _reason, _dtype, _dtext, _mtype, _mname) \
+ (((dsn)->status = (_status)), \
+ ((dsn)->action = (_action)), \
+ ((dsn)->reason = (_reason)), \
+ ((dsn)->dtype = (_dtype)), \
+ ((dsn)->dtext = (_dtext)), \
+ ((dsn)->mtype = (_mtype)), \
+ ((dsn)->mname = (_mname)), \
+ (dsn))
+
+#define DSN_SIMPLE(dsn, _status, _reason) \
+ (((dsn)->status = (_status)), \
+ ((dsn)->action = DSN_NO_ACTION), \
+ ((dsn)->reason = (_reason)), \
+ ((dsn)->dtype = DSN_NO_DTYPE), \
+ ((dsn)->dtext = DSN_NO_DTEXT), \
+ ((dsn)->mtype = DSN_NO_MTYPE), \
+ ((dsn)->mname = DSN_NO_MNAME), \
+ (dsn))
+
+#define DSN_NO_ACTION ""
+#define DSN_NO_DTYPE ""
+#define DSN_NO_DTEXT ""
+#define DSN_NO_MTYPE ""
+#define DSN_NO_MNAME ""
+
+ /*
+ * Early implementations represented unavailable information with null
+ * pointers. This resulted in code that is hard to maintain. We now use
+ * empty strings instead. This does not waste precious memory as long as we
+ * can represent empty strings efficiently by collapsing them.
+ *
+ * The only restriction left is that the status and reason are never null or
+ * empty; this is enforced by dsn_create() which is invoked by DSN_COPY().
+ * This complicates the server reply parsing code in the smtp(8) and lmtp(8)
+ * clients. they must never supply empty strings for these required fields.
+ */
+#define DSN_COPY(dsn) \
+ dsn_create((dsn)->status, (dsn)->action, (dsn)->reason, \
+ (dsn)->dtype, (dsn)->dtext, \
+ (dsn)->mtype, (dsn)->mname)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn_buf.c b/src/global/dsn_buf.c
new file mode 100644
index 0000000..fce9845
--- /dev/null
+++ b/src/global/dsn_buf.c
@@ -0,0 +1,342 @@
+/*++
+/* NAME
+/* dsn_buf 3
+/* SUMMARY
+/* delivery status buffer
+/* SYNOPSIS
+/* #include <dsn_buf.h>
+/*
+/* typedef struct {
+/* .in +4
+/* /* Convenience member */
+/* DSN dsn; /* light-weight, dsn(3) */
+/* /* Formal members... */
+/* VSTRING *status; /* RFC 3463 */
+/* VSTRING *action; /* RFC 3464 */
+/* VSTRING *mtype; /* dns */
+/* VSTRING *mname; /* host or domain */
+/* VSTRING *dtype; /* smtp, x-unix */
+/* VSTRING *dtext; /* RFC 2821, sysexits.h */
+/* /* Informal members... */
+/* VSTRING *reason; /* informal text */
+/* .in -4
+/* } DSN_BUF;
+/*
+/* DSN_BUF *dsb_create(void)
+/*
+/* DSN_BUF *dsb_update(dsb, status, action, mtype, mname, dtype,
+/* dtext, reason_fmt, ...)
+/* DSN_BUF *dsb;
+/* const char *status;
+/* const char *action;
+/* const char *mtype;
+/* const char *mname;
+/* const char *dtype;
+/* const char *dtext;
+/* const char *reason_fmt;
+/*
+/* DSN_BUF *dsb_simple(dsb, status, reason_fmt, ...)
+/* DSN_BUF *dsb;
+/* const char *status;
+/* const char *reason_fmt;
+/*
+/* DSN_BUF *dsb_unix(dsb, status, dtext, reason_fmt, ...)
+/* DSN_BUF *dsb;
+/* const char *status;
+/* const char *reason_fmt;
+/*
+/* DSN_BUF *dsb_formal(dsb, status, action, mtype, mname, dtype,
+/* dtext)
+/* DSN_BUF *dsb;
+/* const char *status;
+/* const char *action;
+/* const char *mtype;
+/* const char *mname;
+/* const char *dtype;
+/* const char *dtext;
+/*
+/* DSN_BUF *dsb_status(dsb, status)
+/* DSN_BUF *dsb;
+/* const char *status;
+/*
+/* void dsb_reset(dsb)
+/* DSN_BUF *dsb;
+/*
+/* void dsb_free(dsb)
+/* DSN_BUF *dsb;
+/*
+/* DSN *DSN_FROM_DSN_BUF(dsb)
+/* DSN_BUF *dsb;
+/* DESCRIPTION
+/* This module implements a simple to update delivery status
+/* buffer for Postfix-internal use. Typically it is filled in
+/* the course of delivery attempt, and then formatted into a
+/* DSN structure for external notification.
+/*
+/* dsb_create() creates initialized storage for formal RFC 3464
+/* attributes, and human-readable informal text.
+/*
+/* dsb_update() updates all fields.
+/*
+/* dsb_simple() updates the status and informal text, and resets all
+/* other fields to defaults.
+/*
+/* dsb_unix() updates the status, diagnostic code, diagnostic
+/* text, and informal text, sets the diagnostic type to UNIX,
+/* and resets all other fields to defaults.
+/*
+/* dsb_formal() updates all fields except the informal text.
+/*
+/* dsb_status() updates the status field, and resets all
+/* formal fields to defaults.
+/*
+/* dsb_reset() resets all fields in a DSN_BUF structure without
+/* deallocating memory.
+/*
+/* dsb_free() recycles the storage that was allocated by
+/* dsb_create(), and so on.
+/*
+/* DSN_FROM_DSN_BUF() populates the DSN member with a shallow
+/* copy of the contents of the formal and informal fields, and
+/* returns a pointer to the DSN member. This is typically used
+/* for external reporting.
+/*
+/* Arguments:
+/* .IP dsb
+/* Delivery status buffer.
+/* .IP status
+/* RFC 3463 "enhanced" status code.
+/* .IP action
+/* RFC 3464 action code; specify DSB_DEF_ACTION to derive the
+/* action from the status value. The only values that really
+/* matter here are "expanded" and "relayed"; all other values
+/* are already implied by the context.
+/* .IP mtype
+/* The remote MTA type.
+/* The only valid type is DSB_MTYPE_DNS. The macro DSB_SKIP_RMTA
+/* conveniently expands into a null argument list for the
+/* remote MTA type and name.
+/* .IP mname
+/* Remote MTA name.
+/* .IP dtype
+/* The reply type.
+/* DSB_DTYPE_SMTP or DSB_DTYPE_UNIX. The macro DSB_SKIP_REPLY
+/* conveniently expands into a null argument list for the reply
+/* type and text.
+/* .IP dtext
+/* The reply text. The reply text is reset when dtype is
+/* DSB_SKIP_REPLY.
+/* .IP reason_fmt
+/* The informal reason format.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <dsn_buf.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+
+/* dsb_create - create delivery status buffer */
+
+DSN_BUF *dsb_create(void)
+{
+ DSN_BUF *dsb;
+
+ /*
+ * Some fields aren't needed until we want to report an error.
+ */
+ dsb = (DSN_BUF *) mymalloc(sizeof(*dsb));
+ dsb->status = vstring_alloc(10);
+ dsb->action = vstring_alloc(10);
+ dsb->mtype = vstring_alloc(10);
+ dsb->mname = vstring_alloc(100);
+ dsb->dtype = vstring_alloc(10);
+ dsb->dtext = vstring_alloc(100);
+ dsb->reason = vstring_alloc(100);
+
+ return (dsb);
+}
+
+/* dsb_free - destroy storage */
+
+void dsb_free(DSN_BUF *dsb)
+{
+ vstring_free(dsb->status);
+ vstring_free(dsb->action);
+ vstring_free(dsb->mtype);
+ vstring_free(dsb->mname);
+ vstring_free(dsb->dtype);
+ vstring_free(dsb->dtext);
+ vstring_free(dsb->reason);
+ myfree((void *) dsb);
+}
+
+ /*
+ * Initial versions of this code represented unavailable inputs with null
+ * pointers, which produced fragile and hard to maintain code. The current
+ * code uses empty strings instead of null pointers.
+ *
+ * For safety we keep the test for null pointers in input. It's cheap.
+ */
+#define DSB_TRUNCATE(s) \
+ do { VSTRING_RESET(s); VSTRING_TERMINATE(s); } while (0)
+
+#define NULL_OR_EMPTY(s) ((s) == 0 || *(s) == 0)
+
+#define DSB_ACTION(dsb, stat, act) \
+ vstring_strcpy((dsb)->action, !NULL_OR_EMPTY(act) ? (act) : "")
+
+#define DSB_MTA(dsb, type, name) do { \
+ if (NULL_OR_EMPTY(type) || NULL_OR_EMPTY(name)) { \
+ DSB_TRUNCATE((dsb)->mtype); \
+ DSB_TRUNCATE((dsb)->mname); \
+ } else { \
+ vstring_strcpy((dsb)->mtype, (type)); \
+ vstring_strcpy((dsb)->mname, (name)); \
+ } \
+} while (0)
+
+#define DSB_DIAG(dsb, type, text) do { \
+ if (NULL_OR_EMPTY(type) || NULL_OR_EMPTY(text)) { \
+ DSB_TRUNCATE((dsb)->dtype); \
+ DSB_TRUNCATE((dsb)->dtext); \
+ } else { \
+ vstring_strcpy((dsb)->dtype, (type)); \
+ vstring_strcpy((dsb)->dtext, (text)); \
+ } \
+} while (0)
+
+/* dsb_update - update formal attributes and informal text */
+
+DSN_BUF *dsb_update(DSN_BUF *dsb, const char *status, const char *action,
+ const char *mtype, const char *mname,
+ const char *dtype, const char *dtext,
+ const char *format,...)
+{
+ va_list ap;
+
+ vstring_strcpy(dsb->status, status);
+ DSB_ACTION(dsb, status, action);
+ DSB_MTA(dsb, mtype, mname);
+ DSB_DIAG(dsb, dtype, dtext);
+ va_start(ap, format);
+ vstring_vsprintf(dsb->reason, format, ap);
+ va_end(ap);
+
+ return (dsb);
+}
+
+/* vdsb_simple - update status and informal text, va_list form */
+
+DSN_BUF *vdsb_simple(DSN_BUF *dsb, const char *status, const char *format,
+ va_list ap)
+{
+ vstring_strcpy(dsb->status, status);
+ DSB_TRUNCATE(dsb->action);
+ DSB_TRUNCATE(dsb->mtype);
+ DSB_TRUNCATE(dsb->mname);
+ DSB_TRUNCATE(dsb->dtype);
+ DSB_TRUNCATE(dsb->dtext);
+ vstring_vsprintf(dsb->reason, format, ap);
+
+ return (dsb);
+}
+
+/* dsb_simple - update status and informal text */
+
+DSN_BUF *dsb_simple(DSN_BUF *dsb, const char *status, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ (void) vdsb_simple(dsb, status, format, ap);
+ va_end(ap);
+ return (dsb);
+}
+
+/* dsb_unix - update status, UNIX diagnostic and informal text */
+
+DSN_BUF *dsb_unix(DSN_BUF *dsb, const char *status,
+ const char *dtext, const char *format,...)
+{
+ va_list ap;
+
+ vstring_strcpy(dsb->status, status);
+ DSB_TRUNCATE(dsb->action);
+ DSB_TRUNCATE(dsb->mtype);
+ DSB_TRUNCATE(dsb->mname);
+ vstring_strcpy(dsb->dtype, DSB_DTYPE_UNIX);
+ vstring_strcpy(dsb->dtext, dtext);
+ va_start(ap, format);
+ vstring_vsprintf(dsb->reason, format, ap);
+ va_end(ap);
+
+ return (dsb);
+}
+
+/* dsb_formal - update the formal fields */
+
+DSN_BUF *dsb_formal(DSN_BUF *dsb, const char *status, const char *action,
+ const char *mtype, const char *mname,
+ const char *dtype, const char *dtext)
+{
+ vstring_strcpy(dsb->status, status);
+ DSB_ACTION(dsb, status, action);
+ DSB_MTA(dsb, mtype, mname);
+ DSB_DIAG(dsb, dtype, dtext);
+ return (dsb);
+}
+
+/* dsb_status - update the status, reset other formal fields */
+
+DSN_BUF *dsb_status(DSN_BUF *dsb, const char *status)
+{
+ vstring_strcpy(dsb->status, status);
+ DSB_TRUNCATE(dsb->action);
+ DSB_TRUNCATE(dsb->mtype);
+ DSB_TRUNCATE(dsb->mname);
+ DSB_TRUNCATE(dsb->dtype);
+ DSB_TRUNCATE(dsb->dtext);
+ return (dsb);
+}
+
+/* dsb_reset - reset all fields */
+
+void dsb_reset(DSN_BUF *dsb)
+{
+ DSB_TRUNCATE(dsb->status);
+ DSB_TRUNCATE(dsb->action);
+ DSB_TRUNCATE(dsb->mtype);
+ DSB_TRUNCATE(dsb->mname);
+ DSB_TRUNCATE(dsb->dtype);
+ DSB_TRUNCATE(dsb->dtext);
+ DSB_TRUNCATE(dsb->reason);
+}
diff --git a/src/global/dsn_buf.h b/src/global/dsn_buf.h
new file mode 100644
index 0000000..6fd53dc
--- /dev/null
+++ b/src/global/dsn_buf.h
@@ -0,0 +1,89 @@
+#ifndef _DSN_BUF_H_INCLUDED_
+#define _DSN_BUF_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn_buf 3h
+/* SUMMARY
+/* delivery status buffer
+/* SYNOPSIS
+/* #include <dsn_buf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn.h>
+
+ /*
+ * Delivery status buffer, Postfix-internal form.
+ */
+typedef struct {
+ DSN dsn; /* convenience */
+ /* Formal members. */
+ VSTRING *status; /* RFC 3463 */
+ VSTRING *action; /* RFC 3464 */
+ VSTRING *mtype; /* null or remote MTA type */
+ VSTRING *mname; /* null or remote MTA name */
+ VSTRING *dtype; /* null, smtp, x-unix */
+ VSTRING *dtext; /* null, RFC 2821, sysexits.h */
+ /* Informal free text. */
+ VSTRING *reason; /* free text */
+} DSN_BUF;
+
+#define DSB_DEF_ACTION ((char *) 0)
+
+#define DSB_SKIP_RMTA ((char *) 0), ((char *) 0)
+#define DSB_MTYPE_NONE ((char *) 0)
+#define DSB_MTYPE_DNS "dns" /* RFC 2821 */
+
+#define DSB_SKIP_REPLY (char *) 0, " " /* XXX Bogus? */
+#define DSB_DTYPE_NONE ((char *) 0)
+#define DSB_DTYPE_SMTP "smtp" /* RFC 2821 */
+#define DSB_DTYPE_UNIX "x-unix" /* sysexits.h */
+#define DSB_DTYPE_SASL "x-sasl" /* libsasl */
+
+extern DSN_BUF *dsb_create(void);
+extern DSN_BUF *PRINTFLIKE(8, 9) dsb_update(DSN_BUF *, const char *, const char *, const char *, const char *, const char *, const char *, const char *,...);
+extern DSN_BUF *vdsb_simple(DSN_BUF *, const char *, const char *, va_list);
+extern DSN_BUF *PRINTFLIKE(3, 4) dsb_simple(DSN_BUF *, const char *, const char *,...);
+extern DSN_BUF *PRINTFLIKE(4, 5) dsb_unix(DSN_BUF *, const char *, const char *, const char *,...);
+extern DSN_BUF *dsb_formal(DSN_BUF *, const char *, const char *, const char *, const char *, const char *, const char *);
+extern DSN_BUF *dsb_status(DSN_BUF *, const char *);
+extern void dsb_reset(DSN_BUF *);
+extern void dsb_free(DSN_BUF *);
+
+ /*
+ * Early implementations of the DSN structure represented unavailable
+ * information with null pointers. This resulted in hard to maintain code.
+ * We now use empty strings instead, so there is no need anymore to convert
+ * empty strings to null pointers in the macro below.
+ */
+#define DSN_FROM_DSN_BUF(dsb) \
+ DSN_ASSIGN(&(dsb)->dsn, \
+ vstring_str((dsb)->status), \
+ vstring_str((dsb)->action), \
+ vstring_str((dsb)->reason), \
+ vstring_str((dsb)->dtype), \
+ vstring_str((dsb)->dtext), \
+ vstring_str((dsb)->mtype), \
+ vstring_str((dsb)->mname))
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn_filter.c b/src/global/dsn_filter.c
new file mode 100644
index 0000000..3ffeb9f
--- /dev/null
+++ b/src/global/dsn_filter.c
@@ -0,0 +1,194 @@
+/*++
+/* NAME
+/* dsn_filter 3
+/* SUMMARY
+/* filter delivery status code or text
+/* SYNOPSIS
+/* #include <dsn_filter.h>
+/*
+/* DSN_FILTER *dsn_filter_create(
+/* const char *title,
+/* const char *map_names)
+/*
+/* DSN *dsn_filter_lookup(
+/* DSN_FILTER *fp,
+/* DSN *dsn)
+/*
+/* void dsn_filter_free(
+/* DSN_FILTER *fp)
+/* DESCRIPTION
+/* This module maps (bounce or defer non-delivery status code
+/* and text) into replacement (bounce or defer non-delivery
+/* status code and text), or maps (success status code and
+/* text) into replacement (success status code and text). Other
+/* DSN attributes are passed through without modification.
+/*
+/* dsn_filter_create() instantiates a delivery status filter.
+/*
+/* dsn_filter_lookup() queries the specified filter. The input
+/* DSN must be a success, bounce or defer DSN. If a match is
+/* found a non-delivery status must map to a non-delivery
+/* status, a success status must map to a success status, and
+/* the text must be non-empty. The result is a null pointer
+/* when no valid match is found. Otherwise, the result is
+/* overwritten upon each call. This function must not be
+/* called with the result from a dsn_filter_lookup() call.
+/*
+/* dsn_filter_free() destroys the specified delivery status
+/* filter.
+/*
+/* Arguments:
+/* .IP title
+/* Origin of the mapnames argument, typically a configuration
+/* parameter name. This is reported in diagnostics.
+/* .IP mapnames
+/* List of lookup tables, separated by whitespace or comma.
+/* .IP fp
+/* filter created with dsn_filter_create()
+/* .IP dsn
+/* A success, bounce or defer DSN data structure. The
+/* dsn_filter_lookup() result value is in part a shallow copy
+/* of this argument.
+/* SEE ALSO
+/* maps(3) multi-table search
+/* DIAGNOSTICS
+/* Panic: invalid dsn argument; recursive call. Fatal error:
+/* memory allocation problem. Warning: invalid DSN lookup
+/* result.
+/* 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 libraries.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+#include <dsn.h>
+#include <dsn_util.h>
+#include <maps.h>
+#include <dsn_filter.h>
+
+ /*
+ * Private data structure.
+ */
+struct DSN_FILTER {
+ MAPS *maps; /* Replacement (status, text) */
+ VSTRING *buffer; /* Status code and text */
+ DSN_SPLIT dp; /* Parsing aid */
+ DSN dsn; /* Shallow copy */
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* dsn_filter_create - create delivery status filter */
+
+DSN_FILTER *dsn_filter_create(const char *title, const char *map_names)
+{
+ static const char myname[] = "dsn_filter_create";
+ DSN_FILTER *fp;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, title, map_names);
+
+ fp = (DSN_FILTER *) mymalloc(sizeof(*fp));
+ fp->buffer = vstring_alloc(100);
+ fp->maps = maps_create(title, map_names, DICT_FLAG_LOCK);
+ return (fp);
+}
+
+/* dsn_filter_lookup - apply delivery status filter */
+
+DSN *dsn_filter_lookup(DSN_FILTER *fp, DSN *dsn)
+{
+ static const char myname[] = "dsn_filter_lookup";
+ const char *result;
+ int ndr_dsn = 0;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, dsn->status, dsn->reason);
+
+ /*
+ * XXX Instead of hard-coded '4' etc., use some form of encapsulation
+ * when reading or updating the status class field.
+ */
+#define IS_SUCCESS_DSN(s) (dsn_valid(s) && (s)[0] == '2')
+#define IS_NDR_DSN(s) (dsn_valid(s) && ((s)[0] == '4' || (s)[0] == '5'))
+
+ /*
+ * Sanity check. We filter only success/bounce/defer DSNs.
+ */
+ if (IS_SUCCESS_DSN(dsn->status))
+ ndr_dsn = 0;
+ else if (IS_NDR_DSN(dsn->status))
+ ndr_dsn = 1;
+ else
+ msg_panic("%s: dsn argument with bad status code: %s",
+ myname, dsn->status);
+
+ /*
+ * Sanity check. A delivery status filter must not be invoked with its
+ * own result.
+ */
+ if (dsn->reason == fp->dsn.reason)
+ msg_panic("%s: recursive call is not allowed", myname);
+
+ /*
+ * Look up replacement status and text.
+ */
+ vstring_sprintf(fp->buffer, "%s %s", dsn->status, dsn->reason);
+ if ((result = maps_find(fp->maps, STR(fp->buffer), 0)) != 0) {
+ /* Sanity check. Do not allow success<=>error mappings. */
+ if ((ndr_dsn == 0 && !IS_SUCCESS_DSN(result))
+ || (ndr_dsn != 0 && !IS_NDR_DSN(result))) {
+ msg_warn("%s: bad status code: %s", fp->maps->title, result);
+ return (0);
+ } else {
+ vstring_strcpy(fp->buffer, result);
+ dsn_split(&fp->dp, "can't happen", STR(fp->buffer));
+ (void) DSN_ASSIGN(&fp->dsn, DSN_STATUS(fp->dp.dsn),
+ (result[0] == '4' ? "delayed" :
+ result[0] == '5' ? "failed" :
+ dsn->action),
+ fp->dp.text, dsn->dtype, dsn->dtext,
+ dsn->mtype, dsn->mname);
+ return (&fp->dsn);
+ }
+ }
+ return (0);
+}
+
+/* dsn_filter_free - destroy delivery status filter */
+
+void dsn_filter_free(DSN_FILTER *fp)
+{
+ static const char myname[] = "dsn_filter_free";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, fp->maps->title);
+
+ maps_free(fp->maps);
+ vstring_free(fp->buffer);
+ myfree((void *) fp);
+}
diff --git a/src/global/dsn_filter.h b/src/global/dsn_filter.h
new file mode 100644
index 0000000..f5e1378
--- /dev/null
+++ b/src/global/dsn_filter.h
@@ -0,0 +1,34 @@
+#ifndef _DSN_FILTER_H_INCLUDED_
+#define _DSN_FILTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn_filter 3h
+/* SUMMARY
+/* delivery status filter
+/* SYNOPSIS
+/* #include <dsn_filter.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct DSN_FILTER DSN_FILTER;
+
+extern DSN_FILTER *dsn_filter_create(const char *, const char *);
+extern DSN *dsn_filter_lookup(DSN_FILTER *, DSN *);
+extern void dsn_filter_free(DSN_FILTER *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn_mask.c b/src/global/dsn_mask.c
new file mode 100644
index 0000000..b45342e
--- /dev/null
+++ b/src/global/dsn_mask.c
@@ -0,0 +1,123 @@
+/*++
+/* NAME
+/* dsn_mask 3
+/* SUMMARY
+/* DSN embedding in SMTP
+/* SYNOPSIS
+/* #include <dsn_mask.h>
+/*
+/* int dsn_notify_mask(str)
+/* const char *str;
+/*
+/* const char *dsn_notify_str(mask)
+/* int mask;
+/*
+/* int dsn_ret_code(str)
+/* const char *str;
+/*
+/* const char *dsn_ret_str(code)
+/* int mask;
+/* DESCRIPTION
+/* dsn_ret_code() converts the parameters of a MAIL FROM ..
+/* RET option to internal form.
+/*
+/* dsn_ret_str() converts internal form to the representation
+/* used in the MAIL FROM .. RET command. The result is in
+/* stable and static memory.
+/*
+/* dsn_notify_mask() converts the parameters of a RCPT TO ..
+/* NOTIFY option to internal form.
+/*
+/* dsn_notify_str() converts internal form to the representation
+/* used in the RCPT TO .. NOTIFY command. The result is in
+/* volatile memory and is clobbered whenever str_name_mask()
+/* is called.
+/*
+/* Arguments:
+/* .IP str
+/* Information received with the MAIL FROM or RCPT TO command.
+/* .IP mask
+/* Internal representation.
+/* DIAGNOSTICS
+/* dsn_ret_code() and dsn_notify_mask() return 0 when the string
+/* specifies an invalid request.
+/*
+/* dsn_ret_str() and dsn_notify_str() abort on failure.
+/* 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 <name_code.h>
+#include <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <dsn_mask.h>
+
+/* Application-specific. */
+
+static const NAME_MASK dsn_notify_table[] = {
+ "NEVER", DSN_NOTIFY_NEVER,
+ "SUCCESS", DSN_NOTIFY_SUCCESS,
+ "FAILURE", DSN_NOTIFY_FAILURE,
+ "DELAY", DSN_NOTIFY_DELAY,
+ 0, 0,
+};
+
+static const NAME_CODE dsn_ret_table[] = {
+ "FULL", DSN_RET_FULL,
+ "HDRS", DSN_RET_HDRS,
+ 0, 0,
+};
+
+/* dsn_ret_code - string to mask */
+
+int dsn_ret_code(const char *str)
+{
+ return (name_code(dsn_ret_table, NAME_CODE_FLAG_NONE, str));
+}
+
+/* dsn_ret_str - mask to string */
+
+const char *dsn_ret_str(int code)
+{
+ const char *cp;
+
+ if ((cp = str_name_code(dsn_ret_table, code)) == 0)
+ msg_panic("dsn_ret_str: unknown code %d", code);
+ return (cp);
+}
+
+/* dsn_notify_mask - string to mask */
+
+int dsn_notify_mask(const char *str)
+{
+ int mask = name_mask_opt("DSN NOTIFY command", dsn_notify_table,
+ str, NAME_MASK_ANY_CASE | NAME_MASK_RETURN);
+
+ return (DSN_NOTIFY_OK(mask) ? mask : 0);
+}
+
+/* dsn_notify_str - mask to string */
+
+const char *dsn_notify_str(int mask)
+{
+ return (str_name_mask_opt((VSTRING *) 0, "DSN NOTIFY command",
+ dsn_notify_table, mask,
+ NAME_MASK_FATAL | NAME_MASK_COMMA));
+}
diff --git a/src/global/dsn_mask.h b/src/global/dsn_mask.h
new file mode 100644
index 0000000..ddf3dcc
--- /dev/null
+++ b/src/global/dsn_mask.h
@@ -0,0 +1,91 @@
+#ifndef _DSN_MASK_H_INCLUDED_
+#define _DSN_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn_mask 3h
+/* SUMMARY
+/* DSN embedding in SMTP
+/* SYNOPSIS
+/* #include "dsn_mask.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Support for MAIL FROM ... RET=mumble.
+ */
+#define DSN_RET_FULL (1<<0)
+#define DSN_RET_HDRS (1<<1)
+#define DSN_RET_BITS (2)
+
+ /*
+ * Use this to filter bad content in queue files.
+ */
+#define DSN_RET_OK(v) ((v) == DSN_RET_FULL || (v) == DSN_RET_HDRS)
+
+ /*
+ * Only when RET is specified by the sender is the SMTP client allowed to
+ * specify RET=mumble while delivering mail (RFC 3461 section 5.2.1).
+ * However, if RET is not requested, then the MTA is allowed to interpret
+ * this as RET=FULL or RET=HDRS (RFC 3461 section 4.3). Postfix chooses the
+ * former.
+ */
+
+ /*
+ * Conversion routines: string to mask and reverse.
+ */
+extern int dsn_ret_code(const char *);
+extern const char *dsn_ret_str(int);
+
+ /*
+ * Support for RCPT TO ... NOTIFY=mumble is in the form of bit masks.
+ */
+#define DSN_NOTIFY_NEVER (1<<0) /* must not */
+#define DSN_NOTIFY_SUCCESS (1<<1) /* must */
+#define DSN_NOTIFY_FAILURE (1<<2) /* must */
+#define DSN_NOTIFY_DELAY (1<<3) /* may */
+#define DSN_NOTIFY_BITS (4)
+
+ /*
+ * Any form of sender-requested notification.
+ */
+#define DSN_NOTIFY_ANY \
+ (DSN_NOTIFY_SUCCESS | DSN_NOTIFY_FAILURE | DSN_NOTIFY_DELAY)
+
+ /*
+ * Override the sender-specified notification restriction.
+ */
+#define DSN_NOTIFY_OVERRIDE (DSN_NOTIFY_ANY | DSN_NOTIFY_NEVER)
+
+ /*
+ * Use this to filter bad content in queue files.
+ */
+#define DSN_NOTIFY_OK(v) \
+ ((v) == DSN_NOTIFY_NEVER || (v) == ((v) & DSN_NOTIFY_ANY))
+
+ /*
+ * Only when NOTIFY=something was requested by the sender is the SMTP client
+ * allowed to specify NOTIFY=mumble while delivering mail (RFC 3461 section
+ * 5.2.1). However, if NOTIFY is not requested, then the MTA is allowed to
+ * interpret this as NOTIFY=FAILURE or NOTIFY=FAILURE,DELAY (RFC 3461
+ * section 4.1). Postfix chooses the latter.
+ */
+
+ /*
+ * Conversion routines: string to mask and reverse.
+ */
+extern int dsn_notify_mask(const char *);
+extern const char *dsn_notify_str(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn_print.c b/src/global/dsn_print.c
new file mode 100644
index 0000000..fde2c34
--- /dev/null
+++ b/src/global/dsn_print.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* dsn_print
+/* SUMMARY
+/* write DSN structure to stream
+/* SYNOPSIS
+/* #include <dsn_print.h>
+/*
+/* int dsn_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* dsn_print() writes a DSN structure to the named stream using
+/* the specified attribute print routine. dsn_print() is meant
+/* to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(dsn_print, (const void *) dsn), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <dsn_print.h>
+
+/* dsn_print - write DSN to stream */
+
+int dsn_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ DSN *dsn = (DSN *) ptr;
+ int ret;
+
+ /*
+ * The attribute order is determined by backwards compatibility. It can
+ * be sanitized after all the ad-hoc DSN read/write code is replaced.
+ */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_DSN_STATUS, dsn->status),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_DTYPE, dsn->dtype),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_DTEXT, dsn->dtext),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_MTYPE, dsn->mtype),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_MNAME, dsn->mname),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ACTION, dsn->action),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason),
+ ATTR_TYPE_END);
+ return (ret);
+}
diff --git a/src/global/dsn_print.h b/src/global/dsn_print.h
new file mode 100644
index 0000000..d258e6e
--- /dev/null
+++ b/src/global/dsn_print.h
@@ -0,0 +1,46 @@
+#ifndef _DSN_PRINT_H_INCLUDED_
+#define _DSN_PRINT_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn_print 3h
+/* SUMMARY
+/* write DSN structure to stream
+/* SYNOPSIS
+/* #include <dsn_print.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn.h>
+
+ /*
+ * External interface.
+ */
+extern int dsn_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dsn_util.c b/src/global/dsn_util.c
new file mode 100644
index 0000000..52b997a
--- /dev/null
+++ b/src/global/dsn_util.c
@@ -0,0 +1,183 @@
+/*++
+/* NAME
+/* dsn_util 3
+/* SUMMARY
+/* DSN status parsing routines
+/* SYNOPSIS
+/* #include <dsn_util.h>
+/*
+/* #define DSN_SIZE ...
+/*
+/* typedef struct { ... } DSN_BUF;
+/*
+/* typedef struct {
+/* .in +4
+/* DSN_STAT dsn; /* RFC 3463 status */
+/* const char *text; /* Free text */
+/* .in -4
+/* } DSN_SPLIT;
+/*
+/* DSN_SPLIT *dsn_split(dp, def_dsn, text)
+/* DSN_SPLIT *dp;
+/* const char *def_dsn;
+/* const char *text;
+/*
+/* char *dsn_prepend(def_dsn, text)
+/* const char *def_dsn;
+/* const char *text;
+/*
+/* size_t dsn_valid(text)
+/* const char *text;
+/*
+/* void DSN_UPDATE(dsn_buf, dsn, len)
+/* DSN_BUF dsn_buf;
+/* const char *dsn;
+/* size_t len;
+/*
+/* const char *DSN_CODE(dsn_buf)
+/* DSN_BUF dsn_buf;
+/*
+/* char *DSN_CLASS(dsn_buf)
+/* DSN_BUF dsn_buf;
+/* DESCRIPTION
+/* The functions in this module manipulate pairs of RFC 3463
+/* status codes and descriptive free text.
+/*
+/* dsn_split() splits text into an RFC 3463 status code and
+/* descriptive free text. When the text does not start with
+/* a status code, the specified default status code is used
+/* instead. Whitespace before the optional status code or
+/* text is skipped. dsn_split() returns a copy of the RFC
+/* 3463 status code, and returns a pointer to (not copy of)
+/* the remainder of the text. The result value is the first
+/* argument.
+/*
+/* dsn_prepend() prepends the specified default RFC 3463 status
+/* code to the specified text if no status code is present in
+/* the text. This function produces the same result as calling
+/* concatenate() with the results from dsn_split(). The result
+/* should be passed to myfree(). Whitespace before the optional
+/* status code or text is skipped.
+/*
+/* dsn_valid() returns the length of the RFC 3463 status code
+/* at the beginning of text, or zero. It does not skip initial
+/* whitespace.
+/*
+/* Arguments:
+/* .IP def_dsn
+/* Null-terminated default RFC 3463 status code that will be
+/* used when the free text does not start with one.
+/* .IP dp
+/* Pointer to storage for copy of DSN status code, and for
+/* pointer to free text.
+/* .IP dsn
+/* Null-terminated RFC 3463 status code.
+/* .IP text
+/* Null-terminated free text.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Panic: invalid default DSN 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <dsn_util.h>
+
+/* dsn_valid - check RFC 3463 enhanced status code, return length or zero */
+
+size_t dsn_valid(const char *text)
+{
+ const unsigned char *cp = (unsigned char *) text;
+ size_t len;
+
+ /* First portion is one digit followed by dot. */
+ if ((cp[0] != '2' && cp[0] != '4' && cp[0] != '5') || cp[1] != '.')
+ return (0);
+
+ /* Second portion is 1-3 digits followed by dot. */
+ cp += 2;
+ if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS2
+ || cp[len] != '.')
+ return (0);
+
+ /* Last portion is 1-3 digits followed by end-of-string or whitespace. */
+ cp += len + 1;
+ if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS3
+ || (cp[len] != 0 && !ISSPACE(cp[len])))
+ return (0);
+
+ return (((char *) cp - text) + len);
+}
+
+/* dsn_split - split text into DSN and text */
+
+DSN_SPLIT *dsn_split(DSN_SPLIT *dp, const char *def_dsn, const char *text)
+{
+ const char *myname = "dsn_split";
+ const char *cp = text;
+ size_t len;
+
+ /*
+ * Look for an optional RFC 3463 enhanced status code.
+ *
+ * XXX If we want to enforce that the first digit of the status code in the
+ * text matches the default status code, then pipe_command() needs to be
+ * changed. It currently auto-detects the reply code without knowing in
+ * advance if the result will start with '4' or '5'.
+ */
+ while (ISSPACE(*cp))
+ cp++;
+ if ((len = dsn_valid(cp)) > 0) {
+ strncpy(dp->dsn.data, cp, len);
+ dp->dsn.data[len] = 0;
+ cp += len + 1;
+ } else if ((len = dsn_valid(def_dsn)) > 0) {
+ strncpy(dp->dsn.data, def_dsn, len);
+ dp->dsn.data[len] = 0;
+ } else {
+ msg_panic("%s: bad default status \"%s\"", myname, def_dsn);
+ }
+
+ /*
+ * The remainder is free text.
+ */
+ while (ISSPACE(*cp))
+ cp++;
+ dp->text = cp;
+
+ return (dp);
+}
+
+/* dsn_prepend - prepend optional status to text, result on heap */
+
+char *dsn_prepend(const char *def_dsn, const char *text)
+{
+ DSN_SPLIT dp;
+
+ dsn_split(&dp, def_dsn, text);
+ return (concatenate(DSN_STATUS(dp.dsn), " ", dp.text, (char *) 0));
+}
diff --git a/src/global/dsn_util.h b/src/global/dsn_util.h
new file mode 100644
index 0000000..8657a3e
--- /dev/null
+++ b/src/global/dsn_util.h
@@ -0,0 +1,77 @@
+#ifndef _DSN_UTIL_H_INCLUDED_
+#define _DSN_UTIL_H_INCLUDED_
+
+/*++
+/* NAME
+/* dsn_util 3h
+/* SUMMARY
+/* DSN status parsing routines
+/* SYNOPSIS
+/* #include <dsn_util.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Detail format is digit "." digit{1,3} "." digit{1,3}.
+ */
+#define DSN_DIGS1 1 /* leading digits */
+#define DSN_DIGS2 3 /* middle digits */
+#define DSN_DIGS3 3 /* trailing digits */
+#define DSN_LEN (DSN_DIGS1 + 1 + DSN_DIGS2 + 1 + DSN_DIGS3)
+#define DSN_SIZE (DSN_LEN + 1)
+
+ /*
+ * Storage for an enhanced status code. Avoid using malloc for itty-bitty
+ * strings with a known size limit.
+ *
+ * XXX gcc version 2 complains about sizeof() as format width specifier.
+ */
+typedef struct {
+ char data[DSN_SIZE]; /* NOT a public interface */
+} DSN_STAT;
+
+#define DSN_UPDATE(dsn_buf, dsn, len) do { \
+ if (len >= sizeof((dsn_buf).data)) \
+ msg_panic("DSN_UPDATE: bad DSN code \"%.*s...\" length %d", \
+ INT_SIZEOF((dsn_buf).data) - 1, dsn, len); \
+ strncpy((dsn_buf).data, (dsn), (len)); \
+ (dsn_buf).data[len] = 0; \
+ } while (0)
+
+#define DSN_STATUS(dsn_buf) ((const char *) (dsn_buf).data)
+
+#define DSN_CLASS(dsn_buf) ((dsn_buf).data[0])
+
+ /*
+ * Split flat text into detail code and free text.
+ */
+typedef struct {
+ DSN_STAT dsn; /* RFC 3463 status */
+ const char *text; /* free text */
+} DSN_SPLIT;
+
+extern DSN_SPLIT *dsn_split(DSN_SPLIT *, const char *, const char *);
+extern size_t dsn_valid(const char *);
+
+ /*
+ * Create flat text from detail code and free text.
+ */
+extern char *dsn_prepend(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/dynamicmaps.c b/src/global/dynamicmaps.c
new file mode 100644
index 0000000..f0213d3
--- /dev/null
+++ b/src/global/dynamicmaps.c
@@ -0,0 +1,367 @@
+/*++
+/* NAME
+/* dynamicmaps 3
+/* SUMMARY
+/* load dictionaries dynamically
+/* SYNOPSIS
+/* #include <dynamicmaps.h>
+/*
+/* void dymap_init(const char *conf_path, const char *plugin_dir)
+/* DESCRIPTION
+/* This module reads the dynamicmaps.cf file and performs
+/* run-time loading of Postfix dictionaries. Each dynamicmaps.cf
+/* entry specifies the name of a dictionary type, the pathname
+/* of a shared-library object, the name of a "dict_open"
+/* function for access to individual dictionary entries, and
+/* optionally the name of a "mkmap_open" function for bulk-mode
+/* dictionary creation. Plugins may be specified with a relative
+/* pathname.
+/*
+/* A dictionary may be installed without editing the file
+/* dynamicmaps.cf, by placing a configuration file under the
+/* directory dynamicmaps.cf.d, with the same format as
+/* dynamicmaps.cf.
+/*
+/* dymap_init() reads the specified configuration file which
+/* is in dynamicmaps.cf format, and hooks itself into the
+/* dict_open(), dict_mapnames(), and mkmap_open() functions.
+/*
+/* dymap_init() may be called multiple times during a process
+/* lifetime, but it will not "unload" dictionaries that have
+/* already been linked into the process address space, nor
+/* will it hide their dictionaries types from later "open"
+/* requests.
+/*
+/* Arguments:
+/* .IP conf_path
+/* Pathname for the dynamicmaps configuration file.
+/* .IP plugin_dir
+/* Default directory for plugins with a relative pathname.
+/* SEE ALSO
+/* load_lib(3) low-level run-time linker adapter
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, dictionary or
+/* dictionary function not available. Panic: invalid use.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* 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>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <argv.h>
+#include <dict.h>
+#include <load_lib.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <scan_dir.h>
+
+ /*
+ * Global library.
+ */
+#include <mkmap.h>
+#include <dynamicmaps.h>
+
+#ifdef USE_DYNAMIC_MAPS
+
+ /*
+ * Contents of one dynamicmaps.cf entry.
+ */
+typedef struct {
+ char *soname; /* shared-object file name */
+ char *dict_name; /* dict_xx_open() function name */
+ char *mkmap_name; /* mkmap_xx_open() function name */
+} DYMAP_INFO;
+
+static HTABLE *dymap_info;
+static int dymap_hooks_done = 0;
+static DICT_OPEN_EXTEND_FN saved_dict_open_hook = 0;
+static MKMAP_OPEN_EXTEND_FN saved_mkmap_open_hook = 0;
+static DICT_MAPNAMES_EXTEND_FN saved_dict_mapnames_hook = 0;
+
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* dymap_dict_lookup - look up "dict_foo_open" function */
+
+static DICT_OPEN_FN dymap_dict_lookup(const char *dict_type)
+{
+ struct stat st;
+ LIB_FN fn[2];
+ DICT_OPEN_FN dict_open_fn;
+ DYMAP_INFO *dp;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_dict_open_hook != 0
+ && (dict_open_fn = saved_dict_open_hook(dict_type)) != 0)
+ return (dict_open_fn);
+
+ /*
+ * Allow for graceful degradation when a database is unavailable. This
+ * allows Postfix daemon processes to continue handling email with
+ * reduced functionality.
+ */
+ if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0)
+ return (0);
+ if (stat(dp->soname, &st) < 0) {
+ msg_warn("unsupported dictionary type: %s (%s: %m)",
+ dict_type, dp->soname);
+ return (0);
+ }
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ msg_warn("unsupported dictionary type: %s "
+ "(%s: file is owned or writable by non-root users)",
+ dict_type, dp->soname);
+ return (0);
+ }
+ fn[0].name = dp->dict_name;
+ fn[1].name = 0;
+ load_library_symbols(dp->soname, fn, (LIB_DP *) 0);
+ return ((DICT_OPEN_FN) fn[0].fptr);
+}
+
+/* dymap_mkmap_lookup - look up "mkmap_foo_open" function */
+
+static MKMAP_OPEN_FN dymap_mkmap_lookup(const char *dict_type)
+{
+ struct stat st;
+ LIB_FN fn[2];
+ MKMAP_OPEN_FN mkmap_open_fn;
+ DYMAP_INFO *dp;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_mkmap_open_hook != 0
+ && (mkmap_open_fn = saved_mkmap_open_hook(dict_type)) != 0)
+ return (mkmap_open_fn);
+
+ /*
+ * All errors are fatal. If the postmap(1) or postalias(1) command can't
+ * create the requested database, then graceful degradation is not
+ * useful.
+ *
+ * Fix 20220416: if this dictionary type is registered for some non-mkmap
+ * purpose, then don't talk nonsense about a missing package.
+ */
+ if ((dp = (DYMAP_INFO *) htable_find(dymap_info, dict_type)) == 0) {
+ ARGV *types = dict_mapnames();
+ char **cpp;
+
+ for (cpp = types->argv; *cpp; cpp++) {
+ if (strcmp(dict_type, *cpp) == 0)
+ msg_fatal("unsupported dictionary type: %s does not support "
+ "bulk-mode creation.", dict_type);
+ }
+ msg_fatal("unsupported dictionary type: %s. "
+ "Is the postfix-%s package installed?",
+ dict_type, dict_type);
+ }
+ if (!dp->mkmap_name)
+ msg_fatal("unsupported dictionary type: %s does not support "
+ "bulk-mode creation.", dict_type);
+ if (stat(dp->soname, &st) < 0)
+ msg_fatal("unsupported dictionary type: %s (%s: %m). "
+ "Is the postfix-%s package installed?",
+ dict_type, dp->soname, dict_type);
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0)
+ msg_fatal("unsupported dictionary type: %s "
+ "(%s: file is owned or writable by non-root users)",
+ dict_type, dp->soname);
+ fn[0].name = dp->mkmap_name;
+ fn[1].name = 0;
+ load_library_symbols(dp->soname, fn, (LIB_DP *) 0);
+ return ((MKMAP_OPEN_FN) fn[0].fptr);
+}
+
+/* dymap_list - enumerate dynamically-linked database type names */
+
+static void dymap_list(ARGV *map_names)
+{
+ HTABLE_INFO **ht_list, **ht;
+
+ /*
+ * Respect the hook nesting order.
+ */
+ if (saved_dict_mapnames_hook != 0)
+ saved_dict_mapnames_hook(map_names);
+
+ for (ht_list = ht = htable_list(dymap_info); *ht != 0; ht++)
+ argv_add(map_names, ht[0]->key, ARGV_END);
+ myfree((void *) ht_list);
+}
+
+/* dymap_entry_alloc - allocate dynamicmaps.cf entry */
+
+static DYMAP_INFO *dymap_entry_alloc(char **argv)
+{
+ DYMAP_INFO *dp;
+
+ dp = (DYMAP_INFO *) mymalloc(sizeof(*dp));
+ dp->soname = mystrdup(argv[0]);
+ dp->dict_name = mystrdup(argv[1]);
+ dp->mkmap_name = argv[2] ? mystrdup(argv[2]) : 0;
+ return (dp);
+}
+
+/* dymap_entry_free - htable(3) call-back to destroy dynamicmaps.cf entry */
+
+static void dymap_entry_free(void *ptr)
+{
+ DYMAP_INFO *dp = (DYMAP_INFO *) ptr;
+
+ myfree(dp->soname);
+ myfree(dp->dict_name);
+ if (dp->mkmap_name)
+ myfree(dp->mkmap_name);
+ myfree((void *) dp);
+}
+
+/* dymap_read_conf - read dynamicmaps.cf-like file */
+
+static void dymap_read_conf(const char *path, const char *path_base)
+{
+ VSTREAM *fp;
+ VSTRING *buf;
+ char *cp;
+ ARGV *argv;
+ int linenum = 0;
+ struct stat st;
+
+ /*
+ * Silently ignore a missing dynamicmaps.cf file, but be explicit about
+ * problems when the file does exist.
+ */
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) != 0) {
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("%s: fstat failed; %m", path);
+ if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ msg_warn("%s: file is owned or writable by non-root users"
+ " -- skipping this file", path);
+ } else {
+ buf = vstring_alloc(100);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
+ cp = vstring_str(buf);
+ linenum++;
+ if (*cp == '#' || *cp == '\0')
+ continue;
+ argv = argv_split(cp, " \t");
+ if (argv->argc != 3 && argv->argc != 4)
+ msg_fatal("%s, line %d: Expected \"dict-type .so-name dict"
+ "-function [mkmap-function]\"", path, linenum);
+ if (!ISALNUM(argv->argv[0][0]))
+ msg_fatal("%s, line %d: unsupported syntax \"%s\"",
+ path, linenum, argv->argv[0]);
+ if (argv->argv[1][0] != '/') {
+ cp = concatenate(path_base, "/", argv->argv[1], (char *) 0);
+ argv_replace_one(argv, 1, cp);
+ myfree(cp);
+ }
+ if (htable_locate(dymap_info, argv->argv[0]) != 0)
+ msg_warn("%s: ignoring duplicate entry for \"%s\"",
+ path, argv->argv[0]);
+ else
+ htable_enter(dymap_info, argv->argv[0],
+ (void *) dymap_entry_alloc(argv->argv + 1));
+ argv_free(argv);
+ }
+ vstring_free(buf);
+
+ /*
+ * Once-only: hook into the dict_open(3) and mkmap_open(3)
+ * infrastructure,
+ */
+ if (dymap_hooks_done == 0) {
+ dymap_hooks_done = 1;
+ saved_dict_open_hook = dict_open_extend(dymap_dict_lookup);
+ saved_mkmap_open_hook = mkmap_open_extend(dymap_mkmap_lookup);
+ saved_dict_mapnames_hook = dict_mapnames_extend(dymap_list);
+ }
+ }
+ vstream_fclose(fp);
+ } else if (errno != ENOENT) {
+ msg_fatal("%s: file open failed: %m", path);
+ }
+}
+
+/* dymap_init - initialize dictionary type to soname etc. mapping */
+
+void dymap_init(const char *conf_path, const char *plugin_dir)
+{
+ static const char myname[] = "dymap_init";
+ SCAN_DIR *dir;
+ char *conf_path_d;
+ const char *conf_name;
+ VSTRING *sub_conf_path;
+
+ /*
+ * Reload dynamicmaps.cf, but don't reload already-loaded plugins.
+ */
+ if (dymap_info != 0)
+ htable_free(dymap_info, dymap_entry_free);
+ dymap_info = htable_create(3);
+
+ /*
+ * Read dynamicmaps.cf.
+ */
+ dymap_read_conf(conf_path, plugin_dir);
+
+ /*
+ * Read dynamicmaps.cf.d/filename entries.
+ */
+ conf_path_d = concatenate(conf_path, ".d", (char *) 0);
+ if (access(conf_path_d, R_OK | X_OK) == 0
+ && (dir = scan_dir_open(conf_path_d)) != 0) {
+ sub_conf_path = vstring_alloc(100);
+ while ((conf_name = scan_dir_next(dir)) != 0) {
+ vstring_sprintf(sub_conf_path, "%s/%s", conf_path_d, conf_name);
+ dymap_read_conf(vstring_str(sub_conf_path), plugin_dir);
+ }
+ if (errno != 0)
+ /* Don't crash all programs - degrade gracefully. */
+ msg_warn("%s: directory read error: %m", conf_path_d);
+ scan_dir_close(dir);
+ vstring_free(sub_conf_path);
+ } else if (errno != ENOENT) {
+ /* Don't crash all programs - degrade gracefully. */
+ msg_warn("%s: directory open failed: %m", conf_path_d);
+ }
+ myfree(conf_path_d);
+
+ /*
+ * Future proofing, in case someone "improves" the code. We can't hook
+ * into other functions without initializing our private lookup table.
+ */
+ if (dymap_hooks_done != 0 && dymap_info == 0)
+ msg_panic("%s: post-condition botch", myname);
+}
+
+#endif
diff --git a/src/global/dynamicmaps.h b/src/global/dynamicmaps.h
new file mode 100644
index 0000000..1ac1f41
--- /dev/null
+++ b/src/global/dynamicmaps.h
@@ -0,0 +1,38 @@
+#ifndef _DYNAMICMAPS_H_INCLUDED_
+#define _DYNAMICMAPS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dynamicmaps 3h
+/* SUMMARY
+/* load dictionaries dynamically
+/* SYNOPSIS
+/* #include <dynamicmaps.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifdef USE_DYNAMIC_LIBS
+
+extern void dymap_init(const char *, const char *);
+
+#endif
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/global/ehlo_mask.c b/src/global/ehlo_mask.c
new file mode 100644
index 0000000..df905e1
--- /dev/null
+++ b/src/global/ehlo_mask.c
@@ -0,0 +1,145 @@
+/*++
+/* NAME
+/* ehlo_mask 3
+/* SUMMARY
+/* map EHLO keywords to bit mask
+/* SYNOPSIS
+/* #include <ehlo_mask.h>
+/*
+/* #define EHLO_MASK_8BITMIME (1<<0)
+/* #define EHLO_MASK_PIPELINING (1<<1)
+/* #define EHLO_MASK_SIZE (1<<2)
+/* #define EHLO_MASK_VRFY (1<<3)
+/* #define EHLO_MASK_ETRN (1<<4)
+/* #define EHLO_MASK_AUTH (1<<5)
+/* #define EHLO_MASK_VERP (1<<6)
+/* #define EHLO_MASK_STARTTLS (1<<7)
+/* #define EHLO_MASK_XCLIENT (1<<8)
+/* #define EHLO_MASK_XFORWARD (1<<9)
+/* #define EHLO_MASK_ENHANCEDSTATUSCODES (1<<10)
+/* #define EHLO_MASK_DSN (1<<11)
+/* #define EHLO_MASK_SMTPUTF8 (1<<12)
+/* #define EHLO_MASK_CHUNKING (1<<13)
+/* #define EHLO_MASK_SILENT (1<<15)
+/*
+/* int ehlo_mask(keyword_list)
+/* const char *keyword_list;
+/*
+/* const char *str_ehlo_mask(bitmask)
+/* int bitmask;
+/* DESCRIPTION
+/* ehlo_mask() computes the bit-wise OR of the masks that correspond
+/* to the names listed in the \fIkeyword_list\fR argument, separated by
+/* comma and/or whitespace characters. Undefined names are silently
+/* ignored.
+/*
+/* str_ehlo_mask() translates a mask into its equivalent names.
+/* The result is written to a static buffer that is overwritten
+/* upon each call. Undefined bits cause a fatal run-time error.
+/* DIAGNOSTICS
+/* Fatal: str_ehlo_mask() found an undefined bit.
+/* 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 <name_mask.h>
+
+/* Global library. */
+
+#include <ehlo_mask.h>
+
+ /*
+ * The lookup table.
+ */
+static const NAME_MASK ehlo_mask_table[] = {
+ "8BITMIME", EHLO_MASK_8BITMIME,
+ "AUTH", EHLO_MASK_AUTH,
+ "ETRN", EHLO_MASK_ETRN,
+ "PIPELINING", EHLO_MASK_PIPELINING,
+ "SIZE", EHLO_MASK_SIZE,
+ "VERP", EHLO_MASK_VERP,
+ "VRFY", EHLO_MASK_VRFY,
+ "XCLIENT", EHLO_MASK_XCLIENT,
+ "XFORWARD", EHLO_MASK_XFORWARD,
+ "STARTTLS", EHLO_MASK_STARTTLS,
+ "ENHANCEDSTATUSCODES", EHLO_MASK_ENHANCEDSTATUSCODES,
+ "DSN", EHLO_MASK_DSN,
+ "EHLO_MASK_SMTPUTF8", EHLO_MASK_SMTPUTF8,
+ "SMTPUTF8", EHLO_MASK_SMTPUTF8,
+ "CHUNKING", EHLO_MASK_CHUNKING,
+ "SILENT-DISCARD", EHLO_MASK_SILENT, /* XXX In-band signaling */
+ 0,
+};
+
+/* ehlo_mask - string to bit mask */
+
+int ehlo_mask(const char *mask_str)
+{
+
+ /*
+ * We allow "STARTTLS" besides "starttls, because EHLO keywords are often
+ * spelled in uppercase. We ignore non-existent EHLO keywords so people
+ * can switch between Postfix versions without trouble.
+ */
+ return (name_mask_opt("ehlo string mask", ehlo_mask_table,
+ mask_str, NAME_MASK_ANY_CASE | NAME_MASK_IGNORE));
+}
+
+/* str_ehlo_mask - mask to string */
+
+const char *str_ehlo_mask(int mask_bits)
+{
+
+ /*
+ * We don't allow non-existent bits. Doing so makes no sense at this
+ * time.
+ */
+ return (str_name_mask("ehlo bitmask", ehlo_mask_table, mask_bits));
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program.
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ int mask_bits;
+ VSTRING *buf = vstring_alloc(1);
+ const char *mask_string;
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ mask_bits = ehlo_mask(vstring_str(buf));
+ mask_string = str_ehlo_mask(mask_bits);
+ vstream_printf("%s -> 0x%x -> %s\n", vstring_str(buf), mask_bits,
+ mask_string);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/ehlo_mask.h b/src/global/ehlo_mask.h
new file mode 100644
index 0000000..9a31897
--- /dev/null
+++ b/src/global/ehlo_mask.h
@@ -0,0 +1,53 @@
+#ifndef _EHLO_MASK_H_INCLUDED_
+#define _EHLO_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* name_mask 3h
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <name_mask.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define EHLO_MASK_8BITMIME (1<<0) /* start of first byte */
+#define EHLO_MASK_PIPELINING (1<<1)
+#define EHLO_MASK_SIZE (1<<2)
+#define EHLO_MASK_VRFY (1<<3)
+#define EHLO_MASK_ETRN (1<<4)
+#define EHLO_MASK_AUTH (1<<5)
+#define EHLO_MASK_VERP (1<<6)
+#define EHLO_MASK_STARTTLS (1<<7)
+
+#define EHLO_MASK_XCLIENT (1<<8) /* start of second byte */
+#define EHLO_MASK_XFORWARD (1<<9)
+#define EHLO_MASK_ENHANCEDSTATUSCODES (1<<10)
+#define EHLO_MASK_DSN (1<<11)
+#define EHLO_MASK_SMTPUTF8 (1<<12)
+#define EHLO_MASK_CHUNKING (1<<13)
+#define EHLO_MASK_SILENT (1<<15)
+
+extern int ehlo_mask(const char *);
+extern const char *str_ehlo_mask(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/ehlo_mask.in b/src/global/ehlo_mask.in
new file mode 100644
index 0000000..50fc248
--- /dev/null
+++ b/src/global/ehlo_mask.in
@@ -0,0 +1,3 @@
+starttls, 8bitmime, verp, etrn, etrn
+foobar, auth, pipelining, size, vrfy
+xclient, xforward
diff --git a/src/global/ehlo_mask.ref b/src/global/ehlo_mask.ref
new file mode 100644
index 0000000..e865ab6
--- /dev/null
+++ b/src/global/ehlo_mask.ref
@@ -0,0 +1,3 @@
+starttls, 8bitmime, verp, etrn, etrn -> 0xd1 -> 8BITMIME ETRN VERP STARTTLS
+foobar, auth, pipelining, size, vrfy -> 0x2e -> AUTH PIPELINING SIZE VRFY
+xclient, xforward -> 0x300 -> XCLIENT XFORWARD
diff --git a/src/global/ext_prop.c b/src/global/ext_prop.c
new file mode 100644
index 0000000..30395b4
--- /dev/null
+++ b/src/global/ext_prop.c
@@ -0,0 +1,78 @@
+/*++
+/* NAME
+/* ext_prop 3
+/* SUMMARY
+/* address extension propagation control
+/* SYNOPSIS
+/* #include <ext_prop.h>
+/*
+/* int ext_prop_mask(param_name, pattern)
+/* const char *param_name;
+/* const char *pattern;
+/* DESCRIPTION
+/* This module controls address extension propagation.
+/*
+/* ext_prop_mask() takes a comma-separated list of names and
+/* computes the corresponding mask. The following names are
+/* recognized in \fBpattern\fR, with the corresponding bit mask
+/* given in parentheses:
+/* .IP "canonical (EXT_PROP_CANONICAL)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of canonical table entries (not: regular expressions).
+/* .IP "virtual (EXT_PROP_VIRTUAL)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of virtual table entries (not: regular expressions).
+/* .IP "alias (EXT_PROP_ALIAS)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of alias database entries.
+/* .IP "forward (EXT_PROP_FORWARD)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of .forward file entries.
+/* .IP "include (EXT_PROP_INCLUDE)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of :include: file entries.
+/* .IP "generic (EXT_PROP_GENERIC)"
+/* Propagate unmatched address extensions to the right-hand side
+/* of smtp_generic_maps entries.
+/* DIAGNOSTICS
+/* Panic: inappropriate use.
+/* 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 <name_mask.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <ext_prop.h>
+
+/* ext_prop_mask - compute extension propagation mask */
+
+int ext_prop_mask(const char *param_name, const char *pattern)
+{
+ static const NAME_MASK table[] = {
+ "canonical", EXT_PROP_CANONICAL,
+ "virtual", EXT_PROP_VIRTUAL,
+ "alias", EXT_PROP_ALIAS,
+ "forward", EXT_PROP_FORWARD,
+ "include", EXT_PROP_INCLUDE,
+ "generic", EXT_PROP_GENERIC,
+ 0,
+ };
+
+ return (name_mask(param_name, table, pattern));
+}
diff --git a/src/global/ext_prop.h b/src/global/ext_prop.h
new file mode 100644
index 0000000..01f0ea0
--- /dev/null
+++ b/src/global/ext_prop.h
@@ -0,0 +1,37 @@
+#ifndef _EXT_PROP_INCLUDED_
+#define _EXT_PROP_INCLUDED_
+
+/*++
+/* NAME
+/* ext_prop 3h
+/* SUMMARY
+/* address extension propagation control
+/* SYNOPSIS
+/* #include <ext_prop.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define EXT_PROP_CANONICAL (1<<0)
+#define EXT_PROP_VIRTUAL (1<<1)
+#define EXT_PROP_ALIAS (1<<2)
+#define EXT_PROP_FORWARD (1<<3)
+#define EXT_PROP_INCLUDE (1<<4)
+#define EXT_PROP_GENERIC (1<<5)
+
+extern int ext_prop_mask(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/file_id.c b/src/global/file_id.c
new file mode 100644
index 0000000..b373056
--- /dev/null
+++ b/src/global/file_id.c
@@ -0,0 +1,101 @@
+/*++
+/* NAME
+/* file_id 3
+/* SUMMARY
+/* file ID printable representation
+/* SYNOPSIS
+/* #include <file_id.h>
+/*
+/* const char *get_file_id_fd(fd, long_flag)
+/* int fd;
+/* int long_flag;
+/*
+/* const char *get_file_id_st(st, long_flag)
+/* struct stat *st;
+/* int long_flag;
+/*
+/* const char *get_file_id(fd)
+/* int fd;
+/* DESCRIPTION
+/* get_file_id_fd() queries the operating system for the unique
+/* file identifier for the specified file descriptor and returns
+/* a printable representation. The result is volatile. Make
+/* a copy if it is to be used for any appreciable amount of
+/* time.
+/*
+/* get_file_id_st() returns the unique identifier for the
+/* specified file status information.
+/*
+/* get_file_id() provides binary compatibility for old programs.
+/* This function should not be used by new programs.
+/*
+/* Arguments:
+/* .IP fd
+/* A valid file descriptor that is associated with an open file.
+/* .IP st
+/* The result from e.g., stat(2) or fstat(2).
+/* .IP long_flag
+/* Encode the result as appropriate for long or short queue
+/* identifiers.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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>
+#include <string.h>
+
+/* Utility library */
+
+#include <msg.h>
+#include <vstring.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#define MAIL_QUEUE_INTERNAL
+#include <mail_queue.h>
+#include "file_id.h"
+
+/* get_file_id - binary compatibility */
+
+const char *get_file_id(int fd)
+{
+ return (get_file_id_fd(fd, 0));
+}
+
+/* get_file_id_fd - return printable file identifier for file descriptor */
+
+const char *get_file_id_fd(int fd, int long_flag)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ msg_fatal("fstat: %m");
+ return (get_file_id_st(&st, long_flag));
+}
+
+/* get_file_id_st - return printable file identifier for file status */
+
+const char *get_file_id_st(struct stat * st, int long_flag)
+{
+ static VSTRING *result;
+
+ if (result == 0)
+ result = vstring_alloc(1);
+ if (long_flag)
+ return (MQID_LG_ENCODE_INUM(result, st->st_ino));
+ else
+ return (MQID_SH_ENCODE_INUM(result, st->st_ino));
+}
diff --git a/src/global/file_id.h b/src/global/file_id.h
new file mode 100644
index 0000000..a20b0ab
--- /dev/null
+++ b/src/global/file_id.h
@@ -0,0 +1,38 @@
+#ifndef _FILE_ID_H_INCLUDED_
+#define _FILE_ID_H_INCLUDED_
+
+/*++
+/* NAME
+/* file_id 3h
+/* SUMMARY
+/* file ID printable representation
+/* SYNOPSIS
+/* #include <file_id.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+System library.
+*/
+#include <sys/stat.h>
+
+ /* External interface. */
+
+extern const char *get_file_id_fd(int, int);
+extern const char *get_file_id_st(struct stat *, int);
+
+ /* Legacy interface. */
+extern const char *get_file_id(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/flush_clnt.c b/src/global/flush_clnt.c
new file mode 100644
index 0000000..8084bf5
--- /dev/null
+++ b/src/global/flush_clnt.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* flush_clnt 3
+/* SUMMARY
+/* fast flush cache manager client interface
+/* SYNOPSIS
+/* #include <flush_clnt.h>
+/*
+/* void flush_init()
+/*
+/* int flush_add(site, queue_id)
+/* const char *site;
+/* const char *queue_id;
+/*
+/* int flush_send_site(site)
+/* const char *site;
+/*
+/* int flush_send_file(queue_id)
+/* const char *queue_id;
+/*
+/* int flush_refresh()
+/*
+/* int flush_purge()
+/* DESCRIPTION
+/* The following routines operate through the "fast flush" service.
+/* This service maintains a cache of what mail is queued. The cache
+/* is maintained for eligible destinations. A destination is the
+/* right-hand side of a user@domain email address.
+/*
+/* flush_init() initializes. It must be called before dropping
+/* privileges in a daemon process.
+/*
+/* flush_add() informs the "fast flush" cache manager that mail is
+/* queued for the specified site with the specified queue ID.
+/*
+/* flush_send_site() requests delivery of all mail that is queued for
+/* the specified destination.
+/*
+/* flush_send_file() requests delivery of mail with the specified
+/* queue ID.
+/*
+/* flush_refresh() requests the "fast flush" cache manager to refresh
+/* cached information that was not used for some configurable amount
+/* time.
+/*
+/* flush_purge() requests the "fast flush" cache manager to refresh
+/* all cached information. This is incredibly expensive, and is not
+/* recommended.
+/* DIAGNOSTICS
+/* The result codes and their meanings are (see flush_clnt(5h)):
+/* .IP MAIL_FLUSH_OK
+/* The request completed successfully (in case of requests that
+/* complete in the background: the request was accepted by the server).
+/* .IP MAIL_FLUSH_FAIL
+/* The request failed (the request could not be sent to the server,
+/* or the server reported failure).
+/* .IP MAIL_FLUSH_BAD
+/* The "fast flush" server rejected the request (invalid request
+/* parameter).
+/* .IP MAIL_FLUSH_DENY
+/* The specified domain is not eligible for "fast flush" service,
+/* or the "fast flush" service is disabled.
+/* SEE ALSO
+/* flush(8) Postfix fast flush cache manager
+/* 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 <unistd.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_flush.h>
+#include <mail_params.h>
+#include <domain_list.h>
+#include <match_parent_style.h>
+#include <flush_clnt.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+
+static DOMAIN_LIST *flush_domains;
+
+/* flush_init - initialize */
+
+void flush_init(void)
+{
+ flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_FFLUSH_DOMAINS),
+ var_fflush_domains);
+}
+
+/* flush_purge - house keeping */
+
+int flush_purge(void)
+{
+ const char *myname = "flush_purge";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s", myname);
+
+ /*
+ * Don't bother the server if the service is turned off.
+ */
+ if (*var_fflush_domains == 0)
+ status = FLUSH_STAT_DENY;
+ else
+ status = mail_command_client(MAIL_CLASS_PUBLIC, var_flush_service,
+ MAIL_ATTR_PROTO_FLUSH,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, FLUSH_REQ_PURGE),
+ ATTR_TYPE_END);
+
+ if (msg_verbose)
+ msg_info("%s: status %d", myname, status);
+
+ return (status);
+}
+
+/* flush_refresh - house keeping */
+
+int flush_refresh(void)
+{
+ const char *myname = "flush_refresh";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s", myname);
+
+ /*
+ * Don't bother the server if the service is turned off.
+ */
+ if (*var_fflush_domains == 0)
+ status = FLUSH_STAT_DENY;
+ else
+ status = mail_command_client(MAIL_CLASS_PUBLIC, var_flush_service,
+ MAIL_ATTR_PROTO_FLUSH,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, FLUSH_REQ_REFRESH),
+ ATTR_TYPE_END);
+
+ if (msg_verbose)
+ msg_info("%s: status %d", myname, status);
+
+ return (status);
+}
+
+/* flush_send_site - deliver mail queued for site */
+
+int flush_send_site(const char *site)
+{
+ const char *myname = "flush_send_site";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: site %s", myname, site);
+
+ /*
+ * Don't bother the server if the service is turned off, or if the site
+ * is not eligible.
+ */
+ if (flush_domains == 0)
+ msg_panic("missing flush client initialization");
+ if (domain_list_match(flush_domains, site) != 0) {
+ if (warn_compat_break_flush_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to flush "
+ "mail for domain \"%s\"", site);
+ status = mail_command_client(MAIL_CLASS_PUBLIC, var_flush_service,
+ MAIL_ATTR_PROTO_FLUSH,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, FLUSH_REQ_SEND_SITE),
+ SEND_ATTR_STR(MAIL_ATTR_SITE, site),
+ ATTR_TYPE_END);
+ } else if (flush_domains->error == 0)
+ status = FLUSH_STAT_DENY;
+ else
+ status = FLUSH_STAT_FAIL;
+
+ if (msg_verbose)
+ msg_info("%s: site %s status %d", myname, site, status);
+
+ return (status);
+}
+
+/* flush_send_file - deliver specific message */
+
+int flush_send_file(const char *queue_id)
+{
+ const char *myname = "flush_send_file";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: queue_id %s", myname, queue_id);
+
+ /*
+ * Require that the service is turned on.
+ */
+ status = mail_command_client(MAIL_CLASS_PUBLIC, var_flush_service,
+ MAIL_ATTR_PROTO_FLUSH,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, FLUSH_REQ_SEND_FILE),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ ATTR_TYPE_END);
+
+ if (msg_verbose)
+ msg_info("%s: queue_id %s status %d", myname, queue_id, status);
+
+ return (status);
+}
+
+/* flush_add - inform "fast flush" cache manager */
+
+int flush_add(const char *site, const char *queue_id)
+{
+ const char *myname = "flush_add";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: site %s id %s", myname, site, queue_id);
+
+ /*
+ * Don't bother the server if the service is turned off, or if the site
+ * is not eligible.
+ */
+ if (flush_domains == 0)
+ msg_panic("missing flush client initialization");
+ if (domain_list_match(flush_domains, site) != 0) {
+ if (warn_compat_break_flush_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to update "
+ "fast-flush logfile for domain \"%s\"", site);
+ status = mail_command_client(MAIL_CLASS_PUBLIC, var_flush_service,
+ MAIL_ATTR_PROTO_FLUSH,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, FLUSH_REQ_ADD),
+ SEND_ATTR_STR(MAIL_ATTR_SITE, site),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ ATTR_TYPE_END);
+ } else if (flush_domains->error == 0)
+ status = FLUSH_STAT_DENY;
+ else
+ status = FLUSH_STAT_FAIL;
+
+ if (msg_verbose)
+ msg_info("%s: site %s id %s status %d", myname, site, queue_id,
+ status);
+
+ return (status);
+}
diff --git a/src/global/flush_clnt.h b/src/global/flush_clnt.h
new file mode 100644
index 0000000..6b891b2
--- /dev/null
+++ b/src/global/flush_clnt.h
@@ -0,0 +1,53 @@
+#ifndef _FLUSH_CLNT_H_INCLUDED_
+#define _FLUSH_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* flush_clnt 3h
+/* SUMMARY
+/* flush backed up mail
+/* SYNOPSIS
+/* #include <flush_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void flush_init(void);
+extern int flush_add(const char *, const char *);
+extern int flush_send_site(const char *);
+extern int flush_send_file(const char *);
+extern int flush_refresh(void);
+extern int flush_purge(void);
+
+ /*
+ * Mail flush server requests.
+ */
+#define FLUSH_REQ_ADD "add" /* append queue ID to site log */
+#define FLUSH_REQ_SEND_SITE "send_site" /* flush mail for site */
+#define FLUSH_REQ_SEND_FILE "send_file" /* flush one queue file */
+#define FLUSH_REQ_REFRESH "rfrsh" /* refresh old logfiles */
+#define FLUSH_REQ_PURGE "purge" /* refresh all logfiles */
+
+ /*
+ * Mail flush server status codes.
+ */
+#define FLUSH_STAT_FAIL -1 /* request failed */
+#define FLUSH_STAT_OK 0 /* request executed */
+#define FLUSH_STAT_BAD 3 /* invalid parameter */
+#define FLUSH_STAT_DENY 4 /* request denied */
+
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/fold_addr.c b/src/global/fold_addr.c
new file mode 100644
index 0000000..86ee7b5
--- /dev/null
+++ b/src/global/fold_addr.c
@@ -0,0 +1,172 @@
+/*++
+/* NAME
+/* fold_addr 3
+/* SUMMARY
+/* address case folding
+/* SYNOPSIS
+/* #include <fold_addr.h>
+/*
+/* char *fold_addr(result, addr, flags)
+/* VSTRING *result;
+/* const char *addr;
+/* int flags;
+/* DESCRIPTION
+/* fold_addr() case folds an address according to the options
+/* specified with \fIflags\fR. The result value is the output
+/* address.
+/*
+/* Arguments
+/* .IP result
+/* Result buffer with the output address. Note: casefolding
+/* may change the string length.
+/* .IP addr
+/* Null-terminated read-only string with the input address.
+/* .IP flags
+/* Zero or the bit-wise OR of:
+/* .RS
+/* .IP FOLD_ADDR_USER
+/* Case fold the address local part.
+/* .IP FOLD_ADDR_HOST
+/* Case fold the address domain part.
+/* .IP FOLD_ADDR_ALL
+/* Alias for (FOLD_ADDR_USER | FOLD_ADDR_HOST).
+/* .RE
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* casefold(3) casefold text
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 <stringops.h>
+
+/* Global library. */
+
+#include <fold_addr.h>
+
+#define STR(x) vstring_str(x)
+
+/* fold_addr - case fold mail address */
+
+char *fold_addr(VSTRING *result, const char *addr, int flags)
+{
+ char *cp;
+
+ /*
+ * Fold the address as appropriate.
+ */
+ switch (flags & FOLD_ADDR_ALL) {
+ case FOLD_ADDR_HOST:
+ if ((cp = strrchr(addr, '@')) != 0) {
+ cp += 1;
+ vstring_strncpy(result, addr, cp - addr);
+ casefold_append(result, cp);
+ break;
+ }
+ /* FALLTHROUGH */
+ case 0:
+ vstring_strcpy(result, addr);
+ break;
+ case FOLD_ADDR_USER:
+ if ((cp = strrchr(addr, '@')) != 0) {
+ casefold_len(result, addr, cp - addr);
+ vstring_strcat(result, cp);
+ break;
+ }
+ /* FALLTHROUGH */
+ case FOLD_ADDR_USER | FOLD_ADDR_HOST:
+ casefold(result, addr);
+ break;
+ }
+ return (STR(result));
+}
+
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+#include <argv.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *line_buffer = vstring_alloc(1);
+ VSTRING *fold_buffer = vstring_alloc(1);
+ ARGV *cmd;
+ char **args;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ util_utf8_enable = 1;
+ while (vstring_fgets_nonl(line_buffer, VSTREAM_IN)) {
+ vstream_printf("> %s\n", STR(line_buffer));
+ cmd = argv_split(STR(line_buffer), CHARS_SPACE);
+ if (cmd->argc == 0 || cmd->argv[0][0] == '#') {
+ argv_free(cmd);
+ continue;
+ }
+ args = cmd->argv;
+
+ /*
+ * Fold the host.
+ */
+ if (strcmp(args[0], "host") == 0 && cmd->argc == 2) {
+ vstream_printf("\"%s\" -> \"%s\"\n", args[1], fold_addr(fold_buffer,
+ args[1], FOLD_ADDR_HOST));
+ }
+
+ /*
+ * Fold the user.
+ */
+ else if (strcmp(args[0], "user") == 0 && cmd->argc == 2) {
+ vstream_printf("\"%s\" -> \"%s\"\n", args[1], fold_addr(fold_buffer,
+ args[1], FOLD_ADDR_USER));
+ }
+
+ /*
+ * Fold user and host.
+ */
+ else if (strcmp(args[0], "all") == 0 && cmd->argc == 2) {
+ vstream_printf("\"%s\" -> \"%s\"\n", args[1], fold_addr(fold_buffer,
+ args[1], FOLD_ADDR_ALL));
+ }
+
+ /*
+ * Fold none.
+ */
+ else if (strcmp(args[0], "none") == 0 && cmd->argc == 2) {
+ vstream_printf("\"%s\" -> \"%s\"\n", args[1], fold_addr(fold_buffer,
+ args[1], 0));
+ }
+
+ /*
+ * Usage.
+ */
+ else {
+ vstream_printf("Usage: %s host <addr> | user <addr> | all <addr>\n",
+ argv[0]);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(cmd);
+ }
+ vstring_free(line_buffer);
+ vstring_free(fold_buffer);
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/src/global/fold_addr.h b/src/global/fold_addr.h
new file mode 100644
index 0000000..ba8021d
--- /dev/null
+++ b/src/global/fold_addr.h
@@ -0,0 +1,35 @@
+#ifndef _FOLD_ADDR_H_INCLUDED_
+#define _FOLD_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* fold_addr 3h
+/* SUMMARY
+/* address case folding
+/* SYNOPSIS
+/* #include <fold_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define FOLD_ADDR_USER (1<<0)
+#define FOLD_ADDR_HOST (1<<1)
+
+#define FOLD_ADDR_ALL (FOLD_ADDR_USER | FOLD_ADDR_HOST)
+
+extern char *fold_addr(VSTRING *, const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/fold_addr_test.in b/src/global/fold_addr_test.in
new file mode 100644
index 0000000..c008587
--- /dev/null
+++ b/src/global/fold_addr_test.in
@@ -0,0 +1,19 @@
+# Regular cases, ASCII.
+host A@B
+user A@B
+all A@B
+none A@B
+# Corner cases, ASCII.
+host A
+host A@
+host @
+user @B
+user @
+all @
+user Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+host Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+all Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+user Δημοσθένους@
+user Δημοσθένους
+host Δημοσθένους@
+host @Δημοσθένους.EXAMPLE.COM
diff --git a/src/global/fold_addr_test.ref b/src/global/fold_addr_test.ref
new file mode 100644
index 0000000..35bf78c
--- /dev/null
+++ b/src/global/fold_addr_test.ref
@@ -0,0 +1,36 @@
+> # Regular cases, ASCII.
+> host A@B
+"A@B" -> "A@b"
+> user A@B
+"A@B" -> "a@B"
+> all A@B
+"A@B" -> "a@b"
+> none A@B
+"A@B" -> "A@B"
+> # Corner cases, ASCII.
+> host A
+"A" -> "A"
+> host A@
+"A@" -> "A@"
+> host @
+"@" -> "@"
+> user @B
+"@B" -> "@B"
+> user @
+"@" -> "@"
+> all @
+"@" -> "@"
+> user Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+"Δημοσθένους@Δημοσθένους.EXAMPLE.COM" -> "δημοσθένουσ@Δημοσθένους.EXAMPLE.COM"
+> host Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+"Δημοσθένους@Δημοσθένους.EXAMPLE.COM" -> "Δημοσθένους@δημοσθένουσ.example.com"
+> all Δημοσθένους@Δημοσθένους.EXAMPLE.COM
+"Δημοσθένους@Δημοσθένους.EXAMPLE.COM" -> "δημοσθένουσ@δημοσθένουσ.example.com"
+> user Δημοσθένους@
+"Δημοσθένους@" -> "δημοσθένουσ@"
+> user Δημοσθένους
+"Δημοσθένους" -> "δημοσθένουσ"
+> host Δημοσθένους@
+"Δημοσθένους@" -> "Δημοσθένους@"
+> host @Δημοσθένους.EXAMPLE.COM
+"@Δημοσθένους.EXAMPLE.COM" -> "@δημοσθένουσ.example.com"
diff --git a/src/global/haproxy_srvr.c b/src/global/haproxy_srvr.c
new file mode 100644
index 0000000..63147c1
--- /dev/null
+++ b/src/global/haproxy_srvr.c
@@ -0,0 +1,891 @@
+/*++
+/* NAME
+/* haproxy_srvr 3
+/* SUMMARY
+/* server-side haproxy protocol support
+/* SYNOPSIS
+/* #include <haproxy_srvr.h>
+/*
+/* const char *haproxy_srvr_parse(str, str_len, non_proxy,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* const char *str;
+/* ssize_t *str_len;
+/* int *non_proxy;
+/* MAI_HOSTADDR_STR *smtp_client_addr,
+/* MAI_SERVPORT_STR *smtp_client_port,
+/* MAI_HOSTADDR_STR *smtp_server_addr,
+/* MAI_SERVPORT_STR *smtp_server_port;
+/*
+/* const char *haproxy_srvr_receive(fd, non_proxy,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int fd;
+/* int *non_proxy;
+/* MAI_HOSTADDR_STR *smtp_client_addr,
+/* MAI_SERVPORT_STR *smtp_client_port,
+/* MAI_HOSTADDR_STR *smtp_server_addr,
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* DESCRIPTION
+/* haproxy_srvr_parse() parses a haproxy v1 or v2 protocol
+/* message. The result is null in case of success, a pointer
+/* to text (with the error type) in case of error. If both
+/* IPv6 and IPv4 support are enabled, IPV4_IN_IPV6 address
+/* form (::ffff:1.2.3.4) is converted to IPV4 form. In case
+/* of success, the str_len argument is updated with the number
+/* of bytes parsed, and the non_proxy argument is true or false
+/* if the haproxy message specifies a non-proxied connection.
+/*
+/* haproxy_srvr_receive() receives and parses a haproxy protocol
+/* handshake. This must be called before any I/O is done on
+/* the specified file descriptor. The result is 0 in case of
+/* success, -1 in case of error. All errors are logged.
+/*
+/* The haproxy v2 protocol support is limited to TCP over IPv4,
+/* TCP over IPv6, and non-proxied connections. In the latter
+/* case, the caller is responsible for any local or remote
+/* address/port lookup.
+/* 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 <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <valid_hostname.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <inet_proto.h>
+#include <split_at.h>
+#include <sock_addr.h>
+
+/* Global library. */
+
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+ /*
+ * The haproxy protocol assumes that a haproxy header will normally not
+ * exceed the default IPv4 TCP MSS, i.e. 576-40=536 bytes (the IPv6 default
+ * is larger: 1280-60=1220). With a proxy header that contains IPv6
+ * addresses, that leaves room for 536-52=484 bytes of TLVs. The Postfix
+ * implementation does not support headers with UNIX-domain addresses.
+ */
+#define HAPROXY_HEADER_MAX_LEN 536
+
+ /*
+ * Begin protocol v2 definitions from haproxy/include/types/connection.h.
+ */
+#define PP2_SIGNATURE "\r\n\r\n\0\r\nQUIT\n"
+#define PP2_SIGNATURE_LEN 12
+#define PP2_HEADER_LEN 16
+
+/* ver_cmd byte */
+#define PP2_CMD_LOCAL 0x00
+#define PP2_CMD_PROXY 0x01
+#define PP2_CMD_MASK 0x0F
+
+#define PP2_VERSION 0x20
+#define PP2_VERSION_MASK 0xF0
+
+/* fam byte */
+#define PP2_TRANS_UNSPEC 0x00
+#define PP2_TRANS_STREAM 0x01
+#define PP2_TRANS_DGRAM 0x02
+#define PP2_TRANS_MASK 0x0F
+
+#define PP2_FAM_UNSPEC 0x00
+#define PP2_FAM_INET 0x10
+#define PP2_FAM_INET6 0x20
+#define PP2_FAM_UNIX 0x30
+#define PP2_FAM_MASK 0xF0
+
+/* len field (2 bytes) */
+#define PP2_ADDR_LEN_UNSPEC (0)
+#define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2)
+#define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2)
+#define PP2_ADDR_LEN_UNIX (108 + 108)
+
+#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
+#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
+#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
+#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
+
+struct proxy_hdr_v2 {
+ uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */
+ uint8_t ver_cmd; /* protocol version | command */
+ uint8_t fam; /* protocol family and transport */
+ uint16_t len; /* length of remainder */
+ union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unx;
+ } addr;
+};
+
+ /*
+ * End protocol v2 definitions from haproxy/include/types/connection.h.
+ */
+
+static const INET_PROTO_INFO *proto_info;
+
+#define STR_OR_NULL(str) ((str) ? (str) : "(null)")
+
+/* haproxy_srvr_parse_lit - extract and validate string literal */
+
+static int haproxy_srvr_parse_lit(const char *str,...)
+{
+ va_list ap;
+ const char *cp;
+ int result = -1;
+ int count;
+
+ if (msg_verbose)
+ msg_info("haproxy_srvr_parse: %s", STR_OR_NULL(str));
+
+ if (str != 0) {
+ va_start(ap, str);
+ for (count = 0; (cp = va_arg(ap, const char *)) != 0; count++) {
+ if (strcmp(str, cp) == 0) {
+ result = count;
+ break;
+ }
+ }
+ va_end(ap);
+ }
+ return (result);
+}
+
+/* haproxy_srvr_parse_proto - parse and validate the protocol type */
+
+static int haproxy_srvr_parse_proto(const char *str, int *addr_family)
+{
+ if (msg_verbose)
+ msg_info("haproxy_srvr_parse: proto=%s", STR_OR_NULL(str));
+
+ if (str == 0)
+ return (-1);
+#ifdef AF_INET6
+ if (strcasecmp(str, "TCP6") == 0) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET6) != 0) {
+ *addr_family = AF_INET6;
+ return (0);
+ }
+ } else
+#endif
+ if (strcasecmp(str, "TCP4") == 0) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0) {
+ *addr_family = AF_INET;
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+/* haproxy_srvr_parse_addr - extract and validate IP address */
+
+static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
+ int addr_family)
+{
+ struct addrinfo *res = 0;
+ int err;
+
+ if (msg_verbose)
+ msg_info("haproxy_srvr_parse: addr=%s proto=%d",
+ STR_OR_NULL(str), addr_family);
+
+ if (str == 0 || strlen(str) >= sizeof(MAI_HOSTADDR_STR))
+ return (-1);
+
+ switch (addr_family) {
+#ifdef AF_INET6
+ case AF_INET6:
+ err = !valid_ipv6_hostaddr(str, DONT_GRIPE);
+ break;
+#endif
+ case AF_INET:
+ err = !valid_ipv4_hostaddr(str, DONT_GRIPE);
+ break;
+ default:
+ msg_panic("haproxy_srvr_parse: unexpected address family: %d",
+ addr_family);
+ }
+ if (err == 0)
+ err = (hostaddr_to_sockaddr(str, (char *) 0, 0, &res)
+ || sockaddr_to_hostaddr(res->ai_addr, res->ai_addrlen,
+ addr, (MAI_SERVPORT_STR *) 0, 0));
+ if (res)
+ freeaddrinfo(res);
+ if (err)
+ return (-1);
+ if (addr->buf[0] == ':' && strncasecmp("::ffff:", addr->buf, 7) == 0
+ && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
+ memmove(addr->buf, addr->buf + 7, strlen(addr->buf) + 1 - 7);
+ return (0);
+}
+
+/* haproxy_srvr_parse_port - extract and validate TCP port */
+
+static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port)
+{
+ if (msg_verbose)
+ msg_info("haproxy_srvr_parse: port=%s", STR_OR_NULL(str));
+ if (str == 0 || strlen(str) >= sizeof(MAI_SERVPORT_STR)
+ || !valid_hostport(str, DONT_GRIPE)) {
+ return (-1);
+ } else {
+ memcpy(port->buf, str, strlen(str) + 1);
+ return (0);
+ }
+}
+
+/* haproxy_srvr_parse_v2_addr_v4 - parse IPv4 info from v2 header */
+
+static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
+ unsigned sin_port,
+ MAI_HOSTADDR_STR *addr,
+ MAI_SERVPORT_STR *port)
+{
+ struct sockaddr_in sin;
+
+ memset((void *) &sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = sin_addr;
+ sin.sin_port = sin_port;
+ if (sockaddr_to_hostaddr((struct sockaddr *) &sin, sizeof(sin),
+ addr, port, 0) < 0)
+ return (-1);
+ return (0);
+}
+
+#ifdef AF_INET6
+
+/* haproxy_srvr_parse_v2_addr_v6 - parse IPv6 info from v2 header */
+
+static int haproxy_srvr_parse_v2_addr_v6(uint8_t *sin6_addr,
+ unsigned sin6_port,
+ MAI_HOSTADDR_STR *addr,
+ MAI_SERVPORT_STR *port)
+{
+ struct sockaddr_in6 sin6;
+
+ memset((void *) &sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ memcpy(&sin6.sin6_addr, sin6_addr, 16);
+ sin6.sin6_port = sin6_port;
+ if (sockaddr_to_hostaddr((struct sockaddr *) &sin6,
+ sizeof(sin6), addr, port, 0) < 0)
+ return (-1);
+ if (addr->buf[0] == ':'
+ && strncasecmp("::ffff:", addr->buf, 7) == 0
+ && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
+ memmove(addr->buf, addr->buf + 7,
+ strlen(addr->buf) + 1 - 7);
+ return (0);
+}
+
+#endif
+
+/* haproxy_srvr_parse_v2_hdr - parse v2 header address info */
+
+static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
+ int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ const char myname[] = "haproxy_srvr_parse_v2_hdr";
+ struct proxy_hdr_v2 *hdr_v2;
+
+ if (*str_len < PP2_HEADER_LEN)
+ return ("short protocol header");
+ hdr_v2 = (struct proxy_hdr_v2 *) str;
+ if (memcmp(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN) != 0)
+ return ("unrecognized protocol header");
+ if ((hdr_v2->ver_cmd & PP2_VERSION_MASK) != PP2_VERSION)
+ return ("unrecognized protocol version");
+ if (*str_len < PP2_HEADER_LEN + ntohs(hdr_v2->len))
+ return ("short version 2 protocol header");
+
+ switch (hdr_v2->ver_cmd & PP2_CMD_MASK) {
+
+ /*
+ * Proxied connection, use the proxy-provided connection info.
+ */
+ case PP2_CMD_PROXY:
+ switch (hdr_v2->fam) {
+ case PP2_FAM_INET | PP2_TRANS_STREAM:{ /* TCP4 */
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) == 0)
+ return ("Postfix IPv4 support is disabled");
+ if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET)
+ return ("short address field");
+ if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr,
+ hdr_v2->addr.ip4.src_port,
+ smtp_client_addr, smtp_client_port) < 0)
+ return ("client network address conversion error");
+ if (msg_verbose)
+ msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
+ myname, smtp_client_addr->buf, smtp_client_port->buf);
+ if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr,
+ hdr_v2->addr.ip4.dst_port,
+ smtp_server_addr, smtp_server_port) < 0)
+ return ("server network address conversion error");
+ if (msg_verbose)
+ msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
+ myname, smtp_server_addr->buf, smtp_server_port->buf);
+ break;
+ }
+ case PP2_FAM_INET6 | PP2_TRANS_STREAM:{/* TCP6 */
+#ifdef AF_INET6
+ if (strchr((char *) proto_info->sa_family_list, AF_INET6) == 0)
+ return ("Postfix IPv6 support is disabled");
+ if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET6)
+ return ("short address field");
+ if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr,
+ hdr_v2->addr.ip6.src_port,
+ smtp_client_addr,
+ smtp_client_port) < 0)
+ return ("client network address conversion error");
+ if (msg_verbose)
+ msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
+ myname, smtp_client_addr->buf, smtp_client_port->buf);
+ if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr,
+ hdr_v2->addr.ip6.dst_port,
+ smtp_server_addr,
+ smtp_server_port) < 0)
+ return ("server network address conversion error");
+ if (msg_verbose)
+ msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
+ myname, smtp_server_addr->buf, smtp_server_port->buf);
+ break;
+#else
+ return ("Postfix IPv6 support is not compiled in");
+#endif
+ }
+ default:
+ return ("unsupported network protocol");
+ }
+ /* For now, skip and ignore TLVs. */
+ *str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
+ return (0);
+
+ /*
+ * Non-proxied connection, use the proxy-to-server connection info.
+ */
+ case PP2_CMD_LOCAL:
+ /* For now, skip and ignore TLVs. */
+ *non_proxy = 1;
+ *str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
+ return (0);
+ default:
+ return ("bad command in proxy header");
+ }
+}
+
+/* haproxy_srvr_parse - parse haproxy line */
+
+const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
+ int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ const char *err;
+
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+
+ *non_proxy = 0;
+
+ /*
+ * XXX We don't accept connections with the "UNKNOWN" protocol type,
+ * because those would sidestep address-based access control mechanisms.
+ */
+
+ /*
+ * Try version 1 protocol.
+ */
+ if (strncmp(str, "PROXY ", 6) == 0) {
+ char *saved_str = mystrndup(str, *str_len);
+ char *cp = saved_str;
+ char *beyond_header = split_at(saved_str, '\n');
+ int addr_family;
+
+#define NEXT_TOKEN mystrtok(&cp, " \r")
+ if (beyond_header == 0)
+ err = "missing protocol header terminator";
+ else if (haproxy_srvr_parse_lit(NEXT_TOKEN, "PROXY", (char *) 0) < 0)
+ err = "bad or missing protocol header";
+ else if (haproxy_srvr_parse_proto(NEXT_TOKEN, &addr_family) < 0)
+ err = "bad or missing protocol type";
+ else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_client_addr,
+ addr_family) < 0)
+ err = "bad or missing client address";
+ else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_server_addr,
+ addr_family) < 0)
+ err = "bad or missing server address";
+ else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port) < 0)
+ err = "bad or missing client port";
+ else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port) < 0)
+ err = "bad or missing server port";
+ else {
+ err = 0;
+ *str_len = beyond_header - saved_str;
+ }
+ myfree(saved_str);
+
+ return (err);
+ }
+
+ /*
+ * Try version 2 protocol.
+ */
+ else {
+ return (haproxy_srvr_parse_v2_hdr(str, str_len, non_proxy,
+ smtp_client_addr, smtp_client_port,
+ smtp_server_addr, smtp_server_port));
+ }
+}
+
+/* haproxy_srvr_receive - receive and parse haproxy protocol handshake */
+
+int haproxy_srvr_receive(int fd, int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ const char *err;
+ VSTRING *escape_buf;
+ char read_buf[HAPROXY_HEADER_MAX_LEN + 1];
+ ssize_t read_len;
+
+ /*
+ * We must not read(2) past the end of the HaProxy handshake. The v2
+ * protocol assumes that the handshake will never be fragmented,
+ * therefore we peek, parse the entire input, then read(2) only the
+ * number of bytes parsed.
+ */
+ if ((read_len = recv(fd, read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
+ msg_warn("haproxy read: EOF");
+ return (-1);
+ }
+
+ /*
+ * Parse the haproxy handshake, and determine the handshake length.
+ */
+ read_buf[read_len] = 0;
+
+ if ((err = haproxy_srvr_parse(read_buf, &read_len, non_proxy,
+ smtp_client_addr, smtp_client_port,
+ smtp_server_addr, smtp_server_port)) != 0) {
+ escape_buf = vstring_alloc(read_len * 2);
+ escape(escape_buf, read_buf, read_len);
+ msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
+ vstring_free(escape_buf);
+ return (-1);
+ }
+
+ /*
+ * Try to pop the haproxy handshake off the input queue.
+ */
+ if (recv(fd, read_buf, read_len, 0) != read_len) {
+ msg_warn("haproxy read: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+ /*
+ * Test program.
+ */
+#ifdef TEST
+
+ /*
+ * Test cases with inputs and expected outputs. A request may contain
+ * trailing garbage, and it may be too short. A v1 request may also contain
+ * malformed address or port information.
+ */
+typedef struct TEST_CASE {
+ const char *haproxy_request; /* v1 or v2 request including thrash */
+ ssize_t haproxy_req_len; /* request length including thrash */
+ ssize_t exp_req_len; /* parsed request length */
+ int exp_non_proxy; /* request is not proxied */
+ const char *exp_return; /* expected error string */
+ const char *exp_client_addr; /* expected client address string */
+ const char *exp_server_addr; /* expected client port string */
+ const char *exp_client_port; /* expected client address string */
+ const char *exp_server_port; /* expected server port string */
+} TEST_CASE;
+static TEST_CASE v1_test_cases[] = {
+ /* IPv6. */
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+ {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+ {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+ /* IPv4 in IPv6. */
+ {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+ /* IPv4. */
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"},
+ /* Missing fields. */
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"},
+ {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"},
+ {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"},
+ /* Other. */
+ {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"},
+ {"PROXY\n", 0, 0, 0, "short protocol header"},
+ {"BLAH\n", 0, 0, 0, "short protocol header"},
+ {"\n", 0, 0, 0, "short protocol header"},
+ {"", 0, 0, 0, "short protocol header"},
+ 0,
+};
+
+static struct proxy_hdr_v2 v2_local_request = {
+ PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL,
+};
+static TEST_CASE v2_non_proxy_test = {
+ (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1,
+};
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* evaluate_test_case - evaluate one test case */
+
+static int evaluate_test_case(const char *test_label,
+ const TEST_CASE *test_case)
+{
+ /* Actual results. */
+ const char *act_return;
+ ssize_t act_req_len;
+ int act_non_proxy;
+ MAI_HOSTADDR_STR act_smtp_client_addr;
+ MAI_HOSTADDR_STR act_smtp_server_addr;
+ MAI_SERVPORT_STR act_smtp_client_port;
+ MAI_SERVPORT_STR act_smtp_server_port;
+ int test_failed;
+
+ if (msg_verbose)
+ msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s "
+ "exp_client_port=%s exp_server_port=%s",
+ test_label, STR_OR_NULL(test_case->exp_client_addr),
+ STR_OR_NULL(test_case->exp_server_addr),
+ STR_OR_NULL(test_case->exp_client_port),
+ STR_OR_NULL(test_case->exp_server_port));
+
+ /*
+ * Start the test.
+ */
+ test_failed = 0;
+ act_req_len = test_case->haproxy_req_len;
+ act_return =
+ haproxy_srvr_parse(test_case->haproxy_request, &act_req_len,
+ &act_non_proxy,
+ &act_smtp_client_addr, &act_smtp_client_port,
+ &act_smtp_server_addr, &act_smtp_server_port);
+ if (act_return != test_case->exp_return) {
+ msg_warn("test case %s return expected=%s actual=%s",
+ test_label, STR_OR_NULL(test_case->exp_return),
+ STR_OR_NULL(act_return));
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (act_req_len != test_case->exp_req_len) {
+ msg_warn("test case %s str_len expected=%ld actual=%ld",
+ test_label,
+ (long) test_case->exp_req_len, (long) act_req_len);
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (act_non_proxy != test_case->exp_non_proxy) {
+ msg_warn("test case %s non_proxy expected=%d actual=%d",
+ test_label,
+ test_case->exp_non_proxy, act_non_proxy);
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (test_case->exp_non_proxy || test_case->exp_return != 0)
+ /* No expected address/port results. */
+ return (test_failed);
+
+ /*
+ * Compare address/port results against expected results.
+ */
+ if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) {
+ msg_warn("test case %s client_addr expected=%s actual=%s",
+ test_label,
+ test_case->exp_client_addr, act_smtp_client_addr.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) {
+ msg_warn("test case %s server_addr expected=%s actual=%s",
+ test_label,
+ test_case->exp_server_addr, act_smtp_server_addr.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) {
+ msg_warn("test case %s client_port expected=%s actual=%s",
+ test_label,
+ test_case->exp_client_port, act_smtp_client_port.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) {
+ msg_warn("test case %s server_port expected=%s actual=%s",
+ test_label,
+ test_case->exp_server_port, act_smtp_server_port.buf);
+ test_failed = 1;
+ }
+ return (test_failed);
+}
+
+/* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
+
+static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
+ ssize_t req_len)
+{
+ const char myname[] = "convert_v1_proxy_req_to_v2";
+ const char *err;
+ int non_proxy;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ struct proxy_hdr_v2 *hdr_v2;
+ struct addrinfo *src_res;
+ struct addrinfo *dst_res;
+
+ /*
+ * Allocate buffer space for the largest possible protocol header, so we
+ * don't have to worry about hidden realloc() calls.
+ */
+ VSTRING_RESET(buf);
+ VSTRING_SPACE(buf, sizeof(struct proxy_hdr_v2));
+ hdr_v2 = (struct proxy_hdr_v2 *) STR(buf);
+
+ /*
+ * Fill in the header,
+ */
+ memcpy(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN);
+ hdr_v2->ver_cmd = PP2_VERSION | PP2_CMD_PROXY;
+ if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr,
+ &smtp_client_port, &smtp_server_addr,
+ &smtp_server_port)) != 0 || non_proxy)
+ msg_fatal("%s: malformed or non-proxy request: %s",
+ myname, req);
+
+ if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0,
+ &src_res) != 0)
+ msg_fatal("%s: unable to convert source address %s port %s",
+ myname, smtp_client_addr.buf, smtp_client_port.buf);
+ if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0,
+ &dst_res) != 0)
+ msg_fatal("%s: unable to convert destination address %s port %s",
+ myname, smtp_server_addr.buf, smtp_server_port.buf);
+ if (src_res->ai_family != dst_res->ai_family)
+ msg_fatal("%s: mixed source/destination address families", myname);
+#ifdef AF_INET6
+ if (src_res->ai_family == PF_INET6) {
+ hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
+ hdr_v2->len = htons(PP2_ADDR_LEN_INET6);
+ memcpy(hdr_v2->addr.ip6.src_addr,
+ &SOCK_ADDR_IN6_ADDR(src_res->ai_addr),
+ sizeof(hdr_v2->addr.ip6.src_addr));
+ hdr_v2->addr.ip6.src_port = SOCK_ADDR_IN6_PORT(src_res->ai_addr);
+ memcpy(hdr_v2->addr.ip6.dst_addr,
+ &SOCK_ADDR_IN6_ADDR(dst_res->ai_addr),
+ sizeof(hdr_v2->addr.ip6.dst_addr));
+ hdr_v2->addr.ip6.dst_port = SOCK_ADDR_IN6_PORT(dst_res->ai_addr);
+ } else
+#endif
+ if (src_res->ai_family == PF_INET) {
+ hdr_v2->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
+ hdr_v2->len = htons(PP2_ADDR_LEN_INET);
+ hdr_v2->addr.ip4.src_addr = SOCK_ADDR_IN_ADDR(src_res->ai_addr).s_addr;
+ hdr_v2->addr.ip4.src_port = SOCK_ADDR_IN_PORT(src_res->ai_addr);
+ hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr;
+ hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr);
+ } else {
+ msg_panic("unknown address family 0x%x", src_res->ai_family);
+ }
+ vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len));
+ freeaddrinfo(src_res);
+ freeaddrinfo(dst_res);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *test_label;
+ TEST_CASE *v1_test_case;
+ TEST_CASE v2_test_case;
+ TEST_CASE mutated_test_case;
+ VSTRING *v2_request_buf;
+ VSTRING *mutated_request_buf;
+
+ /* Findings. */
+ int tests_failed = 0;
+ int test_failed;
+
+ test_label = vstring_alloc(100);
+ v2_request_buf = vstring_alloc(100);
+ mutated_request_buf = vstring_alloc(100);
+
+ for (tests_failed = 0, v1_test_case = v1_test_cases;
+ v1_test_case->haproxy_request != 0;
+ tests_failed += test_failed, v1_test_case++) {
+
+ /*
+ * Fill in missing string length info in v1 test data.
+ */
+ if (v1_test_case->haproxy_req_len == 0)
+ v1_test_case->haproxy_req_len =
+ strlen(v1_test_case->haproxy_request);
+ if (v1_test_case->exp_req_len == 0)
+ v1_test_case->exp_req_len = v1_test_case->haproxy_req_len;
+
+ /*
+ * Evaluate each v1 test case.
+ */
+ vstring_sprintf(test_label, "%d", (int) (v1_test_case - v1_test_cases));
+ test_failed = evaluate_test_case(STR(test_label), v1_test_case);
+
+ /*
+ * If the v1 test input is malformed, skip the mutation tests.
+ */
+ if (v1_test_case->exp_return != 0)
+ continue;
+
+ /*
+ * Mutation test: a well-formed v1 test case should still pass after
+ * appending a byte, and should return the actual parsed header
+ * length. The test uses the implicit VSTRING null safety byte.
+ */
+ vstring_sprintf(test_label, "%d (one byte appended)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = *v1_test_case;
+ mutated_test_case.haproxy_req_len += 1;
+ /* reuse v1_test_case->exp_req_len */
+ test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
+
+ /*
+ * Mutation test: a well-formed v1 test case should fail after
+ * stripping the terminator.
+ */
+ vstring_sprintf(test_label, "%d (last byte stripped)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = *v1_test_case;
+ mutated_test_case.exp_return = "missing protocol header terminator";
+ mutated_test_case.haproxy_req_len -= 1;
+ mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
+ test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
+
+ /*
+ * A 'well-formed' v1 test case should pass after conversion to v2.
+ */
+ vstring_sprintf(test_label, "%d (converted to v2)",
+ (int) (v1_test_case - v1_test_cases));
+ v2_test_case = *v1_test_case;
+ convert_v1_proxy_req_to_v2(v2_request_buf,
+ v1_test_case->haproxy_request,
+ v1_test_case->haproxy_req_len);
+ v2_test_case.haproxy_request = STR(v2_request_buf);
+ v2_test_case.haproxy_req_len = PP2_HEADER_LEN
+ + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
+ v2_test_case.exp_req_len = v2_test_case.haproxy_req_len;
+ test_failed += evaluate_test_case(STR(test_label), &v2_test_case);
+
+ /*
+ * Mutation test: a well-formed v2 test case should still pass after
+ * appending a byte, and should return the actual parsed header
+ * length. The test uses the implicit VSTRING null safety byte.
+ */
+ vstring_sprintf(test_label, "%d (converted to v2, one byte appended)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = v2_test_case;
+ mutated_test_case.haproxy_req_len += 1;
+ /* reuse v2_test_case->exp_req_len */
+ test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
+
+ /*
+ * Mutation test: a well-formed v2 test case should fail after
+ * stripping one byte
+ */
+ vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = v2_test_case;
+ mutated_test_case.haproxy_req_len -= 1;
+ mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
+ mutated_test_case.exp_return = "short version 2 protocol header";
+ test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
+ }
+
+ /*
+ * Additional V2-only tests.
+ */
+ test_failed +=
+ evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test);
+
+ /*
+ * Clean up.
+ */
+ vstring_free(v2_request_buf);
+ vstring_free(mutated_request_buf);
+ vstring_free(test_label);
+ if (tests_failed)
+ msg_info("tests failed: %d", tests_failed);
+ exit(tests_failed != 0);
+}
+
+#endif
diff --git a/src/global/haproxy_srvr.h b/src/global/haproxy_srvr.h
new file mode 100644
index 0000000..4a801f1
--- /dev/null
+++ b/src/global/haproxy_srvr.h
@@ -0,0 +1,52 @@
+#ifndef _HAPROXY_SRVR_H_INCLUDED_
+#define _HAPROXY_SRVR_H_INCLUDED_
+
+/*++
+/* NAME
+/* haproxy_srvr 3h
+/* SUMMARY
+/* server-side haproxy protocol support
+/* SYNOPSIS
+/* #include <haproxy_srvr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <myaddrinfo.h>
+
+ /*
+ * External interface.
+ */
+extern const char *haproxy_srvr_parse(const char *, ssize_t *, int *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+extern int haproxy_srvr_receive(int, int *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+
+#define HAPROXY_PROTO_NAME "haproxy"
+
+#ifndef DO_GRIPE
+#define DO_GRIPE 1
+#define DONT_GRIPE 0
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/header_body_checks.c b/src/global/header_body_checks.c
new file mode 100644
index 0000000..0252dd1
--- /dev/null
+++ b/src/global/header_body_checks.c
@@ -0,0 +1,655 @@
+/*++
+/* NAME
+/* header_body_checks 3
+/* SUMMARY
+/* header/body checks
+/* SYNOPSIS
+/* #include <header_body_checks.h>
+/*
+/* typedef struct {
+/* void (*logger) (void *context, const char *action,
+/* const char *where, const char *line,
+/* const char *optional_text);
+/* void (*prepend) (void *context, int rec_type,
+/* const char *buf, ssize_t len, off_t offset);
+/* char *(*extend) (void *context, const char *command,
+/* ssize_t cmd_len, const char *cmd_args,
+/* const char *where, const char *line,
+/* ssize_t line_len, off_t offset);
+/* } HBC_CALL_BACKS;
+/*
+/* HBC_CHECKS *hbc_header_checks_create(
+/* header_checks_name, header_checks_value
+/* mime_header_checks_name, mime_header_checks_value,
+/* nested_header_checks_name, nested_header_checks_value,
+/* call_backs)
+/* const char *header_checks_name;
+/* const char *header_checks_value;
+/* const char *mime_header_checks_name;
+/* const char *mime_header_checks_value;
+/* const char *nested_header_checks_name;
+/* const char *nested_header_checks_value;
+/* HBC_CALL_BACKS *call_backs;
+/*
+/* HBC_CHECKS *hbc_body_checks_create(
+/* body_checks_name, body_checks_value,
+/* call_backs)
+/* const char *body_checks_name;
+/* const char *body_checks_value;
+/* HBC_CALL_BACKS *call_backs;
+/*
+/* char *hbc_header_checks(context, hbc, header_class, hdr_opts, header)
+/* void *context;
+/* HBC_CHECKS *hbc;
+/* int header_class;
+/* const HEADER_OPTS *hdr_opts;
+/* VSTRING *header;
+/*
+/* char *hbc_body_checks(context, hbc, body_line, body_line_len)
+/* void *context;
+/* HBC_CHECKS *hbc;
+/* const char *body_line;
+/* ssize_t body_line_len;
+/*
+/* void hbc_header_checks_free(hbc)
+/* HBC_CHECKS *hbc;
+/*
+/* void hbc_body_checks_free(hbc)
+/* HBC_CHECKS *hbc;
+/* DESCRIPTION
+/* This module implements header_checks and body_checks.
+/* Actions are executed while mail is being delivered. The
+/* following actions are recognized: INFO, WARN, REPLACE,
+/* PREPEND, IGNORE, DUNNO, and OK. These actions are safe for
+/* use in delivery agents.
+/*
+/* Other actions may be supplied via the extension mechanism
+/* described below. For example, actions that change the
+/* message delivery time or destination. Such actions do not
+/* make sense in delivery agents, but they can be appropriate
+/* in, for example, before-queue filters.
+/*
+/* hbc_header_checks_create() creates a context for header
+/* inspection. This function is typically called once during
+/* program initialization. The result is a null pointer when
+/* all _value arguments specify zero-length strings; in this
+/* case, hbc_header_checks() and hbc_header_checks_free() must
+/* not be called.
+/*
+/* hbc_header_checks() inspects the specified logical header.
+/* The result is either the original header, HBC_CHECKS_STAT_IGNORE
+/* (meaning: discard the header), HBC_CHECKS_STAT_ERROR, or a
+/* new header (meaning: replace the header and destroy the new
+/* header with myfree()).
+/*
+/* hbc_header_checks_free() returns memory to the pool.
+/*
+/* hbc_body_checks_create(), hbc_body_checks(), hbc_body_free()
+/* perform similar functions for body lines.
+/*
+/* Arguments:
+/* .IP body_line
+/* One line of body text.
+/* .IP body_line_len
+/* Body line length.
+/* .IP call_backs
+/* Table with call-back function pointers. This argument is
+/* not copied. Note: the description below is not necessarily
+/* in data structure order.
+/* .RS
+/* .IP logger
+/* Call-back function for logging an action with the action's
+/* name in lower case, a location within a message ("header"
+/* or "body"), the content of the header or body line that
+/* triggered the action, and optional text or a zero-length
+/* string. This call-back feature must be specified.
+/* .IP prepend
+/* Call-back function for the PREPEND action. The arguments
+/* are the same as those of mime_state(3) body output call-back
+/* functions. Specify a null pointer to disable this action.
+/* .IP extend
+/* Call-back function that logs and executes other actions.
+/* This function receives as arguments the command name and
+/* name length, the command arguments if any, the location
+/* within the message ("header" or "body"), the content and
+/* length of the header or body line that triggered the action,
+/* and the input byte offset within the current header or body
+/* segment. The result value is either the original line
+/* argument, HBC_CHECKS_STAT_IGNORE (delete the line from the
+/* input stream) or HBC_CHECKS_STAT_UNKNOWN (the command was
+/* not recognized). Specify a null pointer to disable this
+/* feature.
+/* .RE
+/* .IP context
+/* Application context for call-back functions specified with the
+/* call_backs argument.
+/* .IP header
+/* A logical message header. Lines within a multi-line header
+/* are separated by a newline character.
+/* .IP "header_checks_name, mime_header_checks_name"
+/* .IP "nested_header_checks_name, body_checks_name"
+/* The main.cf configuration parameter names for header and body
+/* map lists.
+/* .IP "header_checks_value, mime_header_checks_value"
+/* .IP "nested_header_checks_value, body_checks_value"
+/* The values of main.cf configuration parameters for header and body
+/* map lists. Specify a zero-length string to disable a specific list.
+/* .IP header_class
+/* A number in the range MIME_HDR_FIRST..MIME_HDR_LAST.
+/* .IP hbc
+/* A handle created with hbc_header_checks_create() or
+/* hbc_body_checks_create().
+/* .IP hdr_opts
+/* Message header properties.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mime_state.h>
+#include <rec_type.h>
+#include <is_header.h>
+#include <cleanup_user.h>
+#include <dsn_util.h>
+#include <header_body_checks.h>
+
+/* Application-specific. */
+
+ /*
+ * Something that is guaranteed to be different from a real string result
+ * from header/body_checks.
+ */
+char hbc_checks_error;
+const char hbc_checks_unknown;
+
+ /*
+ * Header checks are stored as an array of HBC_MAP_INFO structures, one
+ * structure for each header class (MIME_HDR_PRIMARY, MIME_HDR_MULTIPART, or
+ * MIME_HDR_NESTED).
+ *
+ * Body checks are stored as one single HBC_MAP_INFO structure, because we make
+ * no distinction between body segments.
+ */
+#define HBC_HEADER_INDEX(class) ((class) - MIME_HDR_FIRST)
+#define HBC_BODY_INDEX (0)
+
+#define HBC_INIT(hbc, index, name, value) do { \
+ HBC_MAP_INFO *_mp = (hbc)->map_info + (index); \
+ if (*(value) != 0) { \
+ _mp->map_class = (name); \
+ _mp->maps = maps_create((name), (value), DICT_FLAG_LOCK); \
+ } else { \
+ _mp->map_class = 0; \
+ _mp->maps = 0; \
+ } \
+ } while (0)
+
+/* How does the action routine know where we are? */
+
+#define HBC_CTXT_HEADER "header"
+#define HBC_CTXT_BODY "body"
+
+/* Silly little macros. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* hbc_action - act upon a header/body match */
+
+static char *hbc_action(void *context, HBC_CALL_BACKS *cb,
+ const char *map_class, const char *where,
+ const char *cmd, const char *line,
+ ssize_t line_len, off_t offset)
+{
+ const char *cmd_args = cmd + strcspn(cmd, " \t");
+ ssize_t cmd_len = cmd_args - cmd;
+ char *ret;
+
+ /*
+ * XXX We don't use a hash table for action lookup. Mail rarely triggers
+ * an action, and mail that triggers multiple actions is even rarer.
+ * Setting up the hash table costs more than we would gain from using it.
+ */
+ while (*cmd_args && ISSPACE(*cmd_args))
+ cmd_args++;
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ if (cb->extend
+ && (ret = cb->extend(context, cmd, cmd_len, cmd_args, where, line,
+ line_len, offset)) != HBC_CHECKS_STAT_UNKNOWN)
+ return (ret);
+
+ if (STREQUAL(cmd, "WARN", cmd_len)) {
+ cb->logger(context, "warning", where, line, cmd_args);
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "INFO", cmd_len)) {
+ cb->logger(context, "info", where, line, cmd_args);
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "REPLACE", cmd_len)) {
+ if (*cmd_args == 0) {
+ msg_warn("REPLACE action without text in %s map", map_class);
+ return ((char *) line);
+ } else if (strcmp(where, HBC_CTXT_HEADER) == 0
+ && !is_header(cmd_args)) {
+ msg_warn("bad REPLACE header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"", cmd_args, map_class);
+ return ((char *) line);
+ } else {
+ cb->logger(context, "replace", where, line, cmd_args);
+ return (mystrdup(cmd_args));
+ }
+ }
+ if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
+ if (*cmd_args == 0) {
+ msg_warn("PREPEND action without text in %s map", map_class);
+ } else if (strcmp(where, HBC_CTXT_HEADER) == 0
+ && !is_header(cmd_args)) {
+ msg_warn("bad PREPEND header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"", cmd_args, map_class);
+ } else {
+ cb->logger(context, "prepend", where, line, cmd_args);
+ cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
+ }
+ return ((char *) line);
+ }
+ if (STREQUAL(cmd, "STRIP", cmd_len)) {
+ cb->logger(context, "strip", where, line, cmd_args);
+ return (HBC_CHECKS_STAT_IGNORE);
+ }
+ /* Allow and ignore optional text after the action. */
+
+ if (STREQUAL(cmd, "IGNORE", cmd_len))
+ /* XXX Not logged for compatibility with cleanup(8). */
+ return (HBC_CHECKS_STAT_IGNORE);
+
+ if (STREQUAL(cmd, "DUNNO", cmd_len) /* preferred */
+ ||STREQUAL(cmd, "OK", cmd_len)) /* compatibility */
+ return ((char *) line);
+
+ msg_warn("unsupported command in %s map: %s", map_class, cmd);
+ return ((char *) line);
+}
+
+/* hbc_header_checks - process one complete header line */
+
+char *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
+ const HEADER_OPTS *hdr_opts,
+ VSTRING *header, off_t offset)
+{
+ const char *myname = "hbc_header_checks";
+ const char *action;
+ HBC_MAP_INFO *mp;
+
+ if (msg_verbose)
+ msg_info("%s: '%.30s'", myname, STR(header));
+
+ /*
+ * XXX This is for compatibility with the cleanup(8) server.
+ */
+ if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
+ header_class = MIME_HDR_MULTIPART;
+
+ mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
+
+ if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
+ return (hbc_action(context, hbc->call_backs,
+ mp->map_class, HBC_CTXT_HEADER, action,
+ STR(header), LEN(header), offset));
+ } else if (mp->maps && mp->maps->error) {
+ return (HBC_CHECKS_STAT_ERROR);
+ } else {
+ return (STR(header));
+ }
+}
+
+/* hbc_body_checks - inspect one body record */
+
+char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
+ ssize_t len, off_t offset)
+{
+ const char *myname = "hbc_body_checks";
+ const char *action;
+ HBC_MAP_INFO *mp;
+
+ if (msg_verbose)
+ msg_info("%s: '%.30s'", myname, line);
+
+ mp = hbc->map_info;
+
+ if ((action = maps_find(mp->maps, line, 0)) != 0) {
+ return (hbc_action(context, hbc->call_backs,
+ mp->map_class, HBC_CTXT_BODY, action,
+ line, len, offset));
+ } else if (mp->maps->error) {
+ return (HBC_CHECKS_STAT_ERROR);
+ } else {
+ return ((char *) line);
+ }
+}
+
+/* hbc_header_checks_create - create header checking context */
+
+HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
+ const char *header_checks_value,
+ const char *mime_header_checks_name,
+ const char *mime_header_checks_value,
+ const char *nested_header_checks_name,
+ const char *nested_header_checks_value,
+ HBC_CALL_BACKS *call_backs)
+{
+ HBC_CHECKS *hbc;
+
+ /*
+ * Optimize for the common case.
+ */
+ if (*header_checks_value == 0 && *mime_header_checks_value == 0
+ && *nested_header_checks_value == 0) {
+ return (0);
+ } else {
+ hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
+ + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
+ hbc->call_backs = call_backs;
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
+ header_checks_name, header_checks_value);
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
+ mime_header_checks_name, mime_header_checks_value);
+ HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
+ nested_header_checks_name, nested_header_checks_value);
+ return (hbc);
+ }
+}
+
+/* hbc_body_checks_create - create body checking context */
+
+HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
+ const char *body_checks_value,
+ HBC_CALL_BACKS *call_backs)
+{
+ HBC_CHECKS *hbc;
+
+ /*
+ * Optimize for the common case.
+ */
+ if (*body_checks_value == 0) {
+ return (0);
+ } else {
+ hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
+ hbc->call_backs = call_backs;
+ HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
+ return (hbc);
+ }
+}
+
+/* _hbc_checks_free - destroy header/body checking context */
+
+void _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
+{
+ HBC_MAP_INFO *mp;
+
+ for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
+ if (mp->maps)
+ maps_free(mp->maps);
+ myfree((void *) hbc);
+}
+
+ /*
+ * Test program. Specify the four maps on the command line, and feed a
+ * MIME-formatted message on stdin.
+ */
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <rec_streamlf.h>
+#include <mail_params.h>
+
+typedef struct {
+ HBC_CHECKS *header_checks;
+ HBC_CHECKS *body_checks;
+ HBC_CALL_BACKS *call_backs;
+ VSTREAM *fp;
+ VSTRING *buf;
+ const char *queueid;
+ int recno;
+} HBC_TEST_CONTEXT;
+
+/*#define REC_LEN 40*/
+#define REC_LEN 1024
+
+/* log_cb - log action with context */
+
+static void log_cb(void *context, const char *action, const char *where,
+ const char *content, const char *text)
+{
+ const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ if (*text) {
+ msg_info("%s: %s: %s %.200s: %s",
+ dp->queueid, action, where, content, text);
+ } else {
+ msg_info("%s: %s: %s %.200s",
+ dp->queueid, action, where, content);
+ }
+}
+
+/* out_cb - output call-back */
+
+static void out_cb(void *context, int rec_type, const char *buf,
+ ssize_t len, off_t offset)
+{
+ const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ vstream_fwrite(dp->fp, buf, len);
+ VSTREAM_PUTC('\n', dp->fp);
+ vstream_fflush(dp->fp);
+}
+
+/* head_out - MIME_STATE header call-back */
+
+static void head_out(void *context, int header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t offset)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+ char *out;
+
+ if (dp->header_checks == 0
+ || (out = hbc_header_checks(context, dp->header_checks, header_class,
+ header_info, buf, offset)) == STR(buf)) {
+ vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
+ dp->recno,
+ header_class == MIME_HDR_PRIMARY ? "MAIN" :
+ header_class == MIME_HDR_MULTIPART ? "MULT" :
+ header_class == MIME_HDR_NESTED ? "NEST" :
+ "ERROR", (long) offset, STR(buf));
+ out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
+ } else if (out != 0) {
+ vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
+ dp->recno,
+ header_class == MIME_HDR_PRIMARY ? "MAIN" :
+ header_class == MIME_HDR_MULTIPART ? "MULT" :
+ header_class == MIME_HDR_NESTED ? "NEST" :
+ "ERROR", (long) offset, out);
+ out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
+ myfree(out);
+ }
+ dp->recno += 1;
+}
+
+/* header_end - MIME_STATE end-of-header call-back */
+
+static void head_end(void *context)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
+}
+
+/* body_out - MIME_STATE body line call-back */
+
+static void body_out(void *context, int rec_type, const char *buf,
+ ssize_t len, off_t offset)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+ char *out;
+
+ if (dp->body_checks == 0
+ || (out = hbc_body_checks(context, dp->body_checks,
+ buf, len, offset)) == buf) {
+ vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
+ dp->recno, rec_type, (long) offset, buf);
+ out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
+ } else if (out != 0) {
+ vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
+ dp->recno, rec_type, (long) offset, out);
+ out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
+ myfree(out);
+ }
+ dp->recno += 1;
+}
+
+/* body_end - MIME_STATE end-of-message call-back */
+
+static void body_end(void *context)
+{
+ HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
+
+ out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
+}
+
+/* err_print - print MIME_STATE errors */
+
+static void err_print(void *unused_context, int err_flag,
+ const char *text, ssize_t len)
+{
+ msg_warn("%s: %.*s", mime_state_error(err_flag),
+ len < 100 ? (int) len : 100, text);
+}
+
+int var_header_limit = 2000;
+int var_mime_maxdepth = 20;
+int var_mime_bound_len = 2000;
+char *var_drop_hdrs = DEF_DROP_HDRS;
+
+int main(int argc, char **argv)
+{
+ int rec_type;
+ VSTRING *buf;
+ int err;
+ MIME_STATE *mime_state;
+ HBC_TEST_CONTEXT context;
+ static HBC_CALL_BACKS call_backs[1] = {
+ log_cb, /* logger */
+ out_cb, /* prepend */
+ };
+
+ /*
+ * Sanity check.
+ */
+ if (argc != 5)
+ msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
+
+ /*
+ * Initialize.
+ */
+#define MIME_OPTIONS \
+ (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
+ | MIME_OPT_REPORT_8BIT_IN_HEADER \
+ | MIME_OPT_REPORT_ENCODING_DOMAIN \
+ | MIME_OPT_REPORT_TRUNC_HEADER \
+ | MIME_OPT_REPORT_NESTING \
+ | MIME_OPT_DOWNGRADE)
+ msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
+ buf = vstring_alloc(10);
+ mime_state = mime_state_alloc(MIME_OPTIONS,
+ head_out, head_end,
+ body_out, body_end,
+ err_print,
+ (void *) &context);
+ context.header_checks =
+ hbc_header_checks_create("header_checks", argv[1],
+ "mime_header_checks", argv[2],
+ "nested_header_checks", argv[3],
+ call_backs);
+ context.body_checks =
+ hbc_body_checks_create("body_checks", argv[4], call_backs);
+ context.buf = vstring_alloc(100);
+ context.fp = VSTREAM_OUT;
+ context.queueid = "test-queueID";
+ context.recno = 0;
+
+ /*
+ * Main loop.
+ */
+ do {
+ rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
+ VSTRING_TERMINATE(buf);
+ err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
+ vstream_fflush(VSTREAM_OUT);
+ } while (rec_type > 0);
+
+ /*
+ * Error reporting.
+ */
+ if (err & MIME_ERR_TRUNC_HEADER)
+ msg_warn("message header length exceeds safety limit");
+ if (err & MIME_ERR_NESTING)
+ msg_warn("MIME nesting exceeds safety limit");
+ if (err & MIME_ERR_8BIT_IN_HEADER)
+ msg_warn("improper use of 8-bit data in message header");
+ if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
+ msg_warn("improper use of 8-bit data in message body");
+ if (err & MIME_ERR_ENCODING_DOMAIN)
+ msg_warn("improper message/* or multipart/* encoding domain");
+
+ /*
+ * Cleanup.
+ */
+ if (context.header_checks)
+ hbc_header_checks_free(context.header_checks);
+ if (context.body_checks)
+ hbc_body_checks_free(context.body_checks);
+ vstring_free(context.buf);
+ mime_state_free(mime_state);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/header_body_checks.h b/src/global/header_body_checks.h
new file mode 100644
index 0000000..7a2718e
--- /dev/null
+++ b/src/global/header_body_checks.h
@@ -0,0 +1,83 @@
+#ifndef _HBC_H_INCLUDED_
+#define _HBC_H_INCLUDED_
+
+/*++
+/* NAME
+/* header_body_checks 3h
+/* SUMMARY
+/* delivery agent header/body checks
+/* SYNOPSIS
+/* #include <header_body_checks.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mime_state.h>
+#include <maps.h>
+
+ /*
+ * Postfix < 2.5 compatibility.
+ */
+#ifndef MIME_HDR_FIRST
+#define MIME_HDR_FIRST (1)
+#define MIME_HDR_LAST (3)
+#endif
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *map_class; /* parameter name */
+ MAPS *maps; /* map handle */
+} HBC_MAP_INFO;
+
+typedef struct {
+ void (*logger) (void *, const char *, const char *, const char *, const char *);
+ void (*prepend) (void *, int, const char *, ssize_t, off_t);
+ char *(*extend) (void *, const char *, ssize_t, const char *, const char *, const char *, ssize_t, off_t);
+} HBC_CALL_BACKS;
+
+typedef struct {
+ HBC_CALL_BACKS *call_backs;
+ HBC_MAP_INFO map_info[1]; /* actually, a bunch */
+} HBC_CHECKS;
+
+#define HBC_CHECKS_STAT_IGNORE ((char *) 0)
+#define HBC_CHECKS_STAT_ERROR (&hbc_checks_error)
+#define HBC_CHECKS_STAT_UNKNOWN (&hbc_checks_unknown)
+
+extern HBC_CHECKS *hbc_header_checks_create(const char *, const char *,
+ const char *, const char *,
+ const char *, const char *,
+ HBC_CALL_BACKS *);
+extern HBC_CHECKS *hbc_body_checks_create(const char *, const char *,
+ HBC_CALL_BACKS *);
+extern char *hbc_header_checks(void *, HBC_CHECKS *, int, const HEADER_OPTS *,
+ VSTRING *, off_t);
+extern char *hbc_body_checks(void *, HBC_CHECKS *, const char *, ssize_t, off_t);
+
+#define hbc_header_checks_free(hbc) _hbc_checks_free((hbc), HBC_HEADER_SIZE)
+#define hbc_body_checks_free(hbc) _hbc_checks_free((hbc), 1)
+
+ /*
+ * The following are NOT part of the external API.
+ */
+#define HBC_HEADER_SIZE (MIME_HDR_LAST - MIME_HDR_FIRST + 1)
+extern void _hbc_checks_free(HBC_CHECKS *, ssize_t);
+extern char hbc_checks_error;
+extern const char hbc_checks_unknown;
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/header_body_checks_ignore.ref b/src/global/header_body_checks_ignore.ref
new file mode 100644
index 0000000..0d72838
--- /dev/null
+++ b/src/global/header_body_checks_ignore.ref
@@ -0,0 +1,18 @@
+HEADER END
+2 BODY N 0 |
+4 BODY N 15 |
+7 BODY N 0 |
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+13 BODY N 13 |
+16 BODY N 0 |
+18 BODY N 19 |
+21 BODY N 0 |
+23 BODY N 19 |
+26 BODY N 52 |
+28 BODY N 67 |
+31 BODY N 0 |
+33 BODY N 21 |
+35 BODY N 12 |
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_body_checks_null.ref b/src/global/header_body_checks_null.ref
new file mode 100644
index 0000000..ffe179c
--- /dev/null
+++ b/src/global/header_body_checks_null.ref
@@ -0,0 +1,42 @@
+0 MAIN 0 |subject: primary subject
+1 MAIN 71 |content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd
+ ef" mumble
+HEADER END
+2 BODY N 0 |
+3 BODY N 1 |abcdef prolog
+4 BODY N 15 |
+5 BODY N 16 |--abcd ef
+6 MULT 0 |content-type: message/rfc822; mumble
+7 BODY N 0 |
+8 NEST 0 |subject: nested subject
+9 NEST 57 |content-type: multipart/mumble; boundary(comment)="pqrs"
+10 NEST 91 |content-transfer-encoding: base64
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+12 BODY N 1 |pqrs prolog
+13 BODY N 13 |
+14 BODY N 14 |--pqrs
+15 MULT 0 |header: pqrs part 01
+16 BODY N 0 |
+17 BODY N 1 |body pqrs part 01
+18 BODY N 19 |
+19 BODY N 20 |--pqrs
+20 MULT 0 |header: pqrs part 02
+21 BODY N 0 |
+22 BODY N 1 |body pqrs part 02
+23 BODY N 19 |
+24 BODY N 20 |--bogus-boundary
+25 BODY N 37 |header: wietse
+26 BODY N 52 |
+27 BODY N 53 |body asdasads
+28 BODY N 67 |
+29 BODY N 68 |--abcd ef
+30 MULT 0 |header: abcdef part 02
+31 BODY N 0 |
+32 BODY N 1 |body abcdef part 02
+33 BODY N 21 |
+34 BODY N 0 |--abcd ef--
+35 BODY N 12 |
+36 BODY N 13 |epilog
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_body_checks_prepend.ref b/src/global/header_body_checks_prepend.ref
new file mode 100644
index 0000000..deaaefc
--- /dev/null
+++ b/src/global/header_body_checks_prepend.ref
@@ -0,0 +1,88 @@
+header_body_checks: test-queueID: prepend: header subject: primary subject: header: head
+header: head
+0 MAIN 0 |subject: primary subject
+header_body_checks: test-queueID: prepend: header content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd ? ef" mumble: header: mime
+header: mime
+1 MAIN 71 |content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd
+ ef" mumble
+HEADER END
+2 BODY N 0 |
+header_body_checks: test-queueID: prepend: body abcdef prolog: body
+body
+3 BODY N 1 |abcdef prolog
+4 BODY N 15 |
+header_body_checks: test-queueID: prepend: body --abcd ef: body
+body
+5 BODY N 16 |--abcd ef
+header_body_checks: test-queueID: prepend: header content-type: message/rfc822; mumble: header: mime
+header: mime
+6 MULT 0 |content-type: message/rfc822; mumble
+7 BODY N 0 |
+header_body_checks: test-queueID: prepend: header subject: nested subject: header: nest
+header: nest
+8 NEST 0 |subject: nested subject
+header_body_checks: test-queueID: prepend: header content-type: multipart/mumble; boundary(comment)="pqrs": header: mime
+header: mime
+9 NEST 57 |content-type: multipart/mumble; boundary(comment)="pqrs"
+header_body_checks: test-queueID: prepend: header content-transfer-encoding: base64: header: mime
+header: mime
+10 NEST 91 |content-transfer-encoding: base64
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+header_body_checks: test-queueID: prepend: body pqrs prolog: body
+body
+12 BODY N 1 |pqrs prolog
+13 BODY N 13 |
+header_body_checks: test-queueID: prepend: body --pqrs: body
+body
+14 BODY N 14 |--pqrs
+header_body_checks: test-queueID: prepend: header header: pqrs part 01: header: mime
+header: mime
+15 MULT 0 |header: pqrs part 01
+16 BODY N 0 |
+header_body_checks: test-queueID: prepend: body body pqrs part 01: body
+body
+17 BODY N 1 |body pqrs part 01
+18 BODY N 19 |
+header_body_checks: test-queueID: prepend: body --pqrs: body
+body
+19 BODY N 20 |--pqrs
+header_body_checks: test-queueID: prepend: header header: pqrs part 02: header: mime
+header: mime
+20 MULT 0 |header: pqrs part 02
+21 BODY N 0 |
+header_body_checks: test-queueID: prepend: body body pqrs part 02: body
+body
+22 BODY N 1 |body pqrs part 02
+23 BODY N 19 |
+header_body_checks: test-queueID: prepend: body --bogus-boundary: body
+body
+24 BODY N 20 |--bogus-boundary
+header_body_checks: test-queueID: prepend: body header: wietse: body
+body
+25 BODY N 37 |header: wietse
+26 BODY N 52 |
+header_body_checks: test-queueID: prepend: body body asdasads: body
+body
+27 BODY N 53 |body asdasads
+28 BODY N 67 |
+header_body_checks: test-queueID: prepend: body --abcd ef: body
+body
+29 BODY N 68 |--abcd ef
+header_body_checks: test-queueID: prepend: header header: abcdef part 02: header: mime
+header: mime
+30 MULT 0 |header: abcdef part 02
+31 BODY N 0 |
+header_body_checks: test-queueID: prepend: body body abcdef part 02: body
+body
+32 BODY N 1 |body abcdef part 02
+33 BODY N 21 |
+header_body_checks: test-queueID: prepend: body --abcd ef--: body
+body
+34 BODY N 0 |--abcd ef--
+35 BODY N 12 |
+header_body_checks: test-queueID: prepend: body epilog: body
+body
+36 BODY N 13 |epilog
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_body_checks_replace.ref b/src/global/header_body_checks_replace.ref
new file mode 100644
index 0000000..31bbeb3
--- /dev/null
+++ b/src/global/header_body_checks_replace.ref
@@ -0,0 +1,64 @@
+header_body_checks: test-queueID: replace: header subject: primary subject: header: head
+0 MAIN 0 |header: head
+header_body_checks: test-queueID: replace: header content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd ? ef" mumble: header: mime
+1 MAIN 71 |header: mime
+HEADER END
+2 BODY N 0 |
+header_body_checks: test-queueID: replace: body abcdef prolog: body
+3 BODY N 1 |body
+4 BODY N 15 |
+header_body_checks: test-queueID: replace: body --abcd ef: body
+5 BODY N 16 |body
+header_body_checks: test-queueID: replace: header content-type: message/rfc822; mumble: header: mime
+6 MULT 0 |header: mime
+7 BODY N 0 |
+header_body_checks: test-queueID: replace: header subject: nested subject: header: nest
+8 NEST 0 |header: nest
+header_body_checks: test-queueID: replace: header content-type: multipart/mumble; boundary(comment)="pqrs": header: mime
+9 NEST 57 |header: mime
+header_body_checks: test-queueID: replace: header content-transfer-encoding: base64: header: mime
+10 NEST 91 |header: mime
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+header_body_checks: test-queueID: replace: body pqrs prolog: body
+12 BODY N 1 |body
+13 BODY N 13 |
+header_body_checks: test-queueID: replace: body --pqrs: body
+14 BODY N 14 |body
+header_body_checks: test-queueID: replace: header header: pqrs part 01: header: mime
+15 MULT 0 |header: mime
+16 BODY N 0 |
+header_body_checks: test-queueID: replace: body body pqrs part 01: body
+17 BODY N 1 |body
+18 BODY N 19 |
+header_body_checks: test-queueID: replace: body --pqrs: body
+19 BODY N 20 |body
+header_body_checks: test-queueID: replace: header header: pqrs part 02: header: mime
+20 MULT 0 |header: mime
+21 BODY N 0 |
+header_body_checks: test-queueID: replace: body body pqrs part 02: body
+22 BODY N 1 |body
+23 BODY N 19 |
+header_body_checks: test-queueID: replace: body --bogus-boundary: body
+24 BODY N 20 |body
+header_body_checks: test-queueID: replace: body header: wietse: body
+25 BODY N 37 |body
+26 BODY N 52 |
+header_body_checks: test-queueID: replace: body body asdasads: body
+27 BODY N 53 |body
+28 BODY N 67 |
+header_body_checks: test-queueID: replace: body --abcd ef: body
+29 BODY N 68 |body
+header_body_checks: test-queueID: replace: header header: abcdef part 02: header: mime
+30 MULT 0 |header: mime
+31 BODY N 0 |
+header_body_checks: test-queueID: replace: body body abcdef part 02: body
+32 BODY N 1 |body
+33 BODY N 21 |
+header_body_checks: test-queueID: replace: body --abcd ef--: body
+34 BODY N 0 |body
+35 BODY N 12 |
+header_body_checks: test-queueID: replace: body epilog: body
+36 BODY N 13 |body
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_body_checks_strip.ref b/src/global/header_body_checks_strip.ref
new file mode 100644
index 0000000..1e02075
--- /dev/null
+++ b/src/global/header_body_checks_strip.ref
@@ -0,0 +1,41 @@
+header_body_checks: test-queueID: strip: header subject: primary subject: header line
+header_body_checks: test-queueID: strip: header content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd ? ef" mumble: mime header line
+HEADER END
+2 BODY N 0 |
+header_body_checks: test-queueID: strip: body abcdef prolog: body line
+4 BODY N 15 |
+header_body_checks: test-queueID: strip: body --abcd ef: body line
+header_body_checks: test-queueID: strip: header content-type: message/rfc822; mumble: mime header line
+7 BODY N 0 |
+header_body_checks: test-queueID: strip: header subject: nested subject: nested header
+header_body_checks: test-queueID: strip: header content-type: multipart/mumble; boundary(comment)="pqrs": mime header line
+header_body_checks: test-queueID: strip: header content-transfer-encoding: base64: mime header line
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+header_body_checks: test-queueID: strip: body pqrs prolog: body line
+13 BODY N 13 |
+header_body_checks: test-queueID: strip: body --pqrs: body line
+header_body_checks: test-queueID: strip: header header: pqrs part 01: mime header line
+16 BODY N 0 |
+header_body_checks: test-queueID: strip: body body pqrs part 01: body line
+18 BODY N 19 |
+header_body_checks: test-queueID: strip: body --pqrs: body line
+header_body_checks: test-queueID: strip: header header: pqrs part 02: mime header line
+21 BODY N 0 |
+header_body_checks: test-queueID: strip: body body pqrs part 02: body line
+23 BODY N 19 |
+header_body_checks: test-queueID: strip: body --bogus-boundary: body line
+header_body_checks: test-queueID: strip: body header: wietse: body line
+26 BODY N 52 |
+header_body_checks: test-queueID: strip: body body asdasads: body line
+28 BODY N 67 |
+header_body_checks: test-queueID: strip: body --abcd ef: body line
+header_body_checks: test-queueID: strip: header header: abcdef part 02: mime header line
+31 BODY N 0 |
+header_body_checks: test-queueID: strip: body body abcdef part 02: body line
+33 BODY N 21 |
+header_body_checks: test-queueID: strip: body --abcd ef--: body line
+35 BODY N 12 |
+header_body_checks: test-queueID: strip: body epilog: body line
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_body_checks_warn.ref b/src/global/header_body_checks_warn.ref
new file mode 100644
index 0000000..50afd35
--- /dev/null
+++ b/src/global/header_body_checks_warn.ref
@@ -0,0 +1,65 @@
+header_body_checks: test-queueID: warning: header subject: primary subject
+0 MAIN 0 |subject: primary subject
+header_body_checks: test-queueID: warning: header content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd ? ef" mumble
+1 MAIN 71 |content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd
+ ef" mumble
+HEADER END
+2 BODY N 0 |
+header_body_checks: test-queueID: warning: body abcdef prolog
+3 BODY N 1 |abcdef prolog
+4 BODY N 15 |
+header_body_checks: test-queueID: warning: body --abcd ef
+5 BODY N 16 |--abcd ef
+header_body_checks: test-queueID: warning: header content-type: message/rfc822; mumble
+6 MULT 0 |content-type: message/rfc822; mumble
+7 BODY N 0 |
+header_body_checks: test-queueID: warning: header subject: nested subject
+8 NEST 0 |subject: nested subject
+header_body_checks: test-queueID: warning: header content-type: multipart/mumble; boundary(comment)="pqrs"
+9 NEST 57 |content-type: multipart/mumble; boundary(comment)="pqrs"
+header_body_checks: test-queueID: warning: header content-transfer-encoding: base64
+10 NEST 91 |content-transfer-encoding: base64
+header_body_checks: warning: invalid message/* or multipart/* encoding domain: base64
+11 BODY N 0 |
+header_body_checks: test-queueID: warning: body pqrs prolog
+12 BODY N 1 |pqrs prolog
+13 BODY N 13 |
+header_body_checks: test-queueID: warning: body --pqrs
+14 BODY N 14 |--pqrs
+header_body_checks: test-queueID: warning: header header: pqrs part 01
+15 MULT 0 |header: pqrs part 01
+16 BODY N 0 |
+header_body_checks: test-queueID: warning: body body pqrs part 01
+17 BODY N 1 |body pqrs part 01
+18 BODY N 19 |
+header_body_checks: test-queueID: warning: body --pqrs
+19 BODY N 20 |--pqrs
+header_body_checks: test-queueID: warning: header header: pqrs part 02
+20 MULT 0 |header: pqrs part 02
+21 BODY N 0 |
+header_body_checks: test-queueID: warning: body body pqrs part 02
+22 BODY N 1 |body pqrs part 02
+23 BODY N 19 |
+header_body_checks: test-queueID: warning: body --bogus-boundary
+24 BODY N 20 |--bogus-boundary
+header_body_checks: test-queueID: warning: body header: wietse
+25 BODY N 37 |header: wietse
+26 BODY N 52 |
+header_body_checks: test-queueID: warning: body body asdasads
+27 BODY N 53 |body asdasads
+28 BODY N 67 |
+header_body_checks: test-queueID: warning: body --abcd ef
+29 BODY N 68 |--abcd ef
+header_body_checks: test-queueID: warning: header header: abcdef part 02
+30 MULT 0 |header: abcdef part 02
+31 BODY N 0 |
+header_body_checks: test-queueID: warning: body body abcdef part 02
+32 BODY N 1 |body abcdef part 02
+33 BODY N 21 |
+header_body_checks: test-queueID: warning: body --abcd ef--
+34 BODY N 0 |--abcd ef--
+35 BODY N 12 |
+header_body_checks: test-queueID: warning: body epilog
+36 BODY N 13 |epilog
+BODY END
+header_body_checks: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/header_opts.c b/src/global/header_opts.c
new file mode 100644
index 0000000..c0c4d5c
--- /dev/null
+++ b/src/global/header_opts.c
@@ -0,0 +1,179 @@
+/*++
+/* NAME
+/* header_opts 3
+/* SUMMARY
+/* message header classification
+/* SYNOPSIS
+/* #include <header_opts.h>
+/*
+/* const HEADER_OPTS *header_opts_find(string)
+/* const char *string;
+/* DESCRIPTION
+/* header_opts_find() takes a message header line and looks up control
+/* information for the corresponding header type.
+/* DIAGNOSTICS
+/* Panic: input is not a valid header line. The result is a pointer
+/* to HEADER_OPTS in case of success, a null pointer when the header
+/* label was not recognized.
+/* SEE ALSO
+/* header_opts(3h) the gory details
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <argv.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <header_opts.h>
+
+ /*
+ * Header names are given in the preferred capitalization. The lookups are
+ * case-insensitive.
+ *
+ * XXX Removing Return-Path: headers should probably be done only with mail
+ * that enters via a non-SMTP channel. Changing this now could break other
+ * software. See also comments in bounce_notify_util.c.
+ */
+static HEADER_OPTS header_opts[] = {
+ "Apparently-To", HDR_APPARENTLY_TO, HDR_OPT_RECIP,
+ "Bcc", HDR_BCC, HDR_OPT_XRECIP,
+ "Cc", HDR_CC, HDR_OPT_XRECIP,
+ "Content-Description", HDR_CONTENT_DESCRIPTION, HDR_OPT_MIME,
+ "Content-Disposition", HDR_CONTENT_DISPOSITION, HDR_OPT_MIME,
+ "Content-ID", HDR_CONTENT_ID, HDR_OPT_MIME,
+ "Content-Length", HDR_CONTENT_LENGTH, 0,
+ "Content-Transfer-Encoding", HDR_CONTENT_TRANSFER_ENCODING, HDR_OPT_MIME,
+ "Content-Type", HDR_CONTENT_TYPE, HDR_OPT_MIME,
+ "Delivered-To", HDR_DELIVERED_TO, 0,
+ "Disposition-Notification-To", HDR_DISP_NOTIFICATION, HDR_OPT_SENDER,
+ "Date", HDR_DATE, 0,
+ "Errors-To", HDR_ERRORS_TO, HDR_OPT_SENDER,
+ "From", HDR_FROM, HDR_OPT_SENDER,
+ "Mail-Followup-To", HDR_MAIL_FOLLOWUP_TO, HDR_OPT_SENDER,
+ "Message-Id", HDR_MESSAGE_ID, 0,
+ "MIME-Version", HDR_MIME_VERSION, HDR_OPT_MIME,
+ "Received", HDR_RECEIVED, 0,
+ "Reply-To", HDR_REPLY_TO, HDR_OPT_SENDER,
+ "Resent-Bcc", HDR_RESENT_BCC, HDR_OPT_XRECIP | HDR_OPT_RR,
+ "Resent-Cc", HDR_RESENT_CC, HDR_OPT_XRECIP | HDR_OPT_RR,
+ "Resent-Date", HDR_RESENT_DATE, HDR_OPT_RR,
+ "Resent-From", HDR_RESENT_FROM, HDR_OPT_SENDER | HDR_OPT_RR,
+ "Resent-Message-Id", HDR_RESENT_MESSAGE_ID, HDR_OPT_RR,
+ "Resent-Reply-To", HDR_RESENT_REPLY_TO, HDR_OPT_RECIP | HDR_OPT_RR,
+ "Resent-Sender", HDR_RESENT_SENDER, HDR_OPT_SENDER | HDR_OPT_RR,
+ "Resent-To", HDR_RESENT_TO, HDR_OPT_XRECIP | HDR_OPT_RR,
+ "Return-Path", HDR_RETURN_PATH, HDR_OPT_SENDER,
+ "Return-Receipt-To", HDR_RETURN_RECEIPT_TO, HDR_OPT_SENDER,
+ "Sender", HDR_SENDER, HDR_OPT_SENDER,
+ "To", HDR_TO, HDR_OPT_XRECIP,
+};
+
+#define HEADER_OPTS_SIZE (sizeof(header_opts) / sizeof(header_opts[0]))
+
+static HTABLE *header_hash; /* quick lookup */
+static VSTRING *header_key;
+
+/* header_opts_init - initialize */
+
+static void header_opts_init(void)
+{
+ const HEADER_OPTS *hp;
+ const char *cp;
+
+ /*
+ * Build a hash table for quick lookup, and allocate memory for
+ * lower-casing the lookup key.
+ */
+ header_key = vstring_alloc(10);
+ header_hash = htable_create(HEADER_OPTS_SIZE);
+ for (hp = header_opts; hp < header_opts + HEADER_OPTS_SIZE; hp++) {
+ VSTRING_RESET(header_key);
+ for (cp = hp->name; *cp; cp++)
+ VSTRING_ADDCH(header_key, TOLOWER(*cp));
+ VSTRING_TERMINATE(header_key);
+ htable_enter(header_hash, vstring_str(header_key), (void *) hp);
+ }
+}
+
+/* header_drop_init - initialize "header drop" flags */
+
+static void header_drop_init(void)
+{
+ ARGV *hdr_drop_list;
+ char **cpp;
+ HTABLE_INFO *ht;
+ HEADER_OPTS *hp;
+
+ /*
+ * Having one main.cf parameter for the "drop" header flag does not
+ * generalize to the "sender", "extract", etc., flags. Flags would need
+ * to be grouped by header name, but that would be unwieldy, too:
+ *
+ * message_header_flags = { apparently-to = recipient }, { bcc = recipient,
+ * extract, drop }, { from = sender }, ...
+ *
+ * Thus, it is unlikely that all header flags will become configurable.
+ */
+ hdr_drop_list = argv_split(var_drop_hdrs, CHARS_COMMA_SP);
+ for (cpp = hdr_drop_list->argv; *cpp; cpp++) {
+ lowercase(*cpp);
+ if ((ht = htable_locate(header_hash, *cpp)) == 0) {
+ hp = (HEADER_OPTS *) mymalloc(sizeof(*hp));
+ hp->type = HDR_OTHER;
+ hp->flags = HDR_OPT_DROP;
+ ht = htable_enter(header_hash, *cpp, (void *) hp);
+ hp->name = ht->key;
+ } else
+ hp = (HEADER_OPTS *) ht->value;
+ hp->flags |= HDR_OPT_DROP;
+ }
+ argv_free(hdr_drop_list);
+}
+
+/* header_opts_find - look up header options */
+
+const HEADER_OPTS *header_opts_find(const char *string)
+{
+ const char *cp;
+
+ if (header_hash == 0) {
+ header_opts_init();
+ header_drop_init();
+ }
+
+ /*
+ * Look up the lower-cased version of the header name.
+ */
+ VSTRING_RESET(header_key);
+ for (cp = string; *cp != ':'; cp++) {
+ if (*cp == 0)
+ msg_panic("header_opts_find: no colon in header: %.30s", string);
+ VSTRING_ADDCH(header_key, TOLOWER(*cp));
+ }
+ vstring_truncate(header_key,
+ trimblanks(vstring_str(header_key), cp - string)
+ - vstring_str(header_key));
+ VSTRING_TERMINATE(header_key);
+ return ((const HEADER_OPTS *) htable_find(header_hash, vstring_str(header_key)));
+}
diff --git a/src/global/header_opts.h b/src/global/header_opts.h
new file mode 100644
index 0000000..b03cc5d
--- /dev/null
+++ b/src/global/header_opts.h
@@ -0,0 +1,84 @@
+#ifndef _HEADER_OPTS_H_INCLUDED_
+#define _HEADER_OPTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* header_opts 3h
+/* SUMMARY
+/* message header classification
+/* SYNOPSIS
+/* #include <header_opts.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+typedef struct {
+ const char *name; /* name, preferred capitalization */
+ int type; /* type, see below */
+ int flags; /* flags, see below */
+} HEADER_OPTS;
+
+ /*
+ * Header types. If we reach 31, we must group the headers we need to
+ * remember at the beginning, or we should use fd_set bit sets.
+ */
+#define HDR_OTHER 0
+#define HDR_APPARENTLY_TO 1
+#define HDR_BCC 2
+#define HDR_CC 3
+#define HDR_CONTENT_LENGTH 4
+#define HDR_CONTENT_TRANSFER_ENCODING 5
+#define HDR_CONTENT_TYPE 6
+#define HDR_DATE 7
+#define HDR_DELIVERED_TO 8
+#define HDR_ERRORS_TO 9
+#define HDR_FROM 10
+#define HDR_MESSAGE_ID 11
+#define HDR_RECEIVED 12
+#define HDR_REPLY_TO 13
+#define HDR_RESENT_BCC 14
+#define HDR_RESENT_CC 15
+#define HDR_RESENT_DATE 16
+#define HDR_RESENT_FROM 17
+#define HDR_RESENT_MESSAGE_ID 18
+#define HDR_RESENT_REPLY_TO 19
+#define HDR_RESENT_SENDER 20
+#define HDR_RESENT_TO 21
+#define HDR_RETURN_PATH 22
+#define HDR_RETURN_RECEIPT_TO 23
+#define HDR_SENDER 24
+#define HDR_TO 25
+#define HDR_MAIL_FOLLOWUP_TO 26
+#define HDR_CONTENT_DESCRIPTION 27
+#define HDR_CONTENT_DISPOSITION 28
+#define HDR_CONTENT_ID 29
+#define HDR_MIME_VERSION 30
+#define HDR_DISP_NOTIFICATION 31
+
+ /*
+ * Header flags.
+ */
+#define HDR_OPT_DROP (1<<0) /* delete from input */
+#define HDR_OPT_SENDER (1<<1) /* sender address */
+#define HDR_OPT_RECIP (1<<2) /* recipient address */
+#define HDR_OPT_RR (1<<3) /* Resent- header */
+#define HDR_OPT_EXTRACT (1<<4) /* extract flag */
+#define HDR_OPT_MIME (1<<5) /* MIME header */
+
+#define HDR_OPT_XRECIP (HDR_OPT_RECIP | HDR_OPT_EXTRACT)
+
+extern const HEADER_OPTS *header_opts_find(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/header_token.c b/src/global/header_token.c
new file mode 100644
index 0000000..3375125
--- /dev/null
+++ b/src/global/header_token.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* header_token 3
+/* SUMMARY
+/* mail header parser
+/* SYNOPSIS
+/* #include <header_token.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int type;
+/* const char *u.value;
+/* /* ... */
+/* .in
+/* } HEADER_TOKEN;
+/*
+/* ssize_t header_token(token, token_len, token_buffer, ptr,
+/* specials, terminator)
+/* HEADER_TOKEN *token;
+/* ssize_t token_len;
+/* VSTRING *token_buffer;
+/* const char **ptr;
+/* const char *specials;
+/* int terminator;
+/* DESCRIPTION
+/* This module parses a mail header value (text after field-name:)
+/* into tokens. The parser understands RFC 822 linear white space,
+/* quoted-string, comment, control characters, and a set of
+/* user-specified special characters.
+/*
+/* A result token type is one of the following:
+/* .IP HEADER_TOK_QSTRING
+/* Quoted string as per RFC 822.
+/* .IP HEADER_TOK_TOKEN
+/* Token as per RFC 822, and the special characters supplied by the
+/* caller.
+/* .IP other
+/* The value of a control character or special character.
+/* .PP
+/* header_token() tokenizes the input and stops after a user-specified
+/* terminator (ignoring all tokens that exceed the capacity of
+/* the result storage), or when it runs out of space for the result.
+/* The terminator is not stored. The result value is the number of
+/* tokens stored, or -1 when the input was exhausted before any tokens
+/* were found.
+/*
+/* Arguments:
+/* .IP token
+/* Result array of HEADER_TOKEN structures. Token string values
+/* are pointers to null-terminated substrings in the token_buffer.
+/* .IP token_len
+/* Length of the array of HEADER_TOKEN structures.
+/* .IP token_buffer
+/* Storage for result token string values.
+/* .IP ptr
+/* Input/output read position. The input is a null-terminated string.
+/* .IP specials
+/* Special characters according to the relevant RFC, or a
+/* null pointer (default to the RFC 822 special characters).
+/* This must include the optional terminator if one is specified.
+/* .IP terminator
+/* The special character to stop after, or zero.
+/* BUGS
+/* Eight-bit characters are not given special treatment.
+/* SEE ALSO
+/* RFC 822 (ARPA Internet Text Messages)
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <lex_822.h>
+#include <header_token.h>
+
+/* Application-specific. */
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
+
+/* header_token - parse out the next item in a message header */
+
+ssize_t header_token(HEADER_TOKEN *token, ssize_t token_len,
+ VSTRING *token_buffer, const char **ptr,
+ const char *user_specials, int user_terminator)
+{
+ ssize_t comment_level;
+ const unsigned char *cp;
+ ssize_t len;
+ int ch;
+ ssize_t tok_count;
+ ssize_t n;
+
+ /*
+ * Initialize.
+ */
+ VSTRING_RESET(token_buffer);
+ cp = CU_CHAR_PTR(*ptr);
+ tok_count = 0;
+ if (user_specials == 0)
+ user_specials = LEX_822_SPECIALS;
+
+ /*
+ * Main parsing loop.
+ *
+ * XXX What was the reason to continue parsing when user_terminator is
+ * specified? Perhaps this was needed at some intermediate stage of
+ * development?
+ */
+ while ((ch = *cp) != 0 && (user_terminator != 0 || tok_count < token_len)) {
+ cp++;
+
+ /*
+ * Skip RFC 822 linear white space.
+ */
+ if (IS_SPACE_TAB_CR_LF(ch))
+ continue;
+
+ /*
+ * Terminator.
+ */
+ if (ch == user_terminator)
+ break;
+
+ /*
+ * Skip RFC 822 comment.
+ */
+ if (ch == '(') {
+ comment_level = 1;
+ while ((ch = *cp) != 0) {
+ cp++;
+ if (ch == '(') { /* comments can nest! */
+ comment_level++;
+ } else if (ch == ')') {
+ if (--comment_level == 0)
+ break;
+ } else if (ch == '\\') {
+ if ((ch = *cp) == 0)
+ break;
+ cp++;
+ }
+ }
+ continue;
+ }
+
+ /*
+ * Copy quoted text according to RFC 822.
+ */
+ if (ch == '"') {
+ if (tok_count < token_len) {
+ token[tok_count].u.offset = LEN(token_buffer);
+ token[tok_count].type = HEADER_TOK_QSTRING;
+ }
+ while ((ch = *cp) != 0) {
+ cp++;
+ if (ch == '"')
+ break;
+ if (ch == '\n') { /* unfold */
+ if (tok_count < token_len) {
+ len = LEN(token_buffer);
+ while (len > 0
+ && IS_SPACE_TAB_CR_LF(STR(token_buffer)[len - 1]))
+ len--;
+ if (len < LEN(token_buffer))
+ vstring_truncate(token_buffer, len);
+ }
+ continue;
+ }
+ if (ch == '\\') {
+ if ((ch = *cp) == 0)
+ break;
+ cp++;
+ }
+ if (tok_count < token_len)
+ VSTRING_ADDCH(token_buffer, ch);
+ }
+ if (tok_count < token_len) {
+ VSTRING_ADDCH(token_buffer, 0);
+ tok_count++;
+ }
+ continue;
+ }
+
+ /*
+ * Control, or special.
+ */
+ if (strchr(user_specials, ch) || ISCNTRL(ch)) {
+ if (tok_count < token_len) {
+ token[tok_count].u.offset = LEN(token_buffer);
+ token[tok_count].type = ch;
+ VSTRING_ADDCH(token_buffer, ch);
+ VSTRING_ADDCH(token_buffer, 0);
+ tok_count++;
+ }
+ continue;
+ }
+
+ /*
+ * Token.
+ */
+ else {
+ if (tok_count < token_len) {
+ token[tok_count].u.offset = LEN(token_buffer);
+ token[tok_count].type = HEADER_TOK_TOKEN;
+ VSTRING_ADDCH(token_buffer, ch);
+ }
+ while ((ch = *cp) != 0 && !IS_SPACE_TAB_CR_LF(ch)
+ && !ISCNTRL(ch) && !strchr(user_specials, ch)) {
+ cp++;
+ if (tok_count < token_len)
+ VSTRING_ADDCH(token_buffer, ch);
+ }
+ if (tok_count < token_len) {
+ VSTRING_ADDCH(token_buffer, 0);
+ tok_count++;
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Ignore a zero-length item after the last terminator.
+ */
+ if (tok_count == 0 && ch == 0)
+ return (-1);
+
+ /*
+ * Finalize. Fill in the string pointer array, now that the token buffer
+ * is no longer dynamically reallocated as it grows.
+ */
+ *ptr = (const char *) cp;
+ for (n = 0; n < tok_count; n++)
+ token[n].u.value = STR(token_buffer) + token[n].u.offset;
+
+ if (msg_verbose)
+ msg_info("header_token: %s %s %s",
+ tok_count > 0 ? token[0].u.value : "",
+ tok_count > 1 ? token[1].u.value : "",
+ tok_count > 2 ? token[2].u.value : "");
+
+ return (tok_count);
+}
diff --git a/src/global/header_token.h b/src/global/header_token.h
new file mode 100644
index 0000000..7154ef7
--- /dev/null
+++ b/src/global/header_token.h
@@ -0,0 +1,47 @@
+#ifndef _HEADER_TOKEN_H_INCLUDED_
+#define _HEADER_TOKEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* header_token 3h
+/* SUMMARY
+/* mail header parser
+/* SYNOPSIS
+/* #include "header_token.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * HEADER header parser tokens. Specials and controls are represented by
+ * themselves. Character pointers point to substrings in a token buffer.
+ */
+typedef struct HEADER_TOKEN {
+ int type; /* see below */
+ union {
+ const char *value; /* just a pointer, not a copy */
+ ssize_t offset; /* index into token buffer */
+ } u; /* indent beats any alternative */
+} HEADER_TOKEN;
+
+#define HEADER_TOK_TOKEN 256
+#define HEADER_TOK_QSTRING 257
+
+extern ssize_t header_token(HEADER_TOKEN *, ssize_t, VSTRING *, const char **, const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/hfrom_format.c b/src/global/hfrom_format.c
new file mode 100644
index 0000000..f0f850a
--- /dev/null
+++ b/src/global/hfrom_format.c
@@ -0,0 +1,281 @@
+/*++
+/* NAME
+/* hfrom_format 3
+/* SUMMARY
+/* Parse a header_from_format setting
+/* SYNOPSIS
+/* #include <hfrom_format.h>
+/*
+/* int hfrom_format_parse(
+/* const char *name,
+/* const char *value)
+/*
+/* const char *str_hfrom_format_code(int code)
+/* DESCRIPTION
+/* hfrom_format_parse() takes a parameter name (used for
+/* diagnostics) and value, and maps it to the corresponding
+/* code: HFROM_FORMAT_NAME_STD maps to HFROM_FORMAT_CODE_STD,
+/* and HFROM_FORMAT_NAME_OBS maps to HFROM_FORMAT_CODE_OBS.
+/*
+/* str_hfrom_format_code() does the reverse mapping.
+/* DIAGNOSTICS
+/* All input errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+#include <msg.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * Application-specific.
+ */
+#include <hfrom_format.h>
+
+ /*
+ * Primitive dependency injection.
+ */
+#ifdef TEST
+extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...);
+
+#define msg_fatal test_msg_fatal
+#endif
+
+ /*
+ * The name-to-code mapping.
+ */
+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,
+};
+
+/* hfrom_format_parse - parse header_from_format setting */
+
+int hfrom_format_parse(const char *name, const char *value)
+{
+ int code;
+
+ if ((code = name_code(hfrom_format_table, NAME_CODE_FLAG_NONE, value)) < 0)
+ msg_fatal("invalid setting: \"%s = %s\"", name, value);
+ return (code);
+}
+
+/* str_hfrom_format_code - convert code to string */
+
+const char *str_hfrom_format_code(int code)
+{
+ const char *name;
+
+ if ((name = str_name_code(hfrom_format_table, code)) == 0)
+ msg_fatal("invalid header format code: %d", code);
+ return (name);
+}
+
+#ifdef TEST
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+ /*
+ * TODO(wietse) make this a proper VSTREAM interface. Instead of temporarily
+ * swapping streams, we could temporarily swap the stream's write function.
+ */
+
+/* vstream_swap - kludge to capture output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+jmp_buf test_fatal_jbuf;
+
+#undef msg_fatal
+
+/* test_msg_fatal - does not return, and does not terminate */
+
+void test_msg_fatal(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ longjmp(test_fatal_jbuf, 1);
+}
+
+struct name_test_case {
+ const char *label; /* identifies test case */
+ const char *config; /* configuration under test */
+ const char *exp_warning; /* expected warning or empty */
+ const int exp_code; /* expected code */
+};
+
+static struct name_test_case name_test_cases[] = {
+ {"hfrom_format_parse good-standard",
+ /* config */ HFROM_FORMAT_NAME_STD,
+ /* warning */ "",
+ /* exp_code */ HFROM_FORMAT_CODE_STD
+ },
+ {"hfrom_format_parse good-obsolete",
+ /* config */ HFROM_FORMAT_NAME_OBS,
+ /* warning */ "",
+ /* exp_code */ HFROM_FORMAT_CODE_OBS
+ },
+ {"hfrom_format_parse bad",
+ /* config */ "does-not-exist",
+ /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse bad = does-not-exist\"\n",
+ /* code */ 0,
+ },
+ {"hfrom_format_parse empty",
+ /* config */ "",
+ /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse empty = \"\n",
+ /* code */ 0,
+ },
+ 0,
+};
+
+struct code_test_case {
+ const char *label; /* identifies test case */
+ int code; /* code under test */
+ const char *exp_warning; /* expected warning or empty */
+ const char *exp_name; /* expected namme */
+};
+
+static struct code_test_case code_test_cases[] = {
+ {"str_hfrom_format_code good-standard",
+ /* code */ HFROM_FORMAT_CODE_STD,
+ /* warning */ "",
+ /* exp_name */ HFROM_FORMAT_NAME_STD
+ },
+ {"str_hfrom_format_code good-obsolete",
+ /* code */ HFROM_FORMAT_CODE_OBS,
+ /* warning */ "",
+ /* exp_name */ HFROM_FORMAT_NAME_OBS
+ },
+ {"str_hfrom_format_code bad",
+ /* config */ 12345,
+ /* warning */ "hfrom_format: warning: invalid header format code: 12345\n",
+ /* exp_name */ 0
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ struct name_test_case *np;
+ int code;
+ struct code_test_case *cp;
+ const char *name;
+ int pass = 0;
+ int fail = 0;
+ int test_failed;
+ VSTRING *msg_buf;
+ VSTREAM *memory_stream;
+
+ msg_vstream_init("hfrom_format", VSTREAM_ERR);
+ msg_buf = vstring_alloc(100);
+
+ for (np = name_test_cases; np->label != 0; np++) {
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ test_failed = 0;
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ if (setjmp(test_fatal_jbuf) == 0)
+ code = hfrom_format_parse(np->label, np->config);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ if (vstream_fclose(memory_stream))
+ msg_fatal("close memory stream: %m");
+ if (strcmp(STR(msg_buf), np->exp_warning) != 0) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ np->label, STR(msg_buf), np->exp_warning);
+ test_failed = 1;
+ }
+ if (*np->exp_warning == 0) {
+ if (code != np->exp_code) {
+ msg_warn("test case %s: got code: \"%d\", want: \"%d\"(%s)",
+ np->label, code, np->exp_code,
+ str_hfrom_format_code(np->exp_code));
+ test_failed = 1;
+ }
+ }
+ if (test_failed) {
+ msg_info("%s: FAIL", np->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", np->label);
+ pass++;
+ }
+ }
+
+ for (cp = code_test_cases; cp->label != 0; cp++) {
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ test_failed = 0;
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ if (setjmp(test_fatal_jbuf) == 0)
+ name = str_hfrom_format_code(cp->code);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ if (vstream_fclose(memory_stream))
+ msg_fatal("close memory stream: %m");
+ if (strcmp(STR(msg_buf), cp->exp_warning) != 0) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ cp->label, STR(msg_buf), cp->exp_warning);
+ test_failed = 1;
+ } else if (*cp->exp_warning == 0) {
+ if (strcmp(name, cp->exp_name)) {
+ msg_warn("test case %s: got name: \"%s\", want: \"%s\"",
+ cp->label, name, cp->exp_name);
+ test_failed = 1;
+ }
+ }
+ if (test_failed) {
+ msg_info("%s: FAIL", cp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", cp->label);
+ pass++;
+ }
+ }
+
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(msg_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/global/hfrom_format.h b/src/global/hfrom_format.h
new file mode 100644
index 0000000..98d3ddd
--- /dev/null
+++ b/src/global/hfrom_format.h
@@ -0,0 +1,34 @@
+#ifndef _HFROM_FORMAT_INCLUDED_
+#define _HFROM_FORMAT_INCLUDED_
+
+/*++
+/* NAME
+/* hfrom_format 3h
+/* SUMMARY
+/* Parse a header_from_format setting
+/* SYNOPSIS
+/* #include <hfrom_format.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define HFROM_FORMAT_CODE_OBS 0 /* Obsolete */
+#define HFROM_FORMAT_CODE_STD 1 /* Standard */
+
+extern int hfrom_format_parse(const char *, const char *);
+extern const char *str_hfrom_format_code(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/hfrom_format.ref b/src/global/hfrom_format.ref
new file mode 100644
index 0000000..28ba870
--- /dev/null
+++ b/src/global/hfrom_format.ref
@@ -0,0 +1,8 @@
+hfrom_format: hfrom_format_parse good-standard: PASS
+hfrom_format: hfrom_format_parse good-obsolete: PASS
+hfrom_format: hfrom_format_parse bad: PASS
+hfrom_format: hfrom_format_parse empty: PASS
+hfrom_format: str_hfrom_format_code good-standard: PASS
+hfrom_format: str_hfrom_format_code good-obsolete: PASS
+hfrom_format: str_hfrom_format_code bad: PASS
+hfrom_format: PASS=7 FAIL=0
diff --git a/src/global/info_log_addr_form.c b/src/global/info_log_addr_form.c
new file mode 100644
index 0000000..cbe3920
--- /dev/null
+++ b/src/global/info_log_addr_form.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* info_log_addr_form 3
+/* SUMMARY
+/* format internal-form information for info logging
+/* SYNOPSIS
+/* #include <info_log_addr_form.h>
+/*
+/* const char *info_log_addr_form_recipient(
+/* const char *recipient_addr)
+/*
+/* const char *info_log_addr_form_sender_addr(
+/* const char *sender_addr)
+/* DESCRIPTION
+/* info_log_addr_form_recipient() and info_log_addr_form_sender_addr()
+/* format an internal-form recipient or sender email address
+/* for non-debug logging. Each function has its own private
+/* buffer. Each call overwrites the result from a previous call.
+/*
+/* Note: the empty address is passed unchanged; it is not
+/* formatted as "".
+/* .IP recipient_addr
+/* .IP *sender_addr
+/* An internal-form email address.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <name_code.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <info_log_addr_form.h>
+#include <mail_addr_form.h>
+#include <mail_params.h>
+#include <quote_822_local.h>
+
+#define INFO_LOG_ADDR_FORM_VAL_NOT_SET 0
+#define INFO_LOG_ADDR_FORM_VAL_INTERNAL 1
+#define INFO_LOG_ADDR_FORM_VAL_EXTERNAL 2
+
+/* Format for info logging. */
+
+int info_log_addr_form_form = INFO_LOG_ADDR_FORM_VAL_NOT_SET;
+
+#define STR(x) vstring_str(x)
+
+/* info_log_addr_form_init - one-time initialization */
+
+static void info_log_addr_form_init(void)
+{
+ static NAME_CODE info_log_addr_form_table[] = {
+ INFO_LOG_ADDR_FORM_NAME_EXTERNAL, INFO_LOG_ADDR_FORM_VAL_EXTERNAL,
+ INFO_LOG_ADDR_FORM_NAME_INTERNAL, INFO_LOG_ADDR_FORM_VAL_INTERNAL,
+ 0, INFO_LOG_ADDR_FORM_VAL_NOT_SET,
+ };
+ info_log_addr_form_form = name_code(info_log_addr_form_table,
+ NAME_CODE_FLAG_NONE,
+ var_info_log_addr_form);
+
+ if (info_log_addr_form_form == INFO_LOG_ADDR_FORM_VAL_NOT_SET)
+ msg_fatal("invalid parameter setting \"%s = %s\"",
+ VAR_INFO_LOG_ADDR_FORM, var_info_log_addr_form);
+}
+
+/* info_log_addr_form - format an email address for info logging */
+
+static VSTRING *info_log_addr_form(VSTRING *buf, const char *addr)
+{
+ const char myname[] = "info_log_addr_form";
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ if (info_log_addr_form_form == INFO_LOG_ADDR_FORM_VAL_NOT_SET)
+ info_log_addr_form_init();
+ if (*addr == 0
+ || info_log_addr_form_form == INFO_LOG_ADDR_FORM_VAL_INTERNAL) {
+ vstring_strcpy(buf, addr);
+ } else if (info_log_addr_form_form == INFO_LOG_ADDR_FORM_VAL_EXTERNAL) {
+ quote_822_local(buf, addr);
+ } else {
+ msg_panic("%s: bad format type: %d",
+ myname, info_log_addr_form_form);
+ }
+ return (buf);
+}
+
+/* info_log_addr_form_recipient - format a recipient address for info logging */
+
+const char *info_log_addr_form_recipient(const char *recipient_addr)
+{
+ static VSTRING *recipient_buffer = 0;
+
+ recipient_buffer = info_log_addr_form(recipient_buffer, recipient_addr);
+ return (STR(recipient_buffer));
+}
+
+/* info_log_addr_form_sender - format a sender address for info logging */
+
+const char *info_log_addr_form_sender(const char *sender_addr)
+{
+ static VSTRING *sender_buffer = 0;
+
+ sender_buffer = info_log_addr_form(sender_buffer, sender_addr);
+ return (STR(sender_buffer));
+}
diff --git a/src/global/info_log_addr_form.h b/src/global/info_log_addr_form.h
new file mode 100644
index 0000000..3b191f4
--- /dev/null
+++ b/src/global/info_log_addr_form.h
@@ -0,0 +1,31 @@
+#ifndef _INFO_LOG_ADDR_FORM_H_INCLUDED_
+#define _INFO_LOG_ADDR_FORM_H_INCLUDED_
+
+/*++
+/* NAME
+/* info_log_addr_form 3h
+/* SUMMARY
+/* format mail address for info logging
+/* SYNOPSIS
+/* #include <info_log_addr_form.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *info_log_addr_form_recipient(const char *);
+extern const char *info_log_addr_form_sender(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/input_transp.c b/src/global/input_transp.c
new file mode 100644
index 0000000..ca3af34
--- /dev/null
+++ b/src/global/input_transp.c
@@ -0,0 +1,102 @@
+/*++
+/* NAME
+/* input_transp 3
+/* SUMMARY
+/* receive transparency control
+/* SYNOPSIS
+/* #include <input_transp.h>
+/*
+/* int input_transp_mask(param_name, pattern)
+/* const char *param_name;
+/* const char *pattern;
+/*
+/* int input_transp_cleanup(cleanup_flags, transp_mask)
+/* int cleanup_flags;
+/* int transp_mask;
+/* DESCRIPTION
+/* This module controls how much processing happens before mail is
+/* written to the Postfix queue. Each transparency option is either
+/* implemented by a client of the cleanup service, or is passed
+/* along in a client request to the cleanup service. This eliminates
+/* the need to configure multiple cleanup service instances.
+/*
+/* input_transp_mask() takes a comma-separated list of names and
+/* computes the corresponding mask. The following names are
+/* recognized in \fBpattern\fR, with the corresponding bit mask
+/* given in parentheses:
+/* .IP "no_unknown_recipient_checks (INPUT_TRANSP_UNKNOWN_RCPT)"
+/* Do not try to reject unknown recipients.
+/* .IP "no_address_mappings (INPUT_TRANSP_ADDRESS_MAPPING)"
+/* Disable canonical address mapping, virtual alias map expansion,
+/* address masquerading, and automatic BCC recipients.
+/* .IP "no_header_body_checks (INPUT_TRANSP_HEADER_BODY)"
+/* Disable header/body_checks.
+/* .IP "no_milters (INPUT_TRANSP_MILTER)"
+/* Disable Milter applications.
+/*
+/* input_transp_cleanup() takes a bunch of cleanup processing
+/* flags and updates them according to the settings in the
+/* specified input transparency mask.
+/* DIAGNOSTICS
+/* Panic: inappropriate use.
+/* 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 <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <cleanup_user.h>
+#include <input_transp.h>
+
+/* input_transp_mask - compute mail receive transparency mask */
+
+int input_transp_mask(const char *param_name, const char *pattern)
+{
+ static const NAME_MASK table[] = {
+ "no_unknown_recipient_checks", INPUT_TRANSP_UNKNOWN_RCPT,
+ "no_address_mappings", INPUT_TRANSP_ADDRESS_MAPPING,
+ "no_header_body_checks", INPUT_TRANSP_HEADER_BODY,
+ "no_milters", INPUT_TRANSP_MILTER,
+ 0,
+ };
+
+ return (name_mask(param_name, table, pattern));
+}
+
+/* input_transp_cleanup - adjust cleanup options */
+
+int input_transp_cleanup(int cleanup_flags, int transp_mask)
+{
+ const char *myname = "input_transp_cleanup";
+
+ if (msg_verbose)
+ msg_info("before %s: cleanup flags = %s",
+ myname, cleanup_strflags(cleanup_flags));
+ if (transp_mask & INPUT_TRANSP_ADDRESS_MAPPING)
+ cleanup_flags &= ~(CLEANUP_FLAG_BCC_OK | CLEANUP_FLAG_MAP_OK);
+ if (transp_mask & INPUT_TRANSP_HEADER_BODY)
+ cleanup_flags &= ~CLEANUP_FLAG_FILTER;
+ if (transp_mask & INPUT_TRANSP_MILTER)
+ cleanup_flags &= ~CLEANUP_FLAG_MILTER;
+ if (msg_verbose)
+ msg_info("after %s: cleanup flags = %s",
+ myname, cleanup_strflags(cleanup_flags));
+ return (cleanup_flags);
+}
diff --git a/src/global/input_transp.h b/src/global/input_transp.h
new file mode 100644
index 0000000..c98c836
--- /dev/null
+++ b/src/global/input_transp.h
@@ -0,0 +1,36 @@
+#ifndef _INPUT_TRANSP_INCLUDED_
+#define _INPUT_TRANSP_INCLUDED_
+
+/*++
+/* NAME
+/* input_transp 3h
+/* SUMMARY
+/* receive transparency control
+/* SYNOPSIS
+/* #include <input_transp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define INPUT_TRANSP_UNKNOWN_RCPT (1<<0)
+#define INPUT_TRANSP_ADDRESS_MAPPING (1<<1)
+#define INPUT_TRANSP_HEADER_BODY (1<<2)
+#define INPUT_TRANSP_MILTER (1<<3)
+
+extern int input_transp_mask(const char *, const char *);
+extern int input_transp_cleanup(int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/int_filt.c b/src/global/int_filt.c
new file mode 100644
index 0000000..7c5f8b5
--- /dev/null
+++ b/src/global/int_filt.c
@@ -0,0 +1,80 @@
+/*++
+/* NAME
+/* int_filt 3
+/* SUMMARY
+/* internal mail filter control
+/* SYNOPSIS
+/* #include <int_filt.h>
+/*
+/* int int_filt_flags(class)
+/* int class;
+/* DESCRIPTION
+/* int_filt_flags() determines the appropriate mail filtering
+/* flags for the cleanup server, depending on the setting of
+/* the internal_mail_filter_classes configuration parameter.
+/*
+/* Specify one of the following:
+/* .IP MAIL_SRC_MASK_NOTIFY
+/* Postmaster notifications from the smtpd(8) and smtp(8)
+/* protocol adapters.
+/* .IP MAIL_SRC_MASK_BOUNCE
+/* Delivery status notifications from the bounce(8) server.
+/* .PP
+/* Other MAIL_SRC_MASK_XXX arguments are permited but will
+/* have no effect.
+/* DIAGNOSTICS
+/* Fatal: invalid mail category name.
+/* 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 <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <cleanup_user.h>
+#include <mail_proto.h>
+#include <int_filt.h>
+
+/* int_filt_flags - map mail class to submission flags */
+
+int int_filt_flags(int class)
+{
+ static const NAME_MASK table[] = {
+ MAIL_SRC_NAME_NOTIFY, MAIL_SRC_MASK_NOTIFY,
+ MAIL_SRC_NAME_BOUNCE, MAIL_SRC_MASK_BOUNCE,
+ MAIL_SRC_NAME_SENDMAIL, 0,
+ MAIL_SRC_NAME_SMTPD, 0,
+ MAIL_SRC_NAME_QMQPD, 0,
+ MAIL_SRC_NAME_FORWARD, 0,
+ MAIL_SRC_NAME_VERIFY, 0,
+ 0,
+ };
+ int filtered_classes = 0;
+
+ if (class && *var_int_filt_classes) {
+ filtered_classes =
+ name_mask(VAR_INT_FILT_CLASSES, table, var_int_filt_classes);
+ if (filtered_classes == 0)
+ msg_warn("%s: bad input: %s", VAR_INT_FILT_CLASSES,
+ var_int_filt_classes);
+ if (filtered_classes & class)
+ return (CLEANUP_FLAG_FILTER | CLEANUP_FLAG_MILTER);
+ }
+ return (0);
+}
diff --git a/src/global/int_filt.h b/src/global/int_filt.h
new file mode 100644
index 0000000..a85d62d
--- /dev/null
+++ b/src/global/int_filt.h
@@ -0,0 +1,34 @@
+#ifndef _INT_FILT_INCLUDED_
+#define _INT_FILT_INCLUDED_
+
+/*++
+/* NAME
+/* int_filt 3h
+/* SUMMARY
+/* internal mail classification
+/* SYNOPSIS
+/* #include <int_filt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define INT_FILT_MASK_NONE (0)
+#define INT_FILT_MASK_NOTIFY (1<<1)
+#define INT_FILT_MASK_BOUNCE (1<<2)
+
+extern int int_filt_flags(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/is_header.c b/src/global/is_header.c
new file mode 100644
index 0000000..891e137
--- /dev/null
+++ b/src/global/is_header.c
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* is_header 3
+/* SUMMARY
+/* message header classification
+/* SYNOPSIS
+/* #include <is_header.h>
+/*
+/* ssize_t is_header(string)
+/* const char *string;
+/*
+/* ssize_t is_header_buf(string, len)
+/* const char *string;
+/* ssize_t len;
+/* DESCRIPTION
+/* is_header() examines the given string and returns non-zero (true)
+/* when it begins with a mail header name + optional space + colon.
+/* The result is the length of the mail header name.
+/*
+/* is_header_buf() is a more elaborate interface for use with strings
+/* that may not be null terminated.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* 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 <ctype.h>
+
+/* Global library. */
+
+#include "is_header.h"
+
+/* is_header_buf - determine if this can be a header line */
+
+ssize_t is_header_buf(const char *str, ssize_t str_len)
+{
+ const unsigned char *cp;
+ int state;
+ int c;
+ ssize_t len;
+
+#define INIT 0
+#define IN_CHAR 1
+#define IN_CHAR_SPACE 2
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
+
+ /*
+ * XXX RFC 2822 Section 4.5, Obsolete header fields: whitespace may
+ * appear between header label and ":" (see: RFC 822, Section 3.4.2.).
+ *
+ * XXX Don't run off the end in case some non-standard iscntrl()
+ * implementation considers null a non-control character...
+ */
+ for (len = 0, state = INIT, cp = CU_CHAR_PTR(str); /* see below */; cp++) {
+ if (str_len != IS_HEADER_NULL_TERMINATED && str_len-- <= 0)
+ return (0);
+ switch (c = *cp) {
+ default:
+ if (c == 0 || !ISASCII(c) || ISCNTRL(c))
+ return (0);
+ if (state == INIT)
+ state = IN_CHAR;
+ if (state == IN_CHAR) {
+ len++;
+ continue;
+ }
+ return (0);
+ case ' ':
+ case '\t':
+ if (state == IN_CHAR)
+ state = IN_CHAR_SPACE;
+ if (state == IN_CHAR_SPACE)
+ continue;
+ return (0);
+ case ':':
+ return ((state == IN_CHAR || state == IN_CHAR_SPACE) ? len : 0);
+ }
+ }
+ /* Redundant return for future proofing. */
+ return (0);
+}
diff --git a/src/global/is_header.h b/src/global/is_header.h
new file mode 100644
index 0000000..e42957f
--- /dev/null
+++ b/src/global/is_header.h
@@ -0,0 +1,32 @@
+#ifndef _IS_HEADER_H_INCLUDED_
+#define _IS_HEADER_H_INCLUDED_
+
+/*++
+/* NAME
+/* is_header 3h
+/* SUMMARY
+/* message header classification
+/* SYNOPSIS
+/* #include <is_header.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+#define IS_HEADER_NULL_TERMINATED (-1)
+#define is_header(str) is_header_buf(str, IS_HEADER_NULL_TERMINATED)
+
+extern ssize_t is_header_buf(const char *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/lex_822.h b/src/global/lex_822.h
new file mode 100644
index 0000000..f462b98
--- /dev/null
+++ b/src/global/lex_822.h
@@ -0,0 +1,36 @@
+#ifndef _LEX_822_H_INCLUDED_
+#define _LEX_822_H_INCLUDED_
+
+/*++
+/* NAME
+/* lex_822 3h
+/* SUMMARY
+/* RFC822 lexicals
+/* SYNOPSIS
+/* #include <lex_822.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * The predicate macros.
+ */
+#define IS_SPACE_TAB(ch) (ch == ' ' || ch == '\t')
+#define IS_SPACE_TAB_CR_LF(ch) (IS_SPACE_TAB(ch) || ch == '\r' || ch == '\n')
+
+ /*
+ * Special characters as per RFC 822.
+ */
+#define LEX_822_SPECIALS "()<>@,;:\\\".[]"
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/log_adhoc.c b/src/global/log_adhoc.c
new file mode 100644
index 0000000..e00e930
--- /dev/null
+++ b/src/global/log_adhoc.c
@@ -0,0 +1,221 @@
+/*++
+/* NAME
+/* log_adhoc 3
+/* SUMMARY
+/* ad-hoc delivery event logging
+/* SYNOPSIS
+/* #include <log_adhoc.h>
+/*
+/* void log_adhoc(id, stats, recipient, relay, dsn, status)
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *recipient;
+/* const char *relay;
+/* DSN *dsn;
+/* const char *status;
+/* DESCRIPTION
+/* This module logs delivery events in an ad-hoc manner.
+/*
+/* log_adhoc() appends a record to the mail logfile
+/*
+/* Arguments:
+/* .IP queue
+/* The message queue name of the original message file.
+/* .IP id
+/* The queue id of the original message file.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP recipient
+/* Recipient information, see recipient_list(3). The address
+/* is formatted by the info_log_addr_form(3) routines.
+/* .IP relay
+/* Host we could (not) talk to.
+/* .IP status
+/* bounced, deferred, sent, and so on.
+/* .IP dsn
+/* Delivery status information. See dsn(3).
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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 <format_tv.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <log_adhoc.h>
+#include <mail_params.h>
+#include <info_log_addr_form.h>
+
+ /*
+ * Don't use "struct timeval" for time differences; use explicit signed
+ * types instead. The code below relies on signed values to detect clocks
+ * jumping back.
+ */
+typedef struct {
+ long dt_sec; /* make sure it's signed */
+ long dt_usec; /* make sure it's signed */
+} DELTA_TIME;
+
+/* log_adhoc - ad-hoc logging */
+
+void log_adhoc(const char *id, MSG_STATS *stats, RECIPIENT *recipient,
+ const char *relay, DSN *dsn,
+ const char *status)
+{
+ static VSTRING *buf;
+ DELTA_TIME delay; /* end-to-end delay */
+ DELTA_TIME pdelay; /* time before queue manager */
+ DELTA_TIME adelay; /* queue manager latency */
+ DELTA_TIME sdelay; /* connection set-up latency */
+ DELTA_TIME xdelay; /* transmission latency */
+ struct timeval now;
+
+ /*
+ * Alas, we need an intermediate buffer for the pre-formatted result.
+ * There are several optional fields, and the delay fields are formatted
+ * in a manner that is not supported by vstring_sprintf().
+ */
+ if (buf == 0)
+ buf = vstring_alloc(100);
+
+ /*
+ * First, critical information that identifies the nature of the
+ * transaction.
+ */
+ vstring_sprintf(buf, "%s: to=<%s>", id,
+ info_log_addr_form_recipient(recipient->address));
+ if (recipient->orig_addr && *recipient->orig_addr
+ && strcasecmp_utf8(recipient->address, recipient->orig_addr) != 0)
+ vstring_sprintf_append(buf, ", orig_to=<%s>",
+ info_log_addr_form_recipient(recipient->orig_addr));
+ vstring_sprintf_append(buf, ", relay=%s", relay);
+ if (stats->reuse_count > 0)
+ vstring_sprintf_append(buf, ", conn_use=%d", stats->reuse_count + 1);
+
+ /*
+ * Next, performance statistics.
+ *
+ * Use wall clock time to compute pdelay (before active queue latency) if
+ * there is no time stamp for active queue entry. This happens when mail
+ * is bounced by the cleanup server.
+ *
+ * Otherwise, use wall clock time to compute adelay (active queue latency)
+ * if there is no time stamp for hand-off to delivery agent. This happens
+ * when mail was deferred or bounced by the queue manager.
+ *
+ * Otherwise, use wall clock time to compute xdelay (message transfer
+ * latency) if there is no time stamp for delivery completion. In the
+ * case of multi-recipient deliveries the delivery agent may specify the
+ * delivery completion time, so that multiple recipient records show the
+ * same delay values.
+ *
+ * Don't compute the sdelay (connection setup latency) if there is no time
+ * stamp for connection setup completion.
+ *
+ * XXX Apparently, Solaris gettimeofday() can return out-of-range
+ * microsecond values.
+ */
+#define DELTA(x, y, z) \
+ do { \
+ (x).dt_sec = (y).tv_sec - (z).tv_sec; \
+ (x).dt_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).dt_usec < 0) { \
+ (x).dt_usec += 1000000; \
+ (x).dt_sec -= 1; \
+ } \
+ while ((x).dt_usec >= 1000000) { \
+ (x).dt_usec -= 1000000; \
+ (x).dt_sec += 1; \
+ } \
+ if ((x).dt_sec < 0) \
+ (x).dt_sec = (x).dt_usec = 0; \
+ } while (0)
+
+#define DELTA_ZERO(x) ((x).dt_sec = (x).dt_usec = 0)
+
+#define TIME_STAMPED(x) ((x).tv_sec > 0)
+
+ if (TIME_STAMPED(stats->deliver_done))
+ now = stats->deliver_done;
+ else
+ GETTIMEOFDAY(&now);
+
+ DELTA(delay, now, stats->incoming_arrival);
+ DELTA_ZERO(adelay);
+ DELTA_ZERO(sdelay);
+ DELTA_ZERO(xdelay);
+ if (TIME_STAMPED(stats->active_arrival)) {
+ DELTA(pdelay, stats->active_arrival, stats->incoming_arrival);
+ if (TIME_STAMPED(stats->agent_handoff)) {
+ DELTA(adelay, stats->agent_handoff, stats->active_arrival);
+ if (TIME_STAMPED(stats->conn_setup_done)) {
+ DELTA(sdelay, stats->conn_setup_done, stats->agent_handoff);
+ DELTA(xdelay, now, stats->conn_setup_done);
+ } else {
+ /* No network client. */
+ DELTA(xdelay, now, stats->agent_handoff);
+ }
+ } else {
+ /* No delivery agent. */
+ DELTA(adelay, now, stats->active_arrival);
+ }
+ } else {
+ /* No queue manager. */
+ DELTA(pdelay, now, stats->incoming_arrival);
+ }
+
+ /*
+ * Round off large time values to an integral number of seconds, and
+ * display small numbers with only two significant digits, as long as
+ * they do not exceed the time resolution.
+ */
+#define SIG_DIGS 2
+#define PRETTY_FORMAT(b, text, x) \
+ do { \
+ vstring_strcat((b), text); \
+ format_tv((b), (x).dt_sec, (x).dt_usec, SIG_DIGS, var_delay_max_res); \
+ } while (0)
+
+ PRETTY_FORMAT(buf, ", delay=", delay);
+ PRETTY_FORMAT(buf, ", delays=", pdelay);
+ PRETTY_FORMAT(buf, "/", adelay);
+ PRETTY_FORMAT(buf, "/", sdelay);
+ PRETTY_FORMAT(buf, "/", xdelay);
+
+ /*
+ * Finally, the delivery status.
+ */
+ vstring_sprintf_append(buf, ", dsn=%s, status=%s (%s)",
+ dsn->status, status, dsn->reason);
+
+ /*
+ * Ship it off.
+ */
+ msg_info("%s", vstring_str(buf));
+}
diff --git a/src/global/log_adhoc.h b/src/global/log_adhoc.h
new file mode 100644
index 0000000..583cb61
--- /dev/null
+++ b/src/global/log_adhoc.h
@@ -0,0 +1,43 @@
+#ifndef _LOG_ADHOC_H_INCLUDED_
+#define _LOG_ADHOC_H_INCLUDED_
+
+/*++
+/* NAME
+/* log_adhoc 3h
+/* SUMMARY
+/* ad-hoc delivery event logging
+/* SYNOPSIS
+/* #include <log_adhoc.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+#include <dsn.h>
+#include <msg_stats.h>
+
+ /*
+ * Client interface.
+ */
+extern void log_adhoc(const char *, MSG_STATS *, RECIPIENT *, const char *,
+ DSN *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/login_sender_match.c b/src/global/login_sender_match.c
new file mode 100644
index 0000000..e263762
--- /dev/null
+++ b/src/global/login_sender_match.c
@@ -0,0 +1,364 @@
+/*++
+/* NAME
+/* login_sender_match 3
+/* SUMMARY
+/* match login and sender against (login, sender) patterns
+/* SYNOPSIS
+/* #include <login_sender.h>
+/*
+/* typedef LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH;
+/*
+/* LOGIN_SENDER_MATCH *login_sender_create(
+/* const char *title,
+/* const char *map_names,
+/* const char *ext_delimiters,
+/* const char *null_sender,
+/* const char *wildcard)
+/*
+/* void login_sender_free(
+/* LOGIN_SENDER_MATCH *lsm)
+/*
+/* int login_sender_match(
+/* LOGIN_SENDER_MATCH *lsm,
+/* const char *login_name,
+/* const char *sender_addr)
+/* DESCRIPTION
+/* This module determines if a login name and internal-form
+/* sender address match a (login name, external-form sender
+/* patterns) table entry. A login name matches if it matches
+/* a lookup table key. A sender address matches the corresponding
+/* table entry if it matches a sender pattern. A wildcard
+/* sender pattern matches any sender address. A sender pattern
+/* that starts with '@' matches the '@' and the domain portion
+/* of a sender address. Otherwise, the matcher ignores the
+/* extension part of a sender address, and requires a
+/* case-insensitive match against a sender pattern.
+/*
+/* login_sender_create() creates a (login name, sender patterns)
+/* matcher.
+/*
+/* login_sender_free() destroys the specified (login name,
+/* sender patterns) matcher.
+/*
+/* login_sender_match() looks up an entry for the \fBlogin_name\fR
+/* argument, and determines if the lookup result matches the
+/* \fBsender_adddr\fR argument.
+/*
+/* Arguments:
+/* .IP title
+/* The name of the configuration parameter that specifies the
+/* map_names value, used for error messages.
+/* .IP map_names
+r* The lookup table(s) with (login name, sender patterns) entries.
+/* .IP ext_delimiters
+/* The set of address extension delimiters.
+/* .IP null_sender
+/* If a sender pattern equals the null_sender pattern, then
+/* the empty address is matched.
+/* .IP wildcard
+/* Null pointer, or non-empty string with a wildcard pattern.
+/* If a sender pattern equals the wildcard pattern, then any
+/* sender address is matched.
+/* .IP login_name
+/* The login name (for example, UNIX account, or SASL username)
+/* that will be used as a search key to locate a list of senders.
+/* .IP sender_addr
+/* The sender email address (unquoted form) that will be matched
+/* against a (login name, sender patterns) table entry.
+/* DIAGNOSTICS
+/* login_sender_match() returns LSM_STAT_FOUND if a
+/* match was found, LOGIN_STAT_NOTFOUND if no match was found,
+/* LSM_STAT_RETRY if the table lookup failed, or
+/* LSM_STAT_CONFIG in case of a configuration error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* 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 <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <maps.h>
+#include <quote_822_local.h>
+#include <strip_addr.h>
+#include <login_sender_match.h>
+
+ /*
+ * Private data structure.
+ */
+struct LOGIN_SENDER_MATCH {
+ MAPS *maps;
+ VSTRING *ext_stripped_sender;
+ char *ext_delimiters;
+ char *null_sender;
+ char *wildcard;
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* login_sender_create - create (login name, sender patterns) matcher */
+
+LOGIN_SENDER_MATCH *login_sender_create(const char *title,
+ const char *map_names,
+ const char *ext_delimiters,
+ const char *null_sender,
+ const char *wildcard)
+{
+ LOGIN_SENDER_MATCH *lsm = mymalloc(sizeof *lsm);
+
+ lsm->maps = maps_create(title, map_names, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ lsm->ext_stripped_sender = vstring_alloc(100);
+ lsm->ext_delimiters = mystrdup(ext_delimiters);
+ if (null_sender == 0 || *null_sender == 0)
+ msg_panic("login_sender_create: null or empty null_sender");
+ lsm->null_sender = mystrdup(null_sender);
+ lsm->wildcard = (wildcard && *wildcard) ? mystrdup(wildcard) : 0;
+ return (lsm);
+}
+
+/* login_sender_free - destroy (login name, sender patterns) matcher */
+
+void login_sender_free(LOGIN_SENDER_MATCH *lsm)
+{
+ maps_free(lsm->maps);
+ vstring_free(lsm->ext_stripped_sender);
+ myfree(lsm->ext_delimiters);
+ myfree(lsm->null_sender);
+ if (lsm->wildcard)
+ myfree(lsm->wildcard);
+ myfree((void *) lsm);
+}
+
+/* strip_externalize_addr - strip address extension and externalize remainder */
+
+static VSTRING *strip_externalize_addr(VSTRING *ext_addr, const char *int_addr,
+ const char *delims)
+{
+ char *int_stripped_addr;
+
+ if ((int_stripped_addr = strip_addr_internal(int_addr,
+ /* extension= */ (char **) 0,
+ delims)) != 0) {
+ quote_822_local(ext_addr, int_stripped_addr);
+ myfree(int_stripped_addr);
+ return (ext_addr);
+ } else {
+ return quote_822_local(ext_addr, int_addr);
+ }
+}
+
+/* login_sender_match - match login and sender against (login, senders) table */
+
+int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
+ const char *sender_addr)
+{
+ int found_or_error = LSM_STAT_NOTFOUND;
+
+ /* Sender patterns and derived info */
+ const char *sender_patterns;
+ char *saved_sender_patterns;
+ char *cp;
+ char *sender_pattern;
+
+ /* Actual sender and derived info */
+ const char *ext_stripped_sender = 0;
+ const char *at_sender_domain;
+
+ /*
+ * Match the login.
+ */
+ if ((sender_patterns = maps_find(lsm->maps, login_name,
+ /* flags= */ 0)) != 0) {
+
+ /*
+ * Match the sender. Don't break a sender pattern between double
+ * quotes.
+ */
+ cp = saved_sender_patterns = mystrdup(sender_patterns);
+ while (found_or_error == LSM_STAT_NOTFOUND
+ && (sender_pattern = mystrtokdq(&cp, CHARS_COMMA_SP)) != 0) {
+ /* Special pattern: @domain. */
+ if (*sender_pattern == '@') {
+ if ((at_sender_domain = strrchr(sender_addr, '@')) != 0
+ && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Special pattern: wildcard. */
+ else if (strcasecmp(sender_pattern, lsm->wildcard) == 0) {
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Special pattern: empty sender. */
+ else if (strcasecmp(sender_pattern, lsm->null_sender) == 0) {
+ if (*sender_addr == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Literal pattern: match the stripped and externalized sender. */
+ else {
+ if (ext_stripped_sender == 0)
+ ext_stripped_sender =
+ STR(strip_externalize_addr(lsm->ext_stripped_sender,
+ sender_addr,
+ lsm->ext_delimiters));
+ if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ }
+ myfree(saved_sender_patterns);
+ } else {
+ found_or_error = lsm->maps->error;
+ }
+ return (found_or_error);
+}
+
+#ifdef TEST
+
+int main(int argc, char **argv)
+{
+ struct testcase {
+ const char *title;
+ const char *map_names;
+ const char *ext_delimiters;
+ const char *null_sender;
+ const char *wildcard;
+ const char *login_name;
+ const char *sender_addr;
+ int exp_return;
+ };
+ struct testcase testcases[] = {
+ {"wildcard works",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "root", "anything", LSM_STAT_FOUND
+ },
+ {"unknown user",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "toor", "anything", LSM_STAT_NOTFOUND
+ },
+ {"bare user",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo", LSM_STAT_FOUND
+ },
+ {"user@domain",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo@example.com", LSM_STAT_FOUND
+ },
+ {"user+ext@domain",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo+bar@example.com", LSM_STAT_FOUND
+ },
+ {"wrong sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "bar@example.com", LSM_STAT_NOTFOUND
+ },
+ {"@domain",
+ "inline:{root=*, {foo = @example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "anyone@example.com", LSM_STAT_FOUND
+ },
+ {"wrong @domain",
+ "inline:{root=*, {foo = @example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "anyone@example.org", LSM_STAT_NOTFOUND
+ },
+ {"null sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "bar", "", LSM_STAT_FOUND
+ },
+ {"wrong null sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "baz", "", LSM_STAT_NOTFOUND
+ },
+ {"error",
+ "inline:{root=*}, fail:sorry",
+ "+-", "<>", "*", "baz", "whatever", LSM_STAT_RETRY
+ },
+ {"no error",
+ "inline:{root=*}, fail:sorry",
+ "+-", "<>", "*", "root", "whatever", LSM_STAT_FOUND
+ },
+ {"unknown uid:number",
+ "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "uid:54321", "foo", LSM_STAT_NOTFOUND
+ },
+ {"known uid:number",
+ "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND
+ },
+ {"unknown \"other last\"",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND
+ },
+ {"bare \"first last\"",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND
+ },
+ {"\"first last\"@domain",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND
+ },
+ };
+ struct testcase *tp;
+ int act_return;
+ int pass;
+ int fail;
+ LOGIN_SENDER_MATCH *lsm;
+
+ /*
+ * Fake variable settings.
+ */
+ var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
+ var_ownreq_special = DEF_OWNREQ_SPECIAL;
+
+#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
+
+ for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+ msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
+#if 0
+ msg_info("title=%s", tp->title);
+ msg_info("map_names=%s", tp->map_names);
+ msg_info("ext_delimiters=%s", tp->ext_delimiters);
+ msg_info("null_sender=%s", tp->null_sender);
+ msg_info("wildcard=%s", tp->wildcard);
+ msg_info("login_name=%s", tp->login_name);
+ msg_info("sender_addr=%s", tp->sender_addr);
+ msg_info("exp_return=%d", tp->exp_return);
+#endif
+ lsm = login_sender_create("test map", tp->map_names,
+ tp->ext_delimiters, tp->null_sender,
+ tp->wildcard);
+ act_return = login_sender_match(lsm, tp->login_name, tp->sender_addr);
+ if (act_return == tp->exp_return) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ } else {
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ }
+ login_sender_free(lsm);
+ }
+ return (fail > 0);
+}
+
+#endif /* TEST */
diff --git a/src/global/login_sender_match.h b/src/global/login_sender_match.h
new file mode 100644
index 0000000..eec0ba9
--- /dev/null
+++ b/src/global/login_sender_match.h
@@ -0,0 +1,49 @@
+#ifndef _LOGIN_SENDER_MATCH_H_INCLUDED_
+#define _LOGIN_SENDER_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* login_sender_match 3h
+/* SUMMARY
+/* oracle for per-login allowed sender addresses
+/* SYNOPSIS
+/* #include <login_sender_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+typedef struct LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH;
+
+extern LOGIN_SENDER_MATCH *login_sender_create(const char *title,
+ const char *map_names,
+ const char *ext_delimiters,
+ const char *null_sender,
+ const char *wildcard);
+extern void login_sender_free(LOGIN_SENDER_MATCH *lsm);
+extern int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
+ const char *sender_addr);
+
+#define LSM_STAT_FOUND (1)
+#define LSM_STAT_NOTFOUND (0)
+#define LSM_STAT_RETRY (DICT_ERR_RETRY)
+#define LSM_STAT_CONFIG (DICT_ERR_CONFIG)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif /* _LOGIN_SENDER_MATCH_H_INCLUDED_ */
diff --git a/src/global/login_sender_match.ref b/src/global/login_sender_match.ref
new file mode 100644
index 0000000..20ea483
--- /dev/null
+++ b/src/global/login_sender_match.ref
@@ -0,0 +1,35 @@
+unknown: RUN test case 0 wildcard works
+unknown: PASS test 0
+unknown: RUN test case 1 unknown user
+unknown: PASS test 1
+unknown: RUN test case 2 bare user
+unknown: PASS test 2
+unknown: RUN test case 3 user@domain
+unknown: PASS test 3
+unknown: RUN test case 4 user+ext@domain
+unknown: PASS test 4
+unknown: RUN test case 5 wrong sender
+unknown: PASS test 5
+unknown: RUN test case 6 @domain
+unknown: PASS test 6
+unknown: RUN test case 7 wrong @domain
+unknown: PASS test 7
+unknown: RUN test case 8 null sender
+unknown: PASS test 8
+unknown: RUN test case 9 wrong null sender
+unknown: PASS test 9
+unknown: RUN test case 10 error
+unknown: warning: fail:sorry lookup error for "baz"
+unknown: PASS test 10
+unknown: RUN test case 11 no error
+unknown: PASS test 11
+unknown: RUN test case 12 unknown uid:number
+unknown: PASS test 12
+unknown: RUN test case 13 known uid:number
+unknown: PASS test 13
+unknown: RUN test case 14 unknown "other last"
+unknown: PASS test 14
+unknown: RUN test case 15 bare "first last"
+unknown: PASS test 15
+unknown: RUN test case 16 "first last"@domain
+unknown: PASS test 16
diff --git a/src/global/mail_addr.c b/src/global/mail_addr.c
new file mode 100644
index 0000000..9ea3cda
--- /dev/null
+++ b/src/global/mail_addr.c
@@ -0,0 +1,96 @@
+/*++
+/* NAME
+/* mail_addr 3
+/* SUMMARY
+/* pre-defined mail addresses
+/* SYNOPSIS
+/* #include <mail_addr.h>
+/*
+/* const char *mail_addr_double_bounce()
+/*
+/* const char *mail_addr_postmaster()
+/*
+/* const char *mail_addr_mail_daemon()
+/* DESCRIPTION
+/* This module predefines the following addresses:
+/* .IP MAIL_ADDR_POSTMASTER
+/* The postmaster handle. Typically used for sending mail to.
+/* .IP MAIL_ADDR_MAIL_DAEMON
+/* The mailer-daemon handle. Typically used to bring bad news.
+/* .IP MAIL_ADDR_EMPTY
+/* The empty mail address. This refers to the postmaster on the
+/* local machine.
+/* .PP
+/* mail_addr_double_bounce() produces the fully-qualified version
+/* of the local double bounce address.
+/*
+/* mail_addr_postmaster() produces the fully-qualified version
+/* of the local postmaster address.
+/*
+/* mail_addr_mail_daemon() produces the fully-qualified version
+/* of the local mailer-daemon address.
+/* CONFIGURATION PARAMETERS
+/* double_bounce_sender, the double bounce pseudo account.
+/* myhostname, the local machine hostname.
+/* BUGS
+/* Addresses are constructed by string concatenation, instead of
+/* passing them to the rewriting service.
+/* 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 <stringops.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_addr.h"
+
+/* mail_addr_double_bounce - construct the local double-bounce address */
+
+const char *mail_addr_double_bounce(void)
+{
+ static char *addr;
+
+ if (addr == 0)
+ addr = concatenate(var_double_bounce_sender, "@",
+ var_myhostname, (char *) 0);
+ return (addr);
+}
+
+/* mail_addr_postmaster - construct the local postmaster address */
+
+const char *mail_addr_postmaster(void)
+{
+ static char *addr;
+
+ if (addr == 0)
+ addr = concatenate(MAIL_ADDR_POSTMASTER, "@",
+ var_myhostname, (char *) 0);
+ return (addr);
+}
+
+/* mail_addr_mail_daemon - construct the local mailer-daemon address */
+
+const char *mail_addr_mail_daemon(void)
+{
+ static char *addr;
+
+ if (addr == 0)
+ addr = concatenate(MAIL_ADDR_MAIL_DAEMON, "@",
+ var_myhostname, (char *) 0);
+ return (addr);
+}
diff --git a/src/global/mail_addr.h b/src/global/mail_addr.h
new file mode 100644
index 0000000..f06a754
--- /dev/null
+++ b/src/global/mail_addr.h
@@ -0,0 +1,36 @@
+#ifndef _MAIL_ADDR_H_INCLUDED_
+#define _MAIL_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr 3h
+/* SUMMARY
+/* pre-defined mail addresses
+/* SYNOPSIS
+/* #include <mail_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Pre-defined addresses.
+ */
+#define MAIL_ADDR_POSTMASTER "postmaster"
+#define MAIL_ADDR_MAIL_DAEMON "MAILER-DAEMON"
+#define MAIL_ADDR_EMPTY ""
+
+extern const char *mail_addr_double_bounce(void);
+extern const char *mail_addr_postmaster(void);
+extern const char *mail_addr_mail_daemon(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_addr_crunch.c b/src/global/mail_addr_crunch.c
new file mode 100644
index 0000000..faaf741
--- /dev/null
+++ b/src/global/mail_addr_crunch.c
@@ -0,0 +1,231 @@
+/*++
+/* NAME
+/* mail_addr_crunch 3
+/* SUMMARY
+/* parse and canonicalize addresses, apply address extension
+/* SYNOPSIS
+/* #include <mail_addr_crunch.h>
+/*
+/* ARGV *mail_addr_crunch_ext_to_int(string, extension)
+/* const char *string;
+/* const char *extension;
+/*
+/* ARGV *mail_addr_crunch_opt(string, extension, in_form, out_form)
+/* const char *string;
+/* const char *extension;
+/* int in_form;
+/* int out_form;
+/* DESCRIPTION
+/* mail_addr_crunch_*() parses a string with zero or more addresses,
+/* rewrites each address to canonical form, and optionally applies
+/* an address extension to each resulting address. The caller is
+/* expected to pass the result to argv_free().
+/*
+/* With mail_addr_crunch_ext_to_int(), the string is in external
+/* form, and the result is in internal form. This API minimizes
+/* the number of conversions between internal and external forms.
+/*
+/* mail_addr_crunch_opt() gives more control, at the cost of
+/* additional conversions between internal and external forms.
+/*
+/* Arguments:
+/* .IP string
+/* A string with zero or more addresses in external (quoted)
+/* form, or in the form specified with the in_form argument.
+/* .IP extension
+/* A null pointer, or an address extension (including the recipient
+/* address delimiter) that is propagated to all result addresses.
+/* This is in internal (unquoted) form.
+/* .IP in_form
+/* .IP out_form
+/* Input and output address forms, either MA_FORM_INTERNAL
+/* (unquoted form) or MA_FORM_EXTERNAL (quoted form).
+/* DIAGNOSTICS
+/* Fatal error: out of memory.
+/* SEE ALSO
+/* tok822_parse(3), address parser
+/* canon_addr(3), address canonicalization
+/* 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 <argv.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <tok822.h>
+#include <canon_addr.h>
+#include <quote_822_local.h>
+#include <mail_addr_crunch.h>
+
+/* mail_addr_crunch - break string into addresses, optionally add extension */
+
+ARGV *mail_addr_crunch_opt(const char *string, const char *extension,
+ int in_form, int out_form)
+{
+ VSTRING *intern_addr = vstring_alloc(100);
+ VSTRING *extern_addr = vstring_alloc(100);
+ VSTRING *canon_addr = vstring_alloc(100);
+ ARGV *argv = argv_alloc(1);
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+ char *ratsign;
+ ssize_t extlen;
+
+ if (extension)
+ extlen = strlen(extension);
+
+#define STR(x) vstring_str(x)
+
+ /*
+ * Optionally convert input from internal form.
+ */
+ if (in_form == MA_FORM_INTERNAL) {
+ quote_822_local(extern_addr, string);
+ string = STR(extern_addr);
+ }
+
+ /*
+ * Parse the string, rewrite each address to canonical form, and convert
+ * the result to external (quoted) form. Optionally apply the extension
+ * to each address found.
+ *
+ * XXX Workaround for the null address. This works for envelopes but
+ * produces ugly results for message headers.
+ */
+ if (*string == 0 || strcmp(string, "<>") == 0)
+ string = "\"\"";
+ tree = tok822_parse(string);
+ /* string->extern_addr would be invalidated by tok822_externalize() */
+ string = 0;
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++) {
+ tok822_externalize(extern_addr, tpp[0]->head, TOK822_STR_DEFL);
+ canon_addr_external(canon_addr, STR(extern_addr));
+ unquote_822_local(intern_addr, STR(canon_addr));
+ if (extension) {
+ VSTRING_SPACE(intern_addr, extlen + 1);
+ if ((ratsign = strrchr(STR(intern_addr), '@')) == 0) {
+ vstring_strcat(intern_addr, extension);
+ } else {
+ memmove(ratsign + extlen, ratsign, strlen(ratsign) + 1);
+ memcpy(ratsign, extension, extlen);
+ VSTRING_SKIP(intern_addr);
+ }
+ }
+ /* Optionally convert output to external form. */
+ if (out_form == MA_FORM_EXTERNAL) {
+ quote_822_local(extern_addr, STR(intern_addr));
+ argv_add(argv, STR(extern_addr), ARGV_END);
+ } else {
+ argv_add(argv, STR(intern_addr), ARGV_END);
+ }
+ }
+ argv_terminate(argv);
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ vstring_free(canon_addr);
+ vstring_free(extern_addr);
+ vstring_free(intern_addr);
+ return (argv);
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program, sort of interactive.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */
+
+VSTRING *canon_addr_external(VSTRING *result, const char *addr)
+{
+ return (vstring_strcpy(result, addr));
+}
+
+static int get_addr_form(const char *prompt, VSTRING *buf)
+{
+ int addr_form;
+
+ if (prompt) {
+ vstream_printf("%s: ", prompt);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_get_nonl(buf, VSTREAM_IN) == VSTREAM_EOF)
+ exit(0);
+ if ((addr_form = mail_addr_form_from_string(STR(buf))) < 0)
+ msg_fatal("bad address form: %s", STR(buf));
+ return (addr_form);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *extension = vstring_alloc(1);
+ VSTRING *buf = vstring_alloc(1);
+ ARGV *argv;
+ char **cpp;
+ int do_prompt = isatty(0);
+ int in_form;
+ int out_form;
+
+ mail_conf_read();
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ in_form = get_addr_form(do_prompt ? "input form" : 0, buf);
+ out_form = get_addr_form(do_prompt ? "output form" : 0, buf);
+ if (do_prompt) {
+ vstream_printf("extension: (CR for none): ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_get_nonl(extension, VSTREAM_IN) == VSTREAM_EOF)
+ exit(0);
+
+ if (do_prompt) {
+ vstream_printf("print strings to be translated, one per line\n");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ argv = mail_addr_crunch_opt(STR(buf), (VSTRING_LEN(extension) ?
+ STR(extension) : 0),
+ in_form, out_form);
+ for (cpp = argv->argv; *cpp; cpp++)
+ vstream_printf("|%s|\n", *cpp);
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(argv);
+ }
+ vstring_free(extension);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_addr_crunch.h b/src/global/mail_addr_crunch.h
new file mode 100644
index 0000000..9fc6745
--- /dev/null
+++ b/src/global/mail_addr_crunch.h
@@ -0,0 +1,52 @@
+#ifndef _MAIL_ADDR_CRUNCH_H_INCLUDED_
+#define _MAIL_ADDR_CRUNCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr_crunch 3h
+/* SUMMARY
+/* parse and canonicalize addresses, apply address extension
+/* SYNOPSIS
+/* #include <mail_addr_crunch.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+
+ /*
+ * External interface.
+ */
+extern ARGV *mail_addr_crunch_opt(const char *, const char *, int, int);
+
+ /*
+ * The least-overhead form.
+ */
+#define mail_addr_crunch_ext_to_int(string, extension) \
+ mail_addr_crunch_opt((string), (extension), MA_FORM_EXTERNAL, \
+ MA_FORM_INTERNAL)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_addr_crunch.in b/src/global/mail_addr_crunch.in
new file mode 100644
index 0000000..bf25737
--- /dev/null
+++ b/src/global/mail_addr_crunch.in
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+echo ==== external to internal, with extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+external
+internal
++extension
+foo@example.com, "foo bar"@example.com, foo+ext@example.com
+EOF
+
+echo ==== external to internal, without extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+external
+internal
+
+foo@example.com, "foo bar"@example.com, foo+ext@example.com
+EOF
+
+echo ==== external to external, with extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+external
+external
++extension
+foo@example.com, "foo bar"@example.com, foo+ext@example.com
+EOF
+
+echo ==== external to external, without extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+external
+external
+
+foo@example.com, "foo bar"@example.com, foo+ext@example.com
+EOF
+
+echo ==== internal to internal, with extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+internal
+internal
++extension
+foo@example.com
+foo+ext@example.com
+EOF
+
+echo ==== internal to internal, without extension
+$VALGRIND ./mail_addr_crunch <<'EOF'
+internal
+internal
+
+foo@example.com
+foo+ext@example.com
+EOF
diff --git a/src/global/mail_addr_crunch.ref b/src/global/mail_addr_crunch.ref
new file mode 100644
index 0000000..fe5fb2e
--- /dev/null
+++ b/src/global/mail_addr_crunch.ref
@@ -0,0 +1,22 @@
+==== external to internal, with extension
+|foo+extension@example.com|
+|foo bar+extension@example.com|
+|foo+ext+extension@example.com|
+==== external to internal, without extension
+|foo@example.com|
+|foo bar@example.com|
+|foo+ext@example.com|
+==== external to external, with extension
+|foo+extension@example.com|
+|"foo bar+extension"@example.com|
+|foo+ext+extension@example.com|
+==== external to external, without extension
+|foo@example.com|
+|"foo bar"@example.com|
+|foo+ext@example.com|
+==== internal to internal, with extension
+|foo+extension@example.com|
+|foo+ext+extension@example.com|
+==== internal to internal, without extension
+|foo@example.com|
+|foo+ext@example.com|
diff --git a/src/global/mail_addr_find.c b/src/global/mail_addr_find.c
new file mode 100644
index 0000000..afbccd5
--- /dev/null
+++ b/src/global/mail_addr_find.c
@@ -0,0 +1,671 @@
+/*++
+/* NAME
+/* mail_addr_find 3
+/* SUMMARY
+/* generic address-based lookup
+/* SYNOPSIS
+/* #include <mail_addr_find.h>
+/*
+/* const char *mail_addr_find_int_to_ext(maps, address, extension)
+/* MAPS *maps;
+/* const char *address;
+/* char **extension;
+/*
+/* const char *mail_addr_find_opt(maps, address, extension, in_form,
+/* query_form, out_form, strategy)
+/* MAPS *maps;
+/* const char *address;
+/* char **extension;
+/* int in_form;
+/* int in_form;
+/* int out_form;
+/* int strategy;
+/* LEGACY SUPPORT
+/* const char *mail_addr_find(maps, address, extension)
+/* MAPS *maps;
+/* const char *address;
+/* char **extension;
+/*
+/* const char *mail_addr_find_to_internal(maps, address, extension)
+/* MAPS *maps;
+/* const char *address;
+/* char **extension;
+/*
+/* const char *mail_addr_find_strategy(maps, address, extension)
+/* MAPS *maps;
+/* const char *address;
+/* char **extension;
+/* int strategy;
+/* DESCRIPTION
+/* mail_addr_find*() searches the specified maps for an entry with as
+/* key the specified address, and derivations from that address.
+/* It is up to the caller to specify its case sensitivity
+/* preferences when it opens the maps.
+/* The result is overwritten upon each call.
+/*
+/* In the lookup table, the key is expected to be in external
+/* form (as produced with the postmap command) and the value is
+/* expected to be in external (quoted) form if it is an email
+/* address. Override these assumptions with the query_form
+/* and out_form arguments.
+/*
+/* With mail_addr_find_int_to_ext(), the specified address is in
+/* internal (unquoted) form, the query is made in external (quoted)
+/* form, and the result is in the form found in the table (it is
+/* not necessarily an email address). This version minimizes
+/* internal/external (unquoted/quoted) conversions of the input,
+/* query, extension, or result.
+/*
+/* mail_addr_find_opt() gives more control, at the cost of
+/* additional conversions between internal and external forms.
+/* In particular, output conversion to internal form assumes
+/* that the lookup result is an email address.
+/*
+/* mail_addr_find() is used by legacy code that historically searched
+/* with internal-form queries. The input is in internal form. It
+/* searches with external-form queries first, and falls back to
+/* internal-form queries if no result was found and the external
+/* and internal forms differ. The result is external form (i.e. no
+/* conversion).
+/*
+/* mail_addr_find_to_internal() is like mail_addr_find() but assumes
+/* that the lookup result is one external-form email address,
+/* and converts it to internal form.
+/*
+/* mail_addr_find_strategy() is like mail_addr_find() but overrides
+/* the default search strategy for full and partial addresses.
+/*
+/* Arguments:
+/* .IP maps
+/* Dictionary search path (see maps(3)).
+/* .IP address
+/* The address to be looked up.
+/* .IP extension
+/* A null pointer, or the address of a pointer that is set to
+/* the address of a dynamic memory copy of the address extension
+/* that had to be chopped off in order to match the lookup tables.
+/* The copy includes the recipient address delimiter.
+/* The copy is in internal (unquoted) form.
+/* The caller is expected to pass the copy to myfree().
+/* .IP query_form
+/* The address form to use for database queries: one of
+/* MA_FORM_INTERNAL (unquoted form), MA_FORM_EXTERNAL (quoted form),
+/* MA_FORM_EXTERNAL_FIRST (external form, then internal form if the
+/* external and internal forms differ), or MA_FORM_INTERNAL_FIRST
+/* (internal form, then external form if the internal and external
+/* forms differ).
+/* .IP in_form
+/* .IP out_form
+/* Input and output address forms, one of MA_FORM_INTERNAL (unquoted
+/* form), or MA_FORM_EXTERNAL (quoted form).
+/* .IP strategy
+/* The lookup strategy for full and partial addresses, specified
+/* as the binary OR of one or more of the following. These lookups
+/* are implemented in the order as listed below.
+/* .RS
+/* .IP MA_FIND_DEFAULT
+/* A convenience alias for (MA_FIND_FULL |
+/* MA_FIND_NOEXT | MA_FIND_LOCALPART_IF_LOCAL |
+/* MA_FIND_AT_DOMAIN).
+/* .IP MA_FIND_FULL
+/* Look up the full email address.
+/* .IP MA_FIND_NOEXT
+/* If no match was found, and the address has a localpart extension,
+/* look up the address after removing the extension.
+/* .IP MA_FIND_LOCALPART_IF_LOCAL
+/* If no match was found, and the domain matches myorigin,
+/* mydestination, or any inet_interfaces or proxy_interfaces IP
+/* address, look up the localpart. If no match was found, and the
+/* address has a localpart extension, repeat the same query after
+/* removing the extension unless MA_FIND_NOEXT is specified.
+/* .IP MA_FIND_LOCALPART_AT_IF_LOCAL
+/* As above, but using the localpart@ instead.
+/* .IP MA_FIND_AT_DOMAIN
+/* If no match was found, look up the @domain without localpart.
+/* .IP MA_FIND_DOMAIN
+/* If no match was found, look up the domain without localpart.
+/* .IP MA_FIND_PDMS
+/* When used with MA_FIND_DOMAIN, the domain also matches subdomains.
+/* .IP MA_FIND_PDDMDS
+/* When used with MA_FIND_DOMAIN, dot-domain also matches
+/* dot-subdomains.
+/* .IP MA_FIND_LOCALPART_AT
+/* If no match was found, look up the localpart@, regardless of
+/* the domain content.
+/* .RE
+/* DIAGNOSTICS
+/* The maps->error value is non-zero when the lookup failed due to
+/* a non-permanent error.
+/* SEE ALSO
+/* maps(3), multi-dictionary search resolve_local(3), recognize
+/* local system
+/* 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 <name_mask.h>
+#include <dict.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <strip_addr.h>
+#include <mail_addr_find.h>
+#include <resolve_local.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+
+#ifdef TEST
+
+static const NAME_MASK strategy_table[] = {
+ "full", MA_FIND_FULL,
+ "noext", MA_FIND_NOEXT,
+ "localpart_if_local", MA_FIND_LOCALPART_IF_LOCAL,
+ "localpart_at_if_local", MA_FIND_LOCALPART_AT_IF_LOCAL,
+ "at_domain", MA_FIND_AT_DOMAIN,
+ "domain", MA_FIND_DOMAIN,
+ "pdms", MA_FIND_PDMS,
+ "pddms", MA_FIND_PDDMDS,
+ "localpart_at", MA_FIND_LOCALPART_AT,
+ "default", MA_FIND_DEFAULT,
+ 0, -1,
+};
+
+/* strategy_from_string - symbolic strategy flags to internal form */
+
+static int strategy_from_string(const char *strategy_string)
+{
+ return (name_mask_delim_opt("strategy_from_string", strategy_table,
+ strategy_string, "|",
+ NAME_MASK_WARN | NAME_MASK_ANY_CASE));
+}
+
+/* strategy_to_string - internal form to symbolic strategy flags */
+
+static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask)
+{
+ static VSTRING *my_buf;
+
+ if (res_buf == 0 && (res_buf = my_buf) == 0)
+ res_buf = my_buf = vstring_alloc(20);
+ return (str_name_mask_opt(res_buf, "strategy_to_string",
+ strategy_table, strategy_mask,
+ NAME_MASK_WARN | NAME_MASK_PIPE));
+}
+
+#endif
+
+ /*
+ * Specify what keys are partial or full, to avoid matching partial
+ * addresses with regular expressions.
+ */
+#define FULL 0
+#define PARTIAL DICT_FLAG_FIXED
+
+/* find_addr - helper to search maps with the right query form */
+
+static const char *find_addr(MAPS *path, const char *address, int flags,
+ int with_domain, int query_form, VSTRING *ext_addr_buf)
+{
+ const char *result;
+
+#define SANS_DOMAIN 0
+#define WITH_DOMAIN 1
+
+ switch (query_form) {
+
+ /*
+ * Query with external-form (quoted) address. The code looks a bit
+ * unusual to emphasize the symmetry with the other cases.
+ */
+ case MA_FORM_EXTERNAL:
+ case MA_FORM_EXTERNAL_FIRST:
+ quote_822_local_flags(ext_addr_buf, address,
+ with_domain ? QUOTE_FLAG_DEFAULT :
+ QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART);
+ result = maps_find(path, STR(ext_addr_buf), flags);
+ if (result != 0 || path->error != 0
+ || query_form != MA_FORM_EXTERNAL_FIRST
+ || strcmp(address, STR(ext_addr_buf)) == 0)
+ break;
+ result = maps_find(path, address, flags);
+ break;
+
+ /*
+ * Query with internal-form (unquoted) address. The code looks a bit
+ * unusual to emphasize the symmetry with the other cases.
+ */
+ case MA_FORM_INTERNAL:
+ case MA_FORM_INTERNAL_FIRST:
+ result = maps_find(path, address, flags);
+ if (result != 0 || path->error != 0
+ || query_form != MA_FORM_INTERNAL_FIRST)
+ break;
+ quote_822_local_flags(ext_addr_buf, address,
+ with_domain ? QUOTE_FLAG_DEFAULT :
+ QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART);
+ if (strcmp(address, STR(ext_addr_buf)) == 0)
+ break;
+ result = maps_find(path, STR(ext_addr_buf), flags);
+ break;
+
+ /*
+ * Can't happen.
+ */
+ default:
+ msg_panic("mail_addr_find: bad query_form: %d", query_form);
+ }
+ return (result);
+}
+
+/* find_local - search on localpart info */
+
+static const char *find_local(MAPS *path, char *ratsign, int rats_offs,
+ char *int_full_key, char *int_bare_key,
+ int query_form, char **extp, char **saved_ext,
+ VSTRING *ext_addr_buf)
+{
+ const char *myname = "mail_addr_find";
+ const char *result;
+ int with_domain;
+ int saved_ch;
+
+ /*
+ * This code was ripped from the middle of a function so that it can be
+ * reused multiple times, that's why the interface makes little sense.
+ */
+ with_domain = rats_offs ? WITH_DOMAIN : SANS_DOMAIN;
+
+ saved_ch = *(unsigned char *) (ratsign + rats_offs);
+ *(ratsign + rats_offs) = 0;
+ result = find_addr(path, int_full_key, PARTIAL, with_domain,
+ query_form, ext_addr_buf);
+ *(ratsign + rats_offs) = saved_ch;
+ if (result == 0 && path->error == 0 && int_bare_key != 0) {
+ if ((ratsign = strrchr(int_bare_key, '@')) == 0)
+ msg_panic("%s: bare key botch", myname);
+ saved_ch = *(unsigned char *) (ratsign + rats_offs);
+ *(ratsign + rats_offs) = 0;
+ if ((result = find_addr(path, int_bare_key, PARTIAL, with_domain,
+ query_form, ext_addr_buf)) != 0
+ && extp != 0) {
+ *extp = *saved_ext;
+ *saved_ext = 0;
+ }
+ *(ratsign + rats_offs) = saved_ch;
+ }
+ return result;
+}
+
+/* mail_addr_find_opt - map a canonical address */
+
+const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
+ int in_form, int query_form,
+ int out_form, int strategy)
+{
+ const char *myname = "mail_addr_find";
+ VSTRING *ext_addr_buf = 0;
+ VSTRING *int_addr_buf = 0;
+ const char *int_addr;
+ static VSTRING *int_result = 0;
+ const char *result;
+ char *ratsign = 0;
+ char *int_full_key;
+ char *int_bare_key;
+ char *saved_ext;
+ int rc = 0;
+
+ /*
+ * Optionally convert the address from external form.
+ */
+ if (in_form == MA_FORM_EXTERNAL) {
+ int_addr_buf = vstring_alloc(100);
+ unquote_822_local(int_addr_buf, address);
+ int_addr = STR(int_addr_buf);
+ } else {
+ int_addr = address;
+ }
+ if (query_form == MA_FORM_EXTERNAL_FIRST
+ || query_form == MA_FORM_EXTERNAL)
+ ext_addr_buf = vstring_alloc(100);
+
+ /*
+ * Initialize.
+ */
+ int_full_key = mystrdup(int_addr);
+ if (*var_rcpt_delim == 0 || (strategy & MA_FIND_NOEXT) == 0) {
+ int_bare_key = saved_ext = 0;
+ } else {
+ /* XXX This could be done after user+foo@domain fails. */
+ int_bare_key =
+ strip_addr_internal(int_full_key, &saved_ext, var_rcpt_delim);
+ }
+
+ /*
+ * Try user+foo@domain and user@domain.
+ */
+ if ((strategy & MA_FIND_FULL) != 0) {
+ result = find_addr(path, int_full_key, FULL, WITH_DOMAIN,
+ query_form, ext_addr_buf);
+ } else {
+ result = 0;
+ path->error = 0;
+ }
+
+ if (result == 0 && path->error == 0 && int_bare_key != 0
+ && (result = find_addr(path, int_bare_key, PARTIAL, WITH_DOMAIN,
+ query_form, ext_addr_buf)) != 0
+ && extp != 0) {
+ *extp = saved_ext;
+ saved_ext = 0;
+ }
+
+ /*
+ * Try user+foo if the domain matches user+foo@$myorigin,
+ * user+foo@$mydestination or user+foo@[${proxy,inet}_interfaces]. Then
+ * try with +foo stripped off.
+ */
+ if (result == 0 && path->error == 0
+ && (ratsign = strrchr(int_full_key, '@')) != 0
+ && (strategy & (MA_FIND_LOCALPART_IF_LOCAL
+ | MA_FIND_LOCALPART_AT_IF_LOCAL)) != 0) {
+ if (strcasecmp_utf8(ratsign + 1, var_myorigin) == 0
+ || (rc = resolve_local(ratsign + 1)) > 0) {
+ if ((strategy & MA_FIND_LOCALPART_IF_LOCAL) != 0)
+ result = find_local(path, ratsign, 0, int_full_key,
+ int_bare_key, query_form, extp, &saved_ext,
+ ext_addr_buf);
+ if (result == 0 && path->error == 0
+ && (strategy & MA_FIND_LOCALPART_AT_IF_LOCAL) != 0)
+ result = find_local(path, ratsign, 1, int_full_key,
+ int_bare_key, query_form, extp, &saved_ext,
+ ext_addr_buf);
+ } else if (rc < 0)
+ path->error = rc;
+ }
+
+ /*
+ * Try @domain.
+ */
+ if (result == 0 && path->error == 0 && ratsign != 0
+ && (strategy & MA_FIND_AT_DOMAIN) != 0)
+ result = maps_find(path, ratsign, PARTIAL);
+
+ /*
+ * Try domain (optionally, subdomains).
+ */
+ if (result == 0 && path->error == 0 && ratsign != 0
+ && (strategy & MA_FIND_DOMAIN) != 0) {
+ const char *name;
+ const char *next;
+
+ if ((strategy & MA_FIND_PDMS) && (strategy & MA_FIND_PDDMDS))
+ msg_warn("mail_addr_find_opt: do not specify both "
+ "MA_FIND_PDMS and MA_FIND_PDDMDS");
+ for (name = ratsign + 1; *name != 0; name = next) {
+ if ((result = maps_find(path, name, PARTIAL)) != 0
+ || path->error != 0
+ || (strategy & (MA_FIND_PDMS | MA_FIND_PDDMDS)) == 0
+ || (next = strchr(name + 1, '.')) == 0)
+ break;
+ if ((strategy & MA_FIND_PDDMDS) == 0)
+ next++;
+ }
+ }
+
+ /*
+ * Try localpart@ even if the domain is not local.
+ */
+ if ((strategy & MA_FIND_LOCALPART_AT) != 0 \
+ &&result == 0 && path->error == 0)
+ result = find_local(path, ratsign, 1, int_full_key,
+ int_bare_key, query_form, extp, &saved_ext,
+ ext_addr_buf);
+
+ /*
+ * Optionally convert the result to internal form. The lookup result is
+ * supposed to be one external-form email address.
+ */
+ if (result != 0 && out_form == MA_FORM_INTERNAL) {
+ if (int_result == 0)
+ int_result = vstring_alloc(100);
+ unquote_822_local(int_result, result);
+ result = STR(int_result);
+ }
+
+ /*
+ * Clean up.
+ */
+ if (msg_verbose)
+ msg_info("%s: %s -> %s", myname, address,
+ result ? result :
+ path->error ? "(try again)" :
+ "(not found)");
+ myfree(int_full_key);
+ if (int_bare_key)
+ myfree(int_bare_key);
+ if (saved_ext)
+ myfree(saved_ext);
+ if (int_addr_buf)
+ vstring_free(int_addr_buf);
+ if (ext_addr_buf)
+ vstring_free(ext_addr_buf);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read an address and expected results from
+ * stdin, and warn about any discrepancies.
+ */
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <mail_params.h>
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s [-v]", progname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(100);
+ char *bp;
+ MAPS *path = 0;
+ const char *result;
+ char *extent;
+ char *cmd;
+ char *in_field;
+ char *query_field;
+ char *out_field;
+ char *strategy_field;
+ char *key_field;
+ char *expect_res;
+ char *expect_ext;
+ int in_form;
+ int query_form;
+ int out_form;
+ int strategy_flags;
+ int ch;
+ int errs = 0;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
+
+ mail_params_init();
+
+ /*
+ * TODO: move these assignments into the read/eval loop.
+ */
+ UPDATE(var_rcpt_delim, "+");
+ UPDATE(var_mydomain, "localdomain");
+ UPDATE(var_myorigin, "localdomain");
+ UPDATE(var_mydest, "localhost.localdomain");
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+ if (msg_verbose)
+ msg_info("> %s", bp);
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#')
+ continue;
+ while (ISSPACE(*bp))
+ bp++;
+
+ /*
+ * Visible comment.
+ */
+ if (strcmp(cmd, "echo") == 0) {
+ vstream_printf("%s\n", bp);
+ }
+
+ /*
+ * Open maps.
+ */
+ else if (strcmp(cmd, "maps") == 0) {
+ if (path)
+ maps_free(path);
+ path = maps_create(argv[0], bp, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ vstream_printf("%s\n", bp);
+ continue;
+ }
+
+ /*
+ * Lookup and verify.
+ */
+ else if (path && strcmp(cmd, "test") == 0) {
+
+ /*
+ * Parse the input and expectations.
+ */
+ /* internal, external. */
+ if ((in_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no input form");
+ if ((in_form = mail_addr_form_from_string(in_field)) < 0)
+ msg_fatal("bad input form: '%s'", in_field);
+ if ((query_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no query form");
+ /* internal, external, external-first. */
+ if ((query_form = mail_addr_form_from_string(query_field)) < 0)
+ msg_fatal("bad query form: '%s'", query_field);
+ if ((out_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no output form");
+ /* internal, external. */
+ if ((out_form = mail_addr_form_from_string(out_field)) < 0)
+ msg_fatal("bad output form: '%s'", out_field);
+ if ((strategy_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no strategy field");
+ if ((strategy_flags = strategy_from_string(strategy_field)) < 0)
+ msg_fatal("bad strategy field: '%s'", strategy_field);
+ if ((key_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no search key");
+ expect_res = mystrtok(&bp, ":");
+ expect_ext = mystrtok(&bp, ":");
+ if (mystrtok(&bp, ":") != 0)
+ msg_fatal("garbage after extension field");
+
+ /*
+ * Lookups.
+ */
+ extent = 0;
+ result = mail_addr_find_opt(path, key_field, &extent,
+ in_form, query_form, out_form,
+ strategy_flags);
+ vstream_printf("%s:%s -%s-> %s:%s (%s)\n",
+ in_field, key_field, query_field, out_field, result ? result :
+ path->error ? "(try again)" :
+ "(not found)", extent ? extent : "null extension");
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Enforce expectations.
+ */
+ if (expect_res && result) {
+ if (strcmp(expect_res, result) != 0) {
+ msg_warn("expect result '%s' but got '%s'", expect_res, result);
+ errs = 1;
+ if (expect_ext && extent) {
+ if (strcmp(expect_ext, extent) != 0)
+ msg_warn("expect extension '%s' but got '%s'",
+ expect_ext, extent);
+ errs = 1;
+ } else if (expect_ext && !extent) {
+ msg_warn("expect extension '%s' but got none", expect_ext);
+ errs = 1;
+ } else if (!expect_ext && extent) {
+ msg_warn("expect no extension but got '%s'", extent);
+ errs = 1;
+ }
+ }
+ } else if (expect_res && !result) {
+ msg_warn("expect result '%s' but got none", expect_res);
+ errs = 1;
+ } else if (!expect_res && result) {
+ msg_warn("expected no result but got '%s'", result);
+ errs = 1;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ if (extent)
+ myfree(extent);
+ }
+
+ /*
+ * Unknown request.
+ */
+ else {
+ msg_warn("bad request: %s", cmd);
+ }
+ }
+ vstring_free(buffer);
+
+ maps_free(path);
+ return (errs != 0);
+}
+
+#endif
diff --git a/src/global/mail_addr_find.h b/src/global/mail_addr_find.h
new file mode 100644
index 0000000..5a725d7
--- /dev/null
+++ b/src/global/mail_addr_find.h
@@ -0,0 +1,82 @@
+#ifndef _MAIL_ADDR_FIND_H_INCLUDED_
+#define _MAIL_ADDR_FIND_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr_find 3h
+/* SUMMARY
+/* generic address-based lookup
+/* SYNOPSIS
+/* #include <mail_addr_find.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+#include <maps.h>
+
+ /*
+ * External interface.
+ */
+extern const char *mail_addr_find_opt(MAPS *, const char *, char **,
+ int, int, int, int);
+
+#define MA_FIND_FULL (1<<0) /* localpart+ext@domain */
+#define MA_FIND_NOEXT (1<<1) /* localpart@domain */
+#define MA_FIND_LOCALPART_IF_LOCAL \
+ (1<<2) /* localpart (maybe localpart+ext) */
+#define MA_FIND_LOCALPART_AT_IF_LOCAL \
+ (1<<3) /* ditto, with @ at end */
+#define MA_FIND_AT_DOMAIN (1<<4) /* @domain */
+#define MA_FIND_DOMAIN (1<<5) /* domain */
+#define MA_FIND_PDMS (1<<6) /* parent matches subdomain */
+#define MA_FIND_PDDMDS (1<<7) /* parent matches dot-subdomain */
+#define MA_FIND_LOCALPART_AT \
+ (1<<8) /* localpart@ (maybe localpart+ext@) */
+
+#define MA_FIND_DEFAULT (MA_FIND_FULL | MA_FIND_NOEXT \
+ | MA_FIND_LOCALPART_IF_LOCAL \
+ | MA_FIND_AT_DOMAIN)
+
+ /* The least-overhead form. */
+#define mail_addr_find_int_to_ext(maps, address, extension) \
+ mail_addr_find_opt((maps), (address), (extension), \
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, \
+ MA_FORM_EXTERNAL, MA_FIND_DEFAULT)
+
+ /* The legacy forms. */
+#define MA_FIND_FORM_LEGACY \
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL_FIRST, \
+ MA_FORM_EXTERNAL
+
+#define mail_addr_find_strategy(maps, address, extension, strategy) \
+ mail_addr_find_opt((maps), (address), (extension), \
+ MA_FIND_FORM_LEGACY, (strategy))
+
+#define mail_addr_find(maps, address, extension) \
+ mail_addr_find_strategy((maps), (address), (extension), \
+ MA_FIND_DEFAULT)
+
+#define mail_addr_find_to_internal(maps, address, extension) \
+ mail_addr_find_opt((maps), (address), (extension), \
+ MA_FIND_FORM_LEGACY, MA_FIND_DEFAULT)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_addr_find.in b/src/global/mail_addr_find.in
new file mode 100644
index 0000000..f4f2fe4
--- /dev/null
+++ b/src/global/mail_addr_find.in
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Format: input form:output form:query:expected result:expected extension
+# The last fields are optional.
+
+echo ==== no search string extension
+maps inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+test internal:external:external:default:plain1@1.example:plain2@2.example
+test internal:external:external:default:aa bb@cc.example:"dd ee"@dd.example
+test external:external:external:default:"aa bb"@cc.example:"dd ee"@dd.example
+test external:external:internal:default:"aa bb"@cc.example:dd ee@dd.example
+test internal:internal:external:default:plain1@1.example:plain2@2.example
+test internal:internal:external:default:aa bb@cc.example
+test internal:internal:external:default:"aa bb"@cc.example:"dd ee"@dd.example
+
+echo ==== with search string extension
+maps inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+test internal:external:external:default:plain1+ext@1.example:plain2@2.example:+ext
+test internal:external:external:default:aa bb+ax bx@cc.example:"dd ee"@dd.example:+ax bx
+test external:external:external:default:"aa bb+ax bx"@cc.example:"dd ee"@dd.example:+ax bx
+test external:external:internal:default:"aa bb+ax bx"@cc.example:dd ee@dd.example:+ax bx
+test internal:internal:external:default:plain1+ext@1.example:plain2@2.example:+ext
+test internal:internal:external:default:"aa bb+ax bx"@cc.example
+test internal:internal:external:default:"aa bb"+ax bx@cc.example:"dd ee"@dd.example:+ax bx
+
+echo ==== at in localpart
+maps inline:{"a@b"=foo@example,"a.b."=bar@example}
+test external:external:external:default:"a@b"@localhost.localdomain:foo@example
+test external:external:external:default:"a@b+ext"@localhost.localdomain:foo@example:+ext
+test external:external:external:default:"a.b."@localhost.localdomain:bar@example
+
+echo ==== legacy support
+maps inline:{"a@b"=extern-1@example,a@b=intern-1@example,a.b.=intern-2@example}
+test internal:external-first:external:default:a@b@localhost.localdomain:extern-1@example
+test internal:external-first:external:default:a.b.@localhost.localdomain:intern-2@example
+
+echo ==== at_domain test
+maps inline:{plain1@1.example=plain2@2.example,@3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+test external:external:external:default:plain1+ext@1.example:plain2@2.example:+ext
+test external:external:external:default:plain2@2.example:
+test external:external:external:default:plain3@3.example:plain4@4.example
+test external:external:external:default:plain5@3.example:plain6@6.example
+
+echo ==== domain test
+maps inline:{plain1@1.example=plain2@2.example,3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+test external:external:external:full|noext|domain:plain1+ext@1.example:plain2@2.example:+ext
+test external:external:external:full|noext|domain:plain2@2.example:
+test external:external:external:full|noext|domain:plain3@3.example:plain4@4.example
+test external:external:external:full|noext|domain:plain5@3.example:plain6@6.example
+
+echo ==== at_domain for local domain
+maps inline:{ab=foo@example,@localhost.localdomain=@bar.example}
+test external:external:external:default:ab@localhost.localdomain:foo@example:
+test external:external:external:default:cd@localhost.localdomain:@bar.example
+
+echo ==== localpart_at_if_local and domain test
+maps inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+test internal:external:external:localpart_at_if_local|domain:ab@localhost.localdomain:foo@example:
+test internal:external:external:localpart_at_if_local|noext|domain:ab+ext@localhost.localdomain:foo@example:+ext
+test internal:external:external:localpart_at_if_local|domain:cd@localhost.localdomain:@bar.example
+
+echo ==== localpart_at has less precedence than domain test
+maps inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+test external:external:external:localpart_at|domain:ab@localhost.localdomain:@bar.example:
+test external:external:external:localpart_at|domain:ab@foo:foo@example
+
+echo ==== domain and subdomain test
+maps inline:{example=example-result,.example=dot-example-result}
+test external:external:external:domain:plain1+ext@1.example
+test external:external:external:domain:foo@sub.example
+test external:external:external:domain:foo@example:example-result
+test external:external:external:domain|pdms:foo@example:example-result
+test external:external:external:domain|pdms:foo@sub.example:example-result
+test external:external:external:domain|pdms:foo@sub.sub.example:example-result
+test external:external:external:domain|pddms:foo@example:example-result
+test external:external:external:domain|pddms:foo@sub.example:dot-example-result
+test external:external:external:domain|pddms:foo@sub.sub.example:dot-example-result
diff --git a/src/global/mail_addr_find.ref b/src/global/mail_addr_find.ref
new file mode 100644
index 0000000..6833eec
--- /dev/null
+++ b/src/global/mail_addr_find.ref
@@ -0,0 +1,63 @@
+==== no search string extension
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:plain1@1.example -external-> external:plain2@2.example (null extension)
+internal:aa bb@cc.example -external-> external:"dd ee"@dd.example (null extension)
+external:"aa bb"@cc.example -external-> external:"dd ee"@dd.example (null extension)
+external:"aa bb"@cc.example -external-> internal:dd ee@dd.example (null extension)
+internal:plain1@1.example -internal-> external:plain2@2.example (null extension)
+internal:aa bb@cc.example -internal-> external:(not found) (null extension)
+internal:"aa bb"@cc.example -internal-> external:"dd ee"@dd.example (null extension)
+==== with search string extension
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+internal:aa bb+ax bx@cc.example -external-> external:"dd ee"@dd.example (+ax bx)
+external:"aa bb+ax bx"@cc.example -external-> external:"dd ee"@dd.example (+ax bx)
+external:"aa bb+ax bx"@cc.example -external-> internal:dd ee@dd.example (+ax bx)
+internal:plain1+ext@1.example -internal-> external:plain2@2.example (+ext)
+internal:"aa bb+ax bx"@cc.example -internal-> external:(not found) (null extension)
+internal:"aa bb"+ax bx@cc.example -internal-> external:"dd ee"@dd.example (+ax bx)
+==== at in localpart
+inline:{"a@b"=foo@example,"a.b."=bar@example}
+external:"a@b"@localhost.localdomain -external-> external:foo@example (null extension)
+external:"a@b+ext"@localhost.localdomain -external-> external:foo@example (+ext)
+external:"a.b."@localhost.localdomain -external-> external:bar@example (null extension)
+==== legacy support
+inline:{"a@b"=extern-1@example,a@b=intern-1@example,a.b.=intern-2@example}
+internal:a@b@localhost.localdomain -external-first-> external:extern-1@example (null extension)
+internal:a.b.@localhost.localdomain -external-first-> external:intern-2@example (null extension)
+==== at_domain test
+inline:{plain1@1.example=plain2@2.example,@3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+external:plain2@2.example -external-> external:(not found) (null extension)
+external:plain3@3.example -external-> external:plain4@4.example (null extension)
+external:plain5@3.example -external-> external:plain6@6.example (null extension)
+==== domain test
+inline:{plain1@1.example=plain2@2.example,3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+external:plain2@2.example -external-> external:(not found) (null extension)
+external:plain3@3.example -external-> external:plain4@4.example (null extension)
+external:plain5@3.example -external-> external:plain6@6.example (null extension)
+==== at_domain for local domain
+inline:{ab=foo@example,@localhost.localdomain=@bar.example}
+external:ab@localhost.localdomain -external-> external:foo@example (null extension)
+external:cd@localhost.localdomain -external-> external:@bar.example (null extension)
+==== localpart_at_if_local and domain test
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+internal:ab@localhost.localdomain -external-> external:foo@example (null extension)
+internal:ab+ext@localhost.localdomain -external-> external:foo@example (+ext)
+internal:cd@localhost.localdomain -external-> external:@bar.example (null extension)
+==== localpart_at has less precedence than domain test
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+external:ab@localhost.localdomain -external-> external:@bar.example (null extension)
+external:ab@foo -external-> external:foo@example (null extension)
+==== domain and subdomain test
+inline:{example=example-result,.example=dot-example-result}
+external:plain1+ext@1.example -external-> external:(not found) (null extension)
+external:foo@sub.example -external-> external:(not found) (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@sub.example -external-> external:example-result (null extension)
+external:foo@sub.sub.example -external-> external:example-result (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@sub.example -external-> external:dot-example-result (null extension)
+external:foo@sub.sub.example -external-> external:dot-example-result (null extension)
diff --git a/src/global/mail_addr_form.c b/src/global/mail_addr_form.c
new file mode 100644
index 0000000..a3cc4ce
--- /dev/null
+++ b/src/global/mail_addr_form.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* mail_addr_form 3
+/* SUMMARY
+/* mail address formats
+/* SYNOPSIS
+/* #include <mail_addr_form.h>
+/*
+/* int mail_addr_form_from_string(const char *addr_form_name)
+/*
+/* const char *mail_addr_form_to_string(int addr_form)
+/* DESCRIPTION
+/* mail_addr_form_from_string() converts a symbolic mail address
+/* form name ("internal", "external", "internal-first") into the
+/* corresponding internal code. The result is -1 if an unrecognized
+/* name was specified.
+/*
+/* mail_addr_form_to_string() converts from internal code
+/* to the corresponding symbolic name. The result is null if
+/* an unrecognized code was specified.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+
+static const NAME_CODE addr_form_table[] = {
+ "external", MA_FORM_EXTERNAL,
+ "internal", MA_FORM_INTERNAL,
+ "external-first", MA_FORM_EXTERNAL_FIRST,
+ "internal-first", MA_FORM_INTERNAL_FIRST,
+ 0, -1,
+};
+
+/* mail_addr_form_from_string - symbolic mail address to internal form */
+
+int mail_addr_form_from_string(const char *addr_form_name)
+{
+ return (name_code(addr_form_table, NAME_CODE_FLAG_NONE, addr_form_name));
+}
+
+const char *mail_addr_form_to_string(int addr_form)
+{
+ return (str_name_code(addr_form_table, addr_form));
+}
diff --git a/src/global/mail_addr_form.h b/src/global/mail_addr_form.h
new file mode 100644
index 0000000..a9a4dea
--- /dev/null
+++ b/src/global/mail_addr_form.h
@@ -0,0 +1,36 @@
+#ifndef _MAIL_ADDR_FORM_H_INCLUDED_
+#define _MAIL_ADDR_FORM_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr_form 3h
+/* SUMMARY
+/* mail address formats
+/* SYNOPSIS
+/* #include <mail_addr_form.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define MA_FORM_INTERNAL 1 /* unquoted form */
+#define MA_FORM_EXTERNAL 2 /* quoted form */
+#define MA_FORM_EXTERNAL_FIRST 3 /* quoted form, then unquoted */
+#define MA_FORM_INTERNAL_FIRST 4 /* unquoted form, then quoted */
+
+extern int mail_addr_form_from_string(const char *);
+extern const char *mail_addr_form_to_string(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/mail_addr_map.c b/src/global/mail_addr_map.c
new file mode 100644
index 0000000..f226628
--- /dev/null
+++ b/src/global/mail_addr_map.c
@@ -0,0 +1,527 @@
+/*++
+/* NAME
+/* mail_addr_map 3
+/* SUMMARY
+/* generic address mapping
+/* SYNOPSIS
+/* #include <mail_addr_map.h>
+/*
+/* ARGV *mail_addr_map_internal(path, address, propagate)
+/* MAPS *path;
+/* const char *address;
+/* int propagate;
+/*
+/* ARGV *mail_addr_map_opt(path, address, propagate, in_form,
+/* query_form, out_form)
+/* MAPS *path;
+/* const char *address;
+/* int propagate;
+/* int in_form;
+/* int query_form;
+/* int out_form;
+/* DESCRIPTION
+/* mail_addr_map_*() returns the translation for the named address,
+/* or a null pointer if none is found.
+/*
+/* With mail_addr_map_internal(), the search address and results
+/* are in internal (unquoted) form.
+/*
+/* mail_addr_map_opt() gives more control, at the cost of additional
+/* conversions between internal and external forms.
+/*
+/* When the \fBpropagate\fR argument is non-zero,
+/* address extensions that aren't explicitly matched in the lookup
+/* table are propagated to the result addresses. The caller is
+/* expected to pass the lookup result to argv_free().
+/*
+/* Lookups are performed by mail_addr_find_*(). When the result has the
+/* form \fI@otherdomain\fR, the result is the original user in
+/* \fIotherdomain\fR.
+/*
+/* Arguments:
+/* .IP path
+/* Dictionary search path (see maps(3)).
+/* .IP address
+/* The address to be looked up in external (quoted) form, or
+/* in the form specified with the in_form argument.
+/* .IP query_form
+/* Database query address forms, either MA_FORM_INTERNAL (unquoted
+/* form), MA_FORM_EXTERNAL (quoted form), MA_FORM_EXTERNAL_FIRST
+/* (external, then internal if the forms differ), or
+/* MA_FORM_INTERNAL_FIRST (internal, then external if the forms
+/* differ).
+/* .IP in_form
+/* .IP out_form
+/* Input and output address forms, either MA_FORM_INTERNAL (unquoted
+/* form) or MA_FORM_EXTERNAL (quoted form).
+/* DIAGNOSTICS
+/* Warnings: map lookup returns a non-address result.
+/*
+/* The path->error value is non-zero when the lookup
+/* failed with a non-permanent error.
+/* SEE ALSO
+/* mail_addr_find(3), mail address matching
+/* mail_addr_crunch(3), mail address parsing and rewriting
+/* 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 <argv.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <quote_822_local.h>
+#include <mail_addr_find.h>
+#include <mail_addr_crunch.h>
+#include <mail_addr_map.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* mail_addr_map - map a canonical address */
+
+ARGV *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
+ int in_form, int query_form, int out_form)
+{
+ VSTRING *buffer = 0;
+ const char *myname = "mail_addr_map";
+ const char *string;
+ char *ratsign;
+ char *extension = 0;
+ ARGV *argv = 0;
+ int i;
+ VSTRING *int_address = 0;
+ VSTRING *ext_address = 0;
+ const char *int_addr;
+
+ /*
+ * Optionally convert input from external form. We prefer internal-form
+ * input to avoid unnecessary input conversion in mail_addr_find_opt().
+ */
+ if (in_form == MA_FORM_EXTERNAL) {
+ int_address = vstring_alloc(100);
+ unquote_822_local(int_address, address);
+ int_addr = STR(int_address);
+ in_form = MA_FORM_INTERNAL;
+ } else {
+ int_addr = address;
+ }
+
+ /*
+ * Look up the full address; if no match is found, look up the address
+ * with the extension stripped off, and remember the unmatched extension.
+ */
+ if ((string = mail_addr_find_opt(path, int_addr, &extension,
+ in_form, query_form,
+ MA_FORM_EXTERNAL,
+ MA_FIND_DEFAULT)) != 0) {
+
+ /*
+ * Prepend the original user to @otherdomain, but do not propagate
+ * the unmatched address extension. Convert the address to external
+ * form just like the mail_addr_find_opt() output.
+ */
+ if (*string == '@') {
+ buffer = vstring_alloc(100);
+ if ((ratsign = strrchr(int_addr, '@')) != 0)
+ vstring_strncpy(buffer, int_addr, ratsign - int_addr);
+ else
+ vstring_strcpy(buffer, int_addr);
+ if (extension)
+ vstring_truncate(buffer, LEN(buffer) - strlen(extension));
+ vstring_strcat(buffer, string);
+ ext_address = vstring_alloc(2 * LEN(buffer));
+ quote_822_local(ext_address, STR(buffer));
+ string = STR(ext_address);
+ }
+
+ /*
+ * Canonicalize the result, and propagate the unmatched extension to
+ * each address found.
+ */
+ argv = mail_addr_crunch_opt(string, propagate ? extension : 0,
+ MA_FORM_EXTERNAL, out_form);
+ if (buffer)
+ vstring_free(buffer);
+ if (ext_address)
+ vstring_free(ext_address);
+ if (msg_verbose)
+ for (i = 0; i < argv->argc; i++)
+ msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]);
+ if (argv->argc == 0) {
+ msg_warn("%s lookup of %s returns non-address result \"%s\"",
+ path->title, address, string);
+ argv = argv_free(argv);
+ path->error = DICT_ERR_RETRY;
+ }
+ }
+
+ /*
+ * No match found.
+ */
+ else {
+ if (msg_verbose)
+ msg_info("%s: %s -> %s", myname, address,
+ path->error ? "(try again)" : "(not found)");
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (extension)
+ myfree(extension);
+ if (int_address)
+ vstring_free(int_address);
+
+ return (argv);
+}
+
+#ifdef TEST
+
+/*
+ * SYNOPSIS
+ * mail_addr_map pass_tests | fail_tests
+ * DESCRIPTION
+ * mail_addr_map performs the specified set of built-in
+ * unit tests. With 'pass_tests', all tests must pass, and
+ * with 'fail_tests' all tests must fail.
+ * DIAGNOSTICS
+ * When a unit test fails, the program prints details of the
+ * failed test.
+ *
+ * The program terminates with a non-zero exit status when at
+ * least one test does not pass with 'pass_tests', or when at
+ * least one test does not fail with 'fail_tests'.
+ */
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <canon_addr.h>
+#include <mail_addr_map.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+
+typedef struct {
+ const char *testname;
+ const char *database;
+ int propagate;
+ const char *delimiter;
+ int in_form;
+ int query_form;
+ int out_form;
+ const char *address;
+ const char *expect_argv[2];
+ int expect_argc;
+} MAIL_ADDR_MAP_TEST;
+
+#define DONT_PROPAGATE_UNMATCHED_EXTENSION 0
+#define DO_PROPAGATE_UNMATCHED_EXTENSION 1
+#define NO_RECIPIENT_DELIMITER ""
+#define PLUS_RECIPIENT_DELIMITER "+"
+#define DOT_RECIPIENT_DELIMITER "."
+
+ /*
+ * All these tests must pass, so that we know that mail_addr_map_opt() works
+ * as intended. mail_addr_map() has always been used for maps that expect
+ * external-form queries, so there are no tests here for internal-form
+ * queries.
+ */
+static MAIL_ADDR_MAP_TEST pass_tests[] = {
+ {
+ "1 external -external-> external, no extension",
+ "inline:{ aa@example.com=bb@example.com }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "aa@example.com",
+ {"bb@example.com"}, 1,
+ },
+ {
+ "2 external -external-> external, extension, propagation",
+ "inline:{ aa@example.com=bb@example.com }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "aa+ext@example.com",
+ {"bb+ext@example.com"}, 1,
+ },
+ {
+ "3 external -external-> external, extension, no propagation, no match",
+ "inline:{ aa@example.com=bb@example.com }",
+ DONT_PROPAGATE_UNMATCHED_EXTENSION, NO_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "aa+ext@example.com",
+ {0}, 0,
+ },
+ {
+ "4 external -external-> external, extension, full match",
+ "inline:{{cc+ext@example.com=dd@example.com,ee@example.com}}",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "cc+ext@example.com",
+ {"dd@example.com", "ee@example.com"}, 2,
+ },
+ {
+ "5 external -external-> external, no extension, quoted",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "\"a a\"@example.com",
+ {"\"b b\"@example.com"}, 1,
+ },
+ {
+ "6 external -external-> external, extension, propagation, quoted",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "\"a a+ext\"@example.com",
+ {"\"b b+ext\"@example.com"}, 1,
+ },
+ {
+ "7 internal -external-> internal, no extension, propagation, embedded space",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
+ "a a@example.com",
+ {"b b@example.com"}, 1,
+ },
+ {
+ "8 internal -external-> internal, extension, propagation, embedded space",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
+ "a a+ext@example.com",
+ {"b b+ext@example.com"}, 1,
+ },
+ {
+ "9 internal -external-> internal, no extension, propagation, embedded space",
+ "inline:{ {a_a@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
+ "a_a@example.com",
+ {"b b@example.com"}, 1,
+ },
+ {
+ "10 internal -external-> internal, extension, propagation, embedded space",
+ "inline:{ {a_a@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
+ "a_a+ext@example.com",
+ {"b b+ext@example.com"}, 1,
+ },
+ {
+ "11 internal -external-> internal, no extension, @domain",
+ "inline:{ {@example.com=@example.net} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "a@a@example.com",
+ {"\"a@a\"@example.net"}, 1,
+ },
+ {
+ "12 external -external-> external, extension, propagation",
+ "inline:{ aa@example.com=bb@example.com }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, DOT_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "aa.ext@example.com",
+ {"bb.ext@example.com"}, 1,
+ },
+ 0,
+};
+
+ /*
+ * All these tests must fail, so that we know that the tests work.
+ */
+static MAIL_ADDR_MAP_TEST fail_tests[] = {
+ {
+ "selftest 1 external -external-> external, no extension, quoted",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "\"a a\"@example.com",
+ {"\"bXb\"@example.com"}, 1,
+ },
+ {
+ "selftest 2 external -external-> external, no extension, quoted",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "\"aXa\"@example.com",
+ {"\"b b\"@example.com"}, 1,
+ },
+ {
+ "selftest 3 external -external-> external, no extension, quoted",
+ "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
+ DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ "\"a a\"@example.com",
+ {0}, 0,
+ },
+ 0,
+};
+
+/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */
+
+VSTRING *canon_addr_external(VSTRING *result, const char *addr)
+{
+ return (vstring_strcpy(result, addr));
+}
+
+static int compare(const char *testname,
+ const char **expect_argv, int expect_argc,
+ char **result_argv, int result_argc)
+{
+ int n;
+ int err = 0;
+
+ if (expect_argc != 0 && result_argc != 0) {
+ for (n = 0; n < expect_argc && n < result_argc; n++) {
+ if (strcmp(expect_argv[n], result_argv[n]) != 0) {
+ msg_warn("fail test %s: expect[%d]='%s', result[%d]='%s'",
+ testname, n, expect_argv[n], n, result_argv[n]);
+ err = 1;
+ }
+ }
+ }
+ if (expect_argc != result_argc) {
+ msg_warn("fail test %s: expects %d results but there were %d",
+ testname, expect_argc, result_argc);
+ for (n = expect_argc; n < result_argc; n++)
+ msg_info("no expect to match result[%d]='%s'", n, result_argv[n]);
+ for (n = result_argc; n < expect_argc; n++)
+ msg_info("no result to match expect[%d]='%s'", n, expect_argv[n]);
+ err = 1;
+ }
+ return (err);
+}
+
+static char *progname;
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s pass_test | fail_test", progname);
+}
+
+int main(int argc, char **argv)
+{
+ MAIL_ADDR_MAP_TEST *test;
+ MAIL_ADDR_MAP_TEST *tests;
+ int errs = 0;
+
+#define UPDATE(dst, src) { if (dst) myfree(dst); dst = mystrdup(src); }
+
+ /*
+ * Parse JCL.
+ */
+ progname = argv[0];
+ if (argc != 2) {
+ usage();
+ } else if (strcmp(argv[1], "pass_tests") == 0) {
+ tests = pass_tests;
+ } else if (strcmp(argv[1], "fail_tests") == 0) {
+ tests = fail_tests;
+ } else {
+ usage();
+ }
+
+ /*
+ * Initialize.
+ */
+ mail_params_init();
+
+ /*
+ * A read-eval-print loop, because specifying C strings with quotes and
+ * backslashes is painful.
+ */
+ for (test = tests; test->testname; test++) {
+ ARGV *result;
+ int fail = 0;
+
+ if (mail_addr_form_to_string(test->in_form) == 0) {
+ msg_warn("test %s: bad in_form field: %d",
+ test->testname, test->in_form);
+ fail = 1;
+ continue;
+ }
+ if (mail_addr_form_to_string(test->query_form) == 0) {
+ msg_warn("test %s: bad query_form field: %d",
+ test->testname, test->query_form);
+ fail = 1;
+ continue;
+ }
+ if (mail_addr_form_to_string(test->out_form) == 0) {
+ msg_warn("test %s: bad out_form field: %d",
+ test->testname, test->out_form);
+ fail = 1;
+ continue;
+ }
+ MAPS *maps = maps_create("test", test->database, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+
+ UPDATE(var_rcpt_delim, test->delimiter);
+ result = mail_addr_map_opt(maps, test->address, test->propagate,
+ test->in_form, test->query_form, test->out_form);
+ if (compare(test->testname, test->expect_argv, test->expect_argc,
+ result ? result->argv : 0, result ? result->argc : 0) != 0) {
+ msg_info("database = %s", test->database);
+ msg_info("propagate = %d", test->propagate);
+ msg_info("delimiter = '%s'", var_rcpt_delim);
+ msg_info("in_form = %s", mail_addr_form_to_string(test->in_form));
+ msg_info("query_form = %s", mail_addr_form_to_string(test->query_form));
+ msg_info("out_form = %s", mail_addr_form_to_string(test->out_form));
+ msg_info("address = %s", test->address);
+ fail = 1;
+ }
+ maps_free(maps);
+ if (result)
+ argv_free(result);
+
+ /*
+ * It is an error if a test does not pass or fail as intended.
+ */
+ errs += (tests == pass_tests ? fail : !fail);
+ }
+ return (errs != 0);
+}
+
+#endif
diff --git a/src/global/mail_addr_map.h b/src/global/mail_addr_map.h
new file mode 100644
index 0000000..2b7428b
--- /dev/null
+++ b/src/global/mail_addr_map.h
@@ -0,0 +1,51 @@
+#ifndef _MAIL_ADDR_MAP_H_INCLUDED_
+#define _MAIL_ADDR_MAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr_map 3h
+/* SUMMARY
+/* generic address mapping
+/* SYNOPSIS
+/* #include <mail_addr_map.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+#include <maps.h>
+
+ /*
+ * External interface.
+ */
+extern ARGV *mail_addr_map_opt(MAPS *, const char *, int, int, int, int);
+
+ /* The least-overhead form. */
+#define mail_addr_map_internal(path, address, propagate) \
+ mail_addr_map_opt((path), (address), (propagate), \
+ MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_addr_map.ref b/src/global/mail_addr_map.ref
new file mode 100644
index 0000000..28647b1
--- /dev/null
+++ b/src/global/mail_addr_map.ref
@@ -0,0 +1,26 @@
+unknown: warning: fail test selftest 1 external -external-> external, no extension, quoted: expect[0]='"bXb"@example.com', result[0]='"b b"@example.com'
+unknown: database = inline:{ {"a a"@example.com="b b"@example.com} }
+unknown: propagate = 1
+unknown: delimiter = '+'
+unknown: in_form = external
+unknown: query_form = external
+unknown: out_form = external
+unknown: address = "a a"@example.com
+unknown: warning: fail test selftest 2 external -external-> external, no extension, quoted: expects 1 results but there were 0
+unknown: no result to match expect[0]='"b b"@example.com'
+unknown: database = inline:{ {"a a"@example.com="b b"@example.com} }
+unknown: propagate = 1
+unknown: delimiter = '+'
+unknown: in_form = external
+unknown: query_form = external
+unknown: out_form = external
+unknown: address = "aXa"@example.com
+unknown: warning: fail test selftest 3 external -external-> external, no extension, quoted: expects 0 results but there were 1
+unknown: no expect to match result[0]='"b b"@example.com'
+unknown: database = inline:{ {"a a"@example.com="b b"@example.com} }
+unknown: propagate = 1
+unknown: delimiter = '+'
+unknown: in_form = external
+unknown: query_form = external
+unknown: out_form = external
+unknown: address = "a a"@example.com
diff --git a/src/global/mail_command_client.c b/src/global/mail_command_client.c
new file mode 100644
index 0000000..3de1169
--- /dev/null
+++ b/src/global/mail_command_client.c
@@ -0,0 +1,108 @@
+/*++
+/* NAME
+/* mail_command_client 3
+/* SUMMARY
+/* single-command client
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* int mail_command_client(class, name, proto, type, attr, ...)
+/* const char *class;
+/* const char *name;
+/* const char *proto;
+/* int type;
+/* const char *attr;
+/* DESCRIPTION
+/* This module implements a client interface for single-command
+/* clients: a client that sends a single command and expects
+/* a single completion status code.
+/*
+/* Arguments:
+/* .IP class
+/* Service type: MAIL_CLASS_PUBLIC or MAIL_CLASS_PRIVATE
+/* .IP name
+/* Service name (master.cf).
+/* .IP proto
+/* The expected protocol name in the server announcement.
+/* .IP "type, attr, ..."
+/* Attribute information as defined in attr_print(3).
+/* DIAGNOSTICS
+/* The result is -1 if the request could not be sent, otherwise
+/* the result is the status reported by the server.
+/* Warnings: problems connecting to the requested service.
+/* Fatal: out of memory.
+/* SEE ALSO
+/* attr_print(3), send attributes over byte stream
+/* mail_command_server(3), server interface
+/* mail_proto(3h), client-server protocol
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+
+/* mail_command_client - single-command transaction with completion status */
+
+int mail_command_client(const char *class, const char *name,
+ const char *proto,...)
+{
+ va_list ap;
+ VSTREAM *stream;
+ int status;
+
+ /*
+ * Talk a little protocol with the specified service.
+ *
+ * This function is used for non-critical services where it is OK to back
+ * off after the first error. Log what communication stage failed, to
+ * facilitate trouble analysis.
+ */
+ if ((stream = mail_connect(class, name, BLOCKING)) == 0) {
+ msg_warn("connect to %s/%s: %m", class, name);
+ return (-1);
+ }
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, proto),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("read %s: %m", VSTREAM_PATH(stream));
+ status = -1;
+ } else if (va_start(ap, proto),
+ (status = attr_vprint(stream, ATTR_FLAG_NONE, ap)),
+ va_end(ap),
+ (status != 0)) {
+ msg_warn("write %s: %m", VSTREAM_PATH(stream));
+ status = -1;
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("write/read %s: %m", VSTREAM_PATH(stream));
+ status = -1;
+ }
+ (void) vstream_fclose(stream);
+ return (status);
+}
diff --git a/src/global/mail_command_server.c b/src/global/mail_command_server.c
new file mode 100644
index 0000000..9565b55
--- /dev/null
+++ b/src/global/mail_command_server.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* mail_command_server 3
+/* SUMMARY
+/* single-command server
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* int mail_command_server(stream, type, name, ...)
+/* VSTREAM *stream;
+/* int type;
+/* const char *name;
+/* DESCRIPTION
+/* This module implements the server interface for single-command
+/* requests: a clients sends a single command and expects a single
+/* completion status code.
+/*
+/* Arguments:
+/* .IP stream
+/* Server endpoint.
+/* .IP "type, name, ..."
+/* Attribute list as defined in attr_scan(3).
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* SEE ALSO
+/* attr_scan(3)
+/* mail_command_client(3) client interface
+/* mail_proto(3h), client-server protocol
+#include <mail_proto.h>
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstream.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+
+/* mail_command_server - read single-command request */
+
+int mail_command_server(VSTREAM *stream,...)
+{
+ va_list ap;
+ int count;
+
+ va_start(ap, stream);
+ count = attr_vscan(stream, ATTR_FLAG_MISSING, ap);
+ va_end(ap);
+ return (count);
+}
diff --git a/src/global/mail_conf.c b/src/global/mail_conf.c
new file mode 100644
index 0000000..cd79d35
--- /dev/null
+++ b/src/global/mail_conf.c
@@ -0,0 +1,278 @@
+/*++
+/* NAME
+/* mail_conf 3
+/* SUMMARY
+/* global configuration parameter management
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* void mail_conf_read()
+/*
+/* void mail_conf_suck()
+/*
+/* void mail_conf_flush()
+/*
+/* void mail_conf_checkdir(config_dir)
+/* const char *config_dir;
+/*
+/* void mail_conf_update(name, value)
+/* const char *name;
+/* const char *value;
+/*
+/* const char *mail_conf_lookup(name)
+/* const char *name;
+/*
+/* const char *mail_conf_eval(string)
+/* const char *string;
+/*
+/* const char *mail_conf_eval_once(string)
+/* const char *string;
+/*
+/* const char *mail_conf_lookup_eval(name)
+/* const char *name;
+/* DESCRIPTION
+/* mail_conf_suck() reads the global Postfix configuration
+/* file, and stores its values into a global configuration
+/* dictionary. When the configuration directory name is not
+/* trusted, this function requires that the directory name is
+/* authorized with the alternate_config_directories setting
+/* in the default main.cf file.
+/*
+/* This function requires that all configuration directory
+/* override mechanisms set the MAIL_CONFIG environment variable,
+/* even if the override was specified via the command line.
+/* This reduces the number of pathways that need to be checked
+/* for possible security attacks.
+/*
+/* mail_conf_read() invokes mail_conf_suck() and assigns the values
+/* to global variables by calling mail_params_init().
+/*
+/* mail_conf_flush() discards the global configuration dictionary.
+/* This is needed in programs that read main.cf multiple times, to
+/* ensure that deleted parameter settings are handled properly.
+/*
+/* mail_conf_checkdir() verifies that configuration directory
+/* is authorized through settings in the default main.cf file,
+/* and terminates the program if it is not.
+/*
+/* The following routines are wrappers around the generic dictionary
+/* access routines.
+/*
+/* mail_conf_update() updates the named global parameter. This has
+/* no effect on parameters whose value has already been looked up.
+/* The update succeeds or the program terminates with fatal error.
+/*
+/* mail_conf_lookup() looks up the value of the named parameter.
+/* A null pointer result means the parameter was not found.
+/* The result is volatile and should be copied if it is to be
+/* used for any appreciable amount of time.
+/*
+/* mail_conf_eval() recursively expands any $parameters in the
+/* string argument. The result is volatile and should be copied
+/* if it is to be used for any appreciable amount of time.
+/*
+/* mail_conf_eval_once() non-recursively expands any $parameters
+/* in the string argument. The result is volatile and should
+/* be copied if it is to be used for any appreciable amount
+/* of time.
+/*
+/* mail_conf_lookup_eval() looks up the named parameter, and expands any
+/* $parameters in the result. The result is volatile and should be
+/* copied if it is to be used for any appreciable amount of time.
+/* DIAGNOSTICS
+/* Fatal errors: malformed numerical value.
+/* ENVIRONMENT
+/* MAIL_CONFIG, non-default configuration database
+/* MAIL_VERBOSE, enable verbose mode
+/* FILES
+/* /etc/postfix: default Postfix configuration directory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* mail_conf_int(3) integer-valued parameters
+/* mail_conf_str(3) string-valued parameters
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <dict.h>
+#include <safe.h>
+#include <stringops.h>
+#include <readlline.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_conf.h"
+
+/* mail_conf_checkdir - authorize non-default directory */
+
+void mail_conf_checkdir(const char *config_dir)
+{
+ VSTRING *buf;
+ VSTREAM *fp;
+ char *path;
+ char *name;
+ char *value;
+ char *cp;
+ int found = 0;
+
+ /*
+ * If running set-[ug]id, require that a non-default configuration
+ * directory name is blessed as a bona fide configuration directory in
+ * the default main.cf file.
+ */
+ path = concatenate(DEF_CONFIG_DIR, "/", "main.cf", (char *) 0);
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
+ msg_fatal("open file %s: %m", path);
+
+ buf = vstring_alloc(1);
+ while (found == 0 && readlline(buf, fp, (int *) 0)) {
+ if (split_nameval(vstring_str(buf), &name, &value) == 0
+ && (strcmp(name, VAR_CONFIG_DIRS) == 0
+ || strcmp(name, VAR_MULTI_CONF_DIRS) == 0)) {
+ while (found == 0 && (cp = mystrtok(&value, CHARS_COMMA_SP)) != 0)
+ if (strcmp(cp, config_dir) == 0)
+ found = 1;
+ }
+ }
+ if (vstream_fclose(fp))
+ msg_fatal("read file %s: %m", path);
+ vstring_free(buf);
+
+ if (found == 0) {
+ msg_error("unauthorized configuration directory name: %s", config_dir);
+ msg_fatal("specify \"%s = %s\" or \"%s = %s\" in %s",
+ VAR_CONFIG_DIRS, config_dir,
+ VAR_MULTI_CONF_DIRS, config_dir, path);
+ }
+ myfree(path);
+}
+
+/* mail_conf_read - read global configuration file */
+
+void mail_conf_read(void)
+{
+ mail_conf_suck();
+ mail_params_init();
+}
+
+/* mail_conf_suck - suck in the global configuration file */
+
+void mail_conf_suck(void)
+{
+ char *config_dir;
+ char *path;
+
+ /*
+ * The code below requires that all configuration directory override
+ * mechanisms set the CONF_ENV_PATH environment variable, even if the
+ * override was specified via the command line. This reduces the number
+ * of pathways that need to be checked for possible security attacks.
+ *
+ * Note: this code necessarily runs before cleanenv() can enforce the
+ * import_environment scrubbing policy.
+ */
+
+ /*
+ * Permit references to unknown configuration variable names. We rely on
+ * a separate configuration checking tool to spot misspelled names and
+ * other kinds of trouble. Enter the configuration directory into the
+ * default dictionary.
+ */
+ if (var_config_dir)
+ myfree(var_config_dir);
+ if ((config_dir = getenv(CONF_ENV_PATH)) == 0)
+ config_dir = DEF_CONFIG_DIR;
+ var_config_dir = mystrdup(config_dir);
+ set_mail_conf_str(VAR_CONFIG_DIR, var_config_dir);
+
+ /*
+ * If the configuration directory name comes from an untrusted source,
+ * require that it is listed in the default main.cf file.
+ */
+ if (strcmp(var_config_dir, DEF_CONFIG_DIR) != 0 /* non-default */
+ && unsafe()) /* untrusted env and cli */
+ mail_conf_checkdir(var_config_dir);
+ path = concatenate(var_config_dir, "/", "main.cf", (char *) 0);
+ if (dict_load_file_xt(CONFIG_DICT, path) == 0)
+ msg_fatal("open %s: %m", path);
+ myfree(path);
+}
+
+/* mail_conf_flush - discard configuration dictionary */
+
+void mail_conf_flush(void)
+{
+ if (dict_handle(CONFIG_DICT) != 0)
+ dict_unregister(CONFIG_DICT);
+}
+
+/* mail_conf_eval - expand macros in string */
+
+const char *mail_conf_eval(const char *string)
+{
+#define RECURSIVE 1
+
+ return (dict_eval(CONFIG_DICT, string, RECURSIVE));
+}
+
+/* mail_conf_eval_once - expand one level of macros in string */
+
+const char *mail_conf_eval_once(const char *string)
+{
+#define NONRECURSIVE 0
+
+ return (dict_eval(CONFIG_DICT, string, NONRECURSIVE));
+}
+
+/* mail_conf_lookup - lookup named variable */
+
+const char *mail_conf_lookup(const char *name)
+{
+ return (dict_lookup(CONFIG_DICT, name));
+}
+
+/* mail_conf_lookup_eval - expand named variable */
+
+const char *mail_conf_lookup_eval(const char *name)
+{
+ const char *value;
+
+#define RECURSIVE 1
+
+ if ((value = dict_lookup(CONFIG_DICT, name)) != 0)
+ value = dict_eval(CONFIG_DICT, value, RECURSIVE);
+ return (value);
+}
+
+/* mail_conf_update - update parameter */
+
+void mail_conf_update(const char *key, const char *value)
+{
+ dict_update(CONFIG_DICT, key, value);
+}
diff --git a/src/global/mail_conf.h b/src/global/mail_conf.h
new file mode 100644
index 0000000..9c3d2fe
--- /dev/null
+++ b/src/global/mail_conf.h
@@ -0,0 +1,249 @@
+#ifndef _MAIL_CONF_H_INCLUDED_
+#define _MAIL_CONF_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_conf 3h
+/* SUMMARY
+/* global configuration parameter management
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Well known names. These are not configurable. One has to start somewhere.
+ */
+#define CONFIG_DICT "mail_dict" /* global Postfix dictionary */
+
+ /*
+ * Environment variables.
+ */
+#define CONF_ENV_PATH "MAIL_CONFIG" /* config database */
+#define CONF_ENV_VERB "MAIL_VERBOSE" /* verbose mode on */
+#define CONF_ENV_DEBUG "MAIL_DEBUG" /* live debugging */
+#define CONF_ENV_LOGTAG "MAIL_LOGTAG" /* instance name */
+
+ /*
+ * External representation for booleans.
+ */
+#define CONFIG_BOOL_YES "yes"
+#define CONFIG_BOOL_NO "no"
+
+ /*
+ * Basic configuration management.
+ */
+extern void mail_conf_read(void);
+extern void mail_conf_suck(void);
+extern void mail_conf_flush(void);
+extern void mail_conf_checkdir(const char *);
+
+extern void mail_conf_update(const char *, const char *);
+extern const char *mail_conf_lookup(const char *);
+extern const char *mail_conf_eval(const char *);
+extern const char *mail_conf_eval_once(const char *);
+extern const char *mail_conf_lookup_eval(const char *);
+
+ /*
+ * Specific parameter lookup routines.
+ */
+extern char *get_mail_conf_str(const char *, const char *, int, int);
+extern int get_mail_conf_int(const char *, int, int, int);
+extern long get_mail_conf_long(const char *, long, long, long);
+extern int get_mail_conf_bool(const char *, int);
+extern int get_mail_conf_time(const char *, const char *, int, int);
+extern int get_mail_conf_nint(const char *, const char *, int, int);
+extern char *get_mail_conf_raw(const char *, const char *, int, int);
+extern int get_mail_conf_nbool(const char *, const char *);
+
+extern char *get_mail_conf_str2(const char *, const char *, const char *, int, int);
+extern int get_mail_conf_int2(const char *, const char *, int, int, int);
+extern long get_mail_conf_long2(const char *, const char *, long, long, long);
+extern int get_mail_conf_time2(const char *, const char *, int, int, int, int);
+extern int get_mail_conf_nint2(const char *, const char *, int, int, int);
+extern void check_mail_conf_str(const char *, const char *, int, int);
+extern void check_mail_conf_time(const char *, int, int, int);
+extern void check_mail_conf_int(const char *, int, int, int);
+
+ /*
+ * Lookup with function-call defaults.
+ */
+extern char *get_mail_conf_str_fn(const char *, const char *(*) (void), int, int);
+extern int get_mail_conf_int_fn(const char *, int (*) (void), int, int);
+extern long get_mail_conf_long_fn(const char *, long (*) (void), long, long);
+extern int get_mail_conf_bool_fn(const char *, int (*) (void));
+extern int get_mail_conf_time_fn(const char *, const char *(*) (void), int, int, int);
+extern int get_mail_conf_nint_fn(const char *, const char *(*) (void), int, int);
+extern char *get_mail_conf_raw_fn(const char *, const char *(*) (void), int, int);
+extern int get_mail_conf_nbool_fn(const char *, const char *(*) (void));
+
+ /*
+ * Update dictionary.
+ */
+extern void set_mail_conf_str(const char *, const char *);
+extern void set_mail_conf_int(const char *, int);
+extern void set_mail_conf_long(const char *, long);
+extern void set_mail_conf_bool(const char *, int);
+extern void set_mail_conf_time(const char *, const char *);
+extern void set_mail_conf_time_int(const char *, int);
+extern void set_mail_conf_nint(const char *, const char *);
+extern void set_mail_conf_nint_int(const char *, int);
+extern void set_mail_conf_nbool(const char *, const char *);
+
+#define set_mail_conf_nbool_int(name, value) \
+ set_mail_conf_nbool((name), (value) ? CONFIG_BOOL_YES : CONFIG_BOOL_NO)
+
+ /*
+ * Tables that allow us to selectively copy values from the global
+ * configuration file to global variables.
+ */
+typedef struct {
+ const char *name; /* config variable name */
+ const char *defval; /* default value or null */
+ char **target; /* pointer to global variable */
+ int min; /* min length or zero */
+ int max; /* max length or zero */
+} CONFIG_STR_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *defval; /* default value or null */
+ char **target; /* pointer to global variable */
+ int min; /* min length or zero */
+ int max; /* max length or zero */
+} CONFIG_RAW_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ int defval; /* default value */
+ int *target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_INT_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ long defval; /* default value */
+ long *target; /* pointer to global variable */
+ long min; /* lower bound or zero */
+ long max; /* upper bound or zero */
+} CONFIG_LONG_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ int defval; /* default value */
+ int *target; /* pointer to global variable */
+} CONFIG_BOOL_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *defval; /* default value + default unit */
+ int *target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_TIME_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *defval; /* default value + default unit */
+ int *target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_NINT_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *defval; /* default value */
+ int *target; /* pointer to global variable */
+} CONFIG_NBOOL_TABLE;
+
+extern void get_mail_conf_str_table(const CONFIG_STR_TABLE *);
+extern void get_mail_conf_int_table(const CONFIG_INT_TABLE *);
+extern void get_mail_conf_long_table(const CONFIG_LONG_TABLE *);
+extern void get_mail_conf_bool_table(const CONFIG_BOOL_TABLE *);
+extern void get_mail_conf_time_table(const CONFIG_TIME_TABLE *);
+extern void get_mail_conf_nint_table(const CONFIG_NINT_TABLE *);
+extern void get_mail_conf_raw_table(const CONFIG_RAW_TABLE *);
+extern void get_mail_conf_nbool_table(const CONFIG_NBOOL_TABLE *);
+
+ /*
+ * Tables to initialize parameters from the global configuration file or
+ * from function calls.
+ */
+typedef struct {
+ const char *name; /* config variable name */
+ const char *(*defval) (void); /* default value provider */
+ char **target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_STR_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *(*defval) (void); /* default value provider */
+ char **target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_RAW_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ int (*defval) (void); /* default value provider */
+ int *target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_INT_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ long (*defval) (void); /* default value provider */
+ long *target; /* pointer to global variable */
+ long min; /* lower bound or zero */
+ long max; /* upper bound or zero */
+} CONFIG_LONG_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ int (*defval) (void); /* default value provider */
+ int *target; /* pointer to global variable */
+} CONFIG_BOOL_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *(*defval) (void); /* default value provider */
+ int *target; /* pointer to global variable */
+ int min; /* lower bound or zero */
+ int max; /* upper bound or zero */
+} CONFIG_NINT_FN_TABLE;
+
+typedef struct {
+ const char *name; /* config variable name */
+ const char *(*defval) (void); /* default value provider */
+ int *target; /* pointer to global variable */
+} CONFIG_NBOOL_FN_TABLE;
+
+extern void get_mail_conf_str_fn_table(const CONFIG_STR_FN_TABLE *);
+extern void get_mail_conf_int_fn_table(const CONFIG_INT_FN_TABLE *);
+extern void get_mail_conf_long_fn_table(const CONFIG_LONG_FN_TABLE *);
+extern void get_mail_conf_bool_fn_table(const CONFIG_BOOL_FN_TABLE *);
+extern void get_mail_conf_raw_fn_table(const CONFIG_RAW_FN_TABLE *);
+extern void get_mail_conf_nint_fn_table(const CONFIG_NINT_FN_TABLE *);
+extern void get_mail_conf_nbool_fn_table(const CONFIG_NBOOL_FN_TABLE *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_conf_bool.c b/src/global/mail_conf_bool.c
new file mode 100644
index 0000000..30d4813
--- /dev/null
+++ b/src/global/mail_conf_bool.c
@@ -0,0 +1,153 @@
+/*++
+/* NAME
+/* mail_conf_bool 3
+/* SUMMARY
+/* boolean-valued configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_bool(name, defval)
+/* const char *name;
+/* int defval;
+/*
+/* int get_mail_conf_bool_fn(name, defval)
+/* const char *name;
+/* int (*defval)();
+/*
+/* void set_mail_conf_bool(name, value)
+/* const char *name;
+/* int value;
+/*
+/* void get_mail_conf_bool_table(table)
+/* const CONFIG_BOOL_TABLE *table;
+/*
+/* void get_mail_conf_bool_fn_table(table)
+/* const CONFIG_BOOL_TABLE *table;
+/* DESCRIPTION
+/* This module implements configuration parameter support for
+/* boolean values. The internal representation is zero (false)
+/* and non-zero (true). The external representation is "no"
+/* (false) and "yes" (true). The conversion from external
+/* representation is case insensitive.
+/*
+/* get_mail_conf_bool() looks up the named entry in the global
+/* configuration dictionary. The specified default value is
+/* returned when no value was found.
+/*
+/* get_mail_conf_bool_fn() is similar but specifies a function that
+/* provides the default value. The function is called only
+/* when the default value is needed.
+/*
+/* set_mail_conf_bool() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_bool_table() and get_mail_conf_int_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/* DIAGNOSTICS
+/* Fatal errors: malformed boolean value.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* mail_conf_int(3) integer-valued configuration parameters
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* convert_mail_conf_bool - look up and convert boolean parameter value */
+
+static int convert_mail_conf_bool(const char *name, int *intval)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup_eval(name)) == 0) {
+ return (0);
+ } else {
+ if (strcasecmp(strval, CONFIG_BOOL_YES) == 0) {
+ *intval = 1;
+ } else if (strcasecmp(strval, CONFIG_BOOL_NO) == 0) {
+ *intval = 0;
+ } else {
+ msg_fatal("bad boolean configuration: %s = %s", name, strval);
+ }
+ return (1);
+ }
+}
+
+/* get_mail_conf_bool - evaluate boolean-valued configuration variable */
+
+int get_mail_conf_bool(const char *name, int defval)
+{
+ int intval;
+
+ if (convert_mail_conf_bool(name, &intval) == 0)
+ set_mail_conf_bool(name, intval = defval);
+ return (intval);
+}
+
+/* get_mail_conf_bool_fn - evaluate boolean-valued configuration variable */
+
+typedef int (*stupid_indent_int) (void);
+
+int get_mail_conf_bool_fn(const char *name, stupid_indent_int defval)
+{
+ int intval;
+
+ if (convert_mail_conf_bool(name, &intval) == 0)
+ set_mail_conf_bool(name, intval = defval());
+ return (intval);
+}
+
+/* set_mail_conf_bool - update boolean-valued configuration dictionary entry */
+
+void set_mail_conf_bool(const char *name, int value)
+{
+ mail_conf_update(name, value ? CONFIG_BOOL_YES : CONFIG_BOOL_NO);
+}
+
+/* get_mail_conf_bool_table - look up table of booleans */
+
+void get_mail_conf_bool_table(const CONFIG_BOOL_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_bool(table->name, table->defval);
+ table++;
+ }
+}
+
+/* get_mail_conf_bool_fn_table - look up booleans, defaults are functions */
+
+void get_mail_conf_bool_fn_table(const CONFIG_BOOL_FN_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_bool_fn(table->name, table->defval);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_int.c b/src/global/mail_conf_int.c
new file mode 100644
index 0000000..9017183
--- /dev/null
+++ b/src/global/mail_conf_int.c
@@ -0,0 +1,223 @@
+/*++
+/* NAME
+/* mail_conf_int 3
+/* SUMMARY
+/* integer-valued configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_int(name, defval, min, max);
+/* const char *name;
+/* int defval;
+/* int min;
+/* int max;
+/*
+/* int get_mail_conf_int_fn(name, defval, min, max);
+/* const char *name;
+/* int (*defval)();
+/* int min;
+/* int max;
+/*
+/* void set_mail_conf_int(name, value)
+/* const char *name;
+/* int value;
+/*
+/* void get_mail_conf_int_table(table)
+/* const CONFIG_INT_TABLE *table;
+/*
+/* void get_mail_conf_int_fn_table(table)
+/* const CONFIG_INT_TABLE *table;
+/* AUXILIARY FUNCTIONS
+/* int get_mail_conf_int2(name1, name2, defval, min, max);
+/* const char *name1;
+/* const char *name2;
+/* int defval;
+/* int min;
+/* int max;
+/*
+/* void check_mail_conf_int(name, intval, min, max)
+/* const char *name;
+/* int intval;
+/* int min;
+/* int max;
+/* DESCRIPTION
+/* This module implements configuration parameter support
+/* for integer values.
+/*
+/* get_mail_conf_int() looks up the named entry in the global
+/* configuration dictionary. The default value is returned
+/* when no value was found.
+/* \fImin\fR is zero or specifies a lower limit on the integer
+/* value or string length; \fImax\fR is zero or specifies an
+/* upper limit on the integer value or string length.
+/*
+/* get_mail_conf_int_fn() is similar but specifies a function that
+/* provides the default value. The function is called only
+/* when the default value is needed.
+/*
+/* set_mail_conf_int() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_int_table() and get_mail_conf_int_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/*
+/* get_mail_conf_int2() concatenates the two names and is otherwise
+/* identical to get_mail_conf_int().
+/*
+/* check_mail_conf_int() exits with a fatal run-time error
+/* when the integer value does not meet its requirements.
+/* DIAGNOSTICS
+/* Fatal errors: malformed numerical value.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* 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 <stdlib.h>
+#include <stdio.h> /* BUFSIZ */
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* convert_mail_conf_int - look up and convert integer parameter value */
+
+static int convert_mail_conf_int(const char *name, int *intval)
+{
+ const char *strval;
+ char *end;
+ long longval;
+
+ if ((strval = mail_conf_lookup_eval(name)) != 0) {
+ errno = 0;
+ *intval = longval = strtol(strval, &end, 10);
+ if (*strval == 0 || *end != 0 || errno == ERANGE || longval != *intval)
+ msg_fatal("bad numerical configuration: %s = %s", name, strval);
+ return (1);
+ }
+ return (0);
+}
+
+/* check_mail_conf_int - validate integer value */
+
+void check_mail_conf_int(const char *name, int intval, int min, int max)
+{
+ if (min && intval < min)
+ msg_fatal("invalid %s parameter value %d < %d", name, intval, min);
+ if (max && intval > max)
+ msg_fatal("invalid %s parameter value %d > %d", name, intval, max);
+}
+
+/* get_mail_conf_int - evaluate integer-valued configuration variable */
+
+int get_mail_conf_int(const char *name, int defval, int min, int max)
+{
+ int intval;
+
+ if (convert_mail_conf_int(name, &intval) == 0)
+ set_mail_conf_int(name, intval = defval);
+ check_mail_conf_int(name, intval, min, max);
+ return (intval);
+}
+
+/* get_mail_conf_int2 - evaluate integer-valued configuration variable */
+
+int get_mail_conf_int2(const char *name1, const char *name2, int defval,
+ int min, int max)
+{
+ int intval;
+ char *name;
+
+ name = concatenate(name1, name2, (char *) 0);
+ if (convert_mail_conf_int(name, &intval) == 0)
+ set_mail_conf_int(name, intval = defval);
+ check_mail_conf_int(name, intval, min, max);
+ myfree(name);
+ return (intval);
+}
+
+/* get_mail_conf_int_fn - evaluate integer-valued configuration variable */
+
+typedef int (*stupid_indent_int) (void);
+
+int get_mail_conf_int_fn(const char *name, stupid_indent_int defval,
+ int min, int max)
+{
+ int intval;
+
+ if (convert_mail_conf_int(name, &intval) == 0)
+ set_mail_conf_int(name, intval = defval());
+ check_mail_conf_int(name, intval, min, max);
+ return (intval);
+}
+
+/* set_mail_conf_int - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_int(const char *name, int value)
+{
+ const char myname[] = "set_mail_conf_int";
+ char buf[BUFSIZ]; /* yeah! crappy code! */
+
+#ifndef NO_SNPRINTF
+ ssize_t ret;
+
+ ret = snprintf(buf, sizeof(buf), "%d", value);
+ if (ret < 0)
+ msg_panic("%s: output error for %%d", myname);
+ if (ret >= sizeof(buf))
+ msg_panic("%s: output for %%d exceeds space %ld",
+ myname, (long) sizeof(buf));
+#else
+ sprintf(buf, "%d", value); /* yeah! more crappy code! */
+#endif
+ mail_conf_update(name, buf);
+}
+
+/* get_mail_conf_int_table - look up table of integers */
+
+void get_mail_conf_int_table(const CONFIG_INT_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_int(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+/* get_mail_conf_int_fn_table - look up integers, defaults are functions */
+
+void get_mail_conf_int_fn_table(const CONFIG_INT_FN_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_int_fn(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_long.c b/src/global/mail_conf_long.c
new file mode 100644
index 0000000..c702000
--- /dev/null
+++ b/src/global/mail_conf_long.c
@@ -0,0 +1,213 @@
+/*++
+/* NAME
+/* mail_conf_long 3
+/* SUMMARY
+/* long integer-valued configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_long(name, defval, min, max);
+/* const char *name;
+/* long defval;
+/* long min;
+/* long max;
+/*
+/* int get_mail_conf_long_fn(name, defval, min, max);
+/* const char *name;
+/* long (*defval)(void);
+/* long min;
+/* long max;
+/*
+/* void set_mail_conf_long(name, value)
+/* const char *name;
+/* long value;
+/*
+/* void get_mail_conf_long_table(table)
+/* const CONFIG_LONG_TABLE *table;
+/*
+/* void get_mail_conf_long_fn_table(table)
+/* const CONFIG_LONG_TABLE *table;
+/* AUXILIARY FUNCTIONS
+/* int get_mail_conf_long2(name1, name2, defval, min, max);
+/* const char *name1;
+/* const char *name2;
+/* long defval;
+/* long min;
+/* long max;
+/* DESCRIPTION
+/* This module implements configuration parameter support
+/* for long integer values.
+/*
+/* get_mail_conf_long() looks up the named entry in the global
+/* configuration dictionary. The default value is returned
+/* when no value was found.
+/* \fImin\fR is zero or specifies a lower limit on the long
+/* integer value; \fImax\fR is zero or specifies an upper limit
+/* on the long integer value.
+/*
+/* get_mail_conf_long_fn() is similar but specifies a function that
+/* provides the default value. The function is called only
+/* when the default value is needed.
+/*
+/* set_mail_conf_long() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_long_table() and get_mail_conf_long_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/*
+/* get_mail_conf_long2() concatenates the two names and is otherwise
+/* identical to get_mail_conf_long().
+/* DIAGNOSTICS
+/* Fatal errors: malformed numerical value.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* 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 <stdlib.h>
+#include <stdio.h> /* BUFSIZ */
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* convert_mail_conf_long - look up and convert integer parameter value */
+
+static int convert_mail_conf_long(const char *name, long *longval)
+{
+ const char *strval;
+ char *end;
+
+ if ((strval = mail_conf_lookup_eval(name)) != 0) {
+ errno = 0;
+ *longval = strtol(strval, &end, 10);
+ if (*strval == 0 || *end != 0 || errno == ERANGE)
+ msg_fatal("bad numerical configuration: %s = %s", name, strval);
+ return (1);
+ }
+ return (0);
+}
+
+/* check_mail_conf_long - validate integer value */
+
+static void check_mail_conf_long(const char *name, long longval, long min, long max)
+{
+ if (min && longval < min)
+ msg_fatal("invalid %s parameter value %ld < %ld", name, longval, min);
+ if (max && longval > max)
+ msg_fatal("invalid %s parameter value %ld > %ld", name, longval, max);
+}
+
+/* get_mail_conf_long - evaluate integer-valued configuration variable */
+
+long get_mail_conf_long(const char *name, long defval, long min, long max)
+{
+ long longval;
+
+ if (convert_mail_conf_long(name, &longval) == 0)
+ set_mail_conf_long(name, longval = defval);
+ check_mail_conf_long(name, longval, min, max);
+ return (longval);
+}
+
+/* get_mail_conf_long2 - evaluate integer-valued configuration variable */
+
+long get_mail_conf_long2(const char *name1, const char *name2, long defval,
+ long min, long max)
+{
+ long longval;
+ char *name;
+
+ name = concatenate(name1, name2, (char *) 0);
+ if (convert_mail_conf_long(name, &longval) == 0)
+ set_mail_conf_long(name, longval = defval);
+ check_mail_conf_long(name, longval, min, max);
+ myfree(name);
+ return (longval);
+}
+
+/* get_mail_conf_long_fn - evaluate integer-valued configuration variable */
+
+typedef long (*stupid_indent_long) (void);
+
+long get_mail_conf_long_fn(const char *name, stupid_indent_long defval,
+ long min, long max)
+{
+ long longval;
+
+ if (convert_mail_conf_long(name, &longval) == 0)
+ set_mail_conf_long(name, longval = defval());
+ check_mail_conf_long(name, longval, min, max);
+ return (longval);
+}
+
+/* set_mail_conf_long - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_long(const char *name, long value)
+{
+ const char myname[] = "set_mail_conf_long";
+ char buf[BUFSIZ]; /* yeah! crappy code! */
+
+#ifndef NO_SNPRINTF
+ ssize_t ret;
+
+ ret = snprintf(buf, sizeof(buf), "%ld", value);
+ if (ret < 0)
+ msg_panic("%s: output error for %%ld", myname);
+ if (ret >= sizeof(buf))
+ msg_panic("%s: output for %%ld exceeds space %ld",
+ myname, (long) sizeof(buf));
+#else
+ sprintf(buf, "%ld", value); /* yeah! more crappy code! */
+#endif
+ mail_conf_update(name, buf);
+}
+
+/* get_mail_conf_long_table - look up table of integers */
+
+void get_mail_conf_long_table(const CONFIG_LONG_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_long(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+/* get_mail_conf_long_fn_table - look up integers, defaults are functions */
+
+void get_mail_conf_long_fn_table(const CONFIG_LONG_FN_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_long_fn(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_nbool.c b/src/global/mail_conf_nbool.c
new file mode 100644
index 0000000..3ffa6f4
--- /dev/null
+++ b/src/global/mail_conf_nbool.c
@@ -0,0 +1,158 @@
+/*++
+/* NAME
+/* mail_conf_nbool 3
+/* SUMMARY
+/* boolean-valued configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_nbool(name, defval)
+/* const char *name;
+/* const char *defval;
+/*
+/* int get_mail_conf_nbool_fn(name, defval)
+/* const char *name;
+/* const char *(*defval)();
+/*
+/* void set_mail_conf_nbool(name, value)
+/* const char *name;
+/* const char *value;
+/*
+/* void get_mail_conf_nbool_table(table)
+/* const CONFIG_NBOOL_TABLE *table;
+/*
+/* void get_mail_conf_nbool_fn_table(table)
+/* const CONFIG_NBOOL_TABLE *table;
+/* DESCRIPTION
+/* This module implements configuration parameter support for
+/* boolean values. The internal representation is zero (false)
+/* and non-zero (true). The external representation is "no"
+/* (false) and "yes" (true). The conversion from external
+/* representation is case insensitive. Unlike mail_conf_bool(3)
+/* the default is a string value which is subject to macro expansion.
+/*
+/* get_mail_conf_nbool() looks up the named entry in the global
+/* configuration dictionary. The specified default value is
+/* returned when no value was found.
+/*
+/* get_mail_conf_nbool_fn() is similar but specifies a function that
+/* provides the default value. The function is called only
+/* when the default value is needed.
+/*
+/* set_mail_conf_nbool() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_nbool_table() and get_mail_conf_int_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/* DIAGNOSTICS
+/* Fatal errors: malformed boolean value.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* mail_conf_int(3) integer-valued configuration parameters
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* convert_mail_conf_nbool - look up and convert boolean parameter value */
+
+static int convert_mail_conf_nbool(const char *name, int *intval)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup_eval(name)) == 0) {
+ return (0);
+ } else {
+ if (strcasecmp(strval, CONFIG_BOOL_YES) == 0) {
+ *intval = 1;
+ } else if (strcasecmp(strval, CONFIG_BOOL_NO) == 0) {
+ *intval = 0;
+ } else {
+ msg_fatal("bad boolean configuration: %s = %s", name, strval);
+ }
+ return (1);
+ }
+}
+
+/* get_mail_conf_nbool - evaluate boolean-valued configuration variable */
+
+int get_mail_conf_nbool(const char *name, const char *defval)
+{
+ int intval;
+
+ if (convert_mail_conf_nbool(name, &intval) == 0)
+ set_mail_conf_nbool(name, defval);
+ if (convert_mail_conf_nbool(name, &intval) == 0)
+ msg_panic("get_mail_conf_nbool: parameter not found: %s", name);
+ return (intval);
+}
+
+/* get_mail_conf_nbool_fn - evaluate boolean-valued configuration variable */
+
+typedef const char *(*stupid_indent_int) (void);
+
+int get_mail_conf_nbool_fn(const char *name, stupid_indent_int defval)
+{
+ int intval;
+
+ if (convert_mail_conf_nbool(name, &intval) == 0)
+ set_mail_conf_nbool(name, defval());
+ if (convert_mail_conf_nbool(name, &intval) == 0)
+ msg_panic("get_mail_conf_nbool_fn: parameter not found: %s", name);
+ return (intval);
+}
+
+/* set_mail_conf_nbool - update boolean-valued configuration dictionary entry */
+
+void set_mail_conf_nbool(const char *name, const char *value)
+{
+ mail_conf_update(name, value);
+}
+
+/* get_mail_conf_nbool_table - look up table of booleans */
+
+void get_mail_conf_nbool_table(const CONFIG_NBOOL_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_nbool(table->name, table->defval);
+ table++;
+ }
+}
+
+/* get_mail_conf_nbool_fn_table - look up booleans, defaults are functions */
+
+void get_mail_conf_nbool_fn_table(const CONFIG_NBOOL_FN_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_nbool_fn(table->name, table->defval);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_nint.c b/src/global/mail_conf_nint.c
new file mode 100644
index 0000000..e0bd7a1
--- /dev/null
+++ b/src/global/mail_conf_nint.c
@@ -0,0 +1,232 @@
+/*++
+/* NAME
+/* mail_conf_nint 3
+/* SUMMARY
+/* integer-valued configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_nint(name, defval, min, max);
+/* const char *name;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* int get_mail_conf_nint_fn(name, defval, min, max);
+/* const char *name;
+/* char *(*defval)();
+/* int min;
+/* int max;
+/*
+/* void set_mail_conf_nint(name, value)
+/* const char *name;
+/* const char *value;
+/*
+/* void set_mail_conf_nint_int(name, value)
+/* const char *name;
+/* int value;
+/*
+/* void get_mail_conf_nint_table(table)
+/* const CONFIG_NINT_TABLE *table;
+/*
+/* void get_mail_conf_nint_fn_table(table)
+/* const CONFIG_NINT_TABLE *table;
+/* AUXILIARY FUNCTIONS
+/* int get_mail_conf_nint2(name1, name2, defval, min, max);
+/* const char *name1;
+/* const char *name2;
+/* int defval;
+/* int min;
+/* int max;
+/* DESCRIPTION
+/* This module implements configuration parameter support
+/* for integer values. Unlike mail_conf_int, the default
+/* is a string, which can be subjected to macro expansion.
+/*
+/* get_mail_conf_nint() looks up the named entry in the global
+/* configuration dictionary. The default value is returned
+/* when no value was found.
+/* \fImin\fR is zero or specifies a lower limit on the integer
+/* value or string length; \fImax\fR is zero or specifies an
+/* upper limit on the integer value or string length.
+/*
+/* get_mail_conf_nint_fn() is similar but specifies a function that
+/* provides the default value. The function is called only
+/* when the default value is needed.
+/*
+/* set_mail_conf_nint() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_nint_table() and get_mail_conf_nint_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/*
+/* get_mail_conf_nint2() concatenates the two names and is otherwise
+/* identical to get_mail_conf_nint().
+/* DIAGNOSTICS
+/* Fatal errors: malformed numerical value.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* 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 <stdlib.h>
+#include <stdio.h> /* BUFSIZ */
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* convert_mail_conf_nint - look up and convert integer parameter value */
+
+static int convert_mail_conf_nint(const char *name, int *intval)
+{
+ const char *strval;
+ char *end;
+ long longval;
+
+ if ((strval = mail_conf_lookup_eval(name)) != 0) {
+ errno = 0;
+ *intval = longval = strtol(strval, &end, 10);
+ if (*strval == 0 || *end != 0 || errno == ERANGE || longval != *intval)
+ msg_fatal("bad numerical configuration: %s = %s", name, strval);
+ return (1);
+ }
+ return (0);
+}
+
+/* check_mail_conf_nint - validate integer value */
+
+static void check_mail_conf_nint(const char *name, int intval, int min, int max)
+{
+ if (min && intval < min)
+ msg_fatal("invalid %s parameter value %d < %d", name, intval, min);
+ if (max && intval > max)
+ msg_fatal("invalid %s parameter value %d > %d", name, intval, max);
+}
+
+/* get_mail_conf_nint - evaluate integer-valued configuration variable */
+
+int get_mail_conf_nint(const char *name, const char *defval, int min, int max)
+{
+ int intval;
+
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ set_mail_conf_nint(name, defval);
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ msg_panic("get_mail_conf_nint: parameter not found: %s", name);
+ check_mail_conf_nint(name, intval, min, max);
+ return (intval);
+}
+
+/* get_mail_conf_nint2 - evaluate integer-valued configuration variable */
+
+int get_mail_conf_nint2(const char *name1, const char *name2, int defval,
+ int min, int max)
+{
+ int intval;
+ char *name;
+
+ name = concatenate(name1, name2, (char *) 0);
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ set_mail_conf_nint_int(name, defval);
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ msg_panic("get_mail_conf_nint2: parameter not found: %s", name);
+ check_mail_conf_nint(name, intval, min, max);
+ myfree(name);
+ return (intval);
+}
+
+/* get_mail_conf_nint_fn - evaluate integer-valued configuration variable */
+
+typedef const char *(*stupid_indent_int) (void);
+
+int get_mail_conf_nint_fn(const char *name, stupid_indent_int defval,
+ int min, int max)
+{
+ int intval;
+
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ set_mail_conf_nint(name, defval());
+ if (convert_mail_conf_nint(name, &intval) == 0)
+ msg_panic("get_mail_conf_nint_fn: parameter not found: %s", name);
+ check_mail_conf_nint(name, intval, min, max);
+ return (intval);
+}
+
+/* set_mail_conf_nint - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_nint(const char *name, const char *value)
+{
+ mail_conf_update(name, value);
+}
+
+/* set_mail_conf_nint_int - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_nint_int(const char *name, int value)
+{
+ const char myname[] = "set_mail_conf_nint_int";
+ char buf[BUFSIZ]; /* yeah! crappy code! */
+
+#ifndef NO_SNPRINTF
+ ssize_t ret;
+
+ ret = snprintf(buf, sizeof(buf), "%d", value);
+ if (ret < 0)
+ msg_panic("%s: output error for %%d", myname);
+ if (ret >= sizeof(buf))
+ msg_panic("%s: output for %%d exceeds space %ld",
+ myname, (long) sizeof(buf));
+#else
+ sprintf(buf, "%d", value); /* yeah! more crappy code! */
+#endif
+ mail_conf_update(name, buf);
+}
+
+/* get_mail_conf_nint_table - look up table of integers */
+
+void get_mail_conf_nint_table(const CONFIG_NINT_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_nint(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+/* get_mail_conf_nint_fn_table - look up integers, defaults are functions */
+
+void get_mail_conf_nint_fn_table(const CONFIG_NINT_FN_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_nint_fn(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_raw.c b/src/global/mail_conf_raw.c
new file mode 100644
index 0000000..4c9c5bd
--- /dev/null
+++ b/src/global/mail_conf_raw.c
@@ -0,0 +1,145 @@
+/*++
+/* NAME
+/* mail_conf_raw 3
+/* SUMMARY
+/* raw string-valued global configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* char *get_mail_conf_raw(name, defval, min, max)
+/* const char *name;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* char *get_mail_conf_raw_fn(name, defval, min, max)
+/* const char *name;
+/* const char *(*defval)(void);
+/* int min;
+/* int max;
+/*
+/* void get_mail_conf_raw_table(table)
+/* const CONFIG_RAW_TABLE *table;
+/*
+/* void get_mail_conf_raw_fn_table(table)
+/* const CONFIG_RAW_TABLE *table;
+/* DESCRIPTION
+/* This module implements support for string-valued global
+/* configuration parameters that are loaded without $name expansion.
+/*
+/* get_mail_conf_raw() looks up the named entry in the global
+/* configuration dictionary. The default value is returned when
+/* no value was found. String results should be passed to myfree()
+/* when no longer needed. \fImin\fR is zero or specifies a lower
+/* bound on the string length; \fImax\fR is zero or specifies an
+/* upper limit on the string length.
+/*
+/* get_mail_conf_raw_fn() is similar but specifies a function that
+/* provides the default value. The function is called only when
+/* the default value is used.
+/*
+/* get_mail_conf_raw_table() and get_mail_conf_raw_fn_table() read
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/* DIAGNOSTICS
+/* Fatal errors: bad string length.
+/* SEE ALSO
+/* config(3) generic config parameter support
+/* 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 <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* check_mail_conf_raw - validate string length */
+
+static void check_mail_conf_raw(const char *name, const char *strval,
+ int min, int max)
+{
+ ssize_t len = strlen(strval);
+
+ if (min && len < min)
+ msg_fatal("bad string length (%ld < %d): %s = %s",
+ (long) len, min, name, strval);
+ if (max && len > max)
+ msg_fatal("bad string length (%ld > %d): %s = %s",
+ (long) len, max, name, strval);
+}
+
+/* get_mail_conf_raw - evaluate string-valued configuration variable */
+
+char *get_mail_conf_raw(const char *name, const char *defval,
+ int min, int max)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup(name)) == 0) {
+ strval = defval;
+ mail_conf_update(name, strval);
+ }
+ check_mail_conf_raw(name, strval, min, max);
+ return (mystrdup(strval));
+}
+
+/* get_mail_conf_raw_fn - evaluate string-valued configuration variable */
+
+typedef const char *(*stupid_indent_str) (void);
+
+char *get_mail_conf_raw_fn(const char *name, stupid_indent_str defval,
+ int min, int max)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup(name)) == 0) {
+ strval = defval();
+ mail_conf_update(name, strval);
+ }
+ check_mail_conf_raw(name, strval, min, max);
+ return (mystrdup(strval));
+}
+
+/* get_mail_conf_raw_table - look up table of strings */
+
+void get_mail_conf_raw_table(const CONFIG_RAW_TABLE *table)
+{
+ while (table->name) {
+ if (table->target[0])
+ myfree(table->target[0]);
+ table->target[0] = get_mail_conf_raw(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+/* get_mail_conf_raw_fn_table - look up strings, defaults are functions */
+
+void get_mail_conf_raw_fn_table(const CONFIG_RAW_FN_TABLE *table)
+{
+ while (table->name) {
+ if (table->target[0])
+ myfree(table->target[0]);
+ table->target[0] = get_mail_conf_raw_fn(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_str.c b/src/global/mail_conf_str.c
new file mode 100644
index 0000000..d8e0bd1
--- /dev/null
+++ b/src/global/mail_conf_str.c
@@ -0,0 +1,199 @@
+/*++
+/* NAME
+/* mail_conf_str 3
+/* SUMMARY
+/* string-valued global configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* char *get_mail_conf_str(name, defval, min, max)
+/* const char *name;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* char *get_mail_conf_str_fn(name, defval, min, max)
+/* const char *name;
+/* const char *(*defval)(void);
+/* int min;
+/* int max;
+/*
+/* void set_mail_conf_str(name, value)
+/* const char *name;
+/* const char *value;
+/*
+/* void get_mail_conf_str_table(table)
+/* const CONFIG_STR_TABLE *table;
+/*
+/* void get_mail_conf_str_fn_table(table)
+/* const CONFIG_STR_TABLE *table;
+/* AUXILIARY FUNCTIONS
+/* char *get_mail_conf_str2(name, suffix, defval, min, max)
+/* const char *name;
+/* const char *suffix;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* void check_mail_conf_str(name, strval, min, max)
+/* const char *name;
+/* const char *strval;
+/* int min;
+/* int max;
+/* DESCRIPTION
+/* This module implements support for string-valued global
+/* configuration parameters.
+/*
+/* get_mail_conf_str() looks up the named entry in the global
+/* configuration dictionary. The default value is returned when
+/* no value was found. String results should be passed to myfree()
+/* when no longer needed. \fImin\fR is zero or specifies a lower
+/* bound on the string length; \fImax\fR is zero or specifies an
+/* upper limit on the string length.
+/*
+/* get_mail_conf_str_fn() is similar but specifies a function that
+/* provides the default value. The function is called only when
+/* the default value is used.
+/*
+/* set_mail_conf_str() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_str_table() and get_mail_conf_str_fn_table() read
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/*
+/* get_mail_conf_str2() concatenates the two names and is otherwise
+/* identical to get_mail_conf_str().
+/*
+/* check_mail_conf_str() exits with a fatal run-time error
+/* when the string does not meet its length requirements.
+/* DIAGNOSTICS
+/* Fatal errors: bad string length.
+/* SEE ALSO
+/* config(3) generic config parameter support
+/* 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 <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+
+/* check_mail_conf_str - validate string length */
+
+void check_mail_conf_str(const char *name, const char *strval,
+ int min, int max)
+{
+ ssize_t len = strlen(strval);
+
+ if (min && len < min)
+ msg_fatal("bad string length %ld < %d: %s = %s",
+ (long) len, min, name, strval);
+ if (max && len > max)
+ msg_fatal("bad string length %ld > %d: %s = %s",
+ (long) len, max, name, strval);
+}
+
+/* get_mail_conf_str - evaluate string-valued configuration variable */
+
+char *get_mail_conf_str(const char *name, const char *defval,
+ int min, int max)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup_eval(name)) == 0) {
+ strval = mail_conf_eval(defval);
+ mail_conf_update(name, strval);
+ }
+ check_mail_conf_str(name, strval, min, max);
+ return (mystrdup(strval));
+}
+
+/* get_mail_conf_str2 - evaluate string-valued configuration variable */
+
+char *get_mail_conf_str2(const char *name1, const char *name2,
+ const char *defval,
+ int min, int max)
+{
+ const char *strval;
+ char *name;
+
+ name = concatenate(name1, name2, (char *) 0);
+ if ((strval = mail_conf_lookup_eval(name)) == 0) {
+ strval = mail_conf_eval(defval);
+ mail_conf_update(name, strval);
+ }
+ check_mail_conf_str(name, strval, min, max);
+ myfree(name);
+ return (mystrdup(strval));
+}
+
+/* get_mail_conf_str_fn - evaluate string-valued configuration variable */
+
+typedef const char *(*stupid_indent_str) (void);
+
+char *get_mail_conf_str_fn(const char *name, stupid_indent_str defval,
+ int min, int max)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup_eval(name)) == 0) {
+ strval = mail_conf_eval(defval());
+ mail_conf_update(name, strval);
+ }
+ check_mail_conf_str(name, strval, min, max);
+ return (mystrdup(strval));
+}
+
+/* set_mail_conf_str - update string-valued configuration dictionary entry */
+
+void set_mail_conf_str(const char *name, const char *value)
+{
+ mail_conf_update(name, value);
+}
+
+/* get_mail_conf_str_table - look up table of strings */
+
+void get_mail_conf_str_table(const CONFIG_STR_TABLE *table)
+{
+ while (table->name) {
+ if (table->target[0])
+ myfree(table->target[0]);
+ table->target[0] = get_mail_conf_str(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+/* get_mail_conf_str_fn_table - look up strings, defaults are functions */
+
+void get_mail_conf_str_fn_table(const CONFIG_STR_FN_TABLE *table)
+{
+ while (table->name) {
+ if (table->target[0])
+ myfree(table->target[0]);
+ table->target[0] = get_mail_conf_str_fn(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
diff --git a/src/global/mail_conf_time.c b/src/global/mail_conf_time.c
new file mode 100644
index 0000000..5237dad
--- /dev/null
+++ b/src/global/mail_conf_time.c
@@ -0,0 +1,258 @@
+/*++
+/* NAME
+/* mail_conf_time 3
+/* SUMMARY
+/* time interval configuration parameter support
+/* SYNOPSIS
+/* #include <mail_conf.h>
+/*
+/* int get_mail_conf_time(name, defval, min, max);
+/* const char *name;
+/* const char *defval;
+/* int min;
+/* int max;
+/*
+/* void set_mail_conf_time(name, value)
+/* const char *name;
+/* const char *value;
+/*
+/* void set_mail_conf_time_int(name, value)
+/* const char *name;
+/* int value;
+/*
+/* void get_mail_conf_time_table(table)
+/* const CONFIG_TIME_TABLE *table;
+/* AUXILIARY FUNCTIONS
+/* int get_mail_conf_time2(name1, name2, defval, def_unit, min, max);
+/* const char *name1;
+/* const char *name2;
+/* int defval;
+/* int def_unit;
+/* int min;
+/* int max;
+/*
+/* void check_mail_conf_time(name, intval, min, max)
+/* const char *name;
+/* int intval;
+/* int min;
+/* int max;
+/* DESCRIPTION
+/* This module implements configuration parameter support
+/* for time interval values. The conversion routines understand
+/* one-letter suffixes to specify an explicit time unit: s
+/* (seconds), m (minutes), h (hours), d (days) or w (weeks).
+/* Internally, time is represented in seconds.
+/*
+/* get_mail_conf_time() looks up the named entry in the global
+/* configuration dictionary. The default value is returned
+/* when no value was found. \fIdef_unit\fR supplies the default
+/* time unit for numbers specified without explicit unit.
+/* \fImin\fR is zero or specifies a lower limit on the integer
+/* value or string length; \fImax\fR is zero or specifies an
+/* upper limit on the integer value or string length.
+/*
+/* set_mail_conf_time() updates the named entry in the global
+/* configuration dictionary. This has no effect on values that
+/* have been looked up earlier via the get_mail_conf_XXX() routines.
+/*
+/* get_mail_conf_time_table() and get_mail_conf_time_fn_table() initialize
+/* lists of variables, as directed by their table arguments. A table
+/* must be terminated by a null entry.
+/*
+/* check_mail_conf_time() terminates the program with a fatal
+/* runtime error when the time does not meet its requirements.
+/* DIAGNOSTICS
+/* Fatal errors: malformed numerical value, unknown time unit.
+/* BUGS
+/* Values and defaults are given in any unit; upper and lower
+/* bounds are given in seconds.
+/* SEE ALSO
+/* config(3) general configuration
+/* mail_conf_str(3) string-valued configuration parameters
+/* 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 <stdlib.h>
+#include <stdio.h> /* BUFSIZ */
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "conv_time.h"
+#include "mail_conf.h"
+
+/* convert_mail_conf_time - look up and convert integer parameter value */
+
+static int convert_mail_conf_time(const char *name, int *intval, int def_unit)
+{
+ const char *strval;
+
+ if ((strval = mail_conf_lookup_eval(name)) == 0)
+ return (0);
+ if (conv_time(strval, intval, def_unit) == 0)
+ msg_fatal("parameter %s: bad time value or unit: %s", name, strval);
+ return (1);
+}
+
+/* check_mail_conf_time - validate integer value */
+
+void check_mail_conf_time(const char *name, int intval, int min, int max)
+{
+ if (min && intval < min)
+ msg_fatal("invalid %s: %d (min %d)", name, intval, min);
+ if (max && intval > max)
+ msg_fatal("invalid %s: %d (max %d)", name, intval, max);
+}
+
+/* get_def_time_unit - extract time unit from default value */
+
+static int get_def_time_unit(const char *name, const char *defval)
+{
+ const char *cp;
+
+ for (cp = mail_conf_eval(defval); /* void */ ; cp++) {
+ if (*cp == 0)
+ msg_panic("parameter %s: missing time unit in default value: %s",
+ name, defval);
+ if (ISALPHA(*cp)) {
+#if 0
+ if (cp[1] != 0)
+ msg_panic("parameter %s: bad time unit in default value: %s",
+ name, defval);
+#endif
+ return (*cp);
+ }
+ }
+}
+
+/* get_mail_conf_time - evaluate integer-valued configuration variable */
+
+int get_mail_conf_time(const char *name, const char *defval, int min, int max)
+{
+ int intval;
+ int def_unit;
+
+ def_unit = get_def_time_unit(name, defval);
+ if (convert_mail_conf_time(name, &intval, def_unit) == 0)
+ set_mail_conf_time(name, defval);
+ if (convert_mail_conf_time(name, &intval, def_unit) == 0)
+ msg_panic("get_mail_conf_time: parameter not found: %s", name);
+ check_mail_conf_time(name, intval, min, max);
+ return (intval);
+}
+
+/* get_mail_conf_time2 - evaluate integer-valued configuration variable */
+
+int get_mail_conf_time2(const char *name1, const char *name2,
+ int defval, int def_unit, int min, int max)
+{
+ int intval;
+ char *name;
+
+ name = concatenate(name1, name2, (char *) 0);
+ if (convert_mail_conf_time(name, &intval, def_unit) == 0)
+ set_mail_conf_time_int(name, defval);
+ if (convert_mail_conf_time(name, &intval, def_unit) == 0)
+ msg_panic("get_mail_conf_time2: parameter not found: %s", name);
+ check_mail_conf_time(name, intval, min, max);
+ myfree(name);
+ return (intval);
+}
+
+/* set_mail_conf_time - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_time(const char *name, const char *value)
+{
+ mail_conf_update(name, value);
+}
+
+/* set_mail_conf_time_int - update integer-valued configuration dictionary entry */
+
+void set_mail_conf_time_int(const char *name, int value)
+{
+ const char myname[] = "set_mail_conf_time_int";
+ char buf[BUFSIZ]; /* yeah! crappy code! */
+
+#ifndef NO_SNPRINTF
+ ssize_t ret;
+
+ ret = snprintf(buf, sizeof(buf), "%ds", value);
+ if (ret < 0)
+ msg_panic("%s: output error for %%ds", myname);
+ if (ret >= sizeof(buf))
+ msg_panic("%s: output for %%ds exceeds space %ld",
+ myname, (long) sizeof(buf));
+#else
+ sprintf(buf, "%ds", value); /* yeah! more crappy code! */
+#endif
+ mail_conf_update(name, buf);
+}
+
+/* get_mail_conf_time_table - look up table of integers */
+
+void get_mail_conf_time_table(const CONFIG_TIME_TABLE *table)
+{
+ while (table->name) {
+ table->target[0] = get_mail_conf_time(table->name, table->defval,
+ table->min, table->max);
+ table++;
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone driver program for regression testing.
+ */
+#include <vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ static int seconds;
+ static int minutes;
+ static int hours;
+ static int days;
+ static int weeks;
+ static const CONFIG_TIME_TABLE time_table[] = {
+ "seconds", "10s", &seconds, 0, 0,
+ "minutes", "10m", &minutes, 0, 0,
+ "hours", "10h", &hours, 0, 0,
+ "days", "10d", &days, 0, 0,
+ "weeks", "10w", &weeks, 0, 0,
+ 0,
+ };
+
+ get_mail_conf_time_table(time_table);
+ vstream_printf("10 seconds = %d\n", seconds);
+ vstream_printf("10 minutes = %d\n", minutes);
+ vstream_printf("10 hours = %d\n", hours);
+ vstream_printf("10 days = %d\n", days);
+ vstream_printf("10 weeks = %d\n", weeks);
+ vstream_fflush(VSTREAM_OUT);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_conf_time.ref b/src/global/mail_conf_time.ref
new file mode 100644
index 0000000..9494856
--- /dev/null
+++ b/src/global/mail_conf_time.ref
@@ -0,0 +1,5 @@
+10 seconds = 10
+10 minutes = 600
+10 hours = 36000
+10 days = 864000
+10 weeks = 6048000
diff --git a/src/global/mail_connect.c b/src/global/mail_connect.c
new file mode 100644
index 0000000..4fdfe78
--- /dev/null
+++ b/src/global/mail_connect.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* mail_connect 3
+/* SUMMARY
+/* intra-mail system connection management
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* VSTREAM *mail_connect(class, name, block_mode)
+/* const char *class;
+/* const char *name;
+/* int block_mode;
+/*
+/* VSTREAM *mail_connect_wait(class, name)
+/* const char *class;
+/* const char *name;
+/* DESCRIPTION
+/* This module does low-level connection management for intra-mail
+/* communication. All reads and writes are subject to a time limit
+/* (controlled by the global variable \fIvar_ipc_timeout\fR). This
+/* protects against deadlock conditions that should never happen.
+/*
+/* mail_connect() attempts to connect to the UNIX-domain socket of
+/* the named subsystem. The result is a null pointer in case of failure.
+/* By default this function provides no errno logging.
+/*
+/* mail_connect_wait() is like mail_connect(), but keeps trying until
+/* the connection succeeds. However, mail_connect_wait() terminates
+/* with a fatal error when the service is down. This is to ensure that
+/* processes terminate when the mail system shuts down.
+/*
+/* Arguments:
+/* .IP class
+/* Name of a class of local transport channel endpoints,
+/* either \fIpublic\fR (accessible by any local user) or
+/* \fIprivate\fR (administrative access only).
+/* .IP service
+/* The name of a local transport endpoint within the named class.
+/* .IP block_mode
+/* NON_BLOCKING for a non-blocking connection, or BLOCKING.
+/* SEE ALSO
+/* timed_ipc(3), enforce IPC timeouts.
+/* 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 <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "timed_ipc.h"
+#include "mail_proto.h"
+
+/* mail_connect - connect to mail subsystem */
+
+VSTREAM *mail_connect(const char *class, const char *name, int block_mode)
+{
+ char *path;
+ VSTREAM *stream;
+ int fd;
+ char *sock_name;
+
+ path = mail_pathname(class, name);
+ if ((fd = LOCAL_CONNECT(path, block_mode, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("connect to subsystem %s: %m", path);
+ stream = 0;
+ } else {
+ if (msg_verbose)
+ msg_info("connect to subsystem %s", path);
+ stream = vstream_fdopen(fd, O_RDWR);
+ timed_ipc_setup(stream);
+ sock_name = concatenate(path, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(sock_name),
+ CA_VSTREAM_CTL_END);
+ myfree(sock_name);
+ }
+ myfree(path);
+ return (stream);
+}
+
+/* mail_connect_wait - connect to mail service until it succeeds */
+
+VSTREAM *mail_connect_wait(const char *class, const char *name)
+{
+ VSTREAM *stream;
+ int count = 0;
+
+ /*
+ * XXX Solaris workaround for ECONNREFUSED on a busy socket.
+ */
+ while ((stream = mail_connect(class, name, BLOCKING)) == 0) {
+ if (count++ >= 10) {
+ msg_fatal("connect #%d to subsystem %s/%s: %m",
+ count, class, name);
+ } else {
+ msg_warn("connect #%d to subsystem %s/%s: %m",
+ count, class, name);
+ }
+ sleep(10); /* XXX make configurable */
+ }
+ return (stream);
+}
diff --git a/src/global/mail_copy.c b/src/global/mail_copy.c
new file mode 100644
index 0000000..7c60370
--- /dev/null
+++ b/src/global/mail_copy.c
@@ -0,0 +1,315 @@
+/*++
+/* NAME
+/* mail_copy 3
+/* SUMMARY
+/* copy message with extreme prejudice
+/* SYNOPSIS
+/* #include <mail_copy.h>
+/*
+/* int mail_copy(sender, orig_to, delivered, src, dst, flags, eol, why)
+/* const char *sender;
+/* const char *orig_to;
+/* const char *delivered;
+/* VSTREAM *src;
+/* VSTREAM *dst;
+/* int flags;
+/* const char *eol;
+/* DSN_BUF *why;
+/* DESCRIPTION
+/* mail_copy() copies a mail message from record stream to stream-lf
+/* stream, and attempts to detect all possible I/O errors.
+/*
+/* Arguments:
+/* .IP sender
+/* The sender envelope address.
+/* .IP delivered
+/* Null pointer or delivered-to: header address.
+/* .IP src
+/* The source record stream, positioned at the beginning of the
+/* message contents.
+/* .IP dst
+/* The destination byte stream (in stream-lf format). If the message
+/* ends in an incomplete line, a newline character is appended to
+/* the output.
+/* .IP flags
+/* The binary OR of zero or more of the following:
+/* .RS
+/* .IP MAIL_COPY_QUOTE
+/* Prepend a `>' character to lines beginning with `From '.
+/* .IP MAIL_COPY_DOT
+/* Prepend a `.' character to lines beginning with `.'.
+/* .IP MAIL_COPY_TOFILE
+/* On systems that support this, use fsync() to flush the
+/* data to stable storage, and truncate the destination
+/* file to its original length in case of problems.
+/* .IP MAIL_COPY_FROM
+/* Prepend a UNIX-style From_ line to the message.
+/* .IP MAIL_COPY_BLANK
+/* Append an empty line to the end of the message.
+/* .IP MAIL_COPY_DELIVERED
+/* Prepend a Delivered-To: header with the name of the
+/* \fIdelivered\fR attribute.
+/* The address is quoted according to RFC822 rules.
+/* .IP MAIL_COPY_ORIG_RCPT
+/* Prepend an X-Original-To: header with the original
+/* envelope recipient address. This is a NOOP with
+/* var_enable_orcpt === 0.
+/* .IP MAIL_COPY_RETURN_PATH
+/* Prepend a Return-Path: header with the value of the
+/* \fIsender\fR attribute.
+/* .RE
+/* The manifest constant MAIL_COPY_MBOX is a convenient shorthand for
+/* all MAIL_COPY_XXX options that are appropriate for mailbox delivery.
+/* Use MAIL_COPY_NONE to copy a message without any options enabled.
+/* .IP eol
+/* Record delimiter, for example, LF or CF LF.
+/* .IP why
+/* A null pointer, or storage for the reason of failure in
+/* the form of a DSN detail code plus free text.
+/* DIAGNOSTICS
+/* A non-zero result means the operation failed. Warnings: corrupt
+/* message file. A corrupt message is marked as corrupt.
+/*
+/* The result is the bit-wise OR of zero or more of the following:
+/* .IP MAIL_COPY_STAT_CORRUPT
+/* The queue file is marked as corrupt.
+/* .IP MAIL_COPY_STAT_READ
+/* A read error was detected; errno specifies the nature of the problem.
+/* .IP MAIL_COPY_STAT_WRITE
+/* A write error was detected; errno specifies the nature of the problem.
+/* SEE ALSO
+/* mark_corrupt(3), mark queue file as corrupted.
+/* 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/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <iostuff.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include "quote_822_local.h"
+#include "record.h"
+#include "rec_type.h"
+#include "mail_queue.h"
+#include "mail_addr.h"
+#include "mark_corrupt.h"
+#include "mail_params.h"
+#include "mail_copy.h"
+#include "mbox_open.h"
+#include "dsn_buf.h"
+#include "sys_exits.h"
+
+/* mail_copy - copy message with extreme prejudice */
+
+int mail_copy(const char *sender,
+ const char *orig_rcpt,
+ const char *delivered,
+ VSTREAM *src, VSTREAM *dst,
+ int flags, const char *eol, DSN_BUF *why)
+{
+ const char *myname = "mail_copy";
+ VSTRING *buf;
+ char *bp;
+ off_t orig_length;
+ int read_error;
+ int write_error;
+ int corrupt_error = 0;
+ time_t now;
+ int type;
+ int prev_type;
+ struct stat st;
+ off_t size_limit;
+
+ /*
+ * Workaround 20090114. This will hopefully get someone's attention. The
+ * problem with file_size_limit < message_size_limit is that mail will be
+ * delivered again and again until someone removes it from the queue by
+ * hand, because Postfix cannot mark a recipient record as "completed".
+ */
+ if (fstat(vstream_fileno(src), &st) < 0)
+ msg_fatal("fstat: %m");
+ if ((size_limit = get_file_limit()) < st.st_size)
+ msg_panic("file size limit %lu < message size %lu. This "
+ "causes large messages to be delivered repeatedly "
+ "after they were submitted with \"sendmail -t\" "
+ "or after recipients were added with the Milter "
+ "SMFIR_ADDRCPT request",
+ (unsigned long) size_limit,
+ (unsigned long) st.st_size);
+
+ /*
+ * Initialize.
+ */
+#ifndef NO_TRUNCATE
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(dst));
+#endif
+ buf = vstring_alloc(100);
+
+ /*
+ * Prepend a bunch of headers to the message.
+ */
+ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) {
+ if (sender == 0)
+ msg_panic("%s: null sender", myname);
+ quote_822_local(buf, sender);
+ if (flags & MAIL_COPY_FROM) {
+ time(&now);
+ vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ?
+ MAIL_ADDR_MAIL_DAEMON : vstring_str(buf),
+ asctime(localtime(&now)), eol);
+ }
+ if (flags & MAIL_COPY_RETURN_PATH) {
+ vstream_fprintf(dst, "Return-Path: <%s>%s",
+ *sender ? vstring_str(buf) : "", eol);
+ }
+ }
+ if (flags & MAIL_COPY_ORIG_RCPT) {
+ if (orig_rcpt == 0)
+ msg_panic("%s: null orig_rcpt", myname);
+
+ /*
+ * An empty original recipient record almost certainly means that
+ * original recipient processing was disabled.
+ */
+ if (var_enable_orcpt && *orig_rcpt) {
+ quote_822_local(buf, orig_rcpt);
+ vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol);
+ }
+ }
+ if (flags & MAIL_COPY_DELIVERED) {
+ if (delivered == 0)
+ msg_panic("%s: null delivered", myname);
+ quote_822_local(buf, delivered);
+ vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol);
+ }
+
+ /*
+ * Copy the message. Escape lines that could be confused with the ugly
+ * From_ line. Make sure that there is a blank line at the end of the
+ * message so that the next ugly From_ can be found by mail reading
+ * software.
+ *
+ * XXX Rely on the front-end services to enforce record size limits.
+ */
+#define VSTREAM_FWRITE_BUF(s,b) \
+ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b))
+
+ prev_type = REC_TYPE_NORM;
+ while ((type = rec_get(src, buf, 0)) > 0) {
+ if (type != REC_TYPE_NORM && type != REC_TYPE_CONT)
+ break;
+ bp = vstring_str(buf);
+ if (prev_type == REC_TYPE_NORM) {
+ if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5))
+ VSTREAM_PUTC('>', dst);
+ if ((flags & MAIL_COPY_DOT) && *bp == '.')
+ VSTREAM_PUTC('.', dst);
+ }
+ if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf))
+ break;
+ if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF)
+ break;
+ prev_type = type;
+ }
+ if (vstream_ferror(dst) == 0) {
+ if (var_fault_inj_code == 1)
+ type = 0;
+ if (type != REC_TYPE_XTRA) {
+ /* XXX Where is the queue ID? */
+ msg_warn("bad record type: %d in message content", type);
+ corrupt_error = mark_corrupt(src);
+ }
+ if (prev_type != REC_TYPE_NORM)
+ vstream_fputs(eol, dst);
+ if (flags & MAIL_COPY_BLANK)
+ vstream_fputs(eol, dst);
+ }
+ vstring_free(buf);
+
+ /*
+ * Make sure we read and wrote all. Truncate the file to its original
+ * length when the delivery failed. POSIX does not require ftruncate(),
+ * so we may have a portability problem. Note that fclose() may fail even
+ * while fflush and fsync() succeed. Think of remote file systems such as
+ * AFS that copy the file back to the server upon close. Oh well, no
+ * point optimizing the error case. XXX On systems that use flock()
+ * locking, we must truncate the file before closing it (and losing
+ * the exclusive lock).
+ */
+ read_error = vstream_ferror(src);
+ write_error = vstream_fflush(dst);
+#ifdef HAS_FSYNC
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ write_error |= fsync(vstream_fileno(dst));
+#endif
+ if (var_fault_inj_code == 2) {
+ read_error = 1;
+ errno = ENOENT;
+ }
+ if (var_fault_inj_code == 3) {
+ write_error = 1;
+ errno = ENOENT;
+ }
+#ifndef NO_TRUNCATE
+ if ((flags & MAIL_COPY_TOFILE) != 0)
+ if (corrupt_error || read_error || write_error)
+ /* Complain about ignored "undo" errors? So sue me. */
+ (void) ftruncate(vstream_fileno(dst), orig_length);
+#endif
+ write_error |= vstream_fclose(dst);
+
+ /*
+ * Return the optional verbose error description.
+ */
+#define TRY_AGAIN_ERROR(errno) \
+ (errno == EAGAIN || errno == ESTALE)
+
+ if (why && read_error)
+ dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0",
+ sys_exits_detail(EX_IOERR)->text,
+ "error reading message: %m");
+ if (why && write_error)
+ dsb_unix(why, mbox_dsn(errno, "5.3.0"),
+ sys_exits_detail(EX_IOERR)->text,
+ "error writing message: %m");
+
+ /*
+ * Use flag+errno description when the optional verbose description is
+ * not desired.
+ */
+ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0)
+ | (read_error ? MAIL_COPY_STAT_READ : 0)
+ | (write_error ? MAIL_COPY_STAT_WRITE : 0));
+}
diff --git a/src/global/mail_copy.h b/src/global/mail_copy.h
new file mode 100644
index 0000000..4f2d773
--- /dev/null
+++ b/src/global/mail_copy.h
@@ -0,0 +1,63 @@
+#ifndef _MAIL_COPY_H_INCLUDED_
+#define _MAIL_COPY_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_copy 3h
+/* SUMMARY
+/* copy message with extreme prejudice
+/* SYNOPSIS
+/* #include <mail_copy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn_buf.h>
+
+ /*
+ * External interface.
+ */
+extern int mail_copy(const char *, const char *, const char *,
+ VSTREAM *, VSTREAM *,
+ int, const char *, DSN_BUF *);
+
+#define MAIL_COPY_QUOTE (1<<0) /* prepend > to From_ */
+#define MAIL_COPY_TOFILE (1<<1) /* fsync, ftruncate() */
+#define MAIL_COPY_FROM (1<<2) /* prepend From_ */
+#define MAIL_COPY_DELIVERED (1<<3) /* prepend Delivered-To: */
+#define MAIL_COPY_RETURN_PATH (1<<4) /* prepend Return-Path: */
+#define MAIL_COPY_DOT (1<<5) /* escape dots - needed for bsmtp */
+#define MAIL_COPY_BLANK (1<<6) /* append blank line */
+#define MAIL_COPY_ORIG_RCPT (1<<7) /* prepend X-Original-To: */
+#define MAIL_COPY_MBOX (MAIL_COPY_FROM | MAIL_COPY_QUOTE | \
+ MAIL_COPY_TOFILE | MAIL_COPY_DELIVERED | \
+ MAIL_COPY_RETURN_PATH | MAIL_COPY_BLANK | \
+ MAIL_COPY_ORIG_RCPT)
+
+#define MAIL_COPY_NONE 0 /* all turned off */
+
+#define MAIL_COPY_STAT_OK 0
+#define MAIL_COPY_STAT_CORRUPT (1<<0)
+#define MAIL_COPY_STAT_READ (1<<1)
+#define MAIL_COPY_STAT_WRITE (1<<2)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_date.c b/src/global/mail_date.c
new file mode 100644
index 0000000..55d8907
--- /dev/null
+++ b/src/global/mail_date.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* mail_date 3
+/* SUMMARY
+/* return formatted time
+/* SYNOPSIS
+/* #include <mail_date.h>
+/*
+/* const char *mail_date(when)
+/* time_t when;
+/* DESCRIPTION
+/* mail_date() converts the time specified in \fIwhen\fR to the
+/* form: "Mon, 9 Dec 1996 05:38:26 -0500 (EST)" and returns
+/* a pointer to the result. The result is overwritten upon
+/* each call.
+/* DIAGNOSTICS
+/* Panic: the offset from UTC is more than a whole day. Fatal
+/* error: out of memory.
+/* 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 <time.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include "mail_date.h"
+
+ /*
+ * Application-specific.
+ */
+#define DAY_MIN (24 * HOUR_MIN) /* minutes in a day */
+#define HOUR_MIN 60 /* minutes in an hour */
+#define MIN_SEC 60 /* seconds in a minute */
+
+/* mail_date - return formatted time */
+
+const char *mail_date(time_t when)
+{
+ static VSTRING *vp;
+ struct tm *lt;
+ struct tm gmt;
+ int gmtoff;
+
+ /*
+ * As if strftime() isn't expensive enough, we're dynamically adjusting
+ * the size for the result, so we won't be surprised by long names etc.
+ */
+ if (vp == 0)
+ vp = vstring_alloc(100);
+ else
+ VSTRING_RESET(vp);
+
+ /*
+ * POSIX does not require that struct tm has a tm_gmtoff field, so we
+ * must compute the time offset from UTC by hand.
+ *
+ * Starting with the difference in hours/minutes between 24-hour clocks,
+ * adjust for differences in years, in yeardays, and in (leap) seconds.
+ *
+ * Assume 0..23 hours in a day, 0..59 minutes in an hour. The library spec
+ * has changed: we can no longer assume that there are 0..59 seconds in a
+ * minute.
+ */
+ gmt = *gmtime(&when);
+ lt = localtime(&when);
+ gmtoff = (lt->tm_hour - gmt.tm_hour) * HOUR_MIN + lt->tm_min - gmt.tm_min;
+ if (lt->tm_year < gmt.tm_year)
+ gmtoff -= DAY_MIN;
+ else if (lt->tm_year > gmt.tm_year)
+ gmtoff += DAY_MIN;
+ else if (lt->tm_yday < gmt.tm_yday)
+ gmtoff -= DAY_MIN;
+ else if (lt->tm_yday > gmt.tm_yday)
+ gmtoff += DAY_MIN;
+ if (lt->tm_sec <= gmt.tm_sec - MIN_SEC)
+ gmtoff -= 1;
+ else if (lt->tm_sec >= gmt.tm_sec + MIN_SEC)
+ gmtoff += 1;
+
+ /*
+ * First, format the date and wall-clock time. XXX The %e format (day of
+ * month, leading zero replaced by blank) isn't in my POSIX book, but
+ * many vendors seem to support it.
+ */
+#ifdef MISSING_STRFTIME_E
+#define STRFTIME_FMT "%a, %d %b %Y %H:%M:%S "
+#else
+#define STRFTIME_FMT "%a, %e %b %Y %H:%M:%S "
+#endif
+
+ while (strftime(vstring_end(vp), vstring_avail(vp), STRFTIME_FMT, lt) == 0)
+ VSTRING_SPACE(vp, 100);
+ VSTRING_SKIP(vp);
+
+ /*
+ * Then, add the UTC offset.
+ */
+ if (gmtoff < -DAY_MIN || gmtoff > DAY_MIN)
+ msg_panic("UTC time offset %d is larger than one day", gmtoff);
+ vstring_sprintf_append(vp, "%+03d%02d", (int) (gmtoff / HOUR_MIN),
+ (int) (abs(gmtoff) % HOUR_MIN));
+
+ /*
+ * Finally, add the time zone name.
+ */
+ while (strftime(vstring_end(vp), vstring_avail(vp), " (%Z)", lt) == 0)
+ VSTRING_SPACE(vp, vstring_avail(vp) + 100);
+ VSTRING_SKIP(vp);
+
+ return (vstring_str(vp));
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+
+int main(void)
+{
+ vstream_printf("%s\n", mail_date(time((time_t *) 0)));
+ vstream_fflush(VSTREAM_OUT);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_date.h b/src/global/mail_date.h
new file mode 100644
index 0000000..101436c
--- /dev/null
+++ b/src/global/mail_date.h
@@ -0,0 +1,35 @@
+#ifndef _MAIL_DATE_H_INCLUDED_
+#define _MAIL_DATE_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_date 3h
+/* SUMMARY
+/* return formatted time
+/* SYNOPSIS
+/* #include <mail_date.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * External interface
+ */
+extern const char *mail_date(time_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_dict.c b/src/global/mail_dict.c
new file mode 100644
index 0000000..6d6d729
--- /dev/null
+++ b/src/global/mail_dict.c
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* mail_dict 3
+/* SUMMARY
+/* register application-specific dictionaries
+/* SYNOPSIS
+/* #include <mail_dict.h>
+/*
+/* void mail_dict_init()
+/* DESCRIPTION
+/* This module registers dictionary types that depend on higher-level
+/* Postfix-specific interfaces and protocols.
+/*
+/* This also initializes the support for run-time loading of
+/* lookup tables, if applicable.
+/*
+/* The latter requires basic parameter initialization
+/* by either mail_conf_read() or mail_params_init().
+/* 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 <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dynamicmaps.h>
+
+/* Global library. */
+
+#include <dict_proxy.h>
+#include <dict_ldap.h>
+#include <dict_mysql.h>
+#include <dict_pgsql.h>
+#include <dict_sqlite.h>
+#include <dict_memcache.h>
+#include <mail_dict.h>
+#include <mail_params.h>
+#include <mail_dict.h>
+
+typedef struct {
+ char *type;
+ struct DICT *(*open) (const char *, int, int);
+} DICT_OPEN_INFO;
+
+static const DICT_OPEN_INFO dict_open_info[] = {
+ DICT_TYPE_PROXY, dict_proxy_open,
+#ifndef USE_DYNAMIC_MAPS
+#ifdef HAS_LDAP
+ DICT_TYPE_LDAP, dict_ldap_open,
+#endif
+#ifdef HAS_MYSQL
+ DICT_TYPE_MYSQL, dict_mysql_open,
+#endif
+#ifdef HAS_PGSQL
+ DICT_TYPE_PGSQL, dict_pgsql_open,
+#endif
+#ifdef HAS_SQLITE
+ DICT_TYPE_SQLITE, dict_sqlite_open,
+#endif
+#endif /* !USE_DYNAMIC_MAPS */
+ DICT_TYPE_MEMCACHE, dict_memcache_open,
+ 0,
+};
+
+/* mail_dict_init - dictionaries that depend on Postfix-specific interfaces */
+
+void mail_dict_init(void)
+{
+ const DICT_OPEN_INFO *dp;
+
+#ifdef USE_DYNAMIC_MAPS
+ char *path;
+
+ path = concatenate(var_meta_dir, "/", "dynamicmaps.cf",
+#ifdef SHLIB_VERSION
+ ".", SHLIB_VERSION,
+#endif
+ (char *) 0);
+ dymap_init(path, var_shlib_dir);
+ myfree(path);
+#endif
+
+ for (dp = dict_open_info; dp->type; dp++)
+ dict_open_register(dp->type, dp->open);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program.
+ */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+
+int main(int argc, char **argv)
+{
+ var_queue_dir = DEF_QUEUE_DIR;
+ var_proxymap_service = DEF_PROXYMAP_SERVICE;
+ var_proxywrite_service = DEF_PROXYWRITE_SERVICE;
+ var_ipc_timeout = 3600;
+ mail_dict_init();
+ dict_test(argc, argv);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_dict.h b/src/global/mail_dict.h
new file mode 100644
index 0000000..62ad881
--- /dev/null
+++ b/src/global/mail_dict.h
@@ -0,0 +1,25 @@
+/*++
+/* NAME
+/* mail_dict 3h
+/* SUMMARY
+/* register application-specific dictionaries
+/* SYNOPSIS
+/* #include <mail_dict.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void mail_dict_init(void);
+
+/* 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
+/*--*/
diff --git a/src/global/mail_error.c b/src/global/mail_error.c
new file mode 100644
index 0000000..e042f4e
--- /dev/null
+++ b/src/global/mail_error.c
@@ -0,0 +1,80 @@
+/*++
+/* NAME
+/* mail_error 3
+/* SUMMARY
+/* mail error classes
+/* SYNOPSIS
+/* #include <mail_error.h>
+/*
+/* NAME_MASK mail_error_masks[];
+/* DESCRIPTION
+/* This module implements error class support.
+/*
+/* mail_error_masks[] is a null-terminated table with mail error
+/* class names and their corresponding bit masks.
+/*
+/* The following is a list of implemented names, with the
+/* corresponding bit masks indicated in parentheses:
+/* .IP "bounce (MAIL_ERROR_BOUNCE)"
+/* A message could not be delivered because it was too large,
+/* because was sent via too many hops, because the recipient
+/* does not exist, and so on.
+/* .IP "2bounce (MAIL_ERROR_2BOUNCE)"
+/* A bounce message could not be delivered.
+/* .IP "data (MAIL_ERROR_DATA)"
+/* A message could not be delivered because a critical data
+/* file was unavailable.
+/* .IP "policy (MAIL_ERROR_POLICY)"
+/* Policy violation. This depends on what restrictions have
+/* been configured locally.
+/* .IP "protocol (MAIL_ERROR_PROTOCOL)"
+/* Protocol violation. Something did not follow the appropriate
+/* standard, or something requested an unimplemented service.
+/* .IP "resource (MAIL_ERROR_RESOURCE)"
+/* A message could not be delivered due to lack of system
+/* resources, for example, lack of file system space.
+/* .IP "software (MAIL_ERROR_SOFTWARE)"
+/* Software bug. The author of this program made a mistake.
+/* Fixing this requires change to the software.
+/* SEE ALSO
+/* name_mask(3), name to mask conversion
+/* 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. */
+
+/* Global library. */
+
+#include "mail_error.h"
+
+ /*
+ * The table that maps names to error bit masks. This will work on most UNIX
+ * compilation environments.
+ *
+ * In a some environments the table will not be linked in unless this module
+ * also contains a function that is being called explicitly. REF/DEF and all
+ * that.
+ */
+const NAME_MASK mail_error_masks[] = {
+ "bounce", MAIL_ERROR_BOUNCE,
+ "2bounce", MAIL_ERROR_2BOUNCE,
+ "data", MAIL_ERROR_DATA,
+ "delay", MAIL_ERROR_DELAY,
+ "policy", MAIL_ERROR_POLICY,
+ "protocol", MAIL_ERROR_PROTOCOL,
+ "resource", MAIL_ERROR_RESOURCE,
+ "software", MAIL_ERROR_SOFTWARE,
+ 0, 0,
+};
diff --git a/src/global/mail_error.h b/src/global/mail_error.h
new file mode 100644
index 0000000..f90c0ef
--- /dev/null
+++ b/src/global/mail_error.h
@@ -0,0 +1,44 @@
+#ifndef _MAIL_ERROR_H_INCLUDED_
+#define _MAIL_ERROR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_error 3h
+/* SUMMARY
+/* mail error classes
+/* SYNOPSIS
+/* #include <mail_error.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <name_mask.h>
+
+ /*
+ * External interface.
+ */
+#define MAIL_ERROR_POLICY (1<<0)
+#define MAIL_ERROR_PROTOCOL (1<<1)
+#define MAIL_ERROR_BOUNCE (1<<2)
+#define MAIL_ERROR_SOFTWARE (1<<3)
+#define MAIL_ERROR_RESOURCE (1<<4)
+#define MAIL_ERROR_2BOUNCE (1<<5)
+#define MAIL_ERROR_DELAY (1<<6)
+#define MAIL_ERROR_DATA (1<<7)
+
+extern const NAME_MASK mail_error_masks[];
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_flush.c b/src/global/mail_flush.c
new file mode 100644
index 0000000..6faa14d
--- /dev/null
+++ b/src/global/mail_flush.c
@@ -0,0 +1,80 @@
+/*++
+/* NAME
+/* mail_flush 3
+/* SUMMARY
+/* flush backed up mail
+/* SYNOPSIS
+/* #include <mail_flush.h>
+/*
+/* int mail_flush_deferred()
+/*
+/* int mail_flush_maildrop()
+/* DESCRIPTION
+/* This module triggers delivery of backed up mail.
+/*
+/* mail_flush_deferred() triggers delivery of all deferred
+/* or incoming mail. This function tickles the queue manager.
+/*
+/* mail_flush_maildrop() triggers delivery of all mail in
+/* the maildrop directory. This function tickles the pickup
+/* service.
+/* DIAGNOSTICS
+/* The result is 0 in case of success, -1 in case of failure.
+/* FILES
+/* $queue_directory/public/pickup, server endpoint
+/* $queue_directory/public/qmgr, server endpoint
+/* SEE ALSO
+/* mail_trigger(3), see note about event_drain() usage
+/* 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. */
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <mail_flush.h>
+
+/* mail_flush_deferred - flush deferred/incoming queue */
+
+int mail_flush_deferred(void)
+{
+ static char qmgr_trigger[] = {
+ QMGR_REQ_FLUSH_DEAD, /* all hosts, all transports */
+ QMGR_REQ_SCAN_ALL, /* all time stamps */
+ QMGR_REQ_SCAN_DEFERRED, /* scan deferred queue */
+ QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */
+ };
+
+ /*
+ * Trigger the flush queue service.
+ */
+ return (mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
+ qmgr_trigger, sizeof(qmgr_trigger)));
+}
+
+/* mail_flush_maildrop - flush maildrop queue */
+
+int mail_flush_maildrop(void)
+{
+ static char wakeup[] = {TRIGGER_REQ_WAKEUP};
+
+ /*
+ * Trigger the pickup service.
+ */
+ return (mail_trigger(MAIL_CLASS_PUBLIC, var_pickup_service,
+ wakeup, sizeof(wakeup)));
+}
diff --git a/src/global/mail_flush.h b/src/global/mail_flush.h
new file mode 100644
index 0000000..03f3945
--- /dev/null
+++ b/src/global/mail_flush.h
@@ -0,0 +1,30 @@
+#ifndef _MAIL_FLUSH_H_INCLUDED_
+#define _MAIL_FLUSH_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_flush 3h
+/* SUMMARY
+/* flush backed up mail
+/* SYNOPSIS
+/* #include <mail_flush.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int mail_flush_deferred(void);
+extern int mail_flush_maildrop(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_open_ok.c b/src/global/mail_open_ok.c
new file mode 100644
index 0000000..a1bacad
--- /dev/null
+++ b/src/global/mail_open_ok.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* mail_open_ok 3
+/* SUMMARY
+/* scrutinize mail queue file
+/* SYNOPSIS
+/* #include <mail_open_ok.h>
+/*
+/* int mail_open_ok(queue_name, queue_id, statp, pathp)
+/* const char *queue_name;
+/* const char *queue_id;
+/* struct stat *statp;
+/* char **pathp
+/* DESCRIPTION
+/* mail_open_ok() determines if it is OK to open the specified
+/* queue file.
+/*
+/* The queue name and queue id should conform to the syntax
+/* requirements for these names.
+/* Unfortunately, on some systems readdir() etc. can return bogus
+/* file names. For this reason, the code silently ignores invalid
+/* queue file names.
+/*
+/* The file should have mode 0700. Files with other permissions
+/* are silently ignored.
+/*
+/* The file should be a regular file.
+/* Files that do not satisfy this requirement are skipped with
+/* a warning.
+/*
+/* The file link count is not restricted. With a writable maildrop
+/* directory, refusal to deliver linked files is prone to denial of
+/* service attack; it's better to deliver mail too often than not.
+/*
+/* Upon return, the \fIstatp\fR argument receives the file
+/* attributes and \fIpathp\fR a copy of the file name. The file
+/* name is volatile. Make a copy if it is to be used for any
+/* appreciable amount of time.
+/* DIAGNOSTICS
+/* Warnings: bad file attributes (file type), multiple hard links.
+/* mail_open_ok() returns MAIL_OPEN_YES for good files, MAIL_OPEN_NO
+/* for anything else. It is left up to the system administrator to
+/* deal with non-file objects.
+/* BUGS
+/* mail_open_ok() examines a queue file without actually opening
+/* it, and therefore is susceptible to race conditions.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include "mail_queue.h"
+#include "mail_open_ok.h"
+
+/* mail_open_ok - see if this file is OK to open */
+
+int mail_open_ok(const char *queue_name, const char *queue_id,
+ struct stat * statp, const char **path)
+{
+ if (mail_queue_name_ok(queue_name) == 0) {
+ msg_warn("bad mail queue name: %s", queue_name);
+ return (MAIL_OPEN_NO);
+ }
+ if (mail_queue_id_ok(queue_id) == 0)
+ return (MAIL_OPEN_NO);
+
+
+ /*
+ * I really would like to look up the file attributes *after* opening the
+ * file so that we could save one directory traversal on systems without
+ * name-to-inode cache. However, we don't necessarily always want to open
+ * the file.
+ */
+ *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
+
+ if (lstat(*path, statp) < 0) {
+ if (errno != ENOENT)
+ msg_warn("%s: %m", *path);
+ return (MAIL_OPEN_NO);
+ }
+ if (!S_ISREG(statp->st_mode)) {
+ msg_warn("%s: uid %ld: not a regular file", *path, (long) statp->st_uid);
+ return (MAIL_OPEN_NO);
+ }
+ if ((statp->st_mode & S_IRWXU) != MAIL_QUEUE_STAT_READY)
+ return (MAIL_OPEN_NO);
+
+ /*
+ * Workaround for spurious "file has 2 links" warnings in showq. As
+ * kernels have evolved from non-interruptible system calls towards
+ * fine-grained locks, the showq command has become likely to observe a
+ * file while the queue manager is in the middle of renaming it, at a
+ * time when the file has links to both the old and new name. We now log
+ * the warning only when the condition appears to be persistent.
+ */
+#define MINUTE_SECONDS 60 /* XXX should be centralized */
+
+ if (statp->st_nlink > 1) {
+ if (msg_verbose)
+ msg_info("%s: uid %ld: file has %d links", *path,
+ (long) statp->st_uid, (int) statp->st_nlink);
+ else if (statp->st_ctime < time((time_t *) 0) - MINUTE_SECONDS)
+ msg_warn("%s: uid %ld: file has %d links", *path,
+ (long) statp->st_uid, (int) statp->st_nlink);
+ }
+ return (MAIL_OPEN_YES);
+}
diff --git a/src/global/mail_open_ok.h b/src/global/mail_open_ok.h
new file mode 100644
index 0000000..a56521a
--- /dev/null
+++ b/src/global/mail_open_ok.h
@@ -0,0 +1,33 @@
+#ifndef _MAIL_OPEN_OK_H_INCLUDED_
+#define _MAIL_OPEN_OK_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_open_ok 3h
+/* SUMMARY
+/* scrutinize mail queue file
+/* SYNOPSIS
+/* #include <mail_open_ok.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int mail_open_ok(const char *, const char *, struct stat *,
+ const char **);
+
+#define MAIL_OPEN_YES 1
+#define MAIL_OPEN_NO 2
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_params.c b/src/global/mail_params.c
new file mode 100644
index 0000000..81aee73
--- /dev/null
+++ b/src/global/mail_params.c
@@ -0,0 +1,1038 @@
+/*++
+/* NAME
+/* mail_params 3
+/* SUMMARY
+/* global mail configuration parameters
+/* SYNOPSIS
+/* #include <mail_params.h>
+/*
+/* char *var_myhostname;
+/* char *var_mydomain;
+/* char *var_myorigin;
+/* char *var_mydest;
+/* char *var_relayhost;
+/* char *var_transit_origin;
+/* char *var_transit_dest;
+/* char *var_mail_name;
+/* int var_helpful_warnings;
+/* char *var_syslog_name;
+/* char *var_mail_owner;
+/* uid_t var_owner_uid;
+/* gid_t var_owner_gid;
+/* char *var_sgid_group;
+/* gid_t var_sgid_gid;
+/* char *var_default_privs;
+/* uid_t var_default_uid;
+/* gid_t var_default_gid;
+/* char *var_config_dir;
+/* char *var_daemon_dir;
+/* char *var_data_dir;
+/* char *var_command_dir;
+/* char *var_meta_dir;
+/* char *var_queue_dir;
+/* char *var_shlib_dir;
+/* int var_use_limit;
+/* int var_idle_limit;
+/* int var_event_drain;
+/* int var_bundle_rcpt;
+/* char *var_procname;
+/* char *var_servname;
+/* int var_pid;
+/* int var_ipc_timeout;
+/* char *var_pid_dir;
+/* int var_dont_remove;
+/* char *var_inet_interfaces;
+/* char *var_proxy_interfaces;
+/* char *var_inet_protocols;
+/* char *var_mynetworks;
+/* char *var_double_bounce_sender;
+/* int var_line_limit;
+/* char *var_alias_db_map;
+/* long var_message_limit;
+/* char *var_mail_release;
+/* char *var_mail_version;
+/* int var_ipc_idle_limit;
+/* int var_ipc_ttl_limit;
+/* char *var_db_type;
+/* char *var_hash_queue_names;
+/* int var_hash_queue_depth;
+/* int var_trigger_timeout;
+/* char *var_rcpt_delim;
+/* int var_fork_tries;
+/* int var_fork_delay;
+/* int var_flock_tries;
+/* int var_flock_delay;
+/* int var_flock_stale;
+/* int var_disable_dns;
+/* int var_soft_bounce;
+/* time_t var_starttime;
+/* int var_ownreq_special;
+/* int var_daemon_timeout;
+/* char *var_syslog_facility;
+/* char *var_relay_domains;
+/* char *var_fflush_domains;
+/* char *var_mynetworks_style;
+/* char *var_verp_delims;
+/* char *var_verp_filter;
+/* char *var_par_dom_match;
+/* char *var_config_dirs;
+/*
+/* int var_inet_windowsize;
+/* char *var_import_environ;
+/* char *var_export_environ;
+/* char *var_debug_peer_list;
+/* int var_debug_peer_level;
+/* int var_in_flow_delay;
+/* int var_fault_inj_code;
+/* char *var_bounce_service;
+/* char *var_cleanup_service;
+/* char *var_defer_service;
+/* char *var_pickup_service;
+/* char *var_queue_service;
+/* char *var_rewrite_service;
+/* char *var_showq_service;
+/* char *var_error_service;
+/* char *var_flush_service;
+/* char *var_verify_service;
+/* char *var_trace_service;
+/* char *var_proxymap_service;
+/* char *var_proxywrite_service;
+/* int var_db_create_buf;
+/* int var_db_read_buf;
+/* long var_lmdb_map_size;
+/* int var_proc_limit;
+/* int var_mime_maxdepth;
+/* int var_mime_bound_len;
+/* int var_header_limit;
+/* int var_token_limit;
+/* int var_disable_mime_input;
+/* int var_disable_mime_oconv;
+/* int var_strict_8bitmime;
+/* int var_strict_7bit_hdrs;
+/* int var_strict_8bit_body;
+/* int var_strict_encoding;
+/* int var_verify_neg_cache;
+/* int var_oldlog_compat;
+/* int var_delay_max_res;
+/* char *var_int_filt_classes;
+/* int var_cyrus_sasl_authzid;
+/*
+/* char *var_multi_conf_dirs;
+/* char *var_multi_wrapper;
+/* char *var_multi_group;
+/* char *var_multi_name;
+/* bool var_multi_enable;
+/* bool var_long_queue_ids;
+/* bool var_daemon_open_fatal;
+/* char *var_dsn_filter;
+/* int var_smtputf8_enable
+/* int var_strict_smtputf8;
+/* char *var_smtputf8_autoclass;
+/* int var_idna2003_compat;
+/* char *var_compatibility_level;
+/* char *var_drop_hdrs;
+/* char *var_info_log_addr_form;
+/* bool var_enable_orcpt;
+/*
+/* void mail_params_init()
+/*
+/* const char null_format_string[1];
+/*
+/* long compatibility_level;
+/*
+/* int warn_compat_break_app_dot_mydomain;
+/* int warn_compat_break_smtputf8_enable;
+/* int warn_compat_break_chroot;
+/* int warn_compat_break_relay_restrictions;
+/*
+/* int warn_compat_break_relay_domains;
+/* int warn_compat_break_flush_domains;
+/* int warn_compat_break_mynetworks_style;
+/*
+/* int warn_compat_break_smtpd_tls_fpt_dgst;
+/* int warn_compat_break_smtp_tls_fpt_dgst;
+/* int warn_compat_break_lmtp_tls_fpt_dgst;
+/* int warn_compat_relay_before_rcpt_checks;
+/* int warn_compat_respectful_logging;
+/*
+/* char *var_maillog_file;
+/* char *var_maillog_file_pfxs;
+/* char *var_maillog_file_comp;
+/* char *var_maillog_file_stamp;
+/* char *var_postlog_service;
+/*
+/* char *var_dnssec_probe;
+/* bool var_relay_before_rcpt_checks;
+/* bool var_respectful_logging;
+/* char *var_known_tcp_ports;
+/* DESCRIPTION
+/* This module (actually the associated include file) defines
+/* the names and defaults of all mail configuration parameters.
+/*
+/* mail_params_init() initializes the built-in parameters listed above.
+/* These parameters are relied upon by library routines, so they are
+/* initialized globally so as to avoid hard-to-find errors due to
+/* missing initialization. This routine must be called early, at
+/* least before entering a chroot jail.
+/*
+/* null_format_string is a workaround for gcc compilers that complain
+/* about empty or null format strings.
+/*
+/* The warn_compat_XXX variables enable warnings for the use
+/* of legacy default settings after an incompatible change.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory; null system or domain name.
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_syslog.h>
+#include <get_hostname.h>
+#include <valid_hostname.h>
+#include <stringops.h>
+#include <safe.h>
+#include <safe_open.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <dict_db.h>
+#include <dict_lmdb.h>
+#include <inet_proto.h>
+#include <vstring_vstream.h>
+#include <iostuff.h>
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mynetworks.h>
+#include <mail_conf.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <verp_sender.h>
+#include <own_inet_addr.h>
+#include <mail_params.h>
+#include <compat_level.h>
+#include <config_known_tcp_ports.h>
+
+ /*
+ * Special configuration variables.
+ */
+char *var_myhostname;
+char *var_mydomain;
+char *var_myorigin;
+char *var_mydest;
+char *var_relayhost;
+char *var_transit_origin;
+char *var_transit_dest;
+char *var_mail_name;
+int var_helpful_warnings;
+char *var_syslog_name;
+char *var_mail_owner;
+uid_t var_owner_uid;
+gid_t var_owner_gid;
+char *var_sgid_group;
+gid_t var_sgid_gid;
+char *var_default_privs;
+uid_t var_default_uid;
+gid_t var_default_gid;
+char *var_config_dir;
+char *var_daemon_dir;
+char *var_data_dir;
+char *var_command_dir;
+char *var_meta_dir;
+char *var_queue_dir;
+char *var_shlib_dir;
+int var_use_limit;
+int var_event_drain;
+int var_idle_limit;
+int var_bundle_rcpt;
+char *var_procname;
+char *var_servname;
+int var_pid;
+int var_ipc_timeout;
+char *var_pid_dir;
+int var_dont_remove;
+char *var_inet_interfaces;
+char *var_proxy_interfaces;
+char *var_inet_protocols;
+char *var_mynetworks;
+char *var_double_bounce_sender;
+int var_line_limit;
+char *var_alias_db_map;
+long var_message_limit;
+char *var_mail_release;
+char *var_mail_version;
+int var_ipc_idle_limit;
+int var_ipc_ttl_limit;
+char *var_db_type;
+char *var_hash_queue_names;
+int var_hash_queue_depth;
+int var_trigger_timeout;
+char *var_rcpt_delim;
+int var_fork_tries;
+int var_fork_delay;
+int var_flock_tries;
+int var_flock_delay;
+int var_flock_stale;
+int var_disable_dns;
+int var_soft_bounce;
+time_t var_starttime;
+int var_ownreq_special;
+int var_daemon_timeout;
+char *var_syslog_facility;
+char *var_relay_domains;
+char *var_fflush_domains;
+char *var_mynetworks_style;
+char *var_verp_delims;
+char *var_verp_filter;
+int var_in_flow_delay;
+char *var_par_dom_match;
+char *var_config_dirs;
+
+int var_inet_windowsize;
+char *var_import_environ;
+char *var_export_environ;
+char *var_debug_peer_list;
+int var_debug_peer_level;
+int var_fault_inj_code;
+char *var_bounce_service;
+char *var_cleanup_service;
+char *var_defer_service;
+char *var_pickup_service;
+char *var_queue_service;
+char *var_rewrite_service;
+char *var_showq_service;
+char *var_error_service;
+char *var_flush_service;
+char *var_verify_service;
+char *var_trace_service;
+char *var_proxymap_service;
+char *var_proxywrite_service;
+int var_db_create_buf;
+int var_db_read_buf;
+long var_lmdb_map_size;
+int var_proc_limit;
+int var_mime_maxdepth;
+int var_mime_bound_len;
+int var_header_limit;
+int var_token_limit;
+int var_disable_mime_input;
+int var_disable_mime_oconv;
+int var_strict_8bitmime;
+int var_strict_7bit_hdrs;
+int var_strict_8bit_body;
+int var_strict_encoding;
+int var_verify_neg_cache;
+int var_oldlog_compat;
+int var_delay_max_res;
+char *var_int_filt_classes;
+int var_cyrus_sasl_authzid;
+
+char *var_multi_conf_dirs;
+char *var_multi_wrapper;
+char *var_multi_group;
+char *var_multi_name;
+bool var_multi_enable;
+bool var_long_queue_ids;
+bool var_daemon_open_fatal;
+bool var_dns_ncache_ttl_fix;
+char *var_dsn_filter;
+int var_smtputf8_enable;
+int var_strict_smtputf8;
+char *var_smtputf8_autoclass;
+int var_idna2003_compat;
+char *var_compatibility_level;
+char *var_drop_hdrs;
+char *var_info_log_addr_form;
+bool var_enable_orcpt;
+
+char *var_maillog_file;
+char *var_maillog_file_pfxs;
+char *var_maillog_file_comp;
+char *var_maillog_file_stamp;
+char *var_postlog_service;
+
+char *var_dnssec_probe;
+bool var_respectful_logging;
+char *var_known_tcp_ports;
+
+const char null_format_string[1] = "";
+
+ /*
+ * Compatibility level 3.6.
+ */
+int warn_compat_break_smtpd_tls_fpt_dgst;
+int warn_compat_break_smtp_tls_fpt_dgst;
+int warn_compat_break_lmtp_tls_fpt_dgst;
+int warn_compat_relay_before_rcpt_checks;
+int warn_compat_respectful_logging;
+
+ /*
+ * Compatibility level 2.
+ */
+int warn_compat_break_relay_domains;
+int warn_compat_break_flush_domains;
+int warn_compat_break_mynetworks_style;
+
+ /*
+ * Compatibility level 1.
+ */
+int warn_compat_break_app_dot_mydomain;
+int warn_compat_break_smtputf8_enable;
+int warn_compat_break_chroot;
+int warn_compat_break_relay_restrictions;
+
+ /*
+ * Parsed from var_compatibility_level;
+ */
+long compat_level;
+
+/* check_myhostname - lookup hostname and validate */
+
+static const char *check_myhostname(void)
+{
+ static const char *name;
+ const char *dot;
+ const char *domain;
+
+ /*
+ * Use cached result.
+ */
+ if (name)
+ return (name);
+
+ /*
+ * If the local machine name is not in FQDN form, try to append the
+ * contents of $mydomain. Use a default domain as a final workaround.
+ *
+ * DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - IT MAKES EVERY POSTFIX
+ * PROGRAM HANG WHEN DNS SERVICE IS UNAVAILABLE. IF YOU DON'T LIKE THE
+ * DEFAULT, THEN EDIT MAIN.CF.
+ */
+ name = get_hostname();
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ if ((dot = strchr(name, '.')) == 0) {
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ if ((domain = mail_conf_lookup_eval(VAR_MYDOMAIN)) == 0)
+ domain = DEF_MYDOMAIN;
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ name = concatenate(name, ".", domain, (char *) 0);
+ }
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ return (name);
+}
+
+/* check_mydomainname - lookup domain name and validate */
+
+static const char *check_mydomainname(void)
+{
+ char *dot;
+
+ /*
+ * Use a default domain when the hostname is not a FQDN ("foo").
+ *
+ * DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - IT MAKES EVERY POSTFIX
+ * PROGRAM HANG WHEN DNS SERVICE IS UNAVAILABLE. IF YOU DON'T LIKE THE
+ * DEFAULT, THEN EDIT MAIN.CF.
+ */
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ if ((dot = strchr(var_myhostname, '.')) == 0)
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ return (DEF_MYDOMAIN);
+ /* DO NOT CALL GETHOSTBYNAME OR GETNAMEINFO HERE - EDIT MAIN.CF */
+ return (dot + 1);
+}
+
+/* check_default_privs - lookup default user attributes and validate */
+
+static void check_default_privs(void)
+{
+ struct passwd *pwd;
+
+ if ((pwd = getpwnam(var_default_privs)) == 0)
+ msg_fatal("file %s/%s: parameter %s: unknown user name value: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, var_default_privs);
+ if ((var_default_uid = pwd->pw_uid) == 0)
+ msg_fatal("file %s/%s: parameter %s: user %s has privileged user ID",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, var_default_privs);
+ if ((var_default_gid = pwd->pw_gid) == 0)
+ msg_fatal("file %s/%s: parameter %s: user %s has privileged group ID",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, var_default_privs);
+}
+
+/* check_mail_owner - lookup owner user attributes and validate */
+
+static void check_mail_owner(void)
+{
+ struct passwd *pwd;
+
+ if ((pwd = getpwnam(var_mail_owner)) == 0)
+ msg_fatal("file %s/%s: parameter %s: unknown user name value: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MAIL_OWNER, var_mail_owner);
+ if ((var_owner_uid = pwd->pw_uid) == 0)
+ msg_fatal("file %s/%s: parameter %s: user %s has privileged user ID",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MAIL_OWNER, var_mail_owner);
+ if ((var_owner_gid = pwd->pw_gid) == 0)
+ msg_fatal("file %s/%s: parameter %s: user %s has privileged group ID",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MAIL_OWNER, var_mail_owner);
+
+ /*
+ * This detects only some forms of sharing. Enumerating the entire
+ * password file name space could be expensive. The purpose of this code
+ * is to discourage user ID sharing by developers and package
+ * maintainers.
+ */
+ if ((pwd = getpwuid(var_owner_uid)) != 0
+ && strcmp(pwd->pw_name, var_mail_owner) != 0)
+ msg_fatal("file %s/%s: parameter %s: user %s has same user ID as %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MAIL_OWNER, var_mail_owner, pwd->pw_name);
+}
+
+/* check_sgid_group - lookup setgid group attributes and validate */
+
+static void check_sgid_group(void)
+{
+ struct group *grp;
+
+ if ((grp = getgrnam(var_sgid_group)) == 0)
+ msg_fatal("file %s/%s: parameter %s: unknown group name: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_SGID_GROUP, var_sgid_group);
+ if ((var_sgid_gid = grp->gr_gid) == 0)
+ msg_fatal("file %s/%s: parameter %s: group %s has privileged group ID",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_SGID_GROUP, var_sgid_group);
+
+ /*
+ * This detects only some forms of sharing. Enumerating the entire group
+ * file name space could be expensive. The purpose of this code is to
+ * discourage group ID sharing by developers and package maintainers.
+ */
+ if ((grp = getgrgid(var_sgid_gid)) != 0
+ && strcmp(grp->gr_name, var_sgid_group) != 0)
+ msg_fatal("file %s/%s: parameter %s: group %s has same group ID as %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_SGID_GROUP, var_sgid_group, grp->gr_name);
+}
+
+/* check_overlap - disallow UID or GID sharing */
+
+static void check_overlap(void)
+{
+ if (strcmp(var_default_privs, var_mail_owner) == 0)
+ msg_fatal("file %s/%s: parameters %s and %s specify the same user %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER,
+ var_default_privs);
+ if (var_default_uid == var_owner_uid)
+ msg_fatal("file %s/%s: parameters %s and %s: users %s and %s have the same user ID: %ld",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER,
+ var_default_privs, var_mail_owner,
+ (long) var_owner_uid);
+ if (var_default_gid == var_owner_gid)
+ msg_fatal("file %s/%s: parameters %s and %s: users %s and %s have the same group ID: %ld",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER,
+ var_default_privs, var_mail_owner,
+ (long) var_owner_gid);
+ if (var_default_gid == var_sgid_gid)
+ msg_fatal("file %s/%s: parameters %s and %s: user %s and group %s have the same group ID: %ld",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_DEFAULT_PRIVS, VAR_SGID_GROUP,
+ var_default_privs, var_sgid_group,
+ (long) var_sgid_gid);
+ if (var_owner_gid == var_sgid_gid)
+ msg_fatal("file %s/%s: parameters %s and %s: user %s and group %s have the same group ID: %ld",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MAIL_OWNER, VAR_SGID_GROUP,
+ var_mail_owner, var_sgid_group,
+ (long) var_sgid_gid);
+}
+
+#ifdef MYORIGIN_FROM_FILE
+
+/* read_param_from_file - read parameter value from file */
+
+static char *read_param_from_file(const char *path)
+{
+ VSTRING *why = vstring_alloc(100);
+ VSTRING *buf = vstring_alloc(100);
+ VSTREAM *fp;
+ char *bp;
+ char *result;
+
+ /*
+ * Ugly macros to make complex expressions less unreadable.
+ */
+#define SKIP(start, var, cond) do { \
+ for (var = start; *var && (cond); var++) \
+ /* void */; \
+ } while (0)
+
+#define TRIM(s) do { \
+ char *p; \
+ for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--) \
+ /* void */; \
+ *p = 0; \
+ } while (0)
+
+ fp = safe_open(path, O_RDONLY, 0, (struct stat *) 0, -1, -1, why);
+ if (fp == 0)
+ msg_fatal("%s: %s", path, vstring_str(why));
+ vstring_get_nonl(buf, fp);
+ if (vstream_ferror(fp)) /* FIX 20070501 */
+ msg_fatal("%s: read error: %m", path);
+ vstream_fclose(fp);
+ SKIP(vstring_str(buf), bp, ISSPACE(*bp));
+ TRIM(bp);
+ result = mystrdup(bp);
+
+ vstring_free(why);
+ vstring_free(buf);
+ return (result);
+}
+
+#endif
+
+/* check_legacy_defaults - flag parameters that require safety-net logging */
+
+static void check_legacy_defaults(void)
+{
+
+ /*
+ * Basic idea: when an existing parameter default is changed, or a new
+ * parameter is introduced with incompatible default behavior, force
+ * Postfix to run with backwards-compatible default settings and log a
+ * warning when the backwards-compatible behavior is used.
+ *
+ * Based on a review of Postfix logging the system administrator can decide
+ * whether or not to make backwards-compatible default settings permanent
+ * in main.cf or master.cf.
+ *
+ * To turn off further warnings and deploy the new default settings, the
+ * system administrator should update the compatibility_level setting as
+ * recommended in the RELEASE_NOTES file.
+ *
+ * Each incompatible change has its own flag variable, instead of bit in a
+ * shared variable. We don't want to rip up code when we need more flag
+ * bits.
+ */
+
+ /*
+ * Look for specific parameters whose default changed when the
+ * compatibility level changed to 3.6.
+ */
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_3_6, msg_panic)) {
+ if (mail_conf_lookup(VAR_SMTPD_TLS_FPT_DGST) == 0)
+ warn_compat_break_smtpd_tls_fpt_dgst = 1;
+ if (mail_conf_lookup(VAR_SMTP_TLS_FPT_DGST) == 0)
+ warn_compat_break_smtp_tls_fpt_dgst = 1;
+ if (mail_conf_lookup(VAR_LMTP_TLS_FPT_DGST) == 0)
+ warn_compat_break_lmtp_tls_fpt_dgst = 1;
+ if (mail_conf_lookup(VAR_RELAY_BEFORE_RCPT_CHECKS) == 0)
+ warn_compat_relay_before_rcpt_checks = 1;
+ if (mail_conf_lookup(VAR_RESPECTFUL_LOGGING) == 0)
+ warn_compat_respectful_logging = 1;
+ }
+
+ /*
+ * Look for specific parameters whose default changed when the
+ * compatibility level changed to 2.
+ */
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_2, msg_panic)) {
+ if (mail_conf_lookup(VAR_RELAY_DOMAINS) == 0) {
+ warn_compat_break_relay_domains = 1;
+ if (mail_conf_lookup(VAR_FFLUSH_DOMAINS) == 0)
+ warn_compat_break_flush_domains = 1;
+ }
+ if (mail_conf_lookup(VAR_MYNETWORKS) == 0
+ && mail_conf_lookup(VAR_MYNETWORKS_STYLE) == 0)
+ warn_compat_break_mynetworks_style = 1;
+ }
+
+ /*
+ * Look for specific parameters whose default changed when the
+ * compatibility level changed from 0 to 1.
+ */
+ if (compat_level < compat_level_from_string(COMPAT_LEVEL_1, msg_panic)) {
+ if (mail_conf_lookup(VAR_APP_DOT_MYDOMAIN) == 0)
+ warn_compat_break_app_dot_mydomain = 1;
+
+ /*
+ * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a
+ * warning message is logged, so that they don't suddenly start to
+ * lose mail after Postfix is built with EAI support.
+ */
+ if (mail_conf_lookup(VAR_SMTPUTF8_ENABLE) == 0)
+ warn_compat_break_smtputf8_enable = 1;
+ warn_compat_break_chroot = 1;
+
+ /*
+ * Grandfathered in to help sites migrating from Postfix <2.10.
+ */
+ if (mail_conf_lookup(VAR_RELAY_CHECKS) == 0)
+ warn_compat_break_relay_restrictions = 1;
+ }
+}
+
+/* mail_params_init - configure built-in parameters */
+
+void mail_params_init()
+{
+ static const CONFIG_STR_TABLE compat_level_defaults[] = {
+ VAR_COMPAT_LEVEL, DEF_COMPAT_LEVEL, &var_compatibility_level, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE first_str_defaults[] = {
+ /* $mail_version may appear in other parameters. */
+ VAR_MAIL_VERSION, DEF_MAIL_VERSION, &var_mail_version, 1, 0,
+ VAR_SYSLOG_FACILITY, DEF_SYSLOG_FACILITY, &var_syslog_facility, 1, 0,
+ VAR_INET_PROTOCOLS, DEF_INET_PROTOCOLS, &var_inet_protocols, 0, 0,
+ VAR_MULTI_CONF_DIRS, DEF_MULTI_CONF_DIRS, &var_multi_conf_dirs, 0, 0,
+ /* multi_instance_wrapper may have dependencies but not dependents. */
+ VAR_MULTI_GROUP, DEF_MULTI_GROUP, &var_multi_group, 0, 0,
+ VAR_MULTI_NAME, DEF_MULTI_NAME, &var_multi_name, 0, 0,
+ VAR_MAILLOG_FILE, DEF_MAILLOG_FILE, &var_maillog_file, 0, 0,
+ VAR_MAILLOG_FILE_PFXS, DEF_MAILLOG_FILE_PFXS, &var_maillog_file_pfxs, 1, 0,
+ VAR_MAILLOG_FILE_COMP, DEF_MAILLOG_FILE_COMP, &var_maillog_file_comp, 1, 0,
+ VAR_MAILLOG_FILE_STAMP, DEF_MAILLOG_FILE_STAMP, &var_maillog_file_stamp, 1, 0,
+ VAR_POSTLOG_SERVICE, DEF_POSTLOG_SERVICE, &var_postlog_service, 1, 0,
+ VAR_DNSSEC_PROBE, DEF_DNSSEC_PROBE, &var_dnssec_probe, 0, 0,
+ VAR_KNOWN_TCP_PORTS, DEF_KNOWN_TCP_PORTS, &var_known_tcp_ports, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE first_bool_defaults[] = {
+ /* read and process the following before opening tables. */
+ VAR_DAEMON_OPEN_FATAL, DEF_DAEMON_OPEN_FATAL, &var_daemon_open_fatal,
+ VAR_DNS_NCACHE_TTL_FIX, DEF_DNS_NCACHE_TTL_FIX, &var_dns_ncache_ttl_fix,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE first_nbool_defaults[] = {
+ /* read and process the following before opening tables. */
+ VAR_SMTPUTF8_ENABLE, DEF_SMTPUTF8_ENABLE, &var_smtputf8_enable,
+ VAR_IDNA2003_COMPAT, DEF_IDNA2003_COMPAT, &var_idna2003_compat,
+ VAR_RESPECTFUL_LOGGING, DEF_RESPECTFUL_LOGGING, &var_respectful_logging,
+ 0,
+ };
+ static const CONFIG_STR_FN_TABLE function_str_defaults[] = {
+ VAR_MYHOSTNAME, check_myhostname, &var_myhostname, 1, 0,
+ VAR_MYDOMAIN, check_mydomainname, &var_mydomain, 1, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE other_str_defaults[] = {
+ VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name, 1, 0,
+ VAR_SYSLOG_NAME, DEF_SYSLOG_NAME, &var_syslog_name, 1, 0,
+ VAR_MAIL_OWNER, DEF_MAIL_OWNER, &var_mail_owner, 1, 0,
+ VAR_SGID_GROUP, DEF_SGID_GROUP, &var_sgid_group, 1, 0,
+ VAR_MYDEST, DEF_MYDEST, &var_mydest, 0, 0,
+ VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin, 1, 0,
+ VAR_RELAYHOST, DEF_RELAYHOST, &var_relayhost, 0, 0,
+ VAR_DAEMON_DIR, DEF_DAEMON_DIR, &var_daemon_dir, 1, 0,
+ VAR_DATA_DIR, DEF_DATA_DIR, &var_data_dir, 1, 0,
+ VAR_COMMAND_DIR, DEF_COMMAND_DIR, &var_command_dir, 1, 0,
+ VAR_META_DIR, DEF_META_DIR, &var_meta_dir, 1, 0,
+ VAR_QUEUE_DIR, DEF_QUEUE_DIR, &var_queue_dir, 1, 0,
+ VAR_SHLIB_DIR, DEF_SHLIB_DIR, &var_shlib_dir, 1, 0,
+ VAR_PID_DIR, DEF_PID_DIR, &var_pid_dir, 1, 0,
+ VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces, 0, 0,
+ VAR_PROXY_INTERFACES, DEF_PROXY_INTERFACES, &var_proxy_interfaces, 0, 0,
+ VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender, 1, 0,
+ VAR_DEFAULT_PRIVS, DEF_DEFAULT_PRIVS, &var_default_privs, 1, 0,
+ VAR_ALIAS_DB_MAP, DEF_ALIAS_DB_MAP, &var_alias_db_map, 0, 0,
+ VAR_MAIL_RELEASE, DEF_MAIL_RELEASE, &var_mail_release, 1, 0,
+ VAR_DB_TYPE, DEF_DB_TYPE, &var_db_type, 1, 0,
+ VAR_HASH_QUEUE_NAMES, DEF_HASH_QUEUE_NAMES, &var_hash_queue_names, 1, 0,
+ VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim, 0, 0,
+ VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0,
+ VAR_FFLUSH_DOMAINS, DEF_FFLUSH_DOMAINS, &var_fflush_domains, 0, 0,
+ VAR_EXPORT_ENVIRON, DEF_EXPORT_ENVIRON, &var_export_environ, 0, 0,
+ VAR_IMPORT_ENVIRON, DEF_IMPORT_ENVIRON, &var_import_environ, 0, 0,
+ VAR_MYNETWORKS_STYLE, DEF_MYNETWORKS_STYLE, &var_mynetworks_style, 1, 0,
+ VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
+ VAR_VERP_DELIMS, DEF_VERP_DELIMS, &var_verp_delims, 2, 2,
+ VAR_VERP_FILTER, DEF_VERP_FILTER, &var_verp_filter, 1, 0,
+ VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match, 0, 0,
+ VAR_CONFIG_DIRS, DEF_CONFIG_DIRS, &var_config_dirs, 0, 0,
+ VAR_BOUNCE_SERVICE, DEF_BOUNCE_SERVICE, &var_bounce_service, 1, 0,
+ VAR_CLEANUP_SERVICE, DEF_CLEANUP_SERVICE, &var_cleanup_service, 1, 0,
+ VAR_DEFER_SERVICE, DEF_DEFER_SERVICE, &var_defer_service, 1, 0,
+ VAR_PICKUP_SERVICE, DEF_PICKUP_SERVICE, &var_pickup_service, 1, 0,
+ VAR_QUEUE_SERVICE, DEF_QUEUE_SERVICE, &var_queue_service, 1, 0,
+ VAR_REWRITE_SERVICE, DEF_REWRITE_SERVICE, &var_rewrite_service, 1, 0,
+ VAR_SHOWQ_SERVICE, DEF_SHOWQ_SERVICE, &var_showq_service, 1, 0,
+ VAR_ERROR_SERVICE, DEF_ERROR_SERVICE, &var_error_service, 1, 0,
+ VAR_FLUSH_SERVICE, DEF_FLUSH_SERVICE, &var_flush_service, 1, 0,
+ VAR_VERIFY_SERVICE, DEF_VERIFY_SERVICE, &var_verify_service, 1, 0,
+ VAR_TRACE_SERVICE, DEF_TRACE_SERVICE, &var_trace_service, 1, 0,
+ VAR_PROXYMAP_SERVICE, DEF_PROXYMAP_SERVICE, &var_proxymap_service, 1, 0,
+ VAR_PROXYWRITE_SERVICE, DEF_PROXYWRITE_SERVICE, &var_proxywrite_service, 1, 0,
+ VAR_INT_FILT_CLASSES, DEF_INT_FILT_CLASSES, &var_int_filt_classes, 0, 0,
+ /* multi_instance_wrapper may have dependencies but not dependents. */
+ VAR_MULTI_WRAPPER, DEF_MULTI_WRAPPER, &var_multi_wrapper, 0, 0,
+ VAR_DSN_FILTER, DEF_DSN_FILTER, &var_dsn_filter, 0, 0,
+ VAR_SMTPUTF8_AUTOCLASS, DEF_SMTPUTF8_AUTOCLASS, &var_smtputf8_autoclass, 1, 0,
+ VAR_DROP_HDRS, DEF_DROP_HDRS, &var_drop_hdrs, 0, 0,
+ VAR_INFO_LOG_ADDR_FORM, DEF_INFO_LOG_ADDR_FORM, &var_info_log_addr_form, 1, 0,
+ 0,
+ };
+ static const CONFIG_STR_FN_TABLE function_str_defaults_2[] = {
+ VAR_MYNETWORKS, mynetworks, &var_mynetworks, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE other_int_defaults[] = {
+ VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0,
+ VAR_MAX_USE, DEF_MAX_USE, &var_use_limit, 1, 0,
+ VAR_DONT_REMOVE, DEF_DONT_REMOVE, &var_dont_remove, 0, 0,
+ VAR_LINE_LIMIT, DEF_LINE_LIMIT, &var_line_limit, 512, 0,
+ VAR_HASH_QUEUE_DEPTH, DEF_HASH_QUEUE_DEPTH, &var_hash_queue_depth, 1, 0,
+ VAR_FORK_TRIES, DEF_FORK_TRIES, &var_fork_tries, 1, 0,
+ VAR_FLOCK_TRIES, DEF_FLOCK_TRIES, &var_flock_tries, 1, 0,
+ VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
+ VAR_FAULT_INJ_CODE, DEF_FAULT_INJ_CODE, &var_fault_inj_code, 0, 0,
+ VAR_DB_CREATE_BUF, DEF_DB_CREATE_BUF, &var_db_create_buf, 1, 0,
+ VAR_DB_READ_BUF, DEF_DB_READ_BUF, &var_db_read_buf, 1, 0,
+ VAR_HEADER_LIMIT, DEF_HEADER_LIMIT, &var_header_limit, 1, 0,
+ VAR_TOKEN_LIMIT, DEF_TOKEN_LIMIT, &var_token_limit, 1, 0,
+ VAR_MIME_MAXDEPTH, DEF_MIME_MAXDEPTH, &var_mime_maxdepth, 1, 0,
+ VAR_MIME_BOUND_LEN, DEF_MIME_BOUND_LEN, &var_mime_bound_len, 1, 0,
+ VAR_DELAY_MAX_RES, DEF_DELAY_MAX_RES, &var_delay_max_res, MIN_DELAY_MAX_RES, MAX_DELAY_MAX_RES,
+ VAR_INET_WINDOW, DEF_INET_WINDOW, &var_inet_windowsize, 0, 0,
+ 0,
+ };
+ static const CONFIG_LONG_TABLE long_defaults[] = {
+ VAR_MESSAGE_LIMIT, DEF_MESSAGE_LIMIT, &var_message_limit, 0, 0,
+ VAR_LMDB_MAP_SIZE, DEF_LMDB_MAP_SIZE, &var_lmdb_map_size, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_defaults[] = {
+ VAR_EVENT_DRAIN, DEF_EVENT_DRAIN, &var_event_drain, 1, 0,
+ VAR_MAX_IDLE, DEF_MAX_IDLE, &var_idle_limit, 1, 0,
+ VAR_IPC_TIMEOUT, DEF_IPC_TIMEOUT, &var_ipc_timeout, 1, 0,
+ VAR_IPC_IDLE, DEF_IPC_IDLE, &var_ipc_idle_limit, 1, 0,
+ VAR_IPC_TTL, DEF_IPC_TTL, &var_ipc_ttl_limit, 1, 0,
+ VAR_TRIGGER_TIMEOUT, DEF_TRIGGER_TIMEOUT, &var_trigger_timeout, 1, 0,
+ VAR_FORK_DELAY, DEF_FORK_DELAY, &var_fork_delay, 1, 0,
+ VAR_FLOCK_DELAY, DEF_FLOCK_DELAY, &var_flock_delay, 1, 0,
+ VAR_FLOCK_STALE, DEF_FLOCK_STALE, &var_flock_stale, 1, 0,
+ VAR_DAEMON_TIMEOUT, DEF_DAEMON_TIMEOUT, &var_daemon_timeout, 1, 0,
+ VAR_IN_FLOW_DELAY, DEF_IN_FLOW_DELAY, &var_in_flow_delay, 0, 10,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_defaults[] = {
+ VAR_DISABLE_DNS, DEF_DISABLE_DNS, &var_disable_dns,
+ VAR_SOFT_BOUNCE, DEF_SOFT_BOUNCE, &var_soft_bounce,
+ VAR_OWNREQ_SPECIAL, DEF_OWNREQ_SPECIAL, &var_ownreq_special,
+ VAR_STRICT_8BITMIME, DEF_STRICT_8BITMIME, &var_strict_8bitmime,
+ VAR_STRICT_7BIT_HDRS, DEF_STRICT_7BIT_HDRS, &var_strict_7bit_hdrs,
+ VAR_STRICT_8BIT_BODY, DEF_STRICT_8BIT_BODY, &var_strict_8bit_body,
+ VAR_STRICT_ENCODING, DEF_STRICT_ENCODING, &var_strict_encoding,
+ VAR_DISABLE_MIME_INPUT, DEF_DISABLE_MIME_INPUT, &var_disable_mime_input,
+ VAR_DISABLE_MIME_OCONV, DEF_DISABLE_MIME_OCONV, &var_disable_mime_oconv,
+ VAR_VERIFY_NEG_CACHE, DEF_VERIFY_NEG_CACHE, &var_verify_neg_cache,
+ VAR_OLDLOG_COMPAT, DEF_OLDLOG_COMPAT, &var_oldlog_compat,
+ VAR_HELPFUL_WARNINGS, DEF_HELPFUL_WARNINGS, &var_helpful_warnings,
+ VAR_CYRUS_SASL_AUTHZID, DEF_CYRUS_SASL_AUTHZID, &var_cyrus_sasl_authzid,
+ VAR_MULTI_ENABLE, DEF_MULTI_ENABLE, &var_multi_enable,
+ VAR_LONG_QUEUE_IDS, DEF_LONG_QUEUE_IDS, &var_long_queue_ids,
+ VAR_STRICT_SMTPUTF8, DEF_STRICT_SMTPUTF8, &var_strict_smtputf8,
+ VAR_ENABLE_ORCPT, DEF_ENABLE_ORCPT, &var_enable_orcpt,
+ 0,
+ };
+ const char *cp;
+
+ /*
+ * Extract compatibility level first, so that we can determine what
+ * parameters of interest are left at their legacy defaults.
+ */
+ if (var_compatibility_level == 0)
+ compat_level_relop_register();
+ get_mail_conf_str_table(compat_level_defaults);
+ compat_level = compat_level_from_string(var_compatibility_level, msg_fatal);
+ check_legacy_defaults();
+
+ /*
+ * Extract syslog_facility early, so that from here on all errors are
+ * logged with the proper facility.
+ */
+ get_mail_conf_str_table(first_str_defaults);
+
+ if (!msg_syslog_set_facility(var_syslog_facility))
+ msg_fatal("file %s/%s: parameter %s: unrecognized value: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_SYSLOG_FACILITY, var_syslog_facility);
+
+ /*
+ * Should daemons terminate after table open error, or should they
+ * continue execution with reduced functionality?
+ */
+ get_mail_conf_bool_table(first_bool_defaults);
+ if (var_daemon_open_fatal)
+ dict_allow_surrogate = 0;
+
+ /*
+ * Should we open tables with UTF8 support, or in the legacy 8-bit clean
+ * mode with ASCII-only casefolding?
+ */
+ get_mail_conf_nbool_table(first_nbool_defaults);
+
+ /*
+ * Report run-time versus compile-time discrepancies.
+ */
+#ifdef NO_EAI
+ if (var_smtputf8_enable)
+ msg_warn("%s is true, but EAI support is not compiled in",
+ VAR_SMTPUTF8_ENABLE);
+ var_smtputf8_enable = 0;
+#else
+ midna_domain_transitional = var_idna2003_compat;
+ if (var_smtputf8_enable)
+ midna_domain_pre_chroot();
+#endif
+ util_utf8_enable = var_smtputf8_enable;
+
+ /*
+ * Configure the known TCP port mappings.
+ */
+ config_known_tcp_ports(VAR_KNOWN_TCP_PORTS, var_known_tcp_ports);
+
+ /*
+ * What protocols should we attempt to support? The result is stored in
+ * the global inet_proto_table variable.
+ */
+ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Variables whose defaults are determined at runtime. Some sites use
+ * short hostnames in the host table; some sites name their system after
+ * the domain.
+ */
+ get_mail_conf_str_fn_table(function_str_defaults);
+ if (!valid_hostname(var_myhostname, DO_GRIPE))
+ msg_fatal("file %s/%s: parameter %s: bad parameter value: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MYHOSTNAME, var_myhostname);
+ if (!valid_hostname(var_mydomain, DO_GRIPE))
+ msg_fatal("file %s/%s: parameter %s: bad parameter value: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_MYDOMAIN, var_mydomain);
+
+ /*
+ * Variables that are needed by almost every program.
+ *
+ * XXX Reading the myorigin value from file is originally a Debian Linux
+ * feature. This code is not enabled by default because of problems: 1)
+ * it re-implements its own parameter syntax checks, and 2) it does not
+ * implement $name expansions.
+ */
+ get_mail_conf_str_table(other_str_defaults);
+#ifdef MYORIGIN_FROM_FILE
+ if (*var_myorigin == '/') {
+ char *origin = read_param_from_file(var_myorigin);
+
+ if (*origin == 0)
+ msg_fatal("%s file %s is empty", VAR_MYORIGIN, var_myorigin);
+ myfree(var_myorigin); /* FIX 20070501 */
+ var_myorigin = origin;
+ }
+#endif
+ get_mail_conf_int_table(other_int_defaults);
+ get_mail_conf_long_table(long_defaults);
+ get_mail_conf_bool_table(bool_defaults);
+ get_mail_conf_time_table(time_defaults);
+ check_default_privs();
+ check_mail_owner();
+ check_sgid_group();
+ check_overlap();
+ dict_db_cache_size = var_db_read_buf;
+ dict_lmdb_map_size = var_lmdb_map_size;
+ inet_windowsize = var_inet_windowsize;
+
+ /*
+ * Variables whose defaults are determined at runtime, after other
+ * variables have been set. This dependency is admittedly a bit tricky.
+ * XXX Perhaps we should just register variables, and let the evaluator
+ * figure out in what order to evaluate things.
+ */
+ get_mail_conf_str_fn_table(function_str_defaults_2);
+
+ /*
+ * FIX 200412 The IPv6 patch did not call own_inet_addr_list() before
+ * entering the chroot jail on Linux IPv6 systems. Linux has the IPv6
+ * interface list in /proc, which is not available after chrooting.
+ */
+ (void) own_inet_addr_list();
+
+ /*
+ * The PID variable cannot be set from the configuration file!!
+ */
+ set_mail_conf_int(VAR_PID, var_pid = getpid());
+
+ /*
+ * Neither can the start time variable. It isn't even visible.
+ */
+ time(&var_starttime);
+
+ /*
+ * Export the syslog name so children can inherit and use it before they
+ * have initialized.
+ */
+ if ((cp = safe_getenv(CONF_ENV_LOGTAG)) == 0
+ || strcmp(cp, var_syslog_name) != 0)
+ if (setenv(CONF_ENV_LOGTAG, var_syslog_name, 1) < 0)
+ msg_fatal("setenv %s %s: %m", CONF_ENV_LOGTAG, var_syslog_name);
+
+ /*
+ * I have seen this happen just too often.
+ */
+ if (strcasecmp_utf8(var_myhostname, var_relayhost) == 0)
+ msg_fatal("%s and %s parameter settings must not be identical: %s",
+ VAR_MYHOSTNAME, VAR_RELAYHOST, var_myhostname);
+
+ /*
+ * XXX These should be caught by a proper parameter parsing algorithm.
+ */
+ if (var_myorigin[strcspn(var_myorigin, CHARS_COMMA_SP)])
+ msg_fatal("%s parameter setting must not contain multiple values: %s",
+ VAR_MYORIGIN, var_myorigin);
+
+ /*
+ * One more sanity check.
+ */
+ if ((cp = verp_delims_verify(var_verp_delims)) != 0)
+ msg_fatal("file %s/%s: parameters %s and %s: %s",
+ var_config_dir, MAIN_CONF_FILE,
+ VAR_VERP_DELIMS, VAR_VERP_FILTER, cp);
+}
diff --git a/src/global/mail_params.h b/src/global/mail_params.h
new file mode 100644
index 0000000..c579ef0
--- /dev/null
+++ b/src/global/mail_params.h
@@ -0,0 +1,4396 @@
+#ifndef _MAIL_PARAMS_H_INCLUDED_
+#define _MAIL_PARAMS_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_params 3h
+/* SUMMARY
+/* globally configurable parameters
+/* SYNOPSIS
+/* #include <mail_params.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * This is to make it easier to auto-generate tables.
+ */
+typedef int bool;
+
+#ifdef USE_TLS
+#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
+#include <openssl/objects.h> /* SN_* and NID_* macros */
+#if OPENSSL_VERSION_NUMBER < 0x1010100fUL
+#error "OpenSSL releases prior to 1.1.1 are no longer supported"
+#endif
+#endif
+
+ /*
+ * Name used when this mail system announces itself.
+ */
+#define VAR_MAIL_NAME "mail_name"
+#define DEF_MAIL_NAME "Postfix"
+extern char *var_mail_name;
+
+ /*
+ * You want to be helped or not.
+ */
+#define VAR_HELPFUL_WARNINGS "helpful_warnings"
+#define DEF_HELPFUL_WARNINGS 1
+extern bool var_helpful_warnings;
+
+ /*
+ * You want to be helped or not.
+ */
+#define VAR_SHOW_UNK_RCPT_TABLE "show_user_unknown_table_name"
+#define DEF_SHOW_UNK_RCPT_TABLE 1
+extern bool var_show_unk_rcpt_table;
+
+ /*
+ * Compatibility level and migration support. Update postconf(5),
+ * COMPATIBILITY_README, global/mail_params.[hc] and conf/main.cf when
+ * updating the current compatibility level.
+ */
+#define COMPAT_LEVEL_0 "0"
+#define COMPAT_LEVEL_1 "1"
+#define COMPAT_LEVEL_2 "2"
+#define COMPAT_LEVEL_3_6 "3.6"
+#define LAST_COMPAT_LEVEL COMPAT_LEVEL_3_6
+
+#define VAR_COMPAT_LEVEL "compatibility_level"
+#define DEF_COMPAT_LEVEL COMPAT_LEVEL_0
+extern char *var_compatibility_level;
+
+extern int warn_compat_break_app_dot_mydomain;
+extern int warn_compat_break_smtputf8_enable;
+extern int warn_compat_break_chroot;
+extern int warn_compat_break_relay_restrictions; /* Postfix 2.10. */
+
+extern int warn_compat_break_relay_domains;
+extern int warn_compat_break_flush_domains;
+extern int warn_compat_break_mynetworks_style;
+
+extern int warn_compat_break_smtpd_tls_fpt_dgst;
+extern int warn_compat_break_smtp_tls_fpt_dgst;
+extern int warn_compat_break_lmtp_tls_fpt_dgst;
+extern int warn_compat_relay_before_rcpt_checks;
+extern int warn_compat_respectful_logging;
+
+extern long compat_level;
+
+ /*
+ * What problem classes should be reported to the postmaster via email.
+ * Default is bad problems only. See mail_error(3). Even when mail notices
+ * are disabled, problems are still logged to the syslog daemon.
+ *
+ * Do not add "protocol" to the default setting. It gives Postfix a bad
+ * reputation: people get mail whenever spam software makes a mistake.
+ */
+#define VAR_NOTIFY_CLASSES "notify_classes"
+#define DEF_NOTIFY_CLASSES "resource, software" /* Not: "protocol" */
+extern char *var_notify_classes;
+
+ /*
+ * What do I turn <> into? Sendmail defaults to mailer-daemon.
+ */
+#define VAR_EMPTY_ADDR "empty_address_recipient"
+#define DEF_EMPTY_ADDR MAIL_ADDR_MAIL_DAEMON
+extern char *var_empty_addr;
+
+ /*
+ * Privileges used by the mail system: the owner of files and commands, and
+ * the rights to be used when running external commands.
+ */
+#define VAR_MAIL_OWNER "mail_owner"
+#define DEF_MAIL_OWNER "postfix"
+extern char *var_mail_owner;
+extern uid_t var_owner_uid;
+extern gid_t var_owner_gid;
+
+#define VAR_SGID_GROUP "setgid_group"
+#define DEF_SGID_GROUP "postdrop"
+extern char *var_sgid_group;
+extern gid_t var_sgid_gid;
+
+#define VAR_DEFAULT_PRIVS "default_privs"
+#define DEF_DEFAULT_PRIVS "nobody"
+extern char *var_default_privs;
+extern uid_t var_default_uid;
+extern gid_t var_default_gid;
+
+ /*
+ * Access control for local privileged operations:
+ */
+#define STATIC_ANYONE_ACL "static:anyone"
+
+#define VAR_FLUSH_ACL "authorized_flush_users"
+#define DEF_FLUSH_ACL STATIC_ANYONE_ACL
+extern char *var_flush_acl;
+
+#define VAR_SHOWQ_ACL "authorized_mailq_users"
+#define DEF_SHOWQ_ACL STATIC_ANYONE_ACL
+extern char *var_showq_acl;
+
+#define VAR_SUBMIT_ACL "authorized_submit_users"
+#define DEF_SUBMIT_ACL STATIC_ANYONE_ACL
+extern char *var_submit_acl;
+
+ /*
+ * Local submission, envelope sender ownership.
+ */
+#define VAR_LOCAL_LOGIN_SND_MAPS "local_login_sender_maps"
+#define DEF_LOCAL_LOGIN_SND_MAPS "static:*"
+extern char *var_local_login_snd__maps;
+
+#define VAR_NULL_LOCAL_LOGIN_SND_MAPS_KEY "empty_address_local_login_sender_maps_lookup_key"
+#define DEF_NULL_LOCAL_LOGIN_SND_MAPS_KEY "<>"
+extern char *var_null_local_login_snd_maps_key;
+
+ /*
+ * What goes on the right-hand side of addresses of mail sent from this
+ * machine.
+ */
+#define VAR_MYORIGIN "myorigin"
+#define DEF_MYORIGIN "$myhostname"
+extern char *var_myorigin;
+
+ /*
+ * What domains I will receive mail for. Not to be confused with transit
+ * mail to other destinations.
+ */
+#define VAR_MYDEST "mydestination"
+#define DEF_MYDEST "$myhostname, localhost.$mydomain, localhost"
+extern char *var_mydest;
+
+ /*
+ * These are by default taken from the name service.
+ */
+#define VAR_MYHOSTNAME "myhostname" /* my hostname (fqdn) */
+extern char *var_myhostname;
+
+#define VAR_MYDOMAIN "mydomain" /* my domain name */
+#define DEF_MYDOMAIN "localdomain"
+extern char *var_mydomain;
+
+ /*
+ * The default local delivery transport.
+ */
+#define VAR_LOCAL_TRANSPORT "local_transport"
+#define DEF_LOCAL_TRANSPORT MAIL_SERVICE_LOCAL ":$myhostname"
+extern char *var_local_transport;
+
+ /*
+ * Where to send postmaster copies of bounced mail, and other notices.
+ */
+#define VAR_BOUNCE_RCPT "bounce_notice_recipient"
+#define DEF_BOUNCE_RCPT "postmaster"
+extern char *var_bounce_rcpt;
+
+#define VAR_2BOUNCE_RCPT "2bounce_notice_recipient"
+#define DEF_2BOUNCE_RCPT "postmaster"
+extern char *var_2bounce_rcpt;
+
+#define VAR_DELAY_RCPT "delay_notice_recipient"
+#define DEF_DELAY_RCPT "postmaster"
+extern char *var_delay_rcpt;
+
+#define VAR_ERROR_RCPT "error_notice_recipient"
+#define DEF_ERROR_RCPT "postmaster"
+extern char *var_error_rcpt;
+
+ /*
+ * Virtual host support. Default is to listen on all machine interfaces.
+ */
+#define VAR_INET_INTERFACES "inet_interfaces" /* listen addresses */
+#define INET_INTERFACES_ALL "all"
+#define INET_INTERFACES_LOCAL "loopback-only"
+#define DEF_INET_INTERFACES INET_INTERFACES_ALL
+extern char *var_inet_interfaces;
+
+#define VAR_PROXY_INTERFACES "proxy_interfaces" /* proxies, NATs */
+#define DEF_PROXY_INTERFACES ""
+extern char *var_proxy_interfaces;
+
+ /*
+ * Masquerading (i.e. subdomain stripping).
+ */
+#define VAR_MASQ_DOMAINS "masquerade_domains"
+#define DEF_MASQ_DOMAINS ""
+extern char *var_masq_domains;
+
+#define VAR_MASQ_EXCEPTIONS "masquerade_exceptions"
+#define DEF_MASQ_EXCEPTIONS ""
+extern char *var_masq_exceptions;
+
+#define MASQ_CLASS_ENV_FROM "envelope_sender"
+#define MASQ_CLASS_ENV_RCPT "envelope_recipient"
+#define MASQ_CLASS_HDR_FROM "header_sender"
+#define MASQ_CLASS_HDR_RCPT "header_recipient"
+
+#define VAR_MASQ_CLASSES "masquerade_classes"
+#define DEF_MASQ_CLASSES MASQ_CLASS_ENV_FROM ", " \
+ MASQ_CLASS_HDR_FROM ", " \
+ MASQ_CLASS_HDR_RCPT
+extern char *var_masq_classes;
+
+ /*
+ * Intranet versus internet.
+ */
+#define VAR_RELAYHOST "relayhost"
+#define DEF_RELAYHOST ""
+extern char *var_relayhost;
+
+#define VAR_SND_RELAY_MAPS "sender_dependent_relayhost_maps"
+#define DEF_SND_RELAY_MAPS ""
+extern char *var_snd_relay_maps;
+
+#define VAR_NULL_RELAY_MAPS_KEY "empty_address_relayhost_maps_lookup_key"
+#define DEF_NULL_RELAY_MAPS_KEY "<>"
+extern char *var_null_relay_maps_key;
+
+#define VAR_SMTP_FALLBACK "smtp_fallback_relay"
+#define DEF_SMTP_FALLBACK "$fallback_relay"
+#define VAR_LMTP_FALLBACK "lmtp_fallback_relay"
+#define DEF_LMTP_FALLBACK ""
+#define DEF_FALLBACK_RELAY ""
+extern char *var_fallback_relay;
+
+#define VAR_DISABLE_DNS "disable_dns_lookups"
+#define DEF_DISABLE_DNS 0
+extern bool var_disable_dns;
+
+#define SMTP_DNS_SUPPORT_DISABLED "disabled"
+#define SMTP_DNS_SUPPORT_ENABLED "enabled"
+#define SMTP_DNS_SUPPORT_DNSSEC "dnssec"
+
+#define VAR_SMTP_DNS_SUPPORT "smtp_dns_support_level"
+#define DEF_SMTP_DNS_SUPPORT ""
+#define VAR_LMTP_DNS_SUPPORT "lmtp_dns_support_level"
+#define DEF_LMTP_DNS_SUPPORT ""
+extern char *var_smtp_dns_support;
+
+#define SMTP_HOST_LOOKUP_DNS "dns"
+#define SMTP_HOST_LOOKUP_NATIVE "native"
+
+#define VAR_SMTP_HOST_LOOKUP "smtp_host_lookup"
+#define DEF_SMTP_HOST_LOOKUP SMTP_HOST_LOOKUP_DNS
+#define VAR_LMTP_HOST_LOOKUP "lmtp_host_lookup"
+#define DEF_LMTP_HOST_LOOKUP SMTP_HOST_LOOKUP_DNS
+extern char *var_smtp_host_lookup;
+
+#define SMTP_DNS_RES_OPT_DEFNAMES "res_defnames"
+#define SMTP_DNS_RES_OPT_DNSRCH "res_dnsrch"
+
+#define VAR_SMTP_DNS_RES_OPT "smtp_dns_resolver_options"
+#define DEF_SMTP_DNS_RES_OPT ""
+#define VAR_LMTP_DNS_RES_OPT "lmtp_dns_resolver_options"
+#define DEF_LMTP_DNS_RES_OPT ""
+extern char *var_smtp_dns_res_opt;
+
+#define VAR_SMTP_MXADDR_LIMIT "smtp_mx_address_limit"
+#define DEF_SMTP_MXADDR_LIMIT 5
+#define VAR_LMTP_MXADDR_LIMIT "lmtp_mx_address_limit"
+#define DEF_LMTP_MXADDR_LIMIT 5
+extern int var_smtp_mxaddr_limit;
+
+#define VAR_SMTP_MXSESS_LIMIT "smtp_mx_session_limit"
+#define DEF_SMTP_MXSESS_LIMIT 2
+#define VAR_LMTP_MXSESS_LIMIT "lmtp_mx_session_limit"
+#define DEF_LMTP_MXSESS_LIMIT 2
+extern int var_smtp_mxsess_limit;
+
+ /*
+ * Location of the mail queue directory tree.
+ */
+#define VAR_QUEUE_DIR "queue_directory"
+#ifndef DEF_QUEUE_DIR
+#define DEF_QUEUE_DIR "/var/spool/postfix"
+#endif
+extern char *var_queue_dir;
+
+ /*
+ * Location of command and daemon programs.
+ */
+#define VAR_DAEMON_DIR "daemon_directory"
+#ifndef DEF_DAEMON_DIR
+#define DEF_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+extern char *var_daemon_dir;
+
+#define VAR_COMMAND_DIR "command_directory"
+#ifndef DEF_COMMAND_DIR
+#define DEF_COMMAND_DIR "/usr/sbin"
+#endif
+extern char *var_command_dir;
+
+ /*
+ * Location of PID files.
+ */
+#define VAR_PID_DIR "process_id_directory"
+#ifndef DEF_PID_DIR
+#define DEF_PID_DIR "pid"
+#endif
+extern char *var_pid_dir;
+
+ /*
+ * Location of writable data files.
+ */
+#define VAR_DATA_DIR "data_directory"
+#ifndef DEF_DATA_DIR
+#define DEF_DATA_DIR "/var/lib/postfix"
+#endif
+extern char *var_data_dir;
+
+ /*
+ * Program startup time.
+ */
+extern time_t var_starttime;
+
+ /*
+ * Location of configuration files.
+ */
+#define VAR_CONFIG_DIR "config_directory"
+#ifndef DEF_CONFIG_DIR
+#define DEF_CONFIG_DIR "/etc/postfix"
+#endif
+extern char *var_config_dir;
+
+#define VAR_CONFIG_DIRS "alternate_config_directories"
+#define DEF_CONFIG_DIRS ""
+extern char *var_config_dirs;
+
+#define MAIN_CONF_FILE "main.cf"
+#define MASTER_CONF_FILE "master.cf"
+
+ /*
+ * Preferred type of indexed files. The DEF_DB_TYPE macro value is system
+ * dependent. It is defined in <sys_defs.h>.
+ */
+#define VAR_DB_TYPE "default_database_type"
+extern char *var_db_type;
+
+ /*
+ * What syslog facility to use. Unfortunately, something may have to be
+ * logged before parameters are read from the main.cf file. This logging
+ * will go the LOG_FACILITY facility specified below.
+ */
+#define VAR_SYSLOG_FACILITY "syslog_facility"
+extern char *var_syslog_facility;
+
+#ifndef DEF_SYSLOG_FACILITY
+#define DEF_SYSLOG_FACILITY "mail"
+#endif
+
+#ifndef LOG_FACILITY
+#define LOG_FACILITY LOG_MAIL
+#endif
+
+ /*
+ * Big brother: who receives a blank-carbon copy of all mail that enters
+ * this mail system.
+ */
+#define VAR_ALWAYS_BCC "always_bcc"
+#define DEF_ALWAYS_BCC ""
+extern char *var_always_bcc;
+
+ /*
+ * What to put in the To: header when no recipients were disclosed.
+ *
+ * XXX 2822: When no recipient headers remain, a system should insert a Bcc:
+ * header without additional information. That is not so great given that
+ * MTAs routinely strip Bcc: headers from message headers.
+ */
+#define VAR_RCPT_WITHELD "undisclosed_recipients_header"
+#define DEF_RCPT_WITHELD ""
+extern char *var_rcpt_witheld;
+
+ /*
+ * Add missing headers. Postfix 2.6 no longer adds headers to remote mail by
+ * default.
+ */
+#define VAR_ALWAYS_ADD_HDRS "always_add_missing_headers"
+#define DEF_ALWAYS_ADD_HDRS 0
+extern bool var_always_add_hdrs;
+
+ /*
+ * Dropping message headers.
+ */
+#define VAR_DROP_HDRS "message_drop_headers"
+#define DEF_DROP_HDRS "bcc, content-length, resent-bcc, return-path"
+extern char *var_drop_hdrs;
+
+ /*
+ * From: header format: we provide canned versions only, no Sendmail-style
+ * macro expansions.
+ */
+#define HFROM_FORMAT_NAME_STD "standard" /* From: name <address> */
+#define HFROM_FORMAT_NAME_OBS "obsolete" /* From: address (name) */
+#define VAR_HFROM_FORMAT "header_from_format"
+#define DEF_HFROM_FORMAT HFROM_FORMAT_NAME_STD
+extern char *var_hfrom_format;
+
+ /*
+ * Standards violation: allow/permit RFC 822-style addresses in SMTP
+ * commands.
+ */
+#define VAR_STRICT_RFC821_ENV "strict_rfc821_envelopes"
+#define DEF_STRICT_RFC821_ENV 0
+extern bool var_strict_rfc821_env;
+
+ /*
+ * Standards violation: send "250 AUTH=list" in order to accommodate clients
+ * that implement an old version of the protocol.
+ */
+#define VAR_BROKEN_AUTH_CLNTS "broken_sasl_auth_clients"
+#define DEF_BROKEN_AUTH_CLNTS 0
+extern bool var_broken_auth_clients;
+
+ /*
+ * Standards violation: disable VRFY.
+ */
+#define VAR_DISABLE_VRFY_CMD "disable_vrfy_command"
+#define DEF_DISABLE_VRFY_CMD 0
+extern bool var_disable_vrfy_cmd;
+
+ /*
+ * trivial rewrite/resolve service: mapping tables.
+ */
+#define VAR_VIRT_ALIAS_MAPS "virtual_alias_maps"
+#define DEF_VIRT_ALIAS_MAPS "$virtual_maps" /* Compatibility! */
+extern char *var_virt_alias_maps;
+
+#define VAR_VIRT_ALIAS_DOMS "virtual_alias_domains"
+#define DEF_VIRT_ALIAS_DOMS "$virtual_alias_maps"
+extern char *var_virt_alias_doms;
+
+#define VAR_VIRT_ALIAS_CODE "unknown_virtual_alias_reject_code"
+#define DEF_VIRT_ALIAS_CODE 550
+extern int var_virt_alias_code;
+
+#define VAR_CANONICAL_MAPS "canonical_maps"
+#define DEF_CANONICAL_MAPS ""
+extern char *var_canonical_maps;
+
+#define VAR_SEND_CANON_MAPS "sender_canonical_maps"
+#define DEF_SEND_CANON_MAPS ""
+extern char *var_send_canon_maps;
+
+#define VAR_RCPT_CANON_MAPS "recipient_canonical_maps"
+#define DEF_RCPT_CANON_MAPS ""
+extern char *var_rcpt_canon_maps;
+
+#define CANON_CLASS_ENV_FROM "envelope_sender"
+#define CANON_CLASS_ENV_RCPT "envelope_recipient"
+#define CANON_CLASS_HDR_FROM "header_sender"
+#define CANON_CLASS_HDR_RCPT "header_recipient"
+
+#define VAR_CANON_CLASSES "canonical_classes"
+#define DEF_CANON_CLASSES CANON_CLASS_ENV_FROM ", " \
+ CANON_CLASS_ENV_RCPT ", " \
+ CANON_CLASS_HDR_FROM ", " \
+ CANON_CLASS_HDR_RCPT
+extern char *var_canon_classes;
+
+#define VAR_SEND_CANON_CLASSES "sender_canonical_classes"
+#define DEF_SEND_CANON_CLASSES CANON_CLASS_ENV_FROM ", " \
+ CANON_CLASS_HDR_FROM
+extern char *var_send_canon_classes;
+
+#define VAR_RCPT_CANON_CLASSES "recipient_canonical_classes"
+#define DEF_RCPT_CANON_CLASSES CANON_CLASS_ENV_RCPT ", " \
+ CANON_CLASS_HDR_RCPT
+extern char *var_rcpt_canon_classes;
+
+#define VAR_SEND_BCC_MAPS "sender_bcc_maps"
+#define DEF_SEND_BCC_MAPS ""
+extern char *var_send_bcc_maps;
+
+#define VAR_RCPT_BCC_MAPS "recipient_bcc_maps"
+#define DEF_RCPT_BCC_MAPS ""
+extern char *var_rcpt_bcc_maps;
+
+#define VAR_TRANSPORT_MAPS "transport_maps"
+#define DEF_TRANSPORT_MAPS ""
+extern char *var_transport_maps;
+
+#define VAR_DEF_TRANSPORT "default_transport"
+#define DEF_DEF_TRANSPORT MAIL_SERVICE_SMTP
+extern char *var_def_transport;
+
+#define VAR_SND_DEF_XPORT_MAPS "sender_dependent_" VAR_DEF_TRANSPORT "_maps"
+#define DEF_SND_DEF_XPORT_MAPS ""
+extern char *var_snd_def_xport_maps;
+
+#define VAR_NULL_DEF_XPORT_MAPS_KEY "empty_address_" VAR_DEF_TRANSPORT "_maps_lookup_key"
+#define DEF_NULL_DEF_XPORT_MAPS_KEY "<>"
+extern char *var_null_def_xport_maps_key;
+
+ /*
+ * trivial rewrite/resolve service: rewriting controls.
+ */
+#define VAR_SWAP_BANGPATH "swap_bangpath"
+#define DEF_SWAP_BANGPATH 1
+extern bool var_swap_bangpath;
+
+#define VAR_APP_AT_MYORIGIN "append_at_myorigin"
+#define DEF_APP_AT_MYORIGIN 1
+extern bool var_append_at_myorigin;
+
+#define VAR_APP_DOT_MYDOMAIN "append_dot_mydomain"
+#define DEF_APP_DOT_MYDOMAIN "${{$compatibility_level} <level {1} ? " \
+ "{yes} : {no}}"
+extern bool var_append_dot_mydomain;
+
+#define VAR_PERCENT_HACK "allow_percent_hack"
+#define DEF_PERCENT_HACK 1
+extern bool var_percent_hack;
+
+ /*
+ * Local delivery: alias databases.
+ */
+#define VAR_ALIAS_MAPS "alias_maps"
+#ifdef HAS_NIS
+#define DEF_ALIAS_MAPS ALIAS_DB_MAP ", nis:mail.aliases"
+#else
+#define DEF_ALIAS_MAPS ALIAS_DB_MAP
+#endif
+extern char *var_alias_maps;
+
+ /*
+ * Local delivery: to BIFF or not to BIFF.
+ */
+#define VAR_BIFF "biff"
+#define DEF_BIFF 1
+extern bool var_biff;
+
+ /*
+ * Local delivery: mail to files/commands.
+ */
+#define VAR_ALLOW_COMMANDS "allow_mail_to_commands"
+#define DEF_ALLOW_COMMANDS "alias, forward"
+extern char *var_allow_commands;
+
+#define VAR_COMMAND_MAXTIME "command_time_limit"
+#define _MAXTIME "_time_limit"
+#define DEF_COMMAND_MAXTIME "1000s"
+extern int var_command_maxtime;
+
+#define VAR_ALLOW_FILES "allow_mail_to_files"
+#define DEF_ALLOW_FILES "alias, forward"
+extern char *var_allow_files;
+
+#define VAR_LOCAL_CMD_SHELL "local_command_shell"
+#define DEF_LOCAL_CMD_SHELL ""
+extern char *var_local_cmd_shell;
+
+#define VAR_ALIAS_DB_MAP "alias_database"
+#define DEF_ALIAS_DB_MAP ALIAS_DB_MAP /* sys_defs.h */
+extern char *var_alias_db_map;
+
+#define VAR_LUSER_RELAY "luser_relay"
+#define DEF_LUSER_RELAY ""
+extern char *var_luser_relay;
+
+ /*
+ * Local delivery: mailbox delivery.
+ */
+#define VAR_MAIL_SPOOL_DIR "mail_spool_directory"
+#ifndef DEF_MAIL_SPOOL_DIR
+#define DEF_MAIL_SPOOL_DIR _PATH_MAILDIR
+#endif
+extern char *var_mail_spool_dir;
+
+#define VAR_HOME_MAILBOX "home_mailbox"
+#define DEF_HOME_MAILBOX ""
+extern char *var_home_mailbox;
+
+#define VAR_MAILBOX_COMMAND "mailbox_command"
+#define DEF_MAILBOX_COMMAND ""
+extern char *var_mailbox_command;
+
+#define VAR_MAILBOX_CMD_MAPS "mailbox_command_maps"
+#define DEF_MAILBOX_CMD_MAPS ""
+extern char *var_mailbox_cmd_maps;
+
+#define VAR_MAILBOX_TRANSP "mailbox_transport"
+#define DEF_MAILBOX_TRANSP ""
+extern char *var_mailbox_transport;
+
+#define VAR_MBOX_TRANSP_MAPS "mailbox_transport_maps"
+#define DEF_MBOX_TRANSP_MAPS ""
+extern char *var_mbox_transp_maps;
+
+#define VAR_FALLBACK_TRANSP "fallback_transport"
+#define DEF_FALLBACK_TRANSP ""
+extern char *var_fallback_transport;
+
+#define VAR_FBCK_TRANSP_MAPS "fallback_transport_maps"
+#define DEF_FBCK_TRANSP_MAPS ""
+extern char *var_fbck_transp_maps;
+
+ /*
+ * Local delivery: path to per-user forwarding file.
+ */
+#define VAR_FORWARD_PATH "forward_path"
+#define DEF_FORWARD_PATH "$home/.forward${recipient_delimiter}${extension}, $home/.forward"
+extern char *var_forward_path;
+
+ /*
+ * Local delivery: external command execution directory.
+ */
+#define VAR_EXEC_DIRECTORY "command_execution_directory"
+#define DEF_EXEC_DIRECTORY ""
+extern char *var_exec_directory;
+
+#define VAR_EXEC_EXP_FILTER "execution_directory_expansion_filter"
+#define DEF_EXEC_EXP_FILTER "1234567890!@%-_=+:,./\
+abcdefghijklmnopqrstuvwxyz\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+extern char *var_exec_exp_filter;
+
+ /*
+ * Mailbox locking. DEF_MAILBOX_LOCK is defined in sys_defs.h.
+ */
+#define VAR_MAILBOX_LOCK "mailbox_delivery_lock"
+extern char *var_mailbox_lock;
+
+ /*
+ * Mailbox size limit. This used to be enforced as a side effect of the way
+ * the message size limit is implemented, but that is not clean.
+ */
+#define VAR_MAILBOX_LIMIT "mailbox_size_limit"
+#define DEF_MAILBOX_LIMIT (DEF_MESSAGE_LIMIT * 5)
+extern long var_mailbox_limit;
+
+ /*
+ * Miscellaneous.
+ */
+#define VAR_PROP_EXTENSION "propagate_unmatched_extensions"
+#define DEF_PROP_EXTENSION "canonical, virtual"
+extern char *var_prop_extension;
+
+#define VAR_RCPT_DELIM "recipient_delimiter"
+#define DEF_RCPT_DELIM ""
+extern char *var_rcpt_delim;
+
+#define VAR_CMD_EXP_FILTER "command_expansion_filter"
+#define DEF_CMD_EXP_FILTER "1234567890!@%-_=+:,./\
+abcdefghijklmnopqrstuvwxyz\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+extern char *var_cmd_exp_filter;
+
+#define VAR_FWD_EXP_FILTER "forward_expansion_filter"
+#define DEF_FWD_EXP_FILTER "1234567890!@%-_=+:,./\
+abcdefghijklmnopqrstuvwxyz\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+extern char *var_fwd_exp_filter;
+
+#define VAR_DELIVER_HDR "prepend_delivered_header"
+#define DEF_DELIVER_HDR "command, file, forward"
+extern char *var_deliver_hdr;
+
+ /*
+ * Cleanup: enable support for X-Original-To message headers, which are
+ * needed for multi-recipient mailboxes. When this is turned on, perform
+ * duplicate elimination on (original rcpt, rewritten rcpt) pairs, and
+ * generating non-empty original recipient records in the queue file.
+ */
+#define VAR_ENABLE_ORCPT "enable_original_recipient"
+#define DEF_ENABLE_ORCPT 1
+extern bool var_enable_orcpt;
+
+#define VAR_EXP_OWN_ALIAS "expand_owner_alias"
+#define DEF_EXP_OWN_ALIAS 0
+extern bool var_exp_own_alias;
+
+#define VAR_STAT_HOME_DIR "require_home_directory"
+#define DEF_STAT_HOME_DIR 0
+extern bool var_stat_home_dir;
+
+ /*
+ * Cleanup server: maximal size of the duplicate expansion filter. By
+ * default, we do graceful degradation with huge mailing lists.
+ */
+#define VAR_DUP_FILTER_LIMIT "duplicate_filter_limit"
+#define DEF_DUP_FILTER_LIMIT 1000
+extern int var_dup_filter_limit;
+
+ /*
+ * Transport Layer Security (TLS) protocol support.
+ */
+#define VAR_TLS_MGR_SERVICE "tlsmgr_service_name"
+#define DEF_TLS_MGR_SERVICE "tlsmgr"
+extern char *var_tls_mgr_service;
+
+#define VAR_TLS_APPEND_DEF_CA "tls_append_default_CA"
+#define DEF_TLS_APPEND_DEF_CA 0 /* Postfix < 2.8 BC break */
+extern bool var_tls_append_def_CA;
+
+#define VAR_TLS_RAND_EXCH_NAME "tls_random_exchange_name"
+#define DEF_TLS_RAND_EXCH_NAME "${data_directory}/prng_exch"
+extern char *var_tls_rand_exch_name;
+
+#define VAR_TLS_RAND_SOURCE "tls_random_source"
+#ifdef PREFERRED_RAND_SOURCE
+#define DEF_TLS_RAND_SOURCE PREFERRED_RAND_SOURCE
+#else
+#define DEF_TLS_RAND_SOURCE ""
+#endif
+extern char *var_tls_rand_source;
+
+#define VAR_TLS_RAND_BYTES "tls_random_bytes"
+#define DEF_TLS_RAND_BYTES 32
+extern int var_tls_rand_bytes;
+
+#define VAR_TLS_DAEMON_RAND_BYTES "tls_daemon_random_bytes"
+#define DEF_TLS_DAEMON_RAND_BYTES 32
+extern int var_tls_daemon_rand_bytes;
+
+#define VAR_TLS_RESEED_PERIOD "tls_random_reseed_period"
+#define DEF_TLS_RESEED_PERIOD "3600s"
+extern int var_tls_reseed_period;
+
+#define VAR_TLS_PRNG_UPD_PERIOD "tls_random_prng_update_period"
+#define DEF_TLS_PRNG_UPD_PERIOD "3600s"
+extern int var_tls_prng_upd_period;
+
+ /*
+ * Queue manager: relocated databases.
+ */
+#define VAR_RELOCATED_MAPS "relocated_maps"
+#define DEF_RELOCATED_MAPS ""
+extern char *var_relocated_maps;
+
+ /*
+ * Queue manager: after each failed attempt the backoff time (how long we
+ * won't try this host in seconds) is doubled until it reaches the maximum.
+ * MAX_QUEUE_TIME limits the amount of time a message may spend in the mail
+ * queue before it is sent back.
+ */
+#define VAR_QUEUE_RUN_DELAY "queue_run_delay"
+#define DEF_QUEUE_RUN_DELAY "300s"
+
+#define VAR_MIN_BACKOFF_TIME "minimal_backoff_time"
+#define DEF_MIN_BACKOFF_TIME DEF_QUEUE_RUN_DELAY
+extern int var_min_backoff_time;
+
+#define VAR_MAX_BACKOFF_TIME "maximal_backoff_time"
+#define DEF_MAX_BACKOFF_TIME "4000s"
+extern int var_max_backoff_time;
+
+#define VAR_MAX_QUEUE_TIME "maximal_queue_lifetime"
+#define DEF_MAX_QUEUE_TIME "5d"
+extern int var_max_queue_time;
+
+ /*
+ * XXX The default can't be $maximal_queue_lifetime, because that panics
+ * when a non-default maximal_queue_lifetime setting contains no time unit.
+ */
+#define VAR_DSN_QUEUE_TIME "bounce_queue_lifetime"
+#define DEF_DSN_QUEUE_TIME "5d"
+extern int var_dsn_queue_time;
+
+#define VAR_DELAY_WARN_TIME "delay_warning_time"
+#define DEF_DELAY_WARN_TIME "0h"
+extern int var_delay_warn_time;
+
+#define VAR_DSN_DELAY_CLEARED "confirm_delay_cleared"
+#define DEF_DSN_DELAY_CLEARED 0
+extern int var_dsn_delay_cleared;
+
+ /*
+ * Queue manager: various in-core message and recipient limits.
+ */
+#define VAR_QMGR_ACT_LIMIT "qmgr_message_active_limit"
+#define DEF_QMGR_ACT_LIMIT 20000
+extern int var_qmgr_active_limit;
+
+#define VAR_QMGR_RCPT_LIMIT "qmgr_message_recipient_limit"
+#define DEF_QMGR_RCPT_LIMIT 20000
+extern int var_qmgr_rcpt_limit;
+
+#define VAR_QMGR_MSG_RCPT_LIMIT "qmgr_message_recipient_minimum"
+#define DEF_QMGR_MSG_RCPT_LIMIT 10
+extern int var_qmgr_msg_rcpt_limit;
+
+#define VAR_XPORT_RCPT_LIMIT "default_recipient_limit"
+#define _XPORT_RCPT_LIMIT "_recipient_limit"
+#define DEF_XPORT_RCPT_LIMIT 20000
+extern int var_xport_rcpt_limit;
+
+#define VAR_STACK_RCPT_LIMIT "default_extra_recipient_limit"
+#define _STACK_RCPT_LIMIT "_extra_recipient_limit"
+#define DEF_STACK_RCPT_LIMIT 1000
+extern int var_stack_rcpt_limit;
+
+#define VAR_XPORT_REFILL_LIMIT "default_recipient_refill_limit"
+#define _XPORT_REFILL_LIMIT "_recipient_refill_limit"
+#define DEF_XPORT_REFILL_LIMIT 100
+extern int var_xport_refill_limit;
+
+#define VAR_XPORT_REFILL_DELAY "default_recipient_refill_delay"
+#define _XPORT_REFILL_DELAY "_recipient_refill_delay"
+#define DEF_XPORT_REFILL_DELAY "5s"
+extern int var_xport_refill_delay;
+
+ /*
+ * Queue manager: default job scheduler parameters.
+ */
+#define VAR_DELIVERY_SLOT_COST "default_delivery_slot_cost"
+#define _DELIVERY_SLOT_COST "_delivery_slot_cost"
+#define DEF_DELIVERY_SLOT_COST 5
+extern int var_delivery_slot_cost;
+
+#define VAR_DELIVERY_SLOT_LOAN "default_delivery_slot_loan"
+#define _DELIVERY_SLOT_LOAN "_delivery_slot_loan"
+#define DEF_DELIVERY_SLOT_LOAN 3
+extern int var_delivery_slot_loan;
+
+#define VAR_DELIVERY_SLOT_DISCOUNT "default_delivery_slot_discount"
+#define _DELIVERY_SLOT_DISCOUNT "_delivery_slot_discount"
+#define DEF_DELIVERY_SLOT_DISCOUNT 50
+extern int var_delivery_slot_discount;
+
+#define VAR_MIN_DELIVERY_SLOTS "default_minimum_delivery_slots"
+#define _MIN_DELIVERY_SLOTS "_minimum_delivery_slots"
+#define DEF_MIN_DELIVERY_SLOTS 3
+extern int var_min_delivery_slots;
+
+#define VAR_QMGR_FUDGE "qmgr_fudge_factor"
+#define DEF_QMGR_FUDGE 100
+extern int var_qmgr_fudge;
+
+ /*
+ * Queue manager: default destination concurrency levels.
+ */
+#define VAR_INIT_DEST_CON "initial_destination_concurrency"
+#define _INIT_DEST_CON "_initial_destination_concurrency"
+#define DEF_INIT_DEST_CON 5
+extern int var_init_dest_concurrency;
+
+#define VAR_DEST_CON_LIMIT "default_destination_concurrency_limit"
+#define _DEST_CON_LIMIT "_destination_concurrency_limit"
+#define DEF_DEST_CON_LIMIT 20
+extern int var_dest_con_limit;
+
+#define VAR_LOCAL_CON_LIMIT "local" _DEST_CON_LIMIT
+#define DEF_LOCAL_CON_LIMIT 2
+extern int var_local_con_lim;
+
+ /*
+ * Queue manager: default number of recipients per transaction.
+ */
+#define VAR_DEST_RCPT_LIMIT "default_destination_recipient_limit"
+#define _DEST_RCPT_LIMIT "_destination_recipient_limit"
+#define DEF_DEST_RCPT_LIMIT 50
+extern int var_dest_rcpt_limit;
+
+#define VAR_LOCAL_RCPT_LIMIT "local" _DEST_RCPT_LIMIT /* XXX */
+#define DEF_LOCAL_RCPT_LIMIT 1 /* XXX */
+extern int var_local_rcpt_lim;
+
+ /*
+ * Queue manager: default delay before retrying a dead transport.
+ */
+#define VAR_XPORT_RETRY_TIME "transport_retry_time"
+#define DEF_XPORT_RETRY_TIME "60s"
+extern int var_transport_retry_time;
+
+ /*
+ * Queue manager: what transports to defer delivery to.
+ */
+#define VAR_DEFER_XPORTS "defer_transports"
+#define DEF_DEFER_XPORTS ""
+extern char *var_defer_xports;
+
+ /*
+ * Queue manager: how often to warn that a destination is clogging the
+ * active queue.
+ */
+#define VAR_QMGR_CLOG_WARN_TIME "qmgr_clog_warn_time"
+#define DEF_QMGR_CLOG_WARN_TIME "300s"
+extern int var_qmgr_clog_warn_time;
+
+ /*
+ * Master: default process count limit per mail subsystem.
+ */
+#define VAR_PROC_LIMIT "default_process_limit"
+#define DEF_PROC_LIMIT 100
+extern int var_proc_limit;
+
+ /*
+ * Master: default time to wait after service is throttled.
+ */
+#define VAR_THROTTLE_TIME "service_throttle_time"
+#define DEF_THROTTLE_TIME "60s"
+extern int var_throttle_time;
+
+ /*
+ * Master: what master.cf services are turned off.
+ */
+#define VAR_MASTER_DISABLE "master_service_disable"
+#define DEF_MASTER_DISABLE ""
+extern char *var_master_disable;
+
+ /*
+ * Any subsystem: default maximum number of clients serviced before a mail
+ * subsystem terminates (except queue manager).
+ */
+#define VAR_MAX_USE "max_use"
+#define DEF_MAX_USE 100
+extern int var_use_limit;
+
+ /*
+ * Any subsystem: default amount of time a mail subsystem waits for a client
+ * connection (except queue manager).
+ */
+#define VAR_MAX_IDLE "max_idle"
+#define DEF_MAX_IDLE "100s"
+extern int var_idle_limit;
+
+ /*
+ * Any subsystem: default amount of time a mail subsystem waits for
+ * application events to drain.
+ */
+#define VAR_EVENT_DRAIN "application_event_drain_time"
+#define DEF_EVENT_DRAIN "100s"
+extern int var_event_drain;
+
+ /*
+ * Any subsystem: default amount of time a mail subsystem keeps an internal
+ * IPC connection before closing it because it is idle for too much time.
+ */
+#define VAR_IPC_IDLE "ipc_idle"
+#define DEF_IPC_IDLE "5s"
+extern int var_ipc_idle_limit;
+
+ /*
+ * Any subsystem: default amount of time a mail subsystem keeps an internal
+ * IPC connection before closing it because the connection has existed for
+ * too much time.
+ */
+#define VAR_IPC_TTL "ipc_ttl"
+#define DEF_IPC_TTL "1000s"
+extern int var_ipc_ttl_limit;
+
+ /*
+ * Any front-end subsystem: avoid running out of memory when someone sends
+ * infinitely-long requests or replies.
+ */
+#define VAR_LINE_LIMIT "line_length_limit"
+#define DEF_LINE_LIMIT 2048
+extern int var_line_limit;
+
+ /*
+ * Specify what SMTP peers need verbose logging.
+ */
+#define VAR_DEBUG_PEER_LIST "debug_peer_list"
+#define DEF_DEBUG_PEER_LIST ""
+extern char *var_debug_peer_list;
+
+#define VAR_DEBUG_PEER_LEVEL "debug_peer_level"
+#define DEF_DEBUG_PEER_LEVEL 2
+extern int var_debug_peer_level;
+
+ /*
+ * Queue management: what queues are hashed behind a forest of
+ * subdirectories, and how deep the forest is.
+ */
+#define VAR_HASH_QUEUE_NAMES "hash_queue_names"
+#define DEF_HASH_QUEUE_NAMES "deferred, defer"
+extern char *var_hash_queue_names;
+
+#define VAR_HASH_QUEUE_DEPTH "hash_queue_depth"
+#define DEF_HASH_QUEUE_DEPTH 1
+extern int var_hash_queue_depth;
+
+ /*
+ * Short queue IDs contain the time in microseconds and file inode number.
+ * Long queue IDs also contain the time in seconds.
+ */
+#define VAR_LONG_QUEUE_IDS "enable_long_queue_ids"
+#define DEF_LONG_QUEUE_IDS 0
+extern bool var_long_queue_ids;
+
+ /*
+ * Multi-protocol support.
+ */
+#define INET_PROTO_NAME_IPV4 "ipv4"
+#define INET_PROTO_NAME_IPV6 "ipv6"
+#define INET_PROTO_NAME_ALL "all"
+#define INET_PROTO_NAME_ANY "any"
+#define VAR_INET_PROTOCOLS "inet_protocols"
+extern char *var_inet_protocols;
+
+ /*
+ * SMTP client. Timeouts inspired by RFC 1123. The SMTP recipient limit
+ * determines how many recipient addresses the SMTP client sends along with
+ * each message. Unfortunately, some mailers misbehave and disconnect (smap)
+ * when given more recipients than they are willing to handle.
+ *
+ * XXX 2821: A mail system is supposed to use EHLO instead of HELO, and to fall
+ * back to HELO if EHLO is not supported.
+ */
+#define VAR_BESTMX_TRANSP "best_mx_transport"
+#define DEF_BESTMX_TRANSP ""
+extern char *var_bestmx_transp;
+
+#define VAR_SMTP_CACHE_CONNT "smtp_connection_cache_time_limit"
+#define DEF_SMTP_CACHE_CONNT "2s"
+#define VAR_LMTP_CACHE_CONNT "lmtp_connection_cache_time_limit"
+#define DEF_LMTP_CACHE_CONNT "2s"
+extern int var_smtp_cache_conn;
+
+#define VAR_SMTP_REUSE_COUNT "smtp_connection_reuse_count_limit"
+#define DEF_SMTP_REUSE_COUNT 0
+#define VAR_LMTP_REUSE_COUNT "lmtp_connection_reuse_count_limit"
+#define DEF_LMTP_REUSE_COUNT 0
+extern int var_smtp_reuse_count;
+
+#define VAR_SMTP_REUSE_TIME "smtp_connection_reuse_time_limit"
+#define DEF_SMTP_REUSE_TIME "300s"
+#define VAR_LMTP_REUSE_TIME "lmtp_connection_reuse_time_limit"
+#define DEF_LMTP_REUSE_TIME "300s"
+extern int var_smtp_reuse_time;
+
+#define VAR_SMTP_CACHE_DEST "smtp_connection_cache_destinations"
+#define DEF_SMTP_CACHE_DEST ""
+#define VAR_LMTP_CACHE_DEST "lmtp_connection_cache_destinations"
+#define DEF_LMTP_CACHE_DEST ""
+extern char *var_smtp_cache_dest;
+
+#define VAR_SMTP_CACHE_DEMAND "smtp_connection_cache_on_demand"
+#ifndef DEF_SMTP_CACHE_DEMAND
+#define DEF_SMTP_CACHE_DEMAND 1
+#endif
+#define VAR_LMTP_CACHE_DEMAND "lmtp_connection_cache_on_demand"
+#ifndef DEF_LMTP_CACHE_DEMAND
+#define DEF_LMTP_CACHE_DEMAND 1
+#endif
+extern bool var_smtp_cache_demand;
+
+#define VAR_SMTP_CONN_TMOUT "smtp_connect_timeout"
+#define DEF_SMTP_CONN_TMOUT "30s"
+extern int var_smtp_conn_tmout;
+
+#define VAR_SMTP_HELO_TMOUT "smtp_helo_timeout"
+#define DEF_SMTP_HELO_TMOUT "300s"
+#define VAR_LMTP_HELO_TMOUT "lmtp_lhlo_timeout"
+#define DEF_LMTP_HELO_TMOUT "300s"
+extern int var_smtp_helo_tmout;
+
+#define VAR_SMTP_XFWD_TMOUT "smtp_xforward_timeout"
+#define DEF_SMTP_XFWD_TMOUT "300s"
+extern int var_smtp_xfwd_tmout;
+
+#define VAR_SMTP_STARTTLS_TMOUT "smtp_starttls_timeout"
+#define DEF_SMTP_STARTTLS_TMOUT "300s"
+#define VAR_LMTP_STARTTLS_TMOUT "lmtp_starttls_timeout"
+#define DEF_LMTP_STARTTLS_TMOUT "300s"
+extern int var_smtp_starttls_tmout;
+
+#define VAR_SMTP_MAIL_TMOUT "smtp_mail_timeout"
+#define DEF_SMTP_MAIL_TMOUT "300s"
+extern int var_smtp_mail_tmout;
+
+#define VAR_SMTP_RCPT_TMOUT "smtp_rcpt_timeout"
+#define DEF_SMTP_RCPT_TMOUT "300s"
+extern int var_smtp_rcpt_tmout;
+
+#define VAR_SMTP_DATA0_TMOUT "smtp_data_init_timeout"
+#define DEF_SMTP_DATA0_TMOUT "120s"
+extern int var_smtp_data0_tmout;
+
+#define VAR_SMTP_DATA1_TMOUT "smtp_data_xfer_timeout"
+#define DEF_SMTP_DATA1_TMOUT "180s"
+extern int var_smtp_data1_tmout;
+
+#define VAR_SMTP_DATA2_TMOUT "smtp_data_done_timeout"
+#define DEF_SMTP_DATA2_TMOUT "600s"
+extern int var_smtp_data2_tmout;
+
+#define VAR_SMTP_RSET_TMOUT "smtp_rset_timeout"
+#define DEF_SMTP_RSET_TMOUT "20s"
+extern int var_smtp_rset_tmout;
+
+#define VAR_SMTP_QUIT_TMOUT "smtp_quit_timeout"
+#define DEF_SMTP_QUIT_TMOUT "300s"
+extern int var_smtp_quit_tmout;
+
+#define VAR_SMTP_QUOTE_821_ENV "smtp_quote_rfc821_envelope"
+#define DEF_SMTP_QUOTE_821_ENV 1
+#define VAR_LMTP_QUOTE_821_ENV "lmtp_quote_rfc821_envelope"
+#define DEF_LMTP_QUOTE_821_ENV 1
+extern int var_smtp_quote_821_env;
+
+#define VAR_SMTP_SKIP_5XX "smtp_skip_5xx_greeting"
+#define DEF_SMTP_SKIP_5XX 1
+#define VAR_LMTP_SKIP_5XX "lmtp_skip_5xx_greeting"
+#define DEF_LMTP_SKIP_5XX 1
+extern bool var_smtp_skip_5xx_greeting;
+
+#define VAR_IGN_MX_LOOKUP_ERR "ignore_mx_lookup_error"
+#define DEF_IGN_MX_LOOKUP_ERR 0
+extern bool var_ign_mx_lookup_err;
+
+#define VAR_SMTP_SKIP_QUIT_RESP "smtp_skip_quit_response"
+#define DEF_SMTP_SKIP_QUIT_RESP 1
+extern bool var_skip_quit_resp;
+
+#define VAR_SMTP_ALWAYS_EHLO "smtp_always_send_ehlo"
+#ifdef RFC821_SYNTAX
+#define DEF_SMTP_ALWAYS_EHLO 0
+#else
+#define DEF_SMTP_ALWAYS_EHLO 1
+#endif
+extern bool var_smtp_always_ehlo;
+
+#define VAR_SMTP_NEVER_EHLO "smtp_never_send_ehlo"
+#define DEF_SMTP_NEVER_EHLO 0
+extern bool var_smtp_never_ehlo;
+
+#define VAR_SMTP_RESP_FILTER "smtp_reply_filter"
+#define DEF_SMTP_RESP_FILTER ""
+#define VAR_LMTP_RESP_FILTER "lmtp_reply_filter"
+#define DEF_LMTP_RESP_FILTER ""
+extern char *var_smtp_resp_filter;
+
+#define VAR_SMTP_BIND_ADDR "smtp_bind_address"
+#define DEF_SMTP_BIND_ADDR ""
+#define VAR_LMTP_BIND_ADDR "lmtp_bind_address"
+#define DEF_LMTP_BIND_ADDR ""
+extern char *var_smtp_bind_addr;
+
+#define VAR_SMTP_BIND_ADDR6 "smtp_bind_address6"
+#define DEF_SMTP_BIND_ADDR6 ""
+#define VAR_LMTP_BIND_ADDR6 "lmtp_bind_address6"
+#define DEF_LMTP_BIND_ADDR6 ""
+extern char *var_smtp_bind_addr6;
+
+#define VAR_SMTP_BIND_ADDR_ENFORCE "smtp_bind_address_enforce"
+#define DEF_SMTP_BIND_ADDR_ENFORCE 0
+#define VAR_LMTP_BIND_ADDR_ENFORCE "lmtp_bind_address_enforce"
+#define DEF_LMTP_BIND_ADDR_ENFORCE 0
+extern bool var_smtp_bind_addr_enforce;
+
+#define VAR_SMTP_HELO_NAME "smtp_helo_name"
+#define DEF_SMTP_HELO_NAME "$myhostname"
+#define VAR_LMTP_HELO_NAME "lmtp_lhlo_name"
+#define DEF_LMTP_HELO_NAME "$myhostname"
+extern char *var_smtp_helo_name;
+
+#define VAR_SMTP_RAND_ADDR "smtp_randomize_addresses"
+#define DEF_SMTP_RAND_ADDR 1
+#define VAR_LMTP_RAND_ADDR "lmtp_randomize_addresses"
+#define DEF_LMTP_RAND_ADDR 1
+extern bool var_smtp_rand_addr;
+
+#define VAR_SMTP_LINE_LIMIT "smtp_line_length_limit"
+#define DEF_SMTP_LINE_LIMIT 998
+#define VAR_LMTP_LINE_LIMIT "lmtp_line_length_limit"
+#define DEF_LMTP_LINE_LIMIT 998
+extern int var_smtp_line_limit;
+
+#define VAR_SMTP_PIX_THRESH "smtp_pix_workaround_threshold_time"
+#define DEF_SMTP_PIX_THRESH "500s"
+#define VAR_LMTP_PIX_THRESH "lmtp_pix_workaround_threshold_time"
+#define DEF_LMTP_PIX_THRESH "500s"
+extern int var_smtp_pix_thresh;
+
+#define VAR_SMTP_PIX_DELAY "smtp_pix_workaround_delay_time"
+#define DEF_SMTP_PIX_DELAY "10s"
+#define VAR_LMTP_PIX_DELAY "lmtp_pix_workaround_delay_time"
+#define DEF_LMTP_PIX_DELAY "10s"
+extern int var_smtp_pix_delay;
+
+ /*
+ * Courageous people may want to turn off PIX bug workarounds.
+ */
+#define PIX_BUG_DISABLE_ESMTP "disable_esmtp"
+#define PIX_BUG_DELAY_DOTCRLF "delay_dotcrlf"
+#define VAR_SMTP_PIX_BUG_WORDS "smtp_pix_workarounds"
+#define DEF_SMTP_PIX_BUG_WORDS PIX_BUG_DISABLE_ESMTP "," \
+ PIX_BUG_DELAY_DOTCRLF
+#define VAR_LMTP_PIX_BUG_WORDS "lmtp_pix_workarounds"
+#define DEF_LMTP_PIX_BUG_WORDS DEF_SMTP_PIX_BUG_WORDS
+extern char *var_smtp_pix_bug_words;
+
+#define VAR_SMTP_PIX_BUG_MAPS "smtp_pix_workaround_maps"
+#define DEF_SMTP_PIX_BUG_MAPS ""
+#define VAR_LMTP_PIX_BUG_MAPS "lmtp_pix_workaround_maps"
+#define DEF_LMTP_PIX_BUG_MAPS ""
+extern char *var_smtp_pix_bug_maps;
+
+#define VAR_SMTP_DEFER_MXADDR "smtp_defer_if_no_mx_address_found"
+#define DEF_SMTP_DEFER_MXADDR 0
+#define VAR_LMTP_DEFER_MXADDR "lmtp_defer_if_no_mx_address_found"
+#define DEF_LMTP_DEFER_MXADDR 0
+extern bool var_smtp_defer_mxaddr;
+
+#define VAR_SMTP_SEND_XFORWARD "smtp_send_xforward_command"
+#define DEF_SMTP_SEND_XFORWARD 0
+extern bool var_smtp_send_xforward;
+
+#define VAR_SMTP_GENERIC_MAPS "smtp_generic_maps"
+#define DEF_SMTP_GENERIC_MAPS ""
+#define VAR_LMTP_GENERIC_MAPS "lmtp_generic_maps"
+#define DEF_LMTP_GENERIC_MAPS ""
+extern char *var_smtp_generic_maps;
+
+ /*
+ * SMTP server. The soft error limit determines how many errors an SMTP
+ * client may make before we start to slow down; the hard error limit
+ * determines after how many client errors we disconnect.
+ */
+#define VAR_SMTPD_BANNER "smtpd_banner"
+#define DEF_SMTPD_BANNER "$myhostname ESMTP $mail_name"
+extern char *var_smtpd_banner;
+
+#define VAR_SMTPD_TMOUT "smtpd_timeout"
+#define DEF_SMTPD_TMOUT "${stress?{10}:{300}}s"
+extern int var_smtpd_tmout;
+
+#define VAR_SMTPD_STARTTLS_TMOUT "smtpd_starttls_timeout"
+#define DEF_SMTPD_STARTTLS_TMOUT "${stress?{10}:{300}}s"
+extern int var_smtpd_starttls_tmout;
+
+#define VAR_SMTPD_RCPT_LIMIT "smtpd_recipient_limit"
+#define DEF_SMTPD_RCPT_LIMIT 1000
+extern int var_smtpd_rcpt_limit;
+
+#define VAR_SMTPD_SOFT_ERLIM "smtpd_soft_error_limit"
+#define DEF_SMTPD_SOFT_ERLIM "10"
+extern int var_smtpd_soft_erlim;
+
+#define VAR_SMTPD_HARD_ERLIM "smtpd_hard_error_limit"
+#define DEF_SMTPD_HARD_ERLIM "${stress?{1}:{20}}"
+extern int var_smtpd_hard_erlim;
+
+#define VAR_SMTPD_ERR_SLEEP "smtpd_error_sleep_time"
+#define DEF_SMTPD_ERR_SLEEP "1s"
+extern int var_smtpd_err_sleep;
+
+#define VAR_SMTPD_JUNK_CMD "smtpd_junk_command_limit"
+#define DEF_SMTPD_JUNK_CMD "${stress?{1}:{100}}"
+extern int var_smtpd_junk_cmd_limit;
+
+#define VAR_SMTPD_RCPT_OVERLIM "smtpd_recipient_overshoot_limit"
+#define DEF_SMTPD_RCPT_OVERLIM 1000
+extern int var_smtpd_rcpt_overlim;
+
+#define VAR_SMTPD_HIST_THRSH "smtpd_history_flush_threshold"
+#define DEF_SMTPD_HIST_THRSH 100
+extern int var_smtpd_hist_thrsh;
+
+#define VAR_SMTPD_NOOP_CMDS "smtpd_noop_commands"
+#define DEF_SMTPD_NOOP_CMDS ""
+extern char *var_smtpd_noop_cmds;
+
+#define VAR_SMTPD_FORBID_CMDS "smtpd_forbidden_commands"
+#define DEF_SMTPD_FORBID_CMDS "CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}}"
+extern char *var_smtpd_forbid_cmds;
+
+#define VAR_SMTPD_CMD_FILTER "smtpd_command_filter"
+#define DEF_SMTPD_CMD_FILTER ""
+extern char *var_smtpd_cmd_filter;
+
+#define VAR_SMTPD_TLS_WRAPPER "smtpd_tls_wrappermode"
+#define DEF_SMTPD_TLS_WRAPPER 0
+extern bool var_smtpd_tls_wrappermode;
+
+#define VAR_SMTPD_TLS_LEVEL "smtpd_tls_security_level"
+#define DEF_SMTPD_TLS_LEVEL ""
+extern char *var_smtpd_tls_level;
+
+#define VAR_SMTPD_USE_TLS "smtpd_use_tls"
+#define DEF_SMTPD_USE_TLS 0
+extern bool var_smtpd_use_tls;
+
+#define VAR_SMTPD_ENFORCE_TLS "smtpd_enforce_tls"
+#define DEF_SMTPD_ENFORCE_TLS 0
+extern bool var_smtpd_enforce_tls;
+
+#define VAR_SMTPD_TLS_AUTH_ONLY "smtpd_tls_auth_only"
+#define DEF_SMTPD_TLS_AUTH_ONLY 0
+extern bool var_smtpd_tls_auth_only;
+
+#define VAR_SMTPD_TLS_ACERT "smtpd_tls_ask_ccert"
+#define DEF_SMTPD_TLS_ACERT 0
+extern bool var_smtpd_tls_ask_ccert;
+
+#define VAR_SMTPD_TLS_RCERT "smtpd_tls_req_ccert"
+#define DEF_SMTPD_TLS_RCERT 0
+extern bool var_smtpd_tls_req_ccert;
+
+#define VAR_SMTPD_TLS_CCERT_VD "smtpd_tls_ccert_verifydepth"
+#define DEF_SMTPD_TLS_CCERT_VD 9
+extern int var_smtpd_tls_ccert_vd;
+
+#define VAR_SMTPD_TLS_CHAIN_FILES "smtpd_tls_chain_files"
+#define DEF_SMTPD_TLS_CHAIN_FILES ""
+extern char *var_smtpd_tls_chain_files;
+
+#define VAR_SMTPD_TLS_CERT_FILE "smtpd_tls_cert_file"
+#define DEF_SMTPD_TLS_CERT_FILE ""
+extern char *var_smtpd_tls_cert_file;
+
+#define VAR_SMTPD_TLS_KEY_FILE "smtpd_tls_key_file"
+#define DEF_SMTPD_TLS_KEY_FILE "$smtpd_tls_cert_file"
+extern char *var_smtpd_tls_key_file;
+
+#define VAR_SMTPD_TLS_DCERT_FILE "smtpd_tls_dcert_file"
+#define DEF_SMTPD_TLS_DCERT_FILE ""
+extern char *var_smtpd_tls_dcert_file;
+
+#define VAR_SMTPD_TLS_DKEY_FILE "smtpd_tls_dkey_file"
+#define DEF_SMTPD_TLS_DKEY_FILE "$smtpd_tls_dcert_file"
+extern char *var_smtpd_tls_dkey_file;
+
+#define VAR_SMTPD_TLS_ECCERT_FILE "smtpd_tls_eccert_file"
+#define DEF_SMTPD_TLS_ECCERT_FILE ""
+extern char *var_smtpd_tls_eccert_file;
+
+#define VAR_SMTPD_TLS_ECKEY_FILE "smtpd_tls_eckey_file"
+#define DEF_SMTPD_TLS_ECKEY_FILE "$smtpd_tls_eccert_file"
+extern char *var_smtpd_tls_eckey_file;
+
+#define VAR_SMTPD_TLS_CA_FILE "smtpd_tls_CAfile"
+#define DEF_SMTPD_TLS_CA_FILE ""
+extern char *var_smtpd_tls_CAfile;
+
+#define VAR_SMTPD_TLS_CA_PATH "smtpd_tls_CApath"
+#define DEF_SMTPD_TLS_CA_PATH ""
+extern char *var_smtpd_tls_CApath;
+
+#define VAR_SMTPD_TLS_PROTO "smtpd_tls_protocols"
+#define DEF_SMTPD_TLS_PROTO ">=TLSv1"
+extern char *var_smtpd_tls_proto;
+
+#define VAR_SMTPD_TLS_MAND_PROTO "smtpd_tls_mandatory_protocols"
+#define DEF_SMTPD_TLS_MAND_PROTO ">=TLSv1"
+extern char *var_smtpd_tls_mand_proto;
+
+#define VAR_SMTPD_TLS_CIPH "smtpd_tls_ciphers"
+#define DEF_SMTPD_TLS_CIPH "medium"
+extern char *var_smtpd_tls_ciph;
+
+#define VAR_SMTPD_TLS_MAND_CIPH "smtpd_tls_mandatory_ciphers"
+#define DEF_SMTPD_TLS_MAND_CIPH "medium"
+extern char *var_smtpd_tls_mand_ciph;
+
+#define VAR_SMTPD_TLS_EXCL_CIPH "smtpd_tls_exclude_ciphers"
+#define DEF_SMTPD_TLS_EXCL_CIPH ""
+extern char *var_smtpd_tls_excl_ciph;
+
+#define VAR_SMTPD_TLS_MAND_EXCL "smtpd_tls_mandatory_exclude_ciphers"
+#define DEF_SMTPD_TLS_MAND_EXCL ""
+extern char *var_smtpd_tls_mand_excl;
+
+#define VAR_SMTPD_TLS_FPT_DGST "smtpd_tls_fingerprint_digest"
+#define DEF_SMTPD_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
+ "{md5} : {sha256}}"
+extern char *var_smtpd_tls_fpt_dgst;
+
+#define VAR_SMTPD_TLS_512_FILE "smtpd_tls_dh512_param_file"
+#define DEF_SMTPD_TLS_512_FILE ""
+extern char *var_smtpd_tls_dh512_param_file;
+
+#define VAR_SMTPD_TLS_1024_FILE "smtpd_tls_dh1024_param_file"
+#define DEF_SMTPD_TLS_1024_FILE ""
+extern char *var_smtpd_tls_dh1024_param_file;
+
+#define VAR_SMTPD_TLS_EECDH "smtpd_tls_eecdh_grade"
+#define DEF_SMTPD_TLS_EECDH "auto"
+extern char *var_smtpd_tls_eecdh;
+
+#define VAR_SMTPD_TLS_LOGLEVEL "smtpd_tls_loglevel"
+#define DEF_SMTPD_TLS_LOGLEVEL "0"
+extern char *var_smtpd_tls_loglevel;
+
+#define VAR_SMTPD_TLS_RECHEAD "smtpd_tls_received_header"
+#define DEF_SMTPD_TLS_RECHEAD 0
+extern bool var_smtpd_tls_received_header;
+
+#define VAR_SMTPD_TLS_SCACHE_DB "smtpd_tls_session_cache_database"
+#define DEF_SMTPD_TLS_SCACHE_DB ""
+extern char *var_smtpd_tls_scache_db;
+
+#define MAX_SMTPD_TLS_SCACHETIME 8640000
+#define VAR_SMTPD_TLS_SCACHTIME "smtpd_tls_session_cache_timeout"
+#define DEF_SMTPD_TLS_SCACHTIME "3600s"
+extern int var_smtpd_tls_scache_timeout;
+
+#define VAR_SMTPD_TLS_SET_SESSID "smtpd_tls_always_issue_session_ids"
+#define DEF_SMTPD_TLS_SET_SESSID 1
+extern bool var_smtpd_tls_set_sessid;
+
+#define VAR_SMTPD_DELAY_OPEN "smtpd_delay_open_until_valid_rcpt"
+#define DEF_SMTPD_DELAY_OPEN 1
+extern bool var_smtpd_delay_open;
+
+#define VAR_SMTP_TLS_PER_SITE "smtp_tls_per_site"
+#define DEF_SMTP_TLS_PER_SITE ""
+#define VAR_LMTP_TLS_PER_SITE "lmtp_tls_per_site"
+#define DEF_LMTP_TLS_PER_SITE ""
+extern char *var_smtp_tls_per_site;
+
+#define VAR_SMTP_USE_TLS "smtp_use_tls"
+#define DEF_SMTP_USE_TLS 0
+#define VAR_LMTP_USE_TLS "lmtp_use_tls"
+#define DEF_LMTP_USE_TLS 0
+extern bool var_smtp_use_tls;
+
+#define VAR_SMTP_ENFORCE_TLS "smtp_enforce_tls"
+#define DEF_SMTP_ENFORCE_TLS 0
+#define VAR_LMTP_ENFORCE_TLS "lmtp_enforce_tls"
+#define DEF_LMTP_ENFORCE_TLS 0
+extern bool var_smtp_enforce_tls;
+
+#define VAR_SMTP_TLS_ENFORCE_PN "smtp_tls_enforce_peername"
+#define DEF_SMTP_TLS_ENFORCE_PN 1
+#define VAR_LMTP_TLS_ENFORCE_PN "lmtp_tls_enforce_peername"
+#define DEF_LMTP_TLS_ENFORCE_PN 1
+extern bool var_smtp_tls_enforce_peername;
+
+#define VAR_SMTP_TLS_WRAPPER "smtp_tls_wrappermode"
+#define DEF_SMTP_TLS_WRAPPER 0
+#define VAR_LMTP_TLS_WRAPPER "lmtp_tls_wrappermode"
+#define DEF_LMTP_TLS_WRAPPER 0
+extern bool var_smtp_tls_wrappermode;
+
+#define VAR_SMTP_TLS_LEVEL "smtp_tls_security_level"
+#define DEF_SMTP_TLS_LEVEL ""
+#define VAR_LMTP_TLS_LEVEL "lmtp_tls_security_level"
+#define DEF_LMTP_TLS_LEVEL ""
+extern char *var_smtp_tls_level;
+
+#define VAR_SMTP_TLS_SCERT_VD "smtp_tls_scert_verifydepth"
+#define DEF_SMTP_TLS_SCERT_VD 9
+#define VAR_LMTP_TLS_SCERT_VD "lmtp_tls_scert_verifydepth"
+#define DEF_LMTP_TLS_SCERT_VD 9
+extern int var_smtp_tls_scert_vd;
+
+#define VAR_SMTP_TLS_CHAIN_FILES "smtp_tls_chain_files"
+#define DEF_SMTP_TLS_CHAIN_FILES ""
+#define VAR_LMTP_TLS_CHAIN_FILES "lmtp_tls_chain_files"
+#define DEF_LMTP_TLS_CHAIN_FILES ""
+extern char *var_smtp_tls_chain_files;
+
+#define VAR_SMTP_TLS_CERT_FILE "smtp_tls_cert_file"
+#define DEF_SMTP_TLS_CERT_FILE ""
+#define VAR_LMTP_TLS_CERT_FILE "lmtp_tls_cert_file"
+#define DEF_LMTP_TLS_CERT_FILE ""
+extern char *var_smtp_tls_cert_file;
+
+#define VAR_SMTP_TLS_KEY_FILE "smtp_tls_key_file"
+#define DEF_SMTP_TLS_KEY_FILE "$smtp_tls_cert_file"
+#define VAR_LMTP_TLS_KEY_FILE "lmtp_tls_key_file"
+#define DEF_LMTP_TLS_KEY_FILE "$lmtp_tls_cert_file"
+extern char *var_smtp_tls_key_file;
+
+#define VAR_SMTP_TLS_DCERT_FILE "smtp_tls_dcert_file"
+#define DEF_SMTP_TLS_DCERT_FILE ""
+#define VAR_LMTP_TLS_DCERT_FILE "lmtp_tls_dcert_file"
+#define DEF_LMTP_TLS_DCERT_FILE ""
+extern char *var_smtp_tls_dcert_file;
+
+#define VAR_SMTP_TLS_DKEY_FILE "smtp_tls_dkey_file"
+#define DEF_SMTP_TLS_DKEY_FILE "$smtp_tls_dcert_file"
+#define VAR_LMTP_TLS_DKEY_FILE "lmtp_tls_dkey_file"
+#define DEF_LMTP_TLS_DKEY_FILE "$lmtp_tls_dcert_file"
+extern char *var_smtp_tls_dkey_file;
+
+#define VAR_SMTP_TLS_ECCERT_FILE "smtp_tls_eccert_file"
+#define DEF_SMTP_TLS_ECCERT_FILE ""
+#define VAR_LMTP_TLS_ECCERT_FILE "lmtp_tls_eccert_file"
+#define DEF_LMTP_TLS_ECCERT_FILE ""
+extern char *var_smtp_tls_eccert_file;
+
+#define VAR_SMTP_TLS_ECKEY_FILE "smtp_tls_eckey_file"
+#define DEF_SMTP_TLS_ECKEY_FILE "$smtp_tls_eccert_file"
+#define VAR_LMTP_TLS_ECKEY_FILE "lmtp_tls_eckey_file"
+#define DEF_LMTP_TLS_ECKEY_FILE "$lmtp_tls_eccert_file"
+extern char *var_smtp_tls_eckey_file;
+
+#define VAR_SMTP_TLS_CA_FILE "smtp_tls_CAfile"
+#define DEF_SMTP_TLS_CA_FILE ""
+#define VAR_LMTP_TLS_CA_FILE "lmtp_tls_CAfile"
+#define DEF_LMTP_TLS_CA_FILE ""
+extern char *var_smtp_tls_CAfile;
+
+#define VAR_SMTP_TLS_CA_PATH "smtp_tls_CApath"
+#define DEF_SMTP_TLS_CA_PATH ""
+#define VAR_LMTP_TLS_CA_PATH "lmtp_tls_CApath"
+#define DEF_LMTP_TLS_CA_PATH ""
+extern char *var_smtp_tls_CApath;
+
+#define VAR_SMTP_TLS_CIPH "smtp_tls_ciphers"
+#define DEF_SMTP_TLS_CIPH "medium"
+#define VAR_LMTP_TLS_CIPH "lmtp_tls_ciphers"
+#define DEF_LMTP_TLS_CIPH "medium"
+extern char *var_smtp_tls_ciph;
+
+#define VAR_SMTP_TLS_MAND_CIPH "smtp_tls_mandatory_ciphers"
+#define DEF_SMTP_TLS_MAND_CIPH "medium"
+#define VAR_LMTP_TLS_MAND_CIPH "lmtp_tls_mandatory_ciphers"
+#define DEF_LMTP_TLS_MAND_CIPH "medium"
+extern char *var_smtp_tls_mand_ciph;
+
+#define VAR_SMTP_TLS_EXCL_CIPH "smtp_tls_exclude_ciphers"
+#define DEF_SMTP_TLS_EXCL_CIPH ""
+#define VAR_LMTP_TLS_EXCL_CIPH "lmtp_tls_exclude_ciphers"
+#define DEF_LMTP_TLS_EXCL_CIPH ""
+extern char *var_smtp_tls_excl_ciph;
+
+#define VAR_SMTP_TLS_MAND_EXCL "smtp_tls_mandatory_exclude_ciphers"
+#define DEF_SMTP_TLS_MAND_EXCL ""
+#define VAR_LMTP_TLS_MAND_EXCL "lmtp_tls_mandatory_exclude_ciphers"
+#define DEF_LMTP_TLS_MAND_EXCL ""
+extern char *var_smtp_tls_mand_excl;
+
+#define VAR_SMTP_TLS_FPT_DGST "smtp_tls_fingerprint_digest"
+#define DEF_SMTP_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
+ "{md5} : {sha256}}"
+#define VAR_LMTP_TLS_FPT_DGST "lmtp_tls_fingerprint_digest"
+#define DEF_LMTP_TLS_FPT_DGST "${{$compatibility_level} <level {3.6} ? " \
+ "{md5} : {sha256}}"
+extern char *var_smtp_tls_fpt_dgst;
+
+#define VAR_SMTP_TLS_TAFILE "smtp_tls_trust_anchor_file"
+#define DEF_SMTP_TLS_TAFILE ""
+#define VAR_LMTP_TLS_TAFILE "lmtp_tls_trust_anchor_file"
+#define DEF_LMTP_TLS_TAFILE ""
+extern char *var_smtp_tls_tafile;
+
+#define VAR_SMTP_TLS_LOGLEVEL "smtp_tls_loglevel"
+#define DEF_SMTP_TLS_LOGLEVEL "0"
+#define VAR_LMTP_TLS_LOGLEVEL "lmtp_tls_loglevel"
+#define DEF_LMTP_TLS_LOGLEVEL "0"
+extern char *var_smtp_tls_loglevel; /* In smtp(8) and tlsmgr(8) */
+extern char *var_lmtp_tls_loglevel; /* In tlsmgr(8) */
+
+#define VAR_SMTP_TLS_NOTEOFFER "smtp_tls_note_starttls_offer"
+#define DEF_SMTP_TLS_NOTEOFFER 0
+#define VAR_LMTP_TLS_NOTEOFFER "lmtp_tls_note_starttls_offer"
+#define DEF_LMTP_TLS_NOTEOFFER 0
+extern bool var_smtp_tls_note_starttls_offer;
+
+#define VAR_SMTP_TLS_SCACHE_DB "smtp_tls_session_cache_database"
+#define DEF_SMTP_TLS_SCACHE_DB ""
+#define VAR_LMTP_TLS_SCACHE_DB "lmtp_tls_session_cache_database"
+#define DEF_LMTP_TLS_SCACHE_DB ""
+extern char *var_smtp_tls_scache_db;
+extern char *var_lmtp_tls_scache_db;
+
+#define MAX_SMTP_TLS_SCACHETIME 8640000
+#define VAR_SMTP_TLS_SCACHTIME "smtp_tls_session_cache_timeout"
+#define DEF_SMTP_TLS_SCACHTIME "3600s"
+#define MAX_LMTP_TLS_SCACHETIME 8640000
+#define VAR_LMTP_TLS_SCACHTIME "lmtp_tls_session_cache_timeout"
+#define DEF_LMTP_TLS_SCACHTIME "3600s"
+extern int var_smtp_tls_scache_timeout;
+extern int var_lmtp_tls_scache_timeout;
+
+#define VAR_SMTP_TLS_POLICY "smtp_tls_policy_maps"
+#define DEF_SMTP_TLS_POLICY ""
+#define VAR_LMTP_TLS_POLICY "lmtp_tls_policy_maps"
+#define DEF_LMTP_TLS_POLICY ""
+extern char *var_smtp_tls_policy;
+
+#define VAR_SMTP_TLS_PROTO "smtp_tls_protocols"
+#define DEF_SMTP_TLS_PROTO ">=TLSv1"
+#define VAR_LMTP_TLS_PROTO "lmtp_tls_protocols"
+#define DEF_LMTP_TLS_PROTO ">=TLSv1"
+extern char *var_smtp_tls_proto;
+
+#define VAR_SMTP_TLS_MAND_PROTO "smtp_tls_mandatory_protocols"
+#define DEF_SMTP_TLS_MAND_PROTO ">=TLSv1"
+#define VAR_LMTP_TLS_MAND_PROTO "lmtp_tls_mandatory_protocols"
+#define DEF_LMTP_TLS_MAND_PROTO ">=TLSv1"
+extern char *var_smtp_tls_mand_proto;
+
+#define VAR_SMTP_TLS_VFY_CMATCH "smtp_tls_verify_cert_match"
+#define DEF_SMTP_TLS_VFY_CMATCH "hostname"
+#define VAR_LMTP_TLS_VFY_CMATCH "lmtp_tls_verify_cert_match"
+#define DEF_LMTP_TLS_VFY_CMATCH "hostname"
+extern char *var_smtp_tls_vfy_cmatch;
+
+ /*
+ * There are no MX lookups for LMTP, so verify == secure
+ */
+#define VAR_SMTP_TLS_SEC_CMATCH "smtp_tls_secure_cert_match"
+#define DEF_SMTP_TLS_SEC_CMATCH "nexthop, dot-nexthop"
+#define VAR_LMTP_TLS_SEC_CMATCH "lmtp_tls_secure_cert_match"
+#define DEF_LMTP_TLS_SEC_CMATCH "nexthop"
+extern char *var_smtp_tls_sec_cmatch;
+
+
+#define VAR_SMTP_TLS_FPT_CMATCH "smtp_tls_fingerprint_cert_match"
+#define DEF_SMTP_TLS_FPT_CMATCH ""
+#define VAR_LMTP_TLS_FPT_CMATCH "lmtp_tls_fingerprint_cert_match"
+#define DEF_LMTP_TLS_FPT_CMATCH ""
+extern char *var_smtp_tls_fpt_cmatch;
+
+#define VAR_SMTP_TLS_SNI "smtp_tls_servername"
+#define DEF_SMTP_TLS_SNI ""
+#define VAR_LMTP_TLS_SNI "lmtp_tls_servername"
+#define DEF_LMTP_TLS_SNI ""
+extern char *var_smtp_tls_sni;
+
+#define VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY "smtp_tls_block_early_mail_reply"
+#define DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY 0
+#define VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY "lmtp_tls_block_early_mail_reply"
+#define DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY 0
+extern bool var_smtp_tls_blk_early_mail_reply;
+
+#define VAR_SMTP_TLS_FORCE_TLSA "smtp_tls_force_insecure_host_tlsa_lookup"
+#define DEF_SMTP_TLS_FORCE_TLSA 0
+#define VAR_LMTP_TLS_FORCE_TLSA "lmtp_tls_force_insecure_host_tlsa_lookup"
+#define DEF_LMTP_TLS_FORCE_TLSA 0
+extern bool var_smtp_tls_force_tlsa;
+
+ /* SMTP only */
+#define VAR_SMTP_TLS_INSECURE_MX_POLICY "smtp_tls_dane_insecure_mx_policy"
+#define DEF_SMTP_TLS_INSECURE_MX_POLICY "${{$smtp_tls_security_level} == {dane} ? {dane} : {may}}"
+extern char *var_smtp_tls_insecure_mx_policy;
+
+ /*
+ * SASL authentication support, SMTP server side.
+ */
+#define VAR_SMTPD_SASL_ENABLE "smtpd_sasl_auth_enable"
+#define DEF_SMTPD_SASL_ENABLE 0
+extern bool var_smtpd_sasl_enable;
+
+#define VAR_SMTPD_SASL_AUTH_HDR "smtpd_sasl_authenticated_header"
+#define DEF_SMTPD_SASL_AUTH_HDR 0
+extern bool var_smtpd_sasl_auth_hdr;
+
+#define VAR_SMTPD_SASL_OPTS "smtpd_sasl_security_options"
+#define DEF_SMTPD_SASL_OPTS "noanonymous"
+extern char *var_smtpd_sasl_opts;
+
+#define VAR_SMTPD_SASL_PATH "smtpd_sasl_path"
+#define DEF_SMTPD_SASL_PATH "smtpd"
+extern char *var_smtpd_sasl_path;
+
+#define VAR_SMTPD_SASL_SERVICE "smtpd_sasl_service"
+#define DEF_SMTPD_SASL_SERVICE "smtp"
+extern char *var_smtpd_sasl_service;
+
+#define VAR_CYRUS_CONF_PATH "cyrus_sasl_config_path"
+#define DEF_CYRUS_CONF_PATH ""
+extern char *var_cyrus_conf_path;
+
+#define VAR_SMTPD_SASL_TLS_OPTS "smtpd_sasl_tls_security_options"
+#define DEF_SMTPD_SASL_TLS_OPTS "$" VAR_SMTPD_SASL_OPTS
+extern char *var_smtpd_sasl_tls_opts;
+
+#define VAR_SMTPD_SASL_REALM "smtpd_sasl_local_domain"
+#define DEF_SMTPD_SASL_REALM ""
+extern char *var_smtpd_sasl_realm;
+
+#define VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS "smtpd_sasl_exceptions_networks"
+#define DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS ""
+extern char *var_smtpd_sasl_exceptions_networks;
+
+#ifndef DEF_SERVER_SASL_TYPE
+#define DEF_SERVER_SASL_TYPE "cyrus"
+#endif
+
+#define VAR_SMTPD_SASL_TYPE "smtpd_sasl_type"
+#define DEF_SMTPD_SASL_TYPE DEF_SERVER_SASL_TYPE
+extern char *var_smtpd_sasl_type;
+
+#define VAR_SMTPD_SND_AUTH_MAPS "smtpd_sender_login_maps"
+#define DEF_SMTPD_SND_AUTH_MAPS ""
+extern char *var_smtpd_snd_auth_maps;
+
+#define REJECT_SENDER_LOGIN_MISMATCH "reject_sender_login_mismatch"
+#define REJECT_AUTH_SENDER_LOGIN_MISMATCH \
+ "reject_authenticated_sender_login_mismatch"
+#define REJECT_KNOWN_SENDER_LOGIN_MISMATCH \
+ "reject_known_sender_login_mismatch"
+#define REJECT_UNAUTH_SENDER_LOGIN_MISMATCH \
+ "reject_unauthenticated_sender_login_mismatch"
+
+ /*
+ * https://tools.ietf.org/html/rfc4954#page-5
+ *
+ * (At the time of writing of this document, 12288 octets is considered to be a
+ * sufficient line length limit for handling of deployed authentication
+ * mechanisms.)
+ *
+ * The default value is also the minimum permissible value for this parameter.
+ */
+#define VAR_SMTPD_SASL_RESP_LIMIT "smtpd_sasl_response_limit"
+#define DEF_SMTPD_SASL_RESP_LIMIT 12288
+extern int var_smtpd_sasl_resp_limit;
+
+ /*
+ * Some backends claim to support EXTERNAL authentication, but Postfix does
+ * not have code to provide the backend with such credentials. To avoid
+ * confusing errors, do not announce the EXTERNAL mechanism.
+ */
+#define VAR_SMTPD_SASL_MECH_FILTER "smtpd_sasl_mechanism_filter"
+#define DEF_SMTPD_SASL_MECH_FILTER "!external, static:rest"
+extern char *var_smtpd_sasl_mech_filter;
+
+ /*
+ * SASL authentication support, SMTP client side.
+ */
+#define VAR_SMTP_SASL_ENABLE "smtp_sasl_auth_enable"
+#define DEF_SMTP_SASL_ENABLE 0
+extern bool var_smtp_sasl_enable;
+
+#define VAR_SMTP_SASL_PASSWD "smtp_sasl_password_maps"
+#define DEF_SMTP_SASL_PASSWD ""
+extern char *var_smtp_sasl_passwd;
+
+#define VAR_SMTP_SASL_OPTS "smtp_sasl_security_options"
+#define DEF_SMTP_SASL_OPTS "noplaintext, noanonymous"
+extern char *var_smtp_sasl_opts;
+
+#define VAR_SMTP_SASL_PATH "smtp_sasl_path"
+#define DEF_SMTP_SASL_PATH ""
+extern char *var_smtp_sasl_path;
+
+#define VAR_SMTP_SASL_MECHS "smtp_sasl_mechanism_filter"
+#define DEF_SMTP_SASL_MECHS ""
+#define VAR_LMTP_SASL_MECHS "lmtp_sasl_mechanism_filter"
+#define DEF_LMTP_SASL_MECHS ""
+extern char *var_smtp_sasl_mechs;
+
+#ifndef DEF_CLIENT_SASL_TYPE
+#define DEF_CLIENT_SASL_TYPE "cyrus"
+#endif
+
+#define VAR_SMTP_SASL_TYPE "smtp_sasl_type"
+#define DEF_SMTP_SASL_TYPE DEF_CLIENT_SASL_TYPE
+#define VAR_LMTP_SASL_TYPE "lmtp_sasl_type"
+#define DEF_LMTP_SASL_TYPE DEF_CLIENT_SASL_TYPE
+extern char *var_smtp_sasl_type;
+
+#define VAR_SMTP_SASL_TLS_OPTS "smtp_sasl_tls_security_options"
+#define DEF_SMTP_SASL_TLS_OPTS "$" VAR_SMTP_SASL_OPTS
+#define VAR_LMTP_SASL_TLS_OPTS "lmtp_sasl_tls_security_options"
+#define DEF_LMTP_SASL_TLS_OPTS "$" VAR_LMTP_SASL_OPTS
+extern char *var_smtp_sasl_tls_opts;
+
+#define VAR_SMTP_SASL_TLSV_OPTS "smtp_sasl_tls_verified_security_options"
+#define DEF_SMTP_SASL_TLSV_OPTS "$" VAR_SMTP_SASL_TLS_OPTS
+#define VAR_LMTP_SASL_TLSV_OPTS "lmtp_sasl_tls_verified_security_options"
+#define DEF_LMTP_SASL_TLSV_OPTS "$" VAR_LMTP_SASL_TLS_OPTS
+extern char *var_smtp_sasl_tlsv_opts;
+
+#define VAR_SMTP_DUMMY_MAIL_AUTH "smtp_send_dummy_mail_auth"
+#define DEF_SMTP_DUMMY_MAIL_AUTH 0
+extern bool var_smtp_dummy_mail_auth;
+
+#define VAR_LMTP_BALANCE_INET_PROTO "lmtp_balance_inet_protocols"
+#define DEF_LMTP_BALANCE_INET_PROTO DEF_SMTP_BALANCE_INET_PROTO
+#define VAR_SMTP_BALANCE_INET_PROTO "smtp_balance_inet_protocols"
+#define DEF_SMTP_BALANCE_INET_PROTO 1
+extern bool var_smtp_balance_inet_proto;
+
+ /*
+ * LMTP server. The soft error limit determines how many errors an LMTP
+ * client may make before we start to slow down; the hard error limit
+ * determines after how many client errors we disconnect.
+ */
+#define VAR_LMTPD_BANNER "lmtpd_banner"
+#define DEF_LMTPD_BANNER "$myhostname $mail_name"
+extern char *var_lmtpd_banner;
+
+#define VAR_LMTPD_TMOUT "lmtpd_timeout"
+#define DEF_LMTPD_TMOUT "300s"
+extern int var_lmtpd_tmout;
+
+#define VAR_LMTPD_RCPT_LIMIT "lmtpd_recipient_limit"
+#define DEF_LMTPD_RCPT_LIMIT 1000
+extern int var_lmtpd_rcpt_limit;
+
+#define VAR_LMTPD_SOFT_ERLIM "lmtpd_soft_error_limit"
+#define DEF_LMTPD_SOFT_ERLIM 10
+extern int var_lmtpd_soft_erlim;
+
+#define VAR_LMTPD_HARD_ERLIM "lmtpd_hard_error_limit"
+#define DEF_LMTPD_HARD_ERLIM 100
+extern int var_lmtpd_hard_erlim;
+
+#define VAR_LMTPD_ERR_SLEEP "lmtpd_error_sleep_time"
+#define DEF_LMTPD_ERR_SLEEP "5s"
+extern int var_lmtpd_err_sleep;
+
+#define VAR_LMTPD_JUNK_CMD "lmtpd_junk_command_limit"
+#define DEF_LMTPD_JUNK_CMD 1000
+extern int var_lmtpd_junk_cmd_limit;
+
+ /*
+ * SASL authentication support, LMTP server side.
+ */
+#define VAR_LMTPD_SASL_ENABLE "lmtpd_sasl_auth_enable"
+#define DEF_LMTPD_SASL_ENABLE 0
+extern bool var_lmtpd_sasl_enable;
+
+#define VAR_LMTPD_SASL_OPTS "lmtpd_sasl_security_options"
+#define DEF_LMTPD_SASL_OPTS "noanonymous"
+extern char *var_lmtpd_sasl_opts;
+
+#define VAR_LMTPD_SASL_REALM "lmtpd_sasl_local_domain"
+#define DEF_LMTPD_SASL_REALM "$myhostname"
+extern char *var_lmtpd_sasl_realm;
+
+ /*
+ * SASL authentication support, LMTP client side.
+ */
+#define VAR_LMTP_SASL_ENABLE "lmtp_sasl_auth_enable"
+#define DEF_LMTP_SASL_ENABLE 0
+extern bool var_lmtp_sasl_enable;
+
+#define VAR_LMTP_SASL_PASSWD "lmtp_sasl_password_maps"
+#define DEF_LMTP_SASL_PASSWD ""
+extern char *var_lmtp_sasl_passwd;
+
+#define VAR_LMTP_SASL_OPTS "lmtp_sasl_security_options"
+#define DEF_LMTP_SASL_OPTS "noplaintext, noanonymous"
+extern char *var_lmtp_sasl_opts;
+
+#define VAR_LMTP_SASL_PATH "lmtp_sasl_path"
+#define DEF_LMTP_SASL_PATH ""
+extern char *var_lmtp_sasl_path;
+
+#define VAR_LMTP_DUMMY_MAIL_AUTH "lmtp_send_dummy_mail_auth"
+#define DEF_LMTP_DUMMY_MAIL_AUTH 0
+extern bool var_lmtp_dummy_mail_auth;
+
+ /*
+ * SASL-based relay etc. control.
+ */
+#define PERMIT_SASL_AUTH "permit_sasl_authenticated"
+
+#define VAR_CYRUS_SASL_AUTHZID "send_cyrus_sasl_authzid"
+#define DEF_CYRUS_SASL_AUTHZID 0
+extern int var_cyrus_sasl_authzid;
+
+ /*
+ * Special handling of AUTH 535 failures.
+ */
+#define VAR_SMTP_SASL_AUTH_SOFT_BOUNCE "smtp_sasl_auth_soft_bounce"
+#define DEF_SMTP_SASL_AUTH_SOFT_BOUNCE 1
+#define VAR_LMTP_SASL_AUTH_SOFT_BOUNCE "lmtp_sasl_auth_soft_bounce"
+#define DEF_LMTP_SASL_AUTH_SOFT_BOUNCE 1
+extern bool var_smtp_sasl_auth_soft_bounce;
+
+#define VAR_SMTP_SASL_AUTH_CACHE_NAME "smtp_sasl_auth_cache_name"
+#define DEF_SMTP_SASL_AUTH_CACHE_NAME ""
+#define VAR_LMTP_SASL_AUTH_CACHE_NAME "lmtp_sasl_auth_cache_name"
+#define DEF_LMTP_SASL_AUTH_CACHE_NAME ""
+extern char *var_smtp_sasl_auth_cache_name;
+
+#define VAR_SMTP_SASL_AUTH_CACHE_TIME "smtp_sasl_auth_cache_time"
+#define DEF_SMTP_SASL_AUTH_CACHE_TIME "90d"
+#define VAR_LMTP_SASL_AUTH_CACHE_TIME "lmtp_sasl_auth_cache_time"
+#define DEF_LMTP_SASL_AUTH_CACHE_TIME "90d"
+extern int var_smtp_sasl_auth_cache_time;
+
+#define VAR_SMTP_TCP_PORT "smtp_tcp_port"
+#define DEF_SMTP_TCP_PORT "smtp"
+extern char *var_smtp_tcp_port;
+
+ /*
+ * LMTP client. Timeouts inspired by RFC 1123. The LMTP recipient limit
+ * determines how many recipient addresses the LMTP client sends along with
+ * each message. Unfortunately, some mailers misbehave and disconnect (smap)
+ * when given more recipients than they are willing to handle.
+ */
+#define VAR_LMTP_TCP_PORT "lmtp_tcp_port"
+#define DEF_LMTP_TCP_PORT "24"
+extern char *var_lmtp_tcp_port;
+
+#define VAR_LMTP_ASSUME_FINAL "lmtp_assume_final"
+#define DEF_LMTP_ASSUME_FINAL 0
+extern bool var_lmtp_assume_final;
+
+#define VAR_LMTP_CACHE_CONN "lmtp_cache_connection"
+#define DEF_LMTP_CACHE_CONN 1
+extern bool var_lmtp_cache_conn;
+
+#define VAR_LMTP_SKIP_QUIT_RESP "lmtp_skip_quit_response"
+#define DEF_LMTP_SKIP_QUIT_RESP 0
+extern bool var_lmtp_skip_quit_resp;
+
+#define VAR_LMTP_CONN_TMOUT "lmtp_connect_timeout"
+#define DEF_LMTP_CONN_TMOUT "0s"
+extern int var_lmtp_conn_tmout;
+
+#define VAR_LMTP_RSET_TMOUT "lmtp_rset_timeout"
+#define DEF_LMTP_RSET_TMOUT "20s"
+extern int var_lmtp_rset_tmout;
+
+#define VAR_LMTP_LHLO_TMOUT "lmtp_lhlo_timeout"
+#define DEF_LMTP_LHLO_TMOUT "300s"
+extern int var_lmtp_lhlo_tmout;
+
+#define VAR_LMTP_XFWD_TMOUT "lmtp_xforward_timeout"
+#define DEF_LMTP_XFWD_TMOUT "300s"
+extern int var_lmtp_xfwd_tmout;
+
+#define VAR_LMTP_MAIL_TMOUT "lmtp_mail_timeout"
+#define DEF_LMTP_MAIL_TMOUT "300s"
+extern int var_lmtp_mail_tmout;
+
+#define VAR_LMTP_RCPT_TMOUT "lmtp_rcpt_timeout"
+#define DEF_LMTP_RCPT_TMOUT "300s"
+extern int var_lmtp_rcpt_tmout;
+
+#define VAR_LMTP_DATA0_TMOUT "lmtp_data_init_timeout"
+#define DEF_LMTP_DATA0_TMOUT "120s"
+extern int var_lmtp_data0_tmout;
+
+#define VAR_LMTP_DATA1_TMOUT "lmtp_data_xfer_timeout"
+#define DEF_LMTP_DATA1_TMOUT "180s"
+extern int var_lmtp_data1_tmout;
+
+#define VAR_LMTP_DATA2_TMOUT "lmtp_data_done_timeout"
+#define DEF_LMTP_DATA2_TMOUT "600s"
+extern int var_lmtp_data2_tmout;
+
+#define VAR_LMTP_QUIT_TMOUT "lmtp_quit_timeout"
+#define DEF_LMTP_QUIT_TMOUT "300s"
+extern int var_lmtp_quit_tmout;
+
+#define VAR_LMTP_SEND_XFORWARD "lmtp_send_xforward_command"
+#define DEF_LMTP_SEND_XFORWARD 0
+extern bool var_lmtp_send_xforward;
+
+ /*
+ * Cleanup service. Header info that exceeds $header_size_limit bytes or
+ * $header_address_token_limit tokens is discarded.
+ */
+#define VAR_HOPCOUNT_LIMIT "hopcount_limit"
+#define DEF_HOPCOUNT_LIMIT 50
+extern int var_hopcount_limit;
+
+#define VAR_HEADER_LIMIT "header_size_limit"
+#define DEF_HEADER_LIMIT 102400
+extern int var_header_limit;
+
+#define VAR_TOKEN_LIMIT "header_address_token_limit"
+#define DEF_TOKEN_LIMIT 10240
+extern int var_token_limit;
+
+#define VAR_VIRT_RECUR_LIMIT "virtual_alias_recursion_limit"
+#define DEF_VIRT_RECUR_LIMIT 1000
+extern int var_virt_recur_limit;
+
+#define VAR_VIRT_EXPAN_LIMIT "virtual_alias_expansion_limit"
+#define DEF_VIRT_EXPAN_LIMIT 1000
+extern int var_virt_expan_limit;
+
+#define VAR_VIRT_ADDRLEN_LIMIT "virtual_alias_address_length_limit"
+#define DEF_VIRT_ADDRLEN_LIMIT 1000
+extern int var_virt_addrlen_limit;
+
+ /*
+ * Message/queue size limits.
+ */
+#define VAR_MESSAGE_LIMIT "message_size_limit"
+#define DEF_MESSAGE_LIMIT 10240000
+extern long var_message_limit;
+
+#define VAR_QUEUE_MINFREE "queue_minfree"
+#define DEF_QUEUE_MINFREE 0
+extern long var_queue_minfree;
+
+ /*
+ * Light-weight content inspection.
+ */
+#define VAR_HEADER_CHECKS "header_checks"
+#define DEF_HEADER_CHECKS ""
+extern char *var_header_checks;
+
+#define VAR_MIMEHDR_CHECKS "mime_header_checks"
+#define DEF_MIMEHDR_CHECKS "$header_checks"
+extern char *var_mimehdr_checks;
+
+#define VAR_NESTHDR_CHECKS "nested_header_checks"
+#define DEF_NESTHDR_CHECKS "$header_checks"
+extern char *var_nesthdr_checks;
+
+#define VAR_BODY_CHECKS "body_checks"
+#define DEF_BODY_CHECKS ""
+extern char *var_body_checks;
+
+#define VAR_BODY_CHECK_LEN "body_checks_size_limit"
+#define DEF_BODY_CHECK_LEN (50*1024)
+extern int var_body_check_len;
+
+ /*
+ * Bounce service: truncate bounce message that exceed $bounce_size_limit.
+ */
+#define VAR_BOUNCE_LIMIT "bounce_size_limit"
+#define DEF_BOUNCE_LIMIT 50000
+extern int var_bounce_limit;
+
+ /*
+ * Bounce service: reserved sender address for double bounces. The local
+ * delivery service discards undeliverable double bounces.
+ */
+#define VAR_DOUBLE_BOUNCE "double_bounce_sender"
+#define DEF_DOUBLE_BOUNCE "double-bounce"
+extern char *var_double_bounce_sender;
+
+ /*
+ * Bounce service: enable threaded bounces, with References: and
+ * In-Reply-To:.
+ */
+#define VAR_THREADED_BOUNCE "enable_threaded_bounces"
+#define DEF_THREADED_BOUNCE CONFIG_BOOL_NO
+extern bool var_threaded_bounce;
+
+ /*
+ * When forking a process, how often to try and how long to wait.
+ */
+#define VAR_FORK_TRIES "fork_attempts"
+#define DEF_FORK_TRIES 5
+extern int var_fork_tries;
+
+#define VAR_FORK_DELAY "fork_delay"
+#define DEF_FORK_DELAY "1s"
+extern int var_fork_delay;
+
+ /*
+ * When locking a mailbox, how often to try and how long to wait.
+ */
+#define VAR_FLOCK_TRIES "deliver_lock_attempts"
+#define DEF_FLOCK_TRIES 20
+extern int var_flock_tries;
+
+#define VAR_FLOCK_DELAY "deliver_lock_delay"
+#define DEF_FLOCK_DELAY "1s"
+extern int var_flock_delay;
+
+#define VAR_FLOCK_STALE "stale_lock_time"
+#define DEF_FLOCK_STALE "500s"
+extern int var_flock_stale;
+
+#define VAR_MAILTOOL_COMPAT "sun_mailtool_compatibility"
+#define DEF_MAILTOOL_COMPAT 0
+extern int var_mailtool_compat;
+
+ /*
+ * How long a daemon command may take to receive or deliver a message etc.
+ * before we assume it is wedged (should never happen).
+ */
+#define VAR_DAEMON_TIMEOUT "daemon_timeout"
+#define DEF_DAEMON_TIMEOUT "18000s"
+extern int var_daemon_timeout;
+
+#define VAR_QMGR_DAEMON_TIMEOUT "qmgr_daemon_timeout"
+#define DEF_QMGR_DAEMON_TIMEOUT "1000s"
+extern int var_qmgr_daemon_timeout;
+
+ /*
+ * How long an intra-mail command may take before we assume the mail system
+ * is in deadlock (should never happen).
+ */
+#define VAR_IPC_TIMEOUT "ipc_timeout"
+#define DEF_IPC_TIMEOUT "3600s"
+extern int var_ipc_timeout;
+
+#define VAR_QMGR_IPC_TIMEOUT "qmgr_ipc_timeout"
+#define DEF_QMGR_IPC_TIMEOUT "60s"
+extern int var_qmgr_ipc_timeout;
+
+ /*
+ * Time limit on intra-mail triggers.
+ */
+#define VAR_TRIGGER_TIMEOUT "trigger_timeout"
+#define DEF_TRIGGER_TIMEOUT "10s"
+extern int var_trigger_timeout;
+
+ /*
+ * SMTP server restrictions. What networks I am willing to relay from, what
+ * domains I am willing to forward mail from or to, what clients I refuse to
+ * talk to, and what domains I never want to see in the sender address.
+ */
+#define VAR_MYNETWORKS "mynetworks"
+extern char *var_mynetworks;
+
+#define VAR_MYNETWORKS_STYLE "mynetworks_style"
+#define DEF_MYNETWORKS_STYLE "${{$compatibility_level} <level {2} ? " \
+ "{" MYNETWORKS_STYLE_SUBNET "} : " \
+ "{" MYNETWORKS_STYLE_HOST "}}"
+extern char *var_mynetworks_style;
+
+#define MYNETWORKS_STYLE_CLASS "class"
+#define MYNETWORKS_STYLE_SUBNET "subnet"
+#define MYNETWORKS_STYLE_HOST "host"
+
+#define VAR_RELAY_DOMAINS "relay_domains"
+#define DEF_RELAY_DOMAINS "${{$compatibility_level} <level {2} ? " \
+ "{$mydestination} : {}}"
+extern char *var_relay_domains;
+
+#define VAR_RELAY_TRANSPORT "relay_transport"
+#define DEF_RELAY_TRANSPORT MAIL_SERVICE_RELAY
+extern char *var_relay_transport;
+
+#define VAR_RELAY_RCPT_MAPS "relay_recipient_maps"
+#define DEF_RELAY_RCPT_MAPS ""
+extern char *var_relay_rcpt_maps;
+
+#define VAR_RELAY_RCPT_CODE "unknown_relay_recipient_reject_code"
+#define DEF_RELAY_RCPT_CODE 550
+extern int var_relay_rcpt_code;
+
+#define VAR_RELAY_CCERTS "relay_clientcerts"
+#define DEF_RELAY_CCERTS ""
+extern char *var_smtpd_relay_ccerts;
+
+#define VAR_CLIENT_CHECKS "smtpd_client_restrictions"
+#define DEF_CLIENT_CHECKS ""
+extern char *var_client_checks;
+
+#define VAR_HELO_REQUIRED "smtpd_helo_required"
+#define DEF_HELO_REQUIRED 0
+extern bool var_helo_required;
+
+#define VAR_HELO_CHECKS "smtpd_helo_restrictions"
+#define DEF_HELO_CHECKS ""
+extern char *var_helo_checks;
+
+#define VAR_MAIL_CHECKS "smtpd_sender_restrictions"
+#define DEF_MAIL_CHECKS ""
+extern char *var_mail_checks;
+
+#define VAR_RELAY_CHECKS "smtpd_relay_restrictions"
+#define DEF_RELAY_CHECKS "${{$compatibility_level} <level {1} ? " \
+ "{} : {" PERMIT_MYNETWORKS ", " \
+ PERMIT_SASL_AUTH ", " \
+ DEFER_UNAUTH_DEST "}}"
+extern char *var_relay_checks;
+
+ /*
+ * For warn_compat_break_relay_domains check. Same as DEF_RELAY_CHECKS
+ * except that it evaluates to DUNNO instead of REJECT.
+ */
+#define FAKE_RELAY_CHECKS PERMIT_MYNETWORKS ", " \
+ PERMIT_SASL_AUTH ", " \
+ PERMIT_AUTH_DEST
+
+#define VAR_RCPT_CHECKS "smtpd_recipient_restrictions"
+#define DEF_RCPT_CHECKS ""
+extern char *var_rcpt_checks;
+
+#define VAR_RELAY_BEFORE_RCPT_CHECKS "smtpd_relay_before_recipient_restrictions"
+#define DEF_RELAY_BEFORE_RCPT_CHECKS "${{$compatibility_level} <level {3.6} ?" \
+ " {no} : {yes}}"
+extern bool var_relay_before_rcpt_checks;
+
+#define VAR_ETRN_CHECKS "smtpd_etrn_restrictions"
+#define DEF_ETRN_CHECKS ""
+extern char *var_etrn_checks;
+
+#define VAR_DATA_CHECKS "smtpd_data_restrictions"
+#define DEF_DATA_CHECKS ""
+extern char *var_data_checks;
+
+#define VAR_EOD_CHECKS "smtpd_end_of_data_restrictions"
+#define DEF_EOD_CHECKS ""
+extern char *var_eod_checks;
+
+#define VAR_REST_CLASSES "smtpd_restriction_classes"
+#define DEF_REST_CLASSES ""
+extern char *var_rest_classes;
+
+#define VAR_ALLOW_UNTRUST_ROUTE "allow_untrusted_routing"
+#define DEF_ALLOW_UNTRUST_ROUTE 0
+extern bool var_allow_untrust_route;
+
+ /*
+ * Names of specific restrictions, and the corresponding configuration
+ * parameters that control the status codes sent in response to rejected
+ * requests.
+ */
+#define PERMIT_ALL "permit"
+#define REJECT_ALL "reject"
+#define VAR_REJECT_CODE "reject_code"
+#define DEF_REJECT_CODE 554
+extern int var_reject_code;
+
+#define DEFER_ALL "defer"
+#define VAR_DEFER_CODE "defer_code"
+#define DEF_DEFER_CODE 450
+extern int var_defer_code;
+
+#define DEFER_IF_PERMIT "defer_if_permit"
+#define DEFER_IF_REJECT "defer_if_reject"
+
+#define VAR_REJECT_TMPF_ACT "reject_tempfail_action"
+#define DEF_REJECT_TMPF_ACT DEFER_IF_PERMIT
+extern char *var_reject_tmpf_act;
+
+#define SLEEP "sleep"
+
+#define REJECT_PLAINTEXT_SESSION "reject_plaintext_session"
+#define VAR_PLAINTEXT_CODE "plaintext_reject_code"
+#define DEF_PLAINTEXT_CODE 450
+extern int var_plaintext_code;
+
+#define REJECT_UNKNOWN_CLIENT "reject_unknown_client"
+#define REJECT_UNKNOWN_CLIENT_HOSTNAME "reject_unknown_client_hostname"
+#define REJECT_UNKNOWN_REVERSE_HOSTNAME "reject_unknown_reverse_client_hostname"
+#define REJECT_UNKNOWN_FORWARD_HOSTNAME "reject_unknown_forward_client_hostname"
+#define VAR_UNK_CLIENT_CODE "unknown_client_reject_code"
+#define DEF_UNK_CLIENT_CODE 450
+extern int var_unk_client_code;
+
+#define PERMIT_INET_INTERFACES "permit_inet_interfaces"
+
+#define PERMIT_MYNETWORKS "permit_mynetworks"
+
+#define PERMIT_NAKED_IP_ADDR "permit_naked_ip_address"
+
+#define REJECT_INVALID_HELO_HOSTNAME "reject_invalid_helo_hostname"
+#define REJECT_INVALID_HOSTNAME "reject_invalid_hostname"
+#define VAR_BAD_NAME_CODE "invalid_hostname_reject_code"
+#define DEF_BAD_NAME_CODE 501 /* SYNTAX */
+extern int var_bad_name_code;
+
+#define REJECT_UNKNOWN_HELO_HOSTNAME "reject_unknown_helo_hostname"
+#define REJECT_UNKNOWN_HOSTNAME "reject_unknown_hostname"
+#define VAR_UNK_NAME_CODE "unknown_hostname_reject_code"
+#define DEF_UNK_NAME_CODE 450
+extern int var_unk_name_code;
+
+#define VAR_UNK_NAME_TF_ACT "unknown_helo_hostname_tempfail_action"
+#define DEF_UNK_NAME_TF_ACT "$" VAR_REJECT_TMPF_ACT
+extern char *var_unk_name_tf_act;
+
+#define REJECT_NON_FQDN_HELO_HOSTNAME "reject_non_fqdn_helo_hostname"
+#define REJECT_NON_FQDN_HOSTNAME "reject_non_fqdn_hostname"
+#define REJECT_NON_FQDN_SENDER "reject_non_fqdn_sender"
+#define REJECT_NON_FQDN_RCPT "reject_non_fqdn_recipient"
+#define VAR_NON_FQDN_CODE "non_fqdn_reject_code"
+#define DEF_NON_FQDN_CODE 504 /* POLICY */
+extern int var_non_fqdn_code;
+
+#define REJECT_UNKNOWN_SENDDOM "reject_unknown_sender_domain"
+#define REJECT_UNKNOWN_RCPTDOM "reject_unknown_recipient_domain"
+#define REJECT_UNKNOWN_ADDRESS "reject_unknown_address"
+#define REJECT_UNLISTED_SENDER "reject_unlisted_sender"
+#define REJECT_UNLISTED_RCPT "reject_unlisted_recipient"
+#define CHECK_RCPT_MAPS "check_recipient_maps"
+
+#define VAR_UNK_ADDR_CODE "unknown_address_reject_code"
+#define DEF_UNK_ADDR_CODE 450
+extern int var_unk_addr_code;
+
+#define VAR_UNK_ADDR_TF_ACT "unknown_address_tempfail_action"
+#define DEF_UNK_ADDR_TF_ACT "$" VAR_REJECT_TMPF_ACT
+extern char *var_unk_addr_tf_act;
+
+#define VAR_SMTPD_REJ_UNL_FROM "smtpd_reject_unlisted_sender"
+#define DEF_SMTPD_REJ_UNL_FROM 0
+extern bool var_smtpd_rej_unl_from;
+
+#define VAR_SMTPD_REJ_UNL_RCPT "smtpd_reject_unlisted_recipient"
+#define DEF_SMTPD_REJ_UNL_RCPT 1
+extern bool var_smtpd_rej_unl_rcpt;
+
+#define REJECT_UNVERIFIED_RECIP "reject_unverified_recipient"
+#define VAR_UNV_RCPT_RCODE "unverified_recipient_reject_code"
+#define DEF_UNV_RCPT_RCODE 450
+extern int var_unv_rcpt_rcode;
+
+#define REJECT_UNVERIFIED_SENDER "reject_unverified_sender"
+#define VAR_UNV_FROM_RCODE "unverified_sender_reject_code"
+#define DEF_UNV_FROM_RCODE 450
+extern int var_unv_from_rcode;
+
+#define VAR_UNV_RCPT_DCODE "unverified_recipient_defer_code"
+#define DEF_UNV_RCPT_DCODE 450
+extern int var_unv_rcpt_dcode;
+
+#define VAR_UNV_FROM_DCODE "unverified_sender_defer_code"
+#define DEF_UNV_FROM_DCODE 450
+extern int var_unv_from_dcode;
+
+#define VAR_UNV_RCPT_TF_ACT "unverified_recipient_tempfail_action"
+#define DEF_UNV_RCPT_TF_ACT "$" VAR_REJECT_TMPF_ACT
+extern char *var_unv_rcpt_tf_act;
+
+#define VAR_UNV_FROM_TF_ACT "unverified_sender_tempfail_action"
+#define DEF_UNV_FROM_TF_ACT "$" VAR_REJECT_TMPF_ACT
+extern char *var_unv_from_tf_act;
+
+#define VAR_UNV_RCPT_WHY "unverified_recipient_reject_reason"
+#define DEF_UNV_RCPT_WHY ""
+extern char *var_unv_rcpt_why;
+
+#define VAR_UNV_FROM_WHY "unverified_sender_reject_reason"
+#define DEF_UNV_FROM_WHY ""
+extern char *var_unv_from_why;
+
+#define REJECT_MUL_RCPT_BOUNCE "reject_multi_recipient_bounce"
+#define VAR_MUL_RCPT_CODE "multi_recipient_bounce_reject_code"
+#define DEF_MUL_RCPT_CODE 550
+extern int var_mul_rcpt_code;
+
+#define PERMIT_AUTH_DEST "permit_auth_destination"
+#define REJECT_UNAUTH_DEST "reject_unauth_destination"
+#define DEFER_UNAUTH_DEST "defer_unauth_destination"
+#define CHECK_RELAY_DOMAINS "check_relay_domains"
+#define PERMIT_TLS_CLIENTCERTS "permit_tls_clientcerts"
+#define PERMIT_TLS_ALL_CLIENTCERTS "permit_tls_all_clientcerts"
+#define VAR_RELAY_CODE "relay_domains_reject_code"
+#define DEF_RELAY_CODE 554
+extern int var_relay_code;
+
+#define PERMIT_MX_BACKUP "permit_mx_backup"
+
+#define VAR_PERM_MX_NETWORKS "permit_mx_backup_networks"
+#define DEF_PERM_MX_NETWORKS ""
+extern char *var_perm_mx_networks;
+
+#define VAR_MAP_REJECT_CODE "access_map_reject_code"
+#define DEF_MAP_REJECT_CODE 554
+extern int var_map_reject_code;
+
+#define VAR_MAP_DEFER_CODE "access_map_defer_code"
+#define DEF_MAP_DEFER_CODE 450
+extern int var_map_defer_code;
+
+#define CHECK_CLIENT_ACL "check_client_access"
+#define CHECK_REVERSE_CLIENT_ACL "check_reverse_client_hostname_access"
+#define CHECK_CCERT_ACL "check_ccert_access"
+#define CHECK_SASL_ACL "check_sasl_access"
+#define CHECK_HELO_ACL "check_helo_access"
+#define CHECK_SENDER_ACL "check_sender_access"
+#define CHECK_RECIP_ACL "check_recipient_access"
+#define CHECK_ETRN_ACL "check_etrn_access"
+
+#define CHECK_CLIENT_MX_ACL "check_client_mx_access"
+#define CHECK_REVERSE_CLIENT_MX_ACL "check_reverse_client_hostname_mx_access"
+#define CHECK_HELO_MX_ACL "check_helo_mx_access"
+#define CHECK_SENDER_MX_ACL "check_sender_mx_access"
+#define CHECK_RECIP_MX_ACL "check_recipient_mx_access"
+#define CHECK_CLIENT_NS_ACL "check_client_ns_access"
+#define CHECK_REVERSE_CLIENT_NS_ACL "check_reverse_client_hostname_ns_access"
+#define CHECK_HELO_NS_ACL "check_helo_ns_access"
+#define CHECK_SENDER_NS_ACL "check_sender_ns_access"
+#define CHECK_RECIP_NS_ACL "check_recipient_ns_access"
+#define CHECK_CLIENT_A_ACL "check_client_a_access"
+#define CHECK_REVERSE_CLIENT_A_ACL "check_reverse_client_hostname_a_access"
+#define CHECK_HELO_A_ACL "check_helo_a_access"
+#define CHECK_SENDER_A_ACL "check_sender_a_access"
+#define CHECK_RECIP_A_ACL "check_recipient_a_access"
+
+#define WARN_IF_REJECT "warn_if_reject"
+
+#define REJECT_RBL "reject_rbl" /* LaMont compatibility */
+#define REJECT_RBL_CLIENT "reject_rbl_client"
+#define REJECT_RHSBL_CLIENT "reject_rhsbl_client"
+#define REJECT_RHSBL_REVERSE_CLIENT "reject_rhsbl_reverse_client"
+#define REJECT_RHSBL_HELO "reject_rhsbl_helo"
+#define REJECT_RHSBL_SENDER "reject_rhsbl_sender"
+#define REJECT_RHSBL_RECIPIENT "reject_rhsbl_recipient"
+
+#define PERMIT_DNSWL_CLIENT "permit_dnswl_client"
+#define PERMIT_RHSWL_CLIENT "permit_rhswl_client"
+
+#define VAR_RBL_REPLY_MAPS "rbl_reply_maps"
+#define DEF_RBL_REPLY_MAPS ""
+extern char *var_rbl_reply_maps;
+
+#define VAR_DEF_RBL_REPLY "default_rbl_reply"
+#define DEF_DEF_RBL_REPLY "$rbl_code Service unavailable; $rbl_class [$rbl_what] blocked using $rbl_domain${rbl_reason?; $rbl_reason}"
+extern char *var_def_rbl_reply;
+
+#define REJECT_MAPS_RBL "reject_maps_rbl" /* backwards compat */
+#define VAR_MAPS_RBL_CODE "maps_rbl_reject_code"
+#define DEF_MAPS_RBL_CODE 554
+extern int var_maps_rbl_code;
+
+#define VAR_MAPS_RBL_DOMAINS "maps_rbl_domains" /* backwards compat */
+#define DEF_MAPS_RBL_DOMAINS ""
+extern char *var_maps_rbl_domains;
+
+#define VAR_SMTPD_DELAY_REJECT "smtpd_delay_reject"
+#define DEF_SMTPD_DELAY_REJECT 1
+extern int var_smtpd_delay_reject;
+
+#define REJECT_UNAUTH_PIPE "reject_unauth_pipelining"
+
+#define VAR_SMTPD_NULL_KEY "smtpd_null_access_lookup_key"
+#define DEF_SMTPD_NULL_KEY "<>"
+extern char *var_smtpd_null_key;
+
+#define VAR_SMTPD_EXP_FILTER "smtpd_expansion_filter"
+#define DEF_SMTPD_EXP_FILTER "\\t\\40!\"#$%&'()*+,-./0123456789:;<=>?@\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`\
+abcdefghijklmnopqrstuvwxyz{|}~"
+extern char *var_smtpd_exp_filter;
+
+#define VAR_SMTPD_PEERNAME_LOOKUP "smtpd_peername_lookup"
+#define DEF_SMTPD_PEERNAME_LOOKUP 1
+extern bool var_smtpd_peername_lookup;
+
+#define VAR_SMTPD_FORBID_UNAUTH_PIPE "smtpd_forbid_unauth_pipelining"
+#define DEF_SMTPD_FORBID_UNAUTH_PIPE 0
+extern bool var_smtpd_forbid_unauth_pipe;
+
+ /*
+ * Heuristic to reject unknown local recipients at the SMTP port.
+ */
+#define VAR_LOCAL_RCPT_MAPS "local_recipient_maps"
+#define DEF_LOCAL_RCPT_MAPS "proxy:unix:passwd.byname $" VAR_ALIAS_MAPS
+extern char *var_local_rcpt_maps;
+
+#define VAR_LOCAL_RCPT_CODE "unknown_local_recipient_reject_code"
+#define DEF_LOCAL_RCPT_CODE 550
+extern int var_local_rcpt_code;
+
+ /*
+ * List of pre-approved maps that are OK to open with the proxymap service.
+ */
+#define VAR_PROXY_READ_MAPS "proxy_read_maps"
+#define DEF_PROXY_READ_MAPS "$" VAR_LOCAL_RCPT_MAPS \
+ " $" VAR_MYDEST \
+ " $" VAR_VIRT_ALIAS_MAPS \
+ " $" VAR_VIRT_ALIAS_DOMS \
+ " $" VAR_VIRT_MAILBOX_MAPS \
+ " $" VAR_VIRT_MAILBOX_DOMS \
+ " $" VAR_RELAY_RCPT_MAPS \
+ " $" VAR_RELAY_DOMAINS \
+ " $" VAR_CANONICAL_MAPS \
+ " $" VAR_SEND_CANON_MAPS \
+ " $" VAR_RCPT_CANON_MAPS \
+ " $" VAR_RELOCATED_MAPS \
+ " $" VAR_TRANSPORT_MAPS \
+ " $" VAR_MYNETWORKS \
+ " $" VAR_SMTPD_SND_AUTH_MAPS \
+ " $" VAR_SEND_BCC_MAPS \
+ " $" VAR_RCPT_BCC_MAPS \
+ " $" VAR_SMTP_GENERIC_MAPS \
+ " $" VAR_LMTP_GENERIC_MAPS \
+ " $" VAR_ALIAS_MAPS \
+ " $" VAR_CLIENT_CHECKS \
+ " $" VAR_HELO_CHECKS \
+ " $" VAR_MAIL_CHECKS \
+ " $" VAR_RELAY_CHECKS \
+ " $" VAR_RCPT_CHECKS \
+ " $" VAR_VRFY_SND_DEF_XPORT_MAPS \
+ " $" VAR_VRFY_RELAY_MAPS \
+ " $" VAR_VRFY_XPORT_MAPS \
+ " $" VAR_FBCK_TRANSP_MAPS \
+ " $" VAR_LMTP_EHLO_DIS_MAPS \
+ " $" VAR_LMTP_PIX_BUG_MAPS \
+ " $" VAR_LMTP_SASL_PASSWD \
+ " $" VAR_LMTP_TLS_POLICY \
+ " $" VAR_MAILBOX_CMD_MAPS \
+ " $" VAR_MBOX_TRANSP_MAPS \
+ " $" VAR_PSC_EHLO_DIS_MAPS \
+ " $" VAR_RBL_REPLY_MAPS \
+ " $" VAR_SND_DEF_XPORT_MAPS \
+ " $" VAR_SND_RELAY_MAPS \
+ " $" VAR_SMTP_EHLO_DIS_MAPS \
+ " $" VAR_SMTP_PIX_BUG_MAPS \
+ " $" VAR_SMTP_SASL_PASSWD \
+ " $" VAR_SMTP_TLS_POLICY \
+ " $" VAR_SMTPD_EHLO_DIS_MAPS \
+ " $" VAR_SMTPD_MILTER_MAPS \
+ " $" VAR_VIRT_GID_MAPS \
+ " $" VAR_VIRT_UID_MAPS \
+ " $" VAR_LOCAL_LOGIN_SND_MAPS \
+ " $" VAR_PSC_REJ_FTR_MAPS \
+ " $" VAR_SMTPD_REJ_FTR_MAPS \
+ " $" VAR_TLS_SERVER_SNI_MAPS \
+ " $" VAR_TLSP_CLNT_POLICY \
+ " $" VAR_DSN_FILTER \
+ " $" VAR_LMTP_DSN_FILTER \
+ " $" VAR_LMTP_DNS_RE_FILTER \
+ " $" VAR_LMTP_RESP_FILTER \
+ " $" VAR_LOCAL_DSN_FILTER \
+ " $" VAR_PIPE_DSN_FILTER \
+ " $" VAR_PSC_CMD_FILTER \
+ " $" VAR_SMTP_DSN_FILTER \
+ " $" VAR_SMTP_DNS_RE_FILTER \
+ " $" VAR_SMTP_RESP_FILTER \
+ " $" VAR_SMTPD_CMD_FILTER \
+ " $" VAR_SMTPD_DNS_RE_FILTER \
+ " $" VAR_VIRT_DSN_FILTER \
+ " $" VAR_BODY_CHECKS \
+ " $" VAR_HEADER_CHECKS \
+ " $" VAR_LMTP_BODY_CHKS \
+ " $" VAR_LMTP_HEAD_CHKS \
+ " $" VAR_LMTP_MIME_CHKS \
+ " $" VAR_LMTP_NEST_CHKS \
+ " $" VAR_MILT_HEAD_CHECKS \
+ " $" VAR_MIMEHDR_CHECKS \
+ " $" VAR_NESTHDR_CHECKS \
+ " $" VAR_SMTP_BODY_CHKS \
+ " $" VAR_SMTP_HEAD_CHKS \
+ " $" VAR_SMTP_MIME_CHKS \
+ " $" VAR_SMTP_NEST_CHKS
+extern char *var_proxy_read_maps;
+
+#define VAR_PROXY_WRITE_MAPS "proxy_write_maps"
+#define DEF_PROXY_WRITE_MAPS "$" VAR_SMTP_SASL_AUTH_CACHE_NAME \
+ " $" VAR_LMTP_SASL_AUTH_CACHE_NAME \
+ " $" VAR_VERIFY_MAP \
+ " $" VAR_PSC_CACHE_MAP
+extern char *var_proxy_write_maps;
+
+#define VAR_PROXY_READ_ACL "proxy_read_access_list"
+#define DEF_PROXY_READ_ACL "reject"
+extern char *var_proxy_read_acl;
+
+#define VAR_PROXY_WRITE_ACL "proxy_write_access_list"
+#define DEF_PROXY_WRITE_ACL "reject"
+extern char *var_proxy_write_acl;
+
+ /*
+ * Other.
+ */
+#define VAR_PROCNAME "process_name"
+extern char *var_procname;
+
+#define VAR_SERVNAME "service_name"
+extern char *var_servname;
+
+#define VAR_PID "process_id"
+extern int var_pid;
+
+#define VAR_DEBUG_COMMAND "debugger_command"
+
+ /*
+ * Paranoia: save files instead of deleting them.
+ */
+#define VAR_DONT_REMOVE "dont_remove"
+#define DEF_DONT_REMOVE 0
+extern bool var_dont_remove;
+
+ /*
+ * Paranoia: defer messages instead of bouncing them.
+ */
+#define VAR_SOFT_BOUNCE "soft_bounce"
+#define DEF_SOFT_BOUNCE 0
+extern bool var_soft_bounce;
+
+ /*
+ * Give special treatment to owner- and -request.
+ */
+#define VAR_OWNREQ_SPECIAL "owner_request_special"
+#define DEF_OWNREQ_SPECIAL 1
+extern bool var_ownreq_special;
+
+ /*
+ * Allow/disallow recipient addresses starting with `-'.
+ */
+#define VAR_ALLOW_MIN_USER "allow_min_user"
+#define DEF_ALLOW_MIN_USER 0
+extern bool var_allow_min_user;
+
+extern void mail_params_init(void);
+
+ /*
+ * Content inspection and filtering.
+ */
+#define VAR_FILTER_XPORT "content_filter"
+#define DEF_FILTER_XPORT ""
+extern char *var_filter_xport;
+
+#define VAR_DEF_FILTER_NEXTHOP "default_filter_nexthop"
+#define DEF_DEF_FILTER_NEXTHOP ""
+extern char *var_def_filter_nexthop;
+
+ /*
+ * Fast flush service support.
+ */
+#define VAR_FFLUSH_DOMAINS "fast_flush_domains"
+#define DEF_FFLUSH_DOMAINS "$relay_domains"
+extern char *var_fflush_domains;
+
+#define VAR_FFLUSH_PURGE "fast_flush_purge_time"
+#define DEF_FFLUSH_PURGE "7d"
+extern int var_fflush_purge;
+
+#define VAR_FFLUSH_REFRESH "fast_flush_refresh_time"
+#define DEF_FFLUSH_REFRESH "12h"
+extern int var_fflush_refresh;
+
+ /*
+ * Environmental management - what Postfix imports from the external world,
+ * and what Postfix exports to the external world.
+ */
+#define VAR_IMPORT_ENVIRON "import_environment"
+#define DEF_IMPORT_ENVIRON "MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG " \
+ "TZ XAUTHORITY DISPLAY LANG=C " \
+ "POSTLOG_SERVICE POSTLOG_HOSTNAME"
+extern char *var_import_environ;
+
+#define VAR_EXPORT_ENVIRON "export_environment"
+#define DEF_EXPORT_ENVIRON "TZ MAIL_CONFIG LANG"
+extern char *var_export_environ;
+
+ /*
+ * Tunables for the "virtual" local delivery agent
+ */
+#define VAR_VIRT_TRANSPORT "virtual_transport"
+#define DEF_VIRT_TRANSPORT MAIL_SERVICE_VIRTUAL
+extern char *var_virt_transport;
+
+#define VAR_VIRT_MAILBOX_MAPS "virtual_mailbox_maps"
+#define DEF_VIRT_MAILBOX_MAPS ""
+extern char *var_virt_mailbox_maps;
+
+#define VAR_VIRT_MAILBOX_DOMS "virtual_mailbox_domains"
+#define DEF_VIRT_MAILBOX_DOMS "$virtual_mailbox_maps"
+extern char *var_virt_mailbox_doms;
+
+#define VAR_VIRT_MAILBOX_CODE "unknown_virtual_mailbox_reject_code"
+#define DEF_VIRT_MAILBOX_CODE 550
+extern int var_virt_mailbox_code;
+
+#define VAR_VIRT_UID_MAPS "virtual_uid_maps"
+#define DEF_VIRT_UID_MAPS ""
+extern char *var_virt_uid_maps;
+
+#define VAR_VIRT_GID_MAPS "virtual_gid_maps"
+#define DEF_VIRT_GID_MAPS ""
+extern char *var_virt_gid_maps;
+
+#define VAR_VIRT_MINUID "virtual_minimum_uid"
+#define DEF_VIRT_MINUID 100
+extern int var_virt_minimum_uid;
+
+#define VAR_VIRT_MAILBOX_BASE "virtual_mailbox_base"
+#define DEF_VIRT_MAILBOX_BASE ""
+extern char *var_virt_mailbox_base;
+
+#define VAR_VIRT_MAILBOX_LIMIT "virtual_mailbox_limit"
+#define DEF_VIRT_MAILBOX_LIMIT (5 * DEF_MESSAGE_LIMIT)
+extern long var_virt_mailbox_limit;
+
+#define VAR_VIRT_MAILBOX_LOCK "virtual_mailbox_lock"
+#define DEF_VIRT_MAILBOX_LOCK "fcntl, dotlock"
+extern char *var_virt_mailbox_lock;
+
+ /*
+ * Distinct logging tag for multiple Postfix instances.
+ */
+#define VAR_SYSLOG_NAME "syslog_name"
+#if 1
+#define DEF_SYSLOG_NAME \
+ "${" VAR_MULTI_NAME "?{$" VAR_MULTI_NAME "}:{postfix}}"
+#else
+#define DEF_SYSLOG_NAME "postfix"
+#endif
+extern char *var_syslog_name;
+
+ /*
+ * QMQPD
+ */
+#define VAR_QMQPD_CLIENTS "qmqpd_authorized_clients"
+#define DEF_QMQPD_CLIENTS ""
+extern char *var_qmqpd_clients;
+
+#define VAR_QMTPD_TMOUT "qmqpd_timeout"
+#define DEF_QMTPD_TMOUT "300s"
+extern int var_qmqpd_timeout;
+
+#define VAR_QMTPD_ERR_SLEEP "qmqpd_error_delay"
+#define DEF_QMTPD_ERR_SLEEP "1s"
+extern int var_qmqpd_err_sleep;
+
+ /*
+ * VERP, more DJB intellectual cross-pollination. However, we prefer + as
+ * the default recipient delimiter.
+ */
+#define VAR_VERP_DELIMS "default_verp_delimiters"
+#define DEF_VERP_DELIMS "+="
+extern char *var_verp_delims;
+
+#define VAR_VERP_FILTER "verp_delimiter_filter"
+#define DEF_VERP_FILTER "-=+"
+extern char *var_verp_filter;
+
+#define VAR_VERP_BOUNCE_OFF "disable_verp_bounces"
+#define DEF_VERP_BOUNCE_OFF 0
+extern bool var_verp_bounce_off;
+
+#define VAR_VERP_CLIENTS "smtpd_authorized_verp_clients"
+#define DEF_VERP_CLIENTS "$authorized_verp_clients"
+extern char *var_verp_clients;
+
+ /*
+ * XCLIENT, for rule testing and fetchmail like apps.
+ */
+#define VAR_XCLIENT_HOSTS "smtpd_authorized_xclient_hosts"
+#define DEF_XCLIENT_HOSTS ""
+extern char *var_xclient_hosts;
+
+ /*
+ * XFORWARD, for improved post-filter logging.
+ */
+#define VAR_XFORWARD_HOSTS "smtpd_authorized_xforward_hosts"
+#define DEF_XFORWARD_HOSTS ""
+extern char *var_xforward_hosts;
+
+ /*
+ * Inbound mail flow control. This allows for a stiffer coupling between
+ * receiving mail and sending mail. A sending process produces one token for
+ * each message that it takes from the incoming queue; a receiving process
+ * consumes one token for each message that it adds to the incoming queue.
+ * When no token is available (Postfix receives more mail than it is able to
+ * deliver) a receiving process pauses for $in_flow_delay seconds so that
+ * the sending processes get a chance to access the disk.
+ */
+#define VAR_IN_FLOW_DELAY "in_flow_delay"
+#ifdef PIPES_CANT_FIONREAD
+#define DEF_IN_FLOW_DELAY "0s"
+#else
+#define DEF_IN_FLOW_DELAY "1s"
+#endif
+extern int var_in_flow_delay;
+
+ /*
+ * Backwards compatibility: foo.com matches itself and names below foo.com.
+ */
+#define VAR_PAR_DOM_MATCH "parent_domain_matches_subdomains"
+#define DEF_PAR_DOM_MATCH VAR_DEBUG_PEER_LIST "," \
+ VAR_FFLUSH_DOMAINS "," \
+ VAR_MYNETWORKS "," \
+ VAR_PERM_MX_NETWORKS "," \
+ VAR_QMQPD_CLIENTS "," \
+ VAR_RELAY_DOMAINS "," \
+ SMTPD_ACCESS_MAPS
+extern char *var_par_dom_match;
+
+#define SMTPD_ACCESS_MAPS "smtpd_access_maps"
+
+ /*
+ * Run-time fault injection.
+ */
+#define VAR_FAULT_INJ_CODE "fault_injection_code"
+#define DEF_FAULT_INJ_CODE 0
+extern int var_fault_inj_code;
+
+ /*
+ * Install/upgrade information.
+ */
+#define VAR_SENDMAIL_PATH "sendmail_path"
+#ifndef DEF_SENDMAIL_PATH
+#define DEF_SENDMAIL_PATH "/usr/sbin/sendmail"
+#endif
+
+#define VAR_MAILQ_PATH "mailq_path"
+#ifndef DEF_MAILQ_PATH
+#define DEF_MAILQ_PATH "/usr/bin/mailq"
+#endif
+
+#define VAR_NEWALIAS_PATH "newaliases_path"
+#ifndef DEF_NEWALIAS_PATH
+#define DEF_NEWALIAS_PATH "/usr/bin/newaliases"
+#endif
+
+#define VAR_OPENSSL_PATH "openssl_path"
+#ifndef DEF_OPENSSL_PATH
+#define DEF_OPENSSL_PATH "openssl"
+#endif
+extern char *var_openssl_path;
+
+#define VAR_MANPAGE_DIR "manpage_directory"
+#ifndef DEF_MANPAGE_DIR
+#define DEF_MANPAGE_DIR "/usr/local/man"
+#endif
+
+#define VAR_SAMPLE_DIR "sample_directory"
+#ifndef DEF_SAMPLE_DIR
+#define DEF_SAMPLE_DIR DEF_CONFIG_DIR
+#endif
+
+#define VAR_README_DIR "readme_directory"
+#ifndef DEF_README_DIR
+#define DEF_README_DIR "no"
+#endif
+
+#define VAR_HTML_DIR "html_directory"
+#ifndef DEF_HTML_DIR
+#define DEF_HTML_DIR "no"
+#endif
+
+ /*
+ * Safety: resolve the address with unquoted localpart (default, but
+ * technically incorrect), instead of resolving the address with quoted
+ * localpart (technically correct, but unsafe). The default prevents mail
+ * relay loopholes with "user@domain"@domain when relaying mail to a
+ * Sendmail system.
+ */
+#define VAR_RESOLVE_DEQUOTED "resolve_dequoted_address"
+#define DEF_RESOLVE_DEQUOTED 1
+extern bool var_resolve_dequoted;
+
+#define VAR_RESOLVE_NULLDOM "resolve_null_domain"
+#define DEF_RESOLVE_NULLDOM 0
+extern bool var_resolve_nulldom;
+
+#define VAR_RESOLVE_NUM_DOM "resolve_numeric_domain"
+#define DEF_RESOLVE_NUM_DOM 0
+extern bool var_resolve_num_dom;
+
+ /*
+ * Service names. The transport (TCP, FIFO or UNIX-domain) type is frozen
+ * because you cannot simply mix them, and accessibility (private/public) is
+ * frozen for security reasons. We list only the internal services, not the
+ * externally visible SMTP server, or the delivery agents that can already
+ * be chosen via transport mappings etc.
+ */
+#define VAR_BOUNCE_SERVICE "bounce_service_name"
+#define DEF_BOUNCE_SERVICE MAIL_SERVICE_BOUNCE
+extern char *var_bounce_service;
+
+#define VAR_CLEANUP_SERVICE "cleanup_service_name"
+#define DEF_CLEANUP_SERVICE MAIL_SERVICE_CLEANUP
+extern char *var_cleanup_service;
+
+#define VAR_DEFER_SERVICE "defer_service_name"
+#define DEF_DEFER_SERVICE MAIL_SERVICE_DEFER
+extern char *var_defer_service;
+
+#define VAR_PICKUP_SERVICE "pickup_service_name"
+#define DEF_PICKUP_SERVICE MAIL_SERVICE_PICKUP
+extern char *var_pickup_service;
+
+#define VAR_QUEUE_SERVICE "queue_service_name"
+#define DEF_QUEUE_SERVICE MAIL_SERVICE_QUEUE
+extern char *var_queue_service;
+
+ /* XXX resolve does not exist as a separate service */
+
+#define VAR_REWRITE_SERVICE "rewrite_service_name"
+#define DEF_REWRITE_SERVICE MAIL_SERVICE_REWRITE
+extern char *var_rewrite_service;
+
+#define VAR_SHOWQ_SERVICE "showq_service_name"
+#define DEF_SHOWQ_SERVICE MAIL_SERVICE_SHOWQ
+extern char *var_showq_service;
+
+#define VAR_ERROR_SERVICE "error_service_name"
+#define DEF_ERROR_SERVICE MAIL_SERVICE_ERROR
+extern char *var_error_service;
+
+#define VAR_FLUSH_SERVICE "flush_service_name"
+#define DEF_FLUSH_SERVICE MAIL_SERVICE_FLUSH
+extern char *var_flush_service;
+
+ /*
+ * Session cache service.
+ */
+#define VAR_SCACHE_SERVICE "connection_cache_service_name"
+#define DEF_SCACHE_SERVICE "scache"
+extern char *var_scache_service;
+
+#define VAR_SCACHE_PROTO_TMOUT "connection_cache_protocol_timeout"
+#define DEF_SCACHE_PROTO_TMOUT "5s"
+extern int var_scache_proto_tmout;
+
+#define VAR_SCACHE_TTL_LIM "connection_cache_ttl_limit"
+#define DEF_SCACHE_TTL_LIM "2s"
+extern int var_scache_ttl_lim;
+
+#define VAR_SCACHE_STAT_TIME "connection_cache_status_update_time"
+#define DEF_SCACHE_STAT_TIME "600s"
+extern int var_scache_stat_time;
+
+#define VAR_VRFY_PEND_LIMIT "address_verify_pending_request_limit"
+#define DEF_VRFY_PEND_LIMIT (DEF_QMGR_ACT_LIMIT / 4)
+extern int var_vrfy_pend_limit;
+
+ /*
+ * Address verification service.
+ */
+#define VAR_VERIFY_SERVICE "address_verify_service_name"
+#define DEF_VERIFY_SERVICE MAIL_SERVICE_VERIFY
+extern char *var_verify_service;
+
+#define VAR_VERIFY_MAP "address_verify_map"
+#define DEF_VERIFY_MAP "btree:$data_directory/verify_cache"
+extern char *var_verify_map;
+
+#define VAR_VERIFY_POS_EXP "address_verify_positive_expire_time"
+#define DEF_VERIFY_POS_EXP "31d"
+extern int var_verify_pos_exp;
+
+#define VAR_VERIFY_POS_TRY "address_verify_positive_refresh_time"
+#define DEF_VERIFY_POS_TRY "7d"
+extern int var_verify_pos_try;
+
+#define VAR_VERIFY_NEG_EXP "address_verify_negative_expire_time"
+#define DEF_VERIFY_NEG_EXP "3d"
+extern int var_verify_neg_exp;
+
+#define VAR_VERIFY_NEG_TRY "address_verify_negative_refresh_time"
+#define DEF_VERIFY_NEG_TRY "3h"
+extern int var_verify_neg_try;
+
+#define VAR_VERIFY_NEG_CACHE "address_verify_negative_cache"
+#define DEF_VERIFY_NEG_CACHE 1
+extern bool var_verify_neg_cache;
+
+#define VAR_VERIFY_SCAN_CACHE "address_verify_cache_cleanup_interval"
+#define DEF_VERIFY_SCAN_CACHE "12h"
+extern int var_verify_scan_cache;
+
+#define VAR_VERIFY_SENDER "address_verify_sender"
+#define DEF_VERIFY_SENDER "$" VAR_DOUBLE_BOUNCE
+extern char *var_verify_sender;
+
+#define VAR_VERIFY_SENDER_TTL "address_verify_sender_ttl"
+#define DEF_VERIFY_SENDER_TTL "0s"
+extern int var_verify_sender_ttl;
+
+#define VAR_VERIFY_POLL_COUNT "address_verify_poll_count"
+#define DEF_VERIFY_POLL_COUNT "${stress?{1}:{3}}"
+extern int var_verify_poll_count;
+
+#define VAR_VERIFY_POLL_DELAY "address_verify_poll_delay"
+#define DEF_VERIFY_POLL_DELAY "3s"
+extern int var_verify_poll_delay;
+
+#define VAR_VRFY_LOCAL_XPORT "address_verify_local_transport"
+#define DEF_VRFY_LOCAL_XPORT "$" VAR_LOCAL_TRANSPORT
+extern char *var_vrfy_local_xport;
+
+#define VAR_VRFY_VIRT_XPORT "address_verify_virtual_transport"
+#define DEF_VRFY_VIRT_XPORT "$" VAR_VIRT_TRANSPORT
+extern char *var_vrfy_virt_xport;
+
+#define VAR_VRFY_RELAY_XPORT "address_verify_relay_transport"
+#define DEF_VRFY_RELAY_XPORT "$" VAR_RELAY_TRANSPORT
+extern char *var_vrfy_relay_xport;
+
+#define VAR_VRFY_DEF_XPORT "address_verify_default_transport"
+#define DEF_VRFY_DEF_XPORT "$" VAR_DEF_TRANSPORT
+extern char *var_vrfy_def_xport;
+
+#define VAR_VRFY_SND_DEF_XPORT_MAPS "address_verify_" VAR_SND_DEF_XPORT_MAPS
+#define DEF_VRFY_SND_DEF_XPORT_MAPS "$" VAR_SND_DEF_XPORT_MAPS
+extern char *var_snd_def_xport_maps;
+
+#define VAR_VRFY_RELAYHOST "address_verify_relayhost"
+#define DEF_VRFY_RELAYHOST "$" VAR_RELAYHOST
+extern char *var_vrfy_relayhost;
+
+#define VAR_VRFY_RELAY_MAPS "address_verify_sender_dependent_relayhost_maps"
+#define DEF_VRFY_RELAY_MAPS "$" VAR_SND_RELAY_MAPS
+extern char *var_vrfy_relay_maps;
+
+#define VAR_VRFY_XPORT_MAPS "address_verify_transport_maps"
+#define DEF_VRFY_XPORT_MAPS "$" VAR_TRANSPORT_MAPS
+extern char *var_vrfy_xport_maps;
+
+#define SMTP_VRFY_TGT_RCPT "rcpt"
+#define SMTP_VRFY_TGT_DATA "data"
+#define VAR_LMTP_VRFY_TGT "lmtp_address_verify_target"
+#define DEF_LMTP_VRFY_TGT SMTP_VRFY_TGT_RCPT
+#define VAR_SMTP_VRFY_TGT "smtp_address_verify_target"
+#define DEF_SMTP_VRFY_TGT SMTP_VRFY_TGT_RCPT
+extern char *var_smtp_vrfy_tgt;
+
+ /*
+ * Message delivery trace service.
+ */
+#define VAR_TRACE_SERVICE "trace_service_name"
+#define DEF_TRACE_SERVICE MAIL_SERVICE_TRACE
+extern char *var_trace_service;
+
+ /*
+ * Proxymappers.
+ */
+#define VAR_PROXYMAP_SERVICE "proxymap_service_name"
+#define DEF_PROXYMAP_SERVICE MAIL_SERVICE_PROXYMAP
+extern char *var_proxymap_service;
+
+#define VAR_PROXYWRITE_SERVICE "proxywrite_service_name"
+#define DEF_PROXYWRITE_SERVICE MAIL_SERVICE_PROXYWRITE
+extern char *var_proxywrite_service;
+
+ /*
+ * Mailbox/maildir delivery errors that cause delivery to be tried again.
+ */
+#define VAR_MBX_DEFER_ERRS "mailbox_defer_errors"
+#define DEF_MBX_DEFER_ERRS "eagain, enospc, estale"
+extern char *var_mbx_defer_errs;
+
+#define VAR_MDR_DEFER_ERRS "maildir_defer_errors"
+#define DEF_MDR_DEFER_ERRS "enospc, estale"
+extern char *var_mdr_defer_errs;
+
+ /*
+ * Berkeley DB memory pool sizes.
+ */
+#define VAR_DB_CREATE_BUF "berkeley_db_create_buffer_size"
+#define DEF_DB_CREATE_BUF (16 * 1024 *1024)
+extern int var_db_create_buf;
+
+#define VAR_DB_READ_BUF "berkeley_db_read_buffer_size"
+#define DEF_DB_READ_BUF (128 *1024)
+extern int var_db_read_buf;
+
+ /*
+ * OpenLDAP LMDB settings.
+ */
+#define VAR_LMDB_MAP_SIZE "lmdb_map_size"
+#define DEF_LMDB_MAP_SIZE (16 * 1024 *1024)
+extern long var_lmdb_map_size;
+
+ /*
+ * Named queue file attributes.
+ */
+#define VAR_QATTR_COUNT_LIMIT "queue_file_attribute_count_limit"
+#define DEF_QATTR_COUNT_LIMIT 100
+extern int var_qattr_count_limit;
+
+ /*
+ * MIME support.
+ */
+#define VAR_MIME_MAXDEPTH "mime_nesting_limit"
+#define DEF_MIME_MAXDEPTH 100
+extern int var_mime_maxdepth;
+
+#define VAR_MIME_BOUND_LEN "mime_boundary_length_limit"
+#define DEF_MIME_BOUND_LEN 2048
+extern int var_mime_bound_len;
+
+#define VAR_DISABLE_MIME_INPUT "disable_mime_input_processing"
+#define DEF_DISABLE_MIME_INPUT 0
+extern bool var_disable_mime_input;
+
+#define VAR_DISABLE_MIME_OCONV "disable_mime_output_conversion"
+#define DEF_DISABLE_MIME_OCONV 0
+extern bool var_disable_mime_oconv;
+
+#define VAR_STRICT_8BITMIME "strict_8bitmime"
+#define DEF_STRICT_8BITMIME 0
+extern bool var_strict_8bitmime;
+
+#define VAR_STRICT_7BIT_HDRS "strict_7bit_headers"
+#define DEF_STRICT_7BIT_HDRS 0
+extern bool var_strict_7bit_hdrs;
+
+#define VAR_STRICT_8BIT_BODY "strict_8bitmime_body"
+#define DEF_STRICT_8BIT_BODY 0
+extern bool var_strict_8bit_body;
+
+#define VAR_STRICT_ENCODING "strict_mime_encoding_domain"
+#define DEF_STRICT_ENCODING 0
+extern bool var_strict_encoding;
+
+#define VAR_AUTO_8BIT_ENC_HDR "detect_8bit_encoding_header"
+#define DEF_AUTO_8BIT_ENC_HDR 1
+extern int var_auto_8bit_enc_hdr;
+
+ /*
+ * Bizarre.
+ */
+#define VAR_SENDER_ROUTING "sender_based_routing"
+#define DEF_SENDER_ROUTING 0
+extern bool var_sender_routing;
+
+#define VAR_XPORT_NULL_KEY "transport_null_address_lookup_key"
+#define DEF_XPORT_NULL_KEY "<>"
+extern char *var_xport_null_key;
+
+ /*
+ * Bounce service controls.
+ */
+#define VAR_OLDLOG_COMPAT "backwards_bounce_logfile_compatibility"
+#define DEF_OLDLOG_COMPAT 1
+extern bool var_oldlog_compat;
+
+ /*
+ * SMTPD content proxy.
+ */
+#define VAR_SMTPD_PROXY_FILT "smtpd_proxy_filter"
+#define DEF_SMTPD_PROXY_FILT ""
+extern char *var_smtpd_proxy_filt;
+
+#define VAR_SMTPD_PROXY_EHLO "smtpd_proxy_ehlo"
+#define DEF_SMTPD_PROXY_EHLO "$" VAR_MYHOSTNAME
+extern char *var_smtpd_proxy_ehlo;
+
+#define VAR_SMTPD_PROXY_TMOUT "smtpd_proxy_timeout"
+#define DEF_SMTPD_PROXY_TMOUT "100s"
+extern int var_smtpd_proxy_tmout;
+
+#define VAR_SMTPD_PROXY_OPTS "smtpd_proxy_options"
+#define DEF_SMTPD_PROXY_OPTS ""
+extern char *var_smtpd_proxy_opts;
+
+ /*
+ * Transparency options for mail input interfaces and for the cleanup server
+ * behind them. These should turn off stuff we don't want to happen, because
+ * the default is to do a lot of things.
+ */
+#define VAR_INPUT_TRANSP "receive_override_options"
+#define DEF_INPUT_TRANSP ""
+extern char *var_smtpd_input_transp;
+
+ /*
+ * SMTP server policy delegation.
+ */
+#define VAR_SMTPD_POLICY_TMOUT "smtpd_policy_service_timeout"
+#define DEF_SMTPD_POLICY_TMOUT "100s"
+extern int var_smtpd_policy_tmout;
+
+#define VAR_SMTPD_POLICY_REQ_LIMIT "smtpd_policy_service_request_limit"
+#define DEF_SMTPD_POLICY_REQ_LIMIT 0
+extern int var_smtpd_policy_req_limit;
+
+#define VAR_SMTPD_POLICY_IDLE "smtpd_policy_service_max_idle"
+#define DEF_SMTPD_POLICY_IDLE "300s"
+extern int var_smtpd_policy_idle;
+
+#define VAR_SMTPD_POLICY_TTL "smtpd_policy_service_max_ttl"
+#define DEF_SMTPD_POLICY_TTL "1000s"
+extern int var_smtpd_policy_ttl;
+
+#define VAR_SMTPD_POLICY_TRY_LIMIT "smtpd_policy_service_try_limit"
+#define DEF_SMTPD_POLICY_TRY_LIMIT 2
+extern int var_smtpd_policy_try_limit;
+
+#define VAR_SMTPD_POLICY_TRY_DELAY "smtpd_policy_service_retry_delay"
+#define DEF_SMTPD_POLICY_TRY_DELAY "1s"
+extern int var_smtpd_policy_try_delay;
+
+#define VAR_SMTPD_POLICY_DEF_ACTION "smtpd_policy_service_default_action"
+#define DEF_SMTPD_POLICY_DEF_ACTION "451 4.3.5 Server configuration problem"
+extern char *var_smtpd_policy_def_action;
+
+#define VAR_SMTPD_POLICY_CONTEXT "smtpd_policy_service_policy_context"
+#define DEF_SMTPD_POLICY_CONTEXT ""
+extern char *var_smtpd_policy_context;
+
+#define CHECK_POLICY_SERVICE "check_policy_service"
+
+ /*
+ * Client rate control.
+ */
+#define VAR_SMTPD_CRATE_LIMIT "smtpd_client_connection_rate_limit"
+#define DEF_SMTPD_CRATE_LIMIT 0
+extern int var_smtpd_crate_limit;
+
+#define VAR_SMTPD_CCONN_LIMIT "smtpd_client_connection_count_limit"
+#define DEF_SMTPD_CCONN_LIMIT ((DEF_PROC_LIMIT + 1) / 2)
+extern int var_smtpd_cconn_limit;
+
+#define VAR_SMTPD_CMAIL_LIMIT "smtpd_client_message_rate_limit"
+#define DEF_SMTPD_CMAIL_LIMIT 0
+extern int var_smtpd_cmail_limit;
+
+#define VAR_SMTPD_CRCPT_LIMIT "smtpd_client_recipient_rate_limit"
+#define DEF_SMTPD_CRCPT_LIMIT 0
+extern int var_smtpd_crcpt_limit;
+
+#define VAR_SMTPD_CNTLS_LIMIT "smtpd_client_new_tls_session_rate_limit"
+#define DEF_SMTPD_CNTLS_LIMIT 0
+extern int var_smtpd_cntls_limit;
+
+#define VAR_SMTPD_CAUTH_LIMIT "smtpd_client_auth_rate_limit"
+#define DEF_SMTPD_CAUTH_LIMIT 0
+extern int var_smtpd_cauth_limit;
+
+#define VAR_SMTPD_HOGGERS "smtpd_client_event_limit_exceptions"
+#define DEF_SMTPD_HOGGERS "${smtpd_client_connection_limit_exceptions:$" VAR_MYNETWORKS "}"
+extern char *var_smtpd_hoggers;
+
+#define VAR_ANVIL_TIME_UNIT "anvil_rate_time_unit"
+#define DEF_ANVIL_TIME_UNIT "60s"
+extern int var_anvil_time_unit;
+
+#define VAR_ANVIL_STAT_TIME "anvil_status_update_time"
+#define DEF_ANVIL_STAT_TIME "600s"
+extern int var_anvil_stat_time;
+
+ /*
+ * Temporary stop gap.
+ */
+#if 0
+#include <anvil_clnt.h>
+
+#define VAR_ANVIL_SERVICE "client_connection_rate_service_name"
+#define DEF_ANVIL_SERVICE "local:" ANVIL_CLASS "/" ANVIL_SERVICE
+extern char *var_anvil_service;
+
+#endif
+
+ /*
+ * What domain names to assume when no valid domain context exists.
+ */
+#define VAR_REM_RWR_DOMAIN "remote_header_rewrite_domain"
+#define DEF_REM_RWR_DOMAIN ""
+extern char *var_remote_rwr_domain;
+
+#define CHECK_ADDR_MAP "check_address_map"
+
+#define VAR_LOC_RWR_CLIENTS "local_header_rewrite_clients"
+#define DEF_LOC_RWR_CLIENTS PERMIT_INET_INTERFACES
+extern char *var_local_rwr_clients;
+
+ /*
+ * EHLO keyword filter.
+ */
+#define VAR_SMTPD_EHLO_DIS_WORDS "smtpd_discard_ehlo_keywords"
+#define DEF_SMTPD_EHLO_DIS_WORDS ""
+extern char *var_smtpd_ehlo_dis_words;
+
+#define VAR_SMTPD_EHLO_DIS_MAPS "smtpd_discard_ehlo_keyword_address_maps"
+#define DEF_SMTPD_EHLO_DIS_MAPS ""
+extern char *var_smtpd_ehlo_dis_maps;
+
+#define VAR_SMTP_EHLO_DIS_WORDS "smtp_discard_ehlo_keywords"
+#define DEF_SMTP_EHLO_DIS_WORDS ""
+#define VAR_LMTP_EHLO_DIS_WORDS "lmtp_discard_lhlo_keywords"
+#define DEF_LMTP_EHLO_DIS_WORDS ""
+extern char *var_smtp_ehlo_dis_words;
+
+#define VAR_SMTP_EHLO_DIS_MAPS "smtp_discard_ehlo_keyword_address_maps"
+#define DEF_SMTP_EHLO_DIS_MAPS ""
+#define VAR_LMTP_EHLO_DIS_MAPS "lmtp_discard_lhlo_keyword_address_maps"
+#define DEF_LMTP_EHLO_DIS_MAPS ""
+extern char *var_smtp_ehlo_dis_maps;
+
+ /*
+ * gcc workaround for warnings about empty or null format strings.
+ */
+extern const char null_format_string[1];
+
+ /*
+ * Characters to reject or strip.
+ */
+#define VAR_MSG_REJECT_CHARS "message_reject_characters"
+#define DEF_MSG_REJECT_CHARS ""
+extern char *var_msg_reject_chars;
+
+#define VAR_MSG_STRIP_CHARS "message_strip_characters"
+#define DEF_MSG_STRIP_CHARS ""
+extern char *var_msg_strip_chars;
+
+ /*
+ * Local forwarding complexity controls.
+ */
+#define VAR_FROZEN_DELIVERED "frozen_delivered_to"
+#define DEF_FROZEN_DELIVERED 1
+extern bool var_frozen_delivered;
+
+#define VAR_RESET_OWNER_ATTR "reset_owner_alias"
+#define DEF_RESET_OWNER_ATTR 0
+extern bool var_reset_owner_attr;
+
+ /*
+ * Delay logging time roundup.
+ */
+#define VAR_DELAY_MAX_RES "delay_logging_resolution_limit"
+#define MAX_DELAY_MAX_RES 6
+#define DEF_DELAY_MAX_RES 2
+#define MIN_DELAY_MAX_RES 0
+extern int var_delay_max_res;
+
+ /*
+ * Bounce message templates.
+ */
+#define VAR_BOUNCE_TMPL "bounce_template_file"
+#define DEF_BOUNCE_TMPL ""
+extern char *var_bounce_tmpl;
+
+ /*
+ * Sender-dependent authentication.
+ */
+#define VAR_SMTP_SENDER_AUTH "smtp_sender_dependent_authentication"
+#define DEF_SMTP_SENDER_AUTH 0
+#define VAR_LMTP_SENDER_AUTH "lmtp_sender_dependent_authentication"
+#define DEF_LMTP_SENDER_AUTH 0
+extern bool var_smtp_sender_auth;
+
+ /*
+ * Allow CNAME lookup result to override the server hostname.
+ */
+#define VAR_SMTP_CNAME_OVERR "smtp_cname_overrides_servername"
+#define DEF_SMTP_CNAME_OVERR 0
+#define VAR_LMTP_CNAME_OVERR "lmtp_cname_overrides_servername"
+#define DEF_LMTP_CNAME_OVERR 0
+extern bool var_smtp_cname_overr;
+
+ /*
+ * TLS library settings
+ */
+#define VAR_TLS_CNF_FILE "tls_config_file"
+#define DEF_TLS_CNF_FILE "default"
+extern char *var_tls_cnf_file;
+
+#define VAR_TLS_CNF_NAME "tls_config_name"
+#define DEF_TLS_CNF_NAME ""
+extern char *var_tls_cnf_name;
+
+
+#define VAR_TLS_HIGH_CLIST "tls_high_cipherlist"
+#define DEF_TLS_HIGH_CLIST "aNULL:-aNULL:HIGH:@STRENGTH"
+extern char *var_tls_high_clist;
+
+#define VAR_TLS_MEDIUM_CLIST "tls_medium_cipherlist"
+#define DEF_TLS_MEDIUM_CLIST "aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH"
+extern char *var_tls_medium_clist;
+
+#define VAR_TLS_LOW_CLIST "tls_low_cipherlist"
+#define DEF_TLS_LOW_CLIST "aNULL:-aNULL:HIGH:MEDIUM:LOW:+RC4:@STRENGTH"
+extern char *var_tls_low_clist;
+
+#define VAR_TLS_EXPORT_CLIST "tls_export_cipherlist"
+#define DEF_TLS_EXPORT_CLIST "aNULL:-aNULL:HIGH:MEDIUM:LOW:EXPORT:+RC4:@STRENGTH"
+extern char *var_tls_export_clist;
+
+#define VAR_TLS_NULL_CLIST "tls_null_cipherlist"
+#define DEF_TLS_NULL_CLIST "eNULL:!aNULL"
+extern char *var_tls_null_clist;
+
+#if defined(SN_X25519) && defined(NID_X25519)
+#define DEF_TLS_EECDH_AUTO_1 SN_X25519 " "
+#else
+#define DEF_TLS_EECDH_AUTO_1 ""
+#endif
+#if defined(SN_X448) && defined(NID_X448)
+#define DEF_TLS_EECDH_AUTO_2 SN_X448 " "
+#else
+#define DEF_TLS_EECDH_AUTO_2 ""
+#endif
+#if defined(SN_X9_62_prime256v1) && defined(NID_X9_62_prime256v1)
+#define DEF_TLS_EECDH_AUTO_3 SN_X9_62_prime256v1 " "
+#else
+#define DEF_TLS_EECDH_AUTO_3 ""
+#endif
+#if defined(SN_secp521r1) && defined(NID_secp521r1)
+#define DEF_TLS_EECDH_AUTO_4 SN_secp521r1 " "
+#else
+#define DEF_TLS_EECDH_AUTO_4 ""
+#endif
+#if defined(SN_secp384r1) && defined(NID_secp384r1)
+#define DEF_TLS_EECDH_AUTO_5 SN_secp384r1
+#else
+#define DEF_TLS_EECDH_AUTO_5 ""
+#endif
+
+#define VAR_TLS_EECDH_AUTO "tls_eecdh_auto_curves"
+#define DEF_TLS_EECDH_AUTO DEF_TLS_EECDH_AUTO_1 \
+ DEF_TLS_EECDH_AUTO_2 \
+ DEF_TLS_EECDH_AUTO_3 \
+ DEF_TLS_EECDH_AUTO_4 \
+ DEF_TLS_EECDH_AUTO_5
+extern char *var_tls_eecdh_auto;
+
+#define VAR_TLS_EECDH_STRONG "tls_eecdh_strong_curve"
+#define DEF_TLS_EECDH_STRONG "prime256v1"
+extern char *var_tls_eecdh_strong;
+
+#define VAR_TLS_EECDH_ULTRA "tls_eecdh_ultra_curve"
+#define DEF_TLS_EECDH_ULTRA "secp384r1"
+extern char *var_tls_eecdh_ultra;
+
+#define VAR_TLS_PREEMPT_CLIST "tls_preempt_cipherlist"
+#define DEF_TLS_PREEMPT_CLIST 0
+extern bool var_tls_preempt_clist;
+
+#define VAR_TLS_MULTI_WILDCARD "tls_wildcard_matches_multiple_labels"
+#define DEF_TLS_MULTI_WILDCARD 1
+extern bool var_tls_multi_wildcard;
+
+#define VAR_TLS_BUG_TWEAKS "tls_disable_workarounds"
+#define DEF_TLS_BUG_TWEAKS ""
+extern char *var_tls_bug_tweaks;
+
+#define VAR_TLS_SSL_OPTIONS "tls_ssl_options"
+#define DEF_TLS_SSL_OPTIONS ""
+extern char *var_tls_ssl_options;
+
+#define VAR_TLS_TKT_CIPHER "tls_session_ticket_cipher"
+#define DEF_TLS_TKT_CIPHER "aes-256-cbc"
+extern char *var_tls_tkt_cipher;
+
+#define VAR_TLS_BC_PKEY_FPRINT "tls_legacy_public_key_fingerprints"
+#define DEF_TLS_BC_PKEY_FPRINT 0
+extern bool var_tls_bc_pkey_fprint;
+
+#define VAR_TLS_SERVER_SNI_MAPS "tls_server_sni_maps"
+#define DEF_TLS_SERVER_SNI_MAPS ""
+extern char *var_tls_server_sni_maps;
+
+ /*
+ * Ordered list of DANE digest algorithms.
+ */
+#define VAR_TLS_DANE_DIGESTS "tls_dane_digests"
+#define DEF_TLS_DANE_DIGESTS "sha512 sha256"
+extern char *var_tls_dane_digests;
+
+ /*
+ * The default is incompatible with pre-TLSv1.0 protocols.
+ */
+#define VAR_TLS_FAST_SHUTDOWN "tls_fast_shutdown_enable"
+#define DEF_TLS_FAST_SHUTDOWN 1
+extern bool var_tls_fast_shutdown;
+
+ /*
+ * Sendmail-style mail filter support.
+ */
+#define VAR_SMTPD_MILTERS "smtpd_milters"
+#define DEF_SMTPD_MILTERS ""
+extern char *var_smtpd_milters;
+
+#define VAR_SMTPD_MILTER_MAPS "smtpd_milter_maps"
+#define DEF_SMTPD_MILTER_MAPS ""
+extern char *var_smtpd_milter_maps;
+
+#define SMTPD_MILTERS_DISABLE "DISABLE"
+
+#define VAR_CLEANUP_MILTERS "non_smtpd_milters"
+#define DEF_CLEANUP_MILTERS ""
+extern char *var_cleanup_milters;
+
+#define VAR_MILT_DEF_ACTION "milter_default_action"
+#define DEF_MILT_DEF_ACTION "tempfail"
+extern char *var_milt_def_action;
+
+#define VAR_MILT_CONN_MACROS "milter_connect_macros"
+#define DEF_MILT_CONN_MACROS "j {daemon_name} {daemon_addr} v _"
+extern char *var_milt_conn_macros;
+
+#define VAR_MILT_HELO_MACROS "milter_helo_macros"
+#define DEF_MILT_HELO_MACROS "{tls_version} {cipher} {cipher_bits}" \
+ " {cert_subject} {cert_issuer}"
+extern char *var_milt_helo_macros;
+
+#define VAR_MILT_MAIL_MACROS "milter_mail_macros"
+#define DEF_MILT_MAIL_MACROS "i {auth_type} {auth_authen}" \
+ " {auth_author} {mail_addr}" \
+ " {mail_host} {mail_mailer}"
+extern char *var_milt_mail_macros;
+
+#define VAR_MILT_RCPT_MACROS "milter_rcpt_macros"
+#define DEF_MILT_RCPT_MACROS "i {rcpt_addr} {rcpt_host}" \
+ " {rcpt_mailer}"
+extern char *var_milt_rcpt_macros;
+
+#define VAR_MILT_DATA_MACROS "milter_data_macros"
+#define DEF_MILT_DATA_MACROS "i"
+extern char *var_milt_data_macros;
+
+#define VAR_MILT_UNK_MACROS "milter_unknown_command_macros"
+#define DEF_MILT_UNK_MACROS ""
+extern char *var_milt_unk_macros;
+
+#define VAR_MILT_EOH_MACROS "milter_end_of_header_macros"
+#define DEF_MILT_EOH_MACROS "i"
+extern char *var_milt_eoh_macros;
+
+#define VAR_MILT_EOD_MACROS "milter_end_of_data_macros"
+#define DEF_MILT_EOD_MACROS "i"
+extern char *var_milt_eod_macros;
+
+#define VAR_MILT_CONN_TIME "milter_connect_timeout"
+#define DEF_MILT_CONN_TIME "30s"
+extern int var_milt_conn_time;
+
+#define VAR_MILT_CMD_TIME "milter_command_timeout"
+#define DEF_MILT_CMD_TIME "30s"
+extern int var_milt_cmd_time;
+
+#define VAR_MILT_MSG_TIME "milter_content_timeout"
+#define DEF_MILT_MSG_TIME "300s"
+extern int var_milt_msg_time;
+
+#define VAR_MILT_PROTOCOL "milter_protocol"
+#define DEF_MILT_PROTOCOL "6"
+extern char *var_milt_protocol;
+
+#define VAR_MILT_DEF_ACTION "milter_default_action"
+#define DEF_MILT_DEF_ACTION "tempfail"
+extern char *var_milt_def_action;
+
+#define VAR_MILT_DAEMON_NAME "milter_macro_daemon_name"
+#define DEF_MILT_DAEMON_NAME "$" VAR_MYHOSTNAME
+extern char *var_milt_daemon_name;
+
+#define VAR_MILT_V "milter_macro_v"
+#define DEF_MILT_V "$" VAR_MAIL_NAME " $" VAR_MAIL_VERSION
+extern char *var_milt_v;
+
+#define VAR_MILT_HEAD_CHECKS "milter_header_checks"
+#define DEF_MILT_HEAD_CHECKS ""
+extern char *var_milt_head_checks;
+
+#define VAR_MILT_MACRO_DEFLTS "milter_macro_defaults"
+#define DEF_MILT_MACRO_DEFLTS ""
+extern char *var_milt_macro_deflts;
+
+ /*
+ * What internal mail do we inspect/stamp/etc.? This is not yet safe enough
+ * to enable world-wide.
+ */
+#define INT_FILT_CLASS_NONE ""
+#define INT_FILT_CLASS_NOTIFY "notify"
+#define INT_FILT_CLASS_BOUNCE "bounce"
+
+#define VAR_INT_FILT_CLASSES "internal_mail_filter_classes"
+#define DEF_INT_FILT_CLASSES INT_FILT_CLASS_NONE
+extern char *var_int_filt_classes;
+
+ /*
+ * This could break logfile processors, so it's off by default.
+ */
+#define VAR_SMTPD_CLIENT_PORT_LOG "smtpd_client_port_logging"
+#define DEF_SMTPD_CLIENT_PORT_LOG 0
+extern bool var_smtpd_client_port_log;
+
+#define VAR_QMQPD_CLIENT_PORT_LOG "qmqpd_client_port_logging"
+#define DEF_QMQPD_CLIENT_PORT_LOG 0
+extern bool var_qmqpd_client_port_log;
+
+ /*
+ * Header/body checks in delivery agents.
+ */
+#define VAR_SMTP_HEAD_CHKS "smtp_header_checks"
+#define DEF_SMTP_HEAD_CHKS ""
+extern char *var_smtp_head_chks;
+
+#define VAR_SMTP_MIME_CHKS "smtp_mime_header_checks"
+#define DEF_SMTP_MIME_CHKS ""
+extern char *var_smtp_mime_chks;
+
+#define VAR_SMTP_NEST_CHKS "smtp_nested_header_checks"
+#define DEF_SMTP_NEST_CHKS ""
+extern char *var_smtp_nest_chks;
+
+#define VAR_SMTP_BODY_CHKS "smtp_body_checks"
+#define DEF_SMTP_BODY_CHKS ""
+extern char *var_smtp_body_chks;
+
+#define VAR_LMTP_HEAD_CHKS "lmtp_header_checks"
+#define DEF_LMTP_HEAD_CHKS ""
+#define VAR_LMTP_MIME_CHKS "lmtp_mime_header_checks"
+#define DEF_LMTP_MIME_CHKS ""
+#define VAR_LMTP_NEST_CHKS "lmtp_nested_header_checks"
+#define DEF_LMTP_NEST_CHKS ""
+#define VAR_LMTP_BODY_CHKS "lmtp_body_checks"
+#define DEF_LMTP_BODY_CHKS ""
+
+#define VAR_SMTP_ADDR_PREF "smtp_address_preference"
+#ifdef HAS_IPV6
+#define DEF_SMTP_ADDR_PREF INET_PROTO_NAME_ANY
+#else
+#define DEF_SMTP_ADDR_PREF INET_PROTO_NAME_IPV4
+#endif
+extern char *var_smtp_addr_pref;
+
+#define VAR_LMTP_ADDR_PREF "lmtp_address_preference"
+#define DEF_LMTP_ADDR_PREF DEF_SMTP_ADDR_PREF
+
+ /*
+ * Scheduler concurrency feedback algorithms.
+ */
+#define VAR_CONC_POS_FDBACK "default_destination_concurrency_positive_feedback"
+#define _CONC_POS_FDBACK "_destination_concurrency_positive_feedback"
+#define DEF_CONC_POS_FDBACK "1"
+extern char *var_conc_pos_feedback;
+
+#define VAR_CONC_NEG_FDBACK "default_destination_concurrency_negative_feedback"
+#define _CONC_NEG_FDBACK "_destination_concurrency_negative_feedback"
+#define DEF_CONC_NEG_FDBACK "1"
+extern char *var_conc_neg_feedback;
+
+#define CONC_FDBACK_NAME_WIN "concurrency"
+#define CONC_FDBACK_NAME_SQRT_WIN "sqrt_concurrency"
+
+#define VAR_CONC_COHORT_LIM "default_destination_concurrency_failed_cohort_limit"
+#define _CONC_COHORT_LIM "_destination_concurrency_failed_cohort_limit"
+#define DEF_CONC_COHORT_LIM 1
+extern int var_conc_cohort_limit;
+
+#define VAR_CONC_FDBACK_DEBUG "destination_concurrency_feedback_debug"
+#define DEF_CONC_FDBACK_DEBUG 0
+extern bool var_conc_feedback_debug;
+
+#define VAR_DEST_RATE_DELAY "default_destination_rate_delay"
+#define _DEST_RATE_DELAY "_destination_rate_delay"
+#define DEF_DEST_RATE_DELAY "0s"
+extern int var_dest_rate_delay;
+
+#define VAR_XPORT_RATE_DELAY "default_transport_rate_delay"
+#define _XPORT_RATE_DELAY "_transport_rate_delay"
+#define DEF_XPORT_RATE_DELAY "0s"
+extern int var_xport_rate_delay;
+
+ /*
+ * Stress handling.
+ */
+#define VAR_STRESS "stress"
+#define DEF_STRESS ""
+extern char *var_stress;
+
+ /*
+ * Mailbox ownership.
+ */
+#define VAR_STRICT_MBOX_OWNER "strict_mailbox_ownership"
+#define DEF_STRICT_MBOX_OWNER 1
+extern bool var_strict_mbox_owner;
+
+ /*
+ * Window scaling workaround.
+ */
+#define VAR_INET_WINDOW "tcp_windowsize"
+#define DEF_INET_WINDOW 0
+extern int var_inet_windowsize;
+
+ /*
+ * Plug-in multi-instance support. Only the first two parameters are used by
+ * Postfix itself; the other ones are reserved for the instance manager.
+ */
+#define VAR_MULTI_CONF_DIRS "multi_instance_directories"
+#define DEF_MULTI_CONF_DIRS ""
+extern char *var_multi_conf_dirs;
+
+#define VAR_MULTI_WRAPPER "multi_instance_wrapper"
+#define DEF_MULTI_WRAPPER ""
+extern char *var_multi_wrapper;
+
+#define VAR_MULTI_NAME "multi_instance_name"
+#define DEF_MULTI_NAME ""
+extern char *var_multi_name;
+
+#define VAR_MULTI_GROUP "multi_instance_group"
+#define DEF_MULTI_GROUP ""
+extern char *var_multi_group;
+
+#define VAR_MULTI_ENABLE "multi_instance_enable"
+#define DEF_MULTI_ENABLE 0
+extern bool var_multi_enable;
+
+ /*
+ * postmulti(1) instance manager
+ */
+#define VAR_MULTI_START_CMDS "postmulti_start_commands"
+#define DEF_MULTI_START_CMDS "start"
+extern char *var_multi_start_cmds;
+
+#define VAR_MULTI_STOP_CMDS "postmulti_stop_commands"
+#define DEF_MULTI_STOP_CMDS "stop abort drain quick-stop"
+extern char *var_multi_stop_cmds;
+
+#define VAR_MULTI_CNTRL_CMDS "postmulti_control_commands"
+#define DEF_MULTI_CNTRL_CMDS "reload flush"
+extern char *var_multi_cntrl_cmds;
+
+ /*
+ * postscreen(8)
+ */
+#define VAR_PSC_CACHE_MAP "postscreen_cache_map"
+#define DEF_PSC_CACHE_MAP "btree:$data_directory/postscreen_cache"
+extern char *var_psc_cache_map;
+
+#define VAR_SMTPD_SERVICE "smtpd_service_name"
+#define DEF_SMTPD_SERVICE "smtpd"
+extern char *var_smtpd_service;
+
+#define VAR_PSC_POST_QLIMIT "postscreen_post_queue_limit"
+#define DEF_PSC_POST_QLIMIT "$" VAR_PROC_LIMIT
+extern int var_psc_post_queue_limit;
+
+#define VAR_PSC_PRE_QLIMIT "postscreen_pre_queue_limit"
+#define DEF_PSC_PRE_QLIMIT "$" VAR_PROC_LIMIT
+extern int var_psc_pre_queue_limit;
+
+#define VAR_PSC_CACHE_RET "postscreen_cache_retention_time"
+#define DEF_PSC_CACHE_RET "7d"
+extern int var_psc_cache_ret;
+
+#define VAR_PSC_CACHE_SCAN "postscreen_cache_cleanup_interval"
+#define DEF_PSC_CACHE_SCAN "12h"
+extern int var_psc_cache_scan;
+
+#define VAR_PSC_GREET_WAIT "postscreen_greet_wait"
+#define DEF_PSC_GREET_WAIT "${stress?{2}:{6}}s"
+extern int var_psc_greet_wait;
+
+#define VAR_PSC_PREGR_BANNER "postscreen_greet_banner"
+#define DEF_PSC_PREGR_BANNER "$" VAR_SMTPD_BANNER
+extern char *var_psc_pregr_banner;
+
+#define VAR_PSC_PREGR_ENABLE "postscreen_greet_enable"
+#define DEF_PSC_PREGR_ENABLE no
+extern char *var_psc_pregr_enable;
+
+#define VAR_PSC_PREGR_ACTION "postscreen_greet_action"
+#define DEF_PSC_PREGR_ACTION "ignore"
+extern char *var_psc_pregr_action;
+
+#define VAR_PSC_PREGR_TTL "postscreen_greet_ttl"
+#define DEF_PSC_PREGR_TTL "1d"
+extern int var_psc_pregr_ttl;
+
+#define VAR_PSC_DNSBL_SITES "postscreen_dnsbl_sites"
+#define DEF_PSC_DNSBL_SITES ""
+extern char *var_psc_dnsbl_sites;
+
+#define VAR_PSC_DNSBL_THRESH "postscreen_dnsbl_threshold"
+#define DEF_PSC_DNSBL_THRESH 1
+extern int var_psc_dnsbl_thresh;
+
+#define VAR_PSC_DNSBL_WTHRESH "postscreen_dnsbl_whitelist_threshold"
+#define DEF_PSC_DNSBL_WTHRESH 0
+
+#define VAR_PSC_DNSBL_ALTHRESH "postscreen_dnsbl_allowlist_threshold"
+#define DEF_PSC_DNSBL_ALTHRESH \
+ "${" VAR_PSC_DNSBL_WTHRESH "?{$" VAR_PSC_DNSBL_WTHRESH "}:{0}}"
+extern int var_psc_dnsbl_althresh;
+
+#define VAR_PSC_DNSBL_ENABLE "postscreen_dnsbl_enable"
+#define DEF_PSC_DNSBL_ENABLE 0
+extern char *var_psc_dnsbl_enable;
+
+#define VAR_PSC_DNSBL_ACTION "postscreen_dnsbl_action"
+#define DEF_PSC_DNSBL_ACTION "ignore"
+extern char *var_psc_dnsbl_action;
+
+#define VAR_PSC_DNSBL_MIN_TTL "postscreen_dnsbl_min_ttl"
+#define DEF_PSC_DNSBL_MIN_TTL "60s"
+extern int var_psc_dnsbl_min_ttl;
+
+#define VAR_PSC_DNSBL_MAX_TTL "postscreen_dnsbl_max_ttl"
+#define DEF_PSC_DNSBL_MAX_TTL "${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h"
+extern int var_psc_dnsbl_max_ttl;
+
+#define VAR_PSC_DNSBL_REPLY "postscreen_dnsbl_reply_map"
+#define DEF_PSC_DNSBL_REPLY ""
+extern char *var_psc_dnsbl_reply;
+
+#define VAR_PSC_DNSBL_TMOUT "postscreen_dnsbl_timeout"
+#define DEF_PSC_DNSBL_TMOUT "10s"
+extern int var_psc_dnsbl_tmout;
+
+#define VAR_PSC_PIPEL_ENABLE "postscreen_pipelining_enable"
+#define DEF_PSC_PIPEL_ENABLE 0
+extern bool var_psc_pipel_enable;
+
+#define VAR_PSC_PIPEL_ACTION "postscreen_pipelining_action"
+#define DEF_PSC_PIPEL_ACTION "enforce"
+extern char *var_psc_pipel_action;
+
+#define VAR_PSC_PIPEL_TTL "postscreen_pipelining_ttl"
+#define DEF_PSC_PIPEL_TTL "30d"
+extern int var_psc_pipel_ttl;
+
+#define VAR_PSC_NSMTP_ENABLE "postscreen_non_smtp_command_enable"
+#define DEF_PSC_NSMTP_ENABLE 0
+extern bool var_psc_nsmtp_enable;
+
+#define VAR_PSC_NSMTP_ACTION "postscreen_non_smtp_command_action"
+#define DEF_PSC_NSMTP_ACTION "drop"
+extern char *var_psc_nsmtp_action;
+
+#define VAR_PSC_NSMTP_TTL "postscreen_non_smtp_command_ttl"
+#define DEF_PSC_NSMTP_TTL "30d"
+extern int var_psc_nsmtp_ttl;
+
+#define VAR_PSC_BARLF_ENABLE "postscreen_bare_newline_enable"
+#define DEF_PSC_BARLF_ENABLE 0
+extern bool var_psc_barlf_enable;
+
+#define VAR_PSC_BARLF_ACTION "postscreen_bare_newline_action"
+#define DEF_PSC_BARLF_ACTION "ignore"
+extern char *var_psc_barlf_action;
+
+#define VAR_PSC_BARLF_TTL "postscreen_bare_newline_ttl"
+#define DEF_PSC_BARLF_TTL "30d"
+extern int var_psc_barlf_ttl;
+
+#define VAR_PSC_BLIST_ACTION "postscreen_blacklist_action"
+#define DEF_PSC_BLIST_ACTION "ignore"
+
+#define VAR_PSC_DNLIST_ACTION "postscreen_denylist_action"
+#define DEF_PSC_DNLIST_ACTION \
+ "${" VAR_PSC_BLIST_ACTION "?{$" VAR_PSC_BLIST_ACTION "}:{" DEF_PSC_BLIST_ACTION "}}"
+extern char *var_psc_dnlist_nets;
+
+#define VAR_PSC_CMD_COUNT "postscreen_command_count_limit"
+#define DEF_PSC_CMD_COUNT 20
+extern int var_psc_cmd_count;
+
+#define VAR_PSC_CMD_TIME "postscreen_command_time_limit"
+#define DEF_PSC_CMD_TIME DEF_SMTPD_TMOUT
+extern int var_psc_cmd_time;
+
+#define VAR_PSC_WATCHDOG "postscreen_watchdog_timeout"
+#define DEF_PSC_WATCHDOG "10s"
+extern int var_psc_watchdog;
+
+#define VAR_PSC_EHLO_DIS_WORDS "postscreen_discard_ehlo_keywords"
+#define DEF_PSC_EHLO_DIS_WORDS "$" VAR_SMTPD_EHLO_DIS_WORDS
+extern char *var_psc_ehlo_dis_words;
+
+#define VAR_PSC_EHLO_DIS_MAPS "postscreen_discard_ehlo_keyword_address_maps"
+#define DEF_PSC_EHLO_DIS_MAPS "$" VAR_SMTPD_EHLO_DIS_MAPS
+extern char *var_psc_ehlo_dis_maps;
+
+#define VAR_PSC_TLS_LEVEL "postscreen_tls_security_level"
+#define DEF_PSC_TLS_LEVEL "$" VAR_SMTPD_TLS_LEVEL
+extern char *var_psc_tls_level;
+
+#define VAR_PSC_USE_TLS "postscreen_use_tls"
+#define DEF_PSC_USE_TLS "$" VAR_SMTPD_USE_TLS
+extern bool var_psc_use_tls;
+
+#define VAR_PSC_ENFORCE_TLS "postscreen_enforce_tls"
+#define DEF_PSC_ENFORCE_TLS "$" VAR_SMTPD_ENFORCE_TLS
+extern bool var_psc_enforce_tls;
+
+#define VAR_PSC_FORBID_CMDS "postscreen_forbidden_commands"
+#define DEF_PSC_FORBID_CMDS "$" VAR_SMTPD_FORBID_CMDS
+extern char *var_psc_forbid_cmds;
+
+#define VAR_PSC_HELO_REQUIRED "postscreen_helo_required"
+#define DEF_PSC_HELO_REQUIRED "$" VAR_HELO_REQUIRED
+extern bool var_psc_helo_required;
+
+#define VAR_PSC_DISABLE_VRFY "postscreen_disable_vrfy_command"
+#define DEF_PSC_DISABLE_VRFY "$" VAR_DISABLE_VRFY_CMD
+extern bool var_psc_disable_vrfy;
+
+#define VAR_PSC_CCONN_LIMIT "postscreen_client_connection_count_limit"
+#define DEF_PSC_CCONN_LIMIT "$" VAR_SMTPD_CCONN_LIMIT
+extern int var_psc_cconn_limit;
+
+#define VAR_PSC_REJ_FOOTER "postscreen_reject_footer"
+#define DEF_PSC_REJ_FOOTER "$" VAR_SMTPD_REJ_FOOTER
+extern char *var_psc_rej_footer;
+
+#define VAR_PSC_REJ_FTR_MAPS "postscreen_reject_footer_maps"
+#define DEF_PSC_REJ_FTR_MAPS "$" VAR_SMTPD_REJ_FTR_MAPS
+extern char *var_psc_rej_ftr_maps;
+
+#define VAR_PSC_EXP_FILTER "postscreen_expansion_filter"
+#define DEF_PSC_EXP_FILTER "$" VAR_SMTPD_EXP_FILTER
+extern char *var_psc_exp_filter;
+
+#define VAR_PSC_CMD_FILTER "postscreen_command_filter"
+#define DEF_PSC_CMD_FILTER ""
+extern char *var_psc_cmd_filter;
+
+#define VAR_PSC_ACL "postscreen_access_list"
+#define DEF_PSC_ACL SERVER_ACL_NAME_WL_MYNETWORKS
+extern char *var_psc_acl;
+
+#define VAR_PSC_WLIST_IF "postscreen_whitelist_interfaces"
+#define DEF_PSC_WLIST_IF "static:all"
+
+#define VAR_PSC_ALLIST_IF "postscreen_allowlist_interfaces"
+#define DEF_PSC_ALLIST_IF \
+ "${" VAR_PSC_WLIST_IF "?{$" VAR_PSC_WLIST_IF "}:{" DEF_PSC_WLIST_IF "}}"
+extern char *var_psc_allist_if;
+
+#define NOPROXY_PROTO_NAME ""
+
+#define VAR_PSC_UPROXY_PROTO "postscreen_upstream_proxy_protocol"
+#define DEF_PSC_UPROXY_PROTO NOPROXY_PROTO_NAME
+extern char *var_psc_uproxy_proto;
+
+#define VAR_PSC_UPROXY_TMOUT "postscreen_upstream_proxy_timeout"
+#define DEF_PSC_UPROXY_TMOUT "5s"
+extern int var_psc_uproxy_tmout;
+
+#define VAR_RESPECTFUL_LOGGING "respectful_logging"
+#define DEF_RESPECTFUL_LOGGING \
+ "${{$compatibility_level} <level {3.6} ?" " {no} : {yes}}"
+extern bool var_respectful_logging;
+
+#define VAR_DNSBLOG_SERVICE "dnsblog_service_name"
+#define DEF_DNSBLOG_SERVICE MAIL_SERVICE_DNSBLOG
+extern char *var_dnsblog_service;
+
+#define VAR_DNSBLOG_DELAY "dnsblog_reply_delay"
+#define DEF_DNSBLOG_DELAY "0s"
+extern int var_dnsblog_delay;
+
+#define VAR_TLSPROXY_SERVICE "tlsproxy_service_name"
+#define DEF_TLSPROXY_SERVICE MAIL_SERVICE_TLSPROXY
+extern char *var_tlsproxy_service;
+
+#define VAR_TLSP_WATCHDOG "tlsproxy_watchdog_timeout"
+#define DEF_TLSP_WATCHDOG "10s"
+extern int var_tlsp_watchdog;
+
+#define VAR_TLSP_TLS_LEVEL "tlsproxy_tls_security_level"
+#define DEF_TLSP_TLS_LEVEL "$" VAR_SMTPD_TLS_LEVEL
+extern char *var_tlsp_tls_level;
+
+#define VAR_TLSP_USE_TLS "tlsproxy_use_tls"
+#define DEF_TLSP_USE_TLS "$" VAR_SMTPD_USE_TLS
+extern bool var_tlsp_use_tls;
+
+#define VAR_TLSP_ENFORCE_TLS "tlsproxy_enforce_tls"
+#define DEF_TLSP_ENFORCE_TLS "$" VAR_SMTPD_ENFORCE_TLS
+extern bool var_tlsp_enforce_tls;
+
+#define VAR_TLSP_TLS_ACERT "tlsproxy_tls_ask_ccert"
+#define DEF_TLSP_TLS_ACERT "$" VAR_SMTPD_TLS_ACERT
+extern bool var_tlsp_tls_ask_ccert;
+
+#define VAR_TLSP_TLS_RCERT "tlsproxy_tls_req_ccert"
+#define DEF_TLSP_TLS_RCERT "$" VAR_SMTPD_TLS_RCERT
+extern bool var_tlsp_tls_req_ccert;
+
+#define VAR_TLSP_TLS_CCERT_VD "tlsproxy_tls_ccert_verifydepth"
+#define DEF_TLSP_TLS_CCERT_VD "$" VAR_SMTPD_TLS_CCERT_VD
+extern int var_tlsp_tls_ccert_vd;
+
+#define VAR_TLSP_TLS_CHAIN_FILES "tlsproxy_tls_chain_files"
+#define DEF_TLSP_TLS_CHAIN_FILES "$" VAR_SMTPD_TLS_CHAIN_FILES
+extern char *var_tlsp_tls_chain_files;
+
+#define VAR_TLSP_TLS_CERT_FILE "tlsproxy_tls_cert_file"
+#define DEF_TLSP_TLS_CERT_FILE "$" VAR_SMTPD_TLS_CERT_FILE
+extern char *var_tlsp_tls_cert_file;
+
+#define VAR_TLSP_TLS_KEY_FILE "tlsproxy_tls_key_file"
+#define DEF_TLSP_TLS_KEY_FILE "$" VAR_SMTPD_TLS_KEY_FILE
+extern char *var_tlsp_tls_key_file;
+
+#define VAR_TLSP_TLS_DCERT_FILE "tlsproxy_tls_dcert_file"
+#define DEF_TLSP_TLS_DCERT_FILE "$" VAR_SMTPD_TLS_DCERT_FILE
+extern char *var_tlsp_tls_dcert_file;
+
+#define VAR_TLSP_TLS_DKEY_FILE "tlsproxy_tls_dkey_file"
+#define DEF_TLSP_TLS_DKEY_FILE "$" VAR_SMTPD_TLS_DKEY_FILE
+extern char *var_tlsp_tls_dkey_file;
+
+#define VAR_TLSP_TLS_ECCERT_FILE "tlsproxy_tls_eccert_file"
+#define DEF_TLSP_TLS_ECCERT_FILE "$" VAR_SMTPD_TLS_ECCERT_FILE
+extern char *var_tlsp_tls_eccert_file;
+
+#define VAR_TLSP_TLS_ECKEY_FILE "tlsproxy_tls_eckey_file"
+#define DEF_TLSP_TLS_ECKEY_FILE "$" VAR_SMTPD_TLS_ECKEY_FILE
+extern char *var_tlsp_tls_eckey_file;
+
+#define DEF_TLSP_TLS_ECKEY_FILE "$" VAR_SMTPD_TLS_ECKEY_FILE
+extern char *var_tlsp_tls_eckey_file;
+
+#define VAR_TLSP_TLS_CA_FILE "tlsproxy_tls_CAfile"
+#define DEF_TLSP_TLS_CA_FILE "$" VAR_SMTPD_TLS_CA_FILE
+extern char *var_tlsp_tls_CAfile;
+
+#define VAR_TLSP_TLS_CA_PATH "tlsproxy_tls_CApath"
+#define DEF_TLSP_TLS_CA_PATH "$" VAR_SMTPD_TLS_CA_PATH
+extern char *var_tlsp_tls_CApath;
+
+#define VAR_TLSP_TLS_PROTO "tlsproxy_tls_protocols"
+#define DEF_TLSP_TLS_PROTO "$" VAR_SMTPD_TLS_PROTO
+extern char *var_tlsp_tls_proto;
+
+#define VAR_TLSP_TLS_MAND_PROTO "tlsproxy_tls_mandatory_protocols"
+#define DEF_TLSP_TLS_MAND_PROTO "$" VAR_SMTPD_TLS_MAND_PROTO
+extern char *var_tlsp_tls_mand_proto;
+
+#define VAR_TLSP_TLS_CIPH "tlsproxy_tls_ciphers"
+#define DEF_TLSP_TLS_CIPH "$" VAR_SMTPD_TLS_CIPH
+extern char *var_tlsp_tls_ciph;
+
+#define VAR_TLSP_TLS_MAND_CIPH "tlsproxy_tls_mandatory_ciphers"
+#define DEF_TLSP_TLS_MAND_CIPH "$" VAR_SMTPD_TLS_MAND_CIPH
+extern char *var_tlsp_tls_mand_ciph;
+
+#define VAR_TLSP_TLS_EXCL_CIPH "tlsproxy_tls_exclude_ciphers"
+#define DEF_TLSP_TLS_EXCL_CIPH "$" VAR_SMTPD_TLS_EXCL_CIPH
+extern char *var_tlsp_tls_excl_ciph;
+
+#define VAR_TLSP_TLS_MAND_EXCL "tlsproxy_tls_mandatory_exclude_ciphers"
+#define DEF_TLSP_TLS_MAND_EXCL "$" VAR_SMTPD_TLS_MAND_EXCL
+extern char *var_tlsp_tls_mand_excl;
+
+#define VAR_TLSP_TLS_FPT_DGST "tlsproxy_tls_fingerprint_digest"
+#define DEF_TLSP_TLS_FPT_DGST "$" VAR_SMTPD_TLS_FPT_DGST
+extern char *var_tlsp_tls_fpt_dgst;
+
+#define VAR_TLSP_TLS_512_FILE "tlsproxy_tls_dh512_param_file"
+#define DEF_TLSP_TLS_512_FILE "$" VAR_SMTPD_TLS_512_FILE
+extern char *var_tlsp_tls_dh512_param_file;
+
+#define VAR_TLSP_TLS_1024_FILE "tlsproxy_tls_dh1024_param_file"
+#define DEF_TLSP_TLS_1024_FILE "$" VAR_SMTPD_TLS_1024_FILE
+extern char *var_tlsp_tls_dh1024_param_file;
+
+#define VAR_TLSP_TLS_EECDH "tlsproxy_tls_eecdh_grade"
+#define DEF_TLSP_TLS_EECDH "$" VAR_SMTPD_TLS_EECDH
+extern char *var_tlsp_tls_eecdh;
+
+#define VAR_TLSP_TLS_LOGLEVEL "tlsproxy_tls_loglevel"
+#define DEF_TLSP_TLS_LOGLEVEL "$" VAR_SMTPD_TLS_LOGLEVEL
+extern char *var_tlsp_tls_loglevel;
+
+#define VAR_TLSP_TLS_RECHEAD "tlsproxy_tls_received_header"
+#define DEF_TLSP_TLS_RECHEAD "$" VAR_SMTPD_TLS_RECHEAD
+extern bool var_tlsp_tls_received_header;
+
+#define VAR_TLSP_TLS_SET_SESSID "tlsproxy_tls_always_issue_session_ids"
+#define DEF_TLSP_TLS_SET_SESSID "$" VAR_SMTPD_TLS_SET_SESSID
+extern bool var_tlsp_tls_set_sessid;
+
+ /*
+ * Workaround for tlsproxy(8) pre-jail client certs/keys access.
+ */
+#define VAR_TLSP_CLNT_LOGLEVEL "tlsproxy_client_loglevel"
+#define DEF_TLSP_CLNT_LOGLEVEL "$" VAR_SMTP_TLS_LOGLEVEL
+extern char *var_tlsp_clnt_loglevel;
+
+#define VAR_TLSP_CLNT_LOGPARAM "tlsproxy_client_loglevel_parameter"
+#define DEF_TLSP_CLNT_LOGPARAM VAR_SMTP_TLS_LOGLEVEL
+extern char *var_tlsp_clnt_logparam;
+
+#define VAR_TLSP_CLNT_SCERT_VD "tlsproxy_client_scert_verifydepth"
+#define DEF_TLSP_CLNT_SCERT_VD "$" VAR_SMTP_TLS_SCERT_VD
+extern int var_tlsp_clnt_scert_vd;
+
+#define VAR_TLSP_CLNT_CHAIN_FILES "tlsproxy_client_chain_files"
+#define DEF_TLSP_CLNT_CHAIN_FILES "$" VAR_SMTP_TLS_CHAIN_FILES
+extern char *var_tlsp_clnt_chain_files;
+
+#define VAR_TLSP_CLNT_CERT_FILE "tlsproxy_client_cert_file"
+#define DEF_TLSP_CLNT_CERT_FILE "$" VAR_SMTP_TLS_CERT_FILE
+extern char *var_tlsp_clnt_cert_file;
+
+#define VAR_TLSP_CLNT_KEY_FILE "tlsproxy_client_key_file"
+#define DEF_TLSP_CLNT_KEY_FILE "$" VAR_SMTP_TLS_KEY_FILE
+extern char *var_tlsp_clnt_key_file;
+
+#define VAR_TLSP_CLNT_DCERT_FILE "tlsproxy_client_dcert_file"
+#define DEF_TLSP_CLNT_DCERT_FILE "$" VAR_SMTP_TLS_DCERT_FILE
+extern char *var_tlsp_clnt_dcert_file;
+
+#define VAR_TLSP_CLNT_DKEY_FILE "tlsproxy_client_dkey_file"
+#define DEF_TLSP_CLNT_DKEY_FILE "$" VAR_SMTP_TLS_DKEY_FILE
+extern char *var_tlsp_clnt_dkey_file;
+
+#define VAR_TLSP_CLNT_ECCERT_FILE "tlsproxy_client_eccert_file"
+#define DEF_TLSP_CLNT_ECCERT_FILE "$" VAR_SMTP_TLS_ECCERT_FILE
+extern char *var_tlsp_clnt_eccert_file;
+
+#define VAR_TLSP_CLNT_ECKEY_FILE "tlsproxy_client_eckey_file"
+#define DEF_TLSP_CLNT_ECKEY_FILE "$" VAR_SMTP_TLS_ECKEY_FILE
+extern char *var_tlsp_clnt_eckey_file;
+
+#define VAR_TLSP_CLNT_CAFILE "tlsproxy_client_CAfile"
+#define DEF_TLSP_CLNT_CAFILE "$" VAR_SMTP_TLS_CA_FILE
+extern char *var_tlsp_clnt_CAfile;
+
+#define VAR_TLSP_CLNT_CAPATH "tlsproxy_client_CApath"
+#define DEF_TLSP_CLNT_CAPATH "$" VAR_SMTP_TLS_CA_PATH
+extern char *var_tlsp_clnt_CApath;
+
+#define VAR_TLSP_CLNT_FPT_DGST "tlsproxy_client_fingerprint_digest"
+#define DEF_TLSP_CLNT_FPT_DGST "$" VAR_SMTP_TLS_FPT_DGST
+extern char *var_tlsp_clnt_fpt_dgst;
+
+#define VAR_TLSP_CLNT_USE_TLS "tlsproxy_client_use_tls"
+#define DEF_TLSP_CLNT_USE_TLS "$" VAR_SMTP_USE_TLS
+extern bool var_tlsp_clnt_use_tls;
+
+#define VAR_TLSP_CLNT_ENFORCE_TLS "tlsproxy_client_enforce_tls"
+#define DEF_TLSP_CLNT_ENFORCE_TLS "$" VAR_SMTP_ENFORCE_TLS
+extern bool var_tlsp_clnt_enforce_tls;
+
+/* Migrate an incorrect name. */
+#define OBS_TLSP_CLNT_LEVEL "tlsproxy_client_level"
+#define VAR_TLSP_CLNT_LEVEL "tlsproxy_client_security_level"
+#define DEF_TLSP_CLNT_LEVEL "${" OBS_TLSP_CLNT_LEVEL ":$" VAR_SMTP_TLS_LEVEL "}"
+extern char *var_tlsp_clnt_level;
+
+#define VAR_TLSP_CLNT_PER_SITE "tlsproxy_client_per_site"
+#define DEF_TLSP_CLNT_PER_SITE "$" VAR_SMTP_TLS_PER_SITE
+extern char *var_tlsp_clnt_per_site;
+
+/* Migrate an incorrect name. */
+#define OBS_TLSP_CLNT_POLICY "tlsproxy_client_policy"
+#define VAR_TLSP_CLNT_POLICY "tlsproxy_client_policy_maps"
+#define DEF_TLSP_CLNT_POLICY "${" OBS_TLSP_CLNT_POLICY ":$" VAR_SMTP_TLS_POLICY "}"
+extern char *var_tlsp_clnt_policy;
+
+ /*
+ * SMTPD "reject" contact info.
+ */
+#define VAR_SMTPD_REJ_FOOTER "smtpd_reject_footer"
+#define DEF_SMTPD_REJ_FOOTER ""
+extern char *var_smtpd_rej_footer;
+
+#define VAR_SMTPD_REJ_FTR_MAPS "smtpd_reject_footer_maps"
+#define DEF_SMTPD_REJ_FTR_MAPS ""
+extern char *var_smtpd_rej_ftr_maps;
+
+ /*
+ * Per-record time limit support.
+ */
+#define VAR_SMTPD_REC_DEADLINE "smtpd_per_record_deadline"
+#define DEF_SMTPD_REC_DEADLINE "${stress?{yes}:{no}}"
+extern bool var_smtpd_rec_deadline;
+
+#define VAR_SMTP_REC_DEADLINE "smtp_per_record_deadline"
+#define DEF_SMTP_REC_DEADLINE 0
+#define VAR_LMTP_REC_DEADLINE "lmtp_per_record_deadline"
+#define DEF_LMTP_REC_DEADLINE 0
+extern bool var_smtp_rec_deadline;
+
+#define VAR_SMTPD_REQ_DEADLINE "smtpd_per_request_deadline"
+#define DEF_SMTPD_REQ_DEADLINE "${smtpd_per_record_deadline?" \
+ "{$smtpd_per_record_deadline}:" \
+ "{${stress?{yes}:{no}}}}"
+extern bool var_smtpd_req_deadline;
+
+#define VAR_SMTP_REQ_DEADLINE "smtp_per_request_deadline"
+#define DEF_SMTP_REQ_DEADLINE "${smtp_per_record_deadline?" \
+ "{$smtp_per_record_deadline}:{no}}"
+#define VAR_LMTP_REQ_DEADLINE "lmtp_per_request_deadline"
+#define DEF_LMTP_REQ_DEADLINE "${lmtp_per_record_deadline?" \
+ "{$lmtp_per_record_deadline}:{no}}"
+extern bool var_smtp_req_deadline;
+
+#define VAR_SMTPD_MIN_DATA_RATE "smtpd_min_data_rate"
+#define DEF_SMTPD_MIN_DATA_RATE 500
+extern int var_smtpd_min_data_rate;
+
+#define VAR_SMTP_MIN_DATA_RATE "smtp_min_data_rate"
+#define DEF_SMTP_MIN_DATA_RATE 500
+#define VAR_LMTP_MIN_DATA_RATE "lmtp_min_data_rate"
+#define DEF_LMTP_MIN_DATA_RATE 500
+extern int var_smtp_min_data_rate;
+
+ /*
+ * Permit logging.
+ */
+#define VAR_SMTPD_ACL_PERM_LOG "smtpd_log_access_permit_actions"
+#define DEF_SMTPD_ACL_PERM_LOG ""
+extern char *var_smtpd_acl_perm_log;
+
+ /*
+ * Before-smtpd proxy support.
+ */
+#define VAR_SMTPD_UPROXY_PROTO "smtpd_upstream_proxy_protocol"
+#define DEF_SMTPD_UPROXY_PROTO ""
+extern char *var_smtpd_uproxy_proto;
+
+#define VAR_SMTPD_UPROXY_TMOUT "smtpd_upstream_proxy_timeout"
+#define DEF_SMTPD_UPROXY_TMOUT "5s"
+extern int var_smtpd_uproxy_tmout;
+
+ /*
+ * Postfix sendmail command compatibility features.
+ */
+#define SM_FIX_EOL_STRICT "strict"
+#define SM_FIX_EOL_NEVER "never"
+#define SM_FIX_EOL_ALWAYS "always"
+
+#define VAR_SM_FIX_EOL "sendmail_fix_line_endings"
+#define DEF_SM_FIX_EOL SM_FIX_EOL_ALWAYS
+extern char *var_sm_fix_eol;
+
+ /*
+ * Gradual degradation, or fatal exit after table open error?
+ */
+#define VAR_DAEMON_OPEN_FATAL "daemon_table_open_error_is_fatal"
+#define DEF_DAEMON_OPEN_FATAL 0
+extern bool var_daemon_open_fatal;
+
+ /*
+ * Optional delivery status filter.
+ */
+#define VAR_DSN_FILTER "default_delivery_status_filter"
+#define DEF_DSN_FILTER ""
+extern char *var_dsn_filter;
+
+#define VAR_SMTP_DSN_FILTER "smtp_delivery_status_filter"
+#define DEF_SMTP_DSN_FILTER "$" VAR_DSN_FILTER
+#define VAR_LMTP_DSN_FILTER "lmtp_delivery_status_filter"
+#define DEF_LMTP_DSN_FILTER "$" VAR_DSN_FILTER
+extern char *var_smtp_dsn_filter;
+
+#define VAR_PIPE_DSN_FILTER "pipe_delivery_status_filter"
+#define DEF_PIPE_DSN_FILTER "$" VAR_DSN_FILTER
+extern char *var_pipe_dsn_filter;
+
+#define VAR_VIRT_DSN_FILTER "virtual_delivery_status_filter"
+#define DEF_VIRT_DSN_FILTER "$" VAR_DSN_FILTER
+extern char *var_virt_dsn_filter;
+
+#define VAR_LOCAL_DSN_FILTER "local_delivery_status_filter"
+#define DEF_LOCAL_DSN_FILTER "$" VAR_DSN_FILTER
+extern char *var_local_dsn_filter;
+
+ /*
+ * Optional DNS reply filter.
+ */
+#define VAR_SMTP_DNS_RE_FILTER "smtp_dns_reply_filter"
+#define DEF_SMTP_DNS_RE_FILTER ""
+#define VAR_LMTP_DNS_RE_FILTER "lmtp_dns_reply_filter"
+#define DEF_LMTP_DNS_RE_FILTER ""
+extern char *var_smtp_dns_re_filter;
+
+#define VAR_SMTPD_DNS_RE_FILTER "smtpd_dns_reply_filter"
+#define DEF_SMTPD_DNS_RE_FILTER ""
+extern char *var_smtpd_dns_re_filter;
+
+ /*
+ * Backwards compatibility.
+ */
+#define VAR_SMTPD_FORBID_BARE_LF "smtpd_forbid_bare_newline"
+#define DEF_SMTPD_FORBID_BARE_LF "no"
+
+#define VAR_SMTPD_FORBID_BARE_LF_EXCL "smtpd_forbid_bare_newline_exclusions"
+#define DEF_SMTPD_FORBID_BARE_LF_EXCL "$" VAR_MYNETWORKS
+
+#define VAR_SMTPD_FORBID_BARE_LF_CODE "smtpd_forbid_bare_newline_reject_code"
+#define DEF_SMTPD_FORBID_BARE_LF_CODE 550
+
+#define VAR_CLEANUP_MASK_STRAY_CR_LF "cleanup_replace_stray_cr_lf"
+#define DEF_CLEANUP_MASK_STRAY_CR_LF 1
+extern int var_cleanup_mask_stray_cr_lf;
+
+ /*
+ * Share TLS sessions through tlsproxy(8).
+ */
+#define VAR_SMTP_TLS_CONN_REUSE "smtp_tls_connection_reuse"
+#define DEF_SMTP_TLS_CONN_REUSE 0
+#define VAR_LMTP_TLS_CONN_REUSE "lmtp_tls_connection_reuse"
+#define DEF_LMTP_TLS_CONN_REUSE 0
+extern bool var_smtp_tls_conn_reuse;
+
+ /*
+ * Location of shared-library files.
+ *
+ * If the files will be installed into a known directory, such as a directory
+ * that is processed with the ldconfig(1) command, then the shlib_directory
+ * parameter may be configured at installation time.
+ *
+ * Otherwise, the shlib_directory parameter must be specified at compile time,
+ * and it cannot be changed afterwards.
+ */
+#define VAR_SHLIB_DIR "shlib_directory"
+#ifndef DEF_SHLIB_DIR
+#define DEF_SHLIB_DIR "/usr/lib/postfix"
+#endif
+extern char *var_shlib_dir;
+
+#define VAR_META_DIR "meta_directory"
+#ifndef DEF_META_DIR
+#define DEF_META_DIR DEF_CONFIG_DIR
+#endif
+extern char *var_meta_dir;
+
+ /*
+ * SMTPUTF8 support.
+ */
+#define VAR_SMTPUTF8_ENABLE "smtputf8_enable"
+#ifndef DEF_SMTPUTF8_ENABLE
+#define DEF_SMTPUTF8_ENABLE "${{$compatibility_level} <level {1} ? " \
+ "{no} : {yes}}"
+#endif
+extern int var_smtputf8_enable;
+
+#define VAR_STRICT_SMTPUTF8 "strict_smtputf8"
+#define DEF_STRICT_SMTPUTF8 0
+extern int var_strict_smtputf8;
+
+#define VAR_SMTPUTF8_AUTOCLASS "smtputf8_autodetect_classes"
+#define DEF_SMTPUTF8_AUTOCLASS MAIL_SRC_NAME_SENDMAIL ", " \
+ MAIL_SRC_NAME_VERIFY
+extern char *var_smtputf8_autoclass;
+
+#define VAR_IDNA2003_COMPAT "enable_idna2003_compatibility"
+#define DEF_IDNA2003_COMPAT "no"
+extern int var_idna2003_compat;
+
+ /*
+ * Workaround for future incompatibility. Our implementation of RFC 2308
+ * negative reply caching relies on the promise that res_query() and
+ * res_search() invoke res_send(), which returns the server response in an
+ * application buffer even if the requested record does not exist. If this
+ * promise is broken, we have a workaround that is good enough for DNS
+ * reputation lookups.
+ */
+#define VAR_DNS_NCACHE_TTL_FIX "dns_ncache_ttl_fix_enable"
+#define DEF_DNS_NCACHE_TTL_FIX 0
+extern bool var_dns_ncache_ttl_fix;
+
+ /*
+ * Logging. As systems evolve over time, logging becomes more challenging.
+ */
+#define VAR_MAILLOG_FILE "maillog_file"
+#define DEF_MAILLOG_FILE ""
+extern char *var_maillog_file;
+
+#define VAR_MAILLOG_FILE_PFXS "maillog_file_prefixes"
+#define DEF_MAILLOG_FILE_PFXS "/var, /dev/stdout"
+extern char *var_maillog_file_pfxs;
+
+#define VAR_MAILLOG_FILE_COMP "maillog_file_compressor"
+#define DEF_MAILLOG_FILE_COMP "gzip"
+extern char *var_maillog_file_comp;
+
+#define VAR_MAILLOG_FILE_STAMP "maillog_file_rotate_suffix"
+#define DEF_MAILLOG_FILE_STAMP "%Y%m%d-%H%M%S"
+extern char *var_maillog_file_stamp;
+
+#define VAR_POSTLOG_SERVICE "postlog_service_name"
+#define DEF_POSTLOG_SERVICE MAIL_SERVICE_POSTLOG
+extern char *var_postlog_service;
+
+#define VAR_POSTLOGD_WATCHDOG "postlogd_watchdog_timeout"
+#define DEF_POSTLOGD_WATCHDOG "10s"
+extern int var_postlogd_watchdog;
+
+ /*
+ * Backwards compatibility for internal-form address logging.
+ */
+#define INFO_LOG_ADDR_FORM_NAME_EXTERNAL "external"
+#define INFO_LOG_ADDR_FORM_NAME_INTERNAL "internal"
+
+#define VAR_INFO_LOG_ADDR_FORM "info_log_address_format"
+#define DEF_INFO_LOG_ADDR_FORM INFO_LOG_ADDR_FORM_NAME_EXTERNAL
+extern char *var_info_log_addr_form;
+
+ /*
+ * DNSSEC probing, to find out if DNSSEC validation is available.
+ */
+#define VAR_DNSSEC_PROBE "dnssec_probe"
+#define DEF_DNSSEC_PROBE "ns:."
+extern char *var_dnssec_probe;
+
+ /*
+ * Pre-empt services(5) lookups.
+ */
+#define VAR_KNOWN_TCP_PORTS "known_tcp_ports"
+#define DEF_KNOWN_TCP_PORTS \
+ "lmtp=24, smtp=25, smtps=submissions=465, submission=587"
+extern char *var_known_tcp_ports;
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_parm_split.c b/src/global/mail_parm_split.c
new file mode 100644
index 0000000..cf721d8
--- /dev/null
+++ b/src/global/mail_parm_split.c
@@ -0,0 +1,128 @@
+/*++
+/* NAME
+/* mail_parm_split 3
+/* SUMMARY
+/* split parameter list value
+/* SYNOPSIS
+/* #include <mail_parm_split.h>
+/*
+/* ARGV *mail_parm_split(
+/* const char *name,
+/* const char *value)
+/* DESCRIPTION
+/* mail_parm_split() splits a parameter list value into its
+/* elements, and extracts text from elements that are entirely
+/* enclosed in {}. It uses CHARS_COMMA_SP as list element
+/* delimiters, and CHARS_BRACE for grouping.
+/*
+/* Arguments:
+/* .IP name
+/* Parameter name. This is used to provide context for
+/* error messages.
+/* .IP value
+/* Parameter value.
+/* DIAGNOSTICS
+/* fatal: syntax error while extracting text from {}, such as:
+/* missing closing brace, or text after closing brace.
+/* SEE ALSO
+/* argv_splitq(3), string array utilities
+/* extpar(3), extract text from parentheses
+/* 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 <stringops.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_parm_split.h>
+
+/* mail_parm_split - split list, extract {text}, errors are fatal */
+
+ARGV *mail_parm_split(const char *name, const char *value)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(value);
+ char *bp = saved_string;
+ char *arg;
+ char *err;
+
+ /*
+ * The code that detects the error shall either signal or handle the
+ * error. In this case, mystrtokq() detects no error, extpar() signals
+ * the error to its caller, and this function handles the error.
+ */
+ while ((arg = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (*arg == CHARS_BRACE[0]
+ && (err = extpar(&arg, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
+#ifndef TEST
+ msg_fatal("%s: %s", name, err);
+#else
+ msg_warn("%s: %s", name, err);
+ myfree(err);
+#endif
+ }
+ argv_add(argvp, arg, (char *) 0);
+ }
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+#ifdef TEST
+
+ /*
+ * This function is security-critical so it better have a unit-test driver.
+ */
+#include <string.h>
+#include <vstream.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(void)
+{
+ VSTRING *vp = vstring_alloc(100);
+ ARGV *argv;
+ char *start;
+ char *str;
+ char **cpp;
+
+ while (vstring_fgets_nonl(vp, VSTREAM_IN) && VSTRING_LEN(vp) > 0) {
+ start = vstring_str(vp);
+ vstream_printf("Input:\t>%s<\n", start);
+ vstream_fflush(VSTREAM_OUT);
+ argv = mail_parm_split("stdin", start);
+ for (cpp = argv->argv; (str = *cpp) != 0; cpp++)
+ vstream_printf("Output:\t>%s<\n", str);
+ argv_free(argv);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_parm_split.h b/src/global/mail_parm_split.h
new file mode 100644
index 0000000..037b68c
--- /dev/null
+++ b/src/global/mail_parm_split.h
@@ -0,0 +1,38 @@
+#ifndef _MAIL_PARM_SPLIT_H_INCLUDED_
+#define _MAIL_PARM_SPLIT_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_parm_split 3h
+/* SUMMARY
+/* split parameter list value
+/* SYNOPSIS
+/* #include <mail_parm_split.h>
+/* DESCRIPTION
+/* .nf
+
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+
+ /*
+ * External interface. For consistency, the separator and grouping character
+ * sets are not passed as parameters.
+ */
+extern ARGV *mail_parm_split(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_parm_split.in b/src/global/mail_parm_split.in
new file mode 100644
index 0000000..22e0d2b
--- /dev/null
+++ b/src/global/mail_parm_split.in
@@ -0,0 +1,6 @@
+TZ PATH=/bin:/usr/bin XAUTHORITY
+TZ { LESS=-m -C -s -f -e } XAUTHORITY
+{ LESS=-m -C -s -f -e } TZ XAUTHORITY
+TZ XAUTHORITY { LESS=-m -C -s -f -e }
+TZ { LESS=-m -C -s -f -e XAUTHORITY
+TZ { LESS=-m -C -s -f -e }x XAUTHORITY
diff --git a/src/global/mail_parm_split.ref b/src/global/mail_parm_split.ref
new file mode 100644
index 0000000..e85adeb
--- /dev/null
+++ b/src/global/mail_parm_split.ref
@@ -0,0 +1,25 @@
+Input: >TZ PATH=/bin:/usr/bin XAUTHORITY<
+Output: >TZ<
+Output: >PATH=/bin:/usr/bin<
+Output: >XAUTHORITY<
+Input: >TZ { LESS=-m -C -s -f -e } XAUTHORITY<
+Output: >TZ<
+Output: >LESS=-m -C -s -f -e<
+Output: >XAUTHORITY<
+Input: >{ LESS=-m -C -s -f -e } TZ XAUTHORITY<
+Output: >LESS=-m -C -s -f -e<
+Output: >TZ<
+Output: >XAUTHORITY<
+Input: >TZ XAUTHORITY { LESS=-m -C -s -f -e }<
+Output: >TZ<
+Output: >XAUTHORITY<
+Output: >LESS=-m -C -s -f -e<
+Input: >TZ { LESS=-m -C -s -f -e XAUTHORITY<
+unknown: warning: stdin: missing '}' in "{ LESS=-m -C -s -f -e XAUTHORITY"
+Output: >TZ<
+Output: >LESS=-m -C -s -f -e XAUTHORITY<
+Input: >TZ { LESS=-m -C -s -f -e }x XAUTHORITY<
+unknown: warning: stdin: syntax error after '}' in "{ LESS=-m -C -s -f -e }x"
+Output: >TZ<
+Output: >LESS=-m -C -s -f -e<
+Output: >XAUTHORITY<
diff --git a/src/global/mail_pathname.c b/src/global/mail_pathname.c
new file mode 100644
index 0000000..32fa109
--- /dev/null
+++ b/src/global/mail_pathname.c
@@ -0,0 +1,44 @@
+/*++
+/* NAME
+/* mail_pathname 3
+/* SUMMARY
+/* generate pathname from mailer service class and name
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* char *mail_pathname(service_class, service_name)
+/* char *service_class;
+/* char *service_name;
+/* DESCRIPTION
+/* mail_pathname() translates the specified service class and name
+/* to a pathname. The result should be passed to myfree() when it
+/* no longer 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>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+
+/* mail_pathname - map service class and service name to pathname */
+
+char *mail_pathname(const char *service_class, const char *service_name)
+{
+ return (concatenate(service_class, "/", service_name, (char *) 0));
+}
diff --git a/src/global/mail_proto.h b/src/global/mail_proto.h
new file mode 100644
index 0000000..b550463
--- /dev/null
+++ b/src/global/mail_proto.h
@@ -0,0 +1,323 @@
+#ifndef _MAIL_PROTO_H_INCLUDED_
+#define _MAIL_PROTO_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_proto 3h
+/* SUMMARY
+/* mail internal and external protocol support
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <iostuff.h>
+#include <attr.h>
+
+ /*
+ * External protocols.
+ */
+#define MAIL_PROTO_SMTP "SMTP"
+#define MAIL_PROTO_ESMTP "ESMTP"
+#define MAIL_PROTO_QMQP "QMQP"
+
+ /*
+ * Names of services: these are the names of the UNIX-domain socket or
+ * FIFO that a service listens on.
+ */
+#define MAIL_SERVICE_BOUNCE "bounce"
+#define MAIL_SERVICE_CLEANUP "cleanup"
+#define MAIL_SERVICE_DEFER "defer"
+#define MAIL_SERVICE_FORWARD "forward"
+#define MAIL_SERVICE_LOCAL "local"
+#define MAIL_SERVICE_PICKUP "pickup"
+#define MAIL_SERVICE_QUEUE "qmgr"
+#define MAIL_SERVICE_TLSMGR "tlsmgr"
+#define MAIL_SERVICE_RESOLVE "resolve"
+#define MAIL_SERVICE_REWRITE "rewrite"
+#define MAIL_SERVICE_VIRTUAL "virtual"
+#define MAIL_SERVICE_SMTP "smtp"
+#define MAIL_SERVICE_SMTPD "smtpd"
+#define MAIL_SERVICE_SHOWQ "showq"
+#define MAIL_SERVICE_ERROR "error"
+#define MAIL_SERVICE_RETRY "retry"
+#define MAIL_SERVICE_FLUSH "flush"
+#define MAIL_SERVICE_VERIFY "verify"
+#define MAIL_SERVICE_TRACE "trace"
+#define MAIL_SERVICE_RELAY "relay"
+#define MAIL_SERVICE_PROXYMAP "proxymap"
+#define MAIL_SERVICE_PROXYWRITE "proxywrite"
+#define MAIL_SERVICE_SCACHE "scache"
+#define MAIL_SERVICE_DNSBLOG "dnsblog"
+#define MAIL_SERVICE_TLSPROXY "tlsproxy"
+#define MAIL_SERVICE_POSTLOG "postlog"
+
+ /*
+ * Mail source classes. Used to specify policy decisions for content
+ * inspection and SMTPUTF8 detection.
+ */
+#define MAIL_SRC_NAME_SENDMAIL "sendmail" /* sendmail(1) */
+#define MAIL_SRC_NAME_SMTPD "smtpd" /* smtpd(8) */
+#define MAIL_SRC_NAME_QMQPD "qmqpd" /* qmqpd(8) */
+#define MAIL_SRC_NAME_FORWARD "forward" /* local(8) forward/alias */
+#define MAIL_SRC_NAME_BOUNCE "bounce"/* bounce(8) */
+#define MAIL_SRC_NAME_NOTIFY "notify"/* protocol etc. errors */
+#define MAIL_SRC_NAME_VERIFY "verify"/* protocol etc. errors */
+#define MAIL_SRC_NAME_ALL "all" /* all sources */
+
+#define MAIL_SRC_MASK_SENDMAIL (1<<0) /* sendmail(1) */
+#define MAIL_SRC_MASK_SMTPD (1<<1) /* smtpd(8) */
+#define MAIL_SRC_MASK_QMQPD (1<<2) /* qmqpd(8) */
+#define MAIL_SRC_MASK_FORWARD (1<<3) /* local(8) forward/alias */
+#define MAIL_SRC_MASK_BOUNCE (1<<4) /* bounce(8) */
+#define MAIL_SRC_MASK_NOTIFY (1<<5) /* protocol etc. errors */
+#define MAIL_SRC_MASK_VERIFY (1<<6) /* protocol etc. errors */
+
+#define MAIL_SRC_MASK_ALL \
+ ( MAIL_SRC_MASK_SENDMAIL | MAIL_SRC_MASK_SMTPD \
+ | MAIL_SRC_MASK_QMQPD | MAIL_SRC_MASK_FORWARD \
+ | MAIL_SRC_MASK_BOUNCE | MAIL_SRC_MASK_NOTIFY \
+ | MAIL_SRC_MASK_VERIFY)
+
+ /*
+ * Well-known socket or FIFO directories. The main difference is in file
+ * access permissions.
+ */
+#define MAIL_CLASS_PUBLIC "public"
+#define MAIL_CLASS_PRIVATE "private"
+
+ /*
+ * Generic triggers.
+ */
+#define TRIGGER_REQ_WAKEUP 'W' /* wakeup */
+
+ /*
+ * Queue manager requests.
+ */
+#define QMGR_REQ_SCAN_DEFERRED 'D' /* scan deferred queue */
+#define QMGR_REQ_SCAN_INCOMING 'I' /* scan incoming queue */
+#define QMGR_REQ_FLUSH_DEAD 'F' /* flush dead xport/site */
+#define QMGR_REQ_SCAN_ALL 'A' /* ignore time stamps */
+
+ /*
+ * Functional interface.
+ */
+extern VSTREAM *mail_connect(const char *, const char *, int);
+extern VSTREAM *mail_connect_wait(const char *, const char *);
+extern int mail_command_client(const char *, const char *, const char *,...);
+extern int mail_command_server(VSTREAM *,...);
+extern int mail_trigger(const char *, const char *, const char *, ssize_t);
+extern char *mail_pathname(const char *, const char *);
+
+ /*
+ * Each Postfix internal service identifies the protocol that it intends to
+ * use. On the receiver end, this information does not contribute to the
+ * reported number of received attributes (it is a constant).
+ */
+#define MAIL_ATTR_PROTO "protocol"
+
+#define MAIL_ATTR_PROTO_ANVIL "anvil_protocol"
+#define MAIL_ATTR_PROTO_BOUNCE "delivery_status_protocol"
+#define MAIL_ATTR_PROTO_CLEANUP "cleanup_protocol"
+#define MAIL_ATTR_PROTO_DELIVER "delivery_request_protocol"
+#define MAIL_ATTR_PROTO_FLUSH "queue_flush_protocol"
+#define MAIL_ATTR_PROTO_POSTDROP "postdrop_protocol"
+#define MAIL_ATTR_PROTO_PROXYMAP "proxymap_protocol"
+#define MAIL_ATTR_PROTO_SCACHE "connection_cache_protocol"
+#define MAIL_ATTR_PROTO_SHOWQ "mail_queue_list_protocol"
+#define MAIL_ATTR_PROTO_TLSMGR "tlsmgr_protocol"
+#define MAIL_ATTR_PROTO_TLSPROXY "tlsproxy_protocol"
+#define MAIL_ATTR_PROTO_TRIVIAL "trivial_rewrite_protocol"
+#define MAIL_ATTR_PROTO_VERIFY "address_verification_prrotocol"
+
+ /*
+ * Attribute names.
+ */
+#define MAIL_ATTR_REQ "request"
+#define MAIL_ATTR_NREQ "nrequest"
+#define MAIL_ATTR_STATUS "status"
+
+#define MAIL_ATTR_FLAGS "flags"
+#define MAIL_ATTR_QUEUE "queue_name"
+#define MAIL_ATTR_QUEUEID "queue_id"
+#define MAIL_ATTR_SENDER "sender"
+#define MAIL_ATTR_RCPT_COUNT "recipient_count"
+#define MAIL_ATTR_ORCPT "original_recipient"
+#define MAIL_ATTR_RECIP "recipient"
+#define MAIL_ATTR_WHY "reason"
+#define MAIL_ATTR_VERPDL "verp_delimiters"
+#define MAIL_ATTR_SITE "site"
+#define MAIL_ATTR_OFFSET "offset"
+#define MAIL_ATTR_SIZE "size"
+#define MAIL_ATTR_ERRTO "errors-to"
+#define MAIL_ATTR_RRCPT "return-receipt"
+#define MAIL_ATTR_TIME "time"
+#define MAIL_ATTR_LOCALTIME "localtime"
+#define MAIL_ATTR_CREATE_TIME "create_time"
+#define MAIL_ATTR_RULE "rule"
+#define MAIL_ATTR_ADDR "address"
+#define MAIL_ATTR_TRANSPORT "transport"
+#define MAIL_ATTR_NEXTHOP "nexthop"
+#define MAIL_ATTR_TRACE_FLAGS "trace_flags"
+#define MAIL_ATTR_ADDR_STATUS "recipient_status"
+#define MAIL_ATTR_ACTION "action"
+#define MAIL_ATTR_TABLE "table"
+#define MAIL_ATTR_KEY "key"
+#define MAIL_ATTR_VALUE "value"
+#define MAIL_ATTR_INSTANCE "instance"
+#define MAIL_ATTR_SASL_METHOD "sasl_method"
+#define MAIL_ATTR_SASL_USERNAME "sasl_username"
+#define MAIL_ATTR_SASL_SENDER "sasl_sender"
+#define MAIL_ATTR_ETRN_DOMAIN "etrn_domain"
+#define MAIL_ATTR_DUMMY "dummy"
+#define MAIL_ATTR_STRESS "stress"
+#define MAIL_ATTR_LOG_IDENT "log_ident"
+#define MAIL_ATTR_RWR_CONTEXT "rewrite_context"
+#define MAIL_ATTR_POL_CONTEXT "policy_context"
+#define MAIL_ATTR_FORCED_EXPIRE "forced_expire"
+
+#define MAIL_ATTR_RWR_LOCAL "local"
+#define MAIL_ATTR_RWR_REMOTE "remote"
+
+#define MAIL_ATTR_TTL "ttl"
+#define MAIL_ATTR_LABEL "label"
+#define MAIL_ATTR_PROP "property"
+#define MAIL_ATTR_FUNC "function"
+#define MAIL_ATTR_CCERT_SUBJECT "ccert_subject"
+#define MAIL_ATTR_CCERT_ISSUER "ccert_issuer"
+#define MAIL_ATTR_CCERT_CERT_FPRINT "ccert_fingerprint"
+#define MAIL_ATTR_CCERT_PKEY_FPRINT "ccert_pubkey_fingerprint"
+#define MAIL_ATTR_CRYPTO_PROTOCOL "encryption_protocol"
+#define MAIL_ATTR_CRYPTO_CIPHER "encryption_cipher"
+#define MAIL_ATTR_CRYPTO_KEYSIZE "encryption_keysize"
+
+ /*
+ * Suffixes for sender_name, sender_domain etc.
+ */
+#define MAIL_ATTR_S_NAME "_name"
+#define MAIL_ATTR_S_DOMAIN "_domain"
+
+ /*
+ * Special names for RBL results.
+ */
+#define MAIL_ATTR_RBL_WHAT "rbl_what"
+#define MAIL_ATTR_RBL_DOMAIN "rbl_domain"
+#define MAIL_ATTR_RBL_REASON "rbl_reason"
+#define MAIL_ATTR_RBL_TXT "rbl_txt" /* LaMont compatibility */
+#define MAIL_ATTR_RBL_CLASS "rbl_class"
+#define MAIL_ATTR_RBL_CODE "rbl_code"
+#define MAIL_ATTR_RBL_ADDR "rbl_addr"
+
+ /*
+ * The following attribute names are stored in queue files. Changing this
+ * means lots of work to maintain backwards compatibility with queued mail.
+ */
+#define MAIL_ATTR_ENCODING "encoding" /* internal encoding */
+#define MAIL_ATTR_ENC_8BIT "8bit" /* 8BITMIME equivalent */
+#define MAIL_ATTR_ENC_7BIT "7bit" /* 7BIT equivalent */
+#define MAIL_ATTR_ENC_NONE "" /* encoding unknown */
+
+#define MAIL_ATTR_LOG_CLIENT_NAME "log_client_name" /* client hostname */
+#define MAIL_ATTR_LOG_CLIENT_ADDR "log_client_address" /* client address */
+#define MAIL_ATTR_LOG_CLIENT_PORT "log_client_port" /* client port */
+#define MAIL_ATTR_LOG_HELO_NAME "log_helo_name" /* SMTP helo name */
+#define MAIL_ATTR_LOG_PROTO_NAME "log_protocol_name" /* SMTP/ESMTP/QMQP */
+#define MAIL_ATTR_LOG_ORIGIN "log_message_origin" /* name[addr]:port */
+
+#define MAIL_ATTR_ACT_CLIENT "client"/* client name addr */
+#define MAIL_ATTR_ACT_CLIENT_NAME "client_name" /* client name */
+#define MAIL_ATTR_ACT_CLIENT_ADDR "client_address" /* client address */
+#define MAIL_ATTR_ACT_CLIENT_PORT "client_port" /* client TCP port */
+#define MAIL_ATTR_ACT_CLIENT_AF "client_address_type" /* AF_INET etc. */
+#define MAIL_ATTR_ACT_HELO_NAME "helo_name" /* SMTP helo name */
+#define MAIL_ATTR_ACT_PROTO_NAME "protocol_name" /* SMTP/ESMTP/QMQP */
+#define MAIL_ATTR_ACT_REVERSE_CLIENT_NAME "reverse_client_name"
+#define MAIL_ATTR_ACT_FORWARD_CLIENT_NAME "forward_client_name"
+
+#define MAIL_ATTR_ACT_SERVER_ADDR "server_address" /* server address */
+#define MAIL_ATTR_ACT_SERVER_PORT "server_port" /* server TCP port */
+
+#define MAIL_ATTR_PROTO_STATE "protocol_state" /* MAIL/RCPT/... */
+#define MAIL_ATTR_ORG_NONE "unknown" /* origin unknown */
+#define MAIL_ATTR_ORG_LOCAL "local" /* local submission */
+
+ /*
+ * XCLIENT/XFORWARD in SMTP.
+ */
+#define XCLIENT_CMD "XCLIENT" /* XCLIENT command */
+#define XCLIENT_NAME "NAME" /* client name */
+#define XCLIENT_REVERSE_NAME "REVERSE_NAME" /* reverse client name */
+#ifdef FORWARD_CLIENT_NAME
+#define XCLIENT_FORWARD_NAME "FORWARD_NAME" /* forward client name */
+#endif
+#define XCLIENT_ADDR "ADDR" /* client address */
+#define XCLIENT_PORT "PORT" /* client port */
+#define XCLIENT_PROTO "PROTO" /* client protocol */
+#define XCLIENT_HELO "HELO" /* client helo */
+#define XCLIENT_LOGIN "LOGIN" /* SASL login name */
+#define XCLIENT_DESTADDR "DESTADDR" /* server address */
+#define XCLIENT_DESTPORT "DESTPORT" /* server port */
+
+#define XCLIENT_UNAVAILABLE "[UNAVAILABLE]" /* permanently unavailable */
+#define XCLIENT_TEMPORARY "[TEMPUNAVAIL]" /* temporarily unavailable */
+
+#define XFORWARD_CMD "XFORWARD" /* XFORWARD command */
+#define XFORWARD_NAME "NAME" /* client name */
+#define XFORWARD_ADDR "ADDR" /* client address */
+#define XFORWARD_PORT "PORT" /* client port */
+#define XFORWARD_PROTO "PROTO" /* client protocol */
+#define XFORWARD_HELO "HELO" /* client helo */
+#define XFORWARD_IDENT "IDENT" /* message identifier */
+#define XFORWARD_DOMAIN "SOURCE"/* origin type */
+#define XFORWARD_DOM_LOCAL "LOCAL" /* local origin */
+#define XFORWARD_DOM_REMOTE "REMOTE"/* remote origin */
+
+#define XFORWARD_UNAVAILABLE "[UNAVAILABLE]" /* attribute unavailable */
+
+ /*
+ * DSN support.
+ */
+#define MAIL_ATTR_DSN_STATUS "status"/* XXX Postfix <2.3 compat */
+#define MAIL_ATTR_DSN_DTYPE "diag_type" /* dsn diagnostic code */
+#define MAIL_ATTR_DSN_DTEXT "diag_text" /* dsn diagnostic code */
+#define MAIL_ATTR_DSN_MTYPE "mta_type" /* dsn remote MTA */
+#define MAIL_ATTR_DSN_MNAME "mta_mname" /* dsn remote MTA */
+#define MAIL_ATTR_DSN_ACTION "action"/* XXX Postfix <2.3 compat */
+#define MAIL_ATTR_DSN_ENVID "envelope_id" /* dsn envelope id */
+#define MAIL_ATTR_DSN_RET "ret_flags" /* dsn full/headers */
+#define MAIL_ATTR_DSN_NOTIFY "notify_flags" /* dsn notify flags */
+#define MAIL_ATTR_DSN_ORCPT "dsn_orig_rcpt" /* dsn original recipient */
+#define MAIL_ATTR_SMTPUTF8 "smtputf8" /* RFC6531 support */
+
+ /*
+ * SMTP reply footer support.
+ */
+#define MAIL_ATTR_SERVER_NAME "server_name"
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_queue.c b/src/global/mail_queue.c
new file mode 100644
index 0000000..f73d1f1
--- /dev/null
+++ b/src/global/mail_queue.c
@@ -0,0 +1,439 @@
+/*++
+/* NAME
+/* mail_queue 3
+/* SUMMARY
+/* mail queue file access
+/* SYNOPSIS
+/* #include <mail_queue.h>
+/*
+/* VSTREAM *mail_queue_enter(queue_name, mode, tp)
+/* const char *queue_name;
+/* mode_t mode;
+/* struct timeval *tp;
+/*
+/* VSTREAM *mail_queue_open(queue_name, queue_id, flags, mode)
+/* const char *queue_name;
+/* const char *queue_id;
+/* int flags;
+/* mode_t mode;
+/*
+/* char *mail_queue_dir(buf, queue_name, queue_id)
+/* VSTRING *buf;
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* char *mail_queue_path(buf, queue_name, queue_id)
+/* VSTRING *buf;
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* int mail_queue_mkdirs(path)
+/* const char *path;
+/*
+/* int mail_queue_rename(queue_id, old_queue, new_queue)
+/* const char *queue_id;
+/* const char *old_queue;
+/* const char *new_queue;
+/*
+/* int mail_queue_remove(queue_name, queue_id)
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* int mail_queue_name_ok(queue_name)
+/* const char *queue_name;
+/*
+/* int mail_queue_id_ok(queue_id)
+/* const char *queue_id;
+/* DESCRIPTION
+/* This module encapsulates access to the mail queue hierarchy.
+/* Unlike most other modules, this one does not abort the program
+/* in case of file access problems. But it does abort when the
+/* application attempts to use a malformed queue name or queue id.
+/*
+/* mail_queue_enter() creates an entry in the named queue. The queue
+/* id is the file base name, see VSTREAM_PATH(). Queue ids are
+/* relatively short strings and are recycled in the course of time.
+/* The only guarantee given is that on a given machine, no two queue
+/* entries will have the same queue ID at the same time. The tp
+/* argument, if not a null pointer, receives the time stamp that
+/* corresponds with the queue ID.
+/*
+/* mail_queue_open() opens the named queue file. The \fIflags\fR
+/* and \fImode\fR arguments are as with open(2). The result is a
+/* null pointer in case of problems.
+/*
+/* mail_queue_dir() returns the directory name of the specified queue
+/* file. When a null result buffer pointer is provided, the result is
+/* written to a private buffer that may be overwritten upon the next
+/* call.
+/*
+/* mail_queue_path() returns the pathname of the specified queue
+/* file. When a null result buffer pointer is provided, the result
+/* is written to a private buffer that may be overwritten upon the
+/* next call.
+/*
+/* mail_queue_mkdirs() creates missing parent directories
+/* for the file named in \fBpath\fR. A non-zero result means
+/* that the operation failed.
+/*
+/* mail_queue_rename() renames a queue file. A non-zero result
+/* means the operation failed.
+/*
+/* mail_queue_remove() removes the named queue file. A non-zero result
+/* means the operation failed.
+/*
+/* mail_queue_name_ok() validates a mail queue name and returns
+/* non-zero (true) if the name contains no nasty characters.
+/*
+/* mail_queue_id_ok() does the same thing for mail queue ID names.
+/* DIAGNOSTICS
+/* Panic: invalid queue name or id given to mail_queue_path(),
+/* mail_queue_rename(), or mail_queue_remove().
+/* Fatal error: out of memory.
+/* 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 <stdio.h> /* rename() */
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h> /* gettimeofday, not in POSIX */
+#include <string.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <dir_forest.h>
+#include <make_dirs.h>
+#include <split_at.h>
+#include <sane_fsops.h>
+#include <valid_hostname.h>
+
+/* Global library. */
+
+#include "file_id.h"
+#include "mail_params.h"
+#define MAIL_QUEUE_INTERNAL
+#include "mail_queue.h"
+
+#define STR vstring_str
+
+/* mail_queue_dir - construct mail queue directory name */
+
+const char *mail_queue_dir(VSTRING *buf, const char *queue_name,
+ const char *queue_id)
+{
+ const char *myname = "mail_queue_dir";
+ static VSTRING *private_buf = 0;
+ static VSTRING *hash_buf = 0;
+ static ARGV *hash_queue_names = 0;
+ static VSTRING *usec_buf = 0;
+ const char *delim;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (mail_queue_name_ok(queue_name) == 0)
+ msg_panic("%s: bad queue name: %s", myname, queue_name);
+ if (mail_queue_id_ok(queue_id) == 0)
+ msg_panic("%s: bad queue id: %s", myname, queue_id);
+
+ /*
+ * Initialize.
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(100);
+ buf = private_buf;
+ }
+ if (hash_buf == 0) {
+ hash_buf = vstring_alloc(100);
+ hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP);
+ }
+
+ /*
+ * First, put the basic queue directory name into place.
+ */
+ vstring_strcpy(buf, queue_name);
+ vstring_strcat(buf, "/");
+
+ /*
+ * Then, see if we need to append a little directory forest.
+ */
+ for (cpp = hash_queue_names->argv; *cpp; cpp++) {
+ if (strcasecmp(*cpp, queue_name) == 0) {
+ if (MQID_FIND_LG_INUM_SEPARATOR(delim, queue_id)) {
+ if (usec_buf == 0)
+ usec_buf = vstring_alloc(20);
+ MQID_LG_GET_HEX_USEC(usec_buf, delim);
+ queue_id = STR(usec_buf);
+ }
+ vstring_strcat(buf,
+ dir_forest(hash_buf, queue_id, var_hash_queue_depth));
+ break;
+ }
+ }
+ return (STR(buf));
+}
+
+/* mail_queue_path - map mail queue id to path name */
+
+const char *mail_queue_path(VSTRING *buf, const char *queue_name,
+ const char *queue_id)
+{
+ static VSTRING *private_buf = 0;
+
+ /*
+ * Initialize.
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(100);
+ buf = private_buf;
+ }
+
+ /*
+ * Append the queue id to the possibly hashed queue directory.
+ */
+ (void) mail_queue_dir(buf, queue_name, queue_id);
+ vstring_strcat(buf, queue_id);
+ return (STR(buf));
+}
+
+/* mail_queue_mkdirs - fill in missing directories */
+
+int mail_queue_mkdirs(const char *path)
+{
+ const char *myname = "mail_queue_mkdirs";
+ char *saved_path = mystrdup(path);
+ int ret;
+
+ /*
+ * Truncate a copy of the pathname (for safety sake), and create the
+ * missing directories.
+ */
+ if (split_at_right(saved_path, '/') == 0)
+ msg_panic("%s: no slash in: %s", myname, saved_path);
+ ret = make_dirs(saved_path, 0700);
+ myfree(saved_path);
+ return (ret);
+}
+
+/* mail_queue_rename - move message to another queue */
+
+int mail_queue_rename(const char *queue_id, const char *old_queue,
+ const char *new_queue)
+{
+ VSTRING *old_buf = vstring_alloc(100);
+ VSTRING *new_buf = vstring_alloc(100);
+ int error;
+
+ /*
+ * Try the operation. If it fails, see if it is because of missing
+ * intermediate directories.
+ */
+ error = sane_rename(mail_queue_path(old_buf, old_queue, queue_id),
+ mail_queue_path(new_buf, new_queue, queue_id));
+ if (error != 0 && mail_queue_mkdirs(STR(new_buf)) == 0)
+ error = sane_rename(STR(old_buf), STR(new_buf));
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(old_buf);
+ vstring_free(new_buf);
+
+ return (error);
+}
+
+/* mail_queue_remove - remove mail queue file */
+
+int mail_queue_remove(const char *queue_name, const char *queue_id)
+{
+ return (REMOVE(mail_queue_path((VSTRING *) 0, queue_name, queue_id)));
+}
+
+/* mail_queue_name_ok - validate mail queue name */
+
+int mail_queue_name_ok(const char *queue_name)
+{
+ const char *cp;
+
+ if (*queue_name == 0 || strlen(queue_name) > 100)
+ return (0);
+
+ for (cp = queue_name; *cp; cp++)
+ if (!ISALNUM(*cp))
+ return (0);
+ return (1);
+}
+
+/* mail_queue_id_ok - validate mail queue id */
+
+int mail_queue_id_ok(const char *queue_id)
+{
+ const char *cp;
+
+ /*
+ * A file name is either a queue ID (short alphanumeric string in
+ * time+inum form) or a fast flush service logfile name (destination
+ * domain name with non-alphanumeric characters replaced by "_").
+ */
+ if (*queue_id == 0 || strlen(queue_id) > VALID_HOSTNAME_LEN)
+ return (0);
+
+ /*
+ * OK if in time+inum form or in host_domain_tld form.
+ */
+ for (cp = queue_id; *cp; cp++)
+ if (!ISALNUM(*cp) && *cp != '_')
+ return (0);
+ return (1);
+}
+
+/* mail_queue_enter - make mail queue entry with locally-unique name */
+
+VSTREAM *mail_queue_enter(const char *queue_name, mode_t mode,
+ struct timeval * tp)
+{
+ const char *myname = "mail_queue_enter";
+ static VSTRING *sec_buf;
+ static VSTRING *usec_buf;
+ static VSTRING *id_buf;
+ static int pid;
+ static VSTRING *path_buf;
+ static VSTRING *temp_path;
+ struct timeval tv;
+ int fd;
+ const char *file_id;
+ VSTREAM *stream;
+ int count;
+
+ /*
+ * Initialize.
+ */
+ if (id_buf == 0) {
+ pid = getpid();
+ sec_buf = vstring_alloc(10);
+ usec_buf = vstring_alloc(10);
+ id_buf = vstring_alloc(10);
+ path_buf = vstring_alloc(10);
+ temp_path = vstring_alloc(100);
+ }
+ if (tp == 0)
+ tp = &tv;
+
+ /*
+ * Create a file with a temporary name that does not collide. The process
+ * ID alone is not sufficiently unique: maildrops can be shared via the
+ * network. Not that I recommend using a network-based queue, or having
+ * multiple hosts write to the same queue, but we should try to avoid
+ * losing mail if we can.
+ *
+ * If someone is racing against us, try to win.
+ */
+ for (;;) {
+ GETTIMEOFDAY(tp);
+ vstring_sprintf(temp_path, "%s/%d.%d", queue_name,
+ (int) tp->tv_usec, pid);
+ if ((fd = open(STR(temp_path), O_RDWR | O_CREAT | O_EXCL, mode)) >= 0)
+ break;
+ if (errno == EEXIST || errno == EISDIR)
+ continue;
+ msg_warn("%s: create file %s: %m", myname, STR(temp_path));
+ sleep(10);
+ }
+
+ /*
+ * Rename the file to something that is derived from the file ID. I saw
+ * this idea first being used in Zmailer. On any reasonable file system
+ * the file ID is guaranteed to be unique. Better let the OS resolve
+ * collisions than doing a worse job in an application. Another
+ * attractive property of file IDs is that they can appear in messages
+ * without leaking a significant amount of system information (unlike
+ * process ids). Not so nice is that files need to be renamed when they
+ * are moved to another file system.
+ *
+ * If someone is racing against us, try to win.
+ */
+ file_id = get_file_id_fd(fd, var_long_queue_ids);
+
+ /*
+ * XXX Some systems seem to have clocks that correlate with process
+ * scheduling or something. Unfortunately, we cannot add random
+ * quantities to the time, because the non-inode part of a queue ID must
+ * not repeat within the same second. The queue ID is the sole thing that
+ * prevents multiple messages from getting the same Message-ID value.
+ */
+ for (count = 0;; count++) {
+ GETTIMEOFDAY(tp);
+ if (var_long_queue_ids) {
+ vstring_sprintf(id_buf, "%s%s%c%s",
+ MQID_LG_ENCODE_SEC(sec_buf, tp->tv_sec),
+ MQID_LG_ENCODE_USEC(usec_buf, tp->tv_usec),
+ MQID_LG_INUM_SEP, file_id);
+ } else {
+ vstring_sprintf(id_buf, "%s%s",
+ MQID_SH_ENCODE_USEC(usec_buf, tp->tv_usec),
+ file_id);
+ }
+ mail_queue_path(path_buf, queue_name, STR(id_buf));
+ if (sane_rename(STR(temp_path), STR(path_buf)) == 0) /* success */
+ break;
+ if (errno == EPERM || errno == EISDIR) /* collision. weird. */
+ continue;
+ if (errno != ENOENT || mail_queue_mkdirs(STR(path_buf)) < 0) {
+ msg_warn("%s: rename %s to %s: %m", myname,
+ STR(temp_path), STR(path_buf));
+ }
+ if (count > 1000) /* XXX whatever */
+ msg_fatal("%s: rename %s to %s: giving up", myname,
+ STR(temp_path), STR(path_buf));
+ }
+
+ stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(stream, CA_VSTREAM_CTL_PATH(STR(path_buf)), CA_VSTREAM_CTL_END);
+ return (stream);
+}
+
+/* mail_queue_open - open mail queue file */
+
+VSTREAM *mail_queue_open(const char *queue_name, const char *queue_id,
+ int flags, mode_t mode)
+{
+ const char *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
+ VSTREAM *fp;
+
+ /*
+ * Try the operation. If file creation fails, see if it is because of a
+ * missing subdirectory.
+ */
+ if ((fp = vstream_fopen(path, flags, mode)) == 0)
+ if (errno == ENOENT)
+ if ((flags & O_CREAT) == O_CREAT && mail_queue_mkdirs(path) == 0)
+ fp = vstream_fopen(path, flags, mode);
+ return (fp);
+}
diff --git a/src/global/mail_queue.h b/src/global/mail_queue.h
new file mode 100644
index 0000000..4928d60
--- /dev/null
+++ b/src/global/mail_queue.h
@@ -0,0 +1,193 @@
+#ifndef _MAIL_QUEUE_H_INCLUDED_
+#define _MAIL_QUEUE_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_queue 3h
+/* SUMMARY
+/* mail queue access
+/* SYNOPSIS
+/* #include <mail_queue.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Mail queue names.
+ */
+#define MAIL_QUEUE_MAILDROP "maildrop"
+#define MAIL_QUEUE_HOLD "hold"
+#define MAIL_QUEUE_INCOMING "incoming"
+#define MAIL_QUEUE_ACTIVE "active"
+#define MAIL_QUEUE_DEFERRED "deferred"
+#define MAIL_QUEUE_TRACE "trace"
+#define MAIL_QUEUE_DEFER "defer"
+#define MAIL_QUEUE_BOUNCE "bounce"
+#define MAIL_QUEUE_CORRUPT "corrupt"
+#define MAIL_QUEUE_FLUSH "flush"
+#define MAIL_QUEUE_SAVED "saved"
+
+ /*
+ * Queue file modes.
+ *
+ * 4.4BSD-like systems don't allow (sticky AND executable) together, so we use
+ * group read permission bits instead. These are more portable, but they
+ * also are more likely to be turned on by accident. It would not be the end
+ * of the world.
+ */
+#define MAIL_QUEUE_STAT_READY (S_IRUSR | S_IWUSR | S_IXUSR)
+#define MAIL_QUEUE_STAT_CORRUPT (S_IRUSR)
+#ifndef MAIL_QUEUE_STAT_UNTHROTTLE
+#define MAIL_QUEUE_STAT_UNTHROTTLE (S_IRGRP)
+#define MAIL_QUEUE_STAT_EXPIRE (S_IXGRP)
+#endif
+
+extern struct VSTREAM *mail_queue_enter(const char *, mode_t, struct timeval *);
+extern struct VSTREAM *mail_queue_open(const char *, const char *, int, mode_t);
+extern int mail_queue_rename(const char *, const char *, const char *);
+extern int mail_queue_remove(const char *, const char *);
+extern const char *mail_queue_dir(VSTRING *, const char *, const char *);
+extern const char *mail_queue_path(VSTRING *, const char *, const char *);
+extern int mail_queue_mkdirs(const char *);
+extern int mail_queue_name_ok(const char *);
+extern int mail_queue_id_ok(const char *);
+
+ /*
+ * MQID - Mail Queue ID format definitions. Needed only by code that creates
+ * or parses queue ID strings.
+ */
+#ifdef MAIL_QUEUE_INTERNAL
+
+ /*
+ * System library.
+ */
+#include <errno.h>
+
+ /*
+ * Global library.
+ */
+#include <safe_ultostr.h>
+
+ /*
+ * The long non-repeating queue ID is encoded in an alphabet of 10 digits,
+ * 21 upper-case characters, and 21 or fewer lower-case characters. The
+ * alphabet is made "safe" by removing all the vowels (AEIOUaeiou). The ID
+ * is the concatenation of:
+ *
+ * - the time in seconds (base 52 encoded, six or more chars),
+ *
+ * - the time in microseconds (base 52 encoded, exactly four chars),
+ *
+ * - the 'z' character to separate the time and inode information,
+ *
+ * - the inode number (base 51 encoded so that it contains no 'z').
+ */
+#define MQID_LG_SEC_BASE 52 /* seconds safe alphabet base */
+#define MQID_LG_SEC_PAD 6 /* seconds minimum field width */
+#define MQID_LG_USEC_BASE 52 /* microseconds safe alphabet base */
+#define MQID_LG_USEC_PAD 4 /* microseconds exact field width */
+#define MQID_LG_TIME_PAD (MQID_LG_SEC_PAD + MQID_LG_USEC_PAD)
+#define MQID_LG_INUM_SEP 'z' /* time-inode separator */
+#define MQID_LG_INUM_BASE 51 /* inode safe alphabet base */
+#define MQID_LG_INUM_PAD 0 /* no padding needed */
+
+#define MQID_FIND_LG_INUM_SEPARATOR(cp, path) \
+ (((cp) = strrchr((path), MQID_LG_INUM_SEP)) != 0 \
+ && ((cp) - (path) >= MQID_LG_TIME_PAD))
+
+#define MQID_GET_INUM(path, inum, long_form, error) do { \
+ char *_cp; \
+ if (((long_form) = MQID_FIND_LG_INUM_SEPARATOR(_cp, (path))) != 0) { \
+ MQID_LG_DECODE_INUM(_cp + 1, (inum), (error)); \
+ } else { \
+ MQID_SH_DECODE_INUM((path) + MQID_SH_USEC_PAD, (inum), (error)); \
+ } \
+ } while (0)
+
+#define MQID_LG_ENCODE_SEC(buf, val) \
+ MQID_LG_ENCODE((buf), (val), MQID_LG_SEC_BASE, MQID_LG_SEC_PAD)
+
+#define MQID_LG_ENCODE_USEC(buf, val) \
+ MQID_LG_ENCODE((buf), (val), MQID_LG_USEC_BASE, MQID_LG_USEC_PAD)
+
+#define MQID_LG_ENCODE_INUM(buf, val) \
+ MQID_LG_ENCODE((buf), (val), MQID_LG_INUM_BASE, MQID_LG_INUM_PAD)
+
+#define MQID_LG_DECODE_USEC(str, ulval, error) \
+ MQID_LG_DECODE((str), (ulval), MQID_LG_USEC_BASE, (error))
+
+#define MQID_LG_DECODE_INUM(str, ulval, error) \
+ MQID_LG_DECODE((str), (ulval), MQID_LG_INUM_BASE, (error))
+
+#define MQID_LG_ENCODE(buf, val, base, padlen) \
+ safe_ultostr((buf), (unsigned long) (val), (base), (padlen), '0')
+
+#define MQID_LG_DECODE(str, ulval, base, error) do { \
+ char *_end; \
+ errno = 0; \
+ (ulval) = safe_strtoul((str), &_end, (base)); \
+ (error) = (*_end != 0 || ((ulval) == ULONG_MAX && errno == ERANGE)); \
+ } while (0)
+
+#define MQID_LG_GET_HEX_USEC(bp, zp) do { \
+ int _error; \
+ unsigned long _us_val; \
+ vstring_strncpy((bp), (zp) - MQID_LG_USEC_PAD, MQID_LG_USEC_PAD); \
+ MQID_LG_DECODE_USEC(STR(bp), _us_val, _error); \
+ if (_error) \
+ _us_val = 0; \
+ (void) MQID_SH_ENCODE_USEC((bp), _us_val); \
+ } while (0)
+
+ /*
+ * The short repeating queue ID is encoded in upper-case hexadecimal, and is
+ * the concatenation of:
+ *
+ * - the time in microseconds (exactly five chars),
+ *
+ * - the inode number.
+ */
+#define MQID_SH_USEC_PAD 5 /* microseconds exact field width */
+
+#define MQID_SH_ENCODE_USEC(buf, usec) \
+ vstring_str(vstring_sprintf((buf), "%05X", (int) (usec)))
+
+#define MQID_SH_ENCODE_INUM(buf, inum) \
+ vstring_str(vstring_sprintf((buf), "%lX", (unsigned long) (inum)))
+
+#define MQID_SH_DECODE_INUM(str, ulval, error) do { \
+ char *_end; \
+ errno = 0; \
+ (ulval) = strtoul((str), &_end, 16); \
+ (error) = (*_end != 0 || ((ulval) == ULONG_MAX && errno == ERANGE)); \
+ } while (0)
+
+#endif /* MAIL_QUEUE_INTERNAL */
+
+/* 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
+/*--*/
+
+#endif /* _MAIL_QUEUE_H_INCLUDED_ */
diff --git a/src/global/mail_run.c b/src/global/mail_run.c
new file mode 100644
index 0000000..f376434
--- /dev/null
+++ b/src/global/mail_run.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* mail_run 3
+/* SUMMARY
+/* run mail component program
+/* SYNOPSIS
+/* #include <mail_run.h>
+/*
+/* int mail_run_foreground(dir, argv)
+/* const char *dir;
+/* char **argv;
+/*
+/* int mail_run_background(dir, argv)
+/* const char *dir;
+/* char **argv;
+/*
+/* NORETURN mail_run_replace(dir, argv)
+/* const char *dir;
+/* char **argv;
+/* DESCRIPTION
+/* This module runs programs that live in the mail program directory.
+/* Each routine takes a directory and a command-line array. The program
+/* pathname is built by prepending the directory and a slash to the
+/* command name.
+/*
+/* mail_run_foreground() runs the named command in the foreground and
+/* waits until the command terminates.
+/*
+/* mail_run_background() runs the named command in the background.
+/*
+/* mail_run_replace() attempts to replace the current process by
+/* an instance of the named command. This function never returns.
+/*
+/* Arguments:
+/* .IP argv
+/* A null-terminated command-line vector. The first array element
+/* is the base name of the program to be executed.
+/* DIAGNOSTICS
+/* The result is (-1) if the command could not be run. Otherwise,
+/* mail_run_foreground() returns the termination status of the
+/* command. mail_run_background() returns the process id in case
+/* of success.
+/* CONFIGURATION PARAMETERS
+/* fork_attempts: number of attempts to fork() a process;
+/* fork_delay: delay in seconds between fork() attempts.
+/* 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/wait.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_run.h"
+
+/* mail_run_foreground - run command in foreground */
+
+int mail_run_foreground(const char *dir, char **argv)
+{
+ int count;
+ char *path;
+ WAIT_STATUS_T status;
+ int pid;
+ int wpid;
+
+#define RETURN(x) { myfree(path); return(x); }
+
+ path = concatenate(dir, "/", argv[0], (char *) 0);
+
+ for (count = 0; count < var_fork_tries; count++) {
+ switch (pid = fork()) {
+ case -1:
+ msg_warn("fork %s: %m", path);
+ break;
+ case 0:
+ /* Reset the msg_cleanup() handlers in the child process. */
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ execv(path, argv);
+ msg_fatal("execv %s: %m", path);
+ default:
+ do {
+ wpid = waitpid(pid, &status, 0);
+ } while (wpid == -1 && errno == EINTR);
+ RETURN(wpid == -1 ? -1 :
+ WIFEXITED(status) ? WEXITSTATUS(status) : 1)
+ }
+ sleep(var_fork_delay);
+ }
+ RETURN(-1);
+}
+
+/* mail_run_background - run command in background */
+
+int mail_run_background(const char *dir, char **argv)
+{
+ int count;
+ char *path;
+ int pid;
+
+#define RETURN(x) { myfree(path); return(x); }
+
+ path = concatenate(dir, "/", argv[0], (char *) 0);
+
+ for (count = 0; count < var_fork_tries; count++) {
+ switch (pid = fork()) {
+ case -1:
+ msg_warn("fork %s: %m", path);
+ break;
+ case 0:
+ /* Reset the msg_cleanup() handlers in the child process. */
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ execv(path, argv);
+ msg_fatal("execv %s: %m", path);
+ default:
+ RETURN(pid);
+ }
+ sleep(var_fork_delay);
+ }
+ RETURN(-1);
+}
+
+/* mail_run_replace - run command, replacing current process */
+
+NORETURN mail_run_replace(const char *dir, char **argv)
+{
+ char *path;
+
+ path = concatenate(dir, "/", argv[0], (char *) 0);
+ execv(path, argv);
+ msg_fatal("execv %s: %m", path);
+}
diff --git a/src/global/mail_run.h b/src/global/mail_run.h
new file mode 100644
index 0000000..b85109f
--- /dev/null
+++ b/src/global/mail_run.h
@@ -0,0 +1,31 @@
+#ifndef _MAIL_RUN_H_INCLUDED_
+#define _MAIL_RUN_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_run 3h
+/* SUMMARY
+/* run mail component program
+/* SYNOPSIS
+/* #include <mail_run.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int mail_run_foreground(const char *, char **);
+extern int mail_run_background(const char *, char **);
+extern NORETURN mail_run_replace(const char *, char **);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_scan_dir.c b/src/global/mail_scan_dir.c
new file mode 100644
index 0000000..f011536
--- /dev/null
+++ b/src/global/mail_scan_dir.c
@@ -0,0 +1,62 @@
+/*++
+/* NAME
+/* mail_scan_dir 3
+/* SUMMARY
+/* mail queue directory scanning support
+/* SYNOPSIS
+/* #include <mail_scan_dir.h>
+/*
+/* char *mail_scan_dir_next(scan)
+/* SCAN_DIR *scan;
+/* DESCRIPTION
+/* The \fBmail_scan_dir_next\fR() routine is a wrapper around
+/* scan_dir_next() that understands the structure of a Postfix
+/* mail queue. The result is a queue ID or a null pointer.
+/* SEE ALSO
+/* scan_dir(3) directory scanner
+/* 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 <scan_dir.h>
+
+/* Global library. */
+
+#include <mail_scan_dir.h>
+
+/* mail_scan_dir_next - return next queue file */
+
+char *mail_scan_dir_next(SCAN_DIR *scan)
+{
+ char *name;
+
+ /*
+ * Exploit the fact that mail queue subdirectories have one-letter names,
+ * so we don't have to stat() every file in sight. This is a win because
+ * many dirent implementations do not return file type information.
+ */
+ for (;;) {
+ if ((name = scan_dir_next(scan)) == 0) {
+ if (scan_dir_pop(scan) == 0)
+ return (0);
+ } else if (strlen(name) == 1) {
+ scan_dir_push(scan, name);
+ } else {
+ return (name);
+ }
+ }
+}
diff --git a/src/global/mail_scan_dir.h b/src/global/mail_scan_dir.h
new file mode 100644
index 0000000..fda1326
--- /dev/null
+++ b/src/global/mail_scan_dir.h
@@ -0,0 +1,35 @@
+#ifndef _MAIL_SCAN_DIR_H_INCLUDED_
+#define _MAIL_SCAN_DIR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_scan_dir 3h
+/* SUMMARY
+/* mail directory scanner support
+/* SYNOPSIS
+/* #include <mail_scan_dir.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <scan_dir.h>
+
+ /*
+ * External interface.
+ */
+extern char *mail_scan_dir_next(SCAN_DIR *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_stream.c b/src/global/mail_stream.c
new file mode 100644
index 0000000..46cc87e
--- /dev/null
+++ b/src/global/mail_stream.c
@@ -0,0 +1,627 @@
+/*++
+/* NAME
+/* mail_stream 3
+/* SUMMARY
+/* mail stream management
+/* SYNOPSIS
+/* #include <mail_stream.h>
+/*
+/* typedef struct {
+/* .in +4
+/* VSTREAM *stream; /* read/write stream */
+/* char *id; /* queue ID */
+/* struct timeval ctime; /* create time */
+/* private members...
+/* .in -4
+/* } MAIL_STREAM;
+/*
+/* MAIL_STREAM *mail_stream_file(queue, class, service, mode)
+/* const char *queue;
+/* const char *class;
+/* const char *service;
+/* int mode;
+/*
+/* MAIL_STREAM *mail_stream_service(class, service)
+/* const char *class;
+/* const char *service;
+/*
+/* MAIL_STREAM *mail_stream_command(command)
+/* const char *command;
+/*
+/* void mail_stream_cleanup(info)
+/* MAIL_STREAM *info;
+/*
+/* int mail_stream_finish(info, why)
+/* MAIL_STREAM *info;
+/* VSTRING *why;
+/*
+/* void mail_stream_ctl(info, op, ...)
+/* MAIL_STREAM *info;
+/* int op;
+/* DESCRIPTION
+/* This module provides a generic interface to Postfix queue file
+/* format messages to file, to Postfix server, or to external command.
+/* The routines that open a stream return a handle with an initialized
+/* stream and queue id member. The handle is either given to a cleanup
+/* routine, to dispose of a failed request, or to a finish routine, to
+/* complete the request.
+/*
+/* mail_stream_file() opens a mail stream to a newly-created file and
+/* arranges for trigger delivery at finish time. This call never fails.
+/* But it may take forever. The mode argument specifies additional
+/* file permissions that will be OR-ed in when the file is finished.
+/* While embryonic files have mode 0600, finished files have mode 0700.
+/*
+/* mail_stream_command() opens a mail stream to external command,
+/* and receives queue ID information from the command. The result
+/* is a null pointer when the initial handshake fails. The command
+/* is given to the shell only when necessary. At finish time, the
+/* command is expected to send a completion status.
+/*
+/* mail_stream_service() opens a mail stream to Postfix service,
+/* and receives queue ID information from the command. The result
+/* is a null pointer when the initial handshake fails. At finish
+/* time, the daemon is expected to send a completion status.
+/*
+/* mail_stream_cleanup() cancels the operation that was started with
+/* any of the mail_stream_xxx() routines, and destroys the argument.
+/* It is up to the caller to remove incomplete file objects.
+/*
+/* mail_stream_finish() completes the operation that was started with
+/* any of the mail_stream_xxx() routines, and destroys the argument.
+/* The result is any of the status codes defined in <cleanup_user.h>.
+/* It is up to the caller to remove incomplete file objects.
+/* The why argument can be a null pointer.
+/*
+/* mail_stream_ctl() selectively overrides information that
+/* was specified with mail_stream_file(); none of the attributes
+/* are applicable for other mail stream types. The arguments
+/* are a list macros with arguments, terminated with
+/* CA_MAIL_STREAM_CTL_END which has none. The following lists
+/* the macros and the types of the corresponding arguments.
+/* .IP "CA_MAIL_STREAM_CTL_QUEUE(const char *)"
+/* The argument specifies an alternate destination queue. The
+/* queue file is moved to the specified queue before the call
+/* returns. Failure to rename the queue file results in a fatal
+/* error.
+/* .IP "CA_MAIL_STREAM_CTL_CLASS(const char *)"
+/* The argument specifies an alternate trigger class.
+/* .IP "CA_MAIL_STREAM_CTL_SERVICE(const char *)"
+/* The argument specifies an alternate trigger service.
+/* .IP "CA_MAIL_STREAM_CTL_MODE(int)"
+/* The argument specifies alternate permissions that override
+/* the permissions specified with mail_stream_file().
+/* .IP "CA_MAIL_STREAM_CTL_DELAY(int)"
+/* Attempt to postpone initial delivery by advancing the queue
+/* file modification time stamp by this amount. This has
+/* effect only within the deferred mail queue.
+/* This feature may have no effect with remote file systems.
+/* 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/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <utime.h>
+#include <string.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <argv.h>
+#include <sane_fsops.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <opened.h>
+#include <mail_params.h>
+#include <mail_stream.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+static VSTRING *id_buf;
+
+#define FREE_AND_WIPE(free, arg) do { if (arg) free(arg); arg = 0; } while (0)
+
+#define STR(x) vstring_str(x)
+
+/* mail_stream_cleanup - clean up after success or failure */
+
+void mail_stream_cleanup(MAIL_STREAM *info)
+{
+ if (info->stream && info->close(info->stream))
+ msg_warn("mail_stream_cleanup: close error");
+ FREE_AND_WIPE(myfree, info->queue);
+ FREE_AND_WIPE(myfree, info->id);
+ FREE_AND_WIPE(myfree, info->class);
+ FREE_AND_WIPE(myfree, info->service);
+ myfree((void *) info);
+}
+
+#if defined(HAS_FUTIMES_AT)
+#define CAN_STAMP_BY_STREAM
+
+/* stamp_stream - update open file [am]time stamp */
+
+static int stamp_stream(VSTREAM *fp, time_t when)
+{
+ struct timeval tv[2];
+
+ if (when != 0) {
+ tv[0].tv_sec = tv[1].tv_sec = when;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+ return (futimesat(vstream_fileno(fp), (char *) 0, tv));
+ } else {
+ return (futimesat(vstream_fileno(fp), (char *) 0, (struct timeval *) 0));
+ }
+}
+
+#elif defined(HAS_FUTIMES)
+#define CAN_STAMP_BY_STREAM
+
+/* stamp_stream - update open file [am]time stamp */
+
+static int stamp_stream(VSTREAM *fp, time_t when)
+{
+ struct timeval tv[2];
+
+ if (when != 0) {
+ tv[0].tv_sec = tv[1].tv_sec = when;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+ return (futimes(vstream_fileno(fp), tv));
+ } else {
+ return (futimes(vstream_fileno(fp), (struct timeval *) 0));
+ }
+}
+
+#endif
+
+/* stamp_path - update file [am]time stamp by pathname */
+
+static int stamp_path(const char *path, time_t when)
+{
+ struct utimbuf tbuf;
+
+ if (when != 0) {
+ tbuf.actime = tbuf.modtime = when;
+ return (utime(path, &tbuf));
+ } else {
+ return (utime(path, (struct utimbuf *) 0));
+ }
+}
+
+/* mail_stream_finish_file - finish file mail stream */
+
+static int mail_stream_finish_file(MAIL_STREAM *info, VSTRING *unused_why)
+{
+ int status = CLEANUP_STAT_OK;
+ static char wakeup[] = {TRIGGER_REQ_WAKEUP};
+ struct stat st;
+ char *path_to_reset = 0;
+ static int incoming_fs_clock_ok = 0;
+ static int incoming_clock_warned = 0;
+ int check_incoming_fs_clock;
+ int err;
+ time_t want_stamp;
+ time_t expect_stamp;
+
+ /*
+ * Make sure the message makes it to file. Set the execute bit when no
+ * write error was detected. Some people believe that this code has a
+ * problem if the system crashes before fsync() returns; fchmod() could
+ * take effect before all the data blocks are written. Wietse claims that
+ * this is not a problem. Postfix rejects incomplete queue files, even
+ * when the +x attribute is set. Every Postfix queue file record has a
+ * type code and a length field. Files with missing records are rejected,
+ * as are files with unknown record type codes. Every Postfix queue file
+ * must end with an explicit END record. Postfix queue files without END
+ * record are discarded.
+ *
+ * Attempt to detect file system clocks that are ahead of local time, but
+ * don't check the file system clock all the time. The effect of file
+ * system clock drift can be difficult to understand (Postfix ignores new
+ * mail until the local clock catches up with the file mtime stamp).
+ *
+ * This clock drift detection code may not work with file systems that work
+ * on a local copy of the file and that update the server only after the
+ * file is closed.
+ *
+ * Optionally set a cooldown time.
+ *
+ * XXX: We assume that utime() does control the file modification time even
+ * when followed by an fchmod(), fsync(), close() sequence. This may fail
+ * with remote file systems when fsync() actually updates the file. Even
+ * then, we still delay the average message by 1/2 of the
+ * queue_run_delay.
+ *
+ * XXX: Victor does not like running utime() after the close(), since this
+ * creates a race even with local filesystems. But Wietse is not
+ * confident that utime() before fsync() and close() will work reliably
+ * with remote file systems.
+ *
+ * XXX Don't run the clock skew tests with Postfix sendmail submissions.
+ * Don't whine against unsuspecting users or applications.
+ */
+ check_incoming_fs_clock =
+ (!incoming_fs_clock_ok && !strcmp(info->queue, MAIL_QUEUE_INCOMING));
+
+#ifdef DELAY_ACTION
+ if (strcmp(info->queue, MAIL_QUEUE_DEFERRED) != 0)
+ info->delay = 0;
+ if (info->delay > 0)
+ want_stamp = time((time_t *) 0) + info->delay;
+ else
+#endif
+ want_stamp = 0;
+
+ /*
+ * If we can cheaply set the file time stamp (no pathname lookup) do it
+ * anyway, so that we can avoid whining later about file server/client
+ * clock skew.
+ *
+ * Otherwise, if we must set the file time stamp for delayed delivery, use
+ * whatever means we have to get the job done, no matter if it is
+ * expensive.
+ *
+ * XXX Unfortunately, Linux futimes() is not usable because it uses /proc.
+ * This may not be available because of chroot, or because of access
+ * restrictions after a process changes privileges.
+ */
+ if (vstream_fflush(info->stream)
+#ifdef CAN_STAMP_BY_STREAM
+ || stamp_stream(info->stream, want_stamp)
+#else
+ || (want_stamp && stamp_path(VSTREAM_PATH(info->stream), want_stamp))
+#endif
+ || fchmod(vstream_fileno(info->stream), 0700 | info->mode)
+#ifdef HAS_FSYNC
+ || fsync(vstream_fileno(info->stream))
+#endif
+ || (check_incoming_fs_clock
+ && fstat(vstream_fileno(info->stream), &st) < 0)
+ )
+ status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE);
+#ifdef TEST
+ st.st_mtime += 10;
+#endif
+
+ /*
+ * Work around file system clock skew. If the file system clock is ahead
+ * of the local clock, Postfix won't deliver mail immediately, which is
+ * bad for performance. If the file system clock falls behind the local
+ * clock, it just looks silly in mail headers.
+ */
+ if (status == CLEANUP_STAT_OK && check_incoming_fs_clock) {
+ /* Do NOT use time() result from before fsync(). */
+ expect_stamp = want_stamp ? want_stamp : time((time_t *) 0);
+ if (st.st_mtime > expect_stamp) {
+ path_to_reset = mystrdup(VSTREAM_PATH(info->stream));
+ if (incoming_clock_warned == 0) {
+ msg_warn("file system clock is %d seconds ahead of local clock",
+ (int) (st.st_mtime - expect_stamp));
+ msg_warn("resetting file time stamps - this hurts performance");
+ incoming_clock_warned = 1;
+ }
+ } else {
+ if (st.st_mtime < expect_stamp - 100)
+ msg_warn("file system clock is %d seconds behind local clock",
+ (int) (expect_stamp - st.st_mtime));
+ incoming_fs_clock_ok = 1;
+ }
+ }
+
+ /*
+ * Close the queue file and mark it as closed. Be prepared for
+ * vstream_fclose() to fail even after vstream_fflush() and fsync()
+ * reported no error. Reason: after a file is closed, some networked file
+ * systems copy the file out to another machine. Running the queue on a
+ * remote file system is not recommended, if only for performance
+ * reasons.
+ */
+ err = info->close(info->stream);
+ info->stream = 0;
+ if (status == CLEANUP_STAT_OK && err != 0)
+ status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE);
+
+ /*
+ * Work around file system clocks that are ahead of local time.
+ */
+ if (path_to_reset != 0) {
+ if (status == CLEANUP_STAT_OK) {
+ if (stamp_path(path_to_reset, expect_stamp) < 0 && errno != ENOENT)
+ msg_fatal("%s: update file time stamps: %m", info->id);
+ }
+ myfree(path_to_reset);
+ }
+
+ /*
+ * When all is well, notify the next service that a new message has been
+ * queued.
+ */
+ if (status == CLEANUP_STAT_OK && info->class && info->service)
+ mail_trigger(info->class, info->service, wakeup, sizeof(wakeup));
+
+ /*
+ * Cleanup.
+ */
+ mail_stream_cleanup(info);
+ return (status);
+}
+
+/* mail_stream_finish_ipc - finish IPC mail stream */
+
+static int mail_stream_finish_ipc(MAIL_STREAM *info, VSTRING *why)
+{
+ int status = CLEANUP_STAT_WRITE;
+
+ /*
+ * Receive the peer's completion status.
+ */
+ if ((why && attr_scan(info->stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, why),
+ ATTR_TYPE_END) != 2)
+ || (!why && attr_scan(info->stream, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1))
+ status = CLEANUP_STAT_WRITE;
+
+ /*
+ * Cleanup.
+ */
+ mail_stream_cleanup(info);
+ return (status);
+}
+
+/* mail_stream_finish - finish action */
+
+int mail_stream_finish(MAIL_STREAM *info, VSTRING *why)
+{
+ return (info->finish(info, why));
+}
+
+/* mail_stream_file - destination is file */
+
+MAIL_STREAM *mail_stream_file(const char *queue, const char *class,
+ const char *service, int mode)
+{
+ struct timeval tv;
+ MAIL_STREAM *info;
+ VSTREAM *stream;
+
+ stream = mail_queue_enter(queue, 0600 | mode, &tv);
+ if (msg_verbose)
+ msg_info("open %s", VSTREAM_PATH(stream));
+
+ info = (MAIL_STREAM *) mymalloc(sizeof(*info));
+ info->stream = stream;
+ info->finish = mail_stream_finish_file;
+ info->close = vstream_fclose;
+ info->queue = mystrdup(queue);
+ info->id = mystrdup(basename(VSTREAM_PATH(stream)));
+ info->class = mystrdup(class);
+ info->service = mystrdup(service);
+ info->mode = mode;
+#ifdef DELAY_ACTION
+ info->delay = 0;
+#endif
+ info->ctime = tv;
+ return (info);
+}
+
+/* mail_stream_service - destination is service */
+
+MAIL_STREAM *mail_stream_service(const char *class, const char *name)
+{
+ VSTREAM *stream;
+ MAIL_STREAM *info;
+
+ if (id_buf == 0)
+ id_buf = vstring_alloc(10);
+
+ stream = mail_connect_wait(class, name);
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) {
+ vstream_fclose(stream);
+ return (0);
+ } else {
+ info = (MAIL_STREAM *) mymalloc(sizeof(*info));
+ info->stream = stream;
+ info->finish = mail_stream_finish_ipc;
+ info->close = vstream_fclose;
+ info->queue = 0;
+ info->id = mystrdup(vstring_str(id_buf));
+ info->class = 0;
+ info->service = 0;
+ return (info);
+ }
+}
+
+/* mail_stream_command - destination is command */
+
+MAIL_STREAM *mail_stream_command(const char *command)
+{
+ VSTREAM *stream;
+ MAIL_STREAM *info;
+ ARGV *export_env;
+ int status;
+
+ if (id_buf == 0)
+ id_buf = vstring_alloc(10);
+
+ /*
+ * Treat fork() failure as a transient problem. Treat bad handshake as a
+ * permanent error.
+ *
+ * XXX Are we invoking a Postfix process or a non-Postfix process? In the
+ * former case we can share the full environment; in the latter case only
+ * a restricted environment should be propagated. Even though we are
+ * talking a Postfix-internal protocol there is no way we can tell what
+ * is being executed except by duplicating a lot of existing code.
+ */
+ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
+ while ((stream = vstream_popen(O_RDWR,
+ CA_VSTREAM_POPEN_COMMAND(command),
+ CA_VSTREAM_POPEN_EXPORT(export_env->argv),
+ CA_VSTREAM_POPEN_END)) == 0) {
+ msg_warn("fork: %m");
+ sleep(10);
+ }
+ argv_free(export_env);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(command),
+ CA_VSTREAM_CTL_END);
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_POSTDROP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) {
+ if ((status = vstream_pclose(stream)) != 0)
+ msg_warn("command \"%s\" exited with status %d", command, status);
+ return (0);
+ } else {
+ info = (MAIL_STREAM *) mymalloc(sizeof(*info));
+ info->stream = stream;
+ info->finish = mail_stream_finish_ipc;
+ info->close = vstream_pclose;
+ info->queue = 0;
+ info->id = mystrdup(vstring_str(id_buf));
+ info->class = 0;
+ info->service = 0;
+ return (info);
+ }
+}
+
+/* mail_stream_ctl - update file-based mail stream properties */
+
+void mail_stream_ctl(MAIL_STREAM *info, int op,...)
+{
+ const char *myname = "mail_stream_ctl";
+ va_list ap;
+ char *new_queue = 0;
+ char *string_value;
+
+ /*
+ * Sanity check. None of the attributes below are applicable unless the
+ * target is a file-based stream.
+ */
+ if (info->finish != mail_stream_finish_file)
+ msg_panic("%s: attempt to update non-file stream %s",
+ myname, info->id);
+
+ for (va_start(ap, op); op != MAIL_STREAM_CTL_END; op = va_arg(ap, int)) {
+
+ switch (op) {
+
+ /*
+ * Change the queue directory. We do this at the end of this
+ * call.
+ */
+ case MAIL_STREAM_CTL_QUEUE:
+ if ((new_queue = va_arg(ap, char *)) == 0)
+ msg_panic("%s: NULL queue",
+ myname);
+ break;
+
+ /*
+ * Change the service that needs to be notified.
+ */
+ case MAIL_STREAM_CTL_CLASS:
+ FREE_AND_WIPE(myfree, info->class);
+ if ((string_value = va_arg(ap, char *)) != 0)
+ info->class = mystrdup(string_value);
+ break;
+
+ case MAIL_STREAM_CTL_SERVICE:
+ FREE_AND_WIPE(myfree, info->service);
+ if ((string_value = va_arg(ap, char *)) != 0)
+ info->service = mystrdup(string_value);
+ break;
+
+ /*
+ * Change the (finished) file access mode.
+ */
+ case MAIL_STREAM_CTL_MODE:
+ info->mode = va_arg(ap, int);
+ break;
+
+ /*
+ * Advance the (finished) file modification time.
+ */
+#ifdef DELAY_ACTION
+ case MAIL_STREAM_CTL_DELAY:
+ if ((info->delay = va_arg(ap, int)) < 0)
+ msg_panic("%s: bad delay time %d", myname, info->delay);
+ break;
+#endif
+
+ default:
+ msg_panic("%s: bad op code %d", myname, op);
+ }
+ }
+ va_end(ap);
+
+ /*
+ * Rename the queue file after allocating memory for new information, so
+ * that the caller can still remove an embryonic file when memory
+ * allocation fails (there is no risk of deleting the wrong file).
+ *
+ * Wietse opposed the idea to update run-time error handler information
+ * here, because this module wasn't designed to defend against internal
+ * concurrency issues with error handlers that attempt to follow dangling
+ * pointers.
+ *
+ * This code duplicates mail_queue_rename(), except that we need the new
+ * path to update the stream pathname.
+ */
+ if (new_queue != 0 && strcmp(info->queue, new_queue) != 0) {
+ char *saved_queue = info->queue;
+ char *saved_path = mystrdup(VSTREAM_PATH(info->stream));
+ VSTRING *new_path = vstring_alloc(100);
+
+ (void) mail_queue_path(new_path, new_queue, info->id);
+ info->queue = mystrdup(new_queue);
+ vstream_control(info->stream, CA_VSTREAM_CTL_PATH(STR(new_path)),
+ CA_VSTREAM_CTL_END);
+
+ if (sane_rename(saved_path, STR(new_path)) == 0
+ || (mail_queue_mkdirs(STR(new_path)) == 0
+ && sane_rename(saved_path, STR(new_path)) == 0)) {
+ if (msg_verbose)
+ msg_info("%s: placed in %s queue", info->id, info->queue);
+ } else {
+ msg_fatal("%s: move to %s queue failed: %m", info->id,
+ info->queue);
+ }
+
+ myfree(saved_path);
+ myfree(saved_queue);
+ vstring_free(new_path);
+ }
+}
diff --git a/src/global/mail_stream.h b/src/global/mail_stream.h
new file mode 100644
index 0000000..b5280e4
--- /dev/null
+++ b/src/global/mail_stream.h
@@ -0,0 +1,91 @@
+#ifndef _MAIL_STREAM_H_INCLUDED_
+#define _MAIL_STREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_stream 3h
+/* SUMMARY
+/* mail stream management
+/* SYNOPSIS
+/* #include <mail_stream.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+typedef struct MAIL_STREAM MAIL_STREAM;
+
+typedef int (*MAIL_STREAM_FINISH_FN) (MAIL_STREAM *, VSTRING *);
+typedef int (*MAIL_STREAM_CLOSE_FN) (VSTREAM *);
+
+struct MAIL_STREAM {
+ VSTREAM *stream; /* file or pipe or socket */
+ char *queue; /* (initial) queue name */
+ char *id; /* queue id */
+ MAIL_STREAM_FINISH_FN finish; /* finish code */
+ MAIL_STREAM_CLOSE_FN close; /* close stream */
+ char *class; /* trigger class */
+ char *service; /* trigger service */
+ int mode; /* additional permissions */
+#ifdef DELAY_ACTION
+ int delay; /* deferred delivery */
+#endif
+ struct timeval ctime; /* creation time */
+};
+
+/* Legacy type-unchecked API, internal use. */
+#define MAIL_STREAM_CTL_END 0 /* Terminator */
+#define MAIL_STREAM_CTL_QUEUE 1 /* Change queue */
+#define MAIL_STREAM_CTL_CLASS 2 /* Change notification class */
+#define MAIL_STREAM_CTL_SERVICE 3 /* Change notification service */
+#define MAIL_STREAM_CTL_MODE 4 /* Change final queue file mode */
+#ifdef DELAY_ACTION
+#define MAIL_STREAM_CTL_DELAY 5 /* Change final queue file mtime */
+#endif
+
+/* Type-checked API, external use. */
+#define CA_MAIL_STREAM_CTL_END MAIL_STREAM_CTL_END
+#define CA_MAIL_STREAM_CTL_QUEUE(v) MAIL_STREAM_CTL_QUEUE, CHECK_CPTR(MAIL_STREAM, char, (v))
+#define CA_MAIL_STREAM_CTL_CLASS(v) MAIL_STREAM_CTL_CLASS, CHECK_CPTR(MAIL_STREAM, char, (v))
+#define CA_MAIL_STREAM_CTL_SERVICE(v) MAIL_STREAM_CTL_SERVICE, CHECK_CPTR(MAIL_STREAM, char, (v))
+#define CA_MAIL_STREAM_CTL_MODE(v) MAIL_STREAM_CTL_MODE, CHECK_VAL(MAIL_STREAM, int, (v))
+#ifdef DELAY_ACTION
+#define CA_MAIL_STREAM_CTL_DELAY(v) MAIL_STREAM_CTL_DELAY, CHECK_VAL(MAIL_STREAM, int, (v))
+#endif
+
+CHECK_VAL_HELPER_DCL(MAIL_STREAM, int);
+CHECK_CPTR_HELPER_DCL(MAIL_STREAM, char);
+
+extern MAIL_STREAM *mail_stream_file(const char *, const char *, const char *, int);
+extern MAIL_STREAM *mail_stream_service(const char *, const char *);
+extern MAIL_STREAM *mail_stream_command(const char *);
+extern void mail_stream_cleanup(MAIL_STREAM *);
+extern int mail_stream_finish(MAIL_STREAM *, VSTRING *);
+extern void mail_stream_ctl(MAIL_STREAM *, int,...);
+
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_task.c b/src/global/mail_task.c
new file mode 100644
index 0000000..733645d
--- /dev/null
+++ b/src/global/mail_task.c
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/* mail_task 3
+/* SUMMARY
+/* set task name for logging purposes
+/* SYNOPSIS
+/* #include <mail_task.h>
+/*
+/* const char *mail_task(argv0)
+/* const char *argv0;
+/* DESCRIPTION
+/* mail_task() enforces consistent naming of mailer processes.
+/* It strips pathname information from the process name, and
+/* prepends the name of the mail system so that logfile entries
+/* are easier to recognize. The mail system name is specified
+/* with the "syslog_name" configuration parameter.
+/*
+/* The result is overwritten with each call.
+/*
+/* A null argv0 argument requests that the current result is
+/* returned, or "unknown" when no current result exists.
+/* 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 <vstring.h>
+#include <safe.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_conf.h"
+#include "mail_task.h"
+
+/* mail_task - clean up and decorate the process name */
+
+const char *mail_task(const char *argv0)
+{
+ static VSTRING *canon_name;
+ const char *slash;
+ const char *tag;
+
+ if (argv0 == 0 && canon_name == 0)
+ argv0 = "unknown";
+ if (argv0) {
+ if (canon_name == 0)
+ canon_name = vstring_alloc(10);
+ if ((slash = strrchr(argv0, '/')) != 0 && slash[1])
+ argv0 = slash + 1;
+ /* Setenv()-ed from main.cf, or inherited from master. */
+ if ((tag = safe_getenv(CONF_ENV_LOGTAG)) == 0)
+ /* Check main.cf settings directly, in case set-gid. */
+ tag = var_syslog_name ? var_syslog_name :
+ mail_conf_eval(DEF_SYSLOG_NAME);
+ vstring_sprintf(canon_name, "%s/%s", tag, argv0);
+ }
+ return (vstring_str(canon_name));
+}
diff --git a/src/global/mail_task.h b/src/global/mail_task.h
new file mode 100644
index 0000000..942753f
--- /dev/null
+++ b/src/global/mail_task.h
@@ -0,0 +1,29 @@
+#ifndef _MAIL_TASK_H_INCLUDED_
+#define _MAIL_TASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_task 3h
+/* SUMMARY
+/* canonicalize process name
+/* SYNOPSIS
+/* #include <mail_task.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *mail_task(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_trigger.c b/src/global/mail_trigger.c
new file mode 100644
index 0000000..d9f74a5
--- /dev/null
+++ b/src/global/mail_trigger.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* mail_trigger 3
+/* SUMMARY
+/* trigger a mail service
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* int mail_trigger(class, service, request, length)
+/* const char *class;
+/* const char *service;
+/* const char *request;
+/* ssize_t length;
+/* DESCRIPTION
+/* mail_trigger() wakes up the specified mail subsystem, by
+/* sending it the specified request. In the case of non-FIFO
+/* server endpoints, a short-running program should invoke
+/* event_drain() to ensure proper request delivery.
+/*
+/* Arguments:
+/* .IP class
+/* Name of a class of local transport channel endpoints,
+/* either \fIpublic\fR (accessible by any local user) or
+/* \fIprivate\fR (administrative access only).
+/* .IP service
+/* The name of a local transport endpoint within the named class.
+/* .IP request
+/* A string. The list of valid requests is service specific.
+/* .IP length
+/* The length of the request string.
+/* DIAGNOSTICS
+/* The result is -1 in case of problems, 0 otherwise.
+/* Warnings are logged.
+/* BUGS
+/* Works with FIFO or UNIX-domain services only.
+/*
+/* Should use master.cf to find out what transport to use.
+/* SEE ALSO
+/* fifo_trigger(3) trigger a FIFO-based service
+/* unix_trigger(3) trigger a UNIX_domain service
+/* 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 <iostuff.h>
+#include <trigger.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "mail_proto.h"
+
+/* mail_trigger - trigger a service */
+
+int mail_trigger(const char *class, const char *service,
+ const char *req_buf, ssize_t req_len)
+{
+ struct stat st;
+ char *path;
+ int status;
+
+ /*
+ * XXX Some systems cannot tell the difference between a named pipe
+ * (fifo) or a UNIX-domain socket. So we may have to try both.
+ */
+ path = mail_pathname(class, service);
+ if ((status = stat(path, &st)) < 0) {
+ msg_warn("unable to look up %s: %m", path);
+ } else if (S_ISFIFO(st.st_mode)) {
+ status = fifo_trigger(path, req_buf, req_len, var_trigger_timeout);
+ if (status < 0 && S_ISSOCK(st.st_mode))
+ status = LOCAL_TRIGGER(path, req_buf, req_len, var_trigger_timeout);
+ } else if (S_ISSOCK(st.st_mode)) {
+ status = LOCAL_TRIGGER(path, req_buf, req_len, var_trigger_timeout);
+ } else {
+ msg_warn("%s is not a socket or a fifo", path);
+ status = -1;
+ }
+ myfree(path);
+ return (status);
+}
diff --git a/src/global/mail_version.c b/src/global/mail_version.c
new file mode 100644
index 0000000..0ded035
--- /dev/null
+++ b/src/global/mail_version.c
@@ -0,0 +1,258 @@
+/*++
+/* NAME
+/* mail_version 3
+/* SUMMARY
+/* time-dependent probe sender addresses
+/* SYNOPSIS
+/* #include <mail_version.h>
+/*
+/* typedef struct {
+/* char *program; /* postfix */
+/* int major; /* 2 */
+/* int minor; /* 9 */
+/* int patch; /* patchlevel or -1 */
+/* char *snapshot; /* null or snapshot info */
+/* } MAIL_VERSION;
+/*
+/* MAIL_VERSION *mail_version_parse(version_string, why)
+/* const char *version_string;
+/* const char **why;
+/*
+/* void mail_version_free(mp)
+/* MAIL_VERSION *mp;
+/*
+/* const char *get_mail_version()
+/*
+/* int check_mail_version(version_string)
+/* const char *version_string;
+/* DESCRIPTION
+/* This module understands the format of Postfix version strings
+/* (for example the default value of "mail_version"), and
+/* provides support to compare the compile-time version of a
+/* Postfix program with the run-time version of a Postfix
+/* library. Apparently, some distributions don't use proper
+/* so-number versioning, causing programs to fail erratically
+/* after an update replaces the library but not the program.
+/*
+/* A Postfix version string consists of two or three parts
+/* separated by a single "-" character:
+/* .IP \(bu
+/* The first part is a string with the program name.
+/* .IP \(bu
+/* The second part is the program version: either two or three
+/* non-negative integer numbers separated by single "."
+/* character. Stable releases have a major version, minor
+/* version and patchlevel; experimental releases (snapshots)
+/* have only major and minor version numbers.
+/* .IP \(bu
+/* The third part is ignored with a stable release, otherwise
+/* it is a string with the snapshot release date plus some
+/* optional information.
+/*
+/* mail_version_parse() parses a version string.
+/*
+/* get_mail_version() returns the version string (the value
+/* of DEF_MAIL_VERSION) that is compiled into the library.
+/*
+/* check_mail_version() compares the caller's version string
+/* (usually the value of DEF_MAIL_VERSION) that is compiled
+/* into the caller, and logs a warning when the strings differ.
+/* DIAGNOSTICS
+/* In the case of a parsing error, mail_version_parse() returns
+/* a null pointer, and sets the why argument to a string with
+/* problem details.
+/* 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 <stdlib.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_version.h>
+
+/* mail_version_int - convert integer */
+
+static int mail_version_int(const char *strval)
+{
+ char *end;
+ int intval;
+ long longval;
+
+ errno = 0;
+ intval = longval = strtol(strval, &end, 10);
+ if (*strval == 0 || *end != 0 || errno == ERANGE || longval != intval)
+ intval = (-1);
+ return (intval);
+}
+
+/* mail_version_worker - do the parsing work */
+
+static const char *mail_version_worker(MAIL_VERSION *mp, char *cp)
+{
+ char *major_field;
+ char *minor_field;
+ char *patch_field;
+
+ /*
+ * Program name.
+ */
+ if ((mp->program = mystrtok(&cp, "-")) == 0)
+ return ("no program name");
+
+ /*
+ * Major, minor, patchlevel. If this is a stable release, then we ignore
+ * text after the patchlevel, in case there are vendor extensions.
+ */
+ if ((major_field = mystrtok(&cp, "-")) == 0)
+ return ("missing major version");
+
+ if ((minor_field = split_at(major_field, '.')) == 0)
+ return ("missing minor version");
+ if ((mp->major = mail_version_int(major_field)) < 0)
+ return ("bad major version");
+ patch_field = split_at(minor_field, '.');
+ if ((mp->minor = mail_version_int(minor_field)) < 0)
+ return ("bad minor version");
+
+ if (patch_field == 0)
+ mp->patch = -1;
+ else if ((mp->patch = mail_version_int(patch_field)) < 0)
+ return ("bad patchlevel");
+
+ /*
+ * Experimental release. If this is not a stable release, we take
+ * everything to the end of the string.
+ */
+ if (patch_field != 0)
+ mp->snapshot = 0;
+ else if ((mp->snapshot = mystrtok(&cp, "")) == 0)
+ return ("missing snapshot field");
+
+ return (0);
+}
+
+/* mail_version_parse - driver */
+
+MAIL_VERSION *mail_version_parse(const char *string, const char **why)
+{
+ MAIL_VERSION *mp;
+ char *saved_string;
+ const char *err;
+
+ mp = (MAIL_VERSION *) mymalloc(sizeof(*mp));
+ saved_string = mystrdup(string);
+ if ((err = mail_version_worker(mp, saved_string)) != 0) {
+ *why = err;
+ myfree(saved_string);
+ myfree((void *) mp);
+ return (0);
+ } else {
+ return (mp);
+ }
+}
+
+/* mail_version_free - destroy version information */
+
+void mail_version_free(MAIL_VERSION *mp)
+{
+ myfree(mp->program);
+ myfree((void *) mp);
+}
+
+/* get_mail_version - return parsed mail version string */
+
+const char *get_mail_version(void)
+{
+ return (DEF_MAIL_VERSION);
+}
+
+/* check_mail_version - compare caller version with library version */
+
+void check_mail_version(const char *version_string)
+{
+ if (strcmp(version_string, DEF_MAIL_VERSION) != 0)
+ msg_warn("Postfix library version mis-match: wanted %s, found %s",
+ version_string, DEF_MAIL_VERSION);
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+/* parse_sample - parse a sample string from argv or stdin */
+
+static void parse_sample(const char *sample)
+{
+ MAIL_VERSION *mp;
+ const char *why;
+
+ mp = mail_version_parse(sample, &why);
+ if (mp == 0) {
+ vstream_printf("ERROR: %s: %s\n", sample, why);
+ } else {
+ vstream_printf("program: %s\t", mp->program);
+ vstream_printf("major: %d\t", mp->major);
+ vstream_printf("minor: %d\t", mp->minor);
+ if (mp->patch < 0)
+ vstream_printf("snapshot: %s\n", mp->snapshot);
+ else
+ vstream_printf("patch: %d\n", mp->patch);
+ mail_version_free(mp);
+ }
+ vstream_fflush(VSTREAM_OUT);
+}
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ VSTRING *inbuf = vstring_alloc(1);
+ int have_tty = isatty(0);
+
+ if (argc > 1) {
+ while (--argc > 0 && *++argv)
+ parse_sample(*argv);
+ } else {
+ for (;;) {
+ if (have_tty) {
+ vstream_printf("> ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0)
+ break;
+ if (have_tty == 0)
+ vstream_printf("> %s\n", STR(inbuf));
+ if (*STR(inbuf) == 0 || *STR(inbuf) == '#')
+ continue;
+ parse_sample(STR(inbuf));
+ }
+ }
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mail_version.h b/src/global/mail_version.h
new file mode 100644
index 0000000..2192455
--- /dev/null
+++ b/src/global/mail_version.h
@@ -0,0 +1,109 @@
+#ifndef _MAIL_VERSION_H_INCLUDED_
+#define _MAIL_VERSION_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_version 3h
+/* SUMMARY
+/* globally configurable parameters
+/* SYNOPSIS
+/* #include <mail_version.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Version of this program. Official versions are called a.b.c, and
+ * snapshots are called a.b-yyyymmdd, where a=major release number, b=minor
+ * release number, c=patchlevel, and yyyymmdd is the release date:
+ * yyyy=year, mm=month, dd=day.
+ *
+ * Patches change both the patchlevel and the release date. Snapshots have no
+ * patchlevel; they change the release date only.
+ */
+#define MAIL_RELEASE_DATE "20240121"
+#define MAIL_VERSION_NUMBER "3.7.10"
+
+#ifdef SNAPSHOT
+#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
+#else
+#define MAIL_VERSION_DATE ""
+#endif
+
+#ifdef NONPROD
+#define MAIL_VERSION_PROD "-nonprod"
+#else
+#define MAIL_VERSION_PROD ""
+#endif
+
+#define VAR_MAIL_VERSION "mail_version"
+#define DEF_MAIL_VERSION MAIL_VERSION_NUMBER MAIL_VERSION_DATE MAIL_VERSION_PROD
+
+extern char *var_mail_version;
+
+ /*
+ * Release date.
+ */
+#define VAR_MAIL_RELEASE "mail_release_date"
+#define DEF_MAIL_RELEASE MAIL_RELEASE_DATE
+extern char *var_mail_release;
+
+ /*
+ * The following macros stamp executable files as well as core dumps. This
+ * information helps to answer the following questions:
+ *
+ * - What Postfix versions(s) are installed on this machine?
+ *
+ * - Is this installation mixing multiple Postfix versions?
+ *
+ * - What Postfix version generated this core dump?
+ *
+ * To find out: strings -f file... | grep mail_version=
+ */
+#include <string.h>
+
+#define MAIL_VERSION_STAMP_DECLARE \
+ char *mail_version_stamp
+
+#define MAIL_VERSION_STAMP_ALLOCATE \
+ mail_version_stamp = strdup(VAR_MAIL_VERSION "=" DEF_MAIL_VERSION)
+
+ /*
+ * Mail version string parser, plus support to compare the compile-time
+ * version string of a Postfix program with the run-time version string of a
+ * Postfix shared library. When programs are not updated, they may fail in
+ * erratic ways when linked against a newer run-time library. Of course the
+ * right solution is so-number versioning of the Postfix run-time library.
+ */
+typedef struct {
+ char *program; /* postfix */
+ int major; /* 2 */
+ int minor; /* 9 */
+ int patch; /* null */
+ char *snapshot; /* 20111209-nonprod */
+} MAIL_VERSION;
+
+extern MAIL_VERSION *mail_version_parse(const char *, const char **);
+extern void mail_version_free(MAIL_VERSION *);
+extern const char *get_mail_version(void);
+extern void check_mail_version(const char *);
+
+#define MAIL_VERSION_CHECK \
+ check_mail_version(DEF_MAIL_VERSION)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mail_version.in b/src/global/mail_version.in
new file mode 100644
index 0000000..1cf75d8
--- /dev/null
+++ b/src/global/mail_version.in
@@ -0,0 +1,8 @@
+1
+1-2
+1-2.3
+1-2.3.4.5
+1-2.3.4-5
+1-2.3-5
+1-2.3-5-6
+1-2.3-5.6
diff --git a/src/global/mail_version.ref b/src/global/mail_version.ref
new file mode 100644
index 0000000..5dbc3ab
--- /dev/null
+++ b/src/global/mail_version.ref
@@ -0,0 +1,16 @@
+> 1
+ERROR: 1: missing major version
+> 1-2
+ERROR: 1-2: missing minor version
+> 1-2.3
+ERROR: 1-2.3: missing snapshot field
+> 1-2.3.4.5
+ERROR: 1-2.3.4.5: bad patchlevel
+> 1-2.3.4-5
+program: 1 major: 2 minor: 3 patch: 4
+> 1-2.3-5
+program: 1 major: 2 minor: 3 snapshot: 5
+> 1-2.3-5-6
+program: 1 major: 2 minor: 3 snapshot: 5-6
+> 1-2.3-5.6
+program: 1 major: 2 minor: 3 snapshot: 5.6
diff --git a/src/global/maillog_client.c b/src/global/maillog_client.c
new file mode 100644
index 0000000..7f79a1f
--- /dev/null
+++ b/src/global/maillog_client.c
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/* maillog_client 3
+/* SUMMARY
+/* choose between syslog client and postlog client
+/* SYNOPSIS
+/* #include <maillog_client.h>
+/*
+/* int maillog_client_init(
+/* const char *progname,
+/* int flags)
+/* DESCRIPTION
+/* maillog_client_init() chooses between logging to the syslog
+/* service or to the internal postlog service.
+/*
+/* maillog_client_init() may be called before configuration
+/* parameters are initialized. During this time, logging is
+/* controlled by the presence or absence of POSTLOG_SERVICE
+/* in the process environment (this is ignored if a program
+/* runs with set-uid or set-gid permissions).
+/*
+/* maillog_client_init() may also be called after configuration
+/* parameters are initialized. During this time, logging is
+/* controlled by the "maillog_file" parameter value.
+/*
+/* Arguments:
+/* .IP progname
+/* The program name that will be prepended to logfile records.
+/* .IP flags
+/* Specify one of the following:
+/* .RS
+/* .IP MAILLOG_CLIENT_FLAG_NONE
+/* No special processing.
+/* .IP MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK
+/* Try to fall back to writing the "maillog_file" directly,
+/* if logging to the internal postlog service is enabled, but
+/* the postlog service is unavailable. If the fallback fails,
+/* die with a fatal error.
+/* .RE
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* When logging to the internal postlog service is enabled,
+/* each process exports the following information, to help
+/* initialize the logging in a child process, before the child
+/* has initialized its configuration parameters.
+/* .IP POSTLOG_SERVICE
+/* The pathname of the public postlog service endpoint, usually
+/* "$queue_directory/public/$postlog_service_name".
+/* .IP POSTLOG_HOSTNAME
+/* The hostname to prepend to information that is sent to the
+/* internal postlog logging service, usually "$myhostname".
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* .IP "maillog_file (empty)"
+/* The name of an optional logfile. If the value is empty, or
+/* unitialized and the process environment does not specify
+/* POSTLOG_SERVICE, the program will log to the syslog service
+/* instead.
+/* .IP "myhostname (default: see postconf -d output)"
+/* The internet hostname of this mail system.
+/* .IP "postlog_service_name (postlog)"
+/* The name of the internal postlog logging service.
+/* SEE ALSO
+/* msg_syslog(3) syslog client
+/* msg_logger(3) internal logger
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <logwriter.h>
+#include <msg_logger.h>
+#include <msg_syslog.h>
+#include <safe.h>
+#include <stringops.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <maillog_client.h>
+#include <msg.h>
+
+ /*
+ * Using logging to debug logging is painful.
+ */
+#define MAILLOG_CLIENT_DEBUG 0
+
+ /*
+ * Application-specific.
+ */
+static int maillog_client_flags;
+
+#define POSTLOG_SERVICE_ENV "POSTLOG_SERVICE"
+#define POSTLOG_HOSTNAME_ENV "POSTLOG_HOSTNAME"
+
+/* maillog_client_logwriter_fallback - fall back to logfile writer or bust */
+
+static void maillog_client_logwriter_fallback(const char *text)
+{
+ static int fallback_guard = 0;
+
+ /*
+ * Guard against recursive calls.
+ *
+ * If an error happened before the maillog_file parameter was initialized,
+ * or if maillog_file logging is disabled, then we cannot fall back to a
+ * logfile. All we can do is to hope that stderr logging will bring out
+ * the bad news.
+ */
+ if (fallback_guard == 0 && var_maillog_file && *var_maillog_file
+ && logwriter_one_shot(var_maillog_file, text, strlen(text)) < 0) {
+ fallback_guard = 1;
+ msg_fatal("logfile '%s' write error: %m", var_maillog_file);
+ }
+}
+
+/* maillog_client_init - set up syslog or internal log client */
+
+void maillog_client_init(const char *progname, int flags)
+{
+ char *import_service_path;
+ char *import_hostname;
+
+ /*
+ * Crucially, only one logger mode can be in effect at any time,
+ * otherwise postlogd(8) may go into a loop.
+ */
+ enum {
+ MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG,
+ } logger_mode;
+
+ /*
+ * Security: this code may run before the import_environment setting has
+ * taken effect. It has to guard against privilege escalation attacks on
+ * setgid programs, using malicious environment settings.
+ *
+ * Import the postlog service name and hostname from the environment.
+ *
+ * - These will be used and kept if the process has not yet initialized its
+ * configuration parameters.
+ *
+ * - These will be set or updated if the configuration enables postlog
+ * logging.
+ *
+ * - These will be removed if the configuration does not enable postlog
+ * logging.
+ */
+ if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0
+ && *import_service_path == 0)
+ import_service_path = 0;
+ if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0
+ && *import_hostname == 0)
+ import_hostname = 0;
+
+#if MAILLOG_CLIENT_DEBUG
+#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
+ msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
+ msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path));
+ msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname));
+#endif
+
+ /*
+ * Before configuration parameters are initialized, the logging mode is
+ * controlled by the presence or absence of POSTLOG_SERVICE in the
+ * process environment. After configuration parameters are initialized,
+ * the logging mode is controlled by the "maillog_file" parameter value.
+ *
+ * The configured mode may change after a process is started. The
+ * postlogd(8) server will proxy logging to syslogd where needed.
+ */
+ if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) {
+ logger_mode = MAILLOG_CLIENT_MODE_SYSLOG;
+ } else {
+ /* var_maillog_file ? *var_maillog_file : import_service_path != 0 */
+ logger_mode = MAILLOG_CLIENT_MODE_POSTLOG;
+ }
+
+ /*
+ * Postlog logging is enabled. Update the 'progname' as that may have
+ * changed since an earlier call, and update the environment settings if
+ * they differ from configuration settings. This blends two code paths,
+ * one code path where configuration parameters are initialized (the
+ * preferred path), and one code path that uses imports from environment.
+ */
+ if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) {
+ char *myhostname;
+ char *service_path;
+
+ if (var_maillog_file && *var_maillog_file) {
+ ARGV *good_prefixes = argv_split(var_maillog_file_pfxs,
+ CHARS_COMMA_SP);
+ char **cpp;
+
+ for (cpp = good_prefixes->argv; /* see below */ ; cpp++) {
+ if (*cpp == 0)
+ msg_fatal("%s value '%s' does not match any prefix in %s",
+ VAR_MAILLOG_FILE, var_maillog_file,
+ VAR_MAILLOG_FILE_PFXS);
+ if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0)
+ break;
+ }
+ argv_free(good_prefixes);
+ }
+ if (var_myhostname && *var_myhostname) {
+ myhostname = var_myhostname;
+ } else if ((myhostname = import_hostname) == 0) {
+ myhostname = "amnesiac";
+ }
+#if MAILLOG_CLIENT_DEBUG
+ msg_info("myhostname=%s", STRING_OR_NULL(myhostname));
+#endif
+ if (var_postlog_service) {
+ service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC,
+ "/", var_postlog_service, (char *) 0);
+ } else {
+
+ /*
+ * var_postlog_service == 0, therefore var_maillog_file == 0.
+ * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file ==
+ * 0, therefore import_service_path != 0.
+ */
+ service_path = import_service_path;
+ }
+ maillog_client_flags = flags;
+ msg_logger_init(progname, myhostname, service_path,
+ (flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ?
+ maillog_client_logwriter_fallback :
+ (MSG_LOGGER_FALLBACK_FN) 0);
+
+ /*
+ * Export or update the exported postlog service pathname and the
+ * hostname, so that a child process can bootstrap postlog logging
+ * before it has processed main.cf and command-line options.
+ */
+ if (import_service_path == 0
+ || strcmp(service_path, import_service_path) != 0) {
+#if MAILLOG_CLIENT_DEBUG
+ msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path);
+#endif
+ if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0)
+ msg_fatal("setenv: %m");
+ }
+ if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) {
+#if MAILLOG_CLIENT_DEBUG
+ msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname);
+#endif
+ if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0)
+ msg_fatal("setenv: %m");
+ }
+ if (service_path != import_service_path)
+ myfree(service_path);
+ msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW,
+ CA_MSG_LOGGER_CTL_END);
+ }
+
+ /*
+ * Postlog logging is disabled. Silence the msg_logger client, and remove
+ * the environment settings that bootstrap postlog logging in a child
+ * process.
+ */
+ else {
+ msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END);
+ if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV))
+ || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV)))
+ msg_fatal("unsetenv: %m");
+ }
+
+ /*
+ * Syslog logging is enabled. Update the 'progname' as that may have
+ * changed since an earlier call.
+ */
+ if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) {
+ msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
+ }
+
+ /*
+ * Syslog logging is disabled, silence the syslog client.
+ */
+ else {
+ msg_syslog_disable();
+ }
+}
diff --git a/src/global/maillog_client.h b/src/global/maillog_client.h
new file mode 100644
index 0000000..7160187
--- /dev/null
+++ b/src/global/maillog_client.h
@@ -0,0 +1,33 @@
+#ifndef _MAILLOG_CLIENT_H_INCLUDED_
+#define _MAILLOG_CLIENT_H_INCLUDED_
+
+/*++
+/* NAME
+/* maillog_client 3h
+/* SUMMARY
+/* choose between syslog client and postlog client
+/* SYNOPSIS
+/* #include <maillog_client.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#define MAILLOG_CLIENT_FLAG_NONE (0)
+#define MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK (1<<0)
+
+extern void maillog_client_init(const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/map_search.c b/src/global/map_search.c
new file mode 100644
index 0000000..b10f7d5
--- /dev/null
+++ b/src/global/map_search.c
@@ -0,0 +1,397 @@
+/*++
+/* NAME
+/* map_search_search 3
+/* SUMMARY
+/* lookup table search list support
+/* SYNOPSIS
+/* #include <map_search_search.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *map_type_name; /* type:name, owned */
+/* char *search_order; /* null or owned */
+/* .in -4
+/* } MAP_SEARCH;
+/*
+/* void map_search_init(
+/* const NAME_CODE *search_actions)
+/*
+/* const MAP_SEARCH *map_search_create(
+/* const char *map_spec)
+/*
+/* const MAP_SEARCH *map_search_lookup(
+/* const char *map_spec);
+/* DESCRIPTION
+/* This module implements configurable search order support
+/* for Postfix lookup tables.
+/*
+/* map_search_init() must be called once, before other functions
+/* in this module.
+/*
+/* map_search_create() creates a MAP_SEARCH instance for
+/* map_spec, ignoring duplicate requests.
+/*
+/* map_search_lookup() looks up the MAP_SEARCH instance that
+/* was created by map_search_create().
+/*
+/* Arguments:
+/* .IP search_actions
+/* The mapping from search action string form to numeric form.
+/* The numbers must be in the range [1..126] (inclusive). The
+/* value 0 is reserved for the MAP_SEARCH.search_order terminator,
+/* and the value MAP_SEARCH_CODE_UNKNOWN is reserved for the
+/* 'not found' result. The argument is copied (the pointer
+/* value, not the table).
+/* .IP map_spec
+/* lookup table and optional search order: either maptype:mapname,
+/* or { maptype:mapname, { search = name, name }}. The search
+/* attribute is optional. The comma is equivalent to whitespace.
+/* DIAGNOSTICS
+/* map_search_create() returns a null pointer when a map_spec
+/* is a) malformed, b) specifies an unexpected attribute name,
+/* c) the search attribute contains an unknown name. Thus,
+/* map_search_create() will never return a search_order that
+/* contains the value MAP_SEARCH_CODE_UNKNOWN.
+/*
+/* Panic: interface violations. Fatal errors: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <map_search.h>
+
+ /*
+ * Application-specific.
+ */
+static HTABLE *map_search_table;
+static const NAME_CODE *map_search_actions;
+
+#define STR(x) vstring_str(x)
+
+/* map_search_init - one-time initialization */
+
+void map_search_init(const NAME_CODE *search_actions)
+{
+ if (map_search_table != 0 || map_search_actions != 0)
+ msg_panic("map_search_init: multiple calls");
+ map_search_table = htable_create(100);
+ map_search_actions = search_actions;
+}
+
+/* map_search_create - store MAP_SEARCH instance */
+
+const MAP_SEARCH *map_search_create(const char *map_spec)
+{
+ char *copy_of_map_spec = 0;
+ char *bp = 0;
+ const char *const_err;
+ char *heap_err = 0;
+ VSTRING *search_order = 0;
+ const char *map_type_name;
+ char *attr_name_val = 0;
+ char *attr_name = 0;
+ char *attr_value = 0;
+ MAP_SEARCH *map_search;
+ char *atom;
+ int code;
+
+ /*
+ * Sanity check.
+ */
+ if (map_search_table == 0 || map_search_actions == 0)
+ msg_panic("map_search_create: missing initialization");
+
+ /*
+ * Allow exact duplicates. This won't catch duplicates that differ only
+ * in their use of whitespace or comma.
+ */
+ if ((map_search =
+ (MAP_SEARCH *) htable_find(map_search_table, map_spec)) != 0)
+ return (map_search);
+
+ /*
+ * Macro for readability and safety. Let the compiler worry about code
+ * duplication and redundant conditions.
+ */
+#define MAP_SEARCH_CREATE_RETURN(x) do { \
+ if (copy_of_map_spec) myfree(copy_of_map_spec); \
+ if (heap_err) myfree(heap_err); \
+ if (search_order) vstring_free(search_order); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Long form specifies maptype_mapname and optional search attribute.
+ */
+ if (*map_spec == CHARS_BRACE[0]) {
+ bp = copy_of_map_spec = mystrdup(map_spec);
+ if ((heap_err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
+ msg_warn("malformed map specification: '%s'", heap_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ } else if ((map_type_name = mystrtokq(&bp, CHARS_COMMA_SP,
+ CHARS_BRACE)) == 0) {
+ msg_warn("empty map specification: '%s'", map_spec);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ } else {
+ map_type_name = map_spec;
+ }
+
+ /*
+ * Sanity check the map spec before parsing attributes.
+ */
+ if (strchr(map_type_name, ':') == 0) {
+ msg_warn("malformed map specification: '%s'", map_spec);
+ msg_warn("expected maptype:mapname instead of '%s'", map_type_name);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+
+ /*
+ * Parse the attribute list. XXX This does not detect multiple attributes
+ * with the same attribute name.
+ */
+ if (bp != 0) {
+ while ((attr_name_val = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (*attr_name_val == CHARS_BRACE[0]) {
+ if ((heap_err = extpar(&attr_name_val, CHARS_BRACE,
+ EXTPAR_FLAG_STRIP)) != 0) {
+ msg_warn("malformed map attribute: %s", heap_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ }
+ if ((const_err = split_nameval(attr_name_val, &attr_name,
+ &attr_value)) != 0) {
+ msg_warn("malformed map attribute in '%s': '%s'",
+ map_spec, const_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ if (strcasecmp(attr_name, MAP_SEARCH_ATTR_NAME_SEARCH) != 0) {
+ msg_warn("unknown map attribute in '%s': '%s'",
+ map_spec, attr_name);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ }
+ }
+
+ /*
+ * Parse the search list if any.
+ */
+ if (attr_name != 0) {
+ search_order = vstring_alloc(10);
+ while ((atom = mystrtok(&attr_value, CHARS_COMMA_SP)) != 0) {
+ if ((code = name_code(map_search_actions, NAME_CODE_FLAG_NONE,
+ atom)) == MAP_SEARCH_CODE_UNKNOWN) {
+ msg_warn("unknown search type '%s' in '%s'", atom, map_spec);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ VSTRING_ADDCH(search_order, code);
+ }
+ VSTRING_TERMINATE(search_order);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ map_search = (MAP_SEARCH *) mymalloc(sizeof(*map_search));
+ map_search->map_type_name = mystrdup(map_type_name);
+ if (search_order) {
+ map_search->search_order = vstring_export(search_order);
+ search_order = 0;
+ } else {
+ map_search->search_order = 0;
+ }
+
+ /*
+ * Save the ACL to cache.
+ */
+ (void) htable_enter(map_search_table, map_spec, map_search);
+
+ MAP_SEARCH_CREATE_RETURN(map_search);
+}
+
+/* map_search_lookup - lookup MAP_SEARCH instance */
+
+const MAP_SEARCH *map_search_lookup(const char *map_spec)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (map_search_table == 0 || map_search_actions == 0)
+ msg_panic("map_search_lookup: missing initialization");
+
+ return ((MAP_SEARCH *) htable_find(map_search_table, map_spec));
+}
+
+ /*
+ * Test driver.
+ */
+#ifdef TEST
+#include <stdlib.h>
+
+ /*
+ * Test search actions.
+ */
+#define TEST_NAME_1 "one"
+#define TEST_NAME_2 "two"
+#define TEST_CODE_1 1
+#define TEST_CODE_2 2
+
+#define BAD_NAME "bad"
+
+static const NAME_CODE search_actions[] = {
+ TEST_NAME_1, TEST_CODE_1,
+ TEST_NAME_2, TEST_CODE_2,
+ 0, MAP_SEARCH_CODE_UNKNOWN,
+};
+
+/* Helpers to simplify tests. */
+
+static const char *string_or_null(const char *s)
+{
+ return (s ? s : "(null)");
+}
+
+static char *escape_order(VSTRING *buf, const char *search_order)
+{
+ return (STR(escape(buf, search_order, strlen(search_order))));
+}
+
+int main(int argc, char **argv)
+{
+ /* Test cases with inputs and expected outputs. */
+ typedef struct TEST_CASE {
+ const char *map_spec;
+ int exp_return; /* 0=fail, 1=success */
+ const char *exp_map_type_name; /* 0 or match */
+ const char *exp_search_order; /* 0 or match */
+ } TEST_CASE;
+ static TEST_CASE test_cases[] = {
+ {"type", 0, 0, 0},
+ {"type:name", 1, "type:name", 0},
+ {"{type:name}", 1, "type:name", 0},
+ {"{type:name", 0, 0, 0}, /* } */
+ {"{type}", 0, 0, 0},
+ {"{type:name foo}", 0, 0, 0},
+ {"{type:name foo=bar}", 0, 0, 0},
+ {"{type:name search_order=}", 1, "type:name", ""},
+ {"{type:name search_order=one, two}", 0, 0, 0},
+ {"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"},
+ {"{type:name {search_order=one, two, bad}}", 0, 0, 0},
+ {"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"},
+ {"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"},
+ {0},
+ };
+ TEST_CASE *test_case;
+
+ /* Actual results. */
+ const MAP_SEARCH *map_search_from_create;
+ const MAP_SEARCH *map_search_from_create_2nd;
+ const MAP_SEARCH *map_search_from_lookup;
+
+ /* Findings. */
+ int tests_failed = 0;
+ int test_failed;
+
+ /* Scratch */
+ VSTRING *expect_escaped = vstring_alloc(100);
+ VSTRING *actual_escaped = vstring_alloc(100);
+
+ map_search_init(search_actions);
+
+ for (tests_failed = 0, test_case = test_cases; test_case->map_spec;
+ tests_failed += test_failed, test_case++) {
+ test_failed = 0;
+ msg_info("test case %d: '%s'",
+ (int) (test_case - test_cases), test_case->map_spec);
+ map_search_from_create = map_search_create(test_case->map_spec);
+ if (!test_case->exp_return != !map_search_from_create) {
+ if (map_search_from_create)
+ msg_warn("test case %d return expected %s actual {%s, %s}",
+ (int) (test_case - test_cases),
+ test_case->exp_return ? "success" : "fail",
+ map_search_from_create->map_type_name,
+ escape_order(actual_escaped,
+ map_search_from_create->search_order));
+ else
+ msg_warn("test case %d return expected %s actual %s",
+ (int) (test_case - test_cases), "success",
+ map_search_from_create ? "success" : "fail");
+ test_failed = 1;
+ continue;
+ }
+ if (test_case->exp_return == 0)
+ continue;
+ map_search_from_lookup = map_search_lookup(test_case->map_spec);
+ if (map_search_from_create != map_search_from_lookup) {
+ msg_warn("test case %d map_search_lookup expected=%p actual=%p",
+ (int) (test_case - test_cases),
+ map_search_from_create, map_search_from_lookup);
+ test_failed = 1;
+ }
+ map_search_from_create_2nd = map_search_create(test_case->map_spec);
+ if (map_search_from_create != map_search_from_create_2nd) {
+ msg_warn("test case %d repeated map_search_create "
+ "expected=%p actual=%p",
+ (int) (test_case - test_cases),
+ map_search_from_create, map_search_from_create_2nd);
+ test_failed = 1;
+ }
+ if (strcmp(string_or_null(test_case->exp_map_type_name),
+ string_or_null(map_search_from_create->map_type_name))) {
+ msg_warn("test case %d map_type_name expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ string_or_null(test_case->exp_map_type_name),
+ string_or_null(map_search_from_create->map_type_name));
+ test_failed = 1;
+ }
+ if (strcmp(string_or_null(test_case->exp_search_order),
+ string_or_null(map_search_from_create->search_order))) {
+ msg_warn("test case %d search_order expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ escape_order(expect_escaped,
+ string_or_null(test_case->exp_search_order)),
+ escape_order(actual_escaped,
+ string_or_null(map_search_from_create->search_order)));
+ test_failed = 1;
+ }
+ }
+ vstring_free(expect_escaped);
+ vstring_free(actual_escaped);
+
+ if (tests_failed)
+ msg_info("tests failed: %d", tests_failed);
+ exit(tests_failed != 0);
+}
+
+#endif
diff --git a/src/global/map_search.h b/src/global/map_search.h
new file mode 100644
index 0000000..851ab1c
--- /dev/null
+++ b/src/global/map_search.h
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* map_search 3h
+/* SUMMARY
+/* lookup table search list support
+/* SYNOPSIS
+/* #include <map_search.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+
+ /*
+ * External interface.
+ *
+ * The map_search module maintains one lookup table with MAP_SEARCH results,
+ * indexed by the unparsed form of a map specification. The conversion from
+ * unparsed form to MAP_SEARCH result is controlled by a NAME_CODE mapping,
+ * Since one lookup table can support only one mapping per unparsed name,
+ * every MAP_SEARCH result in the lookup table must be built using the same
+ * NAME_CODE table.
+ *
+ * Alternative 1: no lookup table. Allow the user to specify the NAME_CODE
+ * mapping in the map_search_create() request (in addition to the unparsed
+ * form), and let the MAP_SEARCH user store each MAP_SEARCH pointer. But
+ * that would clumsify code that wants to use MAP_SEARCH functionality.
+ *
+ * Alternative 2: one lookup table per NAME_CODE mapping. Change
+ * map_search_init() to return a pointer to {HTABLE *, NAME_CODE *}, and
+ * require that the MAP_SEARCH user pass that pointer to other
+ * map_search_xxx() calls (in addition to the unparsed forms). That would be
+ * about as clumsy as Alternative 1.
+ *
+ * Alternative 3: one lookup table, distinct lookup keys per NAME_CODE table
+ * and map_spec. The caller specifies both the map_spec and the NAME_CODE
+ * mapping when it calls map_seach_create() and map_search_find(). The
+ * implementation securely prepends the name_code argument to the map_spec
+ * argument and uses the result as the table lookup key.
+ *
+ * Alternative 1 is not suitable for the smtpd_mumble_restrictions parser,
+ * which instantiates MAP_SEARCH instances without knowing which specific
+ * access feature is involved. It uses a NAME_CODE mapping that contains the
+ * superset of what all smtpd_mumble_restrictions features need. The
+ * downside is delayed error notification.
+ */
+typedef struct {
+ char *map_type_name; /* "type:name", owned */
+ char *search_order; /* null or owned */
+} MAP_SEARCH;
+
+extern void map_search_init(const NAME_CODE *);
+extern const MAP_SEARCH *map_search_create(const char *);
+extern const MAP_SEARCH *map_search_lookup(const char *);
+
+#define MAP_SEARCH_ATTR_NAME_SEARCH "search_order"
+
+#define MAP_SEARCH_CODE_UNKNOWN 127
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/global/map_search.ref b/src/global/map_search.ref
new file mode 100644
index 0000000..bf8184b
--- /dev/null
+++ b/src/global/map_search.ref
@@ -0,0 +1,29 @@
+unknown: test case 0: 'type'
+unknown: warning: malformed map specification: 'type'
+unknown: warning: expected maptype:mapname instead of 'type'
+unknown: test case 1: 'type:name'
+unknown: test case 2: '{type:name}'
+unknown: test case 3: '{type:name'
+unknown: warning: malformed map specification: 'missing '}' in "{type:name"'
+unknown: test case 4: '{type}'
+unknown: warning: malformed map specification: '{type}'
+unknown: warning: expected maptype:mapname instead of 'type'
+unknown: test case 5: '{type:name foo}'
+unknown: split_nameval("foo"
+unknown: warning: malformed map attribute in '{type:name foo}': 'missing '=' after attribute name'
+unknown: test case 6: '{type:name foo=bar}'
+unknown: split_nameval("foo=bar"
+unknown: warning: unknown map attribute in '{type:name foo=bar}': 'foo'
+unknown: test case 7: '{type:name search_order=}'
+unknown: split_nameval("search_order="
+unknown: test case 8: '{type:name search_order=one, two}'
+unknown: split_nameval("search_order=one"
+unknown: split_nameval("two"
+unknown: warning: malformed map attribute in '{type:name search_order=one, two}': 'missing '=' after attribute name'
+unknown: test case 9: '{type:name {search_order=one, two}}'
+unknown: split_nameval("search_order=one, two"
+unknown: test case 10: '{type:name {search_order=one, two, bad}}'
+unknown: split_nameval("search_order=one, two, bad"
+unknown: warning: unknown search type 'bad' in '{type:name {search_order=one, two, bad}}'
+unknown: test case 11: '{inline:{a=b} {search_order=one, two}}'
+unknown: split_nameval("search_order=one, two"
diff --git a/src/global/maps.c b/src/global/maps.c
new file mode 100644
index 0000000..7c84e9a
--- /dev/null
+++ b/src/global/maps.c
@@ -0,0 +1,337 @@
+/*++
+/* NAME
+/* maps 3
+/* SUMMARY
+/* multi-dictionary search
+/* SYNOPSIS
+/* #include <maps.h>
+/*
+/* MAPS *maps_create(title, map_names, flags)
+/* const char *title;
+/* const char *map_names;
+/* int flags;
+/*
+/* const char *maps_find(maps, key, flags)
+/* MAPS *maps;
+/* const char *key;
+/* int flags;
+/*
+/* const char *maps_file_find(maps, key, flags)
+/* MAPS *maps;
+/* const char *key;
+/* int flags;
+/*
+/* MAPS *maps_free(maps)
+/* MAPS *maps;
+/* DESCRIPTION
+/* This module implements multi-dictionary searches. it goes
+/* through the high-level dictionary interface and does file
+/* locking. Dictionaries are opened read-only, and in-memory
+/* dictionary instances are shared.
+/*
+/* maps_create() takes list of type:name pairs and opens the
+/* named dictionaries.
+/* The result is a handle that must be specified along with all
+/* other maps_xxx() operations.
+/* See dict_open(3) for a description of flags.
+/* This includes the flags that specify preferences for search
+/* string case folding.
+/*
+/* maps_find() searches the specified list of dictionaries
+/* in the specified order for the named key. The result is in
+/* memory that is overwritten upon each call.
+/* The flags argument is either 0 or specifies a filter:
+/* for example, DICT_FLAG_FIXED | DICT_FLAG_PATTERN selects
+/* dictionaries that have fixed keys or pattern keys.
+/*
+/* maps_file_find() implements maps_find() but also decodes
+/* the base64 lookup result. This requires that the maps are
+/* opened with DICT_FLAG_SRC_RHS_IS_FILE.
+/*
+/* maps_free() releases storage claimed by maps_create()
+/* and conveniently returns a null pointer.
+/*
+/* Arguments:
+/* .IP title
+/* String used for diagnostics. Typically one specifies the
+/* type of information stored in the lookup tables.
+/* .IP map_names
+/* Null-terminated string with type:name dictionary specifications,
+/* separated by whitespace or commas.
+/* .IP flags
+/* With maps_create(), flags that are passed to dict_open().
+/* With maps_find(), flags that control searching behavior
+/* as documented above.
+/* .IP maps
+/* A result from maps_create().
+/* .IP key
+/* Null-terminated string with a lookup key. Table lookup is case
+/* sensitive.
+/* DIAGNOSTICS
+/* Panic: inappropriate use; fatal errors: out of memory, unable
+/* to open database. Warnings: null string lookup result.
+/*
+/* maps_find() returns a null pointer when the requested
+/* information was not found, and logs a warning when the
+/* lookup failed due to error. The maps->error value indicates
+/* if the last lookup failed due to error.
+/* BUGS
+/* The dictionary name space is flat, so dictionary names allocated
+/* by maps_create() may collide with dictionary names allocated by
+/* other methods.
+/*
+/* This functionality could be implemented by allowing the user to
+/* specify dictionary search paths to dict_lookup() or dict_eval().
+/* However, that would either require that the dict(3) module adopts
+/* someone else's list notation syntax, or that the dict(3) module
+/* imposes syntax restrictions onto other software, neither of which
+/* is desirable.
+/* 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 <argv.h>
+#include <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+#include <stringops.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include "mail_conf.h"
+#include "maps.h"
+
+/* maps_create - initialize */
+
+MAPS *maps_create(const char *title, const char *map_names, int dict_flags)
+{
+ const char *myname = "maps_create";
+ char *temp;
+ char *bufp;
+ static char sep[] = CHARS_COMMA_SP;
+ static char parens[] = CHARS_BRACE;
+ MAPS *maps;
+ char *map_type_name;
+ VSTRING *map_type_name_flags;
+ DICT *dict;
+
+ /*
+ * Initialize.
+ */
+ maps = (MAPS *) mymalloc(sizeof(*maps));
+ maps->title = mystrdup(title);
+ maps->argv = argv_alloc(2);
+ maps->error = 0;
+
+ /*
+ * For each specified type:name pair, either register a new dictionary,
+ * or increment the reference count of an existing one.
+ */
+ if (*map_names) {
+ bufp = temp = mystrdup(map_names);
+ map_type_name_flags = vstring_alloc(10);
+
+#define OPEN_FLAGS O_RDONLY
+
+ while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
+ vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
+ map_type_name, OPEN_FLAGS,
+ dict_flags_str(dict_flags));
+ if ((dict = dict_handle(vstring_str(map_type_name_flags))) == 0)
+ dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
+ if ((dict->flags & dict_flags) != dict_flags)
+ msg_panic("%s: map %s has flags 0%o, want flags 0%o",
+ myname, map_type_name, dict->flags, dict_flags);
+ dict_register(vstring_str(map_type_name_flags), dict);
+ argv_add(maps->argv, vstring_str(map_type_name_flags), ARGV_END);
+ }
+ myfree(temp);
+ vstring_free(map_type_name_flags);
+ }
+ return (maps);
+}
+
+/* maps_find - search a list of dictionaries */
+
+const char *maps_find(MAPS *maps, const char *name, int flags)
+{
+ const char *myname = "maps_find";
+ char **map_name;
+ const char *expansion;
+ DICT *dict;
+
+ /*
+ * In case of return without map lookup (empty name or no maps).
+ */
+ maps->error = 0;
+
+ /*
+ * Temp. workaround, for buggy callers that pass zero-length keys when
+ * given partial addresses.
+ */
+ if (*name == 0)
+ return (0);
+
+ for (map_name = maps->argv->argv; *map_name; map_name++) {
+ if ((dict = dict_handle(*map_name)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *map_name);
+ if (flags != 0 && (dict->flags & flags) == 0)
+ continue;
+ if ((expansion = dict_get(dict, name)) != 0) {
+ if (*expansion == 0) {
+ msg_warn("%s lookup of %s returns an empty string result",
+ maps->title, name);
+ msg_warn("%s should return NO RESULT in case of NOT FOUND",
+ maps->title);
+ maps->error = DICT_ERR_CONFIG;
+ return (0);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
+ *map_name, name, expansion,
+ strlen(expansion) > 100 ? "..." : "");
+ return (expansion);
+ } else if ((maps->error = dict->error) != 0) {
+ msg_warn("%s:%s lookup error for \"%s\"",
+ dict->type, dict->name, name);
+ break;
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
+ "search aborted" : "not found");
+ return (0);
+}
+
+/* maps_file_find - search a list of dictionaries and base64 decode */
+
+const char *maps_file_find(MAPS *maps, const char *name, int flags)
+{
+ const char *myname = "maps_file_find";
+ char **map_name;
+ const char *expansion;
+ DICT *dict;
+ VSTRING *unb64;
+ char *err;
+
+ /*
+ * In case of return without map lookup (empty name or no maps).
+ */
+ maps->error = 0;
+
+ /*
+ * Temp. workaround, for buggy callers that pass zero-length keys when
+ * given partial addresses.
+ */
+ if (*name == 0)
+ return (0);
+
+ for (map_name = maps->argv->argv; *map_name; map_name++) {
+ if ((dict = dict_handle(*map_name)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *map_name);
+ if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
+ msg_panic("%s: %s: opened without DICT_FLAG_SRC_RHS_IS_FILE",
+ myname, maps->title);
+ if (flags != 0 && (dict->flags & flags) == 0)
+ continue;
+ if ((expansion = dict_get(dict, name)) != 0) {
+ if (*expansion == 0) {
+ msg_warn("%s lookup of %s returns an empty string result",
+ maps->title, name);
+ msg_warn("%s should return NO RESULT in case of NOT FOUND",
+ maps->title);
+ maps->error = DICT_ERR_CONFIG;
+ return (0);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: %s: %s = %.100s%s", myname, maps->title,
+ *map_name, name, expansion,
+ strlen(expansion) > 100 ? "..." : "");
+ if ((unb64 = dict_file_from_b64(dict, expansion)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("table %s:%s: key %s: %s",
+ dict->type, dict->name, name, err);
+ myfree(err);
+ maps->error = DICT_ERR_CONFIG;
+ return (0);
+ }
+ return (vstring_str(unb64));
+ } else if ((maps->error = dict->error) != 0) {
+ msg_warn("%s:%s lookup error for \"%s\"",
+ dict->type, dict->name, name);
+ break;
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
+ "search aborted" : "not found");
+ return (0);
+}
+
+/* maps_free - release storage */
+
+MAPS *maps_free(MAPS *maps)
+{
+ char **map_name;
+
+ for (map_name = maps->argv->argv; *map_name; map_name++) {
+ if (msg_verbose)
+ msg_info("maps_free: %s", *map_name);
+ dict_unregister(*map_name);
+ }
+ myfree(maps->title);
+ argv_free(maps->argv);
+ myfree((void *) maps);
+ return (0);
+}
+
+#ifdef TEST
+
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ MAPS *maps;
+ const char *result;
+
+ if (argc != 2)
+ msg_fatal("usage: %s maps", argv[0]);
+ msg_verbose = 2;
+ maps = maps_create("whatever", argv[1], DICT_FLAG_LOCK);
+
+ while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
+ maps->error = 99;
+ vstream_printf("\"%s\": ", vstring_str(buf));
+ if ((result = maps_find(maps, vstring_str(buf), 0)) != 0) {
+ vstream_printf("%s\n", result);
+ } else if (maps->error != 0) {
+ vstream_printf("lookup error\n");
+ } else {
+ vstream_printf("not found\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ maps_free(maps);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/maps.h b/src/global/maps.h
new file mode 100644
index 0000000..04ee6dc
--- /dev/null
+++ b/src/global/maps.h
@@ -0,0 +1,44 @@
+#ifndef _MAPS_H_INCLUDED_
+#define _MAPS_H_INCLUDED_
+
+/*++
+/* NAME
+/* maps 3h
+/* SUMMARY
+/* multi-dictionary search
+/* SYNOPSIS
+/* #include <maps.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * Dictionary name storage. We're borrowing from the argv(3) module.
+ */
+typedef struct MAPS {
+ char *title;
+ struct ARGV *argv;
+ int error; /* last request only */
+} MAPS;
+
+extern MAPS *maps_create(const char *, const char *, int);
+extern const char *maps_find(MAPS *, const char *, int);
+extern const char *maps_file_find(MAPS *, const char *, int);
+extern MAPS *maps_free(MAPS *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/maps.in b/src/global/maps.in
new file mode 100644
index 0000000..511516e
--- /dev/null
+++ b/src/global/maps.in
@@ -0,0 +1,4 @@
+./maps fail:1maps <<EOF
+
+foobar
+EOF
diff --git a/src/global/maps.ref b/src/global/maps.ref
new file mode 100644
index 0000000..84033c7
--- /dev/null
+++ b/src/global/maps.ref
@@ -0,0 +1,8 @@
+unknown: dict_open: fail:1maps
+unknown: dict_register: fail:1maps(0,lock) 1
+"": not found
+unknown: warning: fail:1maps lookup error for "foobar"
+unknown: maps_find: whatever: foobar: search aborted
+"foobar": lookup error
+unknown: maps_free: fail:1maps(0,lock)
+unknown: dict_unregister: fail:1maps(0,lock) 1
diff --git a/src/global/mark_corrupt.c b/src/global/mark_corrupt.c
new file mode 100644
index 0000000..b0694bb
--- /dev/null
+++ b/src/global/mark_corrupt.c
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/* mark_corrupt 3
+/* SUMMARY
+/* mark queue file as corrupt
+/* SYNOPSIS
+/* #include <mark_corrupt.h>
+/*
+/* char *mark_corrupt(src)
+/* VSTREAM *src;
+/* DESCRIPTION
+/* The \fBmark_corrupt\fR() routine marks the specified open
+/* queue file as corrupt, and returns a suitable delivery status
+/* so that the queue manager will do the right thing.
+/* 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>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <set_eugid.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <deliver_request.h>
+#include <mark_corrupt.h>
+
+/* mark_corrupt - mark queue file as corrupt */
+
+int mark_corrupt(VSTREAM *src)
+{
+ const char *myname = "mark_corrupt";
+ uid_t saved_uid;
+ gid_t saved_gid;
+
+ /*
+ * If not running as the mail system, change privileges first.
+ */
+ if ((saved_uid = geteuid()) != var_owner_uid) {
+ saved_gid = getegid();
+ set_eugid(var_owner_uid, var_owner_gid);
+ }
+
+ /*
+ * For now, the result value is -1; this may become a bit mask, or
+ * something even more advanced than that, when the delivery status
+ * becomes more than just done/deferred.
+ */
+ msg_warn("corrupted queue file: %s", VSTREAM_PATH(src));
+ if (fchmod(vstream_fileno(src), MAIL_QUEUE_STAT_CORRUPT))
+ msg_fatal("%s: fchmod %s: %m", myname, VSTREAM_PATH(src));
+
+ /*
+ * Restore privileges.
+ */
+ if (saved_uid != var_owner_uid)
+ set_eugid(saved_uid, saved_gid);
+
+ return (DEL_STAT_DEFER);
+}
diff --git a/src/global/mark_corrupt.h b/src/global/mark_corrupt.h
new file mode 100644
index 0000000..4fad8b7
--- /dev/null
+++ b/src/global/mark_corrupt.h
@@ -0,0 +1,35 @@
+#ifndef _MARK_CORRUPT_H_INCLUDED_
+#define _MARK_CORRUPT_H_INCLUDED_
+
+/*++
+/* NAME
+/* mark_corrupt 3h
+/* SUMMARY
+/* mark queue file as corrupt
+/* SYNOPSIS
+/* #include <mark_corrupt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern int mark_corrupt(VSTREAM *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/match_parent_style.c b/src/global/match_parent_style.c
new file mode 100644
index 0000000..0ec6b2e
--- /dev/null
+++ b/src/global/match_parent_style.c
@@ -0,0 +1,74 @@
+/*++
+/* NAME
+/* match_parent_style 3
+/* SUMMARY
+/* parent domain matching control
+/* SYNOPSIS
+/* #include <match_parent_style.h>
+/*
+/* int match_parent_style(name)
+/* const char *name;
+/* DESCRIPTION
+/* This module queries configuration parameters for the policy that
+/* controls how wild-card parent domain names are used by various
+/* postfix lookup mechanisms.
+/*
+/* match_parent_style() looks up "name" in the
+/* parent_domain_matches_subdomain configuration parameter
+/* and returns either MATCH_FLAG_PARENT (parent domain matches
+/* subdomains) or MATCH_FLAG_NONE.
+/* DIAGNOSTICS
+/* Fatal error: out of memory, name listed under both parent wild card
+/* matching policies.
+/* SEE ALSO
+/* string_list(3) plain string matching
+/* domain_list(3) match host name patterns
+/* namadr_list(3) match host name/address patterns
+/* 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. */
+
+/* Global library. */
+
+#include <string_list.h>
+#include <mail_params.h>
+#include <match_parent_style.h>
+
+/* Application-specific. */
+
+static STRING_LIST *match_par_dom_list;
+
+int match_parent_style(const char *name)
+{
+ int result;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (match_par_dom_list == 0)
+ match_par_dom_list =
+ string_list_init(VAR_PAR_DOM_MATCH, MATCH_FLAG_NONE,
+ var_par_dom_match);
+
+ /*
+ * Look up the parent domain matching policy.
+ */
+ if (string_list_match(match_par_dom_list, name))
+ result = MATCH_FLAG_PARENT;
+ else
+ result = 0;
+ return (result);
+}
diff --git a/src/global/match_parent_style.h b/src/global/match_parent_style.h
new file mode 100644
index 0000000..59c796e
--- /dev/null
+++ b/src/global/match_parent_style.h
@@ -0,0 +1,35 @@
+#ifndef _MATCH_PARENT_STYLE_H_INCLUDED_
+#define _MATCH_PARENT_STYLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* match_parent_style 3h
+/* SUMMARY
+/* parent domain matching control
+/* SYNOPSIS
+/* #include <match_parent_style.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <match_list.h>
+
+ /*
+ * External interface.
+ */
+extern int match_parent_style(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/match_service.c b/src/global/match_service.c
new file mode 100644
index 0000000..856355e
--- /dev/null
+++ b/src/global/match_service.c
@@ -0,0 +1,176 @@
+/*++
+/* NAME
+/* match_service 3
+/* SUMMARY
+/* simple master.cf service name.type pattern matcher
+/* SYNOPSIS
+/* #include <match_service.h>
+/*
+/* ARGV *match_service_init(pattern_list)
+/* const char *pattern_list;
+/*
+/* ARGV *match_service_init_argv(pattern_list)
+/* char **pattern_list;
+/*
+/* int match_service_match(list, name_type)
+/* ARGV *list;
+/* const char *name_type;
+/*
+/* void match_service_free(list)
+/* ARGV *list;
+/* DESCRIPTION
+/* This module implements pattern matching for Postfix master.cf
+/* services. This is more precise than using domain_list(3),
+/* because match_service(3) won't treat a dotted service name
+/* as a domain hierarchy. Moreover, this module has the advantage
+/* that it does not drag in all the LDAP, SQL and other map
+/* lookup client code into programs that don't need it.
+/*
+/* Each pattern is of the form "name/type" or "type", where
+/* "name" and "type" are the first two fields of a master.cf
+/* entry. Patterns are separated by whitespace and/or commas.
+/* Matches are case insensitive. Patterns are matched in the
+/* specified order, and the matching process stops at the first
+/* match. In order to reverse the result of a pattern match,
+/* precede a pattern with an exclamation point (!).
+/*
+/* For backwards compatibility, the form name.type is still
+/* supported.
+/*
+/* match_service_init() parses the pattern list. The result
+/* must be passed to match_service_match() or match_service_free().
+/*
+/* match_service_init_argv() provides an alternate interface
+/* for pre-parsed strings.
+/*
+/* match_service_match() matches one service name.type string
+/* against the specified pattern list.
+/*
+/* match_service_free() releases storage allocated by
+/* match_service_init().
+/* DIAGNOSTICS
+/* Fatal error: out of memory, malformed pattern.
+/* Panic: malformed search string.
+/* SEE ALSO
+/* domain_list(3) match domain names.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <match_service.h>
+
+/* match_service_compat - backwards compatibility */
+
+static void match_service_compat(ARGV *argv)
+{
+ char **cpp;
+ char *cp;
+
+ for (cpp = argv->argv; *cpp; cpp++) {
+ if (strrchr(*cpp, '/') == 0 && (cp = strrchr(*cpp, '.')) != 0)
+ *cp = '/';
+ }
+}
+
+/* match_service_init - initialize pattern list */
+
+ARGV *match_service_init(const char *patterns)
+{
+ const char *delim = CHARS_COMMA_SP;
+ ARGV *list = argv_alloc(1);
+ char *saved_patterns = mystrdup(patterns);
+ char *bp = saved_patterns;
+ const char *item;
+
+ while ((item = mystrtok(&bp, delim)) != 0)
+ argv_add(list, item, (char *) 0);
+ argv_terminate(list);
+ myfree(saved_patterns);
+ match_service_compat(list);
+ return (list);
+}
+
+/* match_service_init_argv - impedance adapter */
+
+ARGV *match_service_init_argv(char **patterns)
+{
+ ARGV *list = argv_alloc(1);
+ char **cpp;
+
+ for (cpp = patterns; *cpp; cpp++)
+ argv_add(list, *cpp, (char *) 0);
+ argv_terminate(list);
+ match_service_compat(list);
+ return (list);
+}
+
+/* match_service_match - match service name.type against pattern list */
+
+int match_service_match(ARGV *list, const char *name_type)
+{
+ const char *myname = "match_service_match";
+ const char *type;
+ char **cpp;
+ char *pattern;
+ int match;
+
+ /*
+ * Quick check for empty list.
+ */
+ if (list->argv[0] == 0)
+ return (0);
+
+ /*
+ * Sanity check.
+ */
+ if ((type = strrchr(name_type, '/')) == 0 || *++type == 0)
+ msg_panic("%s: malformed service: \"%s\"; need \"name/type\" format",
+ myname, name_type);
+
+ /*
+ * Iterate over all patterns in the list, stop at the first match.
+ */
+ for (cpp = list->argv; (pattern = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s ~? %s", myname, name_type, pattern);
+ for (match = 1; *pattern == '!'; pattern++)
+ match = !match;
+ if (strcasecmp(strchr(pattern, '/') ? name_type : type, pattern) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: found match", myname, name_type);
+ return (match);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: %s: no match", myname, name_type);
+ return (0);
+}
+
+/* match_service_free - release storage */
+
+void match_service_free(ARGV *list)
+{
+ argv_free(list);
+}
diff --git a/src/global/match_service.h b/src/global/match_service.h
new file mode 100644
index 0000000..28828e1
--- /dev/null
+++ b/src/global/match_service.h
@@ -0,0 +1,32 @@
+#ifndef _MATCH_SERVICE_H_INCLUDED_
+#define _MATCH_SERVICE_H_INCLUDED_
+
+/*++
+/* NAME
+/* match_service 3h
+/* SUMMARY
+/* simple master.cf service name.type pattern matcher
+/* SYNOPSIS
+/* #include <match_service.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern ARGV *match_service_init(const char *);
+extern ARGV *match_service_init_argv(char **);
+extern int match_service_match(ARGV *, const char *);
+extern void match_service_free(ARGV *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mbox_conf.c b/src/global/mbox_conf.c
new file mode 100644
index 0000000..2856d86
--- /dev/null
+++ b/src/global/mbox_conf.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* mbox_conf 3
+/* SUMMARY
+/* mailbox lock configuration
+/* SYNOPSIS
+/* #include <mbox_conf.h>
+/*
+/* int mbox_lock_mask(string)
+/* const char *string;
+/*
+/* ARGV *mbox_lock_names()
+/* DESCRIPTION
+/* The functions in this module translate between external
+/* mailbox locking method names and internal representations.
+/*
+/* mbox_lock_mask() translates a string with locking method names
+/* into a bit mask. Names are separated by comma or whitespace.
+/* The following gives the method names and corresponding bit
+/* mask value:
+/* .IP "flock (MBOX_FLOCK_LOCK)"
+/* Use flock() style lock after opening the file. This is the mailbox
+/* locking method traditionally used on BSD-ish systems (including
+/* Ultrix and SunOS). It is not suitable for remote file systems.
+/* .IP "fcntl (MBOX_FCNTL_LOCK)"
+/* Use fcntl() style lock after opening the file. This is the mailbox
+/* locking method on System-V-ish systems (Solaris, AIX, IRIX, HP-UX).
+/* This method is supposed to work for remote systems, but often
+/* has problems.
+/* .IP "dotlock (MBOX_DOT_LOCK)"
+/* Create a lock file with the name \fIfilename\fB.lock\fR. This
+/* method pre-dates kernel locks. This works with remote file systems,
+/* modulo cache coherency problems.
+/* .PP
+/* mbox_lock_names() returns an array with the names of available
+/* mailbox locking methods. The result should be given to argv_free().
+/* DIAGNOSTICS
+/* Fatal errors: undefined locking method name.
+/* 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 <name_mask.h>
+#include <argv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mbox_conf.h>
+
+ /*
+ * The table with available mailbox locking methods. Some systems have
+ * flock() locks; all POSIX-compatible systems have fcntl() locks. Even
+ * though some systems do not use dotlock files by default (4.4BSD), such
+ * locks can be necessary when accessing mailbox files over NFS.
+ */
+static const NAME_MASK mbox_mask[] = {
+#ifdef HAS_FLOCK_LOCK
+ "flock", MBOX_FLOCK_LOCK,
+#endif
+#ifdef HAS_FCNTL_LOCK
+ "fcntl", MBOX_FCNTL_LOCK,
+#endif
+ "dotlock", MBOX_DOT_LOCK,
+ 0,
+};
+
+/* mbox_lock_mask - translate mailbox lock names to bit mask */
+
+int mbox_lock_mask(const char *string)
+{
+ return (name_mask(VAR_MAILBOX_LOCK, mbox_mask, string));
+}
+
+/* mbox_lock_names - return available mailbox lock method names */
+
+ARGV *mbox_lock_names(void)
+{
+ const NAME_MASK *np;
+ ARGV *argv;
+
+ argv = argv_alloc(2);
+ for (np = mbox_mask; np->name != 0; np++)
+ argv_add(argv, np->name, ARGV_END);
+ argv_terminate(argv);
+ return (argv);
+}
diff --git a/src/global/mbox_conf.h b/src/global/mbox_conf.h
new file mode 100644
index 0000000..46e447b
--- /dev/null
+++ b/src/global/mbox_conf.h
@@ -0,0 +1,41 @@
+#ifndef _MBOX_CONF_H_INCLUDED_
+#define _MBOX_CONF_H_INCLUDED_
+
+/*++
+/* NAME
+/* mbox_conf 3h
+/* SUMMARY
+/* mailbox lock configuration
+/* SYNOPSIS
+/* #include <mbox_conf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+
+ /*
+ * External interface.
+ */
+#define MBOX_FLOCK_LOCK (1<<0)
+#define MBOX_FCNTL_LOCK (1<<1)
+#define MBOX_DOT_LOCK (1<<2)
+#define MBOX_DOT_LOCK_MAY_FAIL (1<<3) /* XXX internal only */
+
+extern int mbox_lock_mask(const char *);
+extern ARGV *mbox_lock_names(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mbox_open.c b/src/global/mbox_open.c
new file mode 100644
index 0000000..b38424a
--- /dev/null
+++ b/src/global/mbox_open.c
@@ -0,0 +1,256 @@
+/*++
+/* NAME
+/* mbox_open 3
+/* SUMMARY
+/* mailbox access
+/* SYNOPSIS
+/* #include <mbox_open.h>
+/*
+/* typedef struct {
+/* .in +4
+/* /* public members... */
+/* VSTREAM *fp;
+/* .in -4
+/* } MBOX;
+/*
+/* MBOX *mbox_open(path, flags, mode, st, user, group, lock_style,
+/* def_dsn, why)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/* struct stat *st;
+/* uid_t user;
+/* gid_t group;
+/* int lock_style;
+/* const char *def_dsn;
+/* DSN_BUF *why;
+/*
+/* void mbox_release(mbox)
+/* MBOX *mbox;
+/*
+/* const char *mbox_dsn(err, def_dsn)
+/* int err;
+/* const char *def_dsn;
+/* DESCRIPTION
+/* This module manages access to UNIX mailbox-style files.
+/*
+/* mbox_open() acquires exclusive access to the named file.
+/* The \fBpath, flags, mode, st, user, group, why\fR arguments
+/* are passed to the \fBsafe_open\fR() routine. Attempts to change
+/* file ownership will succeed only if the process runs with
+/* adequate effective privileges.
+/* The \fBlock_style\fR argument specifies a lock style from
+/* mbox_lock_mask(). Locks are applied to regular files only.
+/* The result is a handle that must be destroyed by mbox_release().
+/* The \fBdef_dsn\fR argument is given to mbox_dsn().
+/*
+/* mbox_release() releases the named mailbox. It is up to the
+/* application to close the stream.
+/*
+/* mbox_dsn() translates an errno value to a mailbox related
+/* enhanced status code.
+/* .IP "EAGAIN, ESTALE"
+/* These result in a 4.2.0 soft error (mailbox problem).
+/* .IP ENOSPC
+/* This results in a 4.3.0 soft error (mail system full).
+/* .IP "EDQUOT, EFBIG"
+/* These result in a 5.2.2 hard error (mailbox full).
+/* .PP
+/* All other errors are assigned the specified default error
+/* code. Typically, one would specify 4.2.0 or 5.2.0.
+/* DIAGNOSTICS
+/* mbox_open() returns a null pointer in case of problems, and
+/* sets errno to EAGAIN if someone else has exclusive access.
+/* Other errors are likely to have a more permanent nature.
+/* 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>
+#include <errno.h>
+
+#ifndef EDQUOT
+#define EDQUOT EFBIG
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <safe_open.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <dot_lockfile.h>
+#include <deliver_flock.h>
+#include <mbox_conf.h>
+#include <mbox_open.h>
+
+/* mbox_open - open mailbox-style file for exclusive access */
+
+MBOX *mbox_open(const char *path, int flags, mode_t mode, struct stat * st,
+ uid_t chown_uid, gid_t chown_gid,
+ int lock_style, const char *def_dsn,
+ DSN_BUF *why)
+{
+ struct stat local_statbuf;
+ MBOX *mp;
+ int locked = 0;
+ VSTREAM *fp;
+
+ if (st == 0)
+ st = &local_statbuf;
+
+ /*
+ * If this is a regular file, create a dotlock file. This locking method
+ * does not work well over NFS, but it is better than some alternatives.
+ * With NFS, creating files atomically is a problem, and a successful
+ * operation can fail with EEXIST.
+ *
+ * If filename.lock can't be created for reasons other than "file exists",
+ * issue only a warning if the application says it is non-fatal. This is
+ * for bass-awkward compatibility with existing installations that
+ * deliver to files in non-writable directories.
+ *
+ * We dot-lock the file before opening, so we must avoid doing silly things
+ * like dot-locking /dev/null. Fortunately, deliveries to non-mailbox
+ * files execute with recipient privileges, so we don't have to worry
+ * about creating dotlock files in places where the recipient would not
+ * be able to write.
+ *
+ * Note: we use stat() to follow symlinks, because safe_open() allows the
+ * target to be a root-owned symlink, and we don't want to create dotlock
+ * files for /dev/null or other non-file objects.
+ */
+ if ((lock_style & MBOX_DOT_LOCK)
+ && (stat(path, st) < 0 || S_ISREG(st->st_mode))) {
+ if (dot_lockfile(path, why->reason) == 0) {
+ locked |= MBOX_DOT_LOCK;
+ } else if (errno == EEXIST) {
+ dsb_status(why, mbox_dsn(EAGAIN, def_dsn));
+ return (0);
+ } else if (lock_style & MBOX_DOT_LOCK_MAY_FAIL) {
+ msg_warn("%s", vstring_str(why->reason));
+ } else {
+ dsb_status(why, mbox_dsn(errno, def_dsn));
+ return (0);
+ }
+ }
+
+ /*
+ * Open or create the target file. In case of a privileged open, the
+ * privileged user may be attacked with hard/soft link tricks in an
+ * unsafe parent directory. In case of an unprivileged open, the mail
+ * system may be attacked by a malicious user-specified path, or the
+ * unprivileged user may be attacked with hard/soft link tricks in an
+ * unsafe parent directory. Open non-blocking to fend off attacks
+ * involving non-file targets.
+ */
+ if ((fp = safe_open(path, flags | O_NONBLOCK, mode, st,
+ chown_uid, chown_gid, why->reason)) == 0) {
+ dsb_status(why, mbox_dsn(errno, def_dsn));
+ if (locked & MBOX_DOT_LOCK)
+ dot_unlockfile(path);
+ return (0);
+ }
+ close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
+
+ /*
+ * If this is a regular file, acquire kernel locks. flock() locks are not
+ * intended to work across a network; fcntl() locks are supposed to work
+ * over NFS, but in the real world, NFS lock daemons often have serious
+ * problems.
+ */
+#define HUNKY_DORY(lock_mask, myflock_style) ((lock_style & (lock_mask)) == 0 \
+ || deliver_flock(vstream_fileno(fp), (myflock_style), why->reason) == 0)
+
+ if (S_ISREG(st->st_mode)) {
+ if (HUNKY_DORY(MBOX_FLOCK_LOCK, MYFLOCK_STYLE_FLOCK)
+ && HUNKY_DORY(MBOX_FCNTL_LOCK, MYFLOCK_STYLE_FCNTL)) {
+ locked |= lock_style;
+ } else {
+ dsb_status(why, mbox_dsn(errno, def_dsn));
+ if (locked & MBOX_DOT_LOCK)
+ dot_unlockfile(path);
+ vstream_fclose(fp);
+ return (0);
+ }
+ }
+
+ /*
+ * Sanity check: reportedly, GNU POP3D creates a new mailbox file and
+ * deletes the old one. This does not play well with software that opens
+ * the mailbox first and then locks it, such as software that uses FCNTL
+ * or FLOCK locks on open file descriptors (some UNIX systems don't use
+ * dotlock files).
+ *
+ * To detect that GNU POP3D deletes the mailbox file we look at the target
+ * file hard-link count. Note that safe_open() guarantees a hard-link
+ * count of 1, so any change in this count is a sign of trouble.
+ */
+ if (S_ISREG(st->st_mode)
+ && (fstat(vstream_fileno(fp), st) < 0 || st->st_nlink != 1)) {
+ vstring_sprintf(why->reason, "target file status changed unexpectedly");
+ dsb_status(why, mbox_dsn(EAGAIN, def_dsn));
+ msg_warn("%s: file status changed unexpectedly", path);
+ if (locked & MBOX_DOT_LOCK)
+ dot_unlockfile(path);
+ vstream_fclose(fp);
+ return (0);
+ }
+ mp = (MBOX *) mymalloc(sizeof(*mp));
+ mp->path = mystrdup(path);
+ mp->fp = fp;
+ mp->locked = locked;
+ return (mp);
+}
+
+/* mbox_release - release mailbox exclusive access */
+
+void mbox_release(MBOX *mp)
+{
+
+ /*
+ * Unfortunately we can't close the stream, because on some file systems
+ * (AFS), the only way to find out if a file was written successfully is
+ * to close it, and therefore the close() operation is in the mail_copy()
+ * routine. If we really insist on owning the vstream member, then we
+ * should export appropriate methods that mail_copy() can use in order to
+ * manipulate a message stream.
+ */
+ if (mp->locked & MBOX_DOT_LOCK)
+ dot_unlockfile(mp->path);
+ myfree(mp->path);
+ myfree((void *) mp);
+}
+
+/* mbox_dsn - map errno value to mailbox-related DSN detail */
+
+const char *mbox_dsn(int err, const char *def_dsn)
+{
+#define TRY_AGAIN_ERROR(e) \
+ (e == EAGAIN || e == ESTALE)
+#define SYSTEM_FULL_ERROR(e) \
+ (e == ENOSPC)
+#define MBOX_FULL_ERROR(e) \
+ (e == EDQUOT || e == EFBIG)
+
+ return (TRY_AGAIN_ERROR(err) ? "4.2.0" :
+ SYSTEM_FULL_ERROR(err) ? "4.3.0" :
+ MBOX_FULL_ERROR(err) ? "5.2.2" :
+ def_dsn);
+}
diff --git a/src/global/mbox_open.h b/src/global/mbox_open.h
new file mode 100644
index 0000000..54d13c6
--- /dev/null
+++ b/src/global/mbox_open.h
@@ -0,0 +1,50 @@
+#ifndef _MBOX_OPEN_H_INCLUDED_
+#define _MBOX_OPEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* mbox_open 3h
+/* SUMMARY
+/* mailbox access
+/* SYNOPSIS
+/* #include <mbox_open.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <safe_open.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn_buf.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ char *path; /* saved path, for dot_unlock */
+ VSTREAM *fp; /* open stream or null */
+ int locked; /* what locks were set */
+} MBOX;
+extern MBOX *mbox_open(const char *, int, mode_t, struct stat *, uid_t, gid_t,
+ int, const char *, DSN_BUF *);
+extern void mbox_release(MBOX *);
+extern const char *mbox_dsn(int, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/memcache_proto.c b/src/global/memcache_proto.c
new file mode 100644
index 0000000..1290cf2
--- /dev/null
+++ b/src/global/memcache_proto.c
@@ -0,0 +1,207 @@
+/*++
+/* NAME
+/* memcache_proto 3
+/* SUMMARY
+/* memcache low-level protocol
+/* SYNOPSIS
+/* #include <memcache_proto.h>
+/*
+/* int memcache_get(fp, buf, len)
+/* VSTREAM *fp;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* int memcache_printf(fp, format, ...)
+/* VSTREAM *fp;
+/* const char *format;
+/*
+/* int memcache_vprintf(fp, format, ap)
+/* VSTREAM *fp;
+/* const char *format;
+/* va_list ap;
+/*
+/* int memcache_fread(fp, buf, len)
+/* VSTREAM *fp;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* int memcache_fwrite(fp, buf, len)
+/* VSTREAM *fp;
+/* const char *buf;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module implements the low-level memcache protocol.
+/* All functions return -1 on error and 0 on success.
+/* SEE ALSO
+/* smtp_proto(3) SMTP low-level protocol.
+/* 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
+/*--*/
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <compat_va_copy.h>
+
+/* Application-specific. */
+
+#include <memcache_proto.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* memcache_get - read one line from peer */
+
+int memcache_get(VSTREAM *stream, VSTRING *vp, ssize_t bound)
+{
+ int last_char;
+ int next_char;
+
+ last_char = (bound == 0 ? vstring_get(vp, stream) :
+ vstring_get_bound(vp, stream, bound));
+
+ switch (last_char) {
+
+ /*
+ * Do some repair in the rare case that we stopped reading in the
+ * middle of the CRLF record terminator.
+ */
+ case '\r':
+ if ((next_char = VSTREAM_GETC(stream)) == '\n') {
+ VSTRING_ADDCH(vp, '\n');
+ /* FALLTRHOUGH */
+ } else {
+ if (next_char != VSTREAM_EOF)
+ vstream_ungetc(stream, next_char);
+
+ /*
+ * Input too long, or EOF
+ */
+ default:
+ if (msg_verbose)
+ msg_info("%s got %s", VSTREAM_PATH(stream),
+ LEN(vp) < bound ? "EOF" : "input too long");
+ return (-1);
+ }
+
+ /*
+ * Strip off the record terminator: either CRLF or just bare LF.
+ */
+ case '\n':
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ if (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ VSTRING_TERMINATE(vp);
+ if (msg_verbose)
+ msg_info("%s got: %s", VSTREAM_PATH(stream), STR(vp));
+ return (0);
+ }
+}
+
+/* memcache_fwrite - write one blob to peer */
+
+int memcache_fwrite(VSTREAM *stream, const char *cp, ssize_t todo)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (todo < 0)
+ msg_panic("memcache_fwrite: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O.
+ */
+ if (msg_verbose)
+ msg_info("%s write: %.*s", VSTREAM_PATH(stream), (int) todo, cp);
+ if (vstream_fwrite(stream, cp, todo) != todo
+ || vstream_fputs("\r\n", stream) == VSTREAM_EOF)
+ return (-1);
+ else
+ return (0);
+}
+
+/* memcache_fread - read one blob from peer */
+
+int memcache_fread(VSTREAM *stream, VSTRING *buf, ssize_t todo)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (todo < 0)
+ msg_panic("memcache_fread: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O.
+ */
+ if (vstream_fread_buf(stream, buf, todo) != todo
+ || VSTREAM_GETC(stream) != '\r'
+ || VSTREAM_GETC(stream) != '\n') {
+ if (msg_verbose)
+ msg_info("%s read: error", VSTREAM_PATH(stream));
+ return (-1);
+ } else {
+ VSTRING_TERMINATE(buf);
+ if (msg_verbose)
+ msg_info("%s read: %s", VSTREAM_PATH(stream), STR(buf));
+ return (0);
+ }
+}
+
+/* memcache_vprintf - write one line to peer */
+
+int memcache_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
+{
+
+ /*
+ * Do the I/O.
+ */
+ vstream_vfprintf(stream, fmt, ap);
+ vstream_fputs("\r\n", stream);
+ if (vstream_ferror(stream))
+ return (-1);
+ else
+ return (0);
+}
+
+/* memcache_printf - write one line to peer */
+
+int memcache_printf(VSTREAM *stream, const char *fmt,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+ va_list ap2;
+
+ VA_COPY(ap2, ap);
+ vstring_vsprintf(buf, fmt, ap2);
+ va_end(ap2);
+ msg_info("%s write: %s", VSTREAM_PATH(stream), STR(buf));
+ vstring_free(buf);
+ }
+
+ /*
+ * Do the I/O.
+ */
+ ret = memcache_vprintf(stream, fmt, ap);
+ va_end(ap);
+ return (ret);
+}
diff --git a/src/global/memcache_proto.h b/src/global/memcache_proto.h
new file mode 100644
index 0000000..b39b335
--- /dev/null
+++ b/src/global/memcache_proto.h
@@ -0,0 +1,34 @@
+#ifndef _MEMCACHE_PROTO_H_INCLUDED_
+#define _MEMCACHE_PROTO_H_INCLUDED_
+
+/*++
+/* NAME
+/* memcache_proto 3h
+/* SUMMARY
+/* memcache low-level protocol
+/* SYNOPSIS
+/* #include <memcache_proto.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int memcache_get(VSTREAM *, VSTRING *, ssize_t);
+extern int memcache_vprintf(VSTREAM *, const char *, va_list);
+extern int PRINTFLIKE(2, 3) memcache_printf(VSTREAM *, const char *fmt,...);
+extern int memcache_fread(VSTREAM *, VSTRING *, ssize_t);
+extern int memcache_fwrite(VSTREAM *, const char *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/midna_adomain.c b/src/global/midna_adomain.c
new file mode 100644
index 0000000..81c98d4
--- /dev/null
+++ b/src/global/midna_adomain.c
@@ -0,0 +1,119 @@
+/*++
+/* NAME
+/* midna_adomain 3
+/* SUMMARY
+/* address domain part conversion
+/* SYNOPSIS
+/* #include <midna_adomain.h>
+/*
+/* char *midna_adomain_to_ascii(
+/* VSTRING *dest,
+/* const char *name)
+/*
+/* char *midna_adomain_to_utf8(
+/* VSTRING *dest,
+/* const char *name)
+/* DESCRIPTION
+/* The functions in this module transform the domain portion
+/* of an email address between ASCII and UTF-8 form. Both
+/* functions tolerate a missing domain, and both functions
+/* return a copy of the input when the domain portion requires
+/* no conversion.
+/*
+/* midna_adomain_to_ascii() converts an UTF-8 or ASCII domain
+/* portion to ASCII. The result is a null pointer when
+/* conversion fails. This function verifies that the resulting
+/* domain passes valid_hostname().
+/*
+/* midna_adomain_to_utf8() converts an UTF-8 or ASCII domain
+/* name to UTF-8. The result is a null pointer when conversion
+/* fails. This function verifies that the resulting domain,
+/* after conversion to ASCII, passes valid_hostname().
+/* SEE ALSO
+/* midna_domain(3), Postfix ASCII/UTF-8 domain name conversion
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Warnings: conversion error or result validation error.
+/* 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>
+
+#ifndef NO_EAI
+#include <unicode/uidna.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <stringops.h>
+#include <midna_domain.h>
+
+ /*
+ * Global library.
+ */
+#include <midna_adomain.h>
+
+#define STR(x) vstring_str(x)
+
+/* midna_adomain_to_utf8 - convert address domain portion to UTF8 */
+
+char *midna_adomain_to_utf8(VSTRING *dest, const char *src)
+{
+ const char *cp;
+ const char *domain_utf8;
+
+ if ((cp = strrchr(src, '@')) == 0) {
+ vstring_strcpy(dest, src);
+ } else {
+ vstring_sprintf(dest, "%*s@", (int) (cp - src), src);
+ if (*(cp += 1)) {
+ if (allascii(cp) && strstr(cp, "--") == 0) {
+ vstring_strcat(dest, cp);
+ } else if ((domain_utf8 = midna_domain_to_utf8(cp)) == 0) {
+ return (0);
+ } else {
+ vstring_strcat(dest, domain_utf8);
+ }
+ }
+ }
+ return (STR(dest));
+}
+
+/* midna_adomain_to_ascii - convert address domain portion to ASCII */
+
+char *midna_adomain_to_ascii(VSTRING *dest, const char *src)
+{
+ const char *cp;
+ const char *domain_ascii;
+
+ if ((cp = strrchr(src, '@')) == 0) {
+ vstring_strcpy(dest, src);
+ } else {
+ vstring_sprintf(dest, "%*s@", (int) (cp - src), src);
+ if (*(cp += 1)) {
+ if (allascii(cp)) {
+ vstring_strcat(dest, cp);
+ } else if ((domain_ascii = midna_domain_to_ascii(cp + 1)) == 0) {
+ return (0);
+ } else {
+ vstring_strcat(dest, domain_ascii);
+ }
+ }
+ }
+ return (STR(dest));
+}
+
+#endif /* NO_IDNA */
diff --git a/src/global/midna_adomain.h b/src/global/midna_adomain.h
new file mode 100644
index 0000000..14f02fe
--- /dev/null
+++ b/src/global/midna_adomain.h
@@ -0,0 +1,36 @@
+#ifndef _MIDNA_ADOMAIN_H_INCLUDED_
+#define _MIDNA_ADOMAIN_H_INCLUDED_
+
+/*++
+/* NAME
+/* midna_adomain 3h
+/* SUMMARY
+/* domain name conversion
+/* SYNOPSIS
+/* #include <midna_adomain.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *midna_adomain_to_utf8(VSTRING *, const char *);
+extern char *midna_adomain_to_ascii(VSTRING *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mime_8bit.in b/src/global/mime_8bit.in
new file mode 100644
index 0000000..f50de1d
--- /dev/null
+++ b/src/global/mime_8bit.in
@@ -0,0 +1,3 @@
+Header: f€€bar
+
+b€dy
diff --git a/src/global/mime_8bit.ref b/src/global/mime_8bit.ref
new file mode 100644
index 0000000..c818731
--- /dev/null
+++ b/src/global/mime_8bit.ref
@@ -0,0 +1,9 @@
+mime_state: warning: improper use of 8-bit data in message header: Header: f??bar
+MAIN 0 |Header: f€€bar
+HEADER END
+BODY N 0 |
+mime_state: warning: improper use of 8-bit data in message body: b?dy
+BODY N 1 |b€dy
+BODY END
+mime_state: warning: improper use of 8-bit data in message header
+mime_state: warning: improper use of 8-bit data in message body
diff --git a/src/global/mime_cvt.in b/src/global/mime_cvt.in
new file mode 100644
index 0000000..f3b7321
--- /dev/null
+++ b/src/global/mime_cvt.in
@@ -0,0 +1,83 @@
+mime-version: 1.0
+content-type: text/plain
+content-transfer-encoding: 8bit
+
+
+x
+xx
+xxx
+xxxx
+xxxxx
+xxxxxx
+xxxxxxx
+xxxxxxxx
+xxxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/src/global/mime_cvt.in2 b/src/global/mime_cvt.in2
new file mode 100644
index 0000000..c7b35ae
--- /dev/null
+++ b/src/global/mime_cvt.in2
@@ -0,0 +1,83 @@
+mime-version: 1.0
+content-type: text/plain
+content-transfer-encoding: 8bit
+
+
+x
+xx
+xxx
+xxxx
+xxxxx
+xxxxxx
+xxxxxxx
+xxxxxxxx
+xxxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/src/global/mime_cvt.in3 b/src/global/mime_cvt.in3
new file mode 100644
index 0000000..31621b6
--- /dev/null
+++ b/src/global/mime_cvt.in3
@@ -0,0 +1,83 @@
+mime-version: 1.0
+content-type: text/plain
+content-transfer-encoding: 8bit
+
+
+x
+xx
+xxx
+xxxx
+xxxxx
+xxxxxx
+xxxxxxx
+xxxxxxxx
+xxxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/src/global/mime_cvt.ref b/src/global/mime_cvt.ref
new file mode 100644
index 0000000..9dceafa
--- /dev/null
+++ b/src/global/mime_cvt.ref
@@ -0,0 +1,93 @@
+MAIN 0 |mime-version: 1.0
+mime_state: header_token: text / plain
+MAIN 25 |content-type: text/plain
+mime_state: header_token: 8bit
+MAIN 57 |Content-Transfer-Encoding: quoted-printable
+HEADER END
+BODY N 0 |
+BODY N 1 |=20
+BODY N 5 |x=20
+BODY N 10 |xx=20
+BODY N 16 |xxx=20
+BODY N 23 |xxxx=20
+BODY N 31 |xxxxx=20
+BODY N 40 |xxxxxx=20
+BODY N 50 |xxxxxxx=20
+BODY N 61 |xxxxxxxx=20
+BODY N 73 |xxxxxxxxx=20
+BODY N 86 |xxxxxxxxxx=20
+BODY N 100 |xxxxxxxxxxx=20
+BODY N 115 |xxxxxxxxxxxx=20
+BODY N 131 |xxxxxxxxxxxxx=20
+BODY N 148 |xxxxxxxxxxxxxx=20
+BODY N 166 |xxxxxxxxxxxxxxx=20
+BODY N 185 |xxxxxxxxxxxxxxxx=20
+BODY N 205 |xxxxxxxxxxxxxxxxx=20
+BODY N 226 |xxxxxxxxxxxxxxxxxx=20
+BODY N 248 |xxxxxxxxxxxxxxxxxxx=20
+BODY N 271 |xxxxxxxxxxxxxxxxxxxx=20
+BODY N 295 |xxxxxxxxxxxxxxxxxxxxx=20
+BODY N 320 |xxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 346 |xxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 373 |xxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 401 |xxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 430 |xxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 460 |xxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 491 |xxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 523 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 556 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 590 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 625 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 661 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 698 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 736 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 775 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 815 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 856 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 898 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 941 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 985 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1030 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1076 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1123 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1171 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1220 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1270 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1321 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1373 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1426 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1535 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1591 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1648 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1706 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1765 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1825 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1886 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 1948 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2011 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2075 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2140 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2206 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2273 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2341 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2410 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2551 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2623 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2696 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2770 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2845 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=20
+BODY N 2921 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 2996 |=20
+BODY N 3000 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3075 |x=20
+BODY N 3080 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3155 |xx=20
+BODY N 3161 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3236 |xxx=20
+BODY N 3243 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3318 |xxxx=20
+BODY N 3326 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3401 |xxxxx=20
+BODY END
diff --git a/src/global/mime_cvt.ref2 b/src/global/mime_cvt.ref2
new file mode 100644
index 0000000..619e989
--- /dev/null
+++ b/src/global/mime_cvt.ref2
@@ -0,0 +1,93 @@
+MAIN 0 |mime-version: 1.0
+mime_state: header_token: text / plain
+MAIN 25 |content-type: text/plain
+mime_state: header_token: 8bit
+MAIN 57 |Content-Transfer-Encoding: quoted-printable
+HEADER END
+BODY N 0 |
+BODY N 1 |=09
+BODY N 5 |x=09
+BODY N 10 |xx=09
+BODY N 16 |xxx=09
+BODY N 23 |xxxx=09
+BODY N 31 |xxxxx=09
+BODY N 40 |xxxxxx=09
+BODY N 50 |xxxxxxx=09
+BODY N 61 |xxxxxxxx=09
+BODY N 73 |xxxxxxxxx=09
+BODY N 86 |xxxxxxxxxx=09
+BODY N 100 |xxxxxxxxxxx=09
+BODY N 115 |xxxxxxxxxxxx=09
+BODY N 131 |xxxxxxxxxxxxx=09
+BODY N 148 |xxxxxxxxxxxxxx=09
+BODY N 166 |xxxxxxxxxxxxxxx=09
+BODY N 185 |xxxxxxxxxxxxxxxx=09
+BODY N 205 |xxxxxxxxxxxxxxxxx=09
+BODY N 226 |xxxxxxxxxxxxxxxxxx=09
+BODY N 248 |xxxxxxxxxxxxxxxxxxx=09
+BODY N 271 |xxxxxxxxxxxxxxxxxxxx=09
+BODY N 295 |xxxxxxxxxxxxxxxxxxxxx=09
+BODY N 320 |xxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 346 |xxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 373 |xxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 401 |xxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 430 |xxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 460 |xxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 491 |xxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 523 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 556 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 590 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 625 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 661 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 698 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 736 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 775 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 815 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 856 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 898 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 941 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 985 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1030 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1076 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1123 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1171 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1220 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1270 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1321 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1373 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1426 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1535 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1591 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1648 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1706 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1765 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1825 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1886 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 1948 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2011 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2075 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2140 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2206 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2273 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2341 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2410 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2551 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2623 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2696 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2770 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2845 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=09
+BODY N 2921 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 2996 |=09
+BODY N 3000 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3075 |x=09
+BODY N 3080 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3155 |xx=09
+BODY N 3161 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3236 |xxx=09
+BODY N 3243 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3318 |xxxx=09
+BODY N 3326 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3401 |xxxxx=09
+BODY END
diff --git a/src/global/mime_cvt.ref3 b/src/global/mime_cvt.ref3
new file mode 100644
index 0000000..07e1dfc
--- /dev/null
+++ b/src/global/mime_cvt.ref3
@@ -0,0 +1,93 @@
+MAIN 0 |mime-version: 1.0
+mime_state: header_token: text / plain
+MAIN 25 |content-type: text/plain
+mime_state: header_token: 8bit
+MAIN 57 |Content-Transfer-Encoding: quoted-printable
+HEADER END
+BODY N 0 |
+BODY N 1 |=01
+BODY N 5 |x=01
+BODY N 10 |xx=01
+BODY N 16 |xxx=01
+BODY N 23 |xxxx=01
+BODY N 31 |xxxxx=01
+BODY N 40 |xxxxxx=01
+BODY N 50 |xxxxxxx=01
+BODY N 61 |xxxxxxxx=01
+BODY N 73 |xxxxxxxxx=01
+BODY N 86 |xxxxxxxxxx=01
+BODY N 100 |xxxxxxxxxxx=01
+BODY N 115 |xxxxxxxxxxxx=01
+BODY N 131 |xxxxxxxxxxxxx=01
+BODY N 148 |xxxxxxxxxxxxxx=01
+BODY N 166 |xxxxxxxxxxxxxxx=01
+BODY N 185 |xxxxxxxxxxxxxxxx=01
+BODY N 205 |xxxxxxxxxxxxxxxxx=01
+BODY N 226 |xxxxxxxxxxxxxxxxxx=01
+BODY N 248 |xxxxxxxxxxxxxxxxxxx=01
+BODY N 271 |xxxxxxxxxxxxxxxxxxxx=01
+BODY N 295 |xxxxxxxxxxxxxxxxxxxxx=01
+BODY N 320 |xxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 346 |xxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 373 |xxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 401 |xxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 430 |xxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 460 |xxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 491 |xxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 523 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 556 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 590 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 625 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 661 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 698 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 736 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 775 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 815 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 856 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 898 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 941 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 985 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1030 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1076 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1123 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1171 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1220 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1270 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1321 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1373 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1426 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1535 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1591 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1648 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1706 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1765 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1825 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1886 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 1948 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2011 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2075 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2140 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2206 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2273 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2341 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2410 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2480 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2551 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2623 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2696 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2770 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2845 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=01
+BODY N 2921 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 2996 |=01
+BODY N 3000 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3075 |x=01
+BODY N 3080 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3155 |xx=01
+BODY N 3161 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3236 |xxx=01
+BODY N 3243 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3318 |xxxx=01
+BODY N 3326 |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
+BODY N 3401 |xxxxx=01
+BODY END
diff --git a/src/global/mime_dom.in b/src/global/mime_dom.in
new file mode 100644
index 0000000..08907a9
--- /dev/null
+++ b/src/global/mime_dom.in
@@ -0,0 +1,2 @@
+content-type: message/rfc822
+content-transfer-encoding: base64
diff --git a/src/global/mime_dom.ref b/src/global/mime_dom.ref
new file mode 100644
index 0000000..9369d46
--- /dev/null
+++ b/src/global/mime_dom.ref
@@ -0,0 +1,8 @@
+mime_state: header_token: message / rfc822
+MAIN 0 |content-type: message/rfc822
+mime_state: header_token: base64
+MAIN 34 |content-transfer-encoding: base64
+HEADER END
+mime_state: warning: invalid message/* or multipart/* encoding domain: base64
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/mime_garb1.in b/src/global/mime_garb1.in
new file mode 100644
index 0000000..3a4a0b2
--- /dev/null
+++ b/src/global/mime_garb1.in
@@ -0,0 +1,27 @@
+From: Some One <user@example.com>
+To: Some One <user@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: text/plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+This is a test
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: message/rfc822;
+ name="forwarded message"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+=46rom user@example.com Thu Jan 11 13:08:21 CET 2007
+To: user@example.com
+=46rom: Some One <user@example.com>
+Subject: Forwarded Test
+
+Blah
+
diff --git a/src/global/mime_garb1.ref b/src/global/mime_garb1.ref
new file mode 100644
index 0000000..8d9beb8
--- /dev/null
+++ b/src/global/mime_garb1.ref
@@ -0,0 +1,40 @@
+MAIN 0 |From: Some One <user@example.com>
+MAIN 32 |To: Some One <user@example.com>
+MAIN 46 |Subject: Test
+MAIN 64 |MIME-Version: 1.0
+mime_state: header_token: Multipart / Mixed
+mime_state: header_token: boundary = Boundary-00=_mvhpFky0yqNhsa4
+mime_state: PUSH boundary Boundary-00=_mvhpFky0yqNhsa4
+MAIN 95 |Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+HEADER END
+BODY N 0 |
+BODY N 1 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: text / plain
+MULT 0 |Content-Type: text/plain;
+ charset="utf-8"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+BODY N 0 |
+BODY N 1 |This is a test
+BODY N 16 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: message / rfc822
+MULT 0 |Content-Type: message/rfc822;
+ name="forwarded message"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+mime_state: warning: invalid message/* or multipart/* encoding domain: quoted-printable
+BODY N 0 |
+mime_state: garbage in nested header
+BODY N 0 |=46rom user@example.com Thu Jan 11 13:08:21 CET 2007
+BODY N 53 |To: user@example.com
+BODY N 74 |=46rom: Some One <user@example.com>
+BODY N 110 |Subject: Forwarded Test
+BODY N 134 |
+BODY N 135 |Blah
+BODY N 140 |
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
+mime_state: POP boundary Boundary-00=_mvhpFky0yqNhsa4
diff --git a/src/global/mime_garb2.in b/src/global/mime_garb2.in
new file mode 100644
index 0000000..9add720
--- /dev/null
+++ b/src/global/mime_garb2.in
@@ -0,0 +1,27 @@
+From: Some One <user@example.com>
+To: Some One <user@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: text/plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+This is a test
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: message/rfc822;
+ name="forwarded message"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+From user@example.com Thu Jan 11 13:08:21 CET 2007
+To: user@example.com
+From: Some One <user@example.com>
+Subject: Forwarded Test
+
+Blah
+
diff --git a/src/global/mime_garb2.ref b/src/global/mime_garb2.ref
new file mode 100644
index 0000000..2267138
--- /dev/null
+++ b/src/global/mime_garb2.ref
@@ -0,0 +1,38 @@
+MAIN 0 |From: Some One <user@example.com>
+MAIN 32 |To: Some One <user@example.com>
+MAIN 46 |Subject: Test
+MAIN 64 |MIME-Version: 1.0
+mime_state: header_token: Multipart / Mixed
+mime_state: header_token: boundary = Boundary-00=_mvhpFky0yqNhsa4
+mime_state: PUSH boundary Boundary-00=_mvhpFky0yqNhsa4
+MAIN 95 |Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+HEADER END
+BODY N 0 |
+BODY N 1 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: text / plain
+MULT 0 |Content-Type: text/plain;
+ charset="utf-8"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+BODY N 0 |
+BODY N 1 |This is a test
+BODY N 16 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: message / rfc822
+MULT 0 |Content-Type: message/rfc822;
+ name="forwarded message"
+mime_state: header_token: 7bit
+MULT 32 |Content-Transfer-Encoding: 7bit
+MULT 60 |Content-Disposition: inline
+BODY N 0 |
+mime_state: garbage in nested header
+BODY N 0 |From user@example.com Thu Jan 11 13:08:21 CET 2007
+BODY N 51 |To: user@example.com
+BODY N 72 |From: Some One <user@example.com>
+BODY N 106 |Subject: Forwarded Test
+BODY N 130 |
+BODY N 131 |Blah
+BODY N 136 |
+BODY END
+mime_state: POP boundary Boundary-00=_mvhpFky0yqNhsa4
diff --git a/src/global/mime_garb3.in b/src/global/mime_garb3.in
new file mode 100644
index 0000000..54d129a
--- /dev/null
+++ b/src/global/mime_garb3.in
@@ -0,0 +1,29 @@
+From: Some One <user@example.com>
+To: Some One <user@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+junk in primary header
+
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: text/plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+junk in multipart header
+
+This is a test
+--Boundary-00=_mvhpFky0yqNhsa4
+Content-Type: message/rfc822;
+ name="forwarded message"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+=46rom user@example.com Thu Jan 11 13:08:21 CET 2007
+To: user@example.com
+=46rom: Some One <user@example.com>
+Subject: Forwarded Test
+
+Blah
+
diff --git a/src/global/mime_garb3.ref b/src/global/mime_garb3.ref
new file mode 100644
index 0000000..ee8ef36
--- /dev/null
+++ b/src/global/mime_garb3.ref
@@ -0,0 +1,45 @@
+MAIN 0 |From: Some One <user@example.com>
+MAIN 32 |To: Some One <user@example.com>
+MAIN 46 |Subject: Test
+MAIN 64 |MIME-Version: 1.0
+mime_state: header_token: Multipart / Mixed
+mime_state: header_token: boundary = Boundary-00=_mvhpFky0yqNhsa4
+mime_state: PUSH boundary Boundary-00=_mvhpFky0yqNhsa4
+MAIN 95 |Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_mvhpFky0yqNhsa4"
+HEADER END
+mime_state: garbage in primary header
+BODY N 0 |
+BODY N 0 |junk in primary header
+BODY N 23 |
+BODY N 24 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: text / plain
+MULT 0 |Content-Type: text/plain;
+ charset="utf-8"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+mime_state: garbage in multipart header
+BODY N 0 |junk in multipart header
+BODY N 25 |
+BODY N 26 |This is a test
+BODY N 41 |--Boundary-00=_mvhpFky0yqNhsa4
+mime_state: header_token: message / rfc822
+MULT 0 |Content-Type: message/rfc822;
+ name="forwarded message"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+mime_state: warning: invalid message/* or multipart/* encoding domain: quoted-printable
+BODY N 0 |
+mime_state: garbage in nested header
+BODY N 0 |=46rom user@example.com Thu Jan 11 13:08:21 CET 2007
+BODY N 53 |To: user@example.com
+BODY N 74 |=46rom: Some One <user@example.com>
+BODY N 110 |Subject: Forwarded Test
+BODY N 134 |
+BODY N 135 |Blah
+BODY N 140 |
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
+mime_state: POP boundary Boundary-00=_mvhpFky0yqNhsa4
diff --git a/src/global/mime_garb4.in b/src/global/mime_garb4.in
new file mode 100644
index 0000000..16dcfc6
--- /dev/null
+++ b/src/global/mime_garb4.in
@@ -0,0 +1,34 @@
+From: Some One <user@example.com>
+To: Some One <user@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="top-level-boundary"
+
+--top-level-boundary
+Content-Type: message/rfc822;
+ name="forwarded message"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+To: user@example.com
+=46rom: Some One <user@example.com>
+Subject: Forwarded Test
+Content-Type: Multipart/Mixed;
+ boundary=3D"nested-level-boundary"
+
+--nested-level-boundary
+Content-Type: text/plain;
+Content-Transfer-Encoding: quoted-printable
+
+Blah
+
+--nested-level-boundary--
+
+--top-level-boundary
+Content-Type: text/plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+This is a test
diff --git a/src/global/mime_garb4.ref b/src/global/mime_garb4.ref
new file mode 100644
index 0000000..bd0e5cc
--- /dev/null
+++ b/src/global/mime_garb4.ref
@@ -0,0 +1,50 @@
+MAIN 0 |From: Some One <user@example.com>
+MAIN 32 |To: Some One <user@example.com>
+MAIN 46 |Subject: Test
+MAIN 64 |MIME-Version: 1.0
+mime_state: header_token: Multipart / Mixed
+mime_state: header_token: boundary = top-level-boundary
+mime_state: PUSH boundary top-level-boundary
+MAIN 95 |Content-Type: Multipart/Mixed;
+ boundary="top-level-boundary"
+HEADER END
+BODY N 0 |
+BODY N 1 |--top-level-boundary
+mime_state: header_token: message / rfc822
+MULT 0 |Content-Type: message/rfc822;
+ name="forwarded message"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+mime_state: warning: invalid message/* or multipart/* encoding domain: quoted-printable
+BODY N 0 |
+NEST 0 |To: user@example.com
+NEST 36 |=46rom: Some One <user@example.com>
+NEST 60 |Subject: Forwarded Test
+mime_state: header_token: Multipart / Mixed
+mime_state: header_token: boundary = 3D
+mime_state: PUSH boundary 3D
+NEST 91 |Content-Type: Multipart/Mixed;
+ boundary=3D"nested-level-boundary"
+BODY N 0 |
+BODY N 1 |--nested-level-boundary
+BODY N 25 |Content-Type: text/plain;
+BODY N 51 |Content-Transfer-Encoding: quoted-printable
+BODY N 95 |
+BODY N 96 |Blah
+BODY N 101 |
+BODY N 102 |--nested-level-boundary--
+BODY N 128 |
+mime_state: POP boundary 3D
+BODY N 129 |--top-level-boundary
+mime_state: header_token: text / plain
+MULT 0 |Content-Type: text/plain;
+ charset="utf-8"
+mime_state: header_token: quoted-printable
+MULT 44 |Content-Transfer-Encoding: quoted-printable
+MULT 72 |Content-Disposition: inline
+BODY N 0 |
+BODY N 1 |This is a test
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
+mime_state: POP boundary top-level-boundary
diff --git a/src/global/mime_global.in b/src/global/mime_global.in
new file mode 100644
index 0000000..e76cf37
--- /dev/null
+++ b/src/global/mime_global.in
@@ -0,0 +1,96 @@
+mime-version: 1.0
+Content-Type: multipart/report; report-type=delivery-status; boundary="foobar"
+Content-Transfer-Encoding: 8bit
+
+This is a MIME-encapsulated message.
+
+--foobar
+content-type: message/global
+content-transfer-encoding: 8bit
+
+mime-version: 1.0
+content-type: text/plain
+From: xxx
+To: yyy
+Subject: zzzz
+
+x
+xx
+xxx
+xxxx
+xxxxx
+xxxxxx
+xxxxxxx
+xxxxxxxx
+xxxxxxxxx
+xxxxxxxxxx
+xxxxxxxxxxx
+xxxxxxxxxxxx
+xxxxxxxxxxxxx
+xxxxxxxxxxxxxx
+xxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+--foobar--
diff --git a/src/global/mime_nest.in b/src/global/mime_nest.in
new file mode 100644
index 0000000..26fd6da
--- /dev/null
+++ b/src/global/mime_nest.in
@@ -0,0 +1,69 @@
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
+content-type: multipart/mixed; boundary=foobar
+
+--foobar
diff --git a/src/global/mime_nest.ref b/src/global/mime_nest.ref
new file mode 100644
index 0000000..b6e7ae2
--- /dev/null
+++ b/src/global/mime_nest.ref
@@ -0,0 +1,163 @@
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MAIN 0 |content-type: multipart/mixed; boundary=foobar
+HEADER END
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: PUSH boundary foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+mime_state: header_token: multipart / mixed
+mime_state: header_token: boundary = foobar
+mime_state: warning: MIME nesting exceeds safety limit: content-type: multipart/mixed; boundary=foobar
+MULT 0 |content-type: multipart/mixed; boundary=foobar
+BODY N 0 |
+BODY N 1 |--foobar
+BODY END
+mime_state: warning: MIME nesting exceeds safety limit
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
+mime_state: POP boundary foobar
diff --git a/src/global/mime_state.c b/src/global/mime_state.c
new file mode 100644
index 0000000..e1b6a65
--- /dev/null
+++ b/src/global/mime_state.c
@@ -0,0 +1,1300 @@
+/*++
+/* NAME
+/* mime_state 3
+/* SUMMARY
+/* MIME parser state machine
+/* SYNOPSIS
+/* #include <mime_state.h>
+/*
+/* MIME_STATE *mime_state_alloc(flags, head_out, head_end,
+/* body_out, body_end,
+/* err_print, context)
+/* int flags;
+/* void (*head_out)(void *ptr, int header_class,
+/* const HEADER_OPTS *header_info,
+/* VSTRING *buf, off_t offset);
+/* void (*head_end)(void *ptr);
+/* void (*body_out)(void *ptr, int rec_type,
+/* const char *buf, ssize_t len,
+/* off_t offset);
+/* void (*body_end)(void *ptr);
+/* void (*err_print)(void *ptr, int err_flag, const char *text)
+/* void *context;
+/*
+/* int mime_state_update(state, rec_type, buf, len)
+/* MIME_STATE *state;
+/* int rec_type;
+/* const char *buf;
+/* ssize_t len;
+/*
+/* MIME_STATE *mime_state_free(state)
+/* MIME_STATE *state;
+/*
+/* const char *mime_state_error(error_code)
+/* int error_code;
+/*
+/* typedef struct {
+/* .in +4
+/* const int code; /* internal error code */
+/* const char *dsn; /* RFC 3463 */
+/* const char *text; /* descriptive text */
+/* .in -4
+/* } MIME_STATE_DETAIL;
+/*
+/* const MIME_STATE_DETAIL *mime_state_detail(error_code)
+/* int error_code;
+/* DESCRIPTION
+/* This module implements a one-pass MIME processor with optional
+/* 8-bit to quoted-printable conversion.
+/*
+/* In order to fend off denial of service attacks, message headers
+/* are truncated at or above var_header_limit bytes, message boundary
+/* strings are truncated at var_mime_bound_len bytes, and the multipart
+/* nesting level is limited to var_mime_maxdepth levels.
+/*
+/* mime_state_alloc() creates a MIME state machine. The machine
+/* is delivered in its initial state, expecting content type
+/* text/plain, 7-bit data.
+/*
+/* mime_state_update() updates the MIME state machine according
+/* to the input record type and the record content.
+/* The result value is the bit-wise OR of zero or more of the following:
+/* .IP MIME_ERR_TRUNC_HEADER
+/* A message header was longer than var_header_limit bytes.
+/* .IP MIME_ERR_NESTING
+/* The MIME structure was nested more than var_mime_maxdepth levels.
+/* .IP MIME_ERR_8BIT_IN_HEADER
+/* A message header contains 8-bit data. This is always illegal.
+/* .IP MIME_ERR_8BIT_IN_7BIT_BODY
+/* A MIME header specifies (or defaults to) 7-bit content, but the
+/* corresponding message body or body parts contain 8-bit content.
+/* .IP MIME_ERR_ENCODING_DOMAIN
+/* An entity of type "message" or "multipart" specifies the wrong
+/* content transfer encoding domain, or specifies a transformation
+/* (quoted-printable, base64) instead of a domain (7bit, 8bit,
+/* or binary).
+/* .PP
+/* mime_state_free() releases storage for a MIME state machine,
+/* and conveniently returns a null pointer.
+/*
+/* mime_state_error() returns a string representation for the
+/* specified error code. When multiple errors are specified it
+/* reports what it deems the most serious one.
+/*
+/* mime_state_detail() returns a table entry with error
+/* information for the specified error code. When multiple
+/* errors are specified it reports what it deems the most
+/* serious one.
+/*
+/* Arguments:
+/* .IP body_out
+/* The output routine for body lines. It receives unmodified input
+/* records, or the result of 8-bit -> 7-bit conversion.
+/* .IP body_end
+/* A null pointer, or a pointer to a routine that is called after
+/* the last input record is processed.
+/* .IP buf
+/* Buffer with the content of a logical or physical message record.
+/* .IP context
+/* Caller context that is passed on to the head_out and body_out
+/* routines.
+/* .IP enc_type
+/* The content encoding: MIME_ENC_7BIT or MIME_ENC_8BIT.
+/* .IP err_print
+/* Null pointer, or pointer to a function that is called with
+/* arguments: the application context, the error type, and the
+/* offending input. Only one instance per error type is reported.
+/* .IP flags
+/* Special processing options. Specify the bit-wise OR of zero or
+/* more of the following:
+/* .RS
+/* .IP MIME_OPT_DISABLE_MIME
+/* Pay no attention to Content-* message headers, and switch to
+/* message body state at the end of the primary message headers.
+/* .IP MIME_OPT_REPORT_TRUNC_HEADER
+/* Report errors that set the MIME_ERR_TRUNC_HEADER error flag
+/* (see above).
+/* .IP MIME_OPT_REPORT_8BIT_IN_HEADER
+/* Report errors that set the MIME_ERR_8BIT_IN_HEADER error
+/* flag (see above). This rarely stops legitimate mail.
+/* .IP MIME_OPT_REPORT_8BIT_IN_7BIT_BODY
+/* Report errors that set the MIME_ERR_8BIT_IN_7BIT_BODY error
+/* flag (see above). This currently breaks Majordomo mail that is
+/* forwarded for approval, because Majordomo does not propagate
+/* MIME type information from the enclosed message to the message
+/* headers of the request for approval.
+/* .IP MIME_OPT_REPORT_ENCODING_DOMAIN
+/* Report errors that set the MIME_ERR_ENCODING_DOMAIN error
+/* flag (see above).
+/* .IP MIME_OPT_REPORT_NESTING
+/* Report errors that set the MIME_ERR_NESTING error flag
+/* (see above).
+/* .IP MIME_OPT_DOWNGRADE
+/* Transform content that claims to be 8-bit into quoted-printable.
+/* Where appropriate, update Content-Transfer-Encoding: message
+/* headers.
+/* .RE
+/* .sp
+/* For convenience, MIME_OPT_NONE requests no special processing.
+/* .IP header_class
+/* Specifies where a message header is located.
+/* .RS
+/* .IP MIME_HDR_PRIMARY
+/* In the primary message header section.
+/* .IP MIME_HDR_MULTIPART
+/* In the header section after a multipart boundary string.
+/* .IP MIME_HDR_NESTED
+/* At the start of a nested (e.g., message/rfc822) message.
+/* .RE
+/* .sp
+/* For convenience, the macros MIME_HDR_FIRST and MIME_HDR_LAST
+/* specify the range of MIME_HDR_MUMBLE macros.
+/* .sp
+/* To find out if something is a MIME header at the beginning
+/* of an RFC 822 message or an attached message, look at the
+/* header_info argument.
+/* .IP header_info
+/* Null pointer or information about the message header, see
+/* header_opts(3).
+/* .IP head_out
+/* The output routine that is invoked for outputting a message header.
+/* A multi-line header is passed as one chunk of text with embedded
+/* newlines.
+/* It is the responsibility of the output routine to break the text
+/* at embedded newlines, and to break up long text between newlines
+/* into multiple output records.
+/* Note: an output routine is explicitly allowed to modify the text.
+/* .IP head_end
+/* A null pointer, or a pointer to a routine that is called after
+/* the last message header in the first header block is processed.
+/* .IP len
+/* Length of non-VSTRING input buffer.
+/* .IP offset
+/* The offset in bytes from the start of the current block of message
+/* headers or body lines. Line boundaries are counted as one byte.
+/* .IP rec_type
+/* The input record type as defined in rec_type(3h). State is
+/* updated for text records (REC_TYPE_NORM or REC_TYPE_CONT).
+/* Some input records are stored internally in order to reconstruct
+/* multi-line input. Upon receipt of any non-text record type, all
+/* stored input is flushed and the state is set to "body".
+/* .IP state
+/* MIME parser state created with mime_state_alloc().
+/* BUGS
+/* NOTE: when the end of headers is reached, 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 return no result, it is up to the
+/* call-back routines to check if a previous call-back experienced
+/* an error.
+/*
+/* Different mail user agents treat malformed message boundary
+/* strings in different ways. The Postfix MIME processor cannot
+/* be bug-compatible with everything.
+/*
+/* This module will not glue together multipart boundary strings that
+/* span multiple input records.
+/*
+/* This module will not glue together RFC 2231 formatted (boundary)
+/* parameter values. RFC 2231 claims compatibility with existing
+/* MIME processors. Splitting boundary strings is not backwards
+/* compatible.
+/*
+/* The "8-bit data inside 7-bit body" test is myopic. It is not aware
+/* of any enclosing (message or multipart) encoding information.
+/*
+/* If the input ends in data other than a hard line break, this module
+/* will add a hard line break of its own. No line break is added to
+/* empty input.
+/*
+/* This code recognizes the obsolete form "headername :" but will
+/* normalize it to the canonical form "headername:". Leaving the
+/* obsolete form alone would cause too much trouble with existing code
+/* that expects only the normalized form.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* header_opts(3) header information lookup
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 2045 (MIME: Format of internet message bodies)
+/* RFC 2046 (MIME: Media types)
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This code was implemented from scratch after reading the RFC
+/* documents. This was a relatively straightforward effort with
+/* few if any surprises. Victor Duchovni of Morgan Stanley shared
+/* his experiences with ambiguities in real-life MIME implementations.
+/* Liviu Daia of the Romanian Academy shared his insights in some
+/* of the darker corners.
+/* 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 <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <rec_type.h>
+#include <is_header.h>
+#include <header_opts.h>
+#include <mail_params.h>
+#include <header_token.h>
+#include <lex_822.h>
+#include <mime_state.h>
+
+/* Application-specific. */
+
+ /*
+ * Mime parser stack element for multipart content.
+ */
+typedef struct MIME_STACK {
+ int def_ctype; /* default content type */
+ int def_stype; /* default content subtype */
+ char *boundary; /* boundary string */
+ ssize_t bound_len; /* boundary length */
+ struct MIME_STACK *next; /* linkage */
+} MIME_STACK;
+
+ /*
+ * Mime parser state.
+ */
+#define MIME_MAX_TOKEN 3 /* tokens per attribute */
+
+struct MIME_STATE {
+
+ /*
+ * Volatile members.
+ */
+ int curr_state; /* header/body state */
+ int curr_ctype; /* last or default content type */
+ int curr_stype; /* last or default content subtype */
+ int curr_encoding; /* last or default content encoding */
+ int curr_domain; /* last or default encoding unit */
+ VSTRING *output_buffer; /* headers, quoted-printable body */
+ int prev_rec_type; /* previous input record type */
+ int nesting_level; /* safety */
+ MIME_STACK *stack; /* for composite types */
+ HEADER_TOKEN token[MIME_MAX_TOKEN]; /* header token array */
+ VSTRING *token_buffer; /* header parser scratch buffer */
+ int err_flags; /* processing errors */
+ off_t head_offset; /* offset in header block */
+ off_t body_offset; /* offset in body block */
+
+ /*
+ * Static members.
+ */
+ int static_flags; /* static processing options */
+ MIME_STATE_HEAD_OUT head_out; /* header output routine */
+ MIME_STATE_ANY_END head_end; /* end of primary header routine */
+ MIME_STATE_BODY_OUT body_out; /* body output routine */
+ MIME_STATE_ANY_END body_end; /* end of body output routine */
+ MIME_STATE_ERR_PRINT err_print; /* error report */
+ void *app_context; /* application context */
+};
+
+ /*
+ * Content types and subtypes that we care about, either because we have to,
+ * or because we want to filter out broken MIME messages.
+ */
+#define MIME_CTYPE_OTHER 0
+#define MIME_CTYPE_TEXT 1
+#define MIME_CTYPE_MESSAGE 2
+#define MIME_CTYPE_MULTIPART 3
+
+#define MIME_STYPE_OTHER 0
+#define MIME_STYPE_PLAIN 1
+#define MIME_STYPE_RFC822 2
+#define MIME_STYPE_PARTIAL 3
+#define MIME_STYPE_EXTERN_BODY 4
+#define MIME_STYPE_GLOBAL 5
+
+ /*
+ * MIME parser states. We steal from the public interface.
+ */
+#define MIME_STATE_PRIMARY MIME_HDR_PRIMARY /* primary headers */
+#define MIME_STATE_MULTIPART MIME_HDR_MULTIPART /* after --boundary */
+#define MIME_STATE_NESTED MIME_HDR_NESTED /* message/rfc822 */
+#define MIME_STATE_BODY (MIME_HDR_NESTED + 1)
+
+#define SET_MIME_STATE(ptr, state, ctype, stype, encoding, domain) do { \
+ (ptr)->curr_state = (state); \
+ (ptr)->curr_ctype = (ctype); \
+ (ptr)->curr_stype = (stype); \
+ (ptr)->curr_encoding = (encoding); \
+ (ptr)->curr_domain = (domain); \
+ if ((state) == MIME_STATE_BODY) \
+ (ptr)->body_offset = 0; \
+ else \
+ (ptr)->head_offset = 0; \
+ } while (0)
+
+#define SET_CURR_STATE(ptr, state) do { \
+ (ptr)->curr_state = (state); \
+ if ((state) == MIME_STATE_BODY) \
+ (ptr)->body_offset = 0; \
+ else \
+ (ptr)->head_offset = 0; \
+ } while (0)
+
+ /*
+ * MIME encodings and domains. We intentionally use the same codes for
+ * encodings and domains, so that we can easily find out whether a content
+ * transfer encoding header specifies a domain or whether it specifies
+ * domain+encoding, which is illegal for multipart/any and message/any.
+ */
+typedef struct MIME_ENCODING {
+ const char *name; /* external representation */
+ int encoding; /* internal representation */
+ int domain; /* subset of encoding */
+} MIME_ENCODING;
+
+#define MIME_ENC_QP 1 /* encoding + domain */
+#define MIME_ENC_BASE64 2 /* encoding + domain */
+ /* These are defined in mime_state.h as part of the external interface. */
+#ifndef MIME_ENC_7BIT
+#define MIME_ENC_7BIT 7 /* domain only */
+#define MIME_ENC_8BIT 8 /* domain only */
+#define MIME_ENC_BINARY 9 /* domain only */
+#endif
+
+static const MIME_ENCODING mime_encoding_map[] = { /* RFC 2045 */
+ "7bit", MIME_ENC_7BIT, MIME_ENC_7BIT, /* domain */
+ "8bit", MIME_ENC_8BIT, MIME_ENC_8BIT, /* domain */
+ "binary", MIME_ENC_BINARY, MIME_ENC_BINARY, /* domain */
+ "base64", MIME_ENC_BASE64, MIME_ENC_7BIT, /* encoding */
+ "quoted-printable", MIME_ENC_QP, MIME_ENC_7BIT, /* encoding */
+ 0,
+};
+
+ /*
+ * Silly Little Macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define END(x) vstring_end(x)
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
+
+#define REPORT_ERROR_LEN(state, err_type, text, len) do { \
+ if ((state->err_flags & err_type) == 0) { \
+ if (state->err_print != 0) \
+ state->err_print(state->app_context, err_type, text, len); \
+ state->err_flags |= err_type; \
+ } \
+ } while (0)
+
+#define REPORT_ERROR(state, err_type, text) do { \
+ const char *_text = text; \
+ ssize_t _len = strlen(text); \
+ REPORT_ERROR_LEN(state, err_type, _text, _len); \
+ } while (0)
+
+#define REPORT_ERROR_BUF(state, err_type, buf) \
+ REPORT_ERROR_LEN(state, err_type, STR(buf), LEN(buf))
+
+
+ /*
+ * Outputs and state changes are interleaved, so we must maintain separate
+ * offsets for header and body segments.
+ */
+#define HEAD_OUT(ptr, info, len) do { \
+ if ((ptr)->head_out) { \
+ (ptr)->head_out((ptr)->app_context, (ptr)->curr_state, \
+ (info), (ptr)->output_buffer, (ptr)->head_offset); \
+ (ptr)->head_offset += (len) + 1; \
+ } \
+ } while(0)
+
+#define BODY_OUT(ptr, rec_type, text, len) do { \
+ if ((ptr)->body_out) { \
+ (ptr)->body_out((ptr)->app_context, (rec_type), \
+ (text), (len), (ptr)->body_offset); \
+ (ptr)->body_offset += (len) + 1; \
+ } \
+ } while(0)
+
+/* mime_state_push - push boundary onto stack */
+
+static void mime_state_push(MIME_STATE *state, int def_ctype, int def_stype,
+ const char *boundary)
+{
+ MIME_STACK *stack;
+
+ /*
+ * RFC 2046 mandates that a boundary string be up to 70 characters long.
+ * Some MTAs, including Postfix, include the fully-qualified MTA name
+ * which can be longer, so we are willing to handle boundary strings that
+ * exceed the RFC specification. We allow for message headers of up to
+ * var_header_limit characters. In order to avoid denial of service, we
+ * have to impose a configurable limit on the amount of text that we are
+ * willing to store as a boundary string. Despite this truncation way we
+ * will still correctly detect all intermediate boundaries and all the
+ * message headers that follow those boundaries.
+ */
+ state->nesting_level += 1;
+ stack = (MIME_STACK *) mymalloc(sizeof(*stack));
+ stack->def_ctype = def_ctype;
+ stack->def_stype = def_stype;
+ if ((stack->bound_len = strlen(boundary)) > var_mime_bound_len)
+ stack->bound_len = var_mime_bound_len;
+ stack->boundary = mystrndup(boundary, stack->bound_len);
+ stack->next = state->stack;
+ state->stack = stack;
+ if (msg_verbose)
+ msg_info("PUSH boundary %s", stack->boundary);
+}
+
+/* mime_state_pop - pop boundary from stack */
+
+static void mime_state_pop(MIME_STATE *state)
+{
+ MIME_STACK *stack;
+
+ if ((stack = state->stack) == 0)
+ msg_panic("mime_state_pop: there is no stack");
+ if (msg_verbose)
+ msg_info("POP boundary %s", stack->boundary);
+ state->nesting_level -= 1;
+ state->stack = stack->next;
+ myfree(stack->boundary);
+ myfree((void *) stack);
+}
+
+/* mime_state_alloc - create MIME state machine */
+
+MIME_STATE *mime_state_alloc(int flags,
+ MIME_STATE_HEAD_OUT head_out,
+ MIME_STATE_ANY_END head_end,
+ MIME_STATE_BODY_OUT body_out,
+ MIME_STATE_ANY_END body_end,
+ MIME_STATE_ERR_PRINT err_print,
+ void *context)
+{
+ MIME_STATE *state;
+
+ state = (MIME_STATE *) mymalloc(sizeof(*state));
+
+ /* Volatile members. */
+ state->err_flags = 0;
+ state->body_offset = 0; /* XXX */
+ SET_MIME_STATE(state, MIME_STATE_PRIMARY,
+ MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ state->output_buffer = vstring_alloc(100);
+ state->prev_rec_type = 0;
+ state->stack = 0;
+ state->token_buffer = vstring_alloc(1);
+ state->nesting_level = -1; /* BC Fix 20170512 */
+
+ /* Static members. */
+ state->static_flags = flags;
+ state->head_out = head_out;
+ state->head_end = head_end;
+ state->body_out = body_out;
+ state->body_end = body_end;
+ state->err_print = err_print;
+ state->app_context = context;
+ return (state);
+}
+
+/* mime_state_free - destroy MIME state machine */
+
+MIME_STATE *mime_state_free(MIME_STATE *state)
+{
+ vstring_free(state->output_buffer);
+ while (state->stack)
+ mime_state_pop(state);
+ if (state->token_buffer)
+ vstring_free(state->token_buffer);
+ myfree((void *) state);
+ return (0);
+}
+
+/* mime_state_content_type - process content-type header */
+
+static void mime_state_content_type(MIME_STATE *state,
+ const HEADER_OPTS *header_info)
+{
+ const char *cp;
+ ssize_t tok_count;
+ int def_ctype;
+ int def_stype;
+
+#define TOKEN_MATCH(tok, text) \
+ ((tok).type == HEADER_TOK_TOKEN && strcasecmp((tok).u.value, (text)) == 0)
+
+#define RFC2045_TSPECIALS "()<>@,;:\\\"/[]?="
+
+#define PARSE_CONTENT_TYPE_HEADER(state, ptr) \
+ header_token(state->token, MIME_MAX_TOKEN, \
+ state->token_buffer, ptr, RFC2045_TSPECIALS, ';')
+
+ cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
+ if ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) > 0) {
+
+ /*
+ * text/whatever. Right now we don't really care if it is plain or
+ * not, but we may want to recognize subtypes later, and then this
+ * code can serve as an example.
+ */
+ if (TOKEN_MATCH(state->token[0], "text")) {
+ state->curr_ctype = MIME_CTYPE_TEXT;
+ if (tok_count >= 3
+ && state->token[1].type == '/'
+ && TOKEN_MATCH(state->token[2], "plain"))
+ state->curr_stype = MIME_STYPE_PLAIN;
+ else
+ state->curr_stype = MIME_STYPE_OTHER;
+ return;
+ }
+
+ /*
+ * message/whatever body parts start with another block of message
+ * headers that we may want to look at. The partial and external-body
+ * subtypes cannot be subjected to 8-bit -> 7-bit conversion, so we
+ * must properly recognize them.
+ */
+ if (TOKEN_MATCH(state->token[0], "message")) {
+ state->curr_ctype = MIME_CTYPE_MESSAGE;
+ state->curr_stype = MIME_STYPE_OTHER;
+ if (tok_count >= 3
+ && state->token[1].type == '/') {
+ if (TOKEN_MATCH(state->token[2], "rfc822"))
+ state->curr_stype = MIME_STYPE_RFC822;
+ else if (TOKEN_MATCH(state->token[2], "partial"))
+ state->curr_stype = MIME_STYPE_PARTIAL;
+ else if (TOKEN_MATCH(state->token[2], "external-body"))
+ state->curr_stype = MIME_STYPE_EXTERN_BODY;
+ else if (TOKEN_MATCH(state->token[2], "global"))
+ state->curr_stype = MIME_STYPE_GLOBAL;
+ }
+ return;
+ }
+
+ /*
+ * multipart/digest has default content type message/rfc822,
+ * multipart/whatever has default content type text/plain.
+ */
+ if (TOKEN_MATCH(state->token[0], "multipart")) {
+ state->curr_ctype = MIME_CTYPE_MULTIPART;
+ if (tok_count >= 3
+ && state->token[1].type == '/'
+ && TOKEN_MATCH(state->token[2], "digest")) {
+ def_ctype = MIME_CTYPE_MESSAGE;
+ def_stype = MIME_STYPE_RFC822;
+ } else {
+ def_ctype = MIME_CTYPE_TEXT;
+ def_stype = MIME_STYPE_PLAIN;
+ }
+
+ /*
+ * Yes, this is supposed to capture multiple boundary strings,
+ * which are illegal and which could be used to hide content in
+ * an implementation dependent manner. The code below allows us
+ * to find embedded message headers as long as the sender uses
+ * only one of these same-level boundary strings.
+ *
+ * Yes, this is supposed to ignore the boundary value type.
+ */
+ while ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) >= 0) {
+ if (tok_count >= 3
+ && TOKEN_MATCH(state->token[0], "boundary")
+ && state->token[1].type == '=') {
+ if (state->nesting_level > var_mime_maxdepth) {
+ if (state->static_flags & MIME_OPT_REPORT_NESTING)
+ REPORT_ERROR_BUF(state, MIME_ERR_NESTING,
+ state->output_buffer);
+ } else {
+ mime_state_push(state, def_ctype, def_stype,
+ state->token[2].u.value);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ /*
+ * other/whatever.
+ */
+ else {
+ state->curr_ctype = MIME_CTYPE_OTHER;
+ return;
+ }
+}
+
+/* mime_state_content_encoding - process content-transfer-encoding header */
+
+static void mime_state_content_encoding(MIME_STATE *state,
+ const HEADER_OPTS *header_info)
+{
+ const char *cp;
+ const MIME_ENCODING *cmp;
+
+#define PARSE_CONTENT_ENCODING_HEADER(state, ptr) \
+ header_token(state->token, 1, state->token_buffer, ptr, (char *) 0, 0)
+
+ /*
+ * Do content-transfer-encoding header. Never set the encoding domain to
+ * something other than 7bit, 8bit or binary, even if we don't recognize
+ * the input.
+ */
+ cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
+ if (PARSE_CONTENT_ENCODING_HEADER(state, &cp) > 0
+ && state->token[0].type == HEADER_TOK_TOKEN) {
+ for (cmp = mime_encoding_map; cmp->name != 0; cmp++) {
+ if (strcasecmp(state->token[0].u.value, cmp->name) == 0) {
+ state->curr_encoding = cmp->encoding;
+ state->curr_domain = cmp->domain;
+ break;
+ }
+ }
+ }
+}
+
+/* mime_state_enc_name - encoding to printable form */
+
+static const char *mime_state_enc_name(int encoding)
+{
+ const MIME_ENCODING *cmp;
+
+ for (cmp = mime_encoding_map; cmp->name != 0; cmp++)
+ if (encoding == cmp->encoding)
+ return (cmp->name);
+ return ("unknown");
+}
+
+/* mime_state_downgrade - convert 8-bit data to quoted-printable */
+
+static void mime_state_downgrade(MIME_STATE *state, int rec_type,
+ const char *text, ssize_t len)
+{
+ static char hexchars[] = "0123456789ABCDEF";
+ const unsigned char *cp;
+ int ch;
+
+#define QP_ENCODE(buffer, ch) { \
+ VSTRING_ADDCH(buffer, '='); \
+ VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \
+ VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \
+ }
+
+ /*
+ * Insert a soft line break when the output reaches a critical length
+ * before we reach a hard line break.
+ */
+ for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) {
+ /* Critical length before hard line break. */
+ if (LEN(state->output_buffer) > 72) {
+ VSTRING_ADDCH(state->output_buffer, '=');
+ VSTRING_TERMINATE(state->output_buffer);
+ BODY_OUT(state, REC_TYPE_NORM,
+ STR(state->output_buffer),
+ LEN(state->output_buffer));
+ VSTRING_RESET(state->output_buffer);
+ }
+ /* Append the next character. */
+ ch = *cp;
+ if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) {
+ QP_ENCODE(state->output_buffer, ch);
+ } else {
+ VSTRING_ADDCH(state->output_buffer, ch);
+ }
+ }
+
+ /*
+ * Flush output after a hard line break (i.e. the end of a REC_TYPE_NORM
+ * record). Fix trailing whitespace as per the RFC: in the worst case,
+ * the output length will grow from 73 characters to 75 characters.
+ */
+ if (rec_type == REC_TYPE_NORM) {
+ if (LEN(state->output_buffer) > 0
+ && ((ch = END(state->output_buffer)[-1]) == ' ' || ch == '\t')) {
+ vstring_truncate(state->output_buffer,
+ LEN(state->output_buffer) - 1);
+ QP_ENCODE(state->output_buffer, ch);
+ }
+ VSTRING_TERMINATE(state->output_buffer);
+ BODY_OUT(state, REC_TYPE_NORM,
+ STR(state->output_buffer),
+ LEN(state->output_buffer));
+ VSTRING_RESET(state->output_buffer);
+ }
+}
+
+/* mime_state_update - update MIME state machine */
+
+int mime_state_update(MIME_STATE *state, int rec_type,
+ const char *text, ssize_t len)
+{
+ int input_is_text = (rec_type == REC_TYPE_NORM
+ || rec_type == REC_TYPE_CONT);
+ MIME_STACK *sp;
+ const HEADER_OPTS *header_info;
+ const unsigned char *cp;
+
+#define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \
+ state->prev_rec_type = rec_type; \
+ return (state->err_flags); \
+ } while (0)
+
+ /*
+ * Be sure to flush any partial output line that might still be buffered
+ * up before taking any other "end of input" actions.
+ */
+ if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT)
+ mime_state_update(state, REC_TYPE_NORM, "", 0);
+
+ /*
+ * This message state machine is kept simple for the sake of robustness.
+ * Standards evolve over time, and we want to be able to correctly
+ * process messages that are not yet defined. This state machine knows
+ * about headers and bodies, understands that multipart/whatever has
+ * multiple body parts with a header and body, and that message/whatever
+ * has message headers at the start of a body part.
+ */
+ switch (state->curr_state) {
+
+ /*
+ * First, deal with header information that we have accumulated from
+ * previous input records. Discard text that does not fit in a header
+ * buffer. Our limit is quite generous; Sendmail will refuse mail
+ * with only 32kbyte in all the message headers combined.
+ */
+ case MIME_STATE_PRIMARY:
+ case MIME_STATE_MULTIPART:
+ case MIME_STATE_NESTED:
+ if (LEN(state->output_buffer) > 0) {
+ if (input_is_text) {
+ if (state->prev_rec_type == REC_TYPE_CONT) {
+ if (LEN(state->output_buffer) < var_header_limit) {
+ vstring_strncat(state->output_buffer, text, len);
+ } else {
+ if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
+ REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
+ state->output_buffer);
+ }
+ SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+ }
+ if (IS_SPACE_TAB(*text)) {
+ if (LEN(state->output_buffer) < var_header_limit) {
+ vstring_strcat(state->output_buffer, "\n");
+ vstring_strncat(state->output_buffer, text, len);
+ } else {
+ if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
+ REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
+ state->output_buffer);
+ }
+ SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+ }
+ }
+
+ /*
+ * The input is (the beginning of) another message header, or is
+ * not a message header, or is not even a text record. With no
+ * more input to append to this saved header, do output
+ * processing and reset the saved header buffer. Hold on to the
+ * content transfer encoding header if we have to do a 8->7
+ * transformation, because the proper information depends on the
+ * content type header: message and multipart require a domain,
+ * leaf entities have either a transformation or a domain.
+ */
+ if (LEN(state->output_buffer) > 0) {
+ header_info = header_opts_find(STR(state->output_buffer));
+ if (!(state->static_flags & MIME_OPT_DISABLE_MIME)
+ && header_info != 0) {
+ if (header_info->type == HDR_CONTENT_TYPE)
+ mime_state_content_type(state, header_info);
+ if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING)
+ mime_state_content_encoding(state, header_info);
+ }
+ if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0
+ && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) {
+ for (cp = CU_CHAR_PTR(STR(state->output_buffer));
+ cp < CU_CHAR_PTR(END(state->output_buffer)); cp++)
+ if (*cp & 0200) {
+ REPORT_ERROR_BUF(state, MIME_ERR_8BIT_IN_HEADER,
+ state->output_buffer);
+ break;
+ }
+ }
+ /* Output routine is explicitly allowed to change the data. */
+ if (header_info == 0
+ || header_info->type != HDR_CONTENT_TRANSFER_ENCODING
+ || (state->static_flags & MIME_OPT_DOWNGRADE) == 0
+ || state->curr_domain == MIME_ENC_7BIT)
+ HEAD_OUT(state, header_info, len);
+ state->prev_rec_type = 0;
+ VSTRING_RESET(state->output_buffer);
+ }
+ }
+
+ /*
+ * With past header information moved out of the way, proceed with a
+ * clean slate.
+ */
+ if (input_is_text) {
+ ssize_t header_len;
+
+ /*
+ * See if this input is (the beginning of) a message header.
+ *
+ * Normalize obsolete "name space colon" syntax to "name colon".
+ * Things would be too confusing otherwise.
+ *
+ * Don't assume that the input is null terminated.
+ */
+ if ((header_len = is_header_buf(text, len)) > 0) {
+ vstring_strncpy(state->output_buffer, text, header_len);
+ for (text += header_len, len -= header_len;
+ len > 0 && IS_SPACE_TAB(*text);
+ text++, len--)
+ /* void */ ;
+ vstring_strncat(state->output_buffer, text, len);
+ SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+ }
+ }
+
+ /*
+ * This input terminates a block of message headers. When converting
+ * 8-bit to 7-bit mail, this is the right place to emit the correct
+ * content-transfer-encoding header. With message or multipart we
+ * specify 7bit, with leaf entities we specify quoted-printable.
+ *
+ * We're not going to convert non-text data into base 64. If they send
+ * arbitrary binary data as 8-bit text, then the data is already
+ * broken beyond recovery, because the Postfix SMTP server sanitizes
+ * record boundaries, treating broken record boundaries as CRLF.
+ *
+ * Clear the output buffer, we will need it for storage of the
+ * conversion result.
+ */
+ if ((state->static_flags & MIME_OPT_DOWNGRADE)
+ && state->curr_domain != MIME_ENC_7BIT) {
+ if ((state->curr_ctype == MIME_CTYPE_MESSAGE
+ && state->curr_stype != MIME_STYPE_GLOBAL)
+ || state->curr_ctype == MIME_CTYPE_MULTIPART)
+ cp = CU_CHAR_PTR("7bit");
+ else
+ cp = CU_CHAR_PTR("quoted-printable");
+ vstring_sprintf(state->output_buffer,
+ "Content-Transfer-Encoding: %s", cp);
+ HEAD_OUT(state, (HEADER_OPTS *) 0, len);
+ VSTRING_RESET(state->output_buffer);
+ }
+
+ /*
+ * This input terminates a block of message headers. Call the
+ * optional header end routine at the end of the first header block.
+ */
+ if (state->curr_state == MIME_STATE_PRIMARY && state->head_end)
+ state->head_end(state->app_context);
+
+ /*
+ * This is the right place to check if the sender specified an
+ * appropriate identity encoding (7bit, 8bit, binary) for multipart
+ * and for message.
+ */
+ if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) {
+ if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
+ if (state->curr_stype == MIME_STYPE_PARTIAL
+ || state->curr_stype == MIME_STYPE_EXTERN_BODY) {
+ if (state->curr_domain != MIME_ENC_7BIT)
+ REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
+ mime_state_enc_name(state->curr_encoding));
+ }
+ /* EAI: message/global allows non-identity encoding. */
+ else if (state->curr_stype == MIME_STYPE_RFC822) {
+ if (state->curr_encoding != state->curr_domain)
+ REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
+ mime_state_enc_name(state->curr_encoding));
+ }
+ } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
+ if (state->curr_encoding != state->curr_domain)
+ REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
+ mime_state_enc_name(state->curr_encoding));
+ }
+ }
+
+ /*
+ * Find out if the next body starts with its own message headers. In
+ * aggressive mode, examine headers of partial and external-body
+ * messages. Otherwise, treat such headers as part of the "body". Set
+ * the proper encoding information for the multipart prolog.
+ *
+ * XXX We parse headers inside message/* content even when the encoding
+ * is invalid (encoding != domain). With base64 we won't recognize
+ * any headers, and with quoted-printable we won't recognize MIME
+ * boundary strings, but the MIME processor will still resynchronize
+ * when it runs into the higher-level boundary string at the end of
+ * the message/* content. Although we will treat some headers as body
+ * text, we will still do a better job than if we were treating the
+ * entire message/* content as body text.
+ *
+ * XXX This changes state to MIME_STATE_NESTED and then outputs a body
+ * line, so that the body offset is not properly reset.
+ *
+ * Don't assume that the input is null terminated.
+ */
+ if (input_is_text) {
+ if (len == 0) {
+ state->body_offset = 0; /* XXX */
+ if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
+ if (state->curr_stype == MIME_STYPE_RFC822)
+ SET_MIME_STATE(state, MIME_STATE_NESTED,
+ MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ else if (state->curr_stype == MIME_STYPE_GLOBAL
+ && ((state->static_flags & MIME_OPT_DOWNGRADE) == 0
+ || state->curr_domain == MIME_ENC_7BIT))
+ /* XXX EAI: inspect encoded message/global. */
+ SET_MIME_STATE(state, MIME_STATE_NESTED,
+ MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ else
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
+ SET_MIME_STATE(state, MIME_STATE_BODY,
+ MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ } else {
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ }
+ }
+
+ /*
+ * Invalid input. Force output of one blank line and jump to the
+ * body state, leaving all other state alone.
+ *
+ * We don't break legitimate mail by inserting a blank line
+ * separator between primary headers and a non-empty body. Many
+ * MTA's don't even record the presence or absence of this
+ * separator, nor does the Milter protocol pass it on to Milter
+ * applications.
+ *
+ * XXX We don't insert a blank line separator into attachments, to
+ * avoid breaking digital signatures. Postfix shall not do a
+ * worse mail delivery job than MTAs that can't even parse MIME.
+ * We switch to body state anyway, to avoid treating body text as
+ * header text, and mis-interpreting or truncating it. The code
+ * below for initial From_ lines is for educational purposes.
+ *
+ * Sites concerned about MIME evasion can use a MIME normalizer.
+ * Postfix has a different mission.
+ */
+ else {
+ if (msg_verbose)
+ msg_info("garbage in %s header",
+ state->curr_state == MIME_STATE_MULTIPART ? "multipart" :
+ state->curr_state == MIME_STATE_PRIMARY ? "primary" :
+ state->curr_state == MIME_STATE_NESTED ? "nested" :
+ "other");
+ switch (state->curr_state) {
+ case MIME_STATE_PRIMARY:
+ BODY_OUT(state, REC_TYPE_NORM, "", 0);
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ break;
+#if 0
+ case MIME_STATE_NESTED:
+ if (state->body_offset <= 1
+ && rec_type == REC_TYPE_NORM
+ && len > 7
+ && (strncmp(text + (*text == '>'), "From ", 5) == 0
+ || strncmp(text, "=46rom ", 7) == 0))
+ break;
+ /* FALLTHROUGH */
+#endif
+ default:
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ break;
+ }
+ }
+ }
+
+ /*
+ * This input is not text. Go to body state, unconditionally.
+ */
+ else {
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ }
+ /* FALLTHROUGH */
+
+ /*
+ * Body text. Look for message boundaries, and recover from missing
+ * boundary strings. Missing boundaries can happen in aggressive mode
+ * with text/rfc822-headers or with message/partial. Ignore non-space
+ * cruft after --boundary or --boundary--, because some MUAs do, and
+ * because only perverse software would take advantage of this to
+ * escape detection. We have to ignore trailing cruft anyway, because
+ * our saved copy of the boundary string may have been truncated for
+ * safety reasons.
+ *
+ * Optionally look for 8-bit data in content that was announced as, or
+ * that defaults to, 7-bit. Unfortunately, we cannot turn this on by
+ * default. Majordomo sends requests for approval that do not
+ * propagate the MIME information from the enclosed message to the
+ * message headers of the approval request.
+ *
+ * Set the proper state information after processing a message boundary
+ * string.
+ *
+ * Don't look for boundary strings at the start of a continued record.
+ *
+ * Don't assume that the input is null terminated.
+ */
+ case MIME_STATE_BODY:
+ if (input_is_text) {
+ if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0
+ && state->curr_encoding == MIME_ENC_7BIT
+ && (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) {
+ for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++)
+ if (*cp & 0200) {
+ REPORT_ERROR_LEN(state, MIME_ERR_8BIT_IN_7BIT_BODY,
+ text, len);
+ break;
+ }
+ }
+ if (state->stack && state->prev_rec_type != REC_TYPE_CONT
+ && len > 2 && text[0] == '-' && text[1] == '-') {
+ for (sp = state->stack; sp != 0; sp = sp->next) {
+ if (len >= 2 + sp->bound_len &&
+ strncmp(text + 2, sp->boundary, sp->bound_len) == 0) {
+ while (sp != state->stack)
+ mime_state_pop(state);
+ if (len >= 4 + sp->bound_len &&
+ strncmp(text + 2 + sp->bound_len, "--", 2) == 0) {
+ mime_state_pop(state);
+ SET_MIME_STATE(state, MIME_STATE_BODY,
+ MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ } else {
+ SET_MIME_STATE(state, MIME_STATE_MULTIPART,
+ sp->def_ctype, sp->def_stype,
+ MIME_ENC_7BIT, MIME_ENC_7BIT);
+ }
+ break;
+ }
+ }
+ }
+ /* Put last for consistency with header output routine. */
+ if ((state->static_flags & MIME_OPT_DOWNGRADE)
+ && state->curr_domain != MIME_ENC_7BIT)
+ mime_state_downgrade(state, rec_type, text, len);
+ else
+ BODY_OUT(state, rec_type, text, len);
+ }
+
+ /*
+ * The input is not a text record. Inform the application that this
+ * is the last opportunity to send any pending output.
+ */
+ else {
+ if (state->body_end)
+ state->body_end(state->app_context);
+ }
+ SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+
+ /*
+ * Oops. This can't happen.
+ */
+ default:
+ msg_panic("mime_state_update: unknown state: %d", state->curr_state);
+ }
+}
+
+ /*
+ * Mime error to (DSN, text) mapping. Order matters; more serious errors
+ * must precede less serious errors, because the error-to-text conversion
+ * can report only one error.
+ */
+static const MIME_STATE_DETAIL mime_err_detail[] = {
+ MIME_ERR_NESTING, "5.6.0", "MIME nesting exceeds safety limit",
+ MIME_ERR_TRUNC_HEADER, "5.6.0", "message header length exceeds safety limit",
+ MIME_ERR_8BIT_IN_HEADER, "5.6.0", "improper use of 8-bit data in message header",
+ MIME_ERR_8BIT_IN_7BIT_BODY, "5.6.0", "improper use of 8-bit data in message body",
+ MIME_ERR_ENCODING_DOMAIN, "5.6.0", "invalid message/* or multipart/* encoding domain",
+ 0,
+};
+
+/* mime_state_error - error code to string */
+
+const char *mime_state_error(int error_code)
+{
+ const MIME_STATE_DETAIL *mp;
+
+ if (error_code == 0)
+ msg_panic("mime_state_error: there is no error");
+ for (mp = mime_err_detail; mp->code; mp++)
+ if (mp->code & error_code)
+ return (mp->text);
+ msg_panic("mime_state_error: unknown error code %d", error_code);
+}
+
+/* mime_state_detail - error code to table entry with assorted data */
+
+const MIME_STATE_DETAIL *mime_state_detail(int error_code)
+{
+ const MIME_STATE_DETAIL *mp;
+
+ if (error_code == 0)
+ msg_panic("mime_state_detail: there is no error");
+ for (mp = mime_err_detail; mp->code; mp++)
+ if (mp->code & error_code)
+ return (mp);
+ msg_panic("mime_state_detail: unknown error code %d", error_code);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <rec_streamlf.h>
+
+ /*
+ * Stress test the REC_TYPE_CONT/NORM handling, but don't break header
+ * labels.
+ */
+/*#define REC_LEN 40*/
+
+#define REC_LEN 1024
+
+static void head_out(void *context, int class, const HEADER_OPTS *unused_info,
+ VSTRING *buf, off_t offset)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ vstream_fprintf(stream, "%s %ld\t|%s\n",
+ class == MIME_HDR_PRIMARY ? "MAIN" :
+ class == MIME_HDR_MULTIPART ? "MULT" :
+ class == MIME_HDR_NESTED ? "NEST" :
+ "ERROR", (long) offset, STR(buf));
+}
+
+static void head_end(void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ vstream_fprintf(stream, "HEADER END\n");
+}
+
+static void body_out(void *context, int rec_type, const char *buf, ssize_t len,
+ off_t offset)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ vstream_fprintf(stream, "BODY %c %ld\t|", rec_type, (long) offset);
+ vstream_fwrite(stream, buf, len);
+ if (rec_type == REC_TYPE_NORM)
+ VSTREAM_PUTC('\n', stream);
+}
+
+static void body_end(void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ vstream_fprintf(stream, "BODY END\n");
+}
+
+static void err_print(void *unused_context, int err_flag,
+ const char *text, ssize_t len)
+{
+ msg_warn("%s: %.*s", mime_state_error(err_flag),
+ len < 100 ? (int) len : 100, text);
+}
+
+int var_header_limit = 2000;
+int var_mime_maxdepth = 20;
+int var_mime_bound_len = 2000;
+char *var_drop_hdrs = DEF_DROP_HDRS;
+
+int main(int unused_argc, char **argv)
+{
+ int rec_type;
+ int last = 0;
+ VSTRING *buf;
+ MIME_STATE *state;
+ int err;
+
+ /*
+ * Initialize.
+ */
+#define MIME_OPTIONS \
+ (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
+ | MIME_OPT_REPORT_8BIT_IN_HEADER \
+ | MIME_OPT_REPORT_ENCODING_DOMAIN \
+ | MIME_OPT_REPORT_TRUNC_HEADER \
+ | MIME_OPT_REPORT_NESTING \
+ | MIME_OPT_DOWNGRADE)
+
+ msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
+ msg_verbose = 1;
+ buf = vstring_alloc(10);
+ state = mime_state_alloc(MIME_OPTIONS,
+ head_out, head_end,
+ body_out, body_end,
+ err_print,
+ (void *) VSTREAM_OUT);
+
+ /*
+ * Main loop.
+ */
+ do {
+ rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
+ VSTRING_TERMINATE(buf);
+ err = mime_state_update(state, last = rec_type, STR(buf), LEN(buf));
+ vstream_fflush(VSTREAM_OUT);
+ } while (rec_type > 0);
+
+ /*
+ * Error reporting.
+ */
+ if (err & MIME_ERR_TRUNC_HEADER)
+ msg_warn("message header length exceeds safety limit");
+ if (err & MIME_ERR_NESTING)
+ msg_warn("MIME nesting exceeds safety limit");
+ if (err & MIME_ERR_8BIT_IN_HEADER)
+ msg_warn("improper use of 8-bit data in message header");
+ if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
+ msg_warn("improper use of 8-bit data in message body");
+ if (err & MIME_ERR_ENCODING_DOMAIN)
+ msg_warn("improper message/* or multipart/* encoding domain");
+
+ /*
+ * Cleanup.
+ */
+ mime_state_free(state);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/mime_state.h b/src/global/mime_state.h
new file mode 100644
index 0000000..88a4d7c
--- /dev/null
+++ b/src/global/mime_state.h
@@ -0,0 +1,96 @@
+#ifndef _MIME_STATE_H_INCLUDED_
+#define _MIME_STATE_H_INCLUDED_
+
+/*++
+/* NAME
+/* mime_state 3h
+/* SUMMARY
+/* MIME parser state engine
+/* SYNOPSIS
+/* #include "mime_state.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <header_opts.h>
+
+ /*
+ * External interface. All MIME_STATE structure members are private.
+ */
+typedef struct MIME_STATE MIME_STATE;
+typedef void (*MIME_STATE_HEAD_OUT) (void *, int, const HEADER_OPTS *, VSTRING *, off_t);
+typedef void (*MIME_STATE_BODY_OUT) (void *, int, const char *, ssize_t, off_t);
+typedef void (*MIME_STATE_ANY_END) (void *);
+typedef void (*MIME_STATE_ERR_PRINT) (void *, int, const char *, ssize_t);
+
+extern MIME_STATE *mime_state_alloc(int, MIME_STATE_HEAD_OUT, MIME_STATE_ANY_END, MIME_STATE_BODY_OUT, MIME_STATE_ANY_END, MIME_STATE_ERR_PRINT, void *);
+extern int mime_state_update(MIME_STATE *, int, const char *, ssize_t);
+extern MIME_STATE *mime_state_free(MIME_STATE *);
+
+ /*
+ * Processing options.
+ */
+#define MIME_OPT_NONE (0)
+#define MIME_OPT_DOWNGRADE (1<<0)
+#define MIME_OPT_REPORT_8BIT_IN_7BIT_BODY (1<<1)
+#define MIME_OPT_REPORT_8BIT_IN_HEADER (1<<2)
+#define MIME_OPT_REPORT_ENCODING_DOMAIN (1<<3)
+#define MIME_OPT_RECURSE_ALL_MESSAGE (1<<4)
+#define MIME_OPT_REPORT_TRUNC_HEADER (1<<5)
+#define MIME_OPT_DISABLE_MIME (1<<6)
+#define MIME_OPT_REPORT_NESTING (1<<7)
+
+ /*
+ * Body encoding domains.
+ */
+#define MIME_ENC_7BIT (7)
+#define MIME_ENC_8BIT (8)
+#define MIME_ENC_BINARY (9)
+
+ /*
+ * Processing errors, not necessarily lethal.
+ */
+typedef struct {
+ const int code; /* internal error code */
+ const char *dsn; /* RFC 3463 */
+ const char *text; /* descriptive text */
+} MIME_STATE_DETAIL;
+
+#define MIME_ERR_NESTING (1<<0)
+#define MIME_ERR_TRUNC_HEADER (1<<1)
+#define MIME_ERR_8BIT_IN_HEADER (1<<2)
+#define MIME_ERR_8BIT_IN_7BIT_BODY (1<<3)
+#define MIME_ERR_ENCODING_DOMAIN (1<<4)
+
+extern const MIME_STATE_DETAIL *mime_state_detail(int);
+extern const char *mime_state_error(int);
+
+ /*
+ * With header classes, look at the header_opts argument to recognize MIME
+ * headers in primary or nested sections.
+ */
+#define MIME_HDR_FIRST (1) /* first class */
+#define MIME_HDR_PRIMARY (1) /* initial headers */
+#define MIME_HDR_MULTIPART (2) /* headers after multipart boundary */
+#define MIME_HDR_NESTED (3) /* attached message initial headers */
+#define MIME_HDR_LAST (3) /* last class */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mime_test.in b/src/global/mime_test.in
new file mode 100644
index 0000000..54bcd8d
--- /dev/null
+++ b/src/global/mime_test.in
@@ -0,0 +1,38 @@
+subject: primary subject
+content-type : multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd
+ ef" mumble
+
+abcdef prolog
+
+--abcd ef
+content-type: message/rfc822; mumble
+
+subject: nested subject
+content-type: multipart/mumble; boundary(comment)="pqrs"
+content-transfer-encoding: base64
+
+pqrs prolog
+
+--pqrs
+header: pqrs part 01
+
+body pqrs part 01
+
+--pqrs
+header: pqrs part 02
+
+body pqrs part 02
+
+--bogus-boundary
+header: wietse
+
+body asdasads
+
+--abcd ef
+header: abcdef part 02
+
+body abcdef part 02
+
+--abcd ef--
+
+epilog
diff --git a/src/global/mime_test.ref b/src/global/mime_test.ref
new file mode 100644
index 0000000..3ffdb7b
--- /dev/null
+++ b/src/global/mime_test.ref
@@ -0,0 +1,52 @@
+MAIN 0 |subject: primary subject
+mime_state: header_token: multipart / mumble
+mime_state: header_token: boundary = abcd ef
+mime_state: PUSH boundary abcd ef
+MAIN 71 |content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd
+ ef" mumble
+HEADER END
+BODY N 0 |
+BODY N 1 |abcdef prolog
+BODY N 15 |
+BODY N 16 |--abcd ef
+mime_state: header_token: message / rfc822
+MULT 0 |content-type: message/rfc822; mumble
+BODY N 0 |
+NEST 0 |subject: nested subject
+mime_state: header_token: multipart / mumble
+mime_state: header_token: boundary = pqrs
+mime_state: PUSH boundary pqrs
+NEST 57 |content-type: multipart/mumble; boundary(comment)="pqrs"
+mime_state: header_token: base64
+NEST 91 |content-transfer-encoding: base64
+mime_state: warning: invalid message/* or multipart/* encoding domain: base64
+BODY N 0 |
+BODY N 1 |pqrs prolog
+BODY N 13 |
+BODY N 14 |--pqrs
+MULT 0 |header: pqrs part 01
+BODY N 0 |
+BODY N 1 |body pqrs part 01
+BODY N 19 |
+BODY N 20 |--pqrs
+MULT 0 |header: pqrs part 02
+BODY N 0 |
+BODY N 1 |body pqrs part 02
+BODY N 19 |
+BODY N 20 |--bogus-boundary
+BODY N 37 |header: wietse
+BODY N 52 |
+BODY N 53 |body asdasads
+BODY N 67 |
+mime_state: POP boundary pqrs
+BODY N 68 |--abcd ef
+MULT 0 |header: abcdef part 02
+BODY N 0 |
+BODY N 1 |body abcdef part 02
+BODY N 21 |
+mime_state: POP boundary abcd ef
+BODY N 0 |--abcd ef--
+BODY N 12 |
+BODY N 13 |epilog
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
diff --git a/src/global/mime_trunc.in b/src/global/mime_trunc.in
new file mode 100644
index 0000000..2d66f75
--- /dev/null
+++ b/src/global/mime_trunc.in
@@ -0,0 +1,1790 @@
+Header:
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
diff --git a/src/global/mime_trunc.ref b/src/global/mime_trunc.ref
new file mode 100644
index 0000000..cd6bb52
--- /dev/null
+++ b/src/global/mime_trunc.ref
@@ -0,0 +1,32 @@
+mime_state: warning: message header length exceeds safety limit: Header: ??garbage garbage garbage garbage garbage garbage garbage garbage garbage ??garbage garbage
+MAIN 0 |Header:
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+ garbage garbage garbage garbage garbage garbage garbage garbage garbage
+HEADER END
+BODY END
+mime_state: warning: message header length exceeds safety limit
diff --git a/src/global/mkmap.h b/src/global/mkmap.h
new file mode 100644
index 0000000..e493114
--- /dev/null
+++ b/src/global/mkmap.h
@@ -0,0 +1,64 @@
+#ifndef _MKMAP_H_INCLUDED_
+#define _MKMAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* mkmap 3h
+/* SUMMARY
+/* create or rewrite Postfix database
+/* SYNOPSIS
+/* #include <mkmap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * A database handle is an opaque structure. The user is not supposed to
+ * know its implementation. We try to open and lock a file before DB/DBM
+ * initialization. However, if the file does not exist then we may have to
+ * acquire the lock after the DB/DBM initialization.
+ */
+typedef struct MKMAP {
+ DICT_OPEN_FN open; /* dict_xx_open() */
+ struct DICT *dict; /* dict_xx_open() result */
+ void (*after_open) (struct MKMAP *); /* may be null */
+ void (*after_close) (struct MKMAP *); /* may be null */
+ int multi_writer; /* multi-writer safe */
+} MKMAP;
+
+extern MKMAP *mkmap_open(const char *, const char *, int, int);
+extern void mkmap_append(MKMAP *, const char *, const char *);
+extern void mkmap_close(MKMAP *);
+
+#define mkmap_append(map, key, val) dict_put((map)->dict, (key), (val))
+
+extern MKMAP *mkmap_dbm_open(const char *);
+extern MKMAP *mkmap_cdb_open(const char *);
+extern MKMAP *mkmap_hash_open(const char *);
+extern MKMAP *mkmap_btree_open(const char *);
+extern MKMAP *mkmap_lmdb_open(const char *);
+extern MKMAP *mkmap_sdbm_open(const char *);
+extern MKMAP *mkmap_proxy_open(const char *);
+extern MKMAP *mkmap_fail_open(const char *);
+
+typedef MKMAP *(*MKMAP_OPEN_FN) (const char *);
+typedef MKMAP_OPEN_FN (*MKMAP_OPEN_EXTEND_FN) (const char *);
+extern void mkmap_open_register(const char *, MKMAP_OPEN_FN);
+extern MKMAP_OPEN_EXTEND_FN mkmap_open_extend(MKMAP_OPEN_EXTEND_FN);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mkmap_cdb.c b/src/global/mkmap_cdb.c
new file mode 100644
index 0000000..83bf96d
--- /dev/null
+++ b/src/global/mkmap_cdb.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* mkmap_cdb 3
+/* SUMMARY
+/* create or open database, CDB style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_cdb_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for creating DJB's CDB "constant
+/* databases".
+/*
+/* mkmap_cdb_open() take a file name, append the ".cdb.tmp" suffix,
+/* create the named DB database. On close, this file renamed to
+/* file name with ".cdb" suffix appended (without ".tmp" part).
+/* This routine is a CDB-specific helper for the more
+/* general mkmap_open() interface.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_cdb(3), CDB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Written by Michael Tokarev <mjt@tls.msk.ru> based on mkmap_db by
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef HAS_CDB
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+#include <dict_cdb.h>
+
+/* This is a dummy module, since CDB has all the functionality
+ * built-in, as cdb creation requires one global lock anyway. */
+
+MKMAP *mkmap_cdb_open(const char *unused_path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+ mkmap->open = dict_cdb_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+ return (mkmap);
+}
+
+#endif /* HAS_CDB */
diff --git a/src/global/mkmap_db.c b/src/global/mkmap_db.c
new file mode 100644
index 0000000..d2c87ef
--- /dev/null
+++ b/src/global/mkmap_db.c
@@ -0,0 +1,191 @@
+/*++
+/* NAME
+/* mkmap_db 3
+/* SUMMARY
+/* create or open database, DB style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_hash_open(path)
+/* const char *path;
+/*
+/* MKMAP *mkmap_btree_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating DB databases.
+/*
+/* mkmap_hash_open() and mkmap_btree_open() take a file name,
+/* append the ".db" suffix, and do whatever initialization is
+/* required before the Berkeley DB open routine is called.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_db(3), DB dictionary interface.
+/* 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>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_db.h>
+#include <myflock.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+#ifdef HAS_DB
+#ifdef PATH_DB_H
+#include PATH_DB_H
+#else
+#include <db.h>
+#endif
+
+typedef struct MKMAP_DB {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_DB;
+
+/* mkmap_db_after_close - clean up after closing database */
+
+static void mkmap_db_after_close(MKMAP *mp)
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_db_after_open - lock newly created database */
+
+static void mkmap_db_after_open(MKMAP *mp)
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mp;
+
+ if (mkmap->lock_fd < 0) {
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0)
+ msg_fatal("open lockfile %s: %m", mkmap->lock_file);
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+ }
+}
+
+/* mkmap_db_before_open - lock existing database */
+
+static MKMAP *mkmap_db_before_open(const char *path,
+ DICT *(*db_open) (const char *, int, int))
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mymalloc(sizeof(*mkmap));
+ struct stat st;
+
+ /*
+ * Override the default per-table cache size for map (re)builds.
+ *
+ * db_cache_size" is defined in util/dict_db.c and defaults to 128kB, which
+ * works well for the lookup code.
+ *
+ * We use a larger per-table cache when building ".db" files. For "hash"
+ * files performance degrades rapidly unless the memory pool is O(file
+ * size).
+ *
+ * For "btree" files performance is good with sorted input even for small
+ * memory pools, but with random input degrades rapidly unless the memory
+ * pool is O(file size).
+ *
+ * XXX This should be specified via the DICT interface so that the buffer
+ * size becomes an object property, instead of being specified by poking
+ * a global variable so that it becomes a class property.
+ */
+ dict_db_cache_size = var_db_create_buf;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->lock_file = concatenate(path, ".db", (char *) 0);
+ mkmap->mkmap.open = db_open;
+ mkmap->mkmap.after_open = mkmap_db_after_open;
+ mkmap->mkmap.after_close = mkmap_db_after_close;
+
+ /*
+ * Unfortunately, not all systems that might support db databases do
+ * support locking on open(), so we open the file before updating it.
+ *
+ * XXX Berkeley DB 4.1 refuses to open a zero-length file. This means we can
+ * open and lock only an existing file, and that we must not truncate it.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+ }
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ *
+ * XXX Horror. Berkeley DB 4.1 refuses to open a zero-length file. This
+ * means that we must examine the size while the file is locked, and that
+ * we must unlink a zero-length file while it is locked. Avoid a race
+ * condition where two processes try to open the same zero-length file
+ * and where the second process ends up deleting the wrong file.
+ */
+ else {
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+ if (fstat(mkmap->lock_fd, &st) < 0)
+ msg_fatal("fstat %s: %m", mkmap->lock_file);
+ if (st.st_size == 0) {
+ if (st.st_nlink > 0) {
+ if (unlink(mkmap->lock_file) < 0)
+ msg_fatal("cannot remove zero-length database file %s: %m",
+ mkmap->lock_file);
+ msg_warn("removing zero-length database file: %s",
+ mkmap->lock_file);
+ }
+ close(mkmap->lock_fd);
+ mkmap->lock_fd = -1;
+ }
+ }
+
+ return (&mkmap->mkmap);
+}
+
+/* mkmap_hash_open - create or open hashed DB file */
+
+MKMAP *mkmap_hash_open(const char *path)
+{
+ return (mkmap_db_before_open(path, dict_hash_open));
+}
+
+/* mkmap_btree_open - create or open btree DB file */
+
+MKMAP *mkmap_btree_open(const char *path)
+{
+ return (mkmap_db_before_open(path, dict_btree_open));
+}
+
+#endif
diff --git a/src/global/mkmap_dbm.c b/src/global/mkmap_dbm.c
new file mode 100644
index 0000000..1db588b
--- /dev/null
+++ b/src/global/mkmap_dbm.c
@@ -0,0 +1,116 @@
+/*++
+/* NAME
+/* mkmap_dbm 3
+/* SUMMARY
+/* create or open database, DBM style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_dbm_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating DBM databases.
+/*
+/* mkmap_dbm_open() takes a file name, appends the ".dir" and ".pag"
+/* suffixes, and creates or opens the named DBM database.
+/* This routine is a DBM-specific helper for the more general
+/* mkmap_open() routine.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_dbm(3), DBM dictionary interface.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_dbm.h>
+#include <myflock.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+#ifdef HAS_DBM
+#ifdef PATH_NDBM_H
+#include PATH_NDBM_H
+#else
+#include <ndbm.h>
+#endif
+
+typedef struct MKMAP_DBM {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_DBM;
+
+/* mkmap_dbm_after_close - clean up after closing database */
+
+static void mkmap_dbm_after_close(MKMAP *mp)
+{
+ MKMAP_DBM *mkmap = (MKMAP_DBM *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_dbm_open - create or open database */
+
+MKMAP *mkmap_dbm_open(const char *path)
+{
+ MKMAP_DBM *mkmap = (MKMAP_DBM *) mymalloc(sizeof(*mkmap));
+ char *pag_file;
+ int pag_fd;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->lock_file = concatenate(path, ".dir", (char *) 0);
+ mkmap->mkmap.open = dict_dbm_open;
+ mkmap->mkmap.after_open = 0;
+ mkmap->mkmap.after_close = mkmap_dbm_after_close;
+
+ /*
+ * Unfortunately, not all systems support locking on open(), so we open
+ * the .dir and .pag files before truncating them. Keep one file open for
+ * locking.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+
+ pag_file = concatenate(path, ".pag", (char *) 0);
+ if ((pag_fd = open(pag_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", pag_file);
+ if (close(pag_fd))
+ msg_warn("close %s: %m", pag_file);
+ myfree(pag_file);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ */
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+
+ return (&mkmap->mkmap);
+}
+
+#endif
diff --git a/src/global/mkmap_fail.c b/src/global/mkmap_fail.c
new file mode 100644
index 0000000..c35fe50
--- /dev/null
+++ b/src/global/mkmap_fail.c
@@ -0,0 +1,53 @@
+/*++
+/* NAME
+/* mkmap_fail 3
+/* SUMMARY
+/* create or open database, fail: style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_fail_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for error testing postmap
+/* and postalias with the fail: table type.
+/* SEE ALSO
+/* dict_fail(3), fail dictionary interface.
+/* 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 <mymalloc.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+#include <mkmap.h>
+#include <dict_fail.h>
+
+ /*
+ * Dummy module: the dict_fail module has all the functionality built-in.
+ */
+MKMAP *mkmap_fail_open(const char *unused_path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+
+ mkmap->open = dict_fail_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+ return (mkmap);
+}
diff --git a/src/global/mkmap_lmdb.c b/src/global/mkmap_lmdb.c
new file mode 100644
index 0000000..9aebd25
--- /dev/null
+++ b/src/global/mkmap_lmdb.c
@@ -0,0 +1,84 @@
+/*++
+/* NAME
+/* mkmap_lmdb 3
+/* SUMMARY
+/* create or open database, LMDB style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_lmdb_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for creating LMDB databases.
+/*
+/* mkmap_lmdb_open() takes a file name, appends the ".lmdb"
+/* suffix, and does whatever initialization is required
+/* before the OpenLDAP LMDB open routine is called.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_lmdb(3), LMDB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_lmdb.h>
+#include <myflock.h>
+#include <warn_stat.h>
+
+#ifdef HAS_LMDB
+#ifdef PATH_LMDB_H
+#include PATH_LMDB_H
+#else
+#include <lmdb.h>
+#endif
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+/* mkmap_lmdb_open */
+
+MKMAP *mkmap_lmdb_open(const char *path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->open = dict_lmdb_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+
+ /*
+ * LMDB uses MVCC so it needs no special lock management here.
+ */
+
+ return (mkmap);
+}
+
+#endif
diff --git a/src/global/mkmap_open.c b/src/global/mkmap_open.c
new file mode 100644
index 0000000..9d15eec
--- /dev/null
+++ b/src/global/mkmap_open.c
@@ -0,0 +1,313 @@
+/*++
+/* NAME
+/* mkmap_open 3
+/* SUMMARY
+/* create or rewrite database, generic interface
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* typedef struct MKMAP {
+/* DICT_OPEN_FN open; /* dict_xx_open() */
+/* DICT *dict; /* dict_xx_open() result */
+/* void (*after_open) (struct MKMAP *); /* may be null */
+/* void (*after_close) (struct MKMAP *); /* may be null */
+/* int multi_writer; /* multi-writer safe */
+/* } MKMAP;
+/*
+/* MKMAP *mkmap_open(type, path, open_flags, dict_flags)
+/* char *type;
+/* char *path;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* void mkmap_append(mkmap, key, value, lineno)
+/* MKMAP *mkmap;
+/* char *key;
+/* char *value;
+/* int lineno;
+/*
+/* void mkmap_close(mkmap)
+/* MKMAP *mkmap;
+/*
+/* typedef MKMAP *(*MKMAP_OPEN_FN) (const char *);
+/* typedef MKMAP_OPEN_FN *(*MKMAP_OPEN_EXTEND_FN) (const char *);
+/*
+/* void mkmap_open_register(type, open_fn)
+/* const char *type;
+/* MKMAP_OPEN_FN open_fn;
+/*
+/* MKMAP_OPEN_EXTEND_FN mkmap_open_extend(call_back)
+/* MKMAP_OPEN_EXTEND_FN call_back;
+/* DESCRIPTION
+/* This module implements support for creating Postfix databases.
+/* It is a dict(3) wrapper that adds global locking to dict-level
+/* routines where appropriate.
+/*
+/* mkmap_open() creates or truncates the named database, after
+/* appending the appropriate suffixes to the specified filename.
+/* Before the database is updated, it is locked for exclusive
+/* access, and signal delivery is suspended.
+/* See dict(3) for a description of \fBopen_flags\fR and
+/* \fBdict_flags\fR. All errors are fatal.
+/*
+/* mkmap_append() appends the named (key, value) pair to the
+/* database. Update errors are fatal; duplicate keys are ignored
+/* (but a warning is issued).
+/* \fBlineno\fR is used for diagnostics.
+/*
+/* mkmap_close() closes the database, releases any locks,
+/* and resumes signal delivery. All errors are fatal.
+/*
+/* mkmap_open_register() adds support for a new database type.
+/*
+/* mkmap_open_extend() registers a call-back function that looks
+/* up the mkmap open() function for a database type that is not
+/* registered, or null in case of error. The result value is the
+/* last previously-registered call-back or null. A mkmap open()
+/* function is cached after it is looked up through this extension
+/* mechanism.
+/* SEE ALSO
+/* sigdelay(3) suspend/resume signal delivery
+/* 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 <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <dict.h>
+#include <dict_db.h>
+#include <dict_cdb.h>
+#include <dict_dbm.h>
+#include <dict_lmdb.h>
+#include <dict_sdbm.h>
+#include <dict_proxy.h>
+#include <dict_fail.h>
+#include <sigdelay.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "mkmap.h"
+
+ /*
+ * Information about available database types. Here, we list only those map
+ * types that support "bulk create" operations.
+ *
+ * We use a different table (in dict_open.c and mail_dict.c) when querying maps
+ * or when making incremental updates.
+ */
+typedef struct {
+ const char *type;
+ MKMAP_OPEN_FN before_open;
+} MKMAP_OPEN_INFO;
+
+static const MKMAP_OPEN_INFO mkmap_open_info[] = {
+#ifndef USE_DYNAMIC_MAPS
+#ifdef HAS_CDB
+ DICT_TYPE_CDB, mkmap_cdb_open,
+#endif
+#ifdef HAS_SDBM
+ DICT_TYPE_SDBM, mkmap_sdbm_open,
+#endif
+#ifdef HAS_LMDB
+ DICT_TYPE_LMDB, mkmap_lmdb_open,
+#endif
+#endif /* !USE_DYNAMIC_MAPS */
+#ifdef HAS_DBM
+ DICT_TYPE_DBM, mkmap_dbm_open,
+#endif
+#ifdef HAS_DB
+ DICT_TYPE_HASH, mkmap_hash_open,
+ DICT_TYPE_BTREE, mkmap_btree_open,
+#endif
+ DICT_TYPE_FAIL, mkmap_fail_open,
+ 0,
+};
+
+static HTABLE *mkmap_open_hash;
+
+static MKMAP_OPEN_EXTEND_FN mkmap_open_extend_hook = 0;
+
+/* mkmap_open_init - one-off initialization */
+
+static void mkmap_open_init(void)
+{
+ static const char myname[] = "mkmap_open_init";
+ const MKMAP_OPEN_INFO *mp;
+
+ if (mkmap_open_hash != 0)
+ msg_panic("%s: multiple initialization", myname);
+ mkmap_open_hash = htable_create(10);
+
+ for (mp = mkmap_open_info; mp->type; mp++)
+ htable_enter(mkmap_open_hash, mp->type, (void *) mp);
+}
+
+/* mkmap_open_register - register dictionary type */
+
+void mkmap_open_register(const char *type, MKMAP_OPEN_FN open_fn)
+{
+ static const char myname[] = "mkmap_open_register";
+ MKMAP_OPEN_INFO *mp;
+ HTABLE_INFO *ht;
+
+ if (mkmap_open_hash == 0)
+ mkmap_open_init();
+ if (htable_find(mkmap_open_hash, type))
+ msg_panic("%s: database type exists: %s", myname, type);
+ mp = (MKMAP_OPEN_INFO *) mymalloc(sizeof(*mp));
+ mp->before_open = open_fn;
+ ht = htable_enter(mkmap_open_hash, type, (void *) mp);
+ mp->type = ht->key;
+}
+
+/* mkmap_open_extend - register alternate lookup function */
+
+MKMAP_OPEN_EXTEND_FN mkmap_open_extend(MKMAP_OPEN_EXTEND_FN new_cb)
+{
+ MKMAP_OPEN_EXTEND_FN old_cb;
+
+ old_cb = mkmap_open_extend_hook;
+ mkmap_open_extend_hook = new_cb;
+ return (old_cb);
+}
+
+/* mkmap_append - append entry to map */
+
+#undef mkmap_append
+
+void mkmap_append(MKMAP *mkmap, const char *key, const char *value)
+{
+ DICT *dict = mkmap->dict;
+
+ if (dict_put(dict, key, value) != 0 && dict->error != 0)
+ msg_fatal("%s:%s: update failed", dict->type, dict->name);
+}
+
+/* mkmap_close - close database */
+
+void mkmap_close(MKMAP *mkmap)
+{
+
+ /*
+ * Close the database.
+ */
+ dict_close(mkmap->dict);
+
+ /*
+ * Do whatever special processing is needed after closing the database,
+ * such as releasing a global exclusive lock on the database file.
+ * Individual Postfix dict modules implement locking only for individual
+ * record operations, because most Postfix applications don't need global
+ * exclusive locks.
+ */
+ if (mkmap->after_close)
+ mkmap->after_close(mkmap);
+
+ /*
+ * Resume signal delivery.
+ */
+ if (mkmap->multi_writer == 0)
+ sigresume();
+
+ /*
+ * Cleanup.
+ */
+ myfree((void *) mkmap);
+}
+
+/* mkmap_open - create or truncate database */
+
+MKMAP *mkmap_open(const char *type, const char *path,
+ int open_flags, int dict_flags)
+{
+ MKMAP *mkmap;
+ const MKMAP_OPEN_INFO *mp;
+ MKMAP_OPEN_FN open_fn;
+
+ /*
+ * Find out what map type to use.
+ */
+ if (mkmap_open_hash == 0)
+ mkmap_open_init();
+ if ((mp = (MKMAP_OPEN_INFO *) htable_find(mkmap_open_hash, type)) == 0) {
+ if (mkmap_open_extend_hook != 0 &&
+ (open_fn = mkmap_open_extend_hook(type)) != 0) {
+ mkmap_open_register(type, open_fn);
+ mp = (MKMAP_OPEN_INFO *) htable_find(mkmap_open_hash, type);
+ }
+ if (mp == 0)
+ msg_fatal("unsupported map type for this operation: %s", type);
+ }
+ if (msg_verbose)
+ msg_info("open %s %s", type, path);
+
+ /*
+ * Do whatever before-open initialization is needed, such as acquiring a
+ * global exclusive lock on an existing database file. Individual Postfix
+ * dict modules implement locking only for individual record operations,
+ * because most Postfix applications don't need global exclusive locks.
+ */
+ mkmap = mp->before_open(path);
+
+ /*
+ * Delay signal delivery, so that we won't leave the database in an
+ * inconsistent state if we can avoid it.
+ */
+ sigdelay();
+
+ /*
+ * Truncate the database upon open, and update it. Read-write mode is
+ * needed because the underlying routines read as well as write. We
+ * explicitly clobber lock_fd to trigger a fatal error when a map wants
+ * to unlock the database after individual transactions: that would
+ * result in race condition problems. We clobbber stat_fd as well,
+ * because that, too, is used only for individual-transaction clients.
+ */
+ mkmap->dict = mkmap->open(path, open_flags, dict_flags);
+ mkmap->dict->lock_fd = -1; /* XXX just in case */
+ mkmap->dict->stat_fd = -1; /* XXX just in case */
+ mkmap->dict->flags |= DICT_FLAG_DUP_WARN;
+ mkmap->multi_writer = (mkmap->dict->flags & DICT_FLAG_MULTI_WRITER);
+
+ /*
+ * Do whatever post-open initialization is needed, such as acquiring a
+ * global exclusive lock on a database file that did not exist.
+ * Individual Postfix dict modules implement locking only for individual
+ * record operations, because most Postfix applications don't need global
+ * exclusive locks.
+ */
+ if (mkmap->after_open)
+ mkmap->after_open(mkmap);
+
+ /*
+ * Wrap the dictionary for UTF-8 syntax checks and casefolding.
+ */
+ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags))
+ mkmap->dict = dict_utf8_activate(mkmap->dict);
+
+ /*
+ * Resume signal delivery if multi-writer safe.
+ */
+ if (mkmap->multi_writer)
+ sigresume();
+
+ return (mkmap);
+}
diff --git a/src/global/mkmap_proxy.c b/src/global/mkmap_proxy.c
new file mode 100644
index 0000000..e4f4f34
--- /dev/null
+++ b/src/global/mkmap_proxy.c
@@ -0,0 +1,58 @@
+/*++
+/* NAME
+/* mkmap_proxy 3
+/* SUMMARY
+/* create or proxied database
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_proxy_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for updating proxy databases.
+/*
+/* mkmap_proxy_open() is a proxymap-specific helper for the
+/* more general mkmap_open() routine.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_proxy(3), proxy client interface.
+/* 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 <mymalloc.h>
+#include <dict_proxy.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+/* mkmap_proxy_open - create or open database */
+
+MKMAP *mkmap_proxy_open(const char *unused_path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->open = dict_proxy_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+
+ return (mkmap);
+}
diff --git a/src/global/mkmap_sdbm.c b/src/global/mkmap_sdbm.c
new file mode 100644
index 0000000..7e87e4e
--- /dev/null
+++ b/src/global/mkmap_sdbm.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* mkmap_sdbm 3
+/* SUMMARY
+/* create or open database, SDBM style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_sdbm_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating SDBM databases.
+/*
+/* mkmap_sdbm_open() takes a file name, appends the ".dir" and ".pag"
+/* suffixes, and creates or opens the named SDBM database.
+/* This routine is a SDBM-specific helper for the more general
+/* mkmap_open() routine.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_sdbm(3), SDBM dictionary interface.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_sdbm.h>
+#include <myflock.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+#ifdef HAS_SDBM
+
+#include <sdbm.h>
+
+typedef struct MKMAP_SDBM {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_SDBM;
+
+/* mkmap_sdbm_after_close - clean up after closing database */
+
+static void mkmap_sdbm_after_close(MKMAP *mp)
+{
+ MKMAP_SDBM *mkmap = (MKMAP_SDBM *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_sdbm_open - create or open database */
+
+MKMAP *mkmap_sdbm_open(const char *path)
+{
+ MKMAP_SDBM *mkmap = (MKMAP_SDBM *) mymalloc(sizeof(*mkmap));
+ char *pag_file;
+ int pag_fd;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->lock_file = concatenate(path, ".dir", (char *) 0);
+ mkmap->mkmap.open = dict_sdbm_open;
+ mkmap->mkmap.after_open = 0;
+ mkmap->mkmap.after_close = mkmap_sdbm_after_close;
+
+ /*
+ * Unfortunately, not all systems support locking on open(), so we open
+ * the .dir and .pag files before truncating them. Keep one file open for
+ * locking.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+
+ pag_file = concatenate(path, ".pag", (char *) 0);
+ if ((pag_fd = open(pag_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", pag_file);
+ if (close(pag_fd))
+ msg_warn("close %s: %m", pag_file);
+ myfree(pag_file);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ */
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+
+ return (&mkmap->mkmap);
+}
+
+#endif
diff --git a/src/global/msg_stats.h b/src/global/msg_stats.h
new file mode 100644
index 0000000..c2ab364
--- /dev/null
+++ b/src/global/msg_stats.h
@@ -0,0 +1,104 @@
+#ifndef _MSG_STATS_H_INCLUDED_
+#define _MSG_STATS_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_stats 3h
+/* SUMMARY
+/* message delivery profiling
+/* SYNOPSIS
+/* #include <msg_stats.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <time.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <attr.h>
+#include <vstream.h>
+
+ /*
+ * External interface.
+ *
+ * This structure contains the time stamps from various mail delivery stages,
+ * as well as the connection reuse count. The time stamps provide additional
+ * insight into the nature of performance bottle necks.
+ *
+ * For convenience, we record absolute time stamps instead of time differences.
+ * This is because the decision of what numbers to subtract actually depends
+ * on program history. Since we prefer to compute time differences in one
+ * place, we postpone this task until the end, in log_adhoc().
+ *
+ * A zero time stamp or reuse count means the information is not supplied.
+ *
+ * Specifically, a zero active_arrival value means that the message did not
+ * reach the queue manager; and a zero agent_handoff time means that the
+ * queue manager did not give the message to a delivery agent.
+ *
+ * Some network clients update the conn_setup_done value when connection setup
+ * fails or completes.
+ *
+ * The deliver_done value is usually left at zero, which means use the wall
+ * clock time when reporting recipient status information. The exception is
+ * with delivery agents that can deliver multiple recipients in a single
+ * transaction. These agents explicitly update the deliver_done time stamp
+ * to ensure that multiple recipient records show the exact same delay
+ * values.
+ */
+typedef struct {
+ struct timeval incoming_arrival; /* incoming queue entry */
+ struct timeval active_arrival; /* active queue entry */
+ struct timeval agent_handoff; /* delivery agent hand-off */
+ struct timeval conn_setup_done; /* connection set-up done */
+ struct timeval deliver_done; /* transmission done */
+ int reuse_count; /* connection reuse count */
+} MSG_STATS;
+
+#define MSG_STATS_INIT(st) \
+ ( \
+ memset((char *) (st), 0, sizeof(*(st))), \
+ (st) \
+ )
+
+#define MSG_STATS_INIT1(st, member, value) \
+ ( \
+ memset((char *) (st), 0, sizeof(*(st))), \
+ ((st)->member = (value)), \
+ (st) \
+ )
+
+#define MSG_STATS_INIT2(st, m1, v1, m2, v2) \
+ ( \
+ memset((char *) (st), 0, sizeof(*(st))), \
+ ((st)->m1 = (v1)), \
+ ((st)->m2 = (v2)), \
+ (st) \
+ )
+
+extern int msg_stats_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern int msg_stats_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/msg_stats_print.c b/src/global/msg_stats_print.c
new file mode 100644
index 0000000..6fe667a
--- /dev/null
+++ b/src/global/msg_stats_print.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* msg_stats_print
+/* SUMMARY
+/* write MSG_STATS structure to stream
+/* SYNOPSIS
+/* #include <msg_stats.h>
+/*
+/* int msg_stats_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* msg_stats_print() writes an MSG_STATS structure to the named
+/* stream using the specified attribute print routine.
+/* msg_stats_print() is meant to be passed as a call-back to
+/* attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(msg_stats_print, (const void *) stats), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <msg_stats.h>
+
+/* msg_stats_print - write MSG_STATS to stream */
+
+int msg_stats_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ int ret;
+
+ /*
+ * Send the entire structure. This is not only simpler but also likely to
+ * be quicker than having the sender figure out what fields need to be
+ * sent, converting numbers to string and back, and having the receiver
+ * initialize the unused fields by hand.
+ */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_DATA(MAIL_ATTR_TIME, sizeof(MSG_STATS), ptr),
+ ATTR_TYPE_END);
+ return (ret);
+}
diff --git a/src/global/msg_stats_scan.c b/src/global/msg_stats_scan.c
new file mode 100644
index 0000000..504a6b0
--- /dev/null
+++ b/src/global/msg_stats_scan.c
@@ -0,0 +1,91 @@
+/*++
+/* NAME
+/* msg_stats_scan
+/* SUMMARY
+/* read MSG_STATS from stream
+/* SYNOPSIS
+/* #include <msg_stats.h>
+/*
+/* int msg_stats_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* msg_stats_scan() reads an MSG_STATS from the named stream
+/* using the specified attribute scan routine. msg_stats_scan()
+/* is meant to be passed as a call-back to attr_scan(), thusly:
+/*
+/* ... RECV_ATTR_FUNC(msg_stats_scan, (void *) &stats), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+#include <vstring.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <msg_stats.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* msg_stats_scan - read MSG_STATS from stream */
+
+int msg_stats_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ MSG_STATS *stats = (MSG_STATS *) ptr;
+ VSTRING *buf = vstring_alloc(sizeof(MSG_STATS) * 2);
+ int ret;
+
+ /*
+ * Receive the entire structure. This is not only simpler but also likely
+ * to be quicker than having the sender figure out what fields need to be
+ * sent, converting those numbers to string and back, and having the
+ * receiver initialize the unused fields by hand.
+ *
+ * XXX Would be nice if VSTRINGs could import a fixed-size buffer and
+ * gracefully reject attempts to extend it.
+ */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_DATA(MAIL_ATTR_TIME, buf),
+ ATTR_TYPE_END);
+ if (ret == 1) {
+ if (LEN(buf) == sizeof(*stats)) {
+ memcpy((void *) stats, STR(buf), sizeof(*stats));
+ } else {
+ msg_warn("msg_stats_scan: size mis-match: %u != %u",
+ (unsigned) LEN(buf), (unsigned) sizeof(*stats));
+ ret = (-1);
+ }
+ }
+ vstring_free(buf);
+ return (ret);
+}
diff --git a/src/global/mynetworks.c b/src/global/mynetworks.c
new file mode 100644
index 0000000..007c046
--- /dev/null
+++ b/src/global/mynetworks.c
@@ -0,0 +1,336 @@
+/*++
+/* NAME
+/* mynetworks 3
+/* SUMMARY
+/* generate patterns for my own interface addresses
+/* SYNOPSIS
+/* #include <mynetworks.h>
+/*
+/* const char *mynetworks()
+/* AUXILIARY FUNCTIONS
+/* const char *mynetworks_host()
+/* DESCRIPTION
+/* This routine uses the address list built by own_inet_addr()
+/* to produce a list of patterns that match the corresponding
+/* networks.
+/*
+/* The interface list is specified with the "inet_interfaces"
+/* configuration parameter.
+/*
+/* The address to netblock conversion style is specified with
+/* the "mynetworks_style" parameter: one of "class" (match
+/* whole class A, B, C or D networks), "subnet" (match local
+/* subnets), or "host" (match local interfaces only).
+/*
+/* mynetworks_host() uses the "host" style.
+/* 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
+/*
+/* Dean C. Strik
+/* Department ICT Services
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven, Netherlands
+/* E-mail: <dean@ipnet6.org>
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef IN_CLASSD_NET
+#define IN_CLASSD_NET 0xf0000000
+#define IN_CLASSD_NSHIFT 28
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <inet_addr_list.h>
+#include <name_mask.h>
+#include <myaddrinfo.h>
+#include <mask_addr.h>
+#include <argv.h>
+#include <inet_proto.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <own_inet_addr.h>
+#include <mail_params.h>
+#include <mynetworks.h>
+#include <sock_addr.h>
+#include <been_here.h>
+
+/* Application-specific. */
+
+#define MASK_STYLE_CLASS (1 << 0)
+#define MASK_STYLE_SUBNET (1 << 1)
+#define MASK_STYLE_HOST (1 << 2)
+
+static const NAME_MASK mask_styles[] = {
+ MYNETWORKS_STYLE_CLASS, MASK_STYLE_CLASS,
+ MYNETWORKS_STYLE_SUBNET, MASK_STYLE_SUBNET,
+ MYNETWORKS_STYLE_HOST, MASK_STYLE_HOST,
+ 0,
+};
+
+/* mynetworks_core - return patterns for specific mynetworks style */
+
+static const char *mynetworks_core(const char *style)
+{
+ const char *myname = "mynetworks_core";
+ VSTRING *result;
+ INET_ADDR_LIST *my_addr_list;
+ INET_ADDR_LIST *my_mask_list;
+ unsigned shift;
+ unsigned junk;
+ int i;
+ unsigned mask_style;
+ struct sockaddr_storage *sa;
+ struct sockaddr_storage *ma;
+ int net_mask_count = 0;
+ ARGV *argv;
+ BH_TABLE *dup_filter;
+ char **cpp;
+
+ /*
+ * Avoid run-time errors when all network protocols are disabled. We
+ * can't look up interface information, and we can't convert explicit
+ * names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0) {
+ if (msg_verbose)
+ msg_info("skipping %s setting - "
+ "all network protocols are disabled",
+ VAR_MYNETWORKS);
+ return (mystrdup(""));
+ }
+ mask_style = name_mask("mynetworks mask style", mask_styles, style);
+
+ /*
+ * XXX Workaround: name_mask() needs a flags argument so that we can
+ * require exactly one value, or we need to provide an API that is
+ * dedicated for single-valued flags.
+ *
+ * XXX Why not use name_code() instead?
+ */
+ for (i = 0, junk = mask_style; junk != 0; junk >>= 1U)
+ i += (junk & 1);
+ if (i != 1)
+ msg_fatal("bad %s value: %s; specify exactly one value",
+ VAR_MYNETWORKS_STYLE, var_mynetworks_style);
+
+ result = vstring_alloc(20);
+ my_addr_list = own_inet_addr_list();
+ my_mask_list = own_inet_mask_list();
+
+ for (sa = my_addr_list->addrs, ma = my_mask_list->addrs;
+ sa < my_addr_list->addrs + my_addr_list->used;
+ sa++, ma++) {
+ unsigned long addr;
+ unsigned long mask;
+ struct in_addr net;
+
+ if (SOCK_ADDR_FAMILY(sa) == AF_INET) {
+ addr = ntohl(SOCK_ADDR_IN_ADDR(sa).s_addr);
+ mask = ntohl(SOCK_ADDR_IN_ADDR(ma).s_addr);
+
+ switch (mask_style) {
+
+ /*
+ * Natural mask. This is dangerous if you're customer of an
+ * ISP who gave you a small portion of their network.
+ */
+ case MASK_STYLE_CLASS:
+ if (IN_CLASSA(addr)) {
+ mask = IN_CLASSA_NET;
+ shift = IN_CLASSA_NSHIFT;
+ } else if (IN_CLASSB(addr)) {
+ mask = IN_CLASSB_NET;
+ shift = IN_CLASSB_NSHIFT;
+ } else if (IN_CLASSC(addr)) {
+ mask = IN_CLASSC_NET;
+ shift = IN_CLASSC_NSHIFT;
+ } else if (IN_CLASSD(addr)) {
+ mask = IN_CLASSD_NET;
+ shift = IN_CLASSD_NSHIFT;
+ } else {
+ msg_fatal("%s: unknown address class: %s",
+ myname, inet_ntoa(SOCK_ADDR_IN_ADDR(sa)));
+ }
+ break;
+
+ /*
+ * Subnet mask. This is less unsafe, but still bad if you're
+ * connected to a large subnet.
+ */
+ case MASK_STYLE_SUBNET:
+ for (junk = mask, shift = MAI_V4ADDR_BITS; junk != 0;
+ shift--, junk <<= 1)
+ /* void */ ;
+ break;
+
+ /*
+ * Host only. Do not relay authorize other hosts.
+ */
+ case MASK_STYLE_HOST:
+ mask = ~0UL;
+ shift = 0;
+ break;
+
+ default:
+ msg_panic("unknown mynetworks mask style: %s",
+ var_mynetworks_style);
+ }
+ net.s_addr = htonl(addr & mask);
+ vstring_sprintf_append(result, "%s/%d ",
+ inet_ntoa(net), MAI_V4ADDR_BITS - shift);
+ net_mask_count++;
+ continue;
+ }
+#ifdef HAS_IPV6
+ else if (SOCK_ADDR_FAMILY(sa) == AF_INET6) {
+ MAI_HOSTADDR_STR hostaddr;
+ unsigned char *ac;
+ unsigned char *end;
+ unsigned char ch;
+ struct sockaddr_in6 net6;
+
+ switch (mask_style) {
+
+ /*
+ * There are no classes for IPv6. We default to subnets
+ * instead.
+ */
+ case MASK_STYLE_CLASS:
+
+ /* FALLTHROUGH */
+
+ /*
+ * Subnet mask.
+ */
+ case MASK_STYLE_SUBNET:
+ ac = (unsigned char *) &SOCK_ADDR_IN6_ADDR(ma);
+ end = ac + sizeof(SOCK_ADDR_IN6_ADDR(ma));
+ shift = MAI_V6ADDR_BITS;
+ while (ac < end) {
+ if ((ch = *ac++) == (unsigned char) ~0U) {
+ shift -= CHAR_BIT;
+ continue;
+ } else {
+ while (ch != 0)
+ shift--, ch <<= 1;
+ break;
+ }
+ }
+ break;
+
+ /*
+ * Host only. Do not relay authorize other hosts.
+ */
+ case MASK_STYLE_HOST:
+ shift = 0;
+ break;
+
+ default:
+ msg_panic("unknown mynetworks mask style: %s",
+ var_mynetworks_style);
+ }
+ /* FIX 200501: IPv6 patch did not clear host bits. */
+ net6 = *SOCK_ADDR_IN6_PTR(sa);
+ mask_addr((unsigned char *) &net6.sin6_addr,
+ sizeof(net6.sin6_addr),
+ MAI_V6ADDR_BITS - shift);
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(&net6), SOCK_ADDR_LEN(&net6),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ vstring_sprintf_append(result, "[%s]/%d ",
+ hostaddr.buf, MAI_V6ADDR_BITS - shift);
+ net_mask_count++;
+ continue;
+ }
+#endif
+ else {
+ msg_warn("%s: skipping unknown address family %d",
+ myname, SOCK_ADDR_FAMILY(sa));
+ continue;
+ }
+ }
+
+ /*
+ * FIX 200501 IPv6 patch produced repeated results. Some systems report
+ * the same interface multiple times, notably multi-homed systems with
+ * IPv6 link-local or site-local addresses. A straight-forward sort+uniq
+ * produces ugly results, though. Instead we preserve the original order
+ * and use a duplicate filter to suppress repeated information.
+ */
+ if (net_mask_count > 1) {
+ argv = argv_split(vstring_str(result), " ");
+ VSTRING_RESET(result);
+ dup_filter = been_here_init(net_mask_count, BH_FLAG_NONE);
+ for (cpp = argv->argv; cpp < argv->argv + argv->argc; cpp++)
+ if (!been_here_fixed(dup_filter, *cpp))
+ vstring_sprintf_append(result, "%s ", *cpp);
+ argv_free(argv);
+ been_here_free(dup_filter);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s", myname, vstring_str(result));
+ return (vstring_export(result));
+}
+
+/* mynetworks - return patterns that match my own networks */
+
+const char *mynetworks(void)
+{
+ static const char *result;
+
+ if (result == 0)
+ result = mynetworks_core(var_mynetworks_style);
+ return (result);
+}
+
+/* mynetworks_host - return patterns for "host" mynetworks style */
+
+const char *mynetworks_host(void)
+{
+ static const char *result;
+
+ if (result == 0)
+ result = mynetworks_core(MYNETWORKS_STYLE_HOST);
+ return (result);
+}
+
+#ifdef TEST
+#include <inet_proto.h>
+
+char *var_inet_interfaces;
+char *var_mynetworks_style;
+
+int main(int argc, char **argv)
+{
+ INET_PROTO_INFO *proto_info;
+
+ if (argc != 4)
+ msg_fatal("usage: %s protocols mask_style interface_list (e.g. \"all subnet all\")",
+ argv[0]);
+ msg_verbose = 10;
+ proto_info = inet_proto_init(argv[0], argv[1]);
+ var_mynetworks_style = argv[2];
+ var_inet_interfaces = argv[3];
+ mynetworks();
+ return (0);
+}
+
+#endif
diff --git a/src/global/mynetworks.h b/src/global/mynetworks.h
new file mode 100644
index 0000000..a83087c
--- /dev/null
+++ b/src/global/mynetworks.h
@@ -0,0 +1,31 @@
+#ifndef _MYNETWORKS_H_INCLUDED_
+#define _MYNETWORKS_H_INCLUDED_
+
+/*++
+/* NAME
+/* mynetworks 3h
+/* SUMMARY
+/* lookup patterns for my own interface addresses
+/* SYNOPSIS
+/* #include <mynetworks.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *mynetworks(void);
+extern const char *mynetworks_host(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/mypwd.c b/src/global/mypwd.c
new file mode 100644
index 0000000..43b1fdb
--- /dev/null
+++ b/src/global/mypwd.c
@@ -0,0 +1,369 @@
+/*++
+/* NAME
+/* mypwd 3
+/* SUMMARY
+/* caching getpwnam_r()/getpwuid_r()
+/* SYNOPSIS
+/* #include <mypwd.h>
+/*
+/* int mypwuid_err(uid, pwd)
+/* uid_t uid;
+/* struct mypasswd **pwd;
+/*
+/* int mypwnam_err(name, pwd)
+/* const char *name;
+/* struct mypasswd **pwd;
+/*
+/* void mypwfree(pwd)
+/* struct mypasswd *pwd;
+/* BACKWARDS COMPATIBILITY
+/* struct mypasswd *mypwuid(uid)
+/* uid_t uid;
+/*
+/* struct mypasswd *mypwnam(name)
+/* const char *name;
+/* DESCRIPTION
+/* This module maintains a reference-counted cache of password
+/* database lookup results. The idea is to avoid making repeated
+/* getpw*() calls for the same information.
+/*
+/* mypwnam_err() and mypwuid_err() are wrappers that cache a
+/* private copy of results from the getpwnam_r() and getpwuid_r()
+/* library routines (on legacy systems: from getpwnam() and
+/* getpwuid(). Note: cache updates are not protected by mutex.
+/*
+/* Results are shared between calls with the same \fIname\fR
+/* or \fIuid\fR argument, so changing results is verboten.
+/*
+/* mypwnam() and mypwuid() are binary-compatibility wrappers
+/* for legacy applications.
+/*
+/* mypwfree() cleans up the result of mypwnam*() and mypwuid*().
+/* BUGS
+/* This module is security sensitive and complex at the same
+/* time, which is bad.
+/* DIAGNOSTICS
+/* mypwnam_err() and mypwuid_err() return a non-zero system
+/* error code when the lookup could not be performed. They
+/* return zero, plus a null struct mypasswd pointer, when the
+/* requested information was not found.
+/*
+/* Fatal error: out of memory.
+/* 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 <string.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <htable.h>
+#include <binhash.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include "mypwd.h"
+
+ /*
+ * Workaround: Solaris >= 2.5.1 provides two getpwnam_r() and getpwuid_r()
+ * implementations. The default variant is compatible with historical
+ * Solaris implementations. The non-default variant is POSIX-compliant.
+ *
+ * To get the POSIX-compliant variant, we include the file <pwd.h> after
+ * defining _POSIX_PTHREAD_SEMANTICS. We do this after all other includes,
+ * so that we won't unexpectedly affect any other APIs.
+ *
+ * This happens to work because nothing above this includes <pwd.h>, and
+ * because of the specific way that Solaris redefines getpwnam_r() and
+ * getpwuid_r() for POSIX compliance. We know the latter from peeking under
+ * the hood. What we do is only marginally better than directly invoking
+ * __posix_getpwnam_r() and __posix_getpwuid_r().
+ */
+#ifdef GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS
+#define _POSIX_PTHREAD_SEMANTICS
+#endif
+#include <pwd.h>
+
+ /*
+ * The private cache. One for lookups by name, one for lookups by uid, and
+ * one for the last looked up result. There is a minor problem: multiple
+ * cache entries may have the same uid value, but the cache that is indexed
+ * by uid can store only one entry per uid value.
+ */
+static HTABLE *mypwcache_name = 0;
+static BINHASH *mypwcache_uid = 0;
+static struct mypasswd *last_pwd;
+
+ /*
+ * XXX Solaris promises that we can determine the getpw*_r() buffer size by
+ * calling sysconf(_SC_GETPW_R_SIZE_MAX). Many systems promise that they
+ * will return an ERANGE error when the buffer is too small. However, not
+ * all systems make such promises. Therefore, we settle for the dumbest
+ * option: a large buffer. This is acceptable because the buffer is used
+ * only for short-term storage.
+ */
+#ifdef HAVE_POSIX_GETPW_R
+#define GETPW_R_BUFSIZ 1024
+#endif
+#define MYPWD_ERROR_DELAY (30)
+
+/* mypwenter - enter password info into cache */
+
+static struct mypasswd *mypwenter(const struct passwd * pwd)
+{
+ struct mypasswd *mypwd;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (mypwcache_name == 0) {
+ mypwcache_name = htable_create(0);
+ mypwcache_uid = binhash_create(0);
+ }
+ mypwd = (struct mypasswd *) mymalloc(sizeof(*mypwd));
+ mypwd->refcount = 0;
+ mypwd->pw_name = mystrdup(pwd->pw_name);
+ mypwd->pw_passwd = mystrdup(pwd->pw_passwd);
+ mypwd->pw_uid = pwd->pw_uid;
+ mypwd->pw_gid = pwd->pw_gid;
+ mypwd->pw_gecos = mystrdup(pwd->pw_gecos);
+ mypwd->pw_dir = mystrdup(pwd->pw_dir);
+ mypwd->pw_shell = mystrdup(*pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL);
+
+ /*
+ * Avoid mypwcache_uid memory leak when multiple names have the same UID.
+ * This makes the lookup result dependent on program history. But, it was
+ * already history-dependent before we added this extra check.
+ */
+ htable_enter(mypwcache_name, mypwd->pw_name, (void *) mypwd);
+ if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid,
+ sizeof(mypwd->pw_uid)) == 0)
+ binhash_enter(mypwcache_uid, (void *) &mypwd->pw_uid,
+ sizeof(mypwd->pw_uid), (void *) mypwd);
+ return (mypwd);
+}
+
+/* mypwuid - caching getpwuid() */
+
+struct mypasswd *mypwuid(uid_t uid)
+{
+ struct mypasswd *mypwd;
+
+ while ((errno = mypwuid_err(uid, &mypwd)) != 0) {
+ msg_warn("getpwuid_r: %m");
+ sleep(MYPWD_ERROR_DELAY);
+ }
+ return (mypwd);
+}
+
+/* mypwuid_err - caching getpwuid_r(), minus thread safety */
+
+int mypwuid_err(uid_t uid, struct mypasswd ** result)
+{
+ struct passwd *pwd;
+ struct mypasswd *mypwd;
+
+ /*
+ * See if this is the same user as last time.
+ */
+ if (last_pwd != 0) {
+ if (last_pwd->pw_uid != uid) {
+ mypwfree(last_pwd);
+ last_pwd = 0;
+ } else {
+ *result = mypwd = last_pwd;
+ mypwd->refcount++;
+ return (0);
+ }
+ }
+
+ /*
+ * Find the info in the cache or in the password database.
+ */
+ if ((mypwd = (struct mypasswd *)
+ binhash_find(mypwcache_uid, (void *) &uid, sizeof(uid))) == 0) {
+#ifdef HAVE_POSIX_GETPW_R
+ char pwstore[GETPW_R_BUFSIZ];
+ struct passwd pwbuf;
+ int err;
+
+ err = getpwuid_r(uid, &pwbuf, pwstore, sizeof(pwstore), &pwd);
+ if (err != 0)
+ return (err);
+ if (pwd == 0) {
+ *result = 0;
+ return (0);
+ }
+#else
+ if ((pwd = getpwuid(uid)) == 0) {
+ *result = 0;
+ return (0);
+ }
+#endif
+ mypwd = mypwenter(pwd);
+ }
+ *result = last_pwd = mypwd;
+ mypwd->refcount += 2;
+ return (0);
+}
+
+/* mypwnam - caching getpwnam() */
+
+struct mypasswd *mypwnam(const char *name)
+{
+ struct mypasswd *mypwd;
+
+ while ((errno = mypwnam_err(name, &mypwd)) != 0) {
+ msg_warn("getpwnam_r: %m");
+ sleep(MYPWD_ERROR_DELAY);
+ }
+ return (mypwd);
+}
+
+/* mypwnam_err - caching getpwnam_r(), minus thread safety */
+
+int mypwnam_err(const char *name, struct mypasswd ** result)
+{
+ struct passwd *pwd;
+ struct mypasswd *mypwd;
+
+ /*
+ * See if this is the same user as last time.
+ */
+ if (last_pwd != 0) {
+ if (strcmp(last_pwd->pw_name, name) != 0) {
+ mypwfree(last_pwd);
+ last_pwd = 0;
+ } else {
+ *result = mypwd = last_pwd;
+ mypwd->refcount++;
+ return (0);
+ }
+ }
+
+ /*
+ * Find the info in the cache or in the password database.
+ */
+ if ((mypwd = (struct mypasswd *) htable_find(mypwcache_name, name)) == 0) {
+#ifdef HAVE_POSIX_GETPW_R
+ char pwstore[GETPW_R_BUFSIZ];
+ struct passwd pwbuf;
+ int err;
+
+ err = getpwnam_r(name, &pwbuf, pwstore, sizeof(pwstore), &pwd);
+ if (err != 0)
+ return (err);
+ if (pwd == 0) {
+ *result = 0;
+ return (0);
+ }
+#else
+ if ((pwd = getpwnam(name)) == 0) {
+ *result = 0;
+ return (0);
+ }
+#endif
+ mypwd = mypwenter(pwd);
+ }
+ *result = last_pwd = mypwd;
+ mypwd->refcount += 2;
+ return (0);
+}
+
+/* mypwfree - destroy password info */
+
+void mypwfree(struct mypasswd * mypwd)
+{
+ if (mypwd->refcount < 1)
+ msg_panic("mypwfree: refcount %d", mypwd->refcount);
+
+ /*
+ * See mypwenter() for the reason behind the binhash_locate() test.
+ */
+ if (--mypwd->refcount == 0) {
+ htable_delete(mypwcache_name, mypwd->pw_name, (void (*) (void *)) 0);
+ if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid,
+ sizeof(mypwd->pw_uid)))
+ binhash_delete(mypwcache_uid, (void *) &mypwd->pw_uid,
+ sizeof(mypwd->pw_uid), (void (*) (void *)) 0);
+ myfree(mypwd->pw_name);
+ myfree(mypwd->pw_passwd);
+ myfree(mypwd->pw_gecos);
+ myfree(mypwd->pw_dir);
+ myfree(mypwd->pw_shell);
+ myfree((void *) mypwd);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Test program. Look up a couple users and/or uid values and see if the
+ * results will be properly free()d.
+ */
+#include <stdlib.h>
+#include <ctype.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ struct mypasswd **mypwd;
+ int i;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc == 1)
+ msg_fatal("usage: %s name or uid ...", argv[0]);
+
+ mypwd = (struct mypasswd **) mymalloc((argc + 2) * sizeof(*mypwd));
+
+ /*
+ * Do a sequence of lookups.
+ */
+ for (i = 1; i < argc; i++) {
+ if (ISDIGIT(argv[i][0]))
+ mypwd[i] = mypwuid(atoi(argv[i]));
+ else
+ mypwd[i] = mypwnam(argv[i]);
+ if (mypwd[i] == 0)
+ msg_fatal("%s: not found", argv[i]);
+ msg_info("lookup %s %s/%d refcount=%d name_cache=%d uid_cache=%d",
+ argv[i], mypwd[i]->pw_name, mypwd[i]->pw_uid,
+ mypwd[i]->refcount, mypwcache_name->used, mypwcache_uid->used);
+ }
+ mypwd[argc] = last_pwd;
+
+ /*
+ * The following should free all entries.
+ */
+ for (i = 1; i < argc + 1; i++) {
+ msg_info("free %s/%d refcount=%d name_cache=%d uid_cache=%d",
+ mypwd[i]->pw_name, mypwd[i]->pw_uid, mypwd[i]->refcount,
+ mypwcache_name->used, mypwcache_uid->used);
+ mypwfree(mypwd[i]);
+ }
+ msg_info("name_cache=%d uid_cache=%d",
+ mypwcache_name->used, mypwcache_uid->used);
+
+ myfree((void *) mypwd);
+ return (0);
+}
+
+#endif
diff --git a/src/global/mypwd.h b/src/global/mypwd.h
new file mode 100644
index 0000000..82f2cd3
--- /dev/null
+++ b/src/global/mypwd.h
@@ -0,0 +1,45 @@
+#ifndef _MYPWNAM_H_INCLUDED_
+#define _MYPWNAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* mypwnam 3h
+/* SUMMARY
+/* caching getpwnam_r()/getpwuid_r()
+/* SYNOPSIS
+/* #include <mypwd.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+struct mypasswd {
+ int refcount;
+ char *pw_name;
+ char *pw_passwd;
+ uid_t pw_uid;
+ gid_t pw_gid;
+ char *pw_gecos;
+ char *pw_dir;
+ char *pw_shell;
+};
+
+extern int mypwnam_err(const char *, struct mypasswd **);
+extern int mypwuid_err(uid_t, struct mypasswd **);
+extern struct mypasswd *mypwnam(const char *);
+extern struct mypasswd *mypwuid(uid_t);
+extern void mypwfree(struct mypasswd *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/namadr_list.c b/src/global/namadr_list.c
new file mode 100644
index 0000000..071a733
--- /dev/null
+++ b/src/global/namadr_list.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* namadr_list 3
+/* SUMMARY
+/* name/address list membership
+/* SYNOPSIS
+/* #include <namadr_list.h>
+/*
+/* NAMADR_LIST *namadr_list_init(pname, flags, pattern_list)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/*
+/* int namadr_list_match(list, name, addr)
+/* NAMADR_LIST *list;
+/* const char *name;
+/* const char *addr;
+/*
+/* void namadr_list_free(list)
+/* NAMADR_LIST *list;
+/* DESCRIPTION
+/* This is a convenience wrapper around the match_list module.
+/*
+/* This module implements tests for list membership of a
+/* hostname or network address.
+/*
+/* A list pattern specifies a host name, a domain name,
+/* an internet address, or a network/mask pattern, where the
+/* mask specifies the number of bits in the network part.
+/* When a pattern specifies a file name, its contents are
+/* substituted for the file name; when a pattern is a
+/* type:name table specification, table lookup is used
+/* instead.
+/* Patterns are separated by whitespace and/or commas. In
+/* order to reverse the result, precede a pattern with an
+/* exclamation point (!).
+/*
+/* A host matches a list when its name or address matches
+/* a pattern, or when any of its parent domains matches a
+/* pattern. The matching process is case insensitive.
+/*
+/* namadr_list_init() performs initializations. The pname
+/* argument specifies error reporting context. The flags
+/* argument is the bit-wise OR of zero or more of the
+/* following:
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches itself and any name below
+/* the domain foo.com. If this flag is cleared, foo.com matches itself
+/* only, and .foo.com matches any name below the domain foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Request that namadr_list_match() logs a warning and returns
+/* zero with list->error set to a non-zero dictionary error
+/* code, instead of raising a fatal error.
+/* .PP
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* The last argument is a list of patterns, or the absolute
+/* pathname of a file with patterns.
+/*
+/* namadr_list_match() matches the specified host name and
+/* address against the specified list of patterns.
+/*
+/* namadr_list_free() releases storage allocated by namadr_list_init().
+/* DIAGNOSTICS
+/* Fatal errors: unable to open or read a pattern file; invalid
+/* pattern. Panic: interface violations.
+/* SEE ALSO
+/* match_list(3) generic list matching
+/* match_ops(3) match host by name or by address
+/* 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 <match_list.h>
+
+/* Global library. */
+
+#include "namadr_list.h"
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <dict.h>
+#include <stringops.h> /* util_utf8_enable */
+
+static void usage(char *progname)
+{
+ msg_fatal("usage: %s [-v] pattern_list hostname address", progname);
+}
+
+int main(int argc, char **argv)
+{
+ NAMADR_LIST *list;
+ char *host;
+ char *addr;
+ int ch;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 3)
+ usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ list = namadr_list_init("command line", MATCH_FLAG_PARENT
+ | MATCH_FLAG_RETURN, argv[optind]);
+ host = argv[optind + 1];
+ addr = argv[optind + 2];
+ vstream_printf("%s/%s: %s\n", host, addr,
+ namadr_list_match(list, host, addr) ?
+ "YES" : list->error == 0 ? "NO" : "ERROR");
+ vstream_fflush(VSTREAM_OUT);
+ namadr_list_free(list);
+ return (0);
+}
+
+#endif
diff --git a/src/global/namadr_list.h b/src/global/namadr_list.h
new file mode 100644
index 0000000..e327784
--- /dev/null
+++ b/src/global/namadr_list.h
@@ -0,0 +1,40 @@
+#ifndef _NAMADR_LIST_H_INCLUDED_
+#define _NAMADR_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* namadr 3h
+/* SUMMARY
+/* name/address membership
+/* SYNOPSIS
+/* #include <namadr_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <match_list.h>
+
+ /*
+ * External interface.
+ */
+#define NAMADR_LIST MATCH_LIST
+
+#define namadr_list_init(o, f, p) \
+ match_list_init((o), (f), (p), 2, match_hostname, match_hostaddr)
+#define namadr_list_match match_list_match
+#define namadr_list_free match_list_free
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/namadr_list.in b/src/global/namadr_list.in
new file mode 100644
index 0000000..4542387
--- /dev/null
+++ b/src/global/namadr_list.in
@@ -0,0 +1,42 @@
+./namadr_list 168.100.3.0/28 dummy 168.100.3.2
+./namadr_list '!168.100.3.2 168.100.3.0/28' dummy 168.100.3.2
+./namadr_list '!168.100.3.2 168.100.3.0/28' dummy 168.100.3.3
+./namadr_list 168.100.3.0/28 dummy 168.100.3.16
+./namadr_list 168.100.3.0/98 dummy 168.100.3.16
+./namadr_list 168.100.589.0/28 dummy 168.100.3.16
+./namadr_list 168.100.3.0/28 dummy 168.100.989.16
+./namadr_list 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 dummy 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+./namadr_list '[2001:240:5c7:0:2d0:b7ff:fe88:2ca7]' dummy 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+./namadr_list '[2001:240:5c7:0:2d0:b7ff:fe88:2ca7]' dummy 2001:240:5c7:0:2d0:b7ff:fe88:2ca8
+./namadr_list '[2001:240:5c7:0:2d0:b7ff:fe88:2ca7]/64' dummy 2001:240:5c7:0:2d0:b7ff:fe88:2ca8
+./namadr_list '[2001:240:5c7::]/64' dummy 2001:240:5c7:0:2d0:b7ff:fe88:2ca8
+./namadr_list '[2001:240:5c7::]/64' dummy 2001:24:5c7:0:2d0:b7ff:fe88:2ca8
+./namadr_list '[2001:24:5c7:0:2d0:b7ff:fe88:2ca8]' dummy 2001:24:5c7:0:2d0:b7ff:fe88:2ca8
+./namadr_list '[2001:24:5c7:0:2d0:b7ff:fe88:2ca8]' dummy 2001:24:5c7:0:2d0:b7ff:fe88:2ca7
+./namadr_list 168.100.3.2 dummy 168.100.3.2
+./namadr_list 168.100.3.2 dummy 168.100.3.3
+./namadr_list '[168.100.3.2]' dummy 168.100.3.2
+./namadr_list '[168.100.3.2]' dummy 168.100.3.3
+echo foo !bar baz >junk; mv junk /tmp
+./namadr_list !/tmp/junk dummy 168.100.3.3
+./namadr_list !/tmp/junk foo 168.100.3.3
+./namadr_list !/tmp/junk bar 168.100.3.3
+./namadr_list !/tmp/junk baz 168.100.3.3
+./namadr_list /tmp/junk dummy 168.100.3.3
+./namadr_list /tmp/junk foo 168.100.3.3
+./namadr_list /tmp/junk bar 168.100.3.3
+./namadr_list /tmp/junk baz 168.100.3.3
+rm -f junk
+./namadr_list 'be.be' x.x.x 127.0.0.1
+./namadr_list 'be/be' x.x.x 127.0.0.1
+./namadr_list '[be:be]' x.x.x 127.0.0.1
+./namadr_list '[be:be]' x.x.x ::1
+env foo=x ./namadr_list environ:junk foo 168.100.3.3
+env foo=x ./namadr_list environ:junk bar 168.100.3.3
+env foo=x ./namadr_list !environ:junk foo 168.100.3.3
+env foo=x ./namadr_list !environ:junk bar 168.100.3.3
+env foo=x ./namadr_list !!environ:junk foo 168.100.3.3
+env foo=x ./namadr_list !!environ:junk bar 168.100.3.3
+./namadr_list fail:1 bar 168.100.3.3
+./namadr_list !fail:1 bar 168.100.3.3
+./namadr_list /tmp/nosuchfile bar 168.100.3.3
diff --git a/src/global/namadr_list.ref b/src/global/namadr_list.ref
new file mode 100644
index 0000000..e38b883
--- /dev/null
+++ b/src/global/namadr_list.ref
@@ -0,0 +1,53 @@
+dummy/168.100.3.2: YES
+dummy/168.100.3.2: NO
+dummy/168.100.3.3: YES
+dummy/168.100.3.16: NO
+./namadr_list: warning: command line: bad mask length in "168.100.3.0/98"
+dummy/168.100.3.16: ERROR
+./namadr_list: warning: command line: bad network value in "168.100.589.0/28"
+dummy/168.100.3.16: ERROR
+dummy/168.100.989.16: NO
+./namadr_list: error: unsupported dictionary type: 2001
+./namadr_list: warning: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 is unavailable. unsupported dictionary type: 2001
+./namadr_list: warning: command line: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7: table lookup problem
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: ERROR
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: YES
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: NO
+./namadr_list: warning: command line: non-null host address bits in "2001:240:5c7:0:2d0:b7ff:fe88:2ca7/64", perhaps you should use "2001:240:5c7::/64" instead
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: ERROR
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: YES
+dummy/2001:24:5c7:0:2d0:b7ff:fe88:2ca8: NO
+dummy/2001:24:5c7:0:2d0:b7ff:fe88:2ca8: YES
+dummy/2001:24:5c7:0:2d0:b7ff:fe88:2ca7: NO
+dummy/168.100.3.2: YES
+dummy/168.100.3.3: NO
+dummy/168.100.3.2: YES
+dummy/168.100.3.3: NO
+dummy/168.100.3.3: NO
+foo/168.100.3.3: NO
+bar/168.100.3.3: YES
+baz/168.100.3.3: NO
+dummy/168.100.3.3: NO
+foo/168.100.3.3: YES
+bar/168.100.3.3: NO
+baz/168.100.3.3: YES
+x.x.x/127.0.0.1: NO
+./namadr_list: warning: command line: bad mask value in "be/be"
+x.x.x/127.0.0.1: ERROR
+x.x.x/127.0.0.1: NO
+./namadr_list: warning: command line: bad address pattern: "be:be"
+x.x.x/::1: ERROR
+foo/168.100.3.3: YES
+bar/168.100.3.3: NO
+foo/168.100.3.3: NO
+bar/168.100.3.3: NO
+foo/168.100.3.3: YES
+bar/168.100.3.3: NO
+./namadr_list: warning: command line: fail:1: table lookup problem
+bar/168.100.3.3: ERROR
+./namadr_list: warning: command line: fail:1: table lookup problem
+bar/168.100.3.3: ERROR
+./namadr_list: error: open file /tmp/nosuchfile: No such file or directory
+./namadr_list: warning: non-existent:/tmp/nosuchfile is unavailable. open file /tmp/nosuchfile: No such file or directory
+./namadr_list: warning: command line: non-existent:/tmp/nosuchfile: table lookup problem
+bar/168.100.3.3: ERROR
diff --git a/src/global/normalize_mailhost_addr.c b/src/global/normalize_mailhost_addr.c
new file mode 100644
index 0000000..ba0f7bd
--- /dev/null
+++ b/src/global/normalize_mailhost_addr.c
@@ -0,0 +1,259 @@
+/*++
+/* NAME
+/* normalize_mailhost_addr 3
+/* SUMMARY
+/* normalize mailhost address string representation
+/* SYNOPSIS
+/* #include <normalize_mailhost_addr.h>
+/*
+/* int normalize_mailhost_addr(
+/* const char *string,
+/* char **mailhost_addr,
+/* char **bare_addr,
+/* int *addr_family)
+/* DESCRIPTION
+/* normalize_mailhost_addr() takes the RFC 2821 string
+/* representation of an IPv4 or IPv6 network address, and
+/* normalizes the "IPv6:" prefix and numeric form. An IPv6 or
+/* IPv4 form is rejected if supposed for that protocol is
+/* disabled or non-existent. If both IPv6 and IPv4 support are
+/* enabled, a V4-in-V6 address is replaced with the IPv4 form.
+/*
+/* Arguments:
+/* .IP string
+/* Null-terminated string with the RFC 2821 string representation
+/* of an IPv4 or IPv6 network address.
+/* .IP mailhost_addr
+/* Null pointer, or pointer to null-terminated string with the
+/* normalized RFC 2821 string representation of an IPv4 or
+/* IPv6 network address. Storage must be freed with myfree().
+/* .IP bare_addr
+/* Null pointer, or pointer to null-terminated string with the
+/* numeric address without prefix, such as "IPv6:". Storage
+/* must be freed with myfree().
+/* .IP addr_family
+/* Null pointer, or pointer to integer for storing the address
+/* family.
+/* DIAGNOSTICS
+/* normalize_mailhost_addr() returns -1 if the input is malformed,
+/* zero otherwise.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <inet_proto.h>
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library.
+ */
+#include <normalize_mailhost_addr.h>
+#include <valid_mailhost_addr.h>
+
+/* normalize_mailhost_addr - parse and normalize mailhost IP address */
+
+int normalize_mailhost_addr(const char *string, char **mailhost_addr,
+ char **bare_addr, int *addr_family)
+{
+ const char myname[] = "normalize_mailhost_addr";
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+ struct addrinfo *res = 0;
+ MAI_HOSTADDR_STR hostaddr;
+ const char *valid_addr; /* IPv6:fc00::1 */
+ const char *normal_addr; /* 192.168.0.1 */
+ int normal_family;
+
+#define UPDATE_BARE_ADDR(s, v) do { \
+ if (s) myfree(s); \
+ (s) = mystrdup(v); \
+ } while(0)
+#define UPDATE_MAILHOST_ADDR(s, prefix, addr) do { \
+ if (s) myfree(s); \
+ (s) = concatenate((prefix), (addr), (char *) 0); \
+ } while (0)
+
+ /*
+ * Parse and normalize the input.
+ */
+ if ((valid_addr = valid_mailhost_addr(string, DONT_GRIPE)) == 0
+ || hostaddr_to_sockaddr(valid_addr, (char *) 0, 0, &res) != 0
+ || sockaddr_to_hostaddr(res->ai_addr, res->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0) != 0) {
+ normal_addr = 0;
+#ifdef HAS_IPV6
+ } else if (res->ai_family == AF_INET6
+ && strncasecmp("::ffff:", hostaddr.buf, 7) == 0
+ && strchr((char *) proto_info->sa_family_list, AF_INET)) {
+ normal_addr = hostaddr.buf + 7;
+ normal_family = AF_INET;
+#endif
+ } else if (strchr((char *) proto_info->sa_family_list, res->ai_family)) {
+ normal_addr = hostaddr.buf;
+ normal_family = res->ai_family;
+ } else {
+ normal_addr = 0;
+ }
+ if (res)
+ freeaddrinfo(res);
+ if (normal_addr == 0)
+ return (-1);
+
+ /*
+ * Write the applicable outputs.
+ */
+ if (bare_addr) {
+ UPDATE_BARE_ADDR(*bare_addr, normal_addr);
+ if (msg_verbose)
+ msg_info("%s: bare_addr=%s", myname, *bare_addr);
+ }
+ if (mailhost_addr) {
+#ifdef HAS_IPV6
+ if (normal_family == AF_INET6)
+ UPDATE_MAILHOST_ADDR(*mailhost_addr, IPV6_COL, normal_addr);
+ else
+#endif
+ UPDATE_BARE_ADDR(*mailhost_addr, normal_addr);
+ if (msg_verbose)
+ msg_info("%s: mailhost_addr=%s", myname, *mailhost_addr);
+ }
+ if (addr_family) {
+ *addr_family = normal_family;
+ if (msg_verbose)
+ msg_info("%s: addr_family=%s", myname,
+ *addr_family == AF_INET6 ? "AF_INET6"
+ : *addr_family == AF_INET ? "AF_INET"
+ : "unknown");
+ }
+ return (0);
+}
+
+ /*
+ * Test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <mymalloc.h>
+#include <msg.h>
+
+ /*
+ * Main test program.
+ */
+int main(int argc, char **argv)
+{
+ /* Test cases with inputs and expected outputs. */
+ typedef struct TEST_CASE {
+ const char *inet_protocols;
+ const char *mailhost_addr;
+ int exp_return;
+ const char *exp_mailhost_addr;
+ char *exp_bare_addr;
+ int exp_addr_family;
+ } TEST_CASE;
+ static TEST_CASE test_cases[] = {
+ /* IPv4 in IPv6. */
+ {"ipv4, ipv6", "ipv6:::ffff:1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
+ {"ipv6", "ipv6:::ffff:1.2.3.4", 0, "IPv6:::ffff:1.2.3.4", "::ffff:1.2.3.4", AF_INET6},
+ /* Pass IPv4 or IPV6. */
+ {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", AF_INET6},
+ {"ipv4, ipv6", "1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
+ /* Normalize IPv4 or IPV6. */
+ {"ipv4, ipv6", "ipv6:fc00::0", 0, "IPv6:fc00::", "fc00::", AF_INET6},
+ {"ipv4, ipv6", "01.02.03.04", 0, "1.2.3.4", "1.2.3.4", AF_INET},
+ /* Suppress specific outputs. */
+ {"ipv4, ipv6", "ipv6:fc00::1", 0, 0, "fc00::1", AF_INET6},
+ {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", 0, AF_INET6},
+ {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", -1},
+ /* Address type mismatch. */
+ {"ipv4, ipv6", "::ffff:1.2.3.4", -1},
+ {"ipv4", "ipv6:fc00::1", -1},
+ {"ipv6", "1.2.3.4", -1},
+ 0,
+ };
+ TEST_CASE *test_case;
+
+ /* Actual results. */
+ int act_return;
+ char *act_mailhost_addr = mystrdup("initial_mailhost_addr");
+ char *act_bare_addr = mystrdup("initial_bare_addr");
+ int act_addr_family = 0xdeadbeef;
+
+ /* Findings. */
+ int tests_failed = 0;
+ int test_failed;
+
+ for (tests_failed = 0, test_case = test_cases; test_case->inet_protocols;
+ tests_failed += test_failed, test_case++) {
+ test_failed = 0;
+ inet_proto_init(argv[0], test_case->inet_protocols);
+ act_return =
+ normalize_mailhost_addr(test_case->mailhost_addr,
+ test_case->exp_mailhost_addr ?
+ &act_mailhost_addr : (char **) 0,
+ test_case->exp_bare_addr ?
+ &act_bare_addr : (char **) 0,
+ test_case->exp_addr_family >= 0 ?
+ &act_addr_family : (int *) 0);
+ if (act_return != test_case->exp_return) {
+ msg_warn("test case %d return expected=%d actual=%d",
+ (int) (test_case - test_cases),
+ test_case->exp_return, act_return);
+ test_failed = 1;
+ continue;
+ }
+ if (test_case->exp_return != 0)
+ continue;
+ if (test_case->exp_mailhost_addr
+ && strcmp(test_case->exp_mailhost_addr, act_mailhost_addr)) {
+ msg_warn("test case %d mailhost_addr expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ test_case->exp_mailhost_addr, act_mailhost_addr);
+ test_failed = 1;
+ }
+ if (test_case->exp_bare_addr
+ && strcmp(test_case->exp_bare_addr, act_bare_addr)) {
+ msg_warn("test case %d bare_addr expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ test_case->exp_bare_addr, act_bare_addr);
+ test_failed = 1;
+ }
+ if (test_case->exp_addr_family >= 0
+ && test_case->exp_addr_family != act_addr_family) {
+ msg_warn("test case %d addr_family expected=0x%x actual=0x%x",
+ (int) (test_case - test_cases),
+ test_case->exp_addr_family, act_addr_family);
+ test_failed = 1;
+ }
+ }
+ if (act_mailhost_addr)
+ myfree(act_mailhost_addr);
+ if (act_bare_addr)
+ myfree(act_bare_addr);
+ if (tests_failed)
+ msg_info("tests failed: %d", tests_failed);
+ exit(tests_failed != 0);
+}
+
+#endif
diff --git a/src/global/normalize_mailhost_addr.h b/src/global/normalize_mailhost_addr.h
new file mode 100644
index 0000000..5ea4d3a
--- /dev/null
+++ b/src/global/normalize_mailhost_addr.h
@@ -0,0 +1,30 @@
+#ifndef _NORMALIZE_MAILHOST_ADDR_H_INCLUDED_
+#define _NORMALIZE_MAILHOST_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* normalize_mailhost_addr 3h
+/* SUMMARY
+/* normalize mailhost address string representation
+/* SYNOPSIS
+/* #include <normalize_mailhost_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int normalize_mailhost_addr(const char *, char **, char **, int *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/off_cvt.c b/src/global/off_cvt.c
new file mode 100644
index 0000000..3efb0b7
--- /dev/null
+++ b/src/global/off_cvt.c
@@ -0,0 +1,158 @@
+/*++
+/* NAME
+/* off_cvt 3
+/* SUMMARY
+/* off_t conversions
+/* SYNOPSIS
+/* #include <off_cvt.h>
+/*
+/* off_t off_cvt_string(string)
+/* const char *string;
+/*
+/* VSTRING *off_cvt_number(result, offset)
+/* VSTRING *result;
+/* off_t offset;
+/* DESCRIPTION
+/* This module provides conversions between \fIoff_t\fR and string.
+/*
+/* off_cvt_string() converts a string, containing a non-negative
+/* offset, to numerical form. The result is -1 in case of problems.
+/*
+/* off_cvt_number() converts a non-negative offset to string form.
+/*
+/* Arguments:
+/* .IP string
+/* String with non-negative number to be converted to off_t.
+/* .IP result
+/* Buffer for storage of the result of conversion to string.
+/* .IP offset
+/* Non-negative off_t value to be converted to string.
+/* DIAGNOSTICS
+/* Panic: negative offset
+/* 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/types.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include "off_cvt.h"
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define END vstring_end
+#define SWAP(type, a, b) { type temp; temp = a; a = b; b = temp; }
+
+/* off_cvt_string - string to number */
+
+off_t off_cvt_string(const char *str)
+{
+ int ch;
+ off_t result;
+ off_t digit_value;
+
+ /*
+ * Detect overflow before it happens. Code that attempts to detect
+ * overflow after-the-fact makes assumptions about undefined behavior.
+ * Compilers may invalidate such assumptions.
+ */
+ for (result = 0; (ch = *(unsigned char *) str) != 0; str++) {
+ if (!ISDIGIT(ch))
+ return (-1);
+ digit_value = ch - '0';
+ if (result > OFF_T_MAX / 10
+ || (result *= 10) > OFF_T_MAX - digit_value)
+ return (-1);
+ result += digit_value;
+ }
+ return (result);
+}
+
+/* off_cvt_number - number to string */
+
+VSTRING *off_cvt_number(VSTRING *buf, off_t offset)
+{
+ static char digs[] = "0123456789";
+ char *start;
+ char *last;
+ int i;
+
+ /*
+ * Sanity checks
+ */
+ if (offset < 0)
+ msg_panic("off_cvt_number: negative offset -%s",
+ STR(off_cvt_number(buf, -offset)));
+
+ /*
+ * First accumulate the result, backwards.
+ */
+ VSTRING_RESET(buf);
+ while (offset != 0) {
+ VSTRING_ADDCH(buf, digs[offset % 10]);
+ offset /= 10;
+ }
+ VSTRING_TERMINATE(buf);
+
+ /*
+ * Then, reverse the result.
+ */
+ start = STR(buf);
+ last = END(buf) - 1;
+ for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
+ SWAP(int, start[i], last[-i]);
+ return (buf);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read a number from stdin, convert to
+ * off_t, back to string, and print the result.
+ */
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ off_t offset;
+
+ while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
+ if (STR(buf)[0] == '#' || STR(buf)[0] == 0)
+ continue;
+ if ((offset = off_cvt_string(STR(buf))) < 0) {
+ msg_warn("bad input %s", STR(buf));
+ } else {
+ vstream_printf("%s\n", STR(off_cvt_number(buf, offset)));
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/off_cvt.h b/src/global/off_cvt.h
new file mode 100644
index 0000000..7e506f5
--- /dev/null
+++ b/src/global/off_cvt.h
@@ -0,0 +1,37 @@
+#ifndef _OFF_CVT_H_INCLUDED_
+#define _OFF_CVT_H_INCLUDED_
+
+/*++
+/* NAME
+/* off_cvt 3h
+/* SUMMARY
+/* off_t conversions
+/* SYNOPSIS
+/* #include <vstring.h>
+/* #include <off_cvt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern off_t off_cvt_string(const char *);
+extern VSTRING *off_cvt_number(VSTRING *, off_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/off_cvt.in b/src/global/off_cvt.in
new file mode 100644
index 0000000..15133e9
--- /dev/null
+++ b/src/global/off_cvt.in
@@ -0,0 +1,9 @@
+# Assume 64-bit off_t (assuming 1- or 2-complement).
+# Maximum.
+9223372036854775807
+# Overflow.
+9223372036854775808
+92233720368547758070
+# off_cvt() does not accept leading signs.
++1
+-1
diff --git a/src/global/off_cvt.ref b/src/global/off_cvt.ref
new file mode 100644
index 0000000..5611528
--- /dev/null
+++ b/src/global/off_cvt.ref
@@ -0,0 +1,5 @@
+9223372036854775807
+unknown: warning: bad input 9223372036854775808
+unknown: warning: bad input 92233720368547758070
+unknown: warning: bad input +1
+unknown: warning: bad input -1
diff --git a/src/global/opened.c b/src/global/opened.c
new file mode 100644
index 0000000..86d6dfa
--- /dev/null
+++ b/src/global/opened.c
@@ -0,0 +1,94 @@
+/*++
+/* NAME
+/* opened 3
+/* SUMMARY
+/* log that a message was opened
+/* SYNOPSIS
+/* #include <opened.h>
+/*
+/* void opened(queue_id, sender, size, nrcpt, format, ...)
+/* const char *queue_id;
+/* const char *sender;
+/* long size;
+/* int nrcpt;
+/* const char *format;
+/* DESCRIPTION
+/* opened() logs that a message was successfully delivered.
+/*
+/* vopened() implements an alternative interface.
+/*
+/* Arguments:
+/* .IP queue_id
+/* Message queue ID.
+/* .IP sender
+/* Sender address.
+/* .IP size
+/* Message content size.
+/* .IP nrcpt
+/* Number of recipients.
+/* .IP format
+/* Format of optional text.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <opened.h>
+#include <info_log_addr_form.h>
+
+/* opened - log that a message was opened */
+
+void opened(const char *queue_id, const char *sender, long size, int nrcpt,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vopened(queue_id, sender, size, nrcpt, fmt, ap);
+ va_end(ap);
+}
+
+/* vopened - log that a message was opened */
+
+void vopened(const char *queue_id, const char *sender, long size, int nrcpt,
+ const char *fmt, va_list ap)
+{
+ VSTRING *text = vstring_alloc(100);
+
+#define TEXT (vstring_str(text))
+
+ vstring_vsprintf(text, fmt, ap);
+ msg_info("%s: from=<%s>, size=%ld, nrcpt=%d%s%s%s",
+ queue_id, info_log_addr_form_sender(sender), size, nrcpt,
+ *TEXT ? " (" : "", TEXT, *TEXT ? ")" : "");
+ vstring_free(text);
+}
diff --git a/src/global/opened.h b/src/global/opened.h
new file mode 100644
index 0000000..3fc9959
--- /dev/null
+++ b/src/global/opened.h
@@ -0,0 +1,38 @@
+#ifndef _OPENED_H_INCLUDED_
+#define _OPENED_H_INCLUDED_
+
+/*++
+/* NAME
+/* opened 3h
+/* SUMMARY
+/* log that a message was opened
+/* SYNOPSIS
+/* #include <opened.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * External interface.
+ */
+extern void PRINTFLIKE(5, 6) opened(const char *, const char *, long, int,
+ const char *,...);
+extern void vopened(const char *, const char *, long, int,
+ const char *, va_list);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/own_inet_addr.c b/src/global/own_inet_addr.c
new file mode 100644
index 0000000..d164a20
--- /dev/null
+++ b/src/global/own_inet_addr.c
@@ -0,0 +1,315 @@
+/*++
+/* NAME
+/* own_inet_addr 3
+/* SUMMARY
+/* determine if IP address belongs to this mail system instance
+/* SYNOPSIS
+/* #include <own_inet_addr.h>
+/*
+/* int own_inet_addr(addr)
+/* struct sockaddr *addr;
+/*
+/* INET_ADDR_LIST *own_inet_addr_list()
+/*
+/* INET_ADDR_LIST *own_inet_mask_list()
+/*
+/* int proxy_inet_addr(addr)
+/* struct in_addr *addr;
+/*
+/* INET_ADDR_LIST *proxy_inet_addr_list()
+/* DESCRIPTION
+/* own_inet_addr() determines if the specified IP address belongs
+/* to this mail system instance, i.e. if this mail system instance
+/* is supposed to be listening on this specific IP address.
+/*
+/* own_inet_addr_list() returns the list of all addresses that
+/* belong to this mail system instance.
+/*
+/* own_inet_mask_list() returns the list of all corresponding
+/* netmasks.
+/*
+/* proxy_inet_addr() determines if the specified IP address is
+/* listed with the proxy_interfaces configuration parameter.
+/*
+/* proxy_inet_addr_list() returns the list of all addresses that
+/* belong to proxy network interfaces.
+/* 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 <mymalloc.h>
+#include <inet_addr_list.h>
+#include <inet_addr_local.h>
+#include <inet_addr_host.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+
+/* Application-specific. */
+
+static INET_ADDR_LIST saved_addr_list;
+static INET_ADDR_LIST saved_mask_list;
+static INET_ADDR_LIST saved_proxy_list;
+
+/* own_inet_addr_init - initialize my own address list */
+
+static void own_inet_addr_init(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list)
+{
+ INET_ADDR_LIST local_addrs;
+ INET_ADDR_LIST local_masks;
+ char *hosts;
+ char *host;
+ const char *sep = " \t,";
+ char *bufp;
+ int nvirtual;
+ int nlocal;
+ MAI_HOSTADDR_STR hostaddr;
+ struct sockaddr_storage *sa;
+ struct sockaddr_storage *ma;
+
+ inet_addr_list_init(addr_list);
+ inet_addr_list_init(mask_list);
+
+ /*
+ * Avoid run-time errors when all network protocols are disabled. We
+ * can't look up interface information, and we can't convert explicit
+ * names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0) {
+ if (msg_verbose)
+ msg_info("skipping %s setting - "
+ "all network protocols are disabled",
+ VAR_INET_INTERFACES);
+ return;
+ }
+
+ /*
+ * If we are listening on all interfaces (default), ask the system what
+ * the interfaces are.
+ */
+ if (strcmp(var_inet_interfaces, INET_INTERFACES_ALL) == 0) {
+ if (inet_addr_local(addr_list, mask_list,
+ inet_proto_info()->ai_family_list) == 0)
+ msg_fatal("could not find any active network interfaces");
+ }
+
+ /*
+ * Select all loopback interfaces from the system's available interface
+ * list.
+ */
+ else if (strcmp(var_inet_interfaces, INET_INTERFACES_LOCAL) == 0) {
+ inet_addr_list_init(&local_addrs);
+ inet_addr_list_init(&local_masks);
+ if (inet_addr_local(&local_addrs, &local_masks,
+ inet_proto_info()->ai_family_list) == 0)
+ msg_fatal("could not find any active network interfaces");
+ for (sa = local_addrs.addrs, ma = local_masks.addrs;
+ sa < local_addrs.addrs + local_addrs.used; sa++, ma++) {
+ if (sock_addr_in_loopback(SOCK_ADDR_PTR(sa))) {
+ inet_addr_list_append(addr_list, SOCK_ADDR_PTR(sa));
+ inet_addr_list_append(mask_list, SOCK_ADDR_PTR(ma));
+ }
+ }
+ inet_addr_list_free(&local_addrs);
+ inet_addr_list_free(&local_masks);
+ }
+
+ /*
+ * If we are supposed to be listening only on specific interface
+ * addresses (virtual hosting), look up the addresses of those
+ * interfaces.
+ */
+ else {
+ bufp = hosts = mystrdup(var_inet_interfaces);
+ while ((host = mystrtok(&bufp, sep)) != 0)
+ if (inet_addr_host(addr_list, host) == 0)
+ msg_fatal("config variable %s: host not found: %s",
+ VAR_INET_INTERFACES, host);
+ myfree(hosts);
+
+ /*
+ * Weed out duplicate IP addresses. Duplicates happen when the same
+ * IP address is listed under multiple hostnames. If we don't weed
+ * out duplicates, Postfix can suddenly stop working after the DNS is
+ * changed.
+ */
+ inet_addr_list_uniq(addr_list);
+
+ /*
+ * Find out the netmask for each virtual interface, by looking it up
+ * among all the local interfaces.
+ */
+ inet_addr_list_init(&local_addrs);
+ inet_addr_list_init(&local_masks);
+ if (inet_addr_local(&local_addrs, &local_masks,
+ inet_proto_info()->ai_family_list) == 0)
+ msg_fatal("could not find any active network interfaces");
+ for (nvirtual = 0; nvirtual < addr_list->used; nvirtual++) {
+ for (nlocal = 0; /* see below */ ; nlocal++) {
+ if (nlocal >= local_addrs.used) {
+ SOCKADDR_TO_HOSTADDR(
+ SOCK_ADDR_PTR(addr_list->addrs + nvirtual),
+ SOCK_ADDR_LEN(addr_list->addrs + nvirtual),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_fatal("parameter %s: no local interface found for %s",
+ VAR_INET_INTERFACES, hostaddr.buf);
+ }
+ if (SOCK_ADDR_EQ_ADDR(addr_list->addrs + nvirtual,
+ local_addrs.addrs + nlocal)) {
+ inet_addr_list_append(mask_list,
+ SOCK_ADDR_PTR(local_masks.addrs + nlocal));
+ break;
+ }
+ }
+ }
+ inet_addr_list_free(&local_addrs);
+ inet_addr_list_free(&local_masks);
+ }
+}
+
+/* own_inet_addr - is this my own internet address */
+
+int own_inet_addr(struct sockaddr * addr)
+{
+ int i;
+
+ if (saved_addr_list.used == 0)
+ own_inet_addr_init(&saved_addr_list, &saved_mask_list);
+
+ for (i = 0; i < saved_addr_list.used; i++)
+ if (SOCK_ADDR_EQ_ADDR(addr, saved_addr_list.addrs + i))
+ return (1);
+ return (0);
+}
+
+/* own_inet_addr_list - return list of addresses */
+
+INET_ADDR_LIST *own_inet_addr_list(void)
+{
+ if (saved_addr_list.used == 0)
+ own_inet_addr_init(&saved_addr_list, &saved_mask_list);
+
+ return (&saved_addr_list);
+}
+
+/* own_inet_mask_list - return list of addresses */
+
+INET_ADDR_LIST *own_inet_mask_list(void)
+{
+ if (saved_addr_list.used == 0)
+ own_inet_addr_init(&saved_addr_list, &saved_mask_list);
+
+ return (&saved_mask_list);
+}
+
+/* proxy_inet_addr_init - initialize my proxy interface list */
+
+static void proxy_inet_addr_init(INET_ADDR_LIST *addr_list)
+{
+ char *hosts;
+ char *host;
+ const char *sep = " \t,";
+ char *bufp;
+
+ /*
+ * Parse the proxy_interfaces parameter, and expand any symbolic
+ * hostnames into IP addresses.
+ */
+ inet_addr_list_init(addr_list);
+ bufp = hosts = mystrdup(var_proxy_interfaces);
+ while ((host = mystrtok(&bufp, sep)) != 0)
+ if (inet_addr_host(addr_list, host) == 0)
+ msg_fatal("config variable %s: host not found: %s",
+ VAR_PROXY_INTERFACES, host);
+ myfree(hosts);
+
+ /*
+ * Weed out duplicate IP addresses.
+ */
+ inet_addr_list_uniq(addr_list);
+}
+
+/* proxy_inet_addr - is this my proxy internet address */
+
+int proxy_inet_addr(struct sockaddr * addr)
+{
+ int i;
+
+ if (*var_proxy_interfaces == 0)
+ return (0);
+
+ if (saved_proxy_list.used == 0)
+ proxy_inet_addr_init(&saved_proxy_list);
+
+ for (i = 0; i < saved_proxy_list.used; i++)
+ if (SOCK_ADDR_EQ_ADDR(addr, saved_proxy_list.addrs + i))
+ return (1);
+ return (0);
+}
+
+/* proxy_inet_addr_list - return list of addresses */
+
+INET_ADDR_LIST *proxy_inet_addr_list(void)
+{
+ if (*var_proxy_interfaces != 0 && saved_proxy_list.used == 0)
+ proxy_inet_addr_init(&saved_proxy_list);
+
+ return (&saved_proxy_list);
+}
+
+#ifdef TEST
+#include <inet_proto.h>
+
+static void inet_addr_list_print(INET_ADDR_LIST *list)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ struct sockaddr_storage *sa;
+
+ for (sa = list->addrs; sa < list->addrs + list->used; sa++) {
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s", hostaddr.buf);
+ }
+}
+
+char *var_inet_interfaces;
+
+int main(int argc, char **argv)
+{
+ INET_PROTO_INFO *proto_info;
+ INET_ADDR_LIST *list;
+
+ if (argc != 3)
+ msg_fatal("usage: %s protocols interface_list (e.g. \"all all\")",
+ argv[0]);
+ msg_verbose = 10;
+ proto_info = inet_proto_init(argv[0], argv[1]);
+ var_inet_interfaces = argv[2];
+ list = own_inet_addr_list();
+ inet_addr_list_print(list);
+ return (0);
+}
+
+#endif
diff --git a/src/global/own_inet_addr.h b/src/global/own_inet_addr.h
new file mode 100644
index 0000000..2f3d2f7
--- /dev/null
+++ b/src/global/own_inet_addr.h
@@ -0,0 +1,39 @@
+#ifndef _OWN_INET_ADDR_H_INCLUDED_
+#define _OWN_INET_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* own_inet_addr 3h
+/* SUMMARY
+/* determine if IP address belongs to this mail system instance
+/* SYNOPSIS
+/* #include <own_inet_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern int own_inet_addr(struct sockaddr *);
+extern struct INET_ADDR_LIST *own_inet_addr_list(void);
+extern struct INET_ADDR_LIST *own_inet_mask_list(void);
+extern int proxy_inet_addr(struct sockaddr *);
+extern struct INET_ADDR_LIST *proxy_inet_addr_list(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/pipe_command.c b/src/global/pipe_command.c
new file mode 100644
index 0000000..66aec8a
--- /dev/null
+++ b/src/global/pipe_command.c
@@ -0,0 +1,683 @@
+/*++
+/* NAME
+/* pipe_command 3
+/* SUMMARY
+/* deliver message to external command
+/* SYNOPSIS
+/* #include <pipe_command.h>
+/*
+/* int pipe_command(src, why, key, value, ...)
+/* VSTREAM *src;
+/* DSN_BUF *why;
+/* int key;
+/* DESCRIPTION
+/* pipe_command() runs a command with a message as standard
+/* input. A limited amount of standard output and standard error
+/* output is captured for diagnostics purposes.
+/*
+/* If the command invokes exit() with a non-zero status,
+/* the delivery status is taken from an RFC 3463-style code
+/* at the beginning of command output. If that information is
+/* unavailable, the delivery status is taken from the command
+/* exit status as per <sysexits.h>.
+/*
+/* Arguments:
+/* .IP src
+/* An open message queue file, positioned at the start of the actual
+/* message content.
+/* .IP why
+/* Delivery status information. The reason attribute may contain
+/* a limited portion of command output, among other free text.
+/* .IP key
+/* Specifies what value will follow. pipe_command() takes a list
+/* of macros with arguments, terminated by CA_PIPE_CMD_END which
+/* has no argument. The following is a listing of macros and
+/* expected argument types.
+/* .RS
+/* .IP "CA_PIPE_CMD_COMMAND(const char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of CA_PIPE_CMD_COMMAND or CA_PIPE_CMD_ARGV must be specified.
+/* See also the CA_PIPE_CMD_SHELL attribute below.
+/* .IP "CA_PIPE_CMD_ARGV(char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of CA_PIPE_CMD_COMMAND or CA_PIPE_CMD_ARGV must be specified.
+/* .IP "CA_PIPE_CMD_CHROOT(const char *)"
+/* Root and working directory for command execution. This takes
+/* effect before CA_PIPE_CMD_CWD. A null pointer means don't
+/* change root and working directory anyway. Failure to change
+/* directory causes mail delivery to be deferred.
+/* .IP "CA_PIPE_CMD_CWD(const char *)"
+/* Working directory for command execution, after changing process
+/* privileges to CA_PIPE_CMD_UID and CA_PIPE_CMD_GID. A null pointer means
+/* don't change directory anyway. Failure to change directory
+/* causes mail delivery to be deferred.
+/* .IP "CA_PIPE_CMD_ENV(char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "CA_PIPE_CMD_EXPORT(char **)"
+/* Null-terminated array with names of environment parameters
+/* that can be exported. By default, everything is exported.
+/* .IP "CA_PIPE_CMD_COPY_FLAGS(int)"
+/* Flags that are passed on to the \fImail_copy\fR() routine.
+/* The default flags value is 0 (zero).
+/* .IP "CA_PIPE_CMD_SENDER(const char *)"
+/* The envelope sender address, which is passed on to the
+/* \fImail_copy\fR() routine.
+/* .IP "CA_PIPE_CMD_ORIG_RCPT(const char *)"
+/* The original recipient envelope address, which is passed on
+/* to the \fImail_copy\fR() routine.
+/* .IP "CA_PIPE_CMD_DELIVERED(const char *)"
+/* The recipient envelope address, which is passed on to the
+/* \fImail_copy\fR() routine.
+/* .IP "CA_PIPE_CMD_EOL(const char *)"
+/* End-of-line delimiter. The default is to use the newline character.
+/* .IP "CA_PIPE_CMD_UID(uid_t)"
+/* The user ID to execute the command as. The default is
+/* the user ID corresponding to the \fIdefault_privs\fR
+/* configuration parameter. The user ID must be non-zero.
+/* .IP "CA_PIPE_CMD_GID(gid_t)"
+/* The group ID to execute the command as. The default is
+/* the group ID corresponding to the \fIdefault_privs\fR
+/* configuration parameter. The group ID must be non-zero.
+/* .IP "CA_PIPE_CMD_TIME_LIMIT(int)"
+/* The amount of time the command is allowed to run before it
+/* is terminated with SIGKILL. A non-negative CA_PIPE_CMD_TIME_LIMIT
+/* value must be specified.
+/* .IP "CA_PIPE_CMD_SHELL(const char *)"
+/* The shell to use when executing the command specified with
+/* CA_PIPE_CMD_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: interface violations (for example, a zero-valued
+/* user ID or group ID, or a missing command).
+/*
+/* pipe_command() returns one of the following status codes:
+/* .IP PIPE_STAT_OK
+/* The command has taken responsibility for further delivery of
+/* the message.
+/* .IP PIPE_STAT_DEFER
+/* The command failed with a "try again" type error.
+/* The reason is given via the \fIwhy\fR argument.
+/* .IP PIPE_STAT_BOUNCE
+/* The command indicated that the message was not acceptable,
+/* or the command did not finish within the time limit.
+/* The reason is given via the \fIwhy\fR argument.
+/* .IP PIPE_STAT_CORRUPT
+/* The queue file is corrupted.
+/* SEE ALSO
+/* mail_copy(3) deliver to any.
+/* mark_corrupt(3) mark queue file as corrupt.
+/* sys_exits(3) sendmail-compatible exit status codes.
+/* 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/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <iostuff.h>
+#include <timed_wait.h>
+#include <set_ugid.h>
+#include <set_eugid.h>
+#include <argv.h>
+#include <chroot_uid.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_copy.h>
+#include <clean_env.h>
+#include <pipe_command.h>
+#include <exec_command.h>
+#include <sys_exits.h>
+#include <dsn_util.h>
+#include <dsn_buf.h>
+
+/* Application-specific. */
+
+struct pipe_args {
+ int flags; /* see mail_copy.h */
+ char *sender; /* envelope sender */
+ char *orig_rcpt; /* original recipient */
+ char *delivered; /* envelope recipient */
+ char *eol; /* carriagecontrol */
+ char **argv; /* either an array */
+ char *command; /* or a plain string */
+ uid_t uid; /* privileges */
+ gid_t gid; /* privileges */
+ char **env; /* extra environment */
+ char **export; /* exportable environment */
+ char *shell; /* command shell */
+ char *cwd; /* preferred working directory */
+ char *chroot; /* root directory */
+};
+
+static int pipe_command_timeout; /* command has timed out */
+static int pipe_command_maxtime; /* available time to complete */
+
+/* get_pipe_args - capture the variadic argument list */
+
+static void get_pipe_args(struct pipe_args * args, va_list ap)
+{
+ const char *myname = "get_pipe_args";
+ int key;
+
+ /*
+ * First, set the default values.
+ */
+ args->flags = 0;
+ args->sender = 0;
+ args->orig_rcpt = 0;
+ args->delivered = 0;
+ args->eol = "\n";
+ args->argv = 0;
+ args->command = 0;
+ args->uid = var_default_uid;
+ args->gid = var_default_gid;
+ args->env = 0;
+ args->export = 0;
+ args->shell = 0;
+ args->cwd = 0;
+ args->chroot = 0;
+
+ pipe_command_maxtime = -1;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ while ((key = va_arg(ap, int)) != PIPE_CMD_END) {
+ switch (key) {
+ case PIPE_CMD_COPY_FLAGS:
+ args->flags |= va_arg(ap, int);
+ break;
+ case PIPE_CMD_SENDER:
+ args->sender = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_ORIG_RCPT:
+ args->orig_rcpt = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_DELIVERED:
+ args->delivered = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_EOL:
+ args->eol = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_ARGV:
+ if (args->command)
+ msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case PIPE_CMD_COMMAND:
+ if (args->argv)
+ msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_UID:
+ args->uid = va_arg(ap, uid_t); /* in case uid_t is short */
+ break;
+ case PIPE_CMD_GID:
+ args->gid = va_arg(ap, gid_t); /* in case gid_t is short */
+ break;
+ case PIPE_CMD_TIME_LIMIT:
+ pipe_command_maxtime = va_arg(ap, int);
+ break;
+ case PIPE_CMD_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case PIPE_CMD_EXPORT:
+ args->export = va_arg(ap, char **);
+ break;
+ case PIPE_CMD_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_CWD:
+ args->cwd = va_arg(ap, char *);
+ break;
+ case PIPE_CMD_CHROOT:
+ args->chroot = va_arg(ap, char *);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing PIPE_CMD_ARGV or PIPE_CMD_COMMAND", myname);
+ if (args->uid == 0)
+ msg_panic("%s: privileged uid", myname);
+ if (args->gid == 0)
+ msg_panic("%s: privileged gid", myname);
+ if (pipe_command_maxtime < 0)
+ msg_panic("%s: missing or invalid PIPE_CMD_TIME_LIMIT", myname);
+}
+
+/* pipe_command_write - write to command with time limit */
+
+static ssize_t pipe_command_write(int fd, void *buf, size_t len,
+ int unused_timeout,
+ void *unused_context)
+{
+ int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0;
+ const char *myname = "pipe_command_write";
+
+ /*
+ * Don't wait when all available time was already used up.
+ */
+ if (write_wait(fd, maxtime) < 0) {
+ if (pipe_command_timeout == 0) {
+ msg_warn("%s: write time limit exceeded", myname);
+ pipe_command_timeout = 1;
+ }
+ return (0);
+ } else {
+ return (write(fd, buf, len));
+ }
+}
+
+/* pipe_command_read - read from command with time limit */
+
+static ssize_t pipe_command_read(int fd, void *buf, size_t len,
+ int unused_timeout,
+ void *unused_context)
+{
+ int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0;
+ const char *myname = "pipe_command_read";
+
+ /*
+ * Don't wait when all available time was already used up.
+ */
+ if (read_wait(fd, maxtime) < 0) {
+ if (pipe_command_timeout == 0) {
+ msg_warn("%s: read time limit exceeded", myname);
+ pipe_command_timeout = 1;
+ }
+ return (0);
+ } else {
+ return (read(fd, buf, len));
+ }
+}
+
+/* kill_command - terminate command forcibly */
+
+static void kill_command(pid_t pid, int sig, uid_t kill_uid, gid_t kill_gid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+
+ /*
+ * Switch privileges to that of the child process. Terminate the child
+ * and its offspring.
+ */
+ set_eugid(kill_uid, kill_gid);
+ if (kill(-pid, sig) < 0 && kill(pid, sig) < 0)
+ msg_warn("cannot kill process (group) %lu: %m",
+ (unsigned long) pid);
+ set_eugid(saved_euid, saved_egid);
+}
+
+/* pipe_command_wait_or_kill - wait for command with time limit, or kill it */
+
+static int pipe_command_wait_or_kill(pid_t pid, WAIT_STATUS_T *statusp, int sig,
+ uid_t kill_uid, gid_t kill_gid)
+{
+ int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 1;
+ const char *myname = "pipe_command_wait_or_kill";
+ int n;
+
+ /*
+ * Don't wait when all available time was already used up.
+ */
+ if ((n = timed_waitpid(pid, statusp, 0, maxtime)) < 0 && errno == ETIMEDOUT) {
+ if (pipe_command_timeout == 0) {
+ msg_warn("%s: child wait time limit exceeded", myname);
+ pipe_command_timeout = 1;
+ }
+ kill_command(pid, sig, kill_uid, kill_gid);
+ n = waitpid(pid, statusp, 0);
+ }
+ return (n);
+}
+
+/* pipe_child_cleanup - child fatal error handler */
+
+static void pipe_child_cleanup(void)
+{
+
+ /*
+ * WARNING: don't place code here. This code may run as mail_owner, as
+ * root, or as the user/group specified with the "user" attribute. The
+ * only safe action is to terminate.
+ *
+ * Future proofing. If you need exit() here then you broke Postfix.
+ */
+ _exit(EX_TEMPFAIL);
+}
+
+/* pipe_command - execute command with extreme prejudice */
+
+int pipe_command(VSTREAM *src, DSN_BUF *why,...)
+{
+ const char *myname = "pipe_command";
+ va_list ap;
+ VSTREAM *cmd_in_stream;
+ VSTREAM *cmd_out_stream;
+ char log_buf[VSTREAM_BUFSIZE + 1];
+ ssize_t log_len;
+ pid_t pid;
+ int write_status;
+ int write_errno;
+ WAIT_STATUS_T wait_status;
+ int cmd_in_pipe[2];
+ int cmd_out_pipe[2];
+ struct pipe_args args;
+ char **cpp;
+ ARGV *argv;
+ DSN_SPLIT dp;
+ const SYS_EXITS_DETAIL *sp;
+
+ /*
+ * Process the variadic argument list. This also does sanity checks on
+ * what data the caller is passing to us.
+ */
+ va_start(ap, why);
+ get_pipe_args(&args, ap);
+ va_end(ap);
+
+ /*
+ * For convenience...
+ */
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ /*
+ * Set up pipes that connect us to the command input and output streams.
+ * We're using a rather disgusting hack to capture command output: set
+ * the output to non-blocking mode, and don't attempt to read the output
+ * until AFTER the process has terminated. The rationale for this is: 1)
+ * the command output will be used only when delivery fails; 2) the
+ * amount of output is expected to be small; 3) the output can be
+ * truncated without too much loss. I could even argue that truncating
+ * the amount of diagnostic output is a good thing to do, but I won't go
+ * that far.
+ *
+ * Turn on non-blocking writes to the child process so that we can enforce
+ * timeouts after partial writes.
+ *
+ * XXX Too much trouble with different systems returning weird write()
+ * results when a pipe is writable.
+ */
+ if (pipe(cmd_in_pipe) < 0 || pipe(cmd_out_pipe) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ non_blocking(cmd_out_pipe[1], NON_BLOCKING);
+#if 0
+ non_blocking(cmd_in_pipe[1], NON_BLOCKING);
+#endif
+
+ /*
+ * Spawn off a child process and irrevocably change privilege to the
+ * user. This includes revoking all rights on open files (via the close
+ * on exec flag). If we cannot run the command now, try again some time
+ * later.
+ */
+ switch (pid = fork()) {
+
+ /*
+ * Error. Instead of trying again right now, back off, give the
+ * system a chance to recover, and try again later.
+ */
+ case -1:
+ msg_warn("fork: %m");
+ dsb_unix(why, "4.3.0", sys_exits_detail(EX_OSERR)->text,
+ "Delivery failed: %m");
+ return (PIPE_STAT_DEFER);
+
+ /*
+ * Child. Run the child in a separate process group so that the
+ * parent can kill not just the child but also its offspring.
+ *
+ * Redirect fatal exits to our own fatal exit handler (never leave the
+ * parent's handler enabled :-) so we can replace random exit status
+ * codes by EX_TEMPFAIL.
+ */
+ case 0:
+ (void) msg_cleanup(pipe_child_cleanup);
+
+ /*
+ * In order to chroot it is necessary to switch euid back to root.
+ * Right after chroot we call set_ugid() so all privileges will be
+ * dropped again.
+ *
+ * XXX For consistency we use chroot_uid() to change root+current
+ * directory. However, we must not use chroot_uid() to change process
+ * privileges (assuming a version that accepts numeric privileges).
+ * That would create a maintenance problem, because we would have two
+ * different code paths to set the external command's privileges.
+ */
+ if (args.chroot) {
+ seteuid(0);
+ chroot_uid(args.chroot, (char *) 0);
+ }
+
+ /*
+ * XXX If we put code before the set_ugid() call, then the code that
+ * changes root directory must switch back to the mail_owner UID,
+ * otherwise we'd be running with root privileges.
+ */
+ set_ugid(args.uid, args.gid);
+ if (setsid() < 0)
+ msg_warn("setsid failed: %m");
+
+ /*
+ * Pipe plumbing.
+ */
+ close(cmd_in_pipe[1]);
+ close(cmd_out_pipe[0]);
+ if (DUP2(cmd_in_pipe[0], STDIN_FILENO) < 0
+ || DUP2(cmd_out_pipe[1], STDOUT_FILENO) < 0
+ || DUP2(cmd_out_pipe[1], STDERR_FILENO) < 0)
+ msg_fatal("%s: dup2: %m", myname);
+ close(cmd_in_pipe[0]);
+ close(cmd_out_pipe[1]);
+
+ /*
+ * Working directory plumbing.
+ */
+ if (args.cwd && chdir(args.cwd) < 0)
+ msg_fatal("cannot change directory to \"%s\" for uid=%lu gid=%lu: %m",
+ args.cwd, (unsigned long) args.uid,
+ (unsigned long) args.gid);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ if (args.export)
+ clean_env(args.export);
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ *
+ * As a safety for buggy libraries, we close the syslog socket.
+ * Otherwise we could leak a file descriptor that was created by a
+ * privileged process.
+ *
+ * XXX To avoid losing fatal error messages we open a VSTREAM and
+ * capture the output in the parent process.
+ */
+ closelog();
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, CHARS_SPACE);
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+
+ /*
+ * Parent.
+ */
+ default:
+ close(cmd_in_pipe[0]);
+ close(cmd_out_pipe[1]);
+
+ cmd_in_stream = vstream_fdopen(cmd_in_pipe[1], O_WRONLY);
+ cmd_out_stream = vstream_fdopen(cmd_out_pipe[0], O_RDONLY);
+
+ /*
+ * Give the command a limited amount of time to run, by enforcing
+ * timeouts on all I/O from and to it.
+ */
+ vstream_control(cmd_in_stream,
+ CA_VSTREAM_CTL_WRITE_FN(pipe_command_write),
+ CA_VSTREAM_CTL_END);
+ vstream_control(cmd_out_stream,
+ CA_VSTREAM_CTL_READ_FN(pipe_command_read),
+ CA_VSTREAM_CTL_END);
+ pipe_command_timeout = 0;
+
+ /*
+ * Pipe the message into the command. Examine the error report only
+ * if we can't recognize a more specific error from the command exit
+ * status or from the command output.
+ */
+ write_status = mail_copy(args.sender, args.orig_rcpt,
+ args.delivered, src,
+ cmd_in_stream, args.flags,
+ args.eol, why);
+ write_errno = errno;
+
+ /*
+ * Capture a limited amount of command output, for inclusion in a
+ * bounce message. Turn tabs and newlines into whitespace, and
+ * replace other non-printable characters by underscore.
+ */
+ log_len = vstream_fread(cmd_out_stream, log_buf, sizeof(log_buf) - 1);
+ (void) vstream_fclose(cmd_out_stream);
+ log_buf[log_len] = 0;
+ translit(log_buf, "\t\n", " ");
+ printable(log_buf, '_');
+
+ /*
+ * Just because the child closes its output streams, don't assume
+ * that it will terminate. Instead, be prepared for the situation
+ * that the child does not terminate, even when the parent
+ * experiences no read/write timeout. Make sure that the child
+ * terminates before the parent attempts to retrieve its exit status,
+ * otherwise the parent could become stuck, and the mail system would
+ * eventually run out of delivery agents. Do a thorough job, and kill
+ * not just the child process but also its offspring.
+ */
+ if (pipe_command_timeout)
+ kill_command(pid, SIGKILL, args.uid, args.gid);
+ if (pipe_command_wait_or_kill(pid, &wait_status, SIGKILL,
+ args.uid, args.gid) < 0)
+ msg_fatal("wait: %m");
+ if (pipe_command_timeout) {
+ dsb_unix(why, "5.3.0", log_len ?
+ log_buf : sys_exits_detail(EX_SOFTWARE)->text,
+ "Command time limit exceeded: \"%s\"%s%s",
+ args.command,
+ log_len ? ". Command output: " : "", log_buf);
+ return (PIPE_STAT_BOUNCE);
+ }
+
+ /*
+ * Command exits. Give special treatment to sendmail style exit
+ * status codes.
+ */
+ if (!NORMAL_EXIT_STATUS(wait_status)) {
+ if (WIFSIGNALED(wait_status)) {
+ dsb_unix(why, "4.3.0", log_len ?
+ log_buf : sys_exits_detail(EX_SOFTWARE)->text,
+ "Command died with signal %d: \"%s\"%s%s",
+ WTERMSIG(wait_status), args.command,
+ log_len ? ". Command output: " : "", log_buf);
+ return (PIPE_STAT_DEFER);
+ }
+ /* Use "D.S.N text" command output. XXX What diagnostic code? */
+ else if (dsn_valid(log_buf) > 0) {
+ dsn_split(&dp, "5.3.0", log_buf);
+ dsb_unix(why, DSN_STATUS(dp.dsn), dp.text, "%s", dp.text);
+ return (DSN_CLASS(dp.dsn) == '4' ?
+ PIPE_STAT_DEFER : PIPE_STAT_BOUNCE);
+ }
+ /* Use <sysexits.h> compatible exit status. */
+ else if (SYS_EXITS_CODE(WEXITSTATUS(wait_status))) {
+ sp = sys_exits_detail(WEXITSTATUS(wait_status));
+ dsb_unix(why, sp->dsn,
+ log_len ? log_buf : sp->text, "%s%s%s", sp->text,
+ log_len ? ". Command output: " : "", log_buf);
+ return (sp->dsn[0] == '4' ?
+ PIPE_STAT_DEFER : PIPE_STAT_BOUNCE);
+ }
+
+ /*
+ * No "D.S.N text" or <sysexits.h> compatible status. Fake it.
+ */
+ else {
+ sp = sys_exits_detail(WEXITSTATUS(wait_status));
+ dsb_unix(why, sp->dsn,
+ log_len ? log_buf : sp->text,
+ "Command died with status %d: \"%s\"%s%s",
+ WEXITSTATUS(wait_status), args.command,
+ log_len ? ". Command output: " : "", log_buf);
+ return (PIPE_STAT_BOUNCE);
+ }
+ } else if (write_status &
+ MAIL_COPY_STAT_CORRUPT) {
+ return (PIPE_STAT_CORRUPT);
+ } else if (write_status && write_errno != EPIPE) {
+ vstring_prepend(why->reason, "Command failed: ",
+ sizeof("Command failed: ") - 1);
+ vstring_sprintf_append(why->reason, ": \"%s\"", args.command);
+ return (PIPE_STAT_BOUNCE);
+ } else {
+ vstring_strcpy(why->reason, log_buf);
+ return (PIPE_STAT_OK);
+ }
+ }
+}
diff --git a/src/global/pipe_command.h b/src/global/pipe_command.h
new file mode 100644
index 0000000..f81801d
--- /dev/null
+++ b/src/global/pipe_command.h
@@ -0,0 +1,94 @@
+#ifndef _PIPE_COMMAND_H_INCLUDED_
+#define _PIPE_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* pipe_command 3h
+/* SUMMARY
+/* deliver message to external command
+/* SYNOPSIS
+/* #include <pipe_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <check_arg.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_copy.h>
+#include <dsn_buf.h>
+
+ /*
+ * Legacy API: type-unchecked arguments, internal use.
+ */
+#define PIPE_CMD_END 0 /* terminator */
+#define PIPE_CMD_COMMAND 1 /* command is string */
+#define PIPE_CMD_ARGV 2 /* command is array */
+#define PIPE_CMD_COPY_FLAGS 3 /* mail_copy() flags */
+#define PIPE_CMD_SENDER 4 /* mail_copy() sender */
+#define PIPE_CMD_DELIVERED 5 /* mail_copy() recipient */
+#define PIPE_CMD_UID 6 /* privileges */
+#define PIPE_CMD_GID 7 /* privileges */
+#define PIPE_CMD_TIME_LIMIT 8 /* time limit */
+#define PIPE_CMD_ENV 9 /* extra environment */
+#define PIPE_CMD_SHELL 10 /* alternative shell */
+#define PIPE_CMD_EOL 11 /* record delimiter */
+#define PIPE_CMD_EXPORT 12 /* exportable environment */
+#define PIPE_CMD_ORIG_RCPT 13 /* mail_copy() original recipient */
+#define PIPE_CMD_CWD 14 /* working directory */
+#define PIPE_CMD_CHROOT 15 /* chroot() before exec() */
+
+ /*
+ * Safer API: type-checked arguments, external use.
+ */
+#define CA_PIPE_CMD_END PIPE_CMD_END
+#define CA_PIPE_CMD_COMMAND(v) PIPE_CMD_COMMAND, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_ARGV(v) PIPE_CMD_ARGV, CHECK_PPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_COPY_FLAGS(v) PIPE_CMD_COPY_FLAGS, CHECK_VAL(PIPE_CMD, int, (v))
+#define CA_PIPE_CMD_SENDER(v) PIPE_CMD_SENDER, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_DELIVERED(v) PIPE_CMD_DELIVERED, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_UID(v) PIPE_CMD_UID, CHECK_VAL(PIPE_CMD, uid_t, (v))
+#define CA_PIPE_CMD_GID(v) PIPE_CMD_GID, CHECK_VAL(PIPE_CMD, gid_t, (v))
+#define CA_PIPE_CMD_TIME_LIMIT(v) PIPE_CMD_TIME_LIMIT, CHECK_VAL(PIPE_CMD, int, (v))
+#define CA_PIPE_CMD_ENV(v) PIPE_CMD_ENV, CHECK_PPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_SHELL(v) PIPE_CMD_SHELL, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_EOL(v) PIPE_CMD_EOL, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_EXPORT(v) PIPE_CMD_EXPORT, CHECK_PPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_ORIG_RCPT(v) PIPE_CMD_ORIG_RCPT, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_CWD(v) PIPE_CMD_CWD, CHECK_CPTR(PIPE_CMD, char, (v))
+#define CA_PIPE_CMD_CHROOT(v) PIPE_CMD_CHROOT, CHECK_CPTR(PIPE_CMD, char, (v))
+
+CHECK_VAL_HELPER_DCL(PIPE_CMD, uid_t);
+CHECK_VAL_HELPER_DCL(PIPE_CMD, int);
+CHECK_VAL_HELPER_DCL(PIPE_CMD, gid_t);
+CHECK_PPTR_HELPER_DCL(PIPE_CMD, char);
+CHECK_CPTR_HELPER_DCL(PIPE_CMD, char);
+
+ /*
+ * Command completion status.
+ */
+#define PIPE_STAT_OK 0 /* success */
+#define PIPE_STAT_DEFER 1 /* try again */
+#define PIPE_STAT_BOUNCE 2 /* failed */
+#define PIPE_STAT_CORRUPT 3 /* corrupted file */
+
+extern int pipe_command(VSTREAM *, DSN_BUF *,...);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/post_mail.c b/src/global/post_mail.c
new file mode 100644
index 0000000..e7a9a67
--- /dev/null
+++ b/src/global/post_mail.c
@@ -0,0 +1,571 @@
+/*++
+/* NAME
+/* post_mail 3
+/* SUMMARY
+/* convenient mail posting interface
+/* SYNOPSIS
+/* #include <post_mail.h>
+/*
+/* VSTREAM *post_mail_fopen(sender, recipient, source_class, trace_flags,
+/* utf8_flags, queue_id)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/*
+/* VSTREAM *post_mail_fopen_nowait(sender, recipient, source_class,
+/* trace_flags, utf8_flags, queue_id)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/*
+/* void post_mail_fopen_async(sender, recipient, source_class,
+/* trace_flags, utf8_flags,
+/* queue_id, notify, context)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/* void (*notify)(VSTREAM *stream, void *context);
+/* void *context;
+/*
+/* int post_mail_fprintf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* int post_mail_fputs(stream, str)
+/* VSTREAM *stream;
+/* const char *str;
+/*
+/* int post_mail_buffer(stream, buf, len)
+/* VSTREAM *stream;
+/* const char *buffer;
+/*
+/* int POST_MAIL_BUFFER(stream, buf)
+/* VSTREAM *stream;
+/* VSTRING *buffer;
+/*
+/* int post_mail_fclose(stream)
+/* VSTREAM *STREAM;
+/*
+/* void post_mail_fclose_async(stream, notify, context)
+/* VSTREAM *stream;
+/* void (*notify)(int status, void *context);
+/* void *context;
+/* DESCRIPTION
+/* This module provides a convenient interface for the most
+/* common case of sending one message to one recipient. It
+/* allows the application to concentrate on message content,
+/* without having to worry about queue file structure details.
+/*
+/* post_mail_fopen() opens a connection to the cleanup service
+/* and waits until the service is available, does some option
+/* negotiation, generates message envelope records, and generates
+/* Received: and Date: message headers. The result is a stream
+/* handle that can be used for sending message records.
+/*
+/* post_mail_fopen_nowait() tries to contact the cleanup service
+/* only once, and does not wait until the cleanup service is
+/* available. Otherwise it is identical to post_mail_fopen().
+/*
+/* post_mail_fopen_async() contacts the cleanup service and
+/* invokes the caller-specified notify routine, with the
+/* open stream and the caller-specified context when the
+/* service responds, or with a null stream and the caller-specified
+/* context when the request could not be completed. It is the
+/* responsibility of the application to close an open stream.
+/*
+/* post_mail_fprintf() formats message content (header or body)
+/* and sends it to the cleanup service.
+/*
+/* post_mail_fputs() sends pre-formatted content (header or body)
+/* to the cleanup service.
+/*
+/* post_mail_buffer() sends a pre-formatted buffer to the
+/* cleanup service.
+/*
+/* POST_MAIL_BUFFER() is a wrapper for post_mail_buffer() that
+/* evaluates its buffer argument more than once.
+/*
+/* post_mail_fclose() completes the posting of a message.
+/*
+/* post_mail_fclose_async() completes the posting of a message
+/* and upon completion invokes the caller-specified notify
+/* routine, with the cleanup status and caller-specified context
+/* as arguments.
+/*
+/* Arguments:
+/* .IP sender
+/* The sender envelope address. It is up to the application
+/* to produce From: headers.
+/* .IP recipient
+/* The recipient envelope address. It is up to the application
+/* to produce To: headers.
+/* .IP source_class
+/* The message source class, as defined in \fB<mail_proto.h>\fR.
+/* Depending on the setting of the internal_mail_source_classes
+/* and smtputf8_autodetect_classes parameters, the message
+/* will or won't be subject to content inspection or SMTPUTF8
+/* autodetection.
+/* .IP trace_flags
+/* Message tracing flags as specified in \fB<deliver_request.h>\fR.
+/* .IP utf8_flags
+/* Flags defined in <smtputf8.h>. Flags other than
+/* SMTPUTF8_FLAG_REQUESTED are ignored.
+/* .IP queue_id
+/* Null pointer, or pointer to buffer that receives the queue
+/* ID of the new message.
+/* .IP stream
+/* A stream opened by mail_post_fopen().
+/* .IP notify
+/* Application call-back routine.
+/* .IP context
+/* Application call-back context.
+/* DIAGNOSTICS
+/* post_mail_fopen_nowait() returns a null pointer when the
+/* cleanup service is not available immediately.
+/*
+/* post_mail_fopen_async() returns a null pointer when the
+/* attempt to contact the cleanup service fails immediately.
+/*
+/* post_mail_fprintf(), post_mail_fputs() post_mail_fclose(),
+/* and post_mail_buffer() return the binary OR of the error
+/* status codes defined in \fI<cleanup_user.h>\fR.
+/*
+/* Fatal errors: cleanup initial handshake errors. This means
+/* the client and server speak incompatible protocols.
+/* SEE ALSO
+/* cleanup_user(3h) cleanup options and results
+/* cleanup_strerror(3) translate results to text
+/* cleanup(8) cleanup service
+/* 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/time.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <post_mail.h>
+#include <mail_date.h>
+
+ /*
+ * Call-back state for asynchronous connection requests.
+ */
+typedef struct {
+ char *sender;
+ char *recipient;
+ int source_class;
+ int trace_flags;
+ int utf8_flags;
+ POST_MAIL_NOTIFY notify;
+ void *context;
+ VSTREAM *stream;
+ VSTRING *queue_id;
+} POST_MAIL_STATE;
+
+ /*
+ * Call-back state for asynchronous close requests.
+ */
+typedef struct {
+ int status;
+ VSTREAM *stream;
+ POST_MAIL_FCLOSE_NOTIFY notify;
+ void *context;
+} POST_MAIL_FCLOSE_STATE;
+
+/* post_mail_init - initial negotiations */
+
+static void post_mail_init(VSTREAM *stream, const char *sender,
+ const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTRING *id = queue_id ? queue_id : vstring_alloc(100);
+ struct timeval now;
+ const char *date;
+ int cleanup_flags =
+ int_filt_flags(source_class) | CLEANUP_FLAG_MASK_INTERNAL
+ | smtputf8_autodetect(source_class)
+ | ((utf8_flags & SMTPUTF8_FLAG_REQUESTED) ? CLEANUP_FLAG_SMTPUTF8 : 0);
+
+ GETTIMEOFDAY(&now);
+ date = mail_date(now.tv_sec);
+
+ /*
+ * The comment in the next paragraph is likely obsolete. Fix 20030610
+ * changed the verify server to use asynchronous submission of mail
+ * probes, to avoid blocking the post_mail client for in_flow_delay
+ * seconds when the cleanup service receives email messages faster than
+ * they are delivered. Instead, the post_mail client waits until the
+ * cleanup server announces its availability to receive input. A similar
+ * change was made at the end of submission, to avoid blocking the
+ * post_mail client for up to trigger_timeout seconds when the cleanup
+ * server attempts to notify a queue manager that is overwhelmed.
+ *
+ * XXX Don't flush buffers while sending the initial message records. That
+ * would cause deadlock between verify(8) and cleanup(8) servers.
+ */
+ vstream_control(stream, VSTREAM_CTL_BUFSIZE, 2 * VSTREAM_BUFSIZE,
+ VSTREAM_CTL_END);
+
+ /*
+ * Negotiate with the cleanup service. Give up if we can't agree.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ ATTR_TYPE_END) != 1
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to contact the %s service", var_cleanup_service);
+
+ /*
+ * Generate a minimal envelope section. The cleanup service will add a
+ * size record.
+ */
+ rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(now));
+ rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
+ rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_TRACE_FLAGS, trace_flags);
+ rec_fputs(stream, REC_TYPE_FROM, sender);
+ rec_fputs(stream, REC_TYPE_RCPT, recipient);
+ rec_fputs(stream, REC_TYPE_MESG, "");
+
+ /*
+ * Do the Received: and Date: header lines. This allows us to shave a few
+ * cycles by using the expensive date conversion result for both.
+ */
+ post_mail_fprintf(stream, "Received: by %s (%s)",
+ var_myhostname, var_mail_name);
+ post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
+ post_mail_fprintf(stream, "Date: %s", date);
+ if (queue_id == 0)
+ vstring_free(id);
+}
+
+/* post_mail_fopen - prepare for posting a message */
+
+VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTREAM *stream;
+
+ stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
+ post_mail_init(stream, sender, recipient, source_class, trace_flags,
+ utf8_flags, queue_id);
+ return (stream);
+}
+
+/* post_mail_fopen_nowait - prepare for posting a message */
+
+VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTREAM *stream;
+
+ if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
+ BLOCKING)) != 0)
+ post_mail_init(stream, sender, recipient, source_class, trace_flags,
+ utf8_flags, queue_id);
+ else
+ msg_warn("connect to %s/%s: %m",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ return (stream);
+}
+
+/* post_mail_open_event - handle asynchronous connection events */
+
+static void post_mail_open_event(int event, void *context)
+{
+ POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
+ const char *myname = "post_mail_open_event";
+
+ switch (event) {
+
+ /*
+ * Initial server reply. Stop the watchdog timer, disable further
+ * read events that end up calling this function, and notify the
+ * requestor.
+ */
+ case EVENT_READ:
+ if (msg_verbose)
+ msg_info("%s: read event", myname);
+ event_cancel_timer(post_mail_open_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ non_blocking(vstream_fileno(state->stream), BLOCKING);
+ post_mail_init(state->stream, state->sender,
+ state->recipient, state->source_class,
+ state->trace_flags, state->utf8_flags,
+ state->queue_id);
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify(state->stream, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * No connection or no initial reply within a conservative time
+ * limit. The system is broken and we give up.
+ */
+ case EVENT_TIME:
+ if (state->stream) {
+ msg_warn("timeout connecting to service: %s", var_cleanup_service);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ } else {
+ msg_warn("connect to service: %s: %m", var_cleanup_service);
+ }
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify((VSTREAM *) 0, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * Some exception.
+ */
+ case EVENT_XCPT:
+ msg_warn("error connecting to service: %s", var_cleanup_service);
+ event_cancel_timer(post_mail_open_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify((VSTREAM *) 0, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * Broken software or hardware.
+ */
+ default:
+ msg_panic("%s: unknown event type %d", myname, event);
+ }
+}
+
+/* post_mail_fopen_async - prepare for posting a message */
+
+void post_mail_fopen_async(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id,
+ void (*notify) (VSTREAM *, void *),
+ void *context)
+{
+ VSTREAM *stream;
+ POST_MAIL_STATE *state;
+
+ stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
+ state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
+ state->sender = mystrdup(sender);
+ state->recipient = mystrdup(recipient);
+ state->source_class = source_class;
+ state->trace_flags = trace_flags;
+ state->utf8_flags = utf8_flags;
+ state->notify = notify;
+ state->context = context;
+ state->stream = stream;
+ state->queue_id = queue_id;
+
+ /*
+ * To keep interfaces as simple as possible we report all errors via the
+ * same interface as all successes.
+ */
+ if (stream != 0) {
+ event_enable_read(vstream_fileno(stream), post_mail_open_event,
+ (void *) state);
+ event_request_timer(post_mail_open_event, (void *) state,
+ var_daemon_timeout);
+ } else {
+ event_request_timer(post_mail_open_event, (void *) state, 0);
+ }
+}
+
+/* post_mail_fprintf - format and send message content */
+
+int post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
+{
+ int status;
+ va_list ap;
+
+ va_start(ap, format);
+ status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
+ va_end(ap);
+ return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_buffer - send pre-formatted buffer */
+
+int post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
+{
+ return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
+ CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_fputs - send pre-formatted message content */
+
+int post_mail_fputs(VSTREAM *cleanup, const char *str)
+{
+ ssize_t len = str ? strlen(str) : 0;
+
+ return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
+ CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_fclose - finish posting of message */
+
+int post_mail_fclose(VSTREAM *cleanup)
+{
+ int status = 0;
+
+ /*
+ * Send the message end marker only when there were no errors.
+ */
+ if (vstream_ferror(cleanup) != 0) {
+ status = CLEANUP_STAT_WRITE;
+ } else {
+ rec_fputs(cleanup, REC_TYPE_XTRA, "");
+ rec_fputs(cleanup, REC_TYPE_END, "");
+ if (vstream_fflush(cleanup)
+ || attr_scan(cleanup, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = CLEANUP_STAT_WRITE;
+ }
+ (void) vstream_fclose(cleanup);
+ return (status);
+}
+
+/* post_mail_fclose_event - event handler */
+
+static void post_mail_fclose_event(int event, void *context)
+{
+ POST_MAIL_FCLOSE_STATE *state = (POST_MAIL_FCLOSE_STATE *) context;
+ int status = state->status;
+
+ switch (event) {
+
+ /*
+ * Final server reply. Pick up the completion status.
+ */
+ case EVENT_READ:
+ if (status == 0) {
+ if (vstream_ferror(state->stream) != 0
+ || attr_scan(state->stream, ATTR_FLAG_MISSING,
+ ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
+ ATTR_TYPE_END) != 1)
+ status = CLEANUP_STAT_WRITE;
+ }
+ break;
+
+ /*
+ * No response or error.
+ */
+ default:
+ msg_warn("error talking to service: %s", var_cleanup_service);
+ status = CLEANUP_STAT_WRITE;
+ break;
+ }
+
+ /*
+ * Stop the watchdog timer, and disable further read events that end up
+ * calling this function.
+ */
+ event_cancel_timer(post_mail_fclose_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+
+ /*
+ * Notify the requestor and clean up.
+ */
+ state->notify(status, state->context);
+ (void) vstream_fclose(state->stream);
+ myfree((void *) state);
+}
+
+/* post_mail_fclose_async - finish posting of message */
+
+void post_mail_fclose_async(VSTREAM *stream,
+ void (*notify) (int status, void *context),
+ void *context)
+{
+ POST_MAIL_FCLOSE_STATE *state;
+ int status = 0;
+
+
+ /*
+ * Send the message end marker only when there were no errors.
+ */
+ if (vstream_ferror(stream) != 0) {
+ status = CLEANUP_STAT_WRITE;
+ } else {
+ rec_fputs(stream, REC_TYPE_XTRA, "");
+ rec_fputs(stream, REC_TYPE_END, "");
+ if (vstream_fflush(stream))
+ status = CLEANUP_STAT_WRITE;
+ }
+
+ /*
+ * Bundle up the suspended state.
+ */
+ state = (POST_MAIL_FCLOSE_STATE *) mymalloc(sizeof(*state));
+ state->status = status;
+ state->stream = stream;
+ state->notify = notify;
+ state->context = context;
+
+ /*
+ * To keep interfaces as simple as possible we report all errors via the
+ * same interface as all successes.
+ */
+ if (status == 0) {
+ event_enable_read(vstream_fileno(stream), post_mail_fclose_event,
+ (void *) state);
+ event_request_timer(post_mail_fclose_event, (void *) state,
+ var_daemon_timeout);
+ } else {
+ event_request_timer(post_mail_fclose_event, (void *) state, 0);
+ }
+}
diff --git a/src/global/post_mail.h b/src/global/post_mail.h
new file mode 100644
index 0000000..2e855c3
--- /dev/null
+++ b/src/global/post_mail.h
@@ -0,0 +1,61 @@
+#ifndef _POST_MAIL_H_INCLUDED_
+#define _POST_MAIL_H_INCLUDED_
+
+/*++
+/* NAME
+/* post_mail 3h
+/* SUMMARY
+/* convenient mail posting interface
+/* SYNOPSIS
+/* #include <post_mail.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <cleanup_user.h>
+#include <mail_proto.h>
+#include <smtputf8.h>
+#include <int_filt.h>
+
+ /*
+ * External interface.
+ */
+typedef void (*POST_MAIL_NOTIFY) (VSTREAM *, void *);
+extern VSTREAM *post_mail_fopen(const char *, const char *, int, int, int, VSTRING *);
+extern VSTREAM *post_mail_fopen_nowait(const char *, const char *, int, int, int, VSTRING *);
+extern void post_mail_fopen_async(const char *, const char *, int, int, int, VSTRING *, POST_MAIL_NOTIFY, void *);
+extern int PRINTFLIKE(2, 3) post_mail_fprintf(VSTREAM *, const char *,...);
+extern int post_mail_fputs(VSTREAM *, const char *);
+extern int post_mail_buffer(VSTREAM *, const char *, int);
+extern int post_mail_fclose(VSTREAM *);
+typedef void (*POST_MAIL_FCLOSE_NOTIFY) (int, void *);
+extern void post_mail_fclose_async(VSTREAM *, POST_MAIL_FCLOSE_NOTIFY, void *);
+
+#define POST_MAIL_BUFFER(v, b) \
+ post_mail_buffer((v), vstring_str(b), VSTRING_LEN(b))
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/qmgr_user.h b/src/global/qmgr_user.h
new file mode 100644
index 0000000..566ef5b
--- /dev/null
+++ b/src/global/qmgr_user.h
@@ -0,0 +1,54 @@
+#ifndef _QMGR_USER_H_INCLUDED_
+#define _QMGR_USER_H_INCLUDED_
+
+/*++
+/* NAME
+/* qmgr_user 3h
+/* SUMMARY
+/* qmgr user interface codes
+/* SYNOPSIS
+/* #include <qmgr_user.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <dsn_mask.h>
+
+ /*
+ * Queue file read options. Flags 16- are reserved by qmgr.h; unfortunately
+ * DSN_NOTIFY_* needs to be shifted to avoid breaking compatibility with
+ * already queued mail that uses QMGR_READ_FLAG_MIXED_RCPT_OTHER.
+ */
+#define QMGR_READ_FLAG_NONE 0 /* No special features */
+#define QMGR_READ_FLAG_MIXED_RCPT_OTHER (1<<0)
+#define QMGR_READ_FLAG_FROM_DSN(x) ((x) << 1)
+
+#define QMGR_READ_FLAG_NOTIFY_NEVER (DSN_NOTIFY_NEVER << 1)
+#define QMGR_READ_FLAG_NOTIFY_SUCCESS (DSN_NOTIFY_SUCCESS << 1)
+#define QMGR_READ_FLAG_NOTIFY_DELAY (DSN_NOTIFY_DELAY << 1)
+#define QMGR_READ_FLAG_NOTIFY_FAILURE (DSN_NOTIFY_FAILURE << 1)
+
+#define QMGR_READ_FLAG_USER \
+ (QMGR_READ_FLAG_NOTIFY_NEVER | QMGR_READ_FLAG_NOTIFY_SUCCESS \
+ | QMGR_READ_FLAG_NOTIFY_DELAY | QMGR_READ_FLAG_NOTIFY_FAILURE \
+ | QMGR_READ_FLAG_MIXED_RCPT_OTHER)
+
+ /*
+ * Backwards compatibility.
+ */
+#define QMGR_READ_FLAG_DEFAULT (QMGR_READ_FLAG_MIXED_RCPT_OTHER)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/qmqp_proto.h b/src/global/qmqp_proto.h
new file mode 100644
index 0000000..8ad9e2a
--- /dev/null
+++ b/src/global/qmqp_proto.h
@@ -0,0 +1,27 @@
+/*++
+/* NAME
+/* qmqpd 3h
+/* SUMMARY
+/* QMQP protocol
+/* SYNOPSIS
+/* include <qmqpd_proto.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * QMQP protocol status codes.
+ */
+#define QMQP_STAT_OK 'K' /* success */
+#define QMQP_STAT_RETRY 'Z' /* recoverable error */
+#define QMQP_STAT_HARD 'D' /* unrecoverable error */
+
+/* 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
+/*--*/
diff --git a/src/global/quote_821_local.c b/src/global/quote_821_local.c
new file mode 100644
index 0000000..8cd9b2e
--- /dev/null
+++ b/src/global/quote_821_local.c
@@ -0,0 +1,179 @@
+/*++
+/* NAME
+/* quote_821_local 3
+/* SUMMARY
+/* quote local part of address
+/* SYNOPSIS
+/* #include "quote_821_local.h"
+/*
+/* VSTRING *quote_821_local(dst, src)
+/* VSTRING *dst;
+/* char *src;
+/*
+/* VSTRING *quote_821_local_flags(dst, src, flags)
+/* VSTRING *dst;
+/* const char *src;
+/* int flags;
+/* DESCRIPTION
+/* quote_821_local() quotes the local part of a mailbox address and
+/* returns a result that can be used in SMTP commands as specified
+/* by RFC 821. It implements an 8-bit clean version of RFC 821.
+/*
+/* quote_821_local_flags() provides finer control.
+/*
+/* Arguments:
+/* .IP dst
+/* The result.
+/* .IP src
+/* The input address.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP QUOTE_FLAG_8BITCLEAN
+/* In violation with RFCs, treat 8-bit text as ordinary text.
+/* .IP QUOTE_FLAG_EXPOSE_AT
+/* In violation with RFCs, treat `@' as an ordinary character.
+/* .IP QUOTE_FLAG_APPEND
+/* Append to the result buffer, instead of overwriting it.
+/* .RE
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* BUGS
+/* The code assumes that the domain is RFC 821 clean.
+/* 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>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* Global library. */
+
+#include "quote_821_local.h"
+
+/* Application-specific. */
+
+#define YES 1
+#define NO 0
+
+/* is_821_dot_string - is this local-part an rfc 821 dot-string? */
+
+static int is_821_dot_string(const char *local_part, const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Detect any deviations from the definition of dot-string. We could use
+ * lookup tables to speed up some of the work, but hey, how large can a
+ * local-part be anyway?
+ */
+ if (local_part == end || local_part[0] == 0 || local_part[0] == '.')
+ return (NO);
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == '.' && cp[1] == '.')
+ return (NO);
+ if (ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ return (NO);
+ if (ch == ' ')
+ return (NO);
+ if (ISCNTRL(ch))
+ return (NO);
+ if (ch == '<' || ch == '>'
+ || ch == '(' || ch == ')'
+ || ch == '[' || ch == ']'
+ || ch == '\\' || ch == ','
+ || ch == ';' || ch == ':'
+ || (ch == '@' && !(flags & QUOTE_FLAG_EXPOSE_AT)) || ch == '"')
+ return (NO);
+ }
+ if (cp[-1] == '.')
+ return (NO);
+ return (YES);
+}
+
+/* make_821_quoted_string - make quoted-string from local-part */
+
+static VSTRING *make_821_quoted_string(VSTRING *dst, const char *local_part,
+ const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Put quotes around the result, and prepend a backslash to characters
+ * that need quoting when they occur in a quoted-string.
+ */
+ VSTRING_ADDCH(dst, '"');
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ || ch == '\r' || ch == '\n' || ch == '"' || ch == '\\')
+ VSTRING_ADDCH(dst, '\\');
+ VSTRING_ADDCH(dst, ch);
+ }
+ VSTRING_ADDCH(dst, '"');
+ VSTRING_TERMINATE(dst);
+ return (dst);
+}
+
+/* quote_821_local_flags - quote local part of address according to rfc 821 */
+
+VSTRING *quote_821_local_flags(VSTRING *dst, const char *addr, int flags)
+{
+ const char *at;
+
+ /*
+ * According to RFC 821, a local-part is a dot-string or a quoted-string.
+ * We first see if the local-part is a dot-string. If it is not, we turn
+ * it into a quoted-string. Anything else would be too painful.
+ */
+ if ((at = strrchr(addr, '@')) == 0) /* just in case */
+ at = addr + strlen(addr); /* should not happen */
+ if ((flags & QUOTE_FLAG_APPEND) == 0)
+ VSTRING_RESET(dst);
+ if (is_821_dot_string(addr, at, flags)) {
+ return (vstring_strcat(dst, addr));
+ } else {
+ make_821_quoted_string(dst, addr, at, flags & QUOTE_FLAG_8BITCLEAN);
+ return (vstring_strcat(dst, at));
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Test program for local-part quoting as per rfc 821
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include "quote_821_local.h"
+
+int main(void)
+{
+ VSTRING *src = vstring_alloc(100);
+ VSTRING *dst = vstring_alloc(100);
+
+ while (vstring_fgets_nonl(src, VSTREAM_IN)) {
+ vstream_fprintf(VSTREAM_OUT, "%s\n",
+ vstring_str(quote_821_local(dst, vstring_str(src))));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/global/quote_821_local.h b/src/global/quote_821_local.h
new file mode 100644
index 0000000..f2b4812
--- /dev/null
+++ b/src/global/quote_821_local.h
@@ -0,0 +1,37 @@
+/*++
+/* NAME
+/* quote_821_local 3h
+/* SUMMARY
+/* quote rfc 821 local part
+/* SYNOPSIS
+/* #include "quote_821_local.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <quote_flags.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *quote_821_local_flags(VSTRING *, const char *, int);
+#define quote_821_local(dst, src) \
+ quote_821_local_flags((dst), (src), QUOTE_FLAG_8BITCLEAN)
+
+/* 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
+/*--*/
diff --git a/src/global/quote_822_local.c b/src/global/quote_822_local.c
new file mode 100644
index 0000000..c19ee57
--- /dev/null
+++ b/src/global/quote_822_local.c
@@ -0,0 +1,294 @@
+/*++
+/* NAME
+/* quote_822_local 3
+/* SUMMARY
+/* quote local part of mailbox
+/* SYNOPSIS
+/* #include <quote_822_local.h>
+/*
+/* VSTRING *quote_822_local(dst, src)
+/* VSTRING *dst;
+/* const char *src;
+/*
+/* VSTRING *quote_822_local_flags(dst, src, flags)
+/* VSTRING *dst;
+/* const char *src;
+/* int flags;
+/*
+/* VSTRING *unquote_822_local(dst, src)
+/* VSTRING *dst;
+/* const char *src;
+/* DESCRIPTION
+/* quote_822_local() quotes the local part of a mailbox and
+/* returns a result that can be used in message headers as
+/* specified by RFC 822 (actually, an 8-bit clean version of
+/* RFC 822). It implements an 8-bit clean version of RFC 822.
+/*
+/* quote_822_local_flags() provides finer control.
+/*
+/* unquote_822_local() transforms the local part of a mailbox
+/* address to unquoted (internal) form.
+/*
+/* Arguments:
+/* .IP dst
+/* The result.
+/* .IP src
+/* The input address.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP QUOTE_FLAG_8BITCLEAN
+/* In violation with RFCs, treat 8-bit text as ordinary text.
+/* .IP QUOTE_FLAG_EXPOSE_AT
+/* In violation with RFCs, treat `@' as an ordinary character.
+/* .IP QUOTE_FLAG_APPEND
+/* Append to the result buffer, instead of overwriting it.
+/* .IP QUOTE_FLAG_BARE_LOCALPART
+/* The input is a localpart without @domain part.
+/* .RE
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* BUGS
+/* The code assumes that the domain is RFC 822 clean.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include "quote_822_local.h"
+
+/* Local stuff. */
+
+#define YES 1
+#define NO 0
+
+/* is_822_dot_string - is this local-part an rfc 822 dot-string? */
+
+static int is_822_dot_string(const char *local_part, const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Detect any deviations from a sequence of atoms separated by dots. We
+ * could use lookup tables to speed up some of the work, but hey, how
+ * large can a local-part be anyway?
+ *
+ * RFC 822 expects 7-bit data. Rather than quoting every 8-bit character
+ * (and still passing it on as 8-bit data) we leave 8-bit data alone.
+ */
+ if (local_part == end || local_part[0] == 0 || local_part[0] == '.')
+ return (NO);
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == '.' && (cp + 1) < end && cp[1] == '.')
+ return (NO);
+ if (ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ return (NO);
+ if (ch == ' ')
+ return (NO);
+ if (ISCNTRL(ch))
+ return (NO);
+ if (ch == '(' || ch == ')'
+ || ch == '<' || ch == '>'
+ || (ch == '@' && !(flags & QUOTE_FLAG_EXPOSE_AT)) || ch == ','
+ || ch == ';' || ch == ':'
+ || ch == '\\' || ch == '"'
+ || ch == '[' || ch == ']')
+ return (NO);
+ }
+ if (cp[-1] == '.')
+ return (NO);
+ return (YES);
+}
+
+/* make_822_quoted_string - make quoted-string from local-part */
+
+static VSTRING *make_822_quoted_string(VSTRING *dst, const char *local_part,
+ const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Put quotes around the result, and prepend a backslash to characters
+ * that need quoting when they occur in a quoted-string.
+ */
+ VSTRING_ADDCH(dst, '"');
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ || ch == '"' || ch == '\\' || ch == '\r')
+ VSTRING_ADDCH(dst, '\\');
+ VSTRING_ADDCH(dst, ch);
+ }
+ VSTRING_ADDCH(dst, '"');
+ return (dst);
+}
+
+/* quote_822_local_flags - quote local part of mailbox according to rfc 822 */
+
+VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags)
+{
+ const char *start; /* first byte of localpart */
+ const char *end; /* first byte after localpart */
+ const char *colon;
+
+ /*
+ * According to RFC 822, a local-part is a dot-string or a quoted-string.
+ * We first see if the local-part is a dot-string. If it is not, we turn
+ * it into a quoted-string. Anything else would be too painful. But
+ * first, skip over any source route that precedes the local-part.
+ */
+ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0)
+ start = colon + 1;
+ else
+ start = mbox;
+ if ((flags & QUOTE_FLAG_BARE_LOCALPART) != 0
+ || (end = strrchr(start, '@')) == 0)
+ end = start + strlen(start);
+ if ((flags & QUOTE_FLAG_APPEND) == 0)
+ VSTRING_RESET(dst);
+ if (is_822_dot_string(start, end, flags)) {
+ return (vstring_strcat(dst, mbox));
+ } else {
+ vstring_strncat(dst, mbox, start - mbox);
+ make_822_quoted_string(dst, start, end, flags & QUOTE_FLAG_8BITCLEAN);
+ return (vstring_strcat(dst, end));
+ }
+}
+
+/* unquote_822_local - unquote local part of mailbox according to rfc 822 */
+
+VSTRING *unquote_822_local(VSTRING *dst, const char *mbox)
+{
+ const char *start; /* first byte of localpart */
+ const char *colon;
+ const char *cp;
+ int in_quote = 0;
+ const char *bare_at_src;
+ int bare_at_dst_pos = -1;
+
+ /* Don't unquote a routing prefix. Is this still possible? */
+ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) {
+ start = colon + 1;
+ vstring_strncpy(dst, mbox, start - mbox);
+ } else {
+ start = mbox;
+ VSTRING_RESET(dst);
+ }
+ /* Locate the last unquoted '@'. */
+ for (cp = start; *cp; cp++) {
+ if (*cp == '"') {
+ in_quote = !in_quote;
+ continue;
+ } else if (*cp == '@') {
+ if (!in_quote) {
+ bare_at_dst_pos = VSTRING_LEN(dst);
+ bare_at_src = cp;
+ }
+ } else if (*cp == '\\') {
+ if (cp[1] == 0)
+ continue;
+ cp++;
+ }
+ VSTRING_ADDCH(dst, *cp);
+ }
+ /* Don't unquote text after the last unquoted '@'. */
+ if (bare_at_dst_pos >= 0) {
+ vstring_truncate(dst, bare_at_dst_pos);
+ vstring_strcat(dst, bare_at_src);
+ } else
+ VSTRING_TERMINATE(dst);
+ return (dst);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read an unquoted address from stdin, and
+ * show the quoted and unquoted results. Specify <> to test behavior for an
+ * empty unquoted address.
+ */
+#include <ctype.h>
+#include <string.h>
+
+#include <msg.h>
+#include <name_mask.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+#define STR vstring_str
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(100);
+ VSTRING *out = vstring_alloc(100);
+ char *cmd;
+ char *bp;
+ int flags;
+
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ bp = STR(in);
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) != 0) {
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == 0) {
+ msg_warn("missing argument");
+ continue;
+ }
+ if (strcmp(bp, "<>") == 0)
+ bp = "";
+ if (strcmp(cmd, "quote") == 0) {
+ quote_822_local(out, bp);
+ vstream_printf("'%s' quoted '%s'\n", bp, STR(out));
+ } else if (strcmp(cmd, "quote_with_flags") == 0) {
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0) {
+ msg_warn("missing flags");
+ continue;
+ }
+ while (ISSPACE(*bp))
+ bp++;
+ flags = quote_flags_from_string(cmd);
+ quote_822_local_flags(out, bp, flags);
+ vstream_printf("'%s' quoted flags=%s '%s'\n",
+ bp, quote_flags_to_string((VSTRING *) 0, flags), STR(out));
+ } else if (strcmp(cmd, "unquote") == 0) {
+ unquote_822_local(out, bp);
+ vstream_printf("'%s' unquoted '%s'\n", bp, STR(out));
+ } else {
+ msg_warn("unknown command: %s", cmd);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(in);
+ vstring_free(out);
+ return (0);
+}
+
+#endif
diff --git a/src/global/quote_822_local.h b/src/global/quote_822_local.h
new file mode 100644
index 0000000..f38e23e
--- /dev/null
+++ b/src/global/quote_822_local.h
@@ -0,0 +1,48 @@
+#ifndef _QUOTE_822_H_INCLUDED_
+#define _QUOTE_822_H_INCLUDED_
+
+/*++
+/* NAME
+/* quote_822_local 3h
+/* SUMMARY
+/* quote local part of mailbox
+/* SYNOPSIS
+/* #include "quote_822_local.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <quote_flags.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *quote_822_local_flags(VSTRING *, const char *, int);
+extern VSTRING *unquote_822_local(VSTRING *, const char *);
+#define quote_822_local(dst, src) \
+ quote_822_local_flags((dst), (src), QUOTE_FLAG_DEFAULT)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/quote_822_local.in b/src/global/quote_822_local.in
new file mode 100644
index 0000000..fc811bf
--- /dev/null
+++ b/src/global/quote_822_local.in
@@ -0,0 +1,6 @@
+quote a@b@c@d
+quote_with_flags 8bitclean|bare_localpart a@b@c@d
+unquote "a@b@c"@d
+unquote "a@b@c"
+unquote "a@b@c"@d@e
+quote <>
diff --git a/src/global/quote_822_local.ref b/src/global/quote_822_local.ref
new file mode 100644
index 0000000..3b0fba3
--- /dev/null
+++ b/src/global/quote_822_local.ref
@@ -0,0 +1,6 @@
+'a@b@c@d' quoted '"a@b@c"@d'
+'a@b@c@d' quoted flags=8bitclean|bare_localpart '"a@b@c@d"'
+'"a@b@c"@d' unquoted 'a@b@c@d'
+'"a@b@c"' unquoted 'a@b@c'
+'"a@b@c"@d@e' unquoted 'a@b@c@d@e'
+'' quoted '""'
diff --git a/src/global/quote_flags.c b/src/global/quote_flags.c
new file mode 100644
index 0000000..2ba1675
--- /dev/null
+++ b/src/global/quote_flags.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* quote_flags 3
+/* SUMMARY
+/* quote rfc 821/822 local part
+/* SYNOPSIS
+/* #include <quote_flags.h>
+/*
+/* int quote_flags_from_string(const char *string)
+/*
+/* const char *quote_flags_to_string(VSTRING *res_buf, int mask)
+/* DESCRIPTION
+/* quote_flags_from_string() converts symbolic flag names into
+/* the corresponding internal bitmask. This logs a warning and
+/* returns zero if an unknown symbolic name is specified.
+/*
+/* quote_flags_to_string() converts from internal bitmask to
+/* the corresponding symbolic names. This logs a warning and
+/* returns a null pointer if an unknown bitmask is specified.
+/*
+/* Arguments:
+/* .IP string
+/* Symbolic representation of a quote_flags bitmask, for
+/* example: \fB8bitclean | bare_localpart\fR. The conversion
+/* is case-insensitive.
+/* .IP res_buf
+/* Storage for the quote_flags_to_string() result, which has
+/* the same form as the string argument. If a null pointer is
+/* specified, quote_flags_to_string() uses storage that is
+/* overwritten with each call.
+/* .IP mask
+/* Binary representation of quote_flags.
+/* DIAGNOSTICS
+/* Fatal error: out of memory; or unknown bitmask name or value.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_mask.h>
+
+ /*
+ * Global library.
+ */
+#include <quote_flags.h>
+
+static const NAME_MASK quote_flags_table[] = {
+ "8bitclean", QUOTE_FLAG_8BITCLEAN,
+ "expose_at", QUOTE_FLAG_EXPOSE_AT,
+ "append", QUOTE_FLAG_APPEND,
+ "bare_localpart", QUOTE_FLAG_BARE_LOCALPART,
+ 0,
+};
+
+/* quote_flags_from_string - symbolic quote flags to internal form */
+
+int quote_flags_from_string(const char *quote_flags_string)
+{
+ return (name_mask_delim_opt("quote_flags_from_string", quote_flags_table,
+ quote_flags_string, "|",
+ NAME_MASK_WARN | NAME_MASK_ANY_CASE));
+}
+
+/* quote_flags_to_string - internal form to symbolic quote flags */
+
+const char *quote_flags_to_string(VSTRING *res_buf, int quote_flags_mask)
+{
+ static VSTRING *my_buf;
+
+ if (res_buf == 0 && (res_buf = my_buf) == 0)
+ res_buf = my_buf = vstring_alloc(20);
+ return (str_name_mask_opt(res_buf, "quote_flags_to_string",
+ quote_flags_table, quote_flags_mask,
+ NAME_MASK_WARN | NAME_MASK_PIPE));
+}
diff --git a/src/global/quote_flags.h b/src/global/quote_flags.h
new file mode 100644
index 0000000..388ae47
--- /dev/null
+++ b/src/global/quote_flags.h
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* quote_flags 3h
+/* SUMMARY
+/* quote rfc 821/822 local part
+/* SYNOPSIS
+/* #include "quote_flags.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define QUOTE_FLAG_8BITCLEAN (1<<0) /* be 8-bit clean */
+#define QUOTE_FLAG_EXPOSE_AT (1<<1) /* @ is ordinary text */
+#define QUOTE_FLAG_APPEND (1<<2) /* append, not overwrite */
+#define QUOTE_FLAG_BARE_LOCALPART (1<<3)/* all localpart, no @domain */
+
+#define QUOTE_FLAG_DEFAULT QUOTE_FLAG_8BITCLEAN
+
+extern int quote_flags_from_string(const char *);
+extern const char *quote_flags_to_string(VSTRING *, int);
+
+/* 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/global/rcpt_buf.c b/src/global/rcpt_buf.c
new file mode 100644
index 0000000..8a3ae0f
--- /dev/null
+++ b/src/global/rcpt_buf.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* rcpt_buf
+/* SUMMARY
+/* recipient buffer manager
+/* SYNOPSIS
+/* #include <rcpt_buf.h>
+/*
+/* typedef struct {
+/* RECIPIENT rcpt; /* convenience */
+/* .in +4
+/* VSTRING *address; /* final recipient */
+/* VSTRING *orig_addr; /* original recipient */
+/* VSTRING *dsn_orcpt; /* dsn original recipient */
+/* int dsn_notify; /* DSN notify flags */
+/* long offset; /* REC_TYPE_RCPT byte */
+/* .in -4
+/* } RCPT_BUF;
+/*
+/* RECIPIENT *RECIPIENT_FROM_RCPT_BUF(rcpb)
+/* RCPT_BUF *rcpb;
+/*
+/* RCPT_BUF *rcpb_create(void)
+/*
+/* void rcpb_reset(rcpb)
+/* RCPT_BUF *rcpb;
+/*
+/* void rcpb_free(rcpb)
+/* RCPT_BUF *rcpb;
+/*
+/* int rcpb_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* RECIPIENT_FROM_RCPT_BUF() populates the rcpt member with
+/* a shallow copy of the contents of the other fields.
+/*
+/* rcpb_scan() reads a recipient buffer from the named stream
+/* using the specified attribute scan routine. rcpb_scan()
+/* is meant to be passed as a call-back to attr_scan(), thusly:
+/*
+/* ... ATTR_TYPE_FUNC, rcpb_scan, (void *) rcpt_buf, ...
+/*
+/* rcpb_create(), rcpb_reset() and rcpb_free() create, wipe
+/* and destroy recipient buffer instances.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <vstream.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <rcpt_buf.h>
+
+/* Application-specific. */
+
+/* rcpb_create - create recipient buffer */
+
+RCPT_BUF *rcpb_create(void)
+{
+ RCPT_BUF *rcpt;
+
+ rcpt = (RCPT_BUF *) mymalloc(sizeof(*rcpt));
+ rcpt->offset = 0;
+ rcpt->dsn_orcpt = vstring_alloc(10);
+ rcpt->dsn_notify = 0;
+ rcpt->orig_addr = vstring_alloc(10);
+ rcpt->address = vstring_alloc(10);
+ return (rcpt);
+}
+
+/* rcpb_reset - reset recipient buffer */
+
+void rcpb_reset(RCPT_BUF *rcpt)
+{
+#define BUF_TRUNCATE(s) (vstring_str(s)[0] = 0)
+
+ rcpt->offset = 0;
+ BUF_TRUNCATE(rcpt->dsn_orcpt);
+ rcpt->dsn_notify = 0;
+ BUF_TRUNCATE(rcpt->orig_addr);
+ BUF_TRUNCATE(rcpt->address);
+}
+
+/* rcpb_free - destroy recipient buffer */
+
+void rcpb_free(RCPT_BUF *rcpt)
+{
+ vstring_free(rcpt->dsn_orcpt);
+ vstring_free(rcpt->orig_addr);
+ vstring_free(rcpt->address);
+ myfree((void *) rcpt);
+}
+
+/* rcpb_scan - receive recipient buffer */
+
+int rcpb_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ RCPT_BUF *rcpt = (RCPT_BUF *) ptr;
+ int ret;
+
+ /*
+ * The order of attributes is determined by historical compatibility and
+ * can be fixed after all the ad-hoc read/write code is replaced.
+ */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_ORCPT, rcpt->orig_addr),
+ RECV_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
+ RECV_ATTR_LONG(MAIL_ATTR_OFFSET, &rcpt->offset),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_NOTIFY, &rcpt->dsn_notify),
+ ATTR_TYPE_END);
+ return (ret == 5 ? 1 : -1);
+}
diff --git a/src/global/rcpt_buf.h b/src/global/rcpt_buf.h
new file mode 100644
index 0000000..770f011
--- /dev/null
+++ b/src/global/rcpt_buf.h
@@ -0,0 +1,67 @@
+#ifndef _RCPT_BUF_H_INCLUDED_
+#define _RCPT_BUF_H_INCLUDED_
+
+/*++
+/* NAME
+/* rcpt_buf 3h
+/* SUMMARY
+/* recipient buffer manager
+/* SYNOPSIS
+/* #include <rcpt_buf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <attr.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ RECIPIENT rcpt; /* convenience */
+ VSTRING *address; /* final recipient */
+ VSTRING *orig_addr; /* original recipient */
+ VSTRING *dsn_orcpt; /* dsn original recipient */
+ int dsn_notify; /* DSN notify flags */
+ long offset; /* REC_TYPE_RCPT byte */
+} RCPT_BUF;
+
+extern RCPT_BUF *rcpb_create(void);
+extern void rcpb_reset(RCPT_BUF *);
+extern void rcpb_free(RCPT_BUF *);
+extern int rcpb_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+#define RECIPIENT_FROM_RCPT_BUF(buf) \
+ ((buf)->rcpt.address = vstring_str((buf)->address), \
+ (buf)->rcpt.orig_addr = vstring_str((buf)->orig_addr), \
+ (buf)->rcpt.dsn_orcpt = vstring_str((buf)->dsn_orcpt), \
+ (buf)->rcpt.dsn_notify = (buf)->dsn_notify, \
+ (buf)->rcpt.offset = (buf)->offset, \
+ &(buf)->rcpt)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/rcpt_print.c b/src/global/rcpt_print.c
new file mode 100644
index 0000000..9e001b6
--- /dev/null
+++ b/src/global/rcpt_print.c
@@ -0,0 +1,76 @@
+/*++
+/* NAME
+/* rcpt_print
+/* SUMMARY
+/* write RECIPIENT structure to stream
+/* SYNOPSIS
+/* #include <rcpt_print.h>
+/*
+/* int rcpt_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* rcpt_print() writes the contents of a RECIPIENT structure
+/* to the named stream using the specified attribute print
+/* routine. rcpt_print() is meant to be passed as a call-back
+/* to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(rcpt_print, (const void *) recipient), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <recipient_list.h>
+#include <rcpt_print.h>
+
+/* rcpt_print - write recipient to stream */
+
+int rcpt_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ RECIPIENT *rcpt = (RECIPIENT *) ptr;
+ int ret;
+
+ /*
+ * The attribute order is determined by backwards compatibility. It can
+ * be sanitized after all the ad-hoc recipient read/write code is
+ * replaced.
+ */
+ ret =
+ print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_ORCPT, rcpt->orig_addr),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
+ SEND_ATTR_LONG(MAIL_ATTR_OFFSET, rcpt->offset),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify),
+ ATTR_TYPE_END);
+ return (ret);
+}
diff --git a/src/global/rcpt_print.h b/src/global/rcpt_print.h
new file mode 100644
index 0000000..a677970
--- /dev/null
+++ b/src/global/rcpt_print.h
@@ -0,0 +1,46 @@
+#ifndef _RCPT_PRINT_H_INCLUDED_
+#define _RCPT_PRINT_H_INCLUDED_
+
+/*++
+/* NAME
+/* rcpt_print 3h
+/* SUMMARY
+/* write RECIPIENT structure to stream
+/* SYNOPSIS
+/* #include <rcpt_print.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+
+ /*
+ * External interface.
+ */
+extern int rcpt_print(ATTR_SCAN_COMMON_FN, VSTREAM *, int, const void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/rec2stream.c b/src/global/rec2stream.c
new file mode 100644
index 0000000..4b72c42
--- /dev/null
+++ b/src/global/rec2stream.c
@@ -0,0 +1,47 @@
+/*++
+/* NAME
+/* rec2stream 1
+/* SUMMARY
+/* convert record stream to stream-lf format
+/* SYNOPSIS
+/* rec2stream
+/* DESCRIPTION
+/* rec2stream reads a record stream from standard input and
+/* writes the content to standard output in stream-lf format.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <vstring.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_streamlf.h>
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int type;
+
+ while ((type = rec_get(VSTREAM_IN, buf, 0)) > 0)
+ REC_STREAMLF_PUT_BUF(VSTREAM_OUT, type, buf);
+ vstream_fflush(VSTREAM_OUT);
+ return (0);
+}
diff --git a/src/global/rec_attr_map.c b/src/global/rec_attr_map.c
new file mode 100644
index 0000000..e98dde8
--- /dev/null
+++ b/src/global/rec_attr_map.c
@@ -0,0 +1,54 @@
+/*++
+/* NAME
+/* rec_attr_map 3
+/* SUMMARY
+/* map named attribute record type to pseudo record type
+/* SYNOPSIS
+/* #include <rec_attr_map.h>
+/*
+/* int rec_attr_map(attr_name)
+/* const char *attr_name;
+/* DESCRIPTION
+/* rec_attr_map() maps the record type of a named attribute to
+/* a pseudo record type, if such a mapping exists. The result
+/* is the pseudo record type in case of success, 0 on failure.
+/* 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>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <rec_type.h>
+#include <rec_attr_map.h>
+
+/* rec_attr_map - map named attribute to pseudo record type */
+
+int rec_attr_map(const char *attr_name)
+{
+ if (strcmp(attr_name, MAIL_ATTR_DSN_ORCPT) == 0) {
+ return (REC_TYPE_DSN_ORCPT);
+ } else if (strcmp(attr_name, MAIL_ATTR_DSN_NOTIFY) == 0) {
+ return (REC_TYPE_DSN_NOTIFY);
+ } else if (strcmp(attr_name, MAIL_ATTR_DSN_ENVID) == 0) {
+ return (REC_TYPE_DSN_ENVID);
+ } else if (strcmp(attr_name, MAIL_ATTR_DSN_RET) == 0) {
+ return (REC_TYPE_DSN_RET);
+ } else if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) {
+ return (REC_TYPE_CTIME);
+ } else {
+ return (0);
+ }
+}
diff --git a/src/global/rec_attr_map.h b/src/global/rec_attr_map.h
new file mode 100644
index 0000000..ae57cf1
--- /dev/null
+++ b/src/global/rec_attr_map.h
@@ -0,0 +1,30 @@
+#ifndef _REC_ATTR_MAP_H_INCLUDED_
+#define _REC_ATTR_MAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* rec_attr_map 3h
+/* SUMMARY
+/* map named attribute record type to pseudo record type
+/* SYNOPSIS
+/* #include <rec_attr_map.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int rec_attr_map(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/rec_streamlf.c b/src/global/rec_streamlf.c
new file mode 100644
index 0000000..76bf95a
--- /dev/null
+++ b/src/global/rec_streamlf.c
@@ -0,0 +1,109 @@
+/*++
+/* NAME
+/* rec_streamlf 3
+/* SUMMARY
+/* record interface to stream-lf files
+/* SYNOPSIS
+/* #include <rec_streamlf.h>
+/*
+/* int rec_streamlf_get(stream, buf, maxlen)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* int maxlen;
+/*
+/* int rec_streamlf_put(stream, type, data, len)
+/* VSTREAM *stream;
+/* int type;
+/* const char *data;
+/* int len;
+/* AUXILIARY FUNCTIONS
+/* int REC_STREAMLF_PUT_BUF(stream, type, buf)
+/* VSTREAM *stream;
+/* int type;
+/* VSTRING *buf;
+/* DESCRIPTION
+/* This module implements record I/O on top of stream-lf files.
+/*
+/* rec_streamlf_get() reads one record from the specified stream.
+/* The result is null-terminated and may contain embedded null
+/* characters.
+/* The \fImaxlen\fR argument specifies an upper bound to the amount
+/* of data read. The result is REC_TYPE_NORM when the record was
+/* terminated by newline, REC_TYPE_CONT when no terminating newline
+/* was found (the record was larger than \fImaxlen\fR characters or
+/* EOF was reached), and REC_TYPE_EOF when no data could be read or
+/* when an I/O error was detected.
+/*
+/* rec_streamlf_put() writes one record to the named stream.
+/* When the record type is REC_TYPE_NORM, a newline character is
+/* appended to the output. The result is the record type, or
+/* REC_TYPE_EOF in case of problems.
+/*
+/* REC_STREAMLF_PUT_BUF() is a wrapper for rec_streamlf_put() that
+/* makes it more convenient to output VSTRING buffers.
+/* REC_STREAMLF_PUT_BUF() is an unsafe macro that evaluates some
+/* arguments more than once.
+/* SEE ALSO
+/* record(3) typed records
+/* 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 <vstring.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <rec_streamlf.h>
+
+/* rec_streamlf_get - read record from stream-lf file */
+
+int rec_streamlf_get(VSTREAM *stream, VSTRING *buf, int maxlen)
+{
+ int n = maxlen;
+ int ch;
+
+ /*
+ * If this one character ar a time code proves to be a performance
+ * bottleneck, switch to block search (memchr()) and to block move
+ * (memcpy()) operations.
+ */
+ VSTRING_RESET(buf);
+ while (n-- > 0) {
+ if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF)
+ return (VSTRING_LEN(buf) > 0 ? REC_TYPE_CONT : REC_TYPE_EOF);
+ if (ch == '\n') {
+ VSTRING_TERMINATE(buf);
+ return (REC_TYPE_NORM);
+ }
+ VSTRING_ADDCH(buf, ch);
+ }
+ VSTRING_TERMINATE(buf);
+ return (REC_TYPE_CONT);
+}
+
+/* rec_streamlf_put - write record to stream-lf file */
+
+int rec_streamlf_put(VSTREAM *stream, int type, const char *data, int len)
+{
+ if (len > 0)
+ (void) vstream_fwrite(stream, data, len);
+ if (type == REC_TYPE_NORM)
+ (void) VSTREAM_PUTC('\n', stream);
+ return (vstream_ferror(stream) ? REC_TYPE_EOF : type);
+}
diff --git a/src/global/rec_streamlf.h b/src/global/rec_streamlf.h
new file mode 100644
index 0000000..3706f12
--- /dev/null
+++ b/src/global/rec_streamlf.h
@@ -0,0 +1,45 @@
+#ifndef _REC_STREAMLF_H_INCLUDED_
+#define _REC_STREAMLF_H_INCLUDED_
+
+/*++
+/* NAME
+/* rec_streamlf 3h
+/* SUMMARY
+/* record interface to stream-lf files
+/* SYNOPSIS
+/* #include <rec_streamlf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <rec_type.h>
+
+ /*
+ * External interface.
+ */
+extern int rec_streamlf_get(VSTREAM *, VSTRING *, int);
+extern int rec_streamlf_put(VSTREAM *, int, const char *, int);
+
+#define REC_STREAMLF_PUT_BUF(s, t, b) \
+ rec_streamlf_put((s), (t), vstring_str(b), VSTRING_LEN(b))
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/rec_type.c b/src/global/rec_type.c
new file mode 100644
index 0000000..1ce2111
--- /dev/null
+++ b/src/global/rec_type.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* rec_type 3
+/* SUMMARY
+/* Postfix record types
+/* SYNOPSIS
+/* #include <rec_type.h>
+/*
+/* const char *rec_type_name(type)
+/* int type;
+/* DESCRIPTION
+/* This module and its associated include file implement the
+/* Postfix-specific record types.
+/*
+/* rec_type_name() returns a printable name for the given record
+/* type.
+/* 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
+/*--*/
+
+/* Global library. */
+
+#include "rec_type.h"
+
+ /*
+ * Lookup table with internal record type codes and printable names.
+ */
+typedef struct {
+ int type;
+ const char *name;
+} REC_TYPE_NAME;
+
+REC_TYPE_NAME rec_type_names[] = {
+ REC_TYPE_EOF, "end-of-file", /* not Postfix-specific. */
+ REC_TYPE_ERROR, "error", /* not Postfix-specific. */
+ REC_TYPE_SIZE, "message_size",
+ REC_TYPE_TIME, "message_arrival_time",
+ REC_TYPE_CTIME, "queue_file_create_time",
+ REC_TYPE_FULL, "sender_fullname",
+ REC_TYPE_INSP, "content_inspector",
+ REC_TYPE_FILT, "content_filter",
+ REC_TYPE_FROM, "sender",
+ REC_TYPE_DONE, "done_recipient",
+ REC_TYPE_DRCP, "canceled_recipient",
+ REC_TYPE_RCPT, "recipient",
+ REC_TYPE_ORCP, "original_recipient",
+ REC_TYPE_WARN, "warning_message_time",
+ REC_TYPE_ATTR, "named_attribute",
+ REC_TYPE_PTR, "pointer_record",
+ REC_TYPE_KILL, "killed_record",
+ REC_TYPE_MESG, "message_content",
+ REC_TYPE_CONT, "unterminated_text",
+ REC_TYPE_NORM, "regular_text",
+ REC_TYPE_DTXT, "padding",
+ REC_TYPE_XTRA, "extracted_info",
+ REC_TYPE_RRTO, "return_receipt",
+ REC_TYPE_ERTO, "errors_to",
+ REC_TYPE_PRIO, "priority",
+ REC_TYPE_VERP, "verp_delimiters",
+ REC_TYPE_END, "message_end",
+ REC_TYPE_RDR, "redirect_to",
+ REC_TYPE_FLGS, "flags",
+ REC_TYPE_DSN_RET, "dsn_return_flags",
+ REC_TYPE_DSN_ENVID, "dsn_envelope_id",
+ REC_TYPE_DSN_ORCPT, "dsn_original_recipient",
+ REC_TYPE_DSN_NOTIFY, "dsn_notify_flags",
+ 0, 0,
+};
+
+/* rec_type_name - map record type to printable name */
+
+const char *rec_type_name(int type)
+{
+ REC_TYPE_NAME *p;
+
+ for (p = rec_type_names; p->name != 0; p++)
+ if (p->type == type)
+ return (p->name);
+ return ("unknown_record_type");
+}
diff --git a/src/global/rec_type.h b/src/global/rec_type.h
new file mode 100644
index 0000000..4386529
--- /dev/null
+++ b/src/global/rec_type.h
@@ -0,0 +1,198 @@
+#ifndef _REC_TYPE_H_INCLUDED_
+#define _REC_TYPE_H_INCLUDED_
+
+/*++
+/* NAME
+/* rec_type 3h
+/* SUMMARY
+/* Postfix record types
+/* SYNOPSIS
+/* #include <rec_type.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <ctype.h>
+#include <stdlib.h>
+
+ /*
+ * Diagnostic codes, not real record lookup results.
+ */
+#define REC_TYPE_EOF -1 /* no record */
+#define REC_TYPE_ERROR -2 /* bad record */
+
+ /*
+ * A queue file or IPC mail message consists of a sequence of typed records.
+ * The first record group contains time stamp, full name, sender envelope
+ * information, and optionally contains recipient information. The second
+ * record group contains data records with the message content. The last
+ * record group is optional; it contains information extracted from message
+ * headers, such as recipients, errors-to and return-receipt.
+ *
+ * Note: REC_TYPE_FILT and REC_TYPE_CONT are encoded with the same 'L'
+ * constant, and it is too late to change that now.
+ */
+#define REC_TYPE_SIZE 'C' /* first record, created by cleanup */
+#define REC_TYPE_TIME 'T' /* arrival time, required */
+#define REC_TYPE_CTIME 'c' /* create time, optional */
+#define REC_TYPE_FULL 'F' /* full name, optional */
+#define REC_TYPE_INSP 'I' /* inspector transport */
+#define REC_TYPE_FILT 'L' /* loop filter transport */
+#define REC_TYPE_FROM 'S' /* sender, required */
+#define REC_TYPE_DONE 'D' /* delivered recipient, optional */
+#define REC_TYPE_RCPT 'R' /* todo recipient, optional */
+#define REC_TYPE_ORCP 'O' /* original recipient, optional */
+#define REC_TYPE_DRCP '/' /* canceled recipient, optional */
+#define REC_TYPE_WARN 'W' /* warning message time */
+#define REC_TYPE_ATTR 'A' /* named attribute for extensions */
+#define REC_TYPE_KILL 'K' /* killed record */
+
+#define REC_TYPE_RDR '>' /* redirect target */
+#define REC_TYPE_FLGS 'f' /* cleanup processing flags */
+#define REC_TYPE_DELAY 'd' /* cleanup delay upon arrival */
+
+#define REC_TYPE_MESG 'M' /* start message records */
+
+#define REC_TYPE_CONT 'L' /* long data record */
+#define REC_TYPE_NORM 'N' /* normal data record */
+#define REC_TYPE_DTXT 'w' /* padding (was: deleted data) */
+
+#define REC_TYPE_XTRA 'X' /* start extracted records */
+
+#define REC_TYPE_RRTO 'r' /* return-receipt, from headers */
+#define REC_TYPE_ERTO 'e' /* errors-to, from headers */
+#define REC_TYPE_PRIO 'P' /* priority */
+#define REC_TYPE_PTR 'p' /* pointer indirection */
+#define REC_TYPE_VERP 'V' /* VERP delimiters */
+
+#define REC_TYPE_DSN_RET '<' /* DSN full/hdrs */
+#define REC_TYPE_DSN_ENVID 'i' /* DSN envelope id */
+#define REC_TYPE_DSN_ORCPT 'o' /* DSN orig rcpt address */
+#define REC_TYPE_DSN_NOTIFY 'n' /* DSN notify flags */
+
+#define REC_TYPE_MILT_COUNT 'm'
+
+#define REC_TYPE_END 'E' /* terminator, required */
+
+ /*
+ * What I expect to see in a "pure recipient" sequence at the end of the
+ * initial or extracted envelope segments, respectively. When a queue file
+ * contains pure recipient sequences only, then the queue manager will not
+ * have to read all the queue file records before starting delivery. This is
+ * often the case with list mail, where such optimization is desirable.
+ *
+ * XXX These definitions include the respective segment terminators to avoid
+ * special cases in the cleanup(8) envelope and extracted record processors.
+ */
+#define REC_TYPE_ENV_RECIPIENT "MDRO/Kon"
+#define REC_TYPE_EXT_RECIPIENT "EDRO/Kon"
+
+ /*
+ * The types of records that I expect to see while processing different
+ * record groups. The first member in each set is the record type that
+ * indicates the end of that record group.
+ *
+ * XXX A records in the extracted segment are generated only by the cleanup
+ * server, and are not supposed to be present in locally submitted mail, as
+ * this is "postfix internal" information. However, the pickup server has to
+ * allow for the presence of A records in the extracted segment, because it
+ * can be requested to re-process already queued mail with `postsuper -r'.
+ *
+ * Note: REC_TYPE_FILT and REC_TYPE_CONT are encoded with the same 'L'
+ * constant, and it is too late to change that now.
+ */
+#define REC_TYPE_ENVELOPE "MCTcFILSDRO/WVA>K<ion"
+#define REC_TYPE_CONTENT "XLNw"
+#define REC_TYPE_EXTRACT "EDRO/PreAFIL>Kon"
+
+ /*
+ * The subset of inputs that the postdrop command allows.
+ */
+#define REC_TYPE_POST_ENVELOPE "MFSRVAin"
+#define REC_TYPE_POST_CONTENT "XLN"
+#define REC_TYPE_POST_EXTRACT "EAR"
+
+ /*
+ * The record at the start of the queue file specifies the message content
+ * size (number of bytes between the REC_TYPE_MESG and REC_TYPE_XTRA meta
+ * records), data offset (offset of the first REC_TYPE_NORM or REC_TYPE_CONT
+ * text record), recipient count, and queue manager hints. These are all
+ * fixed-width fields so they can be updated in place. Queue manager hints
+ * are defined in qmgr_user.h
+ *
+ * See also: REC_TYPE_PTR_FORMAT below.
+ */
+#define REC_TYPE_SIZE_FORMAT "%15ld %15ld %15ld %15ld %15ld %15ld"
+#define REC_TYPE_SIZE_CAST1 long /* Vmailer extra offs - data offs */
+#define REC_TYPE_SIZE_CAST2 long /* Postfix 1.0 data offset */
+#define REC_TYPE_SIZE_CAST3 long /* Postfix 1.0 recipient count */
+#define REC_TYPE_SIZE_CAST4 long /* Postfix 2.1 qmgr flags */
+#define REC_TYPE_SIZE_CAST5 long /* Postfix 2.4 content length */
+#define REC_TYPE_SIZE_CAST6 long /* Postfix 3.0 smtputf8 flags */
+
+ /*
+ * The warn record specifies when the next warning that the message was
+ * deferred should be sent. It is updated in place by qmgr, so changing
+ * this value when there are deferred messages in the queue is dangerous!
+ */
+#define REC_TYPE_WARN_FORMAT "%15ld" /* warning time format */
+#define REC_TYPE_WARN_ARG(tv) ((long) (tv))
+#define REC_TYPE_WARN_SCAN(cp, tv) ((tv) = atol(cp))
+
+ /*
+ * Time information is not updated in place, but it does have complex
+ * formatting requirements, so we centralize things here.
+ */
+#define REC_TYPE_TIME_FORMAT "%ld %ld"
+#define REC_TYPE_TIME_ARG(tv) (long) (tv).tv_sec, (long) (tv).tv_usec
+#define REC_TYPE_TIME_SCAN(cp, tv) \
+ do { \
+ const char *_p = cp; \
+ (tv).tv_sec = atol(_p); \
+ while (ISDIGIT(*_p)) \
+ _p++; \
+ (tv).tv_usec = atol(_p); \
+ } while (0)
+
+ /*
+ * Pointer records are used to edit a queue file in place before it is
+ * committed. When a record is appended or modified, we patch it into the
+ * existing record stream with a pointer to storage in a heap after the
+ * end-of-message marker; the new content is followed by a pointer record
+ * back to the existing record stream.
+ *
+ * We need to have a few dummy pointer records in place at strategic places
+ * (after the last recipient, after the last header) so that we can always
+ * append recipients or append/modify headers without having to move message
+ * segment terminators.
+ *
+ * We also need to have a dummy PTR record at the end of the content, so that
+ * we can always replace the message content without having to move the
+ * end-of-message marker.
+ *
+ * A dummy PTR record has a null argument.
+ *
+ * See also: REC_TYPE_SIZE_FORMAT above.
+ */
+#define REC_TYPE_PTR_FORMAT "%15ld"
+#define REC_TYPE_PTR_PAYL_SIZE 15 /* Payload only, excludes record header. */
+
+ /*
+ * Programmatic interface.
+ */
+extern const char *rec_type_name(int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/recdump.c b/src/global/recdump.c
new file mode 100644
index 0000000..aa13eb6
--- /dev/null
+++ b/src/global/recdump.c
@@ -0,0 +1,57 @@
+/*++
+/* NAME
+/* recdump 1
+/* SUMMARY
+/* convert record stream to printable form
+/* SYNOPSIS
+/* recdump
+/* DESCRIPTION
+/* recdump reads a record stream from standard input and
+/* writes the content to standard output in printable form.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <stdlib.h>
+
+/* Utility library. */
+
+#include <msg_vstream.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_streamlf.h>
+#include <rec_type.h>
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ long offset;
+ int type;
+
+ msg_vstream_init(argv[0], VSTREAM_OUT);
+
+ while (offset = vstream_ftell(VSTREAM_IN),
+ ((type = rec_get(VSTREAM_IN, buf, 0)) != REC_TYPE_EOF
+ && type != REC_TYPE_ERROR)) {
+ vstream_fprintf(VSTREAM_OUT, "%15s|%4ld|%3ld|%s\n",
+ rec_type_name(type), offset,
+ (long) VSTRING_LEN(buf), vstring_str(buf));
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ exit(0);
+}
diff --git a/src/global/recipient_list.c b/src/global/recipient_list.c
new file mode 100644
index 0000000..32d1fd9
--- /dev/null
+++ b/src/global/recipient_list.c
@@ -0,0 +1,191 @@
+/*++
+/* NAME
+/* recipient_list 3
+/* SUMMARY
+/* in-core recipient structures
+/* SYNOPSIS
+/* #include <recipient_list.h>
+/*
+/* typedef struct {
+/* .in +4
+/* long offset;
+/* char *dsn_orcpt;
+/* int dsn_notify;
+/* char *orig_addr;
+/* char *address;
+/* union {
+/* .in +4
+/* int status;
+/* struct QMGR_QUEUE *queue;
+/* char *addr_type;
+/* .in -4
+/* }
+/* .in -4
+/* } RECIPIENT;
+/*
+/* typedef struct {
+/* .in +4
+/* RECIPIENT *info;
+/* private members...
+/* .in -4
+/* } RECIPIENT_LIST;
+/*
+/* void recipient_list_init(list, variant)
+/* RECIPIENT_LIST *list;
+/* int variant;
+/*
+/* void recipient_list_add(list, offset, dsn_orcpt, dsn_notify,
+/* orig_rcpt, recipient)
+/* RECIPIENT_LIST *list;
+/* long offset;
+/* const char *dsn_orcpt;
+/* int dsn_notify;
+/* const char *orig_rcpt;
+/* const char *recipient;
+/*
+/* void recipient_list_swap(a, b)
+/* RECIPIENT_LIST *a;
+/* RECIPIENT_LIST *b;
+/*
+/* void recipient_list_free(list)
+/* RECIPIENT_LIST *list;
+/*
+/* void RECIPIENT_ASSIGN(rcpt, offset, dsn_orcpt, dsn_notify,
+/* orig_rcpt, recipient)
+/* RECIPIENT *rcpt;
+/* long offset;
+/* char *dsn_orcpt;
+/* int dsn_notify;
+/* char *orig_rcpt;
+/* char *recipient;
+/* DESCRIPTION
+/* This module maintains lists of recipient structures. Each
+/* recipient is characterized by a destination address and
+/* by the queue file offset of its delivery status record.
+/* The per-recipient status is initialized to zero, and exists
+/* solely for the convenience of the application. It is not used
+/* by the recipient_list module itself.
+/*
+/* recipient_list_init() creates an empty recipient structure list.
+/* The list argument is initialized such that it can be given to
+/* recipient_list_add() and to recipient_list_free(). The variant
+/* argument specifies how list elements should be initialized;
+/* specify RCPT_LIST_INIT_STATUS to zero the status field, and
+/* RCPT_LIST_INIT_QUEUE to zero the queue field.
+/*
+/* recipient_list_add() adds a recipient to the specified list.
+/* Recipient address information is copied with mystrdup().
+/*
+/* recipient_list_swap() swaps the recipients between
+/* the given two recipient lists.
+/*
+/* recipient_list_free() releases memory for the specified list
+/* of recipient structures.
+/*
+/* RECIPIENT_ASSIGN() assigns the fields of a recipient structure
+/* without making copies of its arguments.
+/*
+/* Arguments:
+/* .IP list
+/* Recipient list initialized by recipient_list_init().
+/* .IP offset
+/* Queue file offset of a recipient delivery status record.
+/* .IP dsn_orcpt
+/* DSN original recipient.
+/* .IP notify
+/* DSN notify flags.
+/* .IP recipient
+/* Recipient destination address.
+/* SEE ALSO
+/* recipient_list(3h) data structure
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation.
+/* 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 <mymalloc.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include "recipient_list.h"
+
+/* recipient_list_init - initialize */
+
+void recipient_list_init(RECIPIENT_LIST *list, int variant)
+{
+ list->avail = 1;
+ list->len = 0;
+ list->info = (RECIPIENT *) mymalloc(sizeof(RECIPIENT));
+ list->variant = variant;
+}
+
+/* recipient_list_add - add rcpt to list */
+
+void recipient_list_add(RECIPIENT_LIST *list, long offset,
+ const char *dsn_orcpt, int dsn_notify,
+ const char *orig_rcpt, const char *rcpt)
+{
+ int new_avail;
+
+ if (list->len >= list->avail) {
+ new_avail = list->avail * 2;
+ list->info = (RECIPIENT *)
+ myrealloc((void *) list->info, new_avail * sizeof(RECIPIENT));
+ list->avail = new_avail;
+ }
+ list->info[list->len].orig_addr = mystrdup(orig_rcpt);
+ list->info[list->len].address = mystrdup(rcpt);
+ list->info[list->len].offset = offset;
+ list->info[list->len].dsn_orcpt = mystrdup(dsn_orcpt);
+ list->info[list->len].dsn_notify = dsn_notify;
+ if (list->variant == RCPT_LIST_INIT_STATUS)
+ list->info[list->len].u.status = 0;
+ else if (list->variant == RCPT_LIST_INIT_QUEUE)
+ list->info[list->len].u.queue = 0;
+ else if (list->variant == RCPT_LIST_INIT_ADDR)
+ list->info[list->len].u.addr_type = 0;
+ list->len++;
+}
+
+/* recipient_list_swap - swap recipients between the two recipient lists */
+
+void recipient_list_swap(RECIPIENT_LIST *a, RECIPIENT_LIST *b)
+{
+ if (b->variant != a->variant)
+ msg_panic("recipient_lists_swap: incompatible recipient list variants");
+
+#define SWAP(t, x) do { t x = b->x; b->x = a->x ; a->x = x; } while (0)
+
+ SWAP(RECIPIENT *, info);
+ SWAP(int, len);
+ SWAP(int, avail);
+}
+
+/* recipient_list_free - release memory for in-core recipient structure */
+
+void recipient_list_free(RECIPIENT_LIST *list)
+{
+ RECIPIENT *rcpt;
+
+ for (rcpt = list->info; rcpt < list->info + list->len; rcpt++) {
+ myfree((void *) rcpt->dsn_orcpt);
+ myfree((void *) rcpt->orig_addr);
+ myfree((void *) rcpt->address);
+ }
+ myfree((void *) list->info);
+}
diff --git a/src/global/recipient_list.h b/src/global/recipient_list.h
new file mode 100644
index 0000000..8fc5d58
--- /dev/null
+++ b/src/global/recipient_list.h
@@ -0,0 +1,76 @@
+#ifndef _RECIPIENT_LIST_H_INCLUDED_
+#define _RECIPIENT_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* recipient_list 3h
+/* SUMMARY
+/* recipient list structures
+/* SYNOPSIS
+/* #include <recipient_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Information about a recipient is kept in this structure. The file offset
+ * tells us the position of the REC_TYPE_RCPT byte in the message queue
+ * file, This byte is replaced by REC_TYPE_DONE when the delivery status to
+ * that recipient is established.
+ *
+ * Rather than bothering with subclasses that extend this structure with
+ * application-specific fields we just add them here.
+ */
+typedef struct RECIPIENT {
+ long offset; /* REC_TYPE_RCPT byte */
+ const char *dsn_orcpt; /* DSN original recipient */
+ int dsn_notify; /* DSN notify flags */
+ const char *orig_addr; /* null or original recipient */
+ const char *address; /* complete address */
+ union { /* Application specific. */
+ int status; /* SMTP client */
+ struct QMGR_QUEUE *queue; /* Queue manager */
+ const char *addr_type; /* DSN */
+ } u;
+} RECIPIENT;
+
+#define RECIPIENT_ASSIGN(rcpt, offs, orcpt, notify, orig, addr) do { \
+ (rcpt)->offset = (offs); \
+ (rcpt)->dsn_orcpt = (orcpt); \
+ (rcpt)->dsn_notify = (notify); \
+ (rcpt)->orig_addr = (orig); \
+ (rcpt)->address = (addr); \
+ (rcpt)->u.status = (0); \
+} while (0)
+
+#define RECIPIENT_UPDATE(ptr, new) do { \
+ myfree((char *) (ptr)); (ptr) = mystrdup(new); \
+} while (0)
+
+typedef struct RECIPIENT_LIST {
+ RECIPIENT *info;
+ int len;
+ int avail;
+ int variant;
+} RECIPIENT_LIST;
+
+extern void recipient_list_init(RECIPIENT_LIST *, int);
+extern void recipient_list_add(RECIPIENT_LIST *, long, const char *, int, const char *, const char *);
+extern void recipient_list_swap(RECIPIENT_LIST *, RECIPIENT_LIST *);
+extern void recipient_list_free(RECIPIENT_LIST *);
+
+#define RCPT_LIST_INIT_STATUS 1
+#define RCPT_LIST_INIT_QUEUE 2
+#define RCPT_LIST_INIT_ADDR 3
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/record.c b/src/global/record.c
new file mode 100644
index 0000000..80cb1ac
--- /dev/null
+++ b/src/global/record.c
@@ -0,0 +1,415 @@
+/*++
+/* NAME
+/* record 3
+/* SUMMARY
+/* simple typed record I/O
+/* SYNOPSIS
+/* #include <record.h>
+/*
+/* int rec_get(stream, buf, maxsize)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t maxsize;
+/*
+/* int rec_get_raw(stream, buf, maxsize, flags)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t maxsize;
+/* int flags;
+/*
+/* int rec_put(stream, type, data, len)
+/* VSTREAM *stream;
+/* int type;
+/* const char *data;
+/* ssize_t len;
+/* AUXILIARY FUNCTIONS
+/* int rec_put_type(stream, type, offset)
+/* VSTREAM *stream;
+/* int type;
+/* long offset;
+/*
+/* int rec_fprintf(stream, type, format, ...)
+/* VSTREAM *stream;
+/* int type;
+/* const char *format;
+/*
+/* int rec_fputs(stream, type, str)
+/* VSTREAM *stream;
+/* int type;
+/* const char *str;
+/*
+/* int REC_PUT_BUF(stream, type, buf)
+/* VSTREAM *stream;
+/* int type;
+/* VSTRING *buf;
+/*
+/* int rec_vfprintf(stream, type, format, ap)
+/* VSTREAM *stream;
+/* int type;
+/* const char *format;
+/* va_list ap;
+/*
+/* int rec_goto(stream, where)
+/* VSTREAM *stream;
+/* const char *where;
+/*
+/* int rec_pad(stream, type, len)
+/* VSTREAM *stream;
+/* int type;
+/* ssize_t len;
+/*
+/* REC_SPACE_NEED(buflen, reclen)
+/* ssize_t buflen;
+/* ssize_t reclen;
+/*
+/* REC_GET_HIDDEN_TYPE(type)
+/* int type;
+/* DESCRIPTION
+/* This module reads and writes typed variable-length records.
+/* Each record contains a 1-byte type code (0..255), a length
+/* (1 or more bytes) and as much data as the length specifies.
+/*
+/* rec_get_raw() retrieves a record from the named record stream
+/* and returns the record type. The \fImaxsize\fR argument is
+/* zero, or specifies a maximal acceptable record length.
+/* The result is REC_TYPE_EOF when the end of the file was reached,
+/* and REC_TYPE_ERROR in case of a bad record. The result buffer is
+/* null-terminated for convenience. Records may contain embedded
+/* null characters. The \fIflags\fR argument specifies zero or
+/* more of the following:
+/* .IP REC_FLAG_FOLLOW_PTR
+/* Follow PTR records, instead of exposing them to the application.
+/* .IP REC_FLAG_SKIP_DTXT
+/* Skip "deleted text" records, instead of exposing them to
+/* the application.
+/* .IP REC_FLAG_SEEK_END
+/* Seek to the end-of-file upon reading a REC_TYPE_END record.
+/* .PP
+/* Specify REC_FLAG_NONE to request no special processing,
+/* and REC_FLAG_DEFAULT for normal use.
+/*
+/* rec_get() is a wrapper around rec_get_raw() that always
+/* enables the REC_FLAG_FOLLOW_PTR, REC_FLAG_SKIP_DTXT
+/* and REC_FLAG_SEEK_END features.
+/*
+/* REC_GET_HIDDEN_TYPE() is an unsafe macro that returns
+/* non-zero when the specified record type is "not exposed"
+/* by rec_get().
+/*
+/* rec_put() stores the specified record and returns the record
+/* type, or REC_TYPE_ERROR in case of problems.
+/*
+/* rec_put_type() updates the type field of the record at the
+/* specified file offset. The result is the new record type,
+/* or REC_TYPE_ERROR in case of trouble.
+/*
+/* rec_fprintf() and rec_vfprintf() format their arguments and
+/* write the result to the named stream. The result is the same
+/* as with rec_put().
+/*
+/* rec_fputs() writes a record with as contents a copy of the
+/* specified string. The result is the same as with rec_put().
+/*
+/* REC_PUT_BUF() is a wrapper for rec_put() that makes it
+/* easier to handle VSTRING buffers. It is an unsafe macro
+/* that evaluates some arguments more than once.
+/*
+/* rec_goto() takes the argument of a pointer record and moves
+/* the file pointer to the specified location. A zero position
+/* means do nothing. The result is REC_TYPE_ERROR in case of
+/* failure.
+/*
+/* rec_pad() writes a record that occupies the larger of (the
+/* specified amount) or (an implementation-defined minimum).
+/*
+/* REC_SPACE_NEED(buflen, reclen) converts the specified buffer
+/* length into a record length. This macro modifies its second
+/* argument.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: insufficient memory.
+/* Warnings: corrupted file.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifndef NBBY
+#define NBBY 8 /* XXX should be in sys_defs.h */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <off_cvt.h>
+#include <rec_type.h>
+#include <record.h>
+
+/* rec_put_type - update record type field */
+
+int rec_put_type(VSTREAM *stream, int type, off_t offset)
+{
+ if (type < 0 || type > 255)
+ msg_panic("rec_put_type: bad record type %d", type);
+
+ if (msg_verbose > 2)
+ msg_info("rec_put_type: %d at %ld", type, (long) offset);
+
+ if (vstream_fseek(stream, offset, SEEK_SET) < 0
+ || VSTREAM_PUTC(type, stream) != type) {
+ msg_warn("%s: seek or write error", VSTREAM_PATH(stream));
+ return (REC_TYPE_ERROR);
+ } else {
+ return (type);
+ }
+}
+
+/* rec_put - store typed record */
+
+int rec_put(VSTREAM *stream, int type, const char *data, ssize_t len)
+{
+ ssize_t len_rest;
+ int len_byte;
+
+ if (type < 0 || type > 255)
+ msg_panic("rec_put: bad record type %d", type);
+
+ if (msg_verbose > 2)
+ msg_info("rec_put: type %c len %ld data %.10s",
+ type, (long) len, data);
+
+ /*
+ * Write the record type, one byte.
+ */
+ if (VSTREAM_PUTC(type, stream) == VSTREAM_EOF)
+ return (REC_TYPE_ERROR);
+
+ /*
+ * Write the record data length in 7-bit portions, using the 8th bit to
+ * indicate that there is more. Use as many length bytes as needed.
+ */
+ len_rest = len;
+ do {
+ len_byte = len_rest & 0177;
+ if (len_rest >>= 7U)
+ len_byte |= 0200;
+ if (VSTREAM_PUTC(len_byte, stream) == VSTREAM_EOF) {
+ return (REC_TYPE_ERROR);
+ }
+ } while (len_rest != 0);
+
+ /*
+ * Write the record data portion. Use as many length bytes as needed.
+ */
+ if (len && vstream_fwrite(stream, data, len) != len)
+ return (REC_TYPE_ERROR);
+ return (type);
+}
+
+/* rec_get_raw - retrieve typed record */
+
+int rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags)
+{
+ const char *myname = "rec_get";
+ int type;
+ ssize_t len;
+ int len_byte;
+ unsigned shift;
+
+ /*
+ * Sanity check.
+ */
+ if (maxsize < 0)
+ msg_panic("%s: bad record size limit: %ld", myname, (long) maxsize);
+
+ for (;;) {
+
+ /*
+ * Extract the record type.
+ */
+ if ((type = VSTREAM_GETC(stream)) == VSTREAM_EOF)
+ return (REC_TYPE_EOF);
+
+ /*
+ * Find out the record data length. Return an error result when the
+ * record data length is malformed or when it exceeds the acceptable
+ * limit.
+ */
+ for (len = 0, shift = 0; /* void */ ; shift += 7) {
+ if (shift >= (int) (NBBY * sizeof(int))) {
+ msg_warn("%s: too many length bits, record type %d",
+ VSTREAM_PATH(stream), type);
+ return (REC_TYPE_ERROR);
+ }
+ if ((len_byte = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
+ msg_warn("%s: unexpected EOF reading length, record type %d",
+ VSTREAM_PATH(stream), type);
+ return (REC_TYPE_ERROR);
+ }
+ len |= (len_byte & 0177) << shift;
+ if ((len_byte & 0200) == 0)
+ break;
+ }
+ if (len < 0 || (maxsize > 0 && len > maxsize)) {
+ msg_warn("%s: illegal length %ld, record type %d",
+ VSTREAM_PATH(stream), (long) len, type);
+ while (len-- > 0 && VSTREAM_GETC(stream) != VSTREAM_EOF)
+ /* void */ ;
+ return (REC_TYPE_ERROR);
+ }
+
+ /*
+ * Reserve buffer space for the result, and read the record data into
+ * the buffer.
+ */
+ if (vstream_fread_buf(stream, buf, len) != len) {
+ msg_warn("%s: unexpected EOF in data, record type %d length %ld",
+ VSTREAM_PATH(stream), type, (long) len);
+ return (REC_TYPE_ERROR);
+ }
+ VSTRING_TERMINATE(buf);
+ if (msg_verbose > 2)
+ msg_info("%s: type %c len %ld data %.10s", myname,
+ type, (long) len, vstring_str(buf));
+
+ /*
+ * Transparency options.
+ */
+ if (flags == 0)
+ break;
+ if (type == REC_TYPE_PTR && (flags & REC_FLAG_FOLLOW_PTR) != 0
+ && (type = rec_goto(stream, vstring_str(buf))) != REC_TYPE_ERROR)
+ continue;
+ if (type == REC_TYPE_DTXT && (flags & REC_FLAG_SKIP_DTXT) != 0)
+ continue;
+ if (type == REC_TYPE_END && (flags & REC_FLAG_SEEK_END) != 0
+ && vstream_fseek(stream, (off_t) 0, SEEK_END) < 0) {
+ msg_warn("%s: seek error after reading END record: %m",
+ VSTREAM_PATH(stream));
+ return (REC_TYPE_ERROR);
+ }
+ break;
+ }
+ return (type);
+}
+
+/* rec_goto - follow PTR record */
+
+int rec_goto(VSTREAM *stream, const char *buf)
+{
+ off_t offset;
+ static char *saved_path;
+ static off_t saved_offset;
+ static int reverse_count;
+
+ /*
+ * Crude workaround for queue file loops. VSTREAMs currently have no
+ * option to attach application-specific data, so we use global state and
+ * simple logic to detect if an application switches streams. We trigger
+ * on reverse jumps only. There's one reverse jump for every inserted
+ * header, but only one reverse jump for all appended recipients. No-one
+ * is likely to insert 10000 message headers, but someone might append
+ * 10000 recipients.
+ */
+#define REVERSE_JUMP_LIMIT 10000
+
+ if (saved_path == 0 || strcmp(saved_path, VSTREAM_PATH(stream)) != 0) {
+ if (saved_path)
+ myfree(saved_path);
+ saved_path = mystrdup(VSTREAM_PATH(stream));
+ reverse_count = 0;
+ saved_offset = 0;
+ }
+ while (ISSPACE(*buf))
+ buf++;
+ if ((offset = off_cvt_string(buf)) < 0) {
+ msg_warn("%s: malformed pointer record value: %s",
+ VSTREAM_PATH(stream), buf);
+ return (REC_TYPE_ERROR);
+ } else if (offset == 0) {
+ /* Dummy record. */
+ return (0);
+ } else if (offset <= saved_offset && ++reverse_count > REVERSE_JUMP_LIMIT) {
+ msg_warn("%s: too many reverse jump records", VSTREAM_PATH(stream));
+ return (REC_TYPE_ERROR);
+ } else if (vstream_fseek(stream, offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek error after pointer record: %m",
+ VSTREAM_PATH(stream));
+ return (REC_TYPE_ERROR);
+ } else {
+ saved_offset = offset;
+ return (0);
+ }
+}
+
+/* rec_vfprintf - write formatted string to record */
+
+int rec_vfprintf(VSTREAM *stream, int type, const char *format, va_list ap)
+{
+ static VSTRING *vp;
+
+ if (vp == 0)
+ vp = vstring_alloc(100);
+
+ /*
+ * Writing a formatted string involves an extra copy, because we must
+ * know the record length before we can write it.
+ */
+ vstring_vsprintf(vp, format, ap);
+ return (REC_PUT_BUF(stream, type, vp));
+}
+
+/* rec_fprintf - write formatted string to record */
+
+int rec_fprintf(VSTREAM *stream, int type, const char *format,...)
+{
+ int result;
+ va_list ap;
+
+ va_start(ap, format);
+ result = rec_vfprintf(stream, type, format, ap);
+ va_end(ap);
+ return (result);
+}
+
+/* rec_fputs - write string to record */
+
+int rec_fputs(VSTREAM *stream, int type, const char *str)
+{
+ return (rec_put(stream, type, str, str ? strlen(str) : 0));
+}
+
+/* rec_pad - write padding record */
+
+int rec_pad(VSTREAM *stream, int type, ssize_t len)
+{
+ int width = len - 2; /* type + length */
+
+ return (rec_fprintf(stream, type, "%*s",
+ width < 1 ? 1 : width, "0"));
+}
diff --git a/src/global/record.h b/src/global/record.h
new file mode 100644
index 0000000..8394f6b
--- /dev/null
+++ b/src/global/record.h
@@ -0,0 +1,82 @@
+#ifndef _RECORD_H_INCLUDED_
+#define _RECORD_H_INCLUDED_
+
+/*++
+/* NAME
+/* record 3h
+/* SUMMARY
+/* simple typed record I/O
+/* SYNOPSIS
+/* #include <record.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Record type values are positive numbers 0..255. Negative record type
+ * values are reserved for diagnostics.
+ */
+#define REC_TYPE_EOF -1 /* no record */
+#define REC_TYPE_ERROR -2 /* bad record */
+
+ /*
+ * Functional interface.
+ */
+extern int rec_get_raw(VSTREAM *, VSTRING *, ssize_t, int);
+extern int rec_put(VSTREAM *, int, const char *, ssize_t);
+extern int rec_put_type(VSTREAM *, int, off_t);
+extern int PRINTFLIKE(3, 4) rec_fprintf(VSTREAM *, int, const char *,...);
+extern int rec_fputs(VSTREAM *, int, const char *);
+extern int rec_goto(VSTREAM *, const char *);
+extern int rec_pad(VSTREAM *, int, ssize_t);
+
+#define REC_PUT_BUF(v, t, b) rec_put((v), (t), vstring_str(b), VSTRING_LEN(b))
+
+#define REC_FLAG_NONE (0)
+#define REC_FLAG_FOLLOW_PTR (1<<0) /* follow PTR records */
+#define REC_FLAG_SKIP_DTXT (1<<1) /* skip DTXT records */
+#define REC_FLAG_SEEK_END (1<<2) /* seek EOF after END record */
+
+#define REC_FLAG_DEFAULT \
+ (REC_FLAG_FOLLOW_PTR | REC_FLAG_SKIP_DTXT | REC_FLAG_SEEK_END)
+
+#define REC_GET_HIDDEN_TYPE(t) \
+ ((t) == REC_TYPE_PTR || (t) == REC_TYPE_DTXT)
+
+#define rec_get(fp, buf, limit) \
+ rec_get_raw((fp), (buf), (limit), REC_FLAG_DEFAULT)
+
+#define REC_SPACE_NEED(buflen, reclen) do { \
+ ssize_t _c, _l; \
+ for (_c = 1, _l = (buflen); (_l >>= 7U) != 0; _c++) \
+ ; \
+ (reclen) = 1 + _c + (buflen); \
+ } while (0)
+
+ /*
+ * Stuff that needs <stdarg.h>
+ */
+extern int rec_vfprintf(VSTREAM *, int, const char *, va_list);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/reject_deliver_request.c b/src/global/reject_deliver_request.c
new file mode 100644
index 0000000..1b1b2a5
--- /dev/null
+++ b/src/global/reject_deliver_request.c
@@ -0,0 +1,105 @@
+/*++
+/* NAME
+/* reject_deliver_request 3
+/* SUMMARY
+/* reject an entire delivery request
+/* SYNOPSIS
+/* #include <reject_deliver_request.h>
+/*
+/* int reject_deliver_request(
+/* const char *service,
+/* DELIVER_REQUEST *request,
+/* const char *code,
+/* const char *format, ...);
+/* DESCRIPTION
+/* reject_deliver_request() rejects an entire delivery request
+/* and bounces or defers all its recipients. The result value
+/* is the request's delivery status.
+/*
+/* Arguments:
+/* .IP service
+/* The service name from master.cf.
+/* .IP request
+/* The delivery request that is being rejected.
+/* .IP code
+/* Enhanced status code, must be in 4.X.X or 5.X.X. form.
+/* All recipients in the request are bounced or deferred
+/* depending on the status code value.
+/* .IP "format, ..."
+/* Format string and optional arguments.
+/* DIAGNOSTICS
+/* Panic: interface violation. Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* 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>
+
+ /*
+ * Global library.
+ */
+#include <bounce.h>
+#include <defer.h>
+#include <deliver_completed.h>
+#include <deliver_request.h>
+#include <recipient_list.h>
+
+/* reject_deliver_request - reject an entire delivery request */
+
+int reject_deliver_request(const char *service, DELIVER_REQUEST *request,
+ const char *code,
+ const char *format,...)
+{
+ const char myname[] = "reject_deliver_request";
+ va_list ap;
+ RECIPIENT *rcpt;
+ DSN_BUF *why;
+ int status;
+ int result = 0;
+ int n;
+
+ /*
+ * Format something that we can pass to bounce_append() or
+ * defer_append().
+ */
+ va_start(ap, format);
+ why = vdsb_simple(dsb_create(), code, format, ap);
+ va_end(ap);
+ (void) DSN_FROM_DSN_BUF(why);
+ if (strchr("45", vstring_str(why->status)[0]) == 0)
+ msg_panic("%s: bad enhanced status code %s", myname, code);
+
+ /*
+ * Blindly bounce or defer all recipients.
+ */
+ for (n = 0; n < request->rcpt_list.len; n++) {
+ rcpt = request->rcpt_list.info + n;
+ status = (vstring_str(why->status)[0] != '4' ?
+ bounce_append : defer_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id,
+ &request->msg_stats, rcpt,
+ service, &why->dsn);
+ if (status == 0)
+ deliver_completed(request->fp, rcpt->offset);
+ result |= status;
+ }
+ dsb_free(why);
+ return (result);
+}
diff --git a/src/global/remove.c b/src/global/remove.c
new file mode 100644
index 0000000..5f764b2
--- /dev/null
+++ b/src/global/remove.c
@@ -0,0 +1,72 @@
+/*++
+/* NAME
+/* REMOVE 3
+/* SUMMARY
+/* remove or stash away file
+/* SYNOPSIS
+/* \fBint REMOVE(path)\fR
+/* \fBconst char *path;\fR
+/* DESCRIPTION
+/* \fBREMOVE()\fR removes a file, or renames it to a unique name,
+/* depending on the setting of the boolean \fBvar_dont_remove\fR
+/* flag.
+/* DIAGNOSTICS
+/* The result is 0 in case of success, -1 in case of trouble.
+/* The global \fBerrno\fR variable reflects the nature of the
+/* problem.
+/* FILES
+/* saved/*, stashed-away files.
+/* SEE ALSO
+/* remove(3)
+/* 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>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <warn_stat.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* REMOVE - squirrel away a file instead of removing it */
+
+int REMOVE(const char *path)
+{
+ static VSTRING *dest;
+ char *slash;
+ struct stat st;
+
+ if (var_dont_remove == 0) {
+ return (remove(path));
+ } else {
+ if (dest == 0)
+ dest = vstring_alloc(10);
+ vstring_sprintf(dest, "saved/%s", ((slash = strrchr(path, '/')) != 0) ?
+ slash + 1 : path);
+ for (;;) {
+ if (stat(vstring_str(dest), &st) < 0)
+ break;
+ vstring_strcat(dest, "+");
+ }
+ return (rename(path, vstring_str(dest)));
+ }
+}
diff --git a/src/global/resolve_clnt.c b/src/global/resolve_clnt.c
new file mode 100644
index 0000000..ccd67b2
--- /dev/null
+++ b/src/global/resolve_clnt.c
@@ -0,0 +1,408 @@
+/*++
+/* NAME
+/* resolve_clnt 3
+/* SUMMARY
+/* address resolve service client (internal forms)
+/* SYNOPSIS
+/* #include <resolve_clnt.h>
+/*
+/* typedef struct {
+/* .in +4
+/* VSTRING *transport;
+/* VSTRING *nexthop
+/* VSTRING *recipient;
+/* int flags;
+/* .in -4
+/* } RESOLVE_REPLY;
+/*
+/* void resolve_clnt_init(reply)
+/* RESOLVE_REPLY *reply;
+/*
+/* void resolve_clnt_query_from(sender, address, reply)
+/* const char *sender;
+/* const char *address;
+/* RESOLVE_REPLY *reply;
+/*
+/* void resolve_clnt_verify_from(sender, address, reply)
+/* const char *sender;
+/* const char *address;
+/* RESOLVE_REPLY *reply;
+/*
+/* void resolve_clnt_free(reply)
+/* RESOLVE_REPLY *reply;
+/* DESCRIPTION
+/* This module implements a mail address resolver client.
+/*
+/* resolve_clnt_init() initializes a reply data structure for use
+/* by resolve_clnt_query(). The structure is destroyed by passing
+/* it to resolve_clnt_free().
+/*
+/* resolve_clnt_query_from() sends an internal-form recipient address
+/* (user@domain) to the resolver daemon and returns the resulting
+/* transport name, next_hop host name, and internal-form recipient
+/* address. In case of communication failure the program keeps trying
+/* until the mail system goes down. The internal-form sender
+/* information is used for sender-dependent relayhost lookup.
+/* Specify RESOLVE_NULL_FROM when the sender is unavailable.
+/*
+/* resolve_clnt_verify_from() implements an alternative version that can
+/* be used for address verification.
+/*
+/* In the resolver reply, the flags member is the bit-wise OR of
+/* zero or more of the following:
+/* .IP RESOLVE_FLAG_FINAL
+/* The recipient address resolves to a mail transport that performs
+/* final delivery. The destination is local or corresponds to a hosted
+/* domain that is handled by the local machine. This flag is currently
+/* not used.
+/* .IP RESOLVE_FLAG_ROUTED
+/* After address resolution the recipient localpart contains further
+/* routing information, so the resolved next-hop destination is not
+/* the final destination.
+/* .IP RESOLVE_FLAG_ERROR
+/* The address resolved to something that has invalid syntax.
+/* .IP RESOLVE_FLAG_FAIL
+/* The request could not be completed.
+/* .PP
+/* In addition, the address domain class is returned by setting
+/* one of the following flags (this is preliminary code awaiting
+/* more permanent implementation of address domain class handling):
+/* .IP RESOLVE_CLASS_LOCAL
+/* The address domain matches $mydestination, $inet_interfaces
+/* or $proxy_interfaces.
+/* .IP RESOLVE_CLASS_ALIAS
+/* The address domain matches $virtual_alias_domains (virtual
+/* alias domains, where each address is redirected to a real
+/* local or remote address).
+/* .IP RESOLVE_CLASS_VIRTUAL
+/* The address domain matches $virtual_mailbox_domains (true
+/* virtual domains where each address can have its own mailbox).
+/* .IP RESOLVE_CLASS_RELAY
+/* The address domain matches $relay_domains, i.e. this is an
+/* authorized mail relay destination.
+/* .IP RESOLVE_CLASS_DEFAULT
+/* The address matches none of the above. Access to this domain
+/* should be limited to authorized senders only.
+/* .PP
+/* For convenience, the constant RESOLVE_CLASS_FINAL includes all
+/* cases where the local machine is the final destination.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: mail system is down.
+/* SEE ALSO
+/* mail_proto(3h) low-level mail component glue.
+/* 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 <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <events.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+#include "mail_params.h"
+#include "clnt_stream.h"
+#include "resolve_clnt.h"
+
+/* Application-specific. */
+
+ /*
+ * XXX this is shared with the rewrite client to save a file descriptor.
+ */
+extern CLNT_STREAM *rewrite_clnt_stream;
+
+static time_t last_expire;
+static VSTRING *last_class;
+static VSTRING *last_sender;
+static VSTRING *last_addr;
+static RESOLVE_REPLY last_reply;
+
+/* resolve_clnt_init - initialize reply */
+
+void resolve_clnt_init(RESOLVE_REPLY *reply)
+{
+ reply->transport = vstring_alloc(100);
+ reply->nexthop = vstring_alloc(100);
+ reply->recipient = vstring_alloc(100);
+ reply->flags = 0;
+}
+
+/* resolve_clnt_handshake - receive server protocol announcement */
+
+static int resolve_clnt_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL),
+ ATTR_TYPE_END));
+}
+
+/* resolve_clnt - resolve address to (transport, next hop, recipient) */
+
+void resolve_clnt(const char *class, const char *sender,
+ const char *addr, RESOLVE_REPLY *reply)
+{
+ const char *myname = "resolve_clnt";
+ VSTREAM *stream;
+ int server_flags;
+ int count = 0;
+
+ /*
+ * One-entry cache.
+ */
+ if (last_addr == 0) {
+ last_class = vstring_alloc(10);
+ last_sender = vstring_alloc(10);
+ last_addr = vstring_alloc(100);
+ resolve_clnt_init(&last_reply);
+ }
+
+ /*
+ * Sanity check. The result must not clobber the input because we may
+ * have to retransmit the request.
+ */
+#define STR vstring_str
+
+ if (addr == STR(reply->recipient))
+ msg_panic("%s: result clobbers input", myname);
+
+ /*
+ * Peek at the cache.
+ */
+#define IFSET(flag, text) ((reply->flags & (flag)) ? (text) : "")
+
+ if (time((time_t *) 0) < last_expire
+ && *addr && strcmp(addr, STR(last_addr)) == 0
+ && strcmp(class, STR(last_class)) == 0
+ && strcmp(sender, STR(last_sender)) == 0) {
+ vstring_strcpy(reply->transport, STR(last_reply.transport));
+ vstring_strcpy(reply->nexthop, STR(last_reply.nexthop));
+ vstring_strcpy(reply->recipient, STR(last_reply.recipient));
+ reply->flags = last_reply.flags;
+ if (msg_verbose)
+ msg_info("%s: cached: `%s' -> `%s' -> transp=`%s' host=`%s' rcpt=`%s' flags=%s%s%s%s class=%s%s%s%s%s",
+ myname, sender, addr, STR(reply->transport),
+ STR(reply->nexthop), STR(reply->recipient),
+ IFSET(RESOLVE_FLAG_FINAL, "final"),
+ IFSET(RESOLVE_FLAG_ROUTED, "routed"),
+ IFSET(RESOLVE_FLAG_ERROR, "error"),
+ IFSET(RESOLVE_FLAG_FAIL, "fail"),
+ IFSET(RESOLVE_CLASS_LOCAL, "local"),
+ IFSET(RESOLVE_CLASS_ALIAS, "alias"),
+ IFSET(RESOLVE_CLASS_VIRTUAL, "virtual"),
+ IFSET(RESOLVE_CLASS_RELAY, "relay"),
+ IFSET(RESOLVE_CLASS_DEFAULT, "default"));
+ return;
+ }
+
+ /*
+ * Keep trying until we get a complete response. The resolve service is
+ * CPU bound; making the client asynchronous would just complicate the
+ * code.
+ */
+ if (rewrite_clnt_stream == 0)
+ rewrite_clnt_stream = clnt_stream_create(MAIL_CLASS_PRIVATE,
+ var_rewrite_service,
+ var_ipc_idle_limit,
+ var_ipc_ttl_limit,
+ resolve_clnt_handshake);
+
+ for (;;) {
+ stream = clnt_stream_access(rewrite_clnt_stream);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, class),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags),
+ RECV_ATTR_STR(MAIL_ATTR_TRANSPORT, reply->transport),
+ RECV_ATTR_STR(MAIL_ATTR_NEXTHOP, reply->nexthop),
+ RECV_ATTR_STR(MAIL_ATTR_RECIP, reply->recipient),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &reply->flags),
+ ATTR_TYPE_END) != 5) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ var_rewrite_service);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: `%s' -> `%s' -> transp=`%s' host=`%s' rcpt=`%s' flags=%s%s%s%s class=%s%s%s%s%s",
+ myname, sender, addr, STR(reply->transport),
+ STR(reply->nexthop), STR(reply->recipient),
+ IFSET(RESOLVE_FLAG_FINAL, "final"),
+ IFSET(RESOLVE_FLAG_ROUTED, "routed"),
+ IFSET(RESOLVE_FLAG_ERROR, "error"),
+ IFSET(RESOLVE_FLAG_FAIL, "fail"),
+ IFSET(RESOLVE_CLASS_LOCAL, "local"),
+ IFSET(RESOLVE_CLASS_ALIAS, "alias"),
+ IFSET(RESOLVE_CLASS_VIRTUAL, "virtual"),
+ IFSET(RESOLVE_CLASS_RELAY, "relay"),
+ IFSET(RESOLVE_CLASS_DEFAULT, "default"));
+ /* Server-requested disconnect. */
+ if (server_flags != 0)
+ clnt_stream_recover(rewrite_clnt_stream);
+ if (STR(reply->transport)[0] == 0)
+ msg_warn("%s: null transport result for: <%s>", myname, addr);
+ else if (STR(reply->recipient)[0] == 0 && *addr != 0)
+ msg_warn("%s: null recipient result for: <%s>", myname, addr);
+ else
+ break;
+ }
+ sleep(1); /* XXX make configurable */
+ clnt_stream_recover(rewrite_clnt_stream);
+ }
+
+ /*
+ * Update the cache.
+ */
+ vstring_strcpy(last_class, class);
+ vstring_strcpy(last_sender, sender);
+ vstring_strcpy(last_addr, addr);
+ vstring_strcpy(last_reply.transport, STR(reply->transport));
+ vstring_strcpy(last_reply.nexthop, STR(reply->nexthop));
+ vstring_strcpy(last_reply.recipient, STR(reply->recipient));
+ last_reply.flags = reply->flags;
+ last_expire = time((time_t *) 0) + 30; /* XXX make configurable */
+}
+
+/* resolve_clnt_free - destroy reply */
+
+void resolve_clnt_free(RESOLVE_REPLY *reply)
+{
+ reply->transport = vstring_free(reply->transport);
+ reply->nexthop = vstring_free(reply->nexthop);
+ reply->recipient = vstring_free(reply->recipient);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <mail_conf.h>
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-v] [address...]", myname);
+}
+
+static void resolve(char *class, char *addr, RESOLVE_REPLY *reply)
+{
+ struct RESOLVE_FLAG_TABLE {
+ int flag;
+ const char *name;
+ };
+ struct RESOLVE_FLAG_TABLE resolve_flag_table[] = {
+ RESOLVE_FLAG_FINAL, "FLAG_FINAL",
+ RESOLVE_FLAG_ROUTED, "FLAG_ROUTED",
+ RESOLVE_FLAG_ERROR, "FLAG_ERROR",
+ RESOLVE_FLAG_FAIL, "FLAG_FAIL",
+ RESOLVE_CLASS_LOCAL, "CLASS_LOCAL",
+ RESOLVE_CLASS_ALIAS, "CLASS_ALIAS",
+ RESOLVE_CLASS_VIRTUAL, "CLASS_VIRTUAL",
+ RESOLVE_CLASS_RELAY, "CLASS_RELAY",
+ RESOLVE_CLASS_DEFAULT, "CLASS_DEFAULT",
+ 0,
+ };
+ struct RESOLVE_FLAG_TABLE *fp;
+
+ resolve_clnt(class, RESOLVE_NULL_FROM, addr, reply);
+ if (reply->flags & RESOLVE_FLAG_FAIL) {
+ vstream_printf("request failed\n");
+ } else {
+ vstream_printf("%-10s %s\n", "class", class);
+ vstream_printf("%-10s %s\n", "address", addr);
+ vstream_printf("%-10s %s\n", "transport", STR(reply->transport));
+ vstream_printf("%-10s %s\n", "nexthop", *STR(reply->nexthop) ?
+ STR(reply->nexthop) : "[none]");
+ vstream_printf("%-10s %s\n", "recipient", STR(reply->recipient));
+ vstream_printf("%-10s ", "flags");
+ for (fp = resolve_flag_table; fp->name; fp++) {
+ if (reply->flags & fp->flag) {
+ vstream_printf("%s ", fp->name);
+ reply->flags &= ~fp->flag;
+ }
+ }
+ if (reply->flags != 0)
+ vstream_printf("Unknown flag 0x%x", reply->flags);
+ vstream_printf("\n\n");
+ vstream_fflush(VSTREAM_OUT);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ RESOLVE_REPLY reply;
+ char *addr;
+ int ch;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ resolve_clnt_init(&reply);
+
+ if (argc > optind) {
+ while (argv[optind] && argv[optind + 1]) {
+ resolve(argv[optind], argv[optind + 1], &reply);
+ optind += 2;
+ }
+ } else {
+ VSTRING *buffer = vstring_alloc(1);
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ addr = split_at(STR(buffer), ' ');
+ if (*STR(buffer) == 0)
+ msg_fatal("need as input: class [address]");
+ if (addr == 0)
+ addr = "";
+ resolve(STR(buffer), addr, &reply);
+ }
+ vstring_free(buffer);
+ }
+ resolve_clnt_free(&reply);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/resolve_clnt.h b/src/global/resolve_clnt.h
new file mode 100644
index 0000000..04ad1a1
--- /dev/null
+++ b/src/global/resolve_clnt.h
@@ -0,0 +1,83 @@
+#ifndef _RESOLVE_CLNT_H_INCLUDED_
+#define _RESOLVE_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* resolve_clnt 3h
+/* SUMMARY
+/* address resolver client
+/* SYNOPSIS
+/* #include <resolve_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define RESOLVE_REGULAR "resolve"
+#define RESOLVE_VERIFY "verify"
+
+#define RESOLVE_FLAG_FINAL (1<<0) /* final delivery */
+#define RESOLVE_FLAG_ROUTED (1<<1) /* routed destination */
+#define RESOLVE_FLAG_ERROR (1<<2) /* bad destination syntax */
+#define RESOLVE_FLAG_FAIL (1<<3) /* request failed */
+
+#define RESOLVE_CLASS_LOCAL (1<<8) /* mydestination/inet_interfaces */
+#define RESOLVE_CLASS_ALIAS (1<<9) /* virtual_alias_domains */
+#define RESOLVE_CLASS_VIRTUAL (1<<10) /* virtual_mailbox_domains */
+#define RESOLVE_CLASS_RELAY (1<<11) /* relay_domains */
+#define RESOLVE_CLASS_DEFAULT (1<<12) /* raise reject_unauth_destination */
+
+#define RESOLVE_CLASS_FINAL \
+ (RESOLVE_CLASS_LOCAL | RESOLVE_CLASS_ALIAS | RESOLVE_CLASS_VIRTUAL)
+
+#define RESOLVE_CLASS_MASK \
+ (RESOLVE_CLASS_LOCAL | RESOLVE_CLASS_ALIAS | RESOLVE_CLASS_VIRTUAL \
+ | RESOLVE_CLASS_RELAY | RESOLVE_CLASS_DEFAULT)
+
+typedef struct RESOLVE_REPLY {
+ VSTRING *transport;
+ VSTRING *nexthop;
+ VSTRING *recipient;
+ int flags;
+} RESOLVE_REPLY;
+
+extern void resolve_clnt_init(RESOLVE_REPLY *);
+extern void resolve_clnt(const char *, const char *, const char *, RESOLVE_REPLY *);
+extern void resolve_clnt_free(RESOLVE_REPLY *);
+
+#define RESOLVE_NULL_FROM ""
+
+#define resolve_clnt_query_from(f, a, r) \
+ resolve_clnt(RESOLVE_REGULAR, (f), (a), (r))
+#define resolve_clnt_verify_from(f, a, r) \
+ resolve_clnt(RESOLVE_VERIFY, (f), (a), (r))
+
+#define RESOLVE_CLNT_ASSIGN(reply, transport, nexthop, recipient) { \
+ (reply).transport = (transport); \
+ (reply).nexthop = (nexthop); \
+ (reply).recipient = (recipient); \
+ }
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/resolve_clnt.in b/src/global/resolve_clnt.in
new file mode 100644
index 0000000..8368a42
--- /dev/null
+++ b/src/global/resolve_clnt.in
@@ -0,0 +1,49 @@
+resolve
+resolve @
+resolve @@
+resolve @a.
+resolve @..
+resolve @.@.
+resolve !
+resolve a!
+resolve !b
+resolve a!b
+resolve !@
+resolve a!@
+resolve !b@
+resolve a!b@
+resolve %
+resolve a%
+resolve %b
+resolve a%b
+resolve %@
+resolve a%@
+resolve %b@
+resolve @@
+resolve a@@
+resolve @b@
+resolve a@b@
+resolve a%b@
+resolve a%b@MYHOSTNAME
+resolve a!b@MYHOSTNAME
+resolve a@b@MYHOSTNAME
+resolve a[b]@MYHOSTNAME@MYHOSTNAME
+resolve a[b]%MYHOSTNAME@MYHOSTNAME
+resolve a[b]%MYHOSTNAME%MYHOSTNAME
+resolve MYHOSTNAME!a[b]@MYHOSTNAME
+resolve MYHOSTNAME!a[b]%MYHOSTNAME
+resolve MYHOSTNAME!MYHOSTNAME!a[b]
+resolve user@dom.ain1@dom.ain2
+resolve user%dom.ain1@dom.ain2
+resolve dom.ain1!user@dom.ain2
+resolve user@[1.2.3.4]@dom.ain2
+resolve user%[1.2.3.4]@dom.ain2
+resolve [1.2.3.4]!user@dom.ain2
+resolve user@localhost.MYDOMAIN
+resolve user@[321.1.2.3]
+resolve user@1.2.3
+resolve user@host:port
+resolve user@host
+resolve user@host
+verify user@host
+verify user@host
diff --git a/src/global/resolve_clnt.ref b/src/global/resolve_clnt.ref
new file mode 100644
index 0000000..1692f0a
--- /dev/null
+++ b/src/global/resolve_clnt.ref
@@ -0,0 +1,343 @@
+class resolve
+address
+transport local
+nexthop MYHOSTNAME
+recipient MAILER-DAEMON@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address @
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address @@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address @a.
+transport smtp
+nexthop MYDOMAIN
+recipient @a
+flags CLASS_DEFAULT
+
+class resolve
+address @..
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient @..
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address @.@.
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient @.@.
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address !
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a!
+transport smtp
+nexthop MYDOMAIN
+recipient @a.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address !b
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient b@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a!b
+transport smtp
+nexthop MYDOMAIN
+recipient b@a.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address !@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a!@
+transport smtp
+nexthop MYDOMAIN
+recipient @a.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address !b@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient b@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a!b@
+transport smtp
+nexthop MYDOMAIN
+recipient b@a.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address %
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a%
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient a@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address %b
+transport smtp
+nexthop MYDOMAIN
+recipient @b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a%b
+transport smtp
+nexthop MYDOMAIN
+recipient a@b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address %@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a%@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient a@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address %b@
+transport smtp
+nexthop MYDOMAIN
+recipient @b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address @@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient MAILER-DAEMON@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address a@@
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient a@
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address @b@
+transport smtp
+nexthop MYDOMAIN
+recipient @b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a@b@
+transport smtp
+nexthop MYDOMAIN
+recipient a@b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a%b@
+transport smtp
+nexthop MYDOMAIN
+recipient a@b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a%b@MYHOSTNAME
+transport smtp
+nexthop MYDOMAIN
+recipient a@b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a!b@MYHOSTNAME
+transport smtp
+nexthop MYDOMAIN
+recipient b@a.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a@b@MYHOSTNAME
+transport smtp
+nexthop MYDOMAIN
+recipient a@b.MYDOMAIN
+flags CLASS_DEFAULT
+
+class resolve
+address a[b]@MYHOSTNAME@MYHOSTNAME
+transport local
+nexthop MYHOSTNAME
+recipient a[b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address a[b]%MYHOSTNAME@MYHOSTNAME
+transport local
+nexthop MYHOSTNAME
+recipient a[b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address a[b]%MYHOSTNAME%MYHOSTNAME
+transport local
+nexthop MYHOSTNAME
+recipient a[b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address MYHOSTNAME!a[b]@MYHOSTNAME
+transport local
+nexthop MYHOSTNAME
+recipient a [b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address MYHOSTNAME!a[b]%MYHOSTNAME
+transport local
+nexthop MYHOSTNAME
+recipient a [b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address MYHOSTNAME!MYHOSTNAME!a[b]
+transport local
+nexthop MYHOSTNAME
+recipient a [b]@MYHOSTNAME
+flags CLASS_LOCAL
+
+class resolve
+address user@dom.ain1@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient user@dom.ain1@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address user%dom.ain1@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient user%dom.ain1@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address dom.ain1!user@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient dom.ain1!user@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address user@[1.2.3.4]@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient user@[1.2.3.4]@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address user%[1.2.3.4]@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient user%[1.2.3.4]@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address [1.2.3.4]!user@dom.ain2
+transport smtp
+nexthop MYDOMAIN
+recipient [1.2.3.4]!user@dom.ain2
+flags FLAG_ROUTED CLASS_DEFAULT
+
+class resolve
+address user@localhost.MYDOMAIN
+transport local
+nexthop MYHOSTNAME
+recipient user@localhost.MYDOMAIN
+flags CLASS_LOCAL
+
+class resolve
+address user@[321.1.2.3]
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient user@[321.1.2.3]
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address user@1.2.3
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient user@1.2.3
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address user@host:port
+transport CHANNEL NOT UPDATED
+nexthop NEXTHOP NOT UPDATED
+recipient user@host:port
+flags FLAG_ERROR CLASS_DEFAULT
+
+class resolve
+address user@host
+transport smtp
+nexthop MYDOMAIN
+recipient user@host
+flags CLASS_DEFAULT
+
+class resolve
+address user@host
+transport smtp
+nexthop MYDOMAIN
+recipient user@host
+flags CLASS_DEFAULT
+
+class verify
+address user@host
+transport smtp
+nexthop MYDOMAIN
+recipient user@host
+flags CLASS_DEFAULT
+
+class verify
+address user@host
+transport smtp
+nexthop MYDOMAIN
+recipient user@host
+flags CLASS_DEFAULT
+
diff --git a/src/global/resolve_local.c b/src/global/resolve_local.c
new file mode 100644
index 0000000..c6ef848
--- /dev/null
+++ b/src/global/resolve_local.c
@@ -0,0 +1,192 @@
+/*++
+/* NAME
+/* resolve_local 3
+/* SUMMARY
+/* determine if domain resolves to local mail system
+/* SYNOPSIS
+/* #include <resolve_local.h>
+/*
+/* void resolve_local_init()
+/*
+/* int resolve_local(domain)
+/* const char *domain;
+/* DESCRIPTION
+/* resolve_local() determines if the named domain resolves to the
+/* local mail system, either by case-insensitive exact match
+/* against the domains, files or tables listed in $mydestination,
+/* or by a match of an [address-literal] against of the network
+/* addresses listed in $inet_interfaces or in $proxy_interfaces.
+/* The result is > 0 if the domain matches the list of local
+/* domains and IP addresses, 0 when it does not match, and < 0
+/* in case of error.
+/*
+/* resolve_local_init() performs initialization. If this routine is
+/* not called explicitly ahead of time, it will be called on the fly.
+/* BUGS
+/* Calling resolve_local_init() on the fly is an incomplete solution.
+/* It is bound to fail with applications that enter a chroot jail.
+/* SEE ALSO
+/* own_inet_addr(3), find out my own network interfaces
+/* match_list(3), generic pattern matching engine
+/* match_ops(3), generic pattern matching operators
+/* 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 <mymalloc.h>
+#include <string_list.h>
+#include <myaddrinfo.h>
+#include <valid_mailhost_addr.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <resolve_local.h>
+
+/* Application-specific */
+
+static STRING_LIST *resolve_local_list;
+
+/* resolve_local_init - initialize lookup table */
+
+void resolve_local_init(void)
+{
+ /* Allow on-the-fly update to make testing easier. */
+ if (resolve_local_list)
+ string_list_free(resolve_local_list);
+ resolve_local_list = string_list_init(VAR_MYDEST, MATCH_FLAG_RETURN,
+ var_mydest);
+}
+
+/* resolve_local - match domain against list of local destinations */
+
+int resolve_local(const char *addr)
+{
+ char *saved_addr = mystrdup(addr);
+ char *dest;
+ const char *bare_dest;
+ struct addrinfo *res0 = 0;
+ ssize_t len;
+
+ /*
+ * The optimizer will eliminate tests that always fail.
+ */
+#define RETURN(x) \
+ do { \
+ myfree(saved_addr); \
+ if (res0) \
+ freeaddrinfo(res0); \
+ return(x); \
+ } while (0)
+
+ if (resolve_local_list == 0)
+ resolve_local_init();
+
+ /*
+ * Strip one trailing dot but not dot-dot.
+ *
+ * XXX This should not be distributed all over the code. Problem is,
+ * addresses can enter the system via multiple paths: networks, local
+ * forward/alias/include files, even as the result of address rewriting.
+ */
+ len = strlen(saved_addr);
+ if (len == 0)
+ RETURN(0);
+ if (saved_addr[len - 1] == '.')
+ saved_addr[--len] = 0;
+ if (len == 0 || saved_addr[len - 1] == '.')
+ RETURN(0);
+
+ /*
+ * Compare the destination against the list of destinations that we
+ * consider local.
+ */
+ if (string_list_match(resolve_local_list, saved_addr))
+ RETURN(1);
+ if (resolve_local_list->error != 0)
+ RETURN(resolve_local_list->error);
+
+ /*
+ * Compare the destination against the list of interface addresses that
+ * we are supposed to listen on.
+ *
+ * The destination may be an IPv6 address literal that was buried somewhere
+ * inside a deeply recursively nested address. This information comes
+ * from an untrusted source, and Wietse is not confident that everyone's
+ * getaddrinfo() etc. implementation is sufficiently robust. The syntax
+ * is complex enough with null field compression and with IPv4-in-IPv6
+ * addresses that errors are likely.
+ *
+ * The solution below is ad-hoc. We neutralize the string as soon as we
+ * realize that its contents could be harmful. We neutralize the string
+ * here, instead of neutralizing it in every resolve_local() caller.
+ * That's because resolve_local knows how the address is going to be
+ * parsed and converted into binary form.
+ *
+ * There are several more structural solutions to this.
+ *
+ * - One solution is to disallow address literals. This is not as bad as it
+ * seems: I have never seen actual legitimate use of address literals.
+ *
+ * - Another solution is to label each string with a trustworthiness label
+ * and to expect that all Postfix infrastructure will exercise additional
+ * caution when given a string with untrusted content. This is not likely
+ * to happen.
+ *
+ * FIX 200501 IPv6 patch did not require "IPv6:" prefix in numerical
+ * addresses.
+ */
+ dest = saved_addr;
+ if (*dest == '[' && dest[len - 1] == ']') {
+ dest++;
+ dest[len -= 2] = 0;
+ if ((bare_dest = valid_mailhost_addr(dest, DO_GRIPE)) != 0
+ && hostaddr_to_sockaddr(bare_dest, (char *) 0, 0, &res0) == 0) {
+ if (own_inet_addr(res0->ai_addr) || proxy_inet_addr(res0->ai_addr))
+ RETURN(1);
+ }
+ }
+
+ /*
+ * Must be remote, or a syntax error.
+ */
+ RETURN(0);
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+#include <mail_conf.h>
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ if (argc != 3)
+ msg_fatal("usage: %s mydestination domain", argv[0]);
+ mail_conf_read();
+ myfree(var_mydest);
+ var_mydest = mystrdup(argv[1]);
+ vstream_printf("mydestination=%s destination=%s %s\n", argv[1], argv[2],
+ (rc = resolve_local(argv[2])) > 0 ? "YES" :
+ rc == 0 ? "NO" : "ERROR");
+ vstream_fflush(VSTREAM_OUT);
+ return (0);
+}
+
+#endif
diff --git a/src/global/resolve_local.h b/src/global/resolve_local.h
new file mode 100644
index 0000000..c7ad5e0
--- /dev/null
+++ b/src/global/resolve_local.h
@@ -0,0 +1,36 @@
+#ifndef _RESOLVE_LOCAL_H_INCLUDED_
+#define _RESOLVE_LOCAL_H_INCLUDED_
+
+/*++
+/* NAME
+/* resolve_local 3h
+/* SUMMARY
+/* determine if address resolves to local mail system
+/* SYNOPSIS
+/* #include <resolve_local.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+extern int resolve_local(const char *);
+extern void resolve_local_init(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/resolve_local.in b/src/global/resolve_local.in
new file mode 100644
index 0000000..1646fc5
--- /dev/null
+++ b/src/global/resolve_local.in
@@ -0,0 +1,5 @@
+./resolve_local example.com example.com
+./resolve_local example.net example.com
+./resolve_local fail:1_resolve_local example.com
+./resolve_local fail:1_resolve_local example.com..
+./resolve_local fail:1_resolve_local ''
diff --git a/src/global/resolve_local.ref b/src/global/resolve_local.ref
new file mode 100644
index 0000000..e771469
--- /dev/null
+++ b/src/global/resolve_local.ref
@@ -0,0 +1,6 @@
+mydestination=example.com destination=example.com YES
+mydestination=example.net destination=example.com NO
+unknown: warning: mydestination: fail:1_resolve_local: table lookup problem
+mydestination=fail:1_resolve_local destination=example.com ERROR
+mydestination=fail:1_resolve_local destination=example.com.. NO
+mydestination=fail:1_resolve_local destination= NO
diff --git a/src/global/rewrite_clnt.c b/src/global/rewrite_clnt.c
new file mode 100644
index 0000000..420c778
--- /dev/null
+++ b/src/global/rewrite_clnt.c
@@ -0,0 +1,280 @@
+/*++
+/* NAME
+/* rewrite_clnt 3
+/* SUMMARY
+/* address rewrite service client
+/* SYNOPSIS
+/* #include <vstring.h>
+/* #include <rewrite_clnt.h>
+/*
+/* VSTRING *rewrite_clnt(ruleset, address, result)
+/* const char *ruleset;
+/* const char *address;
+/*
+/* VSTRING *rewrite_clnt_internal(ruleset, address, result)
+/* const char *ruleset;
+/* const char *address;
+/* VSTRING *result;
+/* DESCRIPTION
+/* This module implements a mail address rewriting client.
+/*
+/* rewrite_clnt() sends a rule set name and external-form address to the
+/* rewriting service and returns the resulting external-form address.
+/* In case of communication failure the program keeps trying until the
+/* mail system shuts down.
+/*
+/* rewrite_clnt_internal() performs the same functionality but takes
+/* input in internal (unquoted) form, and produces output in internal
+/* (unquoted) form.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: mail system is down.
+/* SEE ALSO
+/* mail_proto(3h) low-level mail component glue.
+/* 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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <quote_822_local.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+#include "mail_params.h"
+#include "clnt_stream.h"
+#include "rewrite_clnt.h"
+
+/* Application-specific. */
+
+ /*
+ * XXX this is shared with the resolver client to save a file descriptor.
+ */
+CLNT_STREAM *rewrite_clnt_stream = 0;
+
+static time_t last_expire;
+static VSTRING *last_rule;
+static VSTRING *last_addr;
+static VSTRING *last_result;
+
+/* rewrite_clnt_handshake - receive server protocol announcement */
+
+static int rewrite_clnt_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL),
+ ATTR_TYPE_END));
+}
+
+/* rewrite_clnt - rewrite address to (transport, next hop, recipient) */
+
+VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result)
+{
+ VSTREAM *stream;
+ int server_flags;
+ int count = 0;
+
+ /*
+ * One-entry cache.
+ */
+ if (last_addr == 0) {
+ last_rule = vstring_alloc(10);
+ last_addr = vstring_alloc(100);
+ last_result = vstring_alloc(100);
+ }
+
+ /*
+ * Sanity check. An address must be in externalized form. The result must
+ * not clobber the input, because we may have to retransmit the query.
+ */
+#define STR vstring_str
+
+ if (*addr == 0)
+ addr = "";
+ if (addr == STR(result))
+ msg_panic("rewrite_clnt: result clobbers input");
+
+ /*
+ * Peek at the cache.
+ */
+ if (time((time_t *) 0) < last_expire
+ && strcmp(addr, STR(last_addr)) == 0
+ && strcmp(rule, STR(last_rule)) == 0) {
+ vstring_strcpy(result, STR(last_result));
+ if (msg_verbose)
+ msg_info("rewrite_clnt: cached: %s: %s -> %s",
+ rule, addr, vstring_str(result));
+ return (result);
+ }
+
+ /*
+ * Keep trying until we get a complete response. The rewrite service is
+ * CPU bound and making the client asynchronous would just complicate the
+ * code.
+ */
+ if (rewrite_clnt_stream == 0)
+ rewrite_clnt_stream = clnt_stream_create(MAIL_CLASS_PRIVATE,
+ var_rewrite_service,
+ var_ipc_idle_limit,
+ var_ipc_ttl_limit,
+ rewrite_clnt_handshake);
+
+ for (;;) {
+ stream = clnt_stream_access(rewrite_clnt_stream);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, REWRITE_ADDR),
+ SEND_ATTR_STR(MAIL_ATTR_RULE, rule),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, result),
+ ATTR_TYPE_END) != 2) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ var_rewrite_service);
+ } else {
+ if (msg_verbose)
+ msg_info("rewrite_clnt: %s: %s -> %s",
+ rule, addr, vstring_str(result));
+ /* Server-requested disconnect. */
+ if (server_flags != 0)
+ clnt_stream_recover(rewrite_clnt_stream);
+ break;
+ }
+ sleep(1); /* XXX make configurable */
+ clnt_stream_recover(rewrite_clnt_stream);
+ }
+
+ /*
+ * Update the cache.
+ */
+ vstring_strcpy(last_rule, rule);
+ vstring_strcpy(last_addr, addr);
+ vstring_strcpy(last_result, STR(result));
+ last_expire = time((time_t *) 0) + 30; /* XXX make configurable */
+
+ return (result);
+}
+
+/* rewrite_clnt_internal - rewrite from/to internal form */
+
+VSTRING *rewrite_clnt_internal(const char *ruleset, const char *addr, VSTRING *result)
+{
+ VSTRING *src = vstring_alloc(100);
+ VSTRING *dst = vstring_alloc(100);
+
+ /*
+ * Convert the address from internal address form to external RFC822
+ * form, then rewrite it. After rewriting, convert to internal form.
+ */
+ quote_822_local(src, addr);
+ rewrite_clnt(ruleset, STR(src), dst);
+ unquote_822_local(result, STR(dst));
+ vstring_free(src);
+ vstring_free(dst);
+ return (result);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <string.h>
+#include <msg_vstream.h>
+#include <split_at.h>
+#include <vstring_vstream.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-v] [rule address...]", myname);
+}
+
+static void rewrite(char *rule, char *addr, VSTRING *reply)
+{
+ rewrite_clnt(rule, addr, reply);
+ vstream_printf("%-10s %s\n", "rule", rule);
+ vstream_printf("%-10s %s\n", "address", addr);
+ vstream_printf("%-10s %s\n\n", "result", STR(reply));
+ vstream_fflush(VSTREAM_OUT);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *reply;
+ int ch;
+ char *rule;
+ char *addr;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ reply = vstring_alloc(1);
+
+ if (argc > optind) {
+ for (;;) {
+ if ((rule = argv[optind++]) == 0)
+ break;
+ if ((addr = argv[optind++]) == 0)
+ usage(argv[0]);
+ rewrite(rule, addr, reply);
+ }
+ } else {
+ VSTRING *buffer = vstring_alloc(1);
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ if ((addr = split_at(STR(buffer), ' ')) == 0
+ || *(rule = STR(buffer)) == 0)
+ usage(argv[0]);
+ rewrite(rule, addr, reply);
+ }
+ vstring_free(buffer);
+ }
+ vstring_free(reply);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/rewrite_clnt.h b/src/global/rewrite_clnt.h
new file mode 100644
index 0000000..85c19f4
--- /dev/null
+++ b/src/global/rewrite_clnt.h
@@ -0,0 +1,44 @@
+#ifndef _REWRITE_CLNT_H_INCLUDED_
+#define _REWRITE_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* rewrite_clnt 3h
+/* SUMMARY
+/* address rewriter client
+/* SYNOPSIS
+/* #include <rewrite_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_proto.h> /* MAIL_ATTR_RWR_LOCAL */
+
+ /*
+ * External interface.
+ */
+#define REWRITE_ADDR "rewrite"
+#define REWRITE_CANON MAIL_ATTR_RWR_LOCAL /* backwards compatibility */
+
+extern VSTRING *rewrite_clnt(const char *, const char *, VSTRING *);
+extern VSTRING *rewrite_clnt_internal(const char *, const char *, VSTRING *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/rewrite_clnt.in b/src/global/rewrite_clnt.in
new file mode 100644
index 0000000..addf663
--- /dev/null
+++ b/src/global/rewrite_clnt.in
@@ -0,0 +1,26 @@
+local !
+local a!
+local !b
+local a!b
+local %
+local a%
+local %b
+local a%b
+local @
+local a@
+local a@.
+local a@b
+local a@b.
+remote !
+remote a!
+remote !b
+remote a!b
+remote %
+remote a%
+remote %b
+remote a%b
+remote @
+remote a@
+remote a@.
+remote a@b
+remote a@b.
diff --git a/src/global/rewrite_clnt.ref b/src/global/rewrite_clnt.ref
new file mode 100644
index 0000000..edcd0e0
--- /dev/null
+++ b/src/global/rewrite_clnt.ref
@@ -0,0 +1,104 @@
+rule local
+address !
+result ""@
+
+rule local
+address a!
+result ""@a.MYDOMAIN
+
+rule local
+address !b
+result b@
+
+rule local
+address a!b
+result b@a.MYDOMAIN
+
+rule local
+address %
+result ""@
+
+rule local
+address a%
+result a@
+
+rule local
+address %b
+result ""@b.MYDOMAIN
+
+rule local
+address a%b
+result a@b.MYDOMAIN
+
+rule local
+address @
+result ""
+
+rule local
+address a@
+result a@
+
+rule local
+address a@.
+result a@.
+
+rule local
+address a@b
+result a@b.MYDOMAIN
+
+rule local
+address a@b.
+result a@b
+
+rule remote
+address !
+result ""@
+
+rule remote
+address a!
+result ""@a.INVALID_DOMAIN
+
+rule remote
+address !b
+result b@
+
+rule remote
+address a!b
+result b@a.INVALID_DOMAIN
+
+rule remote
+address %
+result ""@
+
+rule remote
+address a%
+result a@
+
+rule remote
+address %b
+result ""@b.INVALID_DOMAIN
+
+rule remote
+address a%b
+result a@b.INVALID_DOMAIN
+
+rule remote
+address @
+result ""
+
+rule remote
+address a@
+result a@
+
+rule remote
+address a@.
+result a@.
+
+rule remote
+address a@b
+result a@b.INVALID_DOMAIN
+
+rule remote
+address a@b.
+result a@b
+
diff --git a/src/global/safe_ultostr.c b/src/global/safe_ultostr.c
new file mode 100644
index 0000000..910c2ee
--- /dev/null
+++ b/src/global/safe_ultostr.c
@@ -0,0 +1,256 @@
+/*++
+/* NAME
+/* safe_ultostr 3
+/* SUMMARY
+/* convert unsigned long to safe string
+/* SYNOPSIS
+/* #include <safe_ultostr.h>
+/*
+/* char *safe_ultostr(result, ulval, base, padlen, padchar)
+/* VSTRING *result;
+/* unsigned long ulval;
+/* int base;
+/* int padlen;
+/* int padchar;
+/*
+/* unsigned long safe_strtoul(start, end, base)
+/* const char *start;
+/* char **end;
+/* int base;
+/* DESCRIPTION
+/* The functions in this module perform conversions between
+/* unsigned long values and "safe" alphanumerical strings
+/* (strings with digits, uppercase letters and lowercase
+/* letters, but without the vowels AEIOUaeiou). Specifically,
+/* the characters B-Z represent the numbers 10-30, and b-z
+/* represent 31-51.
+/*
+/* safe_ultostr() converts an unsigned long value to a safe
+/* alphanumerical string. This is the reverse of safe_strtoul().
+/*
+/* safe_strtoul() implements similar functionality as strtoul()
+/* except that it uses a safe alphanumerical string as input,
+/* and that it supports no signs or 0/0x prefixes.
+/*
+/* Arguments:
+/* .IP result
+/* Buffer for storage of the result of conversion to string.
+/* .IP ulval
+/* Unsigned long value.
+/* .IP base
+/* Value between 2 and 52 inclusive.
+/* .IP padlen
+/* .IP padchar
+/* Left-pad a short result with padchar characters to the
+/* specified length. Specify padlen=0 to disable padding.
+/* .IP start
+/* Pointer to the first character of the string to be converted.
+/* .IP end
+/* On return, pointer to the first character not in the input
+/* alphabet, or to the string terminator.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* safe_strtoul() returns (0, EINVAL) when no conversion could
+/* be performed, and (ULONG_MAX, ERANGE) in case of overflow.
+/* 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 <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <safe_ultostr.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define END vstring_end
+#define SWAP(type, a, b) { type temp; temp = a; a = b; b = temp; }
+
+static unsigned char safe_chars[] =
+"0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
+
+#define SAFE_MAX_BASE (sizeof(safe_chars) - 1)
+#define SAFE_MIN_BASE (2)
+
+/* safe_ultostr - convert unsigned long to safe alphanumerical string */
+
+char *safe_ultostr(VSTRING *buf, unsigned long ulval, int base,
+ int padlen, int padchar)
+{
+ const char *myname = "safe_ultostr";
+ char *start;
+ char *last;
+ int i;
+
+ /*
+ * Sanity check.
+ */
+ if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
+ msg_panic("%s: bad base: %d", myname, base);
+
+ /*
+ * First accumulate the result, backwards.
+ */
+ VSTRING_RESET(buf);
+ while (ulval != 0) {
+ VSTRING_ADDCH(buf, safe_chars[ulval % base]);
+ ulval /= base;
+ }
+ while (VSTRING_LEN(buf) < padlen)
+ VSTRING_ADDCH(buf, padchar);
+ VSTRING_TERMINATE(buf);
+
+ /*
+ * Then, reverse the result.
+ */
+ start = STR(buf);
+ last = END(buf) - 1;
+ for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
+ SWAP(int, start[i], last[-i]);
+ return (STR(buf));
+}
+
+/* safe_strtoul - convert safe alphanumerical string to unsigned long */
+
+unsigned long safe_strtoul(const char *start, char **end, int base)
+{
+ const char *myname = "safe_strtoul";
+ static unsigned char *char_map = 0;
+ unsigned char *cp;
+ unsigned long sum;
+ unsigned long div_limit;
+ unsigned long mod_limit;
+ int char_val;
+
+ /*
+ * Sanity check.
+ */
+ if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
+ msg_panic("%s: bad base: %d", myname, base);
+
+ /*
+ * One-time initialization. Assume 8-bit bytes.
+ */
+ if (char_map == 0) {
+ char_map = (unsigned char *) mymalloc(256);
+ for (char_val = 0; char_val < 256; char_val++)
+ char_map[char_val] = SAFE_MAX_BASE;
+ for (char_val = 0; char_val < SAFE_MAX_BASE; char_val++)
+ char_map[safe_chars[char_val]] = char_val;
+ }
+
+ /*
+ * Per-call initialization.
+ */
+ sum = 0;
+ div_limit = ULONG_MAX / base;
+ mod_limit = ULONG_MAX % base;
+
+ /*
+ * Skip leading whitespace. We don't implement sign/base prefixes.
+ */
+ if (end)
+ *end = (char *) start;
+ while (ISSPACE(*start))
+ ++start;
+
+ /*
+ * Start the conversion.
+ */
+ errno = 0;
+ for (cp = (unsigned char *) start; (char_val = char_map[*cp]) < base; cp++) {
+ /* Return (ULONG_MAX, ERANGE) if the result is too large. */
+ if (sum > div_limit
+ || (sum == div_limit && char_val > mod_limit)) {
+ sum = ULONG_MAX;
+ errno = ERANGE;
+ /* Skip "valid" characters, per the strtoul() spec. */
+ while (char_map[*++cp] < base)
+ /* void */ ;
+ break;
+ }
+ sum = sum * base + char_val;
+ }
+ /* Return (0, EINVAL) after no conversion. Test moved here 20131209. */
+ if (cp == (unsigned char *) start)
+ errno = EINVAL;
+ else if (end)
+ *end = (char *) cp;
+ return (sum);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read a number from stdin, convert to
+ * string, and print the result.
+ */
+#include <stdio.h> /* sscanf */
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ char *junk;
+ unsigned long ulval;
+ int base;
+ char ch;
+ unsigned long ulval2;
+
+#ifdef MISSING_STRTOUL
+#define strtoul strtol
+#endif
+
+ /*
+ * Hard-coded string-to-number test.
+ */
+ ulval2 = safe_strtoul(" ", &junk, 10);
+ if (*junk == 0 || errno != EINVAL)
+ msg_warn("input=' ' result=%lu errno=%m", ulval2);
+
+ /*
+ * Configurable number-to-string-to-number test.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ ch = 0;
+ if (sscanf(STR(buf), "%lu %d%c", &ulval, &base, &ch) != 2 || ch) {
+ msg_warn("bad input %s", STR(buf));
+ } else {
+ (void) safe_ultostr(buf, ulval, base, 5, '0');
+ vstream_printf("%lu = %s\n", ulval, STR(buf));
+ ulval2 = safe_strtoul(STR(buf), &junk, base);
+ if (*junk || (ulval2 == ULONG_MAX && errno == ERANGE))
+ msg_warn("%s: %m", STR(buf));
+ if (ulval2 != ulval)
+ msg_warn("%lu != %lu", ulval2, ulval);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/safe_ultostr.h b/src/global/safe_ultostr.h
new file mode 100644
index 0000000..7ae7445
--- /dev/null
+++ b/src/global/safe_ultostr.h
@@ -0,0 +1,36 @@
+#ifndef _SAFE_ULTOSTR_H_INCLUDED_
+#define _SAFE_ULTOSTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* safe_ultostr 3h
+/* SUMMARY
+/* convert unsigned long to safe string
+/* SYNOPSIS
+/* #include <safe_ultostr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *safe_ultostr(VSTRING *, unsigned long, int, int, int);
+extern unsigned long safe_strtoul(const char *, char **, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/safe_ultostr.in b/src/global/safe_ultostr.in
new file mode 100644
index 0000000..49e2743
--- /dev/null
+++ b/src/global/safe_ultostr.in
@@ -0,0 +1,4 @@
+4294967295 2
+4294967295 10
+4294967295 16
+4294967295 52
diff --git a/src/global/safe_ultostr.ref b/src/global/safe_ultostr.ref
new file mode 100644
index 0000000..829b79e
--- /dev/null
+++ b/src/global/safe_ultostr.ref
@@ -0,0 +1,4 @@
+4294967295 = 11111111111111111111111111111111
+4294967295 = 4294967295
+4294967295 = HHHHHHHH
+4294967295 = CHPgSv
diff --git a/src/global/sasl_mech_filter.c b/src/global/sasl_mech_filter.c
new file mode 100644
index 0000000..bbc3496
--- /dev/null
+++ b/src/global/sasl_mech_filter.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* sasl_mech_filter 3
+/* SUMMARY
+/* Filter SASL mechanism names
+/* SYNOPSIS
+/* #include sasl_mech_filter.h
+/*
+/* const char *sasl_mech_filter(
+/* STRING_LIST *filter,
+/* const char *words)
+/* DESCRIPTION
+/* sasl_mech_filter() applies the specified filter to a list
+/* of SASL mechanism names. The filter is case-insensitive,
+/* but preserves the case of input words.
+/*
+/* Arguments:
+/* .IP filter
+/* Null pointer or filter specification. If this is a nulll
+/* pointer, no filter will be applied.
+/* .IP words
+/* List of SASL authentication mechanisms (separated by blanks).
+/* If the string is empty, the filter will not be applied.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation, table lookup error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <sasl_mech_filter.h>
+
+#ifdef USE_SASL_AUTH
+
+/* sasl_mech_filter - filter a SASL mechanism list */
+
+const char *sasl_mech_filter(STRING_LIST *filter,
+ const char *words)
+{
+ const char myname[] = "sasl_mech_filter";
+ static VSTRING *buf;
+ char *mech_list;
+ char *save_mech;
+ char *mech;
+
+ /*
+ * NOOP if there is no filter, or if the mechanism list is empty.
+ */
+ if (filter == 0 || *words == 0)
+ return (words);
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ VSTRING_RESET(buf);
+ VSTRING_TERMINATE(buf);
+
+ save_mech = mech_list = mystrdup(words);
+
+ while ((mech = mystrtok(&mech_list, " \t")) != 0) {
+ if (string_list_match(filter, mech)) {
+ if (VSTRING_LEN(buf) > 0)
+ VSTRING_ADDCH(buf, ' ');
+ vstring_strcat(buf, mech);
+ if (msg_verbose)
+ msg_info("%s: keep SASL mechanism: '%s'", myname, mech);
+ } else if (filter->error) {
+ msg_fatal("%s: SASL mechanism filter failed for: '%s'",
+ myname, mech);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: drop SASL mechanism: '%s'", myname, mech);
+ }
+ }
+ myfree(save_mech);
+
+ return (vstring_str(buf));
+}
+
+#endif
diff --git a/src/global/sasl_mech_filter.h b/src/global/sasl_mech_filter.h
new file mode 100644
index 0000000..29f9bfe
--- /dev/null
+++ b/src/global/sasl_mech_filter.h
@@ -0,0 +1,35 @@
+#ifndef _SASL_MECH_FILTER_H_INCLUDED_
+#define _SASL_MECH_FILTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* sasl_mech_filter 3h
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include "sasl_mech_filter.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <string_list.h>
+
+ /*
+ * External interface.
+ */
+extern const char *sasl_mech_filter(STRING_LIST *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/global/scache.c b/src/global/scache.c
new file mode 100644
index 0000000..a3c12f1
--- /dev/null
+++ b/src/global/scache.c
@@ -0,0 +1,407 @@
+/*++
+/* NAME
+/* scache 3
+/* SUMMARY
+/* generic session cache API
+/* SYNOPSIS
+/* #include <scache.h>
+/* DESCRIPTION
+/* typedef struct {
+/* .in +4
+/* int dest_count;
+/* int endp_count;
+/* int sess_count;
+/* .in -4
+/* } SCACHE_SIZE;
+/*
+/* unsigned scache_size(scache, size)
+/* SCACHE *scache;
+/* SCACHE_SIZE *size;
+/*
+/* void scache_free(scache)
+/* SCACHE *scache;
+/*
+/* void scache_save_endp(scache, endp_ttl, endp_label, endp_prop, fd)
+/* SCACHE *scache;
+/* int endp_ttl;
+/* const char *endp_label;
+/* const char *endp_prop;
+/* int fd;
+/*
+/* int scache_find_endp(scache, endp_label, endp_prop)
+/* SCACHE *scache;
+/* const char *endp_label;
+/* VSTRING *endp_prop;
+/*
+/* void scache_save_dest(scache, dest_ttl, dest_label,
+/* dest_prop, endp_label)
+/* SCACHE *scache;
+/* int dest_ttl;
+/* const char *dest_label;
+/* const char *dest_prop;
+/* const char *endp_label;
+/*
+/* int scache_find_dest(dest_label, dest_prop, endp_prop)
+/* SCACHE *scache;
+/* const char *dest_label;
+/* VSTRING *dest_prop;
+/* VSTRING *endp_prop;
+/* DESCRIPTION
+/* This module implements a generic session cache interface.
+/* Specific cache types are described in scache_single(3),
+/* scache_clnt(3) and scache_multi(3). These documents also
+/* describe now to instantiate a specific session cache type.
+/*
+/* The code maintains two types of association: a) physical
+/* endpoint to file descriptor, and b) logical endpoint
+/* to physical endpoint. Physical endpoints are stored and
+/* looked up under their low-level session details such as
+/* numerical addresses, while logical endpoints are stored
+/* and looked up by the domain name that humans use. One logical
+/* endpoint can refer to multiple physical endpoints, one
+/* physical endpoint may be referenced by multiple logical
+/* endpoints, and one physical endpoint may have multiple
+/* sessions.
+/*
+/* scache_size() returns the number of logical destination
+/* names, physical endpoint addresses, and cached sessions.
+/*
+/* scache_free() destroys the specified session cache.
+/*
+/* scache_save_endp() stores an open session under the specified
+/* physical endpoint name.
+/*
+/* scache_find_endp() looks up a saved session under the
+/* specified physical endpoint name.
+/*
+/* scache_save_dest() associates the specified physical endpoint
+/* with the specified logical endpoint name.
+/*
+/* scache_find_dest() looks up a saved session under the
+/* specified physical endpoint name.
+/*
+/* Arguments:
+/* .IP endp_ttl
+/* How long the session should be cached. When information
+/* expires it is purged automatically.
+/* .IP endp_label
+/* The transport name and the physical endpoint name under
+/* which the session is stored and looked up.
+/*
+/* In the case of SMTP, the physical endpoint includes the numerical
+/* IP address, address family information, and the numerical TCP port.
+/* .IP endp_prop
+/* Application-specific data with endpoint attributes. It is up to
+/* the application to passivate (flatten) and re-activate this content
+/* upon storage and retrieval, respectively.
+/* .sp
+/* In the case of SMTP, the endpoint attributes specify the
+/* server hostname, IP address, numerical TCP port, as well
+/* as ESMTP features advertised by the server, and when information
+/* expires. All this in some application-specific format that is easy
+/* to unravel when re-activating a cached session.
+/* .IP dest_ttl
+/* How long the destination-to-endpoint binding should be
+/* cached. When information expires it is purged automatically.
+/* .IP dest_label
+/* The transport name and the logical destination under which the
+/* destination-to-endpoint binding is stored and looked up.
+/*
+/* In the case of SMTP, the logical destination is the DNS
+/* host or domain name with or without [], plus the numerical TCP port.
+/* .IP dest_prop
+/* Application-specific attributes that describe features of
+/* this logical to physical binding. It is up to the application
+/* to passivate (flatten) and re-activate this content.
+/* upon storage and retrieval, respectively
+/* .sp
+/* In case the of an SMTP logical destination to physical
+/* endpoint binding, the attributes specify the logical
+/* destination name, numerical port, whether the physical
+/* endpoint is best mx host with respect to a logical or
+/* fall-back destination, and when information expires.
+/* .IP fd
+/* File descriptor with session to be cached.
+/* DIAGNOSTICS
+/* scache_find_endp() and scache_find_dest() return -1 when
+/* the lookup fails, and a file descriptor upon success.
+/*
+/* Other diagnostics: fatal error: memory allocation problem;
+/* panic: internal consistency failure.
+/* SEE ALSO
+/* scache_single(3), single-session, in-memory cache
+/* scache_clnt(3), session cache client
+/* scache_multi(3), multi-session, in-memory cache
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <argv.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <scache.h>
+
+#ifdef TEST
+
+ /*
+ * Driver program for cache regression tests. Although all variants are
+ * relatively simple to verify by hand for single session storage, more
+ * sophisticated instrumentation is needed to demonstrate that the
+ * multi-session cache manager properly handles collisions in the time
+ * domain and in all the name space domains.
+ */
+static SCACHE *scache;
+static VSTRING *endp_prop;
+static VSTRING *dest_prop;
+static int verbose_level = 3;
+
+ /*
+ * Cache mode lookup table. We don't do the client-server variant because
+ * that drags in a ton of configuration junk; the client-server protocol is
+ * relatively easy to verify manually.
+ */
+struct cache_type {
+ char *mode;
+ SCACHE *(*create) (void);
+};
+
+static struct cache_type cache_types[] = {
+ "single", scache_single_create,
+ "multi", scache_multi_create,
+ 0,
+};
+
+#define STR(x) vstring_str(x)
+
+/* cache_type - select session cache type */
+
+static void cache_type(ARGV *argv)
+{
+ struct cache_type *cp;
+
+ if (argv->argc != 2) {
+ msg_error("usage: %s mode", argv->argv[0]);
+ return;
+ }
+ if (scache != 0)
+ scache_free(scache);
+ for (cp = cache_types; cp->mode != 0; cp++) {
+ if (strcmp(cp->mode, argv->argv[1]) == 0) {
+ scache = cp->create();
+ return;
+ }
+ }
+ msg_error("unknown cache type: %s", argv->argv[1]);
+}
+
+/* handle_events - handle events while time advances */
+
+static void handle_events(ARGV *argv)
+{
+ int delay;
+ time_t before;
+ time_t after;
+
+ if (argv->argc != 2 || (delay = atoi(argv->argv[1])) <= 0) {
+ msg_error("usage: %s time", argv->argv[0]);
+ return;
+ }
+ before = event_time();
+ event_drain(delay);
+ after = event_time();
+ if (after < before + delay)
+ sleep(before + delay - after);
+}
+
+/* save_endp - save endpoint->session binding */
+
+static void save_endp(ARGV *argv)
+{
+ int ttl;
+ int fd;
+
+ if (argv->argc != 5
+ || (ttl = atoi(argv->argv[1])) <= 0
+ || (fd = atoi(argv->argv[4])) <= 0) {
+ msg_error("usage: save_endp ttl endpoint endp_props fd");
+ return;
+ }
+ if (DUP2(0, fd) < 0)
+ msg_fatal("dup2(0, %d): %m", fd);
+ scache_save_endp(scache, ttl, argv->argv[2], argv->argv[3], fd);
+}
+
+/* find_endp - find endpoint->session binding */
+
+static void find_endp(ARGV *argv)
+{
+ int fd;
+
+ if (argv->argc != 2) {
+ msg_error("usage: find_endp endpoint");
+ return;
+ }
+ if ((fd = scache_find_endp(scache, argv->argv[1], endp_prop)) >= 0)
+ close(fd);
+}
+
+/* save_dest - save destination->endpoint binding */
+
+static void save_dest(ARGV *argv)
+{
+ int ttl;
+
+ if (argv->argc != 5 || (ttl = atoi(argv->argv[1])) <= 0) {
+ msg_error("usage: save_dest ttl destination dest_props endpoint");
+ return;
+ }
+ scache_save_dest(scache, ttl, argv->argv[2], argv->argv[3], argv->argv[4]);
+}
+
+/* find_dest - find destination->endpoint->session binding */
+
+static void find_dest(ARGV *argv)
+{
+ int fd;
+
+ if (argv->argc != 2) {
+ msg_error("usage: find_dest destination");
+ return;
+ }
+ if ((fd = scache_find_dest(scache, argv->argv[1], dest_prop, endp_prop)) >= 0)
+ close(fd);
+}
+
+/* verbose - adjust noise level during cache manipulation */
+
+static void verbose(ARGV *argv)
+{
+ int level;
+
+ if (argv->argc != 2 || (level = atoi(argv->argv[1])) < 0) {
+ msg_error("usage: verbose level");
+ return;
+ }
+ verbose_level = level;
+}
+
+ /*
+ * The command lookup table.
+ */
+struct action {
+ char *command;
+ void (*action) (ARGV *);
+ int flags;
+};
+
+#define FLAG_NEED_CACHE (1<<0)
+
+static void help(ARGV *);
+
+static struct action actions[] = {
+ "cache_type", cache_type, 0,
+ "save_endp", save_endp, FLAG_NEED_CACHE,
+ "find_endp", find_endp, FLAG_NEED_CACHE,
+ "save_dest", save_dest, FLAG_NEED_CACHE,
+ "find_dest", find_dest, FLAG_NEED_CACHE,
+ "sleep", handle_events, 0,
+ "verbose", verbose, 0,
+ "?", help, 0,
+ 0,
+};
+
+/* help - list commands */
+
+static void help(ARGV *argv)
+{
+ struct action *ap;
+
+ vstream_printf("commands:");
+ for (ap = actions; ap->command != 0; ap++)
+ vstream_printf(" %s", ap->command);
+ vstream_printf("\n");
+ vstream_fflush(VSTREAM_OUT);
+}
+
+/* get_buffer - prompt for input or log input */
+
+static int get_buffer(VSTRING *buf, VSTREAM *fp, int interactive)
+{
+ int status;
+
+ if (interactive) {
+ vstream_printf("> ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if ((status = vstring_get_nonl(buf, fp)) != VSTREAM_EOF) {
+ if (!interactive) {
+ vstream_printf(">>> %s\n", STR(buf));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ return (status);
+}
+
+/* at last, the main program */
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(1);
+ ARGV *argv;
+ struct action *ap;
+ int interactive = isatty(0);
+
+ endp_prop = vstring_alloc(1);
+ dest_prop = vstring_alloc(1);
+
+ vstream_fileno(VSTREAM_ERR) = 1;
+
+ while (get_buffer(buf, VSTREAM_IN, interactive) != VSTREAM_EOF) {
+ argv = argv_split(STR(buf), CHARS_SPACE);
+ if (argv->argc > 0 && argv->argv[0][0] != '#') {
+ msg_verbose = verbose_level;
+ for (ap = actions; ap->command != 0; ap++) {
+ if (strcmp(ap->command, argv->argv[0]) == 0) {
+ if ((ap->flags & FLAG_NEED_CACHE) != 0 && scache == 0)
+ msg_error("no session cache");
+ else
+ ap->action(argv);
+ break;
+ }
+ }
+ msg_verbose = 0;
+ if (ap->command == 0)
+ msg_error("bad command: %s", argv->argv[0]);
+ }
+ argv_free(argv);
+ }
+ scache_free(scache);
+ vstring_free(endp_prop);
+ vstring_free(dest_prop);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/scache.h b/src/global/scache.h
new file mode 100644
index 0000000..a46362a
--- /dev/null
+++ b/src/global/scache.h
@@ -0,0 +1,165 @@
+#ifndef _SCACHE_H_INCLUDED_
+#define _SCACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* scache 3h
+/* SUMMARY
+/* generic session cache API
+/* SYNOPSIS
+/* #include <scache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+typedef struct SCACHE SCACHE;
+typedef struct SCACHE_SIZE SCACHE_SIZE;
+
+ /*
+ * In order to cache a session, we specify:
+ *
+ * - TTL for this session.
+ *
+ * - File descriptor.
+ *
+ * - Transport name and physical endpoint. The transport name must be included,
+ * because fall-back destinations can be transport-dependent, and routing
+ * for a given destination may be context dependent.
+ *
+ * In the case of SMTP, the physical endpoint is the numerical IP address and
+ * TCP port.
+ *
+ * - Application-specific endpoint properties.
+ *
+ * In the case of SMTP, the properties specify the ESMTP features advertised by
+ * the server.
+ *
+ * Note: with message delivery transports that have only one endpoint per
+ * logical destination, the above is the only information that needs to be
+ * maintained in a connection cache.
+ */
+typedef void (*SCACHE_SAVE_ENDP_FN) (SCACHE *, int, const char *, const char *, int);
+typedef int (*SCACHE_FIND_ENDP_FN) (SCACHE *, const char *, VSTRING *);
+
+ /*
+ * The following information is stored in order to make a binding from
+ * logical destination to physical destination. One logical destination can
+ * have multiple physical destinations (and one physical destination can
+ * have multiple sessions).
+ *
+ * - TTL for this binding.
+ *
+ * - Transport name and logical destination.
+ *
+ * In the case of SMTP: the next-hop (NOT: fall-back) destination domain and
+ * destination network port. It is not useful to create a link for an
+ * address literal (but it is not harmful either: it just wastes a few
+ * bits). This information specifies the destination domain in [] if no MX
+ * lookup is done.
+ *
+ * - Application-specific properties.
+ *
+ * In case the of SMTP, the properties specify a) whether a physical endpoint
+ * is best mx host with respect to a logical or fall-back destination (this
+ * information is needed by the loop detection code in smtp_proto.c).
+ *
+ * - Transport name and physical endpoint (see above).
+ *
+ * Note 1: there is no need to store the binding's MX preference or equivalent
+ * with respect to the logical destination; a client should store only the
+ * first successful session for a given delivery request (otherwise the
+ * client would keep talking to a less preferred server after the cached
+ * connection for a more preferred server expires). After a failed delivery,
+ * the client should not attempt to cache follow-up sessions with less
+ * preferred endpoints under the same logical destination.
+ *
+ * Note 2: logical to physical bindings exist independently from cached
+ * sessions. The two types of information have independent TTLs; creation
+ * and destruction proceed independently. Thus, a logical to physical
+ * binding can refer to an endpoint for which all cached connections are
+ * occupied or expired.
+ */
+typedef void (*SCACHE_SAVE_DEST_FN) (SCACHE *, int, const char *, const char *, const char *);
+typedef int (*SCACHE_FIND_DEST_FN) (SCACHE *, const char *, VSTRING *, VSTRING *);
+
+ /*
+ * Session cache statistics. These are the actual numbers at a specific
+ * point in time.
+ */
+struct SCACHE_SIZE {
+ int dest_count; /* Nr of destination names */
+ int endp_count; /* Nr of endpoint addresses */
+ int sess_count; /* Nr of cached sessions */
+};
+
+ /*
+ * Generic session cache object. Actual session cache objects are derived
+ * types with some additional, cache dependent, private information.
+ */
+struct SCACHE {
+ SCACHE_SAVE_ENDP_FN save_endp;
+ SCACHE_FIND_ENDP_FN find_endp;
+ SCACHE_SAVE_DEST_FN save_dest;
+ SCACHE_FIND_DEST_FN find_dest;
+ void (*size) (struct SCACHE *, SCACHE_SIZE *);
+ void (*free) (struct SCACHE *);
+};
+
+extern SCACHE *scache_single_create(void);
+extern SCACHE *scache_clnt_create(const char *, int, int, int);
+extern SCACHE *scache_multi_create(void);
+
+#define scache_save_endp(scache, ttl, endp_label, endp_prop, fd) \
+ (scache)->save_endp((scache), (ttl), (endp_label), (endp_prop), (fd))
+#define scache_find_endp(scache, endp_label, endp_prop) \
+ (scache)->find_endp((scache), (endp_label), (endp_prop))
+#define scache_save_dest(scache, ttl, dest_label, dest_prop, endp_label) \
+ (scache)->save_dest((scache), (ttl), (dest_label), (dest_prop), (endp_label))
+#define scache_find_dest(scache, dest_label, dest_prop, endp_prop) \
+ (scache)->find_dest((scache), (dest_label), (dest_prop), (endp_prop))
+#define scache_size(scache, stats) (scache)->size((scache), (stats))
+#define scache_free(scache) (scache)->free(scache)
+
+ /*
+ * Cache types.
+ */
+#define SCACHE_TYPE_SINGLE 1 /* single-instance cache */
+#define SCACHE_TYPE_CLIENT 2 /* session cache client */
+#define SCACHE_TYPE_MULTI 3 /* multi-instance cache */
+
+ /*
+ * Client-server protocol.
+ */
+#define SCACHE_REQ_FIND_ENDP "find_endp"
+#define SCACHE_REQ_SAVE_ENDP "save_endp"
+#define SCACHE_REQ_FIND_DEST "find_dest"
+#define SCACHE_REQ_SAVE_DEST "save_dest"
+
+ /*
+ * Session cache server status codes.
+ */
+#define SCACHE_STAT_OK 0 /* request completed successfully */
+#define SCACHE_STAT_BAD 1 /* malformed request */
+#define SCACHE_STAT_FAIL 2 /* request completed unsuccessfully */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/scache_clnt.c b/src/global/scache_clnt.c
new file mode 100644
index 0000000..97fe8aa
--- /dev/null
+++ b/src/global/scache_clnt.c
@@ -0,0 +1,443 @@
+/*++
+/* NAME
+/* scache_clnt 3
+/* SUMMARY
+/* session cache manager client
+/* SYNOPSIS
+/* #include <scache.h>
+/* DESCRIPTION
+/* SCACHE *scache_clnt_create(server, timeout, idle_limit, ttl_limit)
+/* const char *server;
+/* int timeout;
+/* int idle_limit;
+/* int ttl_limit;
+/* DESCRIPTION
+/* This module implements the client-side protocol of the
+/* session cache service.
+/*
+/* scache_clnt_create() creates a session cache service client.
+/*
+/* Arguments:
+/* .IP server
+/* The session cache service name.
+/* .IP timeout
+/* Time limit for connect, send or receive operations.
+/* .IP idle_limit
+/* Idle time after which the client disconnects.
+/* .IP ttl_limit
+/* Upper bound on the time that a connection is allowed to persist.
+/* DIAGNOSTICS
+/* Fatal error: memory allocation problem;
+/* warning: communication error;
+/* panic: internal consistency failure.
+/* SEE ALSO
+/* scache(3), generic session cache API
+/* 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 <mymalloc.h>
+#include <auto_clnt.h>
+#include <stringops.h>
+
+/*#define msg_verbose 1*/
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <scache.h>
+
+/* Application-specific. */
+
+ /*
+ * SCACHE_CLNT is a derived type from the SCACHE super-class.
+ */
+typedef struct {
+ SCACHE scache[1]; /* super-class */
+ AUTO_CLNT *auto_clnt; /* client endpoint */
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ VSTRING *dummy; /* dummy buffer */
+#endif
+} SCACHE_CLNT;
+
+#define STR(x) vstring_str(x)
+
+#define SCACHE_MAX_TRIES 2
+
+/* scache_clnt_handshake - receive server protocol announcement */
+
+static int scache_clnt_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE),
+ ATTR_TYPE_END));
+}
+
+/* scache_clnt_save_endp - save endpoint */
+
+static void scache_clnt_save_endp(SCACHE *scache, int endp_ttl,
+ const char *endp_label,
+ const char *endp_prop, int fd)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
+ const char *myname = "scache_clnt_save_endp";
+ VSTREAM *stream;
+ int status;
+ int tries;
+ int count = 0;
+
+ if (msg_verbose)
+ msg_info("%s: endp=%s prop=%s fd=%d",
+ myname, endp_label, endp_prop, fd);
+
+ /*
+ * Sanity check.
+ */
+ if (endp_ttl <= 0)
+ msg_panic("%s: bad endp_ttl: %d", myname, endp_ttl);
+
+ /*
+ * Try a few times before disabling the cache. We use synchronous calls;
+ * the session cache service is CPU bound and making the client
+ * asynchronous would just complicate the code.
+ */
+ for (tries = 0; sp->auto_clnt != 0; tries++) {
+ if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
+ errno = 0;
+ count += 1;
+ if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_SAVE_ENDP),
+ SEND_ATTR_INT(MAIL_ATTR_TTL, endp_ttl),
+ SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, sp->dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ || LOCAL_SEND_FD(vstream_fileno(stream), fd) < 0
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else {
+ if (msg_verbose && status != 0)
+ msg_warn("%s: descriptor save failed with status %d",
+ myname, status);
+ break;
+ }
+ }
+ /* Give up or recover. */
+ if (tries >= SCACHE_MAX_TRIES - 1) {
+ msg_warn("disabling connection caching");
+ auto_clnt_free(sp->auto_clnt);
+ sp->auto_clnt = 0;
+ break;
+ }
+ sleep(1); /* XXX make configurable */
+ auto_clnt_recover(sp->auto_clnt);
+ }
+ /* Always close the descriptor before returning. */
+ if (close(fd) < 0)
+ msg_warn("%s: close(%d): %m", myname, fd);
+}
+
+/* scache_clnt_find_endp - look up cached session */
+
+static int scache_clnt_find_endp(SCACHE *scache, const char *endp_label,
+ VSTRING *endp_prop)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
+ const char *myname = "scache_clnt_find_endp";
+ VSTREAM *stream;
+ int status;
+ int tries;
+ int fd;
+
+ /*
+ * Try a few times before disabling the cache. We use synchronous calls;
+ * the session cache service is CPU bound and making the client
+ * asynchronous would just complicate the code.
+ */
+ for (tries = 0; sp->auto_clnt != 0; tries++) {
+ if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
+ errno = 0;
+ if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_FIND_ENDP),
+ SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
+ ATTR_TYPE_END) != 2) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else if (status != 0) {
+ if (msg_verbose)
+ msg_info("%s: not found: %s", myname, endp_label);
+ return (-1);
+ } else if (
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream) != 0
+ || read_wait(vstream_fileno(stream),
+ stream->timeout) < 0 || /* XXX */
+#endif
+ (fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else {
+#ifdef MUST_READ_AFTER_SENDING_FD
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+#endif
+ if (msg_verbose)
+ msg_info("%s: endp=%s prop=%s fd=%d",
+ myname, endp_label, STR(endp_prop), fd);
+ return (fd);
+ }
+ }
+ /* Give up or recover. */
+ if (tries >= SCACHE_MAX_TRIES - 1) {
+ msg_warn("disabling connection caching");
+ auto_clnt_free(sp->auto_clnt);
+ sp->auto_clnt = 0;
+ return (-1);
+ }
+ sleep(1); /* XXX make configurable */
+ auto_clnt_recover(sp->auto_clnt);
+ }
+ return (-1);
+}
+
+/* scache_clnt_save_dest - create destination/endpoint association */
+
+static void scache_clnt_save_dest(SCACHE *scache, int dest_ttl,
+ const char *dest_label,
+ const char *dest_prop,
+ const char *endp_label)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
+ const char *myname = "scache_clnt_save_dest";
+ VSTREAM *stream;
+ int status;
+ int tries;
+
+ if (msg_verbose)
+ msg_info("%s: dest_label=%s dest_prop=%s endp_label=%s",
+ myname, dest_label, dest_prop, endp_label);
+
+ /*
+ * Sanity check.
+ */
+ if (dest_ttl <= 0)
+ msg_panic("%s: bad dest_ttl: %d", myname, dest_ttl);
+
+ /*
+ * Try a few times before disabling the cache. We use synchronous calls;
+ * the session cache service is CPU bound and making the client
+ * asynchronous would just complicate the code.
+ */
+ for (tries = 0; sp->auto_clnt != 0; tries++) {
+ if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
+ errno = 0;
+ if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_SAVE_DEST),
+ SEND_ATTR_INT(MAIL_ATTR_TTL, dest_ttl),
+ SEND_ATTR_STR(MAIL_ATTR_LABEL, dest_label),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, dest_prop),
+ SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else {
+ if (msg_verbose && status != 0)
+ msg_warn("%s: destination save failed with status %d",
+ myname, status);
+ break;
+ }
+ }
+ /* Give up or recover. */
+ if (tries >= SCACHE_MAX_TRIES - 1) {
+ msg_warn("disabling connection caching");
+ auto_clnt_free(sp->auto_clnt);
+ sp->auto_clnt = 0;
+ break;
+ }
+ sleep(1); /* XXX make configurable */
+ auto_clnt_recover(sp->auto_clnt);
+ }
+}
+
+/* scache_clnt_find_dest - look up cached session */
+
+static int scache_clnt_find_dest(SCACHE *scache, const char *dest_label,
+ VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
+ const char *myname = "scache_clnt_find_dest";
+ VSTREAM *stream;
+ int status;
+ int tries;
+ int fd;
+
+ /*
+ * Try a few times before disabling the cache. We use synchronous calls;
+ * the session cache service is CPU bound and making the client
+ * asynchronous would just complicate the code.
+ */
+ for (tries = 0; sp->auto_clnt != 0; tries++) {
+ if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
+ errno = 0;
+ if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_FIND_DEST),
+ SEND_ATTR_STR(MAIL_ATTR_LABEL, dest_label),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, dest_prop),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
+ ATTR_TYPE_END) != 3) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else if (status != 0) {
+ if (msg_verbose)
+ msg_info("%s: not found: %s", myname, dest_label);
+ return (-1);
+ } else if (
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream) != 0
+ || read_wait(vstream_fileno(stream),
+ stream->timeout) < 0 || /* XXX */
+#endif
+ (fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ VSTREAM_PATH(stream));
+ /* Give up or recover. */
+ } else {
+#ifdef MUST_READ_AFTER_SENDING_FD
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+#endif
+ if (msg_verbose)
+ msg_info("%s: dest=%s dest_prop=%s endp_prop=%s fd=%d",
+ myname, dest_label, STR(dest_prop), STR(endp_prop), fd);
+ return (fd);
+ }
+ }
+ /* Give up or recover. */
+ if (tries >= SCACHE_MAX_TRIES - 1) {
+ msg_warn("disabling connection caching");
+ auto_clnt_free(sp->auto_clnt);
+ sp->auto_clnt = 0;
+ return (-1);
+ }
+ sleep(1); /* XXX make configurable */
+ auto_clnt_recover(sp->auto_clnt);
+ }
+ return (-1);
+}
+
+/* scache_clnt_size - dummy */
+
+static void scache_clnt_size(SCACHE *unused_scache, SCACHE_SIZE *size)
+{
+ /* XXX Crap in a hurry. */
+ size->dest_count = 0;
+ size->endp_count = 0;
+ size->sess_count = 0;
+}
+
+/* scache_clnt_free - destroy cache */
+
+static void scache_clnt_free(SCACHE *scache)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
+
+ if (sp->auto_clnt)
+ auto_clnt_free(sp->auto_clnt);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ vstring_free(sp->dummy);
+#endif
+ myfree((void *) sp);
+}
+
+/* scache_clnt_create - initialize */
+
+SCACHE *scache_clnt_create(const char *server, int timeout,
+ int idle_limit, int ttl_limit)
+{
+ SCACHE_CLNT *sp = (SCACHE_CLNT *) mymalloc(sizeof(*sp));
+ char *service;
+
+ sp->scache->save_endp = scache_clnt_save_endp;
+ sp->scache->find_endp = scache_clnt_find_endp;
+ sp->scache->save_dest = scache_clnt_save_dest;
+ sp->scache->find_dest = scache_clnt_find_dest;
+ sp->scache->size = scache_clnt_size;
+ sp->scache->free = scache_clnt_free;
+
+ service = concatenate("local:" MAIL_CLASS_PRIVATE "/", server, (char *) 0);
+ sp->auto_clnt = auto_clnt_create(service, timeout, idle_limit, ttl_limit);
+ auto_clnt_control(sp->auto_clnt,
+ AUTO_CLNT_CTL_HANDSHAKE, scache_clnt_handshake,
+ AUTO_CLNT_CTL_END);
+ myfree(service);
+
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ sp->dummy = vstring_alloc(1);
+#endif
+
+ return (sp->scache);
+}
diff --git a/src/global/scache_multi.c b/src/global/scache_multi.c
new file mode 100644
index 0000000..7d8373d
--- /dev/null
+++ b/src/global/scache_multi.c
@@ -0,0 +1,493 @@
+/*++
+/* NAME
+/* scache_multi 3
+/* SUMMARY
+/* multi-session cache
+/* SYNOPSIS
+/* #include <scache.h>
+/* DESCRIPTION
+/* SCACHE *scache_multi_create()
+/* DESCRIPTION
+/* This module implements an in-memory, multi-session cache.
+/*
+/* scache_multi_create() instantiates a session cache that
+/* stores multiple sessions.
+/* DIAGNOSTICS
+/* Fatal error: memory allocation problem;
+/* panic: internal consistency failure.
+/* SEE ALSO
+/* scache(3), generic session cache API
+/* 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 <stddef.h> /* offsetof() */
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <ring.h>
+#include <htable.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <events.h>
+
+/*#define msg_verbose 1*/
+
+/* Global library. */
+
+#include <scache.h>
+
+/* Application-specific. */
+
+ /*
+ * SCACHE_MULTI is a derived type from the SCACHE super-class.
+ *
+ * Each destination has an entry in the destination hash table, and each
+ * destination->endpoint binding is kept in a circular list under its
+ * destination hash table entry.
+ *
+ * Likewise, each endpoint has an entry in the endpoint hash table, and each
+ * endpoint->session binding is kept in a circular list under its endpoint
+ * hash table entry.
+ *
+ * We do not attempt to limit the number of destination or endpoint entries,
+ * nor do we attempt to limit the number of sessions. Doing so would require
+ * a write-through cache. Currently, the CTABLE cache module supports only
+ * read-through caching.
+ *
+ * This is no problem because the number of cached destinations is limited by
+ * design. Sites that specify a wild-card domain pattern, and thus cache
+ * every session in recent history, may be in for a surprise.
+ */
+typedef struct {
+ SCACHE scache[1]; /* super-class */
+ HTABLE *dest_cache; /* destination->endpoint bindings */
+ HTABLE *endp_cache; /* endpoint->session bindings */
+ int sess_count; /* number of cached sessions */
+} SCACHE_MULTI;
+
+ /*
+ * Storage for a destination or endpoint list head. Each list head knows its
+ * own hash table entry name, so that we can remove the list when it becomes
+ * empty. List items are stored in a circular list under the list head.
+ */
+typedef struct {
+ RING ring[1]; /* circular list linkage */
+ char *parent_key; /* parent linkage: hash table */
+ SCACHE_MULTI *cache; /* parent linkage: cache */
+} SCACHE_MULTI_HEAD;
+
+#define RING_TO_MULTI_HEAD(p) RING_TO_APPL((p), SCACHE_MULTI_HEAD, ring)
+
+ /*
+ * Storage for a destination->endpoint binding. This is an element in a
+ * circular list, whose list head specifies the destination.
+ */
+typedef struct {
+ RING ring[1]; /* circular list linkage */
+ SCACHE_MULTI_HEAD *head; /* parent linkage: list head */
+ char *endp_label; /* endpoint name */
+ char *dest_prop; /* binding properties */
+} SCACHE_MULTI_DEST;
+
+#define RING_TO_MULTI_DEST(p) RING_TO_APPL((p), SCACHE_MULTI_DEST, ring)
+
+static void scache_multi_expire_dest(int, void *);
+
+ /*
+ * Storage for an endpoint->session binding. This is an element in a
+ * circular list, whose list head specifies the endpoint.
+ */
+typedef struct {
+ RING ring[1]; /* circular list linkage */
+ SCACHE_MULTI_HEAD *head; /* parent linkage: list head */
+ int fd; /* cached session */
+ char *endp_prop; /* binding properties */
+} SCACHE_MULTI_ENDP;
+
+#define RING_TO_MULTI_ENDP(p) RING_TO_APPL((p), SCACHE_MULTI_ENDP, ring)
+
+static void scache_multi_expire_endp(int, void *);
+
+ /*
+ * When deleting a circular list element, are we deleting the entire
+ * circular list, or are we removing a single list element. We need this
+ * distinction to avoid a re-entrancy problem between htable_delete() and
+ * htable_free().
+ */
+#define BOTTOM_UP 1 /* one item */
+#define TOP_DOWN 2 /* whole list */
+
+/* scache_multi_drop_endp - destroy endpoint->session binding */
+
+static void scache_multi_drop_endp(SCACHE_MULTI_ENDP *endp, int direction)
+{
+ const char *myname = "scache_multi_drop_endp";
+ SCACHE_MULTI_HEAD *head;
+
+ if (msg_verbose)
+ msg_info("%s: endp_prop=%s fd=%d", myname,
+ endp->endp_prop, endp->fd);
+
+ /*
+ * Stop the timer.
+ */
+ event_cancel_timer(scache_multi_expire_endp, (void *) endp);
+
+ /*
+ * In bottom-up mode, remove the list head from the endpoint hash when
+ * the list becomes empty. Otherwise, remove the endpoint->session
+ * binding from the list.
+ */
+ ring_detach(endp->ring);
+ head = endp->head;
+ head->cache->sess_count--;
+ if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
+ htable_delete(head->cache->endp_cache, head->parent_key, myfree);
+
+ /*
+ * Destroy the endpoint->session binding.
+ */
+ if (endp->fd >= 0 && close(endp->fd) != 0)
+ msg_warn("%s: close(%d): %m", myname, endp->fd);
+ myfree(endp->endp_prop);
+
+ myfree((void *) endp);
+}
+
+/* scache_multi_expire_endp - event timer call-back */
+
+static void scache_multi_expire_endp(int unused_event, void *context)
+{
+ SCACHE_MULTI_ENDP *endp = (SCACHE_MULTI_ENDP *) context;
+
+ scache_multi_drop_endp(endp, BOTTOM_UP);
+}
+
+/* scache_multi_free_endp - hash table destructor call-back */
+
+static void scache_multi_free_endp(void *ptr)
+{
+ SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
+ SCACHE_MULTI_ENDP *endp;
+ RING *ring;
+
+ /*
+ * Delete each endpoint->session binding in the list, then delete the
+ * list head. Note: this changes the list, so we must iterate carefully.
+ */
+ while ((ring = ring_succ(head->ring)) != head->ring) {
+ endp = RING_TO_MULTI_ENDP(ring);
+ scache_multi_drop_endp(endp, TOP_DOWN);
+ }
+ myfree((void *) head);
+}
+
+/* scache_multi_save_endp - save endpoint->session binding */
+
+static void scache_multi_save_endp(SCACHE *scache, int ttl,
+ const char *endp_label,
+ const char *endp_prop, int fd)
+{
+ const char *myname = "scache_multi_save_endp";
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+ SCACHE_MULTI_HEAD *head;
+ SCACHE_MULTI_ENDP *endp;
+
+ if (ttl < 0)
+ msg_panic("%s: bad ttl: %d", myname, ttl);
+
+ /*
+ * Look up or instantiate the list head with the endpoint name.
+ */
+ if ((head = (SCACHE_MULTI_HEAD *)
+ htable_find(sp->endp_cache, endp_label)) == 0) {
+ head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
+ ring_init(head->ring);
+ head->parent_key =
+ htable_enter(sp->endp_cache, endp_label, (void *) head)->key;
+ head->cache = sp;
+ }
+
+ /*
+ * Add the endpoint->session binding to the list. There can never be a
+ * duplicate, because each session must have a different file descriptor.
+ */
+ endp = (SCACHE_MULTI_ENDP *) mymalloc(sizeof(*endp));
+ endp->head = head;
+ endp->fd = fd;
+ endp->endp_prop = mystrdup(endp_prop);
+ ring_prepend(head->ring, endp->ring);
+ sp->sess_count++;
+
+ /*
+ * Make sure this binding will go away eventually.
+ */
+ event_request_timer(scache_multi_expire_endp, (void *) endp, ttl);
+
+ if (msg_verbose)
+ msg_info("%s: endp_label=%s -> endp_prop=%s fd=%d",
+ myname, endp_label, endp_prop, fd);
+}
+
+/* scache_multi_find_endp - look up session for named endpoint */
+
+static int scache_multi_find_endp(SCACHE *scache, const char *endp_label,
+ VSTRING *endp_prop)
+{
+ const char *myname = "scache_multi_find_endp";
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+ SCACHE_MULTI_HEAD *head;
+ SCACHE_MULTI_ENDP *endp;
+ RING *ring;
+ int fd;
+
+ /*
+ * Look up the list head with the endpoint name.
+ */
+ if ((head = (SCACHE_MULTI_HEAD *)
+ htable_find(sp->endp_cache, endp_label)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: no endpoint cache: endp_label=%s",
+ myname, endp_label);
+ return (-1);
+ }
+
+ /*
+ * Use the first available session. Remove the session from the cache
+ * because we're giving it to someone else.
+ */
+ if ((ring = ring_succ(head->ring)) != head->ring) {
+ endp = RING_TO_MULTI_ENDP(ring);
+ fd = endp->fd;
+ endp->fd = -1;
+ vstring_strcpy(endp_prop, endp->endp_prop);
+ if (msg_verbose)
+ msg_info("%s: found: endp_label=%s -> endp_prop=%s fd=%d",
+ myname, endp_label, endp->endp_prop, fd);
+ scache_multi_drop_endp(endp, BOTTOM_UP);
+ return (fd);
+ }
+ if (msg_verbose)
+ msg_info("%s: not found: endp_label=%s", myname, endp_label);
+ return (-1);
+}
+
+/* scache_multi_drop_dest - delete destination->endpoint binding */
+
+static void scache_multi_drop_dest(SCACHE_MULTI_DEST *dest, int direction)
+{
+ const char *myname = "scache_multi_drop_dest";
+ SCACHE_MULTI_HEAD *head;
+
+ if (msg_verbose)
+ msg_info("%s: dest_prop=%s endp_label=%s",
+ myname, dest->dest_prop, dest->endp_label);
+
+ /*
+ * Stop the timer.
+ */
+ event_cancel_timer(scache_multi_expire_dest, (void *) dest);
+
+ /*
+ * In bottom-up mode, remove the list head from the destination hash when
+ * the list becomes empty. Otherwise, remove the destination->endpoint
+ * binding from the list.
+ */
+ ring_detach(dest->ring);
+ head = dest->head;
+ if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
+ htable_delete(head->cache->dest_cache, head->parent_key, myfree);
+
+ /*
+ * Destroy the destination->endpoint binding.
+ */
+ myfree(dest->dest_prop);
+ myfree(dest->endp_label);
+
+ myfree((void *) dest);
+}
+
+/* scache_multi_expire_dest - event timer call-back */
+
+static void scache_multi_expire_dest(int unused_event, void *context)
+{
+ SCACHE_MULTI_DEST *dest = (SCACHE_MULTI_DEST *) context;
+
+ scache_multi_drop_dest(dest, BOTTOM_UP);
+}
+
+/* scache_multi_free_dest - hash table destructor call-back */
+
+static void scache_multi_free_dest(void *ptr)
+{
+ SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
+ SCACHE_MULTI_DEST *dest;
+ RING *ring;
+
+ /*
+ * Delete each destination->endpoint binding in the list, then delete the
+ * list head. Note: this changes the list, so we must iterate carefully.
+ */
+ while ((ring = ring_succ(head->ring)) != head->ring) {
+ dest = RING_TO_MULTI_DEST(ring);
+ scache_multi_drop_dest(dest, TOP_DOWN);
+ }
+ myfree((void *) head);
+}
+
+/* scache_multi_save_dest - save destination->endpoint binding */
+
+static void scache_multi_save_dest(SCACHE *scache, int ttl,
+ const char *dest_label,
+ const char *dest_prop,
+ const char *endp_label)
+{
+ const char *myname = "scache_multi_save_dest";
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+ SCACHE_MULTI_HEAD *head;
+ SCACHE_MULTI_DEST *dest;
+ RING *ring;
+ int refresh = 0;
+
+ if (ttl < 0)
+ msg_panic("%s: bad ttl: %d", myname, ttl);
+
+ /*
+ * Look up or instantiate the list head with the destination name.
+ */
+ if ((head = (SCACHE_MULTI_HEAD *)
+ htable_find(sp->dest_cache, dest_label)) == 0) {
+ head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
+ ring_init(head->ring);
+ head->parent_key =
+ htable_enter(sp->dest_cache, dest_label, (void *) head)->key;
+ head->cache = sp;
+ }
+
+ /*
+ * Look up or instantiate the destination->endpoint binding. Update the
+ * expiration time if this destination->endpoint binding already exists.
+ */
+ RING_FOREACH(ring, head->ring) {
+ dest = RING_TO_MULTI_DEST(ring);
+ if (strcmp(dest->endp_label, endp_label) == 0
+ && strcmp(dest->dest_prop, dest_prop) == 0) {
+ refresh = 1;
+ break;
+ }
+ }
+ if (refresh == 0) {
+ dest = (SCACHE_MULTI_DEST *) mymalloc(sizeof(*dest));
+ dest->head = head;
+ dest->endp_label = mystrdup(endp_label);
+ dest->dest_prop = mystrdup(dest_prop);
+ ring_prepend(head->ring, dest->ring);
+ }
+
+ /*
+ * Make sure this binding will go away eventually.
+ */
+ event_request_timer(scache_multi_expire_dest, (void *) dest, ttl);
+
+ if (msg_verbose)
+ msg_info("%s: dest_label=%s -> dest_prop=%s endp_label=%s%s",
+ myname, dest_label, dest_prop, endp_label,
+ refresh ? " (refreshed)" : "");
+}
+
+/* scache_multi_find_dest - look up session for named destination */
+
+static int scache_multi_find_dest(SCACHE *scache, const char *dest_label,
+ VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ const char *myname = "scache_multi_find_dest";
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+ SCACHE_MULTI_HEAD *head;
+ SCACHE_MULTI_DEST *dest;
+ RING *ring;
+ int fd;
+
+ /*
+ * Look up the list head with the destination name.
+ */
+ if ((head = (SCACHE_MULTI_HEAD *)
+ htable_find(sp->dest_cache, dest_label)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: no destination cache: dest_label=%s",
+ myname, dest_label);
+ return (-1);
+ }
+
+ /*
+ * Search endpoints for the first available session.
+ */
+ RING_FOREACH(ring, head->ring) {
+ dest = RING_TO_MULTI_DEST(ring);
+ fd = scache_multi_find_endp(scache, dest->endp_label, endp_prop);
+ if (fd >= 0) {
+ vstring_strcpy(dest_prop, dest->dest_prop);
+ return (fd);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: not found: dest_label=%s", myname, dest_label);
+ return (-1);
+}
+
+/* scache_multi_size - size of multi-element cache object */
+
+static void scache_multi_size(SCACHE *scache, SCACHE_SIZE *size)
+{
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+
+ size->dest_count = sp->dest_cache->used;
+ size->endp_count = sp->endp_cache->used;
+ size->sess_count = sp->sess_count;
+}
+
+/* scache_multi_free - destroy multi-element cache object */
+
+static void scache_multi_free(SCACHE *scache)
+{
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
+
+ htable_free(sp->dest_cache, scache_multi_free_dest);
+ htable_free(sp->endp_cache, scache_multi_free_endp);
+
+ myfree((void *) sp);
+}
+
+/* scache_multi_create - initialize */
+
+SCACHE *scache_multi_create(void)
+{
+ SCACHE_MULTI *sp = (SCACHE_MULTI *) mymalloc(sizeof(*sp));
+
+ sp->scache->save_endp = scache_multi_save_endp;
+ sp->scache->find_endp = scache_multi_find_endp;
+ sp->scache->save_dest = scache_multi_save_dest;
+ sp->scache->find_dest = scache_multi_find_dest;
+ sp->scache->size = scache_multi_size;
+ sp->scache->free = scache_multi_free;
+
+ sp->dest_cache = htable_create(1);
+ sp->endp_cache = htable_create(1);
+ sp->sess_count = 0;
+
+ return (sp->scache);
+}
diff --git a/src/global/scache_multi.in b/src/global/scache_multi.in
new file mode 100644
index 0000000..1a65650
--- /dev/null
+++ b/src/global/scache_multi.in
@@ -0,0 +1,52 @@
+# Initialize
+
+verbose 0
+cache_type multi
+
+# Destination name space collision test
+
+save_dest 2 a_dest a_prop b_endp
+sleep 1
+save_dest 2 a_dest a_prop b_endp
+sleep 1
+save_dest 2 a_dest a_prop b_endp
+sleep 2
+
+# Another destination name space collision test
+
+save_dest 2 a_dest a_prop b_endp
+sleep 1
+save_dest 2 a_dest a_prop2 b_endp
+sleep 1
+save_dest 2 a_dest a_prop2 b_endp2
+sleep 2
+
+# Endpoint name space collision test
+
+save_endp 2 b_endp b_prop 12
+save_endp 2 b_endp b_prop 13
+sleep 3
+
+# Combined destiation and endpoint collision test with lookup
+
+save_dest 2 a_dest a_prop b_endp
+save_dest 2 a_dest a_prop2 b_endp
+save_dest 2 a_dest a_prop2 b_endp2
+save_endp 2 b_endp b_prop 12
+save_endp 2 b_endp b_prop 13
+find_dest a_dest
+find_dest a_dest
+find_dest a_dest
+
+# Another combined destiation and endpoint collision test with lookup
+
+save_endp 2 b_endp2 b_prop 12
+save_endp 2 b_endp2 b_prop 13
+save_endp 2 b_endp2 b_prop 14
+find_dest a_dest
+find_dest a_dest
+find_dest a_dest
+find_dest a_dest
+
+# Let the exit handler clean up the destiation->endpoint bindings.
+
diff --git a/src/global/scache_multi.ref b/src/global/scache_multi.ref
new file mode 100644
index 0000000..972d45c
--- /dev/null
+++ b/src/global/scache_multi.ref
@@ -0,0 +1,52 @@
+>>> # Initialize
+>>>
+>>> verbose 0
+>>> cache_type multi
+>>>
+>>> # Destination name space collision test
+>>>
+>>> save_dest 2 a_dest a_prop b_endp
+>>> sleep 1
+>>> save_dest 2 a_dest a_prop b_endp
+>>> sleep 1
+>>> save_dest 2 a_dest a_prop b_endp
+>>> sleep 2
+>>>
+>>> # Another destination name space collision test
+>>>
+>>> save_dest 2 a_dest a_prop b_endp
+>>> sleep 1
+>>> save_dest 2 a_dest a_prop2 b_endp
+>>> sleep 1
+>>> save_dest 2 a_dest a_prop2 b_endp2
+>>> sleep 2
+>>>
+>>> # Endpoint name space collision test
+>>>
+>>> save_endp 2 b_endp b_prop 12
+>>> save_endp 2 b_endp b_prop 13
+>>> sleep 3
+>>>
+>>> # Combined destiation and endpoint collision test with lookup
+>>>
+>>> save_dest 2 a_dest a_prop b_endp
+>>> save_dest 2 a_dest a_prop2 b_endp
+>>> save_dest 2 a_dest a_prop2 b_endp2
+>>> save_endp 2 b_endp b_prop 12
+>>> save_endp 2 b_endp b_prop 13
+>>> find_dest a_dest
+>>> find_dest a_dest
+>>> find_dest a_dest
+>>>
+>>> # Another combined destiation and endpoint collision test with lookup
+>>>
+>>> save_endp 2 b_endp2 b_prop 12
+>>> save_endp 2 b_endp2 b_prop 13
+>>> save_endp 2 b_endp2 b_prop 14
+>>> find_dest a_dest
+>>> find_dest a_dest
+>>> find_dest a_dest
+>>> find_dest a_dest
+>>>
+>>> # Let the exit handler clean up the destiation->endpoint bindings.
+>>>
diff --git a/src/global/scache_single.c b/src/global/scache_single.c
new file mode 100644
index 0000000..2a3864b
--- /dev/null
+++ b/src/global/scache_single.c
@@ -0,0 +1,312 @@
+/*++
+/* NAME
+/* scache_single 3
+/* SUMMARY
+/* single-item session cache
+/* SYNOPSIS
+/* #include <scache.h>
+/* DESCRIPTION
+/* SCACHE *scache_single_create()
+/* DESCRIPTION
+/* This module implements an in-memory, single-session cache.
+/*
+/* scache_single_create() creates a session cache instance
+/* that stores a single session.
+/* DIAGNOSTICS
+/* Fatal error: memory allocation problem;
+/* panic: internal consistency failure.
+/* SEE ALSO
+/* scache(3), generic session cache API
+/* 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 <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <events.h>
+
+/*#define msg_verbose 1*/
+
+/* Global library. */
+
+#include <scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Data structure for one saved connection. It is left up to the application
+ * to serialize attributes upon passivation, and to de-serialize them upon
+ * re-activation.
+ */
+typedef struct {
+ VSTRING *endp_label; /* physical endpoint name */
+ VSTRING *endp_prop; /* endpoint properties, serialized */
+ int fd; /* the session */
+} SCACHE_SINGLE_ENDP;
+
+ /*
+ * Data structure for a logical name to physical endpoint binding. It is
+ * left up to the application to serialize attributes upon passivation, and
+ * to de-serialize then upon re-activation.
+ */
+typedef struct {
+ VSTRING *dest_label; /* logical destination name */
+ VSTRING *dest_prop; /* binding properties, serialized */
+ VSTRING *endp_label; /* physical endpoint name */
+} SCACHE_SINGLE_DEST;
+
+ /*
+ * SCACHE_SINGLE is a derived type from the SCACHE super-class.
+ */
+typedef struct {
+ SCACHE scache[1]; /* super-class */
+ SCACHE_SINGLE_ENDP endp; /* one cached session */
+ SCACHE_SINGLE_DEST dest; /* one cached binding */
+} SCACHE_SINGLE;
+
+static void scache_single_expire_endp(int, void *);
+static void scache_single_expire_dest(int, void *);
+
+#define SCACHE_SINGLE_ENDP_BUSY(sp) (VSTRING_LEN(sp->endp.endp_label) > 0)
+#define SCACHE_SINGLE_DEST_BUSY(sp) (VSTRING_LEN(sp->dest.dest_label) > 0)
+
+#define STR(x) vstring_str(x)
+
+/* scache_single_free_endp - discard endpoint */
+
+static void scache_single_free_endp(SCACHE_SINGLE *sp)
+{
+ const char *myname = "scache_single_free_endp";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, STR(sp->endp.endp_label));
+
+ event_cancel_timer(scache_single_expire_endp, (void *) sp);
+ if (sp->endp.fd >= 0 && close(sp->endp.fd) < 0)
+ msg_warn("close session endpoint %s: %m", STR(sp->endp.endp_label));
+ VSTRING_RESET(sp->endp.endp_label);
+ VSTRING_TERMINATE(sp->endp.endp_label);
+ VSTRING_RESET(sp->endp.endp_prop);
+ VSTRING_TERMINATE(sp->endp.endp_prop);
+ sp->endp.fd = -1;
+}
+
+/* scache_single_expire_endp - discard expired session */
+
+static void scache_single_expire_endp(int unused_event, void *context)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) context;
+
+ scache_single_free_endp(sp);
+}
+
+/* scache_single_save_endp - save endpoint */
+
+static void scache_single_save_endp(SCACHE *scache, int endp_ttl,
+ const char *endp_label,
+ const char *endp_prop, int fd)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+ const char *myname = "scache_single_save_endp";
+
+ if (endp_ttl <= 0)
+ msg_panic("%s: bad endp_ttl: %d", myname, endp_ttl);
+
+ if (SCACHE_SINGLE_ENDP_BUSY(sp))
+ scache_single_free_endp(sp); /* dump the cached fd */
+
+ vstring_strcpy(sp->endp.endp_label, endp_label);
+ vstring_strcpy(sp->endp.endp_prop, endp_prop);
+ sp->endp.fd = fd;
+ event_request_timer(scache_single_expire_endp, (void *) sp, endp_ttl);
+
+ if (msg_verbose)
+ msg_info("%s: %s fd=%d", myname, endp_label, fd);
+}
+
+/* scache_single_find_endp - look up cached session */
+
+static int scache_single_find_endp(SCACHE *scache, const char *endp_label,
+ VSTRING *endp_prop)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+ const char *myname = "scache_single_find_endp";
+ int fd;
+
+ if (!SCACHE_SINGLE_ENDP_BUSY(sp)) {
+ if (msg_verbose)
+ msg_info("%s: no endpoint cache: %s", myname, endp_label);
+ return (-1);
+ }
+ if (strcmp(STR(sp->endp.endp_label), endp_label) == 0) {
+ vstring_strcpy(endp_prop, STR(sp->endp.endp_prop));
+ fd = sp->endp.fd;
+ sp->endp.fd = -1;
+ scache_single_free_endp(sp);
+ if (msg_verbose)
+ msg_info("%s: found: %s fd=%d", myname, endp_label, fd);
+ return (fd);
+ }
+ if (msg_verbose)
+ msg_info("%s: not found: %s", myname, endp_label);
+ return (-1);
+}
+
+/* scache_single_free_dest - discard destination/endpoint association */
+
+static void scache_single_free_dest(SCACHE_SINGLE *sp)
+{
+ const char *myname = "scache_single_free_dest";
+
+ if (msg_verbose)
+ msg_info("%s: %s -> %s", myname, STR(sp->dest.dest_label),
+ STR(sp->dest.endp_label));
+
+ event_cancel_timer(scache_single_expire_dest, (void *) sp);
+ VSTRING_RESET(sp->dest.dest_label);
+ VSTRING_TERMINATE(sp->dest.dest_label);
+ VSTRING_RESET(sp->dest.dest_prop);
+ VSTRING_TERMINATE(sp->dest.dest_prop);
+ VSTRING_RESET(sp->dest.endp_label);
+ VSTRING_TERMINATE(sp->dest.endp_label);
+}
+
+/* scache_single_expire_dest - discard expired destination/endpoint binding */
+
+static void scache_single_expire_dest(int unused_event, void *context)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) context;
+
+ scache_single_free_dest(sp);
+}
+
+/* scache_single_save_dest - create destination/endpoint association */
+
+static void scache_single_save_dest(SCACHE *scache, int dest_ttl,
+ const char *dest_label,
+ const char *dest_prop,
+ const char *endp_label)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+ const char *myname = "scache_single_save_dest";
+ int refresh;
+
+ if (dest_ttl <= 0)
+ msg_panic("%s: bad dest_ttl: %d", myname, dest_ttl);
+
+ /*
+ * Optimize: reset timer only, if nothing has changed.
+ */
+ refresh =
+ (SCACHE_SINGLE_DEST_BUSY(sp)
+ && strcmp(STR(sp->dest.dest_label), dest_label) == 0
+ && strcmp(STR(sp->dest.dest_prop), dest_prop) == 0
+ && strcmp(STR(sp->dest.endp_label), endp_label) == 0);
+
+ if (refresh == 0) {
+ vstring_strcpy(sp->dest.dest_label, dest_label);
+ vstring_strcpy(sp->dest.dest_prop, dest_prop);
+ vstring_strcpy(sp->dest.endp_label, endp_label);
+ }
+ event_request_timer(scache_single_expire_dest, (void *) sp, dest_ttl);
+
+ if (msg_verbose)
+ msg_info("%s: %s -> %s%s", myname, dest_label, endp_label,
+ refresh ? " (refreshed)" : "");
+}
+
+/* scache_single_find_dest - look up cached session */
+
+static int scache_single_find_dest(SCACHE *scache, const char *dest_label,
+ VSTRING *dest_prop, VSTRING *endp_prop)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+ const char *myname = "scache_single_find_dest";
+ int fd;
+
+ if (!SCACHE_SINGLE_DEST_BUSY(sp)) {
+ if (msg_verbose)
+ msg_info("%s: no destination cache: %s", myname, dest_label);
+ return (-1);
+ }
+ if (strcmp(STR(sp->dest.dest_label), dest_label) == 0) {
+ if (msg_verbose)
+ msg_info("%s: found: %s", myname, dest_label);
+ if ((fd = scache_single_find_endp(scache, STR(sp->dest.endp_label), endp_prop)) >= 0) {
+ vstring_strcpy(dest_prop, STR(sp->dest.dest_prop));
+ return (fd);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: not found: %s", myname, dest_label);
+ return (-1);
+}
+
+/* scache_single_size - size of single-element cache :-) */
+
+static void scache_single_size(SCACHE *scache, SCACHE_SIZE *size)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+
+ size->dest_count = (!SCACHE_SINGLE_DEST_BUSY(sp) ? 0 : 1);
+ size->endp_count = (!SCACHE_SINGLE_ENDP_BUSY(sp) ? 0 : 1);
+ size->sess_count = (sp->endp.fd < 0 ? 0 : 1);
+}
+
+/* scache_single_free - destroy single-element cache object */
+
+static void scache_single_free(SCACHE *scache)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
+
+ vstring_free(sp->endp.endp_label);
+ vstring_free(sp->endp.endp_prop);
+ if (sp->endp.fd >= 0)
+ close(sp->endp.fd);
+
+ vstring_free(sp->dest.dest_label);
+ vstring_free(sp->dest.dest_prop);
+ vstring_free(sp->dest.endp_label);
+
+ myfree((void *) sp);
+}
+
+/* scache_single_create - initialize */
+
+SCACHE *scache_single_create(void)
+{
+ SCACHE_SINGLE *sp = (SCACHE_SINGLE *) mymalloc(sizeof(*sp));
+
+ sp->scache->save_endp = scache_single_save_endp;
+ sp->scache->find_endp = scache_single_find_endp;
+ sp->scache->save_dest = scache_single_save_dest;
+ sp->scache->find_dest = scache_single_find_dest;
+ sp->scache->size = scache_single_size;
+ sp->scache->free = scache_single_free;
+
+ sp->endp.endp_label = vstring_alloc(10);
+ sp->endp.endp_prop = vstring_alloc(10);
+ sp->endp.fd = -1;
+
+ sp->dest.dest_label = vstring_alloc(10);
+ sp->dest.dest_prop = vstring_alloc(10);
+ sp->dest.endp_label = vstring_alloc(10);
+
+ return (sp->scache);
+}
diff --git a/src/global/sent.c b/src/global/sent.c
new file mode 100644
index 0000000..c81cceb
--- /dev/null
+++ b/src/global/sent.c
@@ -0,0 +1,176 @@
+/*++
+/* NAME
+/* sent 3
+/* SUMMARY
+/* log that a message was or could be sent
+/* SYNOPSIS
+/* #include <sent.h>
+/*
+/* int sent(flags, queue_id, stats, recipient, relay, dsn)
+/* int flags;
+/* const char *queue_id;
+/* MSG_STATS *stats;
+/* RECIPIENT *recipient;
+/* const char *relay;
+/* DSN *dsn;
+/* DESCRIPTION
+/* sent() logs that a message was successfully delivered,
+/* updates the address verification service, or updates a
+/* sender-requested message delivery record. The
+/* flags argument determines the action.
+/*
+/* Arguments:
+/* .IP flags
+/* Zero or more of the following:
+/* .RS
+/* .IP SENT_FLAG_NONE
+/* The message is a normal delivery request.
+/* .IP DEL_REQ_FLAG_MTA_VRFY
+/* The message is an MTA-requested address verification probe.
+/* Update the address verification database.
+/* .IP DEL_REQ_FLAG_USR_VRFY
+/* The message is a user-requested address expansion probe.
+/* Update the message delivery record.
+/* .IP DEL_REQ_FLAG_RECORD
+/* This is a normal message with logged delivery. Update the
+/* the message delivery record.
+/* .RE
+/* .IP queue_id
+/* The message queue id.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP recipient
+/* Recipient information. See recipient_list(3).
+/* .IP relay
+/* Name of the host we're talking to.
+/* .IP dsn
+/* Delivery status. See dsn(3). The action is ignored in case
+/* of a probe message. Otherwise, "delivered" is assumed when
+/* no action is specified.
+/* DIAGNOSTICS
+/* A non-zero result means the operation failed.
+/*
+/* Fatal: out of memory.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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>
+
+/* Global library. */
+
+#define DSN_INTERN
+#include <mail_params.h>
+#include <verify.h>
+#include <log_adhoc.h>
+#include <trace.h>
+#include <defer.h>
+#include <sent.h>
+#include <dsn_util.h>
+#include <dsn_mask.h>
+
+/* Application-specific. */
+
+/* sent - log that a message was or could be sent */
+
+int sent(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *recipient, const char *relay,
+ DSN *dsn)
+{
+ DSN my_dsn = *dsn;
+ DSN *dsn_res;
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if (my_dsn.status[0] != '2' || !dsn_valid(my_dsn.status)) {
+ msg_warn("sent: ignoring dsn code \"%s\"", my_dsn.status);
+ my_dsn.status = "2.0.0";
+ }
+
+ /*
+ * DSN filter (Postfix 3.0).
+ */
+ if (delivery_status_filter != 0
+ && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0)
+ my_dsn = *dsn_res;
+
+ /*
+ * MTA-requested address verification information is stored in the verify
+ * service database.
+ */
+ if (flags & DEL_REQ_FLAG_MTA_VRFY) {
+ my_dsn.action = "deliverable";
+ status = verify_append(id, stats, recipient, relay, &my_dsn,
+ DEL_RCPT_STAT_OK);
+ return (status);
+ }
+
+ /*
+ * User-requested address verification information is logged and mailed
+ * to the requesting user.
+ */
+ if (flags & DEL_REQ_FLAG_USR_VRFY) {
+ my_dsn.action = "deliverable";
+ status = trace_append(flags, id, stats, recipient, relay, &my_dsn);
+ return (status);
+ }
+
+ /*
+ * Normal mail delivery. May also send a delivery record to the user.
+ */
+ else {
+
+ /* Readability macros: record all deliveries, or the delayed ones. */
+#define REC_ALL_SENT(flags) (flags & DEL_REQ_FLAG_RECORD)
+#define REC_DLY_SENT(flags, rcpt) \
+ ((flags & DEL_REQ_FLAG_REC_DLY_SENT) \
+ && (rcpt->dsn_notify == 0 || (rcpt->dsn_notify & DSN_NOTIFY_DELAY)))
+
+ if (my_dsn.action == 0 || my_dsn.action[0] == 0)
+ my_dsn.action = "delivered";
+
+ if (((REC_ALL_SENT(flags) == 0 && REC_DLY_SENT(flags, recipient) == 0)
+ || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0)
+ && ((recipient->dsn_notify & DSN_NOTIFY_SUCCESS) == 0
+ || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0)) {
+ log_adhoc(id, stats, recipient, relay, &my_dsn, "sent");
+ status = 0;
+ } else {
+ VSTRING *junk = vstring_alloc(100);
+
+ vstring_sprintf(junk, "%s: %s service failed",
+ id, var_trace_service);
+ my_dsn.reason = vstring_str(junk);
+ my_dsn.status = "4.3.0";
+ status = defer_append(flags, id, stats, recipient, relay, &my_dsn);
+ vstring_free(junk);
+ }
+ return (status);
+ }
+}
diff --git a/src/global/sent.h b/src/global/sent.h
new file mode 100644
index 0000000..eb9a23f
--- /dev/null
+++ b/src/global/sent.h
@@ -0,0 +1,45 @@
+#ifndef _SENT_H_INCLUDED_
+#define _SENT_H_INCLUDED_
+
+/*++
+/* NAME
+/* sent 3h
+/* SUMMARY
+/* log that message was sent
+/* SYNOPSIS
+/* #include <sent.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+#include <stdarg.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <bounce.h>
+
+ /*
+ * External interface.
+ */
+#define SENT_FLAG_NONE (0)
+
+extern int sent(int, const char *, MSG_STATS *, RECIPIENT *, const char *,
+ DSN *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/server_acl.c b/src/global/server_acl.c
new file mode 100644
index 0000000..5385a5a
--- /dev/null
+++ b/src/global/server_acl.c
@@ -0,0 +1,305 @@
+/*++
+/* NAME
+/* server_acl 3
+/* SUMMARY
+/* server access list
+/* SYNOPSIS
+/* #include <server_acl.h>
+/*
+/* void server_acl_pre_jail_init(mynetworks, param_name)
+/* const char *mynetworks;
+/* const char *param_name;
+/*
+/* SERVER_ACL *server_acl_parse(extern_acl, param_name)
+/* const char *extern_acl;
+/* const char *param_name;
+/*
+/* int server_acl_eval(client_addr, intern_acl, param_name)
+/* const char *client_addr;
+/* SERVER_ACL *intern_acl;
+/* const char *param_name;
+/* DESCRIPTION
+/* This module implements a permanent allow/denylist that
+/* is meant to be evaluated immediately after a client connects
+/* to a server.
+/*
+/* server_acl_pre_jail_init() does before-chroot initialization
+/* for the permit_mynetworks setting.
+/*
+/* server_acl_parse() converts an access list from raw string
+/* form to binary form. It should also be called as part of
+/* before-chroot initialization.
+/*
+/* server_acl_eval() evaluates an access list for the specified
+/* client address. The result is SERVER_ACL_ACT_PERMIT (permit),
+/* SERVER_ACL_ACT_REJECT (reject), SERVER_ACL_ACT_DUNNO (no
+/* decision), or SERVER_ACL_ACT_ERROR (error, unknown command
+/* or database access error).
+/*
+/* Arguments:
+/* .IP mynetworks
+/* Network addresses that match "permit_mynetworks".
+/* .IP param_name
+/* The configuration parameter name for the access list from
+/* main.cf. The information is used for error reporting (nested
+/* table, unknown keyword) and to select the appropriate
+/* behavior from parent_domain_matches_subdomains.
+/* .IP extern_acl
+/* External access list representation.
+/* .IP intern_acl
+/* Internal access list representation.
+/* .IP client_addr
+/* The client IP address as printable string (without []).
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <addr_match_list.h>
+#include <match_parent_style.h>
+#include <mynetworks.h>
+#include <server_acl.h>
+
+/* Application-specific. */
+
+static ADDR_MATCH_LIST *server_acl_mynetworks;
+static ADDR_MATCH_LIST *server_acl_mynetworks_host;
+
+#define STR vstring_str
+
+/* server_acl_pre_jail_init - initialize */
+
+void server_acl_pre_jail_init(const char *mynetworks, const char *origin)
+{
+ if (server_acl_mynetworks) {
+ addr_match_list_free(server_acl_mynetworks);
+ if (server_acl_mynetworks_host)
+ addr_match_list_free(server_acl_mynetworks_host);
+ }
+ server_acl_mynetworks =
+ addr_match_list_init(origin, MATCH_FLAG_RETURN
+ | match_parent_style(origin), mynetworks);
+ if (warn_compat_break_mynetworks_style)
+ server_acl_mynetworks_host =
+ addr_match_list_init(origin, MATCH_FLAG_RETURN
+ | match_parent_style(origin), mynetworks_host());
+}
+
+/* server_acl_parse - parse access list */
+
+SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
+{
+ char *saved_acl = mystrdup(extern_acl);
+ SERVER_ACL *intern_acl = argv_alloc(1);
+ char *bp = saved_acl;
+ char *acl;
+
+#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
+#define STRNE(x,y) (strcasecmp((x), (y)) != 0)
+
+ /*
+ * Nested tables are not allowed. Tables are opened before entering the
+ * chroot jail, while access lists are evaluated after entering the
+ * chroot jail.
+ */
+ while ((acl = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (strchr(acl, ':') != 0) {
+ if (strchr(origin, ':') != 0) {
+ msg_warn("table %s: lookup result \"%s\" is not allowed"
+ " -- ignoring remainder of access list",
+ origin, acl);
+ argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
+ break;
+ } else {
+ if (dict_handle(acl) == 0)
+ dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST));
+ }
+ }
+ argv_add(intern_acl, acl, (char *) 0);
+ }
+ argv_terminate(intern_acl);
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_acl);
+ return (intern_acl);
+}
+
+/* server_acl_eval - evaluate access list */
+
+int server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
+ const char *origin)
+{
+ const char *myname = "server_acl_eval";
+ char **cpp;
+ DICT *dict;
+ SERVER_ACL *argv;
+ const char *acl;
+ const char *dict_val;
+ int ret;
+
+ for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("source=%s address=%s acl=%s",
+ origin, client_addr, acl);
+ if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
+ return (SERVER_ACL_ACT_REJECT);
+ } else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
+ return (SERVER_ACL_ACT_PERMIT);
+ } else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
+ if (addr_match_list_match(server_acl_mynetworks, client_addr)) {
+ if (warn_compat_break_mynetworks_style
+ && !addr_match_list_match(server_acl_mynetworks_host,
+ client_addr))
+ msg_info("using backwards-compatible default setting "
+ VAR_MYNETWORKS_STYLE "=%s to permit "
+ "request from client \"%s\"",
+ var_mynetworks_style, client_addr);
+ return (SERVER_ACL_ACT_PERMIT);
+ }
+ if (server_acl_mynetworks->error != 0) {
+ msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
+ "remainder of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ } else if (strchr(acl, ':') != 0) {
+ if ((dict = dict_handle(acl)) == 0)
+ msg_panic("%s: unexpected dictionary: %s", myname, acl);
+ if ((dict_val = dict_get(dict, client_addr)) != 0) {
+ /* Fake up an ARGV to avoid lots of mallocs and frees. */
+ if (dict_val[strcspn(dict_val, ":" CHARS_COMMA_SP)] == 0) {
+ ARGV_FAKE_BEGIN(fake_argv, dict_val);
+ ret = server_acl_eval(client_addr, &fake_argv, acl);
+ ARGV_FAKE_END;
+ } else {
+ argv = server_acl_parse(dict_val, acl);
+ ret = server_acl_eval(client_addr, argv, acl);
+ argv_free(argv);
+ }
+ if (ret != SERVER_ACL_ACT_DUNNO)
+ return (ret);
+ } else if (dict->error != 0) {
+ msg_warn("%s: %s: table lookup error -- ignoring the remainder "
+ "of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ } else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
+ return (SERVER_ACL_ACT_DUNNO);
+ } else {
+ msg_warn("%s: unknown command: %s -- ignoring the remainder "
+ "of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ }
+ if (msg_verbose)
+ msg_info("source=%s address=%s - no match",
+ origin, client_addr);
+ return (SERVER_ACL_ACT_DUNNO);
+}
+
+ /*
+ * Access lists need testing. Not only with good inputs; error cases must
+ * also be handled appropriately.
+ */
+#ifdef TEST
+#include <unistd.h>
+#include <stdlib.h>
+#include <vstring_vstream.h>
+#include <name_code.h>
+#include <split_at.h>
+
+char *var_server_acl = "";
+
+#define UPDATE_VAR(s,v) do { if (*(s)) myfree(s); (s) = mystrdup(v); } while (0)
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ SERVER_ACL *argv;
+ int ret;
+ int have_tty = isatty(0);
+ char *bufp;
+ char *cmd;
+ char *value;
+ const NAME_CODE acl_map[] = {
+ SERVER_ACL_NAME_ERROR, SERVER_ACL_ACT_ERROR,
+ SERVER_ACL_NAME_PERMIT, SERVER_ACL_ACT_PERMIT,
+ SERVER_ACL_NAME_REJECT, SERVER_ACL_ACT_REJECT,
+ SERVER_ACL_NAME_DUNNO, SERVER_ACL_ACT_DUNNO,
+ 0,
+ };
+
+ /*
+ * No static initializer because these are owned by a library.
+ */
+ var_par_dom_match = DEF_PAR_DOM_MATCH;
+ var_mynetworks = "";
+
+#define VAR_SERVER_ACL "server_acl"
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ bufp = STR(buf);
+ if (have_tty == 0) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((cmd = mystrtok(&bufp, " =")) == 0 || STREQ(cmd, "?")) {
+ vstream_printf("usage: %s=value|%s=value|address=value\n",
+ VAR_MYNETWORKS, VAR_SERVER_ACL);
+ } else if ((value = mystrtok(&bufp, " =")) == 0) {
+ vstream_printf("missing value\n");
+ } else if (STREQ(cmd, VAR_MYNETWORKS)) {
+ UPDATE_VAR(var_mynetworks, value);
+ } else if (STREQ(cmd, VAR_SERVER_ACL)) {
+ UPDATE_VAR(var_server_acl, value);
+ } else if (STREQ(cmd, "address")) {
+ server_acl_pre_jail_init(var_mynetworks, VAR_MYNETWORKS);
+ argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL);
+ ret = server_acl_eval(value, argv, VAR_SERVER_ACL);
+ argv_free(argv);
+ vstream_printf("%s: %s\n", value, str_name_code(acl_map, ret));
+ } else {
+ vstream_printf("unknown command: \"%s\"\n", cmd);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/server_acl.h b/src/global/server_acl.h
new file mode 100644
index 0000000..4951f8f
--- /dev/null
+++ b/src/global/server_acl.h
@@ -0,0 +1,49 @@
+#ifndef _SERVER_ACL_INCLUDED_
+#define _SERVER_ACL_INCLUDED_
+
+/*++
+/* NAME
+/* dict_memcache 3h
+/* SUMMARY
+/* dictionary interface to memcache databases
+/* SYNOPSIS
+/* #include <dict_memcache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+
+ /*
+ * External interface.
+ */
+typedef ARGV SERVER_ACL;
+extern void server_acl_pre_jail_init(const char *, const char *);
+extern SERVER_ACL *server_acl_parse(const char *, const char *);
+extern int server_acl_eval(const char *, SERVER_ACL *, const char *);
+
+#define SERVER_ACL_NAME_WL_MYNETWORKS "permit_mynetworks"
+#define SERVER_ACL_NAME_PERMIT "permit"
+#define SERVER_ACL_NAME_DUNNO "dunno"
+#define SERVER_ACL_NAME_REJECT "reject"
+#define SERVER_ACL_NAME_ERROR "error"
+
+#define SERVER_ACL_ACT_PERMIT 1
+#define SERVER_ACL_ACT_DUNNO 0
+#define SERVER_ACL_ACT_REJECT (-1)
+#define SERVER_ACL_ACT_ERROR (-2)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/server_acl.in b/src/global/server_acl.in
new file mode 100644
index 0000000..c26251a
--- /dev/null
+++ b/src/global/server_acl.in
@@ -0,0 +1,10 @@
+mynetworks=168.100.3.0/27
+server_acl=permit_mynetworks,reject
+address=168.100.3.2
+mynetworks=!168.100.3.2,168.100.3.0/27
+address=168.100.3.2
+address=168.100.3.3
+mynetworks=fail:1
+address=168.100.3.4
+server_acl=fail:1,reject
+address=168.100.3.2
diff --git a/src/global/server_acl.ref b/src/global/server_acl.ref
new file mode 100644
index 0000000..d3e9363
--- /dev/null
+++ b/src/global/server_acl.ref
@@ -0,0 +1,18 @@
+> mynetworks=168.100.3.0/27
+> server_acl=permit_mynetworks,reject
+> address=168.100.3.2
+168.100.3.2: permit
+> mynetworks=!168.100.3.2,168.100.3.0/27
+> address=168.100.3.2
+168.100.3.2: reject
+> address=168.100.3.3
+168.100.3.3: permit
+> mynetworks=fail:1
+> address=168.100.3.4
+unknown: warning: mynetworks: fail:1: table lookup problem
+unknown: warning: server_acl: permit_mynetworks: mynetworks lookup error -- ignoring the remainder of this access list
+168.100.3.4: error
+> server_acl=fail:1,reject
+> address=168.100.3.2
+unknown: warning: server_acl: fail:1: table lookup error -- ignoring the remainder of this access list
+168.100.3.2: error
diff --git a/src/global/smtp_reply_footer.c b/src/global/smtp_reply_footer.c
new file mode 100644
index 0000000..6e5bb75
--- /dev/null
+++ b/src/global/smtp_reply_footer.c
@@ -0,0 +1,288 @@
+/*++
+/* NAME
+/* smtp_reply_footer 3
+/* SUMMARY
+/* SMTP reply footer text support
+/* SYNOPSIS
+/* #include <smtp_reply_footer.h>
+/*
+/* int smtp_reply_footer(buffer, start, template, filter,
+/* lookup, context)
+/* VSTRING *buffer;
+/* ssize_t start;
+/* const char *template;
+/* const char *filter;
+/* const char *(*lookup) (const char *name, void *context);
+/* void *context;
+/* DESCRIPTION
+/* smtp_reply_footer() expands a reply template, and appends
+/* the result to an existing reply text.
+/*
+/* Arguments:
+/* .IP buffer
+/* Result buffer. This should contain a properly formatted
+/* one-line or multi-line SMTP reply, with or without the final
+/* <CR><LF>. The reply code and optional enhanced status code
+/* will be replicated in the footer text. One space character
+/* after the SMTP reply code is replaced by '-'. If the existing
+/* reply ends in <CR><LF>, the result text will also end in
+/* <CR><LF>.
+/* .IP start
+/* The beginning of the SMTP reply that the footer will be
+/* appended to. This supports applications that buffer up
+/* multiple responses in one buffer.
+/* .IP template
+/* Template text, with optional $name attributes that will be
+/* expanded. The two-character sequence "\n" is replaced by a
+/* line break followed by a copy of the original SMTP reply
+/* code and optional enhanced status code.
+/* The two-character sequence "\c" at the start of the template
+/* suppresses the line break between the reply text and the
+/* template text.
+/* .IP filter
+/* The set of characters that are allowed in attribute expansion.
+/* .IP lookup
+/* Attribute name/value lookup function. The result value must
+/* be a null for a name that is not found, otherwise a pointer
+/* to null-terminated string.
+/* .IP context
+/* Call-back context for the lookup function.
+/* SEE ALSO
+/* mac_expand(3) macro expansion
+/* DIAGNOSTICS
+/* smtp_reply_footer() returns 0 upon success, -1 if the existing
+/* reply text is malformed, -2 in the case of a template macro
+/* parsing error (an undefined macro value is not an error).
+/*
+/* Fatal errors: memory allocation problem.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <dsn_util.h>
+#include <smtp_reply_footer.h>
+
+/* SLMs. */
+
+#define STR vstring_str
+
+int smtp_reply_footer(VSTRING *buffer, ssize_t start,
+ const char *template,
+ const char *filter,
+ MAC_EXP_LOOKUP_FN lookup,
+ void *context)
+{
+ const char *myname = "smtp_reply_footer";
+ char *cp;
+ char *next;
+ char *end;
+ ssize_t dsn_len; /* last status code length */
+ ssize_t dsn_offs = -1; /* last status code offset */
+ int crlf_at_end = 0;
+ ssize_t reply_code_offs = -1; /* last SMTP reply code offset */
+ ssize_t reply_patch_undo_len; /* length without final CRLF */
+ int mac_expand_error = 0;
+ int line_added;
+ char *saved_template;
+
+ /*
+ * Sanity check.
+ */
+ if (start < 0 || start > VSTRING_LEN(buffer))
+ msg_panic("%s: bad start: %ld", myname, (long) start);
+ if (*template == 0)
+ msg_panic("%s: empty template", myname);
+
+ /*
+ * Scan the original response without making changes. If the response is
+ * not what we expect, report an error. Otherwise, remember the offset of
+ * the last SMTP reply code.
+ */
+ for (cp = STR(buffer) + start, end = cp + strlen(cp);;) {
+ if (!ISDIGIT(cp[0]) || !ISDIGIT(cp[1]) || !ISDIGIT(cp[2])
+ || (cp[3] != ' ' && cp[3] != '-'))
+ return (-1);
+ reply_code_offs = cp - STR(buffer);
+ if ((next = strstr(cp, "\r\n")) == 0) {
+ next = end;
+ break;
+ }
+ cp = next + 2;
+ if (cp == end) {
+ crlf_at_end = 1;
+ break;
+ }
+ }
+ if (reply_code_offs < 0)
+ return (-1);
+
+ /*
+ * Truncate text after the first null, and truncate the trailing CRLF.
+ */
+ if (next < vstring_end(buffer))
+ vstring_truncate(buffer, next - STR(buffer));
+ reply_patch_undo_len = VSTRING_LEN(buffer);
+
+ /*
+ * Append the footer text one line at a time. Caution: before we append
+ * parts from the buffer to itself, we must extend the buffer first,
+ * otherwise we would have a dangling pointer "read" bug.
+ *
+ * XXX mac_expand() has no template length argument, so we must
+ * null-terminate the template in the middle.
+ */
+ dsn_offs = reply_code_offs + 4;
+ dsn_len = dsn_valid(STR(buffer) + dsn_offs);
+ line_added = 0;
+ saved_template = mystrdup(template);
+ for (cp = saved_template, end = cp + strlen(cp);;) {
+ if ((next = strstr(cp, "\\n")) != 0) {
+ *next = 0;
+ } else {
+ next = end;
+ }
+ if (cp == saved_template && strncmp(cp, "\\c", 2) == 0) {
+ /* Handle \c at start of template. */
+ cp += 2;
+ } else {
+ /* Append a clone of the SMTP reply code. */
+ vstring_strcat(buffer, "\r\n");
+ VSTRING_SPACE(buffer, 3);
+ vstring_strncat(buffer, STR(buffer) + reply_code_offs, 3);
+ vstring_strcat(buffer, next != end ? "-" : " ");
+ /* Append a clone of the optional enhanced status code. */
+ if (dsn_len > 0) {
+ VSTRING_SPACE(buffer, dsn_len);
+ vstring_strncat(buffer, STR(buffer) + dsn_offs, dsn_len);
+ vstring_strcat(buffer, " ");
+ }
+ line_added = 1;
+ }
+ /* Append one line of footer text. */
+ mac_expand_error = (mac_expand(buffer, cp, MAC_EXP_FLAG_APPEND, filter,
+ lookup, context) & MAC_PARSE_ERROR);
+ if (mac_expand_error)
+ break;
+ if (next < end) {
+ cp = next + 2;
+ } else
+ break;
+ }
+ myfree(saved_template);
+ /* Discard appended text after error, or finalize the result. */
+ if (mac_expand_error) {
+ vstring_truncate(buffer, reply_patch_undo_len);
+ VSTRING_TERMINATE(buffer);
+ } else if (line_added > 0) {
+ STR(buffer)[reply_code_offs + 3] = '-';
+ }
+ /* Restore CRLF at end. */
+ if (crlf_at_end)
+ vstring_strcat(buffer, "\r\n");
+ return (mac_expand_error ? -2 : 0);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+struct test_case {
+ const char *title;
+ const char *orig_reply;
+ const char *template;
+ const char *filter;
+ int expected_status;
+ const char *expected_reply;
+};
+
+#define NO_FILTER ((char *) 0)
+#define NO_TEMPLATE "NO_TEMPLATE"
+#define NO_ERROR (0)
+#define BAD_SMTP (-1)
+#define BAD_MACRO (-2)
+
+static const struct test_case test_cases[] = {
+ {"missing reply", "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+ {"long smtp_code", "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+ {"short smtp_code", "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+ {"good+bad smtp_code", "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+ {"1-line no dsn", "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"},
+ {"1-line no dsn", "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"},
+ {"2-line no dsn", "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"},
+ {"1-line with dsn", "550 5.1.1 Foo", "Bar", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n550 5.1.1 Bar"},
+ {"2-line with dsn", "550-5.1.1 Foo\r\n450 4.1.1 Bar", "Baz", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n450-4.1.1 Bar\r\n450 4.1.1 Baz"},
+ {"bad macro", "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
+ {"bad macroCRLF", "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
+ {"good macro", "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"},
+ {"good macroCRLF", "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"},
+ 0,
+};
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ return "DUMMY";
+}
+
+int main(int argc, char **argv)
+{
+ const struct test_case *tp;
+ int status;
+ VSTRING *buf = vstring_alloc(10);
+ void *context = 0;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ for (tp = test_cases; tp->title != 0; tp++) {
+ vstring_strcpy(buf, tp->orig_reply);
+ status = smtp_reply_footer(buf, 0, tp->template, tp->filter,
+ lookup, context);
+ if (status != tp->expected_status) {
+ msg_warn("test \"%s\": status %d, expected %d",
+ tp->title, status, tp->expected_status);
+ } else if (status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) {
+ msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
+ tp->title, STR(buf), tp->orig_reply);
+ } else if (status == 0 && strcmp(STR(buf), tp->expected_reply) != 0) {
+ msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
+ tp->title, STR(buf), tp->expected_reply);
+ } else {
+ msg_info("test \"%s\": pass", tp->title);
+ }
+ }
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/smtp_reply_footer.h b/src/global/smtp_reply_footer.h
new file mode 100644
index 0000000..ab053a5
--- /dev/null
+++ b/src/global/smtp_reply_footer.h
@@ -0,0 +1,42 @@
+#ifndef _SMTP_REPLY_FOOTER_H_INCLUDED_
+#define _SMTP_REPLY_FOOTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* smtp_reply_footer 3h
+/* SUMMARY
+/* SMTP reply footer text support
+/* SYNOPSIS
+/* #include <smtp_reply_footer.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <mac_expand.h>
+
+ /*
+ * External interface.
+ */
+extern int smtp_reply_footer(VSTRING *, ssize_t, const char *, const char *,
+ MAC_EXP_LOOKUP_FN, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/smtp_reply_footer.ref b/src/global/smtp_reply_footer.ref
new file mode 100644
index 0000000..d7eb5a7
--- /dev/null
+++ b/src/global/smtp_reply_footer.ref
@@ -0,0 +1,15 @@
+./smtp_reply_footer: test "missing reply": pass
+./smtp_reply_footer: test "long smtp_code": pass
+./smtp_reply_footer: test "short smtp_code": pass
+./smtp_reply_footer: test "good+bad smtp_code": pass
+./smtp_reply_footer: test "1-line no dsn": pass
+./smtp_reply_footer: test "1-line no dsn": pass
+./smtp_reply_footer: test "2-line no dsn": pass
+./smtp_reply_footer: test "1-line with dsn": pass
+./smtp_reply_footer: test "2-line with dsn": pass
+./smtp_reply_footer: warning: truncated macro reference: " ${whatever"
+./smtp_reply_footer: test "bad macro": pass
+./smtp_reply_footer: warning: truncated macro reference: " ${whatever"
+./smtp_reply_footer: test "bad macroCRLF": pass
+./smtp_reply_footer: test "good macro": pass
+./smtp_reply_footer: test "good macroCRLF": pass
diff --git a/src/global/smtp_stream.c b/src/global/smtp_stream.c
new file mode 100644
index 0000000..6629508
--- /dev/null
+++ b/src/global/smtp_stream.c
@@ -0,0 +1,562 @@
+/*++
+/* NAME
+/* smtp_stream 3
+/* SUMMARY
+/* smtp stream I/O support
+/* SYNOPSIS
+/* #include <smtp_stream.h>
+/*
+/* void smtp_stream_setup(stream, timeout, enable_deadline,
+/* min_data_rate)
+/* VSTREAM *stream;
+/* int timeout;
+/* int enable_deadline;
+/* int min_data_rate;
+/*
+/* void smtp_printf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* void smtp_flush(stream)
+/* VSTREAM *stream;
+/*
+/* int smtp_fgetc(stream)
+/* VSTREAM *stream;
+/*
+/* int smtp_get(vp, stream, maxlen, flags)
+/* VSTRING *vp;
+/* VSTREAM *stream;
+/* ssize_t maxlen;
+/* int flags;
+/*
+/* void smtp_fputs(str, len, stream)
+/* const char *str;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fwrite(str, len, stream)
+/* const char *str;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fread_buf(vp, len, stream)
+/* VSTRING *vp;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fputc(ch, stream)
+/* int ch;
+/* VSTREAM *stream;
+/*
+/* void smtp_vprintf(stream, format, ap)
+/* VSTREAM *stream;
+/* char *format;
+/* va_list ap;
+/*
+/* int smtp_detect_bare_lf;
+/* int smtp_got_bare_lf;
+/* AUXILIARY API
+/* int smtp_get_noexcept(vp, stream, maxlen, flags)
+/* VSTRING *vp;
+/* VSTREAM *stream;
+/* ssize_t maxlen;
+/* int flags;
+/* LEGACY API
+/* void smtp_timeout_setup(stream, timeout)
+/* VSTREAM *stream;
+/* int timeout;
+/* DESCRIPTION
+/* This module reads and writes text records delimited by CR LF,
+/* with error detection: timeouts or unexpected end-of-file.
+/* A trailing CR LF is added upon writing and removed upon reading.
+/*
+/* smtp_stream_setup() prepares the specified stream for SMTP read
+/* and write operations described below.
+/* This routine alters the behavior of streams as follows:
+/* .IP \(bu
+/* When enable_deadline is non-zero, then the timeout argument
+/* specifies a deadline for the total amount time that may be
+/* spent in all subsequent read/write operations.
+/* Otherwise, the stream is configured to enforce
+/* a time limit for each individual read/write system call.
+/* .IP \f(bu
+/* Additionally, when min_data_rate is > 0, the deadline is
+/* incremented by 1/min_data_rate seconds for every min_data_rate
+/* bytes transferred. However, the deadline will never exceed
+/* the value specified with the timeout argument.
+/* .IP \f(bu
+/* The stream is configured to use double buffering.
+/* .IP \f(bu
+/* The stream is configured to enable exception handling.
+/* .PP
+/* smtp_printf() formats its arguments and writes the result to
+/* the named stream, followed by a CR LF pair. The stream is NOT flushed.
+/* Long lines of text are not broken.
+/*
+/* smtp_flush() flushes the named stream.
+/*
+/* smtp_fgetc() reads one character from the named stream.
+/*
+/* smtp_get() reads the named stream up to and including
+/* the next LF character and strips the trailing CR LF. The
+/* \fImaxlen\fR argument limits the length of a line of text,
+/* and protects the program against running out of memory.
+/* Specify a zero bound to turn off bounds checking.
+/* The result is the last character read, or VSTREAM_EOF.
+/* The \fIflags\fR argument is zero or more of:
+/* .RS
+/* .IP SMTP_GET_FLAG_SKIP
+/* Skip over input in excess of \fImaxlen\fR). Either way, a result
+/* value of '\n' means that the input did not exceed \fImaxlen\fR.
+/* .IP SMTP_GET_FLAG_APPEND
+/* Append content to the buffer instead of overwriting it.
+/* .RE
+/* Specify SMTP_GET_FLAG_NONE for no special processing.
+/*
+/* smtp_fputs() writes its string argument to the named stream.
+/* Long strings are not broken. Each string is followed by a
+/* CR LF pair. The stream is not flushed.
+/*
+/* smtp_fwrite() writes its string argument to the named stream.
+/* Long strings are not broken. No CR LF is appended. The stream
+/* is not flushed.
+/*
+/* smtp_fread_buf() invokes vstream_fread_buf() to read the
+/* specified number of unformatted bytes from the stream. The
+/* result is not null-terminated. NOTE: do not skip calling
+/* smtp_fread_buf() when len == 0. This function has side
+/* effects including resetting the buffer write position, and
+/* skipping the call would invalidate the buffer state.
+/*
+/* smtp_fputc() writes one character to the named stream.
+/* The stream is not flushed.
+/*
+/* smtp_vprintf() is the machine underneath smtp_printf().
+/*
+/* smtp_get_noexcept() implements the subset of smtp_get()
+/* without timeouts and without making long jumps. Instead,
+/* query the stream status with vstream_feof() etc.
+/*
+/* This function assigns smtp_got_bare_lf = smtp_detect_bare_lf,
+/* if smtp_detect_bare_lf is non-zero and the last read line
+/* was terminated with a bare newline. Otherwise, this function
+/* sets smtp_got_bare_lf to zero.
+/*
+/* smtp_timeout_setup() is a backwards-compatibility interface
+/* for programs that don't require deadline or data-rate support.
+/* DIAGNOSTICS
+/* .fi
+/* .ad
+/* In case of error, a vstream_longjmp() call is performed to the
+/* context specified with vstream_setjmp().
+/* After write error, further writes to the socket are disabled.
+/* This eliminates the need for clumsy code to avoid unwanted
+/* I/O while shutting down a TLS engine or closing a VSTREAM.
+/* Error codes passed along with vstream_longjmp() are:
+/* .IP SMTP_ERR_EOF
+/* An I/O error happened, or the peer has disconnected unexpectedly.
+/* .IP SMTP_ERR_TIME
+/* The time limit specified to smtp_stream_setup() was exceeded.
+/* .PP
+/* Additional error codes that may be used by applications:
+/* .IP SMTP_ERR_QUIET
+/* Perform silent cleanup; the error was already reported by
+/* the application.
+/* This error is never generated by the smtp_stream(3) module, but
+/* is defined for application-specific use.
+/* .IP SMTP_ERR_DATA
+/* Application data error - the program cannot proceed with this
+/* SMTP session.
+/* .IP SMTP_ERR_NONE
+/* A non-error code that makes setjmp()/longjmp() convenient
+/* to use.
+/* BUGS
+/* The timeout deadline affects all I/O on the named stream, not
+/* just the I/O done on behalf of this module.
+/*
+/* The timeout deadline overwrites any previously set up state on
+/* the named stream.
+/* 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>
+#include <sys/time.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h> /* FD_ZERO() needs bzero() prototype */
+#include <errno.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+#include "smtp_stream.h"
+
+ /*
+ * Important: the time limit feature must not introduce any system calls
+ * when the input is already in the buffer, or when the output still fits in
+ * the buffer. Such system calls would really hurt when receiving or sending
+ * body content one line at a time.
+ */
+int smtp_detect_bare_lf;
+int smtp_got_bare_lf;
+
+/* smtp_timeout_reset - reset per-stream error flags */
+
+static void smtp_timeout_reset(VSTREAM *stream)
+{
+
+ /*
+ * Individual smtp_stream(3) I/O functions must not recharge the deadline
+ * timer, because multiline responses involve multiple smtp_stream(3)
+ * calls, and we really want to limit the time to send or receive a
+ * response.
+ */
+ vstream_clearerr(stream);
+}
+
+/* smtp_longjmp - raise an exception */
+
+static NORETURN smtp_longjmp(VSTREAM *stream, int err, const char *context)
+{
+
+ /*
+ * If we failed to write, don't bang our head against the wall another
+ * time when closing the stream. In the case of SMTP over TLS, poisoning
+ * the socket with shutdown() is more robust than purging the VSTREAM
+ * buffer or replacing the write function pointer with dummy_write().
+ */
+ if (msg_verbose)
+ msg_info("%s: %s", context, err == SMTP_ERR_TIME ? "timeout" : "EOF");
+ if (vstream_wr_error(stream))
+ /* Don't report ECONNRESET (hangup), EINVAL (already shut down), etc. */
+ (void) shutdown(vstream_fileno(stream), SHUT_WR);
+ vstream_longjmp(stream, err);
+}
+
+/* smtp_stream_setup - configure timeout trap */
+
+void smtp_stream_setup(VSTREAM *stream, int maxtime, int enable_deadline,
+ int min_data_rate)
+{
+ const char *myname = "smtp_stream_setup";
+
+ if (msg_verbose)
+ msg_info("%s: maxtime=%d enable_deadline=%d min_data_rate=%d",
+ myname, maxtime, enable_deadline, min_data_rate);
+
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(maxtime),
+ enable_deadline ? CA_VSTREAM_CTL_START_DEADLINE
+ : CA_VSTREAM_CTL_STOP_DEADLINE,
+ CA_VSTREAM_CTL_MIN_DATA_RATE(min_data_rate),
+ CA_VSTREAM_CTL_EXCEPT,
+ CA_VSTREAM_CTL_END);
+}
+
+/* smtp_flush - flush stream */
+
+void smtp_flush(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = vstream_fflush(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_flush");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_flush");
+}
+
+/* smtp_vprintf - write one line to SMTP peer */
+
+void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
+{
+ int err;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ vstream_vfprintf(stream, fmt, ap);
+ vstream_fputs("\r\n", stream);
+ err = vstream_ferror(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_vprintf");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_vprintf");
+}
+
+/* smtp_printf - write one line to SMTP peer */
+
+void smtp_printf(VSTREAM *stream, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ smtp_vprintf(stream, fmt, ap);
+ va_end(ap);
+}
+
+/* smtp_fgetc - read one character from SMTP peer */
+
+int smtp_fgetc(VSTREAM *stream)
+{
+ int ch;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ ch = VSTREAM_GETC(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fgetc");
+ if (vstream_feof(stream) || vstream_ferror(stream))
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fgetc");
+ return (ch);
+}
+
+/* smtp_get - read one line from SMTP peer */
+
+int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
+{
+ int last_char;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ last_char = smtp_get_noexcept(vp, stream, bound, flags);
+
+ /*
+ * EOF is bad, whether or not it happens in the middle of a record. Don't
+ * allow data that was truncated because of EOF.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get");
+ if (vstream_feof(stream) || vstream_ferror(stream))
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get");
+ return (last_char);
+}
+
+/* smtp_get_noexcept - read one line from SMTP peer, without exceptions */
+
+int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
+{
+ int last_char;
+ int next_char;
+
+ smtp_got_bare_lf = 0;
+
+ /*
+ * It's painful to do I/O with records that may span multiple buffers.
+ * Allow for partial long lines (we will read the remainder later) and
+ * allow for lines ending in bare LF. The idea is to be liberal in what
+ * we accept, strict in what we send.
+ *
+ * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
+ * bare LF as record terminator.
+ */
+ last_char = (bound == 0 ?
+ vstring_get_flags(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0) :
+ vstring_get_flags_bound(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0, bound));
+
+ switch (last_char) {
+
+ /*
+ * Do some repair in the rare case that we stopped reading in the
+ * middle of the CRLF record terminator.
+ */
+ case '\r':
+ if ((next_char = VSTREAM_GETC(stream)) == '\n') {
+ VSTRING_ADDCH(vp, '\n');
+ last_char = '\n';
+ /* FALLTRHOUGH */
+ } else {
+ if (next_char != VSTREAM_EOF)
+ vstream_ungetc(stream, next_char);
+ break;
+ }
+
+ /*
+ * Strip off the record terminator: either CRLF or just bare LF.
+ *
+ * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR
+ * if received before CRLF, and leave it alone otherwise.
+ */
+ case '\n':
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ if (smtp_detect_bare_lf) {
+ if (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r')
+ smtp_got_bare_lf = smtp_detect_bare_lf;
+ else
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ } else {
+ while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ }
+ VSTRING_TERMINATE(vp);
+ /* FALLTRHOUGH */
+
+ /*
+ * Partial line: just read the remainder later. If we ran into EOF,
+ * the next test will deal with it.
+ */
+ default:
+ break;
+ }
+
+ /*
+ * Optionally, skip over excess input, protected by the same time limit.
+ */
+ if (last_char != '\n' && (flags & SMTP_GET_FLAG_SKIP)
+ && vstream_feof(stream) == 0 && vstream_ferror(stream) == 0)
+ while ((next_char = VSTREAM_GETC(stream)) != VSTREAM_EOF
+ && next_char != '\n')
+ /* void */ ;
+
+ return (last_char);
+}
+
+/* smtp_fputs - write one line to SMTP peer */
+
+void smtp_fputs(const char *cp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ if (todo < 0)
+ msg_panic("smtp_fputs: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fwrite(stream, cp, todo) != todo
+ || vstream_fputs("\r\n", stream) == VSTREAM_EOF);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputs");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputs");
+}
+
+/* smtp_fwrite - write one string to SMTP peer */
+
+void smtp_fwrite(const char *cp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ if (todo < 0)
+ msg_panic("smtp_fwrite: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fwrite(stream, cp, todo) != todo);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fwrite");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fwrite");
+}
+
+/* smtp_fread_buf - read one buffer from SMTP peer */
+
+void smtp_fread_buf(VSTRING *vp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * Do not return early if todo == 0. We still need the side effects from
+ * calling vstream_fread_buf() including resetting the buffer write
+ * position. Skipping the call would invalidate the buffer state.
+ */
+ if (todo < 0)
+ msg_panic("smtp_fread_buf: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fread_buf(stream, vp, todo) != todo);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fread");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fread");
+}
+
+/* smtp_fputc - write to SMTP peer */
+
+void smtp_fputc(int ch, VSTREAM *stream)
+{
+ int stat;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ stat = VSTREAM_PUTC(ch, stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputc");
+ if (stat == VSTREAM_EOF)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputc");
+}
diff --git a/src/global/smtp_stream.h b/src/global/smtp_stream.h
new file mode 100644
index 0000000..2cf011a
--- /dev/null
+++ b/src/global/smtp_stream.h
@@ -0,0 +1,74 @@
+#ifndef _SMTP_STREAM_H_INCLUDED_
+#define _SMTP_STREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* smtp_stream 3h
+/* SUMMARY
+/* smtp stream I/O support
+/* SYNOPSIS
+/* #include <smtp_stream.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+#include <setjmp.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * External interface. The following codes are meant for use in longjmp(),
+ * so they must all be non-zero.
+ */
+#define SMTP_ERR_EOF 1 /* unexpected client disconnect */
+#define SMTP_ERR_TIME 2 /* time out */
+#define SMTP_ERR_QUIET 3 /* silent cleanup (application) */
+#define SMTP_ERR_NONE 4 /* non-error case */
+#define SMTP_ERR_DATA 5 /* application data error */
+
+extern void smtp_stream_setup(VSTREAM *, int, int, int);
+extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...);
+extern void smtp_flush(VSTREAM *);
+extern int smtp_fgetc(VSTREAM *);
+extern int smtp_get(VSTRING *, VSTREAM *, ssize_t, int);
+extern int smtp_get_noexcept(VSTRING *, VSTREAM *, ssize_t, int);
+extern void smtp_fputs(const char *, ssize_t len, VSTREAM *);
+extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *);
+extern void smtp_fread_buf(VSTRING *, ssize_t len, VSTREAM *);
+extern void smtp_fputc(int, VSTREAM *);
+extern int smtp_detect_bare_lf;
+extern int smtp_got_bare_lf;
+
+extern void smtp_vprintf(VSTREAM *, const char *, va_list);
+
+#define smtp_timeout_setup(stream, timeout) \
+ smtp_stream_setup((stream), (timeout), 0, 0)
+
+#define SMTP_GET_FLAG_NONE 0
+#define SMTP_GET_FLAG_SKIP (1<<0) /* skip over excess input */
+#define SMTP_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/smtputf8.c b/src/global/smtputf8.c
new file mode 100644
index 0000000..d361a3b
--- /dev/null
+++ b/src/global/smtputf8.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* smtputf8 3
+/* SUMMARY
+/* SMTPUTF8 support
+/* SYNOPSIS
+/* #include <smtputf8.h>
+/*
+/* int smtputf8_autodetect(class)
+/* int class;
+/* DESCRIPTION
+/* smtputf8_autodetect() determines whether the cleanup server
+/* should perform SMTPUTF8 detection, depending on the declared
+/* source class and the setting of the smtputf8_autodetect_classes
+/* configuration parameter.
+/*
+/* Specify one of the following:
+/* .IP MAIL_SRC_MASK_SENDMAIL
+/* Submission with the Postfix sendmail(1) command.
+/* .IP MAIL_SRC_MASK_SMTPD
+/* Mail received with the smtpd(8) daemon.
+/* .IP MAIL_SRC_MASK_QMQPD
+/* Mail received with the qmqpd(8) daemon.
+/* .IP MAIL_SRC_MASK_FORWARD
+/* Local forwarding or aliasing.
+/* .IP MAIL_SRC_MASK_BOUNCE
+/* Submission by the bounce(8) daemon.
+/* .IP MAIL_SRC_MASK_NOTIFY
+/* Postmaster notification from the smtp(8) or smtpd(8) daemon.
+/* .IP MAIL_SRC_MASK_VERIFY
+/* Address verification probe.
+/* DIAGNOSTICS
+/* Panic: no valid class argument.
+/*
+/* Specify one of the following:
+/* Warning: the smtputf8_autodetect_classes parameter specifies
+/* an invalid source category name.
+/* 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 <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <cleanup_user.h>
+#include <mail_proto.h>
+#include <smtputf8.h>
+
+/* smtputf8_autodetect - enable SMTPUTF8 autodetection */
+
+int smtputf8_autodetect(int class)
+{
+ static const char myname[] = "smtputf8_autodetect";
+ static const NAME_MASK table[] = {
+ MAIL_SRC_NAME_SENDMAIL, MAIL_SRC_MASK_SENDMAIL,
+ MAIL_SRC_NAME_SMTPD, MAIL_SRC_MASK_SMTPD,
+ MAIL_SRC_NAME_QMQPD, MAIL_SRC_MASK_QMQPD,
+ MAIL_SRC_NAME_FORWARD, MAIL_SRC_MASK_FORWARD,
+ MAIL_SRC_NAME_BOUNCE, MAIL_SRC_MASK_BOUNCE,
+ MAIL_SRC_NAME_NOTIFY, MAIL_SRC_MASK_NOTIFY,
+ MAIL_SRC_NAME_VERIFY, MAIL_SRC_MASK_VERIFY,
+ MAIL_SRC_NAME_ALL, MAIL_SRC_MASK_ALL,
+ 0,
+ };
+ int autodetect_classes = 0;
+
+ if (class == 0 || (class & ~MAIL_SRC_MASK_ALL) != 0)
+ msg_panic("%s: bad source class: %d", myname, class);
+ if (*var_smtputf8_autoclass) {
+ autodetect_classes =
+ name_mask(VAR_SMTPUTF8_AUTOCLASS, table, var_smtputf8_autoclass);
+ if (autodetect_classes == 0)
+ msg_warn("%s: bad input: %s", VAR_SMTPUTF8_AUTOCLASS,
+ var_smtputf8_autoclass);
+ if (autodetect_classes & class)
+ return (CLEANUP_FLAG_AUTOUTF8);
+ }
+ return (0);
+}
diff --git a/src/global/smtputf8.h b/src/global/smtputf8.h
new file mode 100644
index 0000000..95d6583
--- /dev/null
+++ b/src/global/smtputf8.h
@@ -0,0 +1,113 @@
+#ifndef _SMTPUTF8_H_INCLUDED_
+#define _SMTPUTF8_H_INCLUDED_
+
+/*++
+/* NAME
+/* smtputf8 3h
+/* SUMMARY
+/* SMTPUTF8 support
+/* SYNOPSIS
+/* #include <smtputf8.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Avoiding chicken-and-egg problems during the initial SMTPUTF8 roll-out in
+ * environments with pre-existing mail flows that contain UTF8.
+ *
+ * Prior to SMTPUTF8, mail flows that contain UTF8 worked because the vast
+ * majority of MTAs is perfectly capable of handling UTF8 in address
+ * localparts (and in headers), even if pre-SMTPUTF8 standards do not
+ * support this practice.
+ *
+ * When turning on Postfix SMTPUTF8 support for the first time, we don't want
+ * to suddenly break pre-existing mail flows that contain UTF8 because 1) a
+ * client does not request SMTPUTF8 support, and because 2) a down-stream
+ * MTA does not announce SMTPUTF8 support.
+ *
+ * While 1) is easy enough to avoid (keep accepting UTF8 in address localparts
+ * just like Postfix has always done), 2) presents a thornier problem. The
+ * root cause of that problem is the need for SMTPUTF8 autodetection.
+ *
+ * What is SMTPUTF8 autodetection? Postfix cannot rely solely on the sender's
+ * declaration that a message requires SMTPUTF8 support, because UTF8 may be
+ * introduced during local processing (for example, the client hostname in
+ * Postfix's Received: header, adding @$myorigin or .$mydomain to an
+ * incomplete address, address rewriting, alias expansion, automatic BCC
+ * recipients, local forwarding, and changes made by header checks or Milter
+ * applications).
+ *
+ * In summary, after local processing has happened, Postfix may decide that a
+ * message requires SMTPUTF8 support, even when that message initially did
+ * not require SMTPUTF8 support. This could make the message undeliverable
+ * to destinations that do not support SMTPUTF8. In an environment with
+ * pre-existing mail flows that contain UTF8, we want to avoid disrupting
+ * those mail flows when rolling out SMTPUTF8 support.
+ *
+ * For the vast majority of sites, the simplest solution is to autodetect
+ * SMTPUTF8 support only for Postfix sendmail command-line submissions, at
+ * least as long as SMTPUTF8 support has not yet achieved wold domination.
+ *
+ * However, sites that add UTF8 content via local processing (see above) should
+ * autodetect SMTPUTF8 support for all email.
+ *
+ * smtputf8_autodetect() uses the setting of the smtputf8_autodetect_classes
+ * parameter, and the mail source classes defined in mail_params.h.
+ */
+extern int smtputf8_autodetect(int);
+
+ /*
+ * The flag SMTPUTF8_FLAG_REQUESTED is raised on request by the sender, or
+ * when a queue file contains at least one UTF8 envelope recipient. One this
+ * flag is raised it is preserved when mail is forwarded or bounced.
+ *
+ * The flag SMTPUTF8_FLAG_HEADER is raised when a queue file contains at least
+ * one UTF8 message header.
+ *
+ * The flag SMTPUTF8_FLAG_SENDER is raised when a queue file contains an UTF8
+ * envelope sender.
+ *
+ * The three flags SMTPUTF8_FLAG_REQUESTED/HEADER/SENDER are stored in the
+ * queue file, are sent with delivery requests to Postfix delivery agents,
+ * and are sent with "flush" requests to the bounce daemon to ensure that
+ * the resulting notification message will have a content-transfer-encoding
+ * of 8bit.
+ *
+ * In the future, mailing lists will have a mix of UTF8 and non-UTF8
+ * subscribers. With the following flag, Postfix can avoid requiring
+ * SMTPUTF8 delivery when it isn't really needed.
+ *
+ * The flag SMTPUTF8_FLAG_RECIPIENT is raised when a delivery request (NOT:
+ * message) contains at least one UTF8 envelope recipient. The flag is NOT
+ * stored in the queue file. The flag sent in requests to the bounce daemon
+ * ONLY when bouncing a single recipient. The flag is used ONLY in requests
+ * to Postfix delivery agents, to give Postfix flexibility when delivering
+ * messages to non-SMTPUTF8 servers.
+ *
+ * If a delivery request has none of the flags SMTPUTF8_FLAG_RECIPIENT,
+ * SMTPUTF8_FLAG_SENDER, or SMTPUTF8_FLAG_HEADER, then the message can
+ * safely be delivered to a non-SMTPUTF8 server (DSN original recipients
+ * will be encoded appropriately per RFC 6533).
+ *
+ * To allow even more SMTPUTF8 mail to be sent to non-SMTPUTF8 servers,
+ * implement RFC 2047 header encoding in the Postfix SMTP client, and update
+ * the SMTP client protocol engine.
+ */
+#define SMTPUTF8_FLAG_NONE (0)
+#define SMTPUTF8_FLAG_REQUESTED (1<<0) /* queue file/delivery/bounce request */
+#define SMTPUTF8_FLAG_HEADER (1<<1) /* queue file/delivery/bounce request */
+#define SMTPUTF8_FLAG_SENDER (1<<2) /* queue file/delivery/bounce request */
+#define SMTPUTF8_FLAG_RECIPIENT (1<<3) /* delivery request only */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/split_addr.c b/src/global/split_addr.c
new file mode 100644
index 0000000..fb8bb34
--- /dev/null
+++ b/src/global/split_addr.c
@@ -0,0 +1,103 @@
+/*++
+/* NAME
+/* split_addr 3
+/* SUMMARY
+/* recipient localpart address splitter
+/* SYNOPSIS
+/* #include <split_addr.h>
+/*
+/* char *split_addr_internal(localpart, delimiter_set)
+/* char *localpart;
+/* const char *delimiter_set;
+/* LEGACY SUPPORT
+/* char *split_addr(localpart, delimiter_set)
+/* char *localpart;
+/* const char *delimiter_set;
+/* DESCRIPTION
+/* split_addr*() null-terminates \fIlocalpart\fR at the first
+/* occurrence of the \fIdelimiter\fR character(s) found, and
+/* returns a pointer to the remainder.
+/*
+/* With split_addr_internal(), the address must be in internal
+/* (unquoted) form.
+/*
+/* split_addr() is a backwards-compatible form for legacy code.
+/*
+/* Reserved addresses are not split: postmaster, mailer-daemon,
+/* double-bounce. Addresses that begin with owner-, or addresses
+/* that end in -request are not split when the owner_request_special
+/* parameter is set.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <split_at.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <split_addr.h>
+
+/* split_addr_internal - split address with extreme prejudice */
+
+char *split_addr_internal(char *localpart, const char *delimiter_set)
+{
+ ssize_t len;
+
+ /*
+ * Don't split these, regardless of what the delimiter is.
+ */
+ if (strcasecmp(localpart, MAIL_ADDR_POSTMASTER) == 0)
+ return (0);
+ if (strcasecmp(localpart, MAIL_ADDR_MAIL_DAEMON) == 0)
+ return (0);
+ if (strcasecmp_utf8(localpart, var_double_bounce_sender) == 0)
+ return (0);
+
+ /*
+ * Backwards compatibility: don't split owner-foo or foo-request.
+ */
+ if (strchr(delimiter_set, '-') != 0 && var_ownreq_special != 0) {
+ if (strncasecmp(localpart, "owner-", 6) == 0)
+ return (0);
+ if ((len = strlen(localpart) - 8) > 0
+ && strcasecmp(localpart + len, "-request") == 0)
+ return (0);
+ }
+
+ /*
+ * Safe to split this address. Do not split the address if the result
+ * would have a null localpart.
+ */
+ if ((len = strcspn(localpart, delimiter_set)) == 0 || localpart[len] == 0) {
+ return (0);
+ } else {
+ localpart[len] = 0;
+ return (localpart + len + 1);
+ }
+}
diff --git a/src/global/split_addr.h b/src/global/split_addr.h
new file mode 100644
index 0000000..ef151c3
--- /dev/null
+++ b/src/global/split_addr.h
@@ -0,0 +1,38 @@
+#ifndef _SPLIT_ADDR_H_INCLUDED_
+#define _SPLIT_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* split_addr 3h
+/* SUMMARY
+/* recipient localpart address splitter
+/* SYNOPSIS
+/* #include <split_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern char *split_addr_internal(char *, const char *);
+
+ /* Legacy API. */
+
+#define split_addr split_addr_internal
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/stream2rec.c b/src/global/stream2rec.c
new file mode 100644
index 0000000..1dbd962
--- /dev/null
+++ b/src/global/stream2rec.c
@@ -0,0 +1,47 @@
+/*++
+/* NAME
+/* stream2rec 1
+/* SUMMARY
+/* convert stream-lf data to record format
+/* SYNOPSIS
+/* stream2rec
+/* DESCRIPTION
+/* stream2rec reads lines from standard input and writes
+/* them to standard output in record form.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <vstream.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_streamlf.h>
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(150);
+ int type;
+
+ while ((type = rec_streamlf_get(VSTREAM_IN, buf, 150)) > 0)
+ REC_PUT_BUF(VSTREAM_OUT, type, buf);
+ vstream_fflush(VSTREAM_OUT);
+ return (0);
+}
diff --git a/src/global/string_list.c b/src/global/string_list.c
new file mode 100644
index 0000000..ddd950a
--- /dev/null
+++ b/src/global/string_list.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* string_list 3
+/* SUMMARY
+/* match a string against a pattern list
+/* SYNOPSIS
+/* #include <string_list.h>
+/*
+/* STRING_LIST *string_list_init(pname, flags, pattern_list)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/*
+/* int string_list_match(list, name)
+/* STRING_LIST *list;
+/* const char *name;
+/*
+/* void string_list_free(list)
+/* STRING_LIST *list;
+/* DESCRIPTION
+/* This is a convenience wrapper around the match_list module.
+/*
+/* This module implements tests for list membership of a string.
+/*
+/* Patterns are separated by whitespace and/or commas. A pattern
+/* is either a string, a file name (in which case the contents
+/* of the file are substituted for the file name) or a type:name
+/* lookup table specification.
+/*
+/* A string matches a string list when it appears in the list of
+/* string patterns. The matching process is case insensitive.
+/* In order to reverse the result, precede a pattern with an
+/* exclamation point (!).
+/*
+/* string_list_init() performs initializations. The pname
+/* argument specifies error reporting context. The flags argument
+/* is a bit-wise OR of zero or more of following:
+/* .IP MATCH_FLAG_RETURN
+/* Request that string_list_match() logs a warning and returns
+/* zero with list->error set to a non-zero dictionary error
+/* code, instead of raising a fatal error.
+/* .PP
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* The last argument specifies a list of string patterns.
+/*
+/* string_list_match() matches the specified string against the
+/* compiled pattern list.
+/*
+/* string_list_free() releases storage allocated by string_list_init().
+/* DIAGNOSTICS
+/* Fatal error: unable to open or read a pattern file or table.
+/* SEE ALSO
+/* match_list(3) generic list matching
+/* match_ops(3) match strings by name or by address
+/* 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 <match_list.h>
+
+/* Global library. */
+
+#include "string_list.h"
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+#include <dict.h>
+#include <stringops.h> /* util_utf8_enable */
+
+static void usage(char *progname)
+{
+ msg_fatal("usage: %s [-v] patterns string", progname);
+}
+
+int main(int argc, char **argv)
+{
+ STRING_LIST *list;
+ char *string;
+ int ch;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 2)
+ usage(argv[0]);
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ list = string_list_init("command line", MATCH_FLAG_RETURN, argv[optind]);
+ string = argv[optind + 1];
+ vstream_printf("%s: %s\n", string, string_list_match(list, string) ?
+ "YES" : list->error == 0 ? "NO" : "ERROR");
+ vstream_fflush(VSTREAM_OUT);
+ string_list_free(list);
+ return (0);
+}
+
+#endif
diff --git a/src/global/string_list.h b/src/global/string_list.h
new file mode 100644
index 0000000..1079a76
--- /dev/null
+++ b/src/global/string_list.h
@@ -0,0 +1,40 @@
+#ifndef _STRING_LIST_H_INCLUDED_
+#define _STRING_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* string_list 3h
+/* SUMMARY
+/* match a string against a pattern list
+/* SYNOPSIS
+/* #include <string_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <match_list.h>
+
+ /*
+ * External interface.
+ */
+#define STRING_LIST MATCH_LIST
+
+#define string_list_init(o, f, p) \
+ match_list_init((o), (f), (p), 1, match_string)
+#define string_list_match match_list_match
+#define string_list_free match_list_free
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/strip_addr.c b/src/global/strip_addr.c
new file mode 100644
index 0000000..c4c39b0
--- /dev/null
+++ b/src/global/strip_addr.c
@@ -0,0 +1,252 @@
+/*++
+/* NAME
+/* strip_addr 3
+/* SUMMARY
+/* strip extension from full or localpart-only address
+/* SYNOPSIS
+/* #include <strip_addr.h>
+/*
+/* char *strip_addr_internal(address, extension, delimiter_set)
+/* const char *address;
+/* char **extension;
+/* const char *delimiter_set;
+/* LEGACY SUPPORT
+/* char *strip_addr(address, extension, delimiter_set)
+/* const char *address;
+/* char **extension;
+/* const char *delimiter_set;
+/* DESCRIPTION
+/* strip_addr*() takes an address and either returns a null
+/* pointer when the address contains no address extension,
+/* or returns a copy of the address without address extension.
+/* The caller is expected to pass the copy to myfree().
+/*
+/* With strip_addr_internal(), the input and result are in
+/* internal form.
+/*
+/* strip_addr() is a backwards-compatible form for legacy code.
+/*
+/* Arguments:
+/* .IP address
+/* Address localpart or user@domain form.
+/* .IP extension
+/* A null pointer, or the address of a pointer that is set to
+/* the address of a dynamic memory copy of the address extension
+/* that had to be chopped off.
+/* The copy includes the recipient address delimiter.
+/* The caller is expected to pass the copy to myfree().
+/* .IP delimiter_set
+/* Set of recipient address delimiter characters.
+/* SEE ALSO
+/* split_addr(3) strip extension from localpart
+/* 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>
+
+/* Global library. */
+
+#include <split_addr.h>
+#include <strip_addr.h>
+
+/* strip_addr - strip extension from address */
+
+char *strip_addr_internal(const char *full, char **extension,
+ const char *delimiter_set)
+{
+ char *ratsign;
+ char *extent;
+ char *saved_ext;
+ char *stripped;
+
+ /*
+ * A quick test to eliminate inputs without delimiter anywhere.
+ */
+ if (*delimiter_set == 0 || full[strcspn(full, delimiter_set)] == 0) {
+ stripped = saved_ext = 0;
+ } else {
+ stripped = mystrdup(full);
+ if ((ratsign = strrchr(stripped, '@')) != 0)
+ *ratsign = 0;
+ if ((extent = split_addr(stripped, delimiter_set)) != 0) {
+ extent -= 1;
+ if (extension) {
+ *extent = full[strlen(stripped)];
+ saved_ext = mystrdup(extent);
+ *extent = 0;
+ } else
+ saved_ext = 0;
+ if (ratsign != 0) {
+ *ratsign = '@';
+ memmove(extent, ratsign, strlen(ratsign) + 1);
+ }
+ } else {
+ myfree(stripped);
+ stripped = saved_ext = 0;
+ }
+ }
+ if (extension)
+ *extension = saved_ext;
+ return (stripped);
+}
+
+#ifdef TEST
+
+#include <msg.h>
+#include <mail_params.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ char *extension;
+ char *stripped;
+ char *delim = "+-";
+
+#define NO_DELIM ""
+
+ /*
+ * No static initializer, because this is owned by a library.
+ */
+ var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
+
+ /*
+ * Incredible. This function takes only three arguments, and the tests
+ * already take more lines of code than the code being tested.
+ */
+ stripped = strip_addr_internal("foo", (char **) 0, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 1");
+
+ stripped = strip_addr_internal("foo", &extension, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 2");
+ if (extension != 0)
+ msg_panic("strip_addr botch 3");
+
+ stripped = strip_addr_internal("foo", (char **) 0, delim);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 4");
+
+ stripped = strip_addr_internal("foo", &extension, delim);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 5");
+ if (extension != 0)
+ msg_panic("strip_addr botch 6");
+
+ stripped = strip_addr_internal("foo@bar", (char **) 0, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 7");
+
+ stripped = strip_addr_internal("foo@bar", &extension, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 8");
+ if (extension != 0)
+ msg_panic("strip_addr botch 9");
+
+ stripped = strip_addr_internal("foo@bar", (char **) 0, delim);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 10");
+
+ stripped = strip_addr_internal("foo@bar", &extension, delim);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 11");
+ if (extension != 0)
+ msg_panic("strip_addr botch 12");
+
+ stripped = strip_addr_internal("foo-ext", (char **) 0, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 13");
+
+ stripped = strip_addr_internal("foo-ext", &extension, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 14");
+ if (extension != 0)
+ msg_panic("strip_addr botch 15");
+
+ stripped = strip_addr_internal("foo-ext", (char **) 0, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 16");
+ msg_info("wanted: foo-ext -> %s", "foo");
+ msg_info("strip_addr foo-ext -> %s", stripped);
+ myfree(stripped);
+
+ stripped = strip_addr_internal("foo-ext", &extension, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 17");
+ if (extension == 0)
+ msg_panic("strip_addr botch 18");
+ msg_info("wanted: foo-ext -> %s %s", "foo", "-ext");
+ msg_info("strip_addr foo-ext -> %s %s", stripped, extension);
+ myfree(stripped);
+ myfree(extension);
+
+ stripped = strip_addr_internal("foo-ext@bar", (char **) 0, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 19");
+
+ stripped = strip_addr_internal("foo-ext@bar", &extension, NO_DELIM);
+ if (stripped != 0)
+ msg_panic("strip_addr botch 20");
+ if (extension != 0)
+ msg_panic("strip_addr botch 21");
+
+ stripped = strip_addr_internal("foo-ext@bar", (char **) 0, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 22");
+ msg_info("wanted: foo-ext@bar -> %s", "foo@bar");
+ msg_info("strip_addr foo-ext@bar -> %s", stripped);
+ myfree(stripped);
+
+ stripped = strip_addr_internal("foo-ext@bar", &extension, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 23");
+ if (extension == 0)
+ msg_panic("strip_addr botch 24");
+ msg_info("wanted: foo-ext@bar -> %s %s", "foo@bar", "-ext");
+ msg_info("strip_addr foo-ext@bar -> %s %s", stripped, extension);
+ myfree(stripped);
+ myfree(extension);
+
+ stripped = strip_addr_internal("foo+ext@bar", &extension, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 25");
+ if (extension == 0)
+ msg_panic("strip_addr botch 26");
+ msg_info("wanted: foo+ext@bar -> %s %s", "foo@bar", "+ext");
+ msg_info("strip_addr foo+ext@bar -> %s %s", stripped, extension);
+ myfree(stripped);
+ myfree(extension);
+
+ stripped = strip_addr_internal("foo bar+ext", &extension, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 27");
+ if (extension == 0)
+ msg_panic("strip_addr botch 28");
+ msg_info("wanted: foo bar+ext -> %s %s", "foo bar", "+ext");
+ msg_info("strip_addr foo bar+ext -> %s %s", stripped, extension);
+ myfree(stripped);
+ myfree(extension);
+
+ return (0);
+}
+
+#endif
diff --git a/src/global/strip_addr.h b/src/global/strip_addr.h
new file mode 100644
index 0000000..02170d5
--- /dev/null
+++ b/src/global/strip_addr.h
@@ -0,0 +1,36 @@
+#ifndef _STRIP_ADDR_H_INCLUDED_
+#define _STRIP_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* strip_addr 3h
+/* SUMMARY
+/* strip extension from full address
+/* SYNOPSIS
+/* #include <strip_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern char *strip_addr_internal(const char *, char **, const char *);
+
+#define strip_addr strip_addr_internal
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/strip_addr.ref b/src/global/strip_addr.ref
new file mode 100644
index 0000000..1a28395
--- /dev/null
+++ b/src/global/strip_addr.ref
@@ -0,0 +1,12 @@
+unknown: wanted: foo-ext -> foo
+unknown: strip_addr foo-ext -> foo
+unknown: wanted: foo-ext -> foo -ext
+unknown: strip_addr foo-ext -> foo -ext
+unknown: wanted: foo-ext@bar -> foo@bar
+unknown: strip_addr foo-ext@bar -> foo@bar
+unknown: wanted: foo-ext@bar -> foo@bar -ext
+unknown: strip_addr foo-ext@bar -> foo@bar -ext
+unknown: wanted: foo+ext@bar -> foo@bar +ext
+unknown: strip_addr foo+ext@bar -> foo@bar +ext
+unknown: wanted: foo bar+ext -> foo bar +ext
+unknown: strip_addr foo bar+ext -> foo bar +ext
diff --git a/src/global/surrogate.ref b/src/global/surrogate.ref
new file mode 100644
index 0000000..cd12a54
--- /dev/null
+++ b/src/global/surrogate.ref
@@ -0,0 +1,36 @@
+./mail_dict: error: ldap:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: ldap:/xx is unavailable. ldap:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: ldap:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: mysql:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: mysql:/xx is unavailable. mysql:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: mysql:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: pgsql:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: pgsql:/xx is unavailable. pgsql:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: pgsql:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: sqlite:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: sqlite:/xx is unavailable. sqlite:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: sqlite:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: memcache:/xx is unavailable. open /xx: No such file or directory
+foo: error
diff --git a/src/global/sys_exits.c b/src/global/sys_exits.c
new file mode 100644
index 0000000..1e628d7
--- /dev/null
+++ b/src/global/sys_exits.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* sys_exits 3
+/* SUMMARY
+/* sendmail-compatible exit status handling
+/* SYNOPSIS
+/* #include <sys_exits.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int status; /* exit status */
+/* const char *dsn; /* RFC 3463 */
+/* const char *text; /* free text */
+/* .in -4
+/* } SYS_EXITS_DETAIL;
+/*
+/* int SYS_EXITS_CODE(code)
+/* int code;
+/*
+/* const char *sys_exits_strerror(code)
+/* int code;
+/*
+/* const SYS_EXITS_DETAIL *sys_exits_detail(code)
+/* int code;
+/*
+/* int sys_exits_softerror(code)
+/* int code;
+/* DESCRIPTION
+/* This module interprets sendmail-compatible process exit status
+/* codes.
+/*
+/* SYS_EXITS_CODE() returns non-zero when the specified code
+/* is a sendmail-compatible process exit status code.
+/*
+/* sys_exits_strerror() returns a descriptive text for the
+/* specified sendmail-compatible status code, or a generic
+/* text for an unknown status code.
+/*
+/* sys_exits_detail() returns a table entry with assorted
+/* information about the specified sendmail-compatible status
+/* code, or a generic entry for an unknown status code.
+/* The generic entry may be overwritten with each sys_exits_detail()
+/* call.
+/*
+/* sys_exits_softerror() returns non-zero when the specified
+/* sendmail-compatible status code corresponds to a recoverable error.
+/* An unknown status code is always unrecoverable.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <sys_exits.h>
+
+/* Application-specific. */
+
+static const SYS_EXITS_DETAIL sys_exits_table[] = {
+ EX_USAGE, "5.3.0", "command line usage error",
+ EX_DATAERR, "5.6.0", "data format error",
+ EX_NOINPUT, "5.3.0", "cannot open input",
+ EX_NOUSER, "5.1.1", "user unknown",
+ EX_NOHOST, "5.1.2", "host name unknown",
+ EX_UNAVAILABLE, "5.3.0", "service unavailable",
+ EX_SOFTWARE, "5.3.0", "internal software error",
+ EX_OSERR, "4.3.0", "system resource problem",
+ EX_OSFILE, "5.3.0", "critical OS file missing",
+ EX_CANTCREAT, "5.2.0", "can't create user output file",
+ EX_IOERR, "5.3.0", "input/output error",
+ EX_TEMPFAIL, "4.3.0", "temporary failure",
+ EX_PROTOCOL, "5.5.0", "remote error in protocol",
+ EX_NOPERM, "5.7.0", "permission denied",
+ EX_CONFIG, "5.3.5", "local configuration error",
+};
+
+static VSTRING *sys_exits_def_text = 0;
+
+static SYS_EXITS_DETAIL sys_exits_default[] = {
+ 0, "5.3.0", 0,
+};
+
+/* sys_exits_fake - fake an entry for an unknown code */
+
+static SYS_EXITS_DETAIL *sys_exits_fake(int code)
+{
+ if (sys_exits_def_text == 0)
+ sys_exits_def_text = vstring_alloc(30);
+
+ vstring_sprintf(sys_exits_def_text, "unknown mail system error %d", code);
+ sys_exits_default->text = vstring_str(sys_exits_def_text);
+ return (sys_exits_default);
+}
+
+/* sys_exits_strerror - map exit status to error string */
+
+const char *sys_exits_strerror(int code)
+{
+ if (!SYS_EXITS_CODE(code)) {
+ return (sys_exits_fake(code)->text);
+ } else {
+ return (sys_exits_table[code - EX__BASE].text);
+ }
+}
+
+/* sys_exits_detail - map exit status info table entry */
+
+const SYS_EXITS_DETAIL *sys_exits_detail(int code)
+{
+ if (!SYS_EXITS_CODE(code)) {
+ return (sys_exits_fake(code));
+ } else {
+ return (sys_exits_table + code - EX__BASE);
+ }
+}
+
+/* sys_exits_softerror - determine if error is transient */
+
+int sys_exits_softerror(int code)
+{
+ if (!SYS_EXITS_CODE(code)) {
+ return (sys_exits_default->dsn[0] == '4');
+ } else {
+ return (sys_exits_table[code - EX__BASE].dsn[0] == '4');
+ }
+}
diff --git a/src/global/sys_exits.h b/src/global/sys_exits.h
new file mode 100644
index 0000000..bf4cce1
--- /dev/null
+++ b/src/global/sys_exits.h
@@ -0,0 +1,60 @@
+#ifndef _SYS_EXITS_H_INCLUDED_
+#define _SYS_EXITS_H_INCLUDED_
+
+/*++
+/* NAME
+/* sys_exits 3h
+/* SUMMARY
+/* sendmail-compatible exit status handling
+/* SYNOPSIS
+/* #include <sys_exits.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const int status; /* exit status code */
+ const char *dsn; /* DSN detail */
+ const char *text; /* descriptive text */
+} SYS_EXITS_DETAIL;
+
+extern const char *sys_exits_strerror(int);
+extern const SYS_EXITS_DETAIL *sys_exits_detail(int);
+extern int sys_exits_softerror(int);
+
+#define SYS_EXITS_CODE(n) ((n) >= EX__BASE && (n) <= EX__MAX)
+
+#define EX__BASE 64 /* base value for error messages */
+
+#define EX_USAGE 64 /* command line usage error */
+#define EX_DATAERR 65 /* data format error */
+#define EX_NOINPUT 66 /* cannot open input */
+#define EX_NOUSER 67 /* addressee unknown */
+#define EX_NOHOST 68 /* host name unknown */
+#define EX_UNAVAILABLE 69 /* service unavailable */
+#define EX_SOFTWARE 70 /* internal software error */
+#define EX_OSERR 71 /* system error (e.g., can't fork) */
+#define EX_OSFILE 72 /* critical OS file missing */
+#define EX_CANTCREAT 73 /* can't create (user) output file */
+#define EX_IOERR 74 /* input/output error */
+#define EX_TEMPFAIL 75 /* temporary failure */
+#define EX_PROTOCOL 76 /* remote error in protocol */
+#define EX_NOPERM 77 /* permission denied */
+#define EX_CONFIG 78 /* configuration error */
+
+#define EX__MAX 78 /* maximum listed value */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/test_main.c b/src/global/test_main.c
new file mode 100644
index 0000000..a783ce3
--- /dev/null
+++ b/src/global/test_main.c
@@ -0,0 +1,224 @@
+/*++
+/* NAME
+/* test_main 3
+/* SUMMARY
+/* test main program
+/* SYNOPSIS
+/* #include <test_main.h>
+/*
+/* NORETURN test_main(argc, argv, test_driver, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*test_driver)(int argc, char **argv);
+/* int key;
+/* DESCRIPTION
+/* This module implements a test main program for stand-alone
+/* module tests.
+/*
+/* test_main() should be called from a main program. It does
+/* generic command-line options processing, and initializes
+/* configurable parameters. After calling the test_driver()
+/* function, the test_main() function terminates.
+/*
+/* Arguments:
+/* .IP "void (*test_driver)(int argc, char **argv)"
+/* A pointer to a function that is called after processing
+/* command-line options and initializing configuration parameters.
+/* The argc and argv specify the process name and non-option
+/* command-line arguments.
+/* .PP
+/* Optional test_main() arguments are specified as a null-terminated
+/* list with macros that have zero or more arguments:
+/* .IP "CA_TEST_MAIN_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_TEST_MAIN_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_TEST_MAIN_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* DIAGNOSTICS
+/* Problems and transactions are logged stderr.
+/* BUGS
+/* 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 <stdlib.h>
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <mail_task.h>
+#include <mail_version.h>
+
+ /*
+ * Test library.
+ */
+#include <test_main.h>
+
+/* test_driver_main - the real main program */
+
+NORETURN test_main(int argc, char **argv, TEST_DRIVER_FN test_driver,...)
+{
+ const char *myname = "test_driver_main";
+ va_list ap;
+ int ch;
+ int key;
+ int test_driver_argc;
+ char **test_driver_argv;
+
+ /*
+ * Set up logging.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "c:v")) > 0) {
+ switch (ch) {
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ msg_fatal("invalid option: %c. Usage: %s [-c config_dir] [-v]",
+ optopt, argv[0]);
+ break;
+ }
+ }
+
+ /*
+ * Initialize generic parameters.
+ */
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ set_mail_conf_str(VAR_SERVNAME, var_procname);
+ mail_conf_read();
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, test_driver);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case TEST_MAIN_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case TEST_MAIN_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case TEST_MAIN_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case TEST_MAIN_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case TEST_MAIN_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case TEST_MAIN_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case TEST_MAIN_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case TEST_MAIN_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ /*
+ * Set up call-back info.
+ */
+ test_driver_argv = argv + optind - 1;
+ if (test_driver_argv != argv)
+ test_driver_argv[0] = argv[0];
+ test_driver_argc = argc - optind + 1;
+
+ /*
+ * Call the test driver and terminate (if they didn't terminate already).
+ */
+ test_driver(test_driver_argc, test_driver_argv);
+ exit(0);
+}
diff --git a/src/global/test_main.h b/src/global/test_main.h
new file mode 100644
index 0000000..aea605a
--- /dev/null
+++ b/src/global/test_main.h
@@ -0,0 +1,64 @@
+/*++
+/* NAME
+/* test_main 3h
+/* SUMMARY
+/* test main program
+/* SYNOPSIS
+/* #include <test_main.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mail_conf.h>
+
+ /*
+ * External interface. Copied from master/mail_server.h, but without
+ * introducing libmaster dependencies.
+ */
+#define TEST_MAIN_INT_TABLE 1
+#define TEST_MAIN_STR_TABLE 2
+#define TEST_MAIN_BOOL_TABLE 3
+#define TEST_MAIN_TIME_TABLE 4
+#define TEST_MAIN_RAW_TABLE 5
+#define TEST_MAIN_NINT_TABLE 6
+#define TEST_MAIN_NBOOL_TABLE 7
+#define TEST_MAIN_LONG_TABLE 8
+
+#define CA_TEST_MAIN_INT_TABLE(v) TEST_MAIN_INT_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_INT_TABLE, (v))
+#define CA_TEST_MAIN_STR_TABLE(v) TEST_MAIN_STR_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_STR_TABLE, (v))
+#define CA_TEST_MAIN_BOOL_TABLE(v) TEST_MAIN_BOOL_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_BOOL_TABLE, (v))
+#define CA_TEST_MAIN_TIME_TABLE(v) TEST_MAIN_TIME_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_TIME_TABLE, (v))
+#define CA_TEST_MAIN_RAW_TABLE(v) TEST_MAIN_RAW_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_RAW_TABLE, (v))
+#define CA_TEST_MAIN_NINT_TABLE(v) TEST_MAIN_NINT_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_NINT_TABLE, (v))
+#define CA_TEST_MAIN_NBOOL_TABLE(v) TEST_MAIN_NBOOL_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_NBOOL_TABLE, (v))
+#define CA_TEST_MAIN_LONG_TABLE(v) TEST_MAIN_LONG_TABLE, CHECK_CPTR(TEST_MAIN, CONFIG_LONG_TABLE, (v))
+
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_INT_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_STR_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_BOOL_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_TIME_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_RAW_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_NINT_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_NBOOL_TABLE);
+CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_LONG_TABLE);
+
+typedef void (*TEST_DRIVER_FN) (int, char **);
+extern NORETURN test_main(int, char **, TEST_DRIVER_FN,...);
+
+/* 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/global/timed_ipc.c b/src/global/timed_ipc.c
new file mode 100644
index 0000000..a883e83
--- /dev/null
+++ b/src/global/timed_ipc.c
@@ -0,0 +1,55 @@
+/*++
+/* NAME
+/* timed_ipc 3
+/* SUMMARY
+/* enforce IPC timeout on stream
+/* SYNOPSIS
+/* #include <time_ipc.h>
+/*
+/* void timed_ipc_setup(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* timed_ipc() enforces on the specified stream the timeout as
+/* specified via the \fIipc_timeout\fR configuration parameter:
+/* a read or write operation fails if it does not succeed within
+/* \fIipc_timeout\fR seconds. This deadline exists as a safety
+/* measure for communication between mail subsystem programs,
+/* and should never be exceeded.
+/* DIAGNOSTICS
+/* Panic: sanity check failed. Fatal error: deadline exceeded.
+/* 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 <vstream.h>
+
+/* Global library. */
+
+#include "mail_params.h"
+#include "timed_ipc.h"
+
+/* timed_ipc_setup - enable ipc with timeout */
+
+void timed_ipc_setup(VSTREAM *stream)
+{
+ if (var_ipc_timeout <= 0)
+ msg_panic("timed_ipc_setup: bad ipc_timeout %d", var_ipc_timeout);
+
+ vstream_control(stream,
+ CA_VSTREAM_CTL_TIMEOUT(var_ipc_timeout),
+ CA_VSTREAM_CTL_END);
+}
diff --git a/src/global/timed_ipc.h b/src/global/timed_ipc.h
new file mode 100644
index 0000000..5014b3c
--- /dev/null
+++ b/src/global/timed_ipc.h
@@ -0,0 +1,35 @@
+#ifndef _TIMED_IPC_H_INCLUDED_
+#define _TIMED_IPC_H_INCLUDED_
+
+/*++
+/* NAME
+/* timed_ipc 3h
+/* SUMMARY
+/* enforce IPC timeout on stream
+/* SYNOPSIS
+/* #include <timed_ipc.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern void timed_ipc_setup(VSTREAM *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/tok822.h b/src/global/tok822.h
new file mode 100644
index 0000000..1143296
--- /dev/null
+++ b/src/global/tok822.h
@@ -0,0 +1,123 @@
+#ifndef _TOK822_H_INCLUDED_
+#define _TOK822_H_INCLUDED_
+
+/*++
+/* NAME
+/* tok822 3h
+/* SUMMARY
+/* RFC822 token structures
+/* SYNOPSIS
+/* #include <tok822.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <resolve_clnt.h>
+
+ /*
+ * Internal address representation: a token tree.
+ */
+typedef struct TOK822 {
+ int type; /* token value, see below */
+ VSTRING *vstr; /* token contents */
+ struct TOK822 *prev; /* peer */
+ struct TOK822 *next; /* peer */
+ struct TOK822 *head; /* group members */
+ struct TOK822 *tail; /* group members */
+ struct TOK822 *owner; /* group owner */
+} TOK822;
+
+ /*
+ * Token values for multi-character objects. Single-character operators are
+ * represented by their own character value.
+ */
+#define TOK822_MINTOK 256
+#define TOK822_ATOM 256 /* non-special character sequence */
+#define TOK822_QSTRING 257 /* stuff between "", not nesting */
+#define TOK822_COMMENT 258 /* comment including (), may nest */
+#define TOK822_DOMLIT 259 /* stuff between [] not nesting */
+#define TOK822_ADDR 260 /* actually a token group */
+#define TOK822_STARTGRP 261 /* start of named group */
+#define TOK822_MAXTOK 261
+
+ /*
+ * tok822_node.c
+ */
+extern TOK822 *tok822_alloc(int, const char *);
+extern TOK822 *tok822_free(TOK822 *);
+
+ /*
+ * tok822_tree.c
+ */
+extern TOK822 *tok822_append(TOK822 *, TOK822 *);
+extern TOK822 *tok822_prepend(TOK822 *, TOK822 *);
+extern TOK822 *tok822_cut_before(TOK822 *);
+extern TOK822 *tok822_cut_after(TOK822 *);
+extern TOK822 *tok822_unlink(TOK822 *);
+extern TOK822 *tok822_sub_append(TOK822 *, TOK822 *);
+extern TOK822 *tok822_sub_prepend(TOK822 *, TOK822 *);
+extern TOK822 *tok822_sub_keep_before(TOK822 *, TOK822 *);
+extern TOK822 *tok822_sub_keep_after(TOK822 *, TOK822 *);
+extern TOK822 *tok822_free_tree(TOK822 *);
+
+typedef int (*TOK822_ACTION) (TOK822 *);
+extern int tok822_apply(TOK822 *, int, TOK822_ACTION);
+extern TOK822 **tok822_grep(TOK822 *, int);
+
+ /*
+ * tok822_parse.c
+ */
+extern TOK822 *tok822_scan_limit(const char *, TOK822 **, int);
+extern TOK822 *tok822_scan_addr(const char *);
+extern TOK822 *tok822_parse_limit(const char *, int);
+extern VSTRING *tok822_externalize(VSTRING *, TOK822 *, int);
+extern VSTRING *tok822_internalize(VSTRING *, TOK822 *, int);
+
+#define tok822_scan(cp, ptr) tok822_scan_limit((cp), (ptr), 0)
+#define tok822_parse(cp) tok822_parse_limit((cp), 0)
+
+#define TOK822_STR_NONE (0)
+#define TOK822_STR_WIPE (1<<0)
+#define TOK822_STR_TERM (1<<1)
+#define TOK822_STR_LINE (1<<2)
+#define TOK822_STR_TRNC (1<<3)
+#define TOK822_STR_DEFL (TOK822_STR_WIPE | TOK822_STR_TERM)
+#define TOK822_STR_HEAD (TOK822_STR_TERM | TOK822_STR_LINE | TOK822_STR_TRNC)
+
+ /*
+ * tok822_find.c
+ */
+extern TOK822 *tok822_find_type(TOK822 *, int);
+extern TOK822 *tok822_rfind_type(TOK822 *, int);
+
+ /*
+ * tok822_rewrite.c
+ */
+extern TOK822 *tok822_rewrite(TOK822 *, const char *);
+
+ /*
+ * tok822_resolve.c
+ */
+#define tok822_resolve(t, r) tok822_resolve_from(RESOLVE_NULL_FROM, (t), (r))
+
+extern void tok822_resolve_from(const char *, TOK822 *, RESOLVE_REPLY *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/tok822_find.c b/src/global/tok822_find.c
new file mode 100644
index 0000000..c815646
--- /dev/null
+++ b/src/global/tok822_find.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* tok822_find 3
+/* SUMMARY
+/* token list search operators
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* TOK822 *tok822_find_type(head, type)
+/* TOK822 *head;
+/* int type;
+/*
+/* TOK822 *tok822_rfind_type(tail, type)
+/* TOK822 *tail;
+/* int type;
+/* DESCRIPTION
+/* This module implements token list search operations.
+/*
+/* tok822_find_type() searches a list of tokens for the first
+/* instance of the specified token type. The result is the
+/* found token or a null pointer when the search failed.
+/*
+/* tok822_rfind_type() searches a list of tokens in reverse direction
+/* for the first instance of the specified token type. The result
+/* is the found token or a null pointer when the search failed.
+/* 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 <vstring.h>
+
+/* Global library. */
+
+#include <tok822.h>
+
+/* tok822_find_type - find specific token type, forward search */
+
+TOK822 *tok822_find_type(TOK822 *head, int op)
+{
+ TOK822 *tp;
+
+ for (tp = head; tp != 0 && tp->type != op; tp = tp->next)
+ /* void */ ;
+ return (tp);
+}
+
+/* tok822_rfind_type - find specific token type, backward search */
+
+TOK822 *tok822_rfind_type(TOK822 *tail, int op)
+{
+ TOK822 *tp;
+
+ for (tp = tail; tp != 0 && tp->type != op; tp = tp->prev)
+ /* void */ ;
+ return (tp);
+}
diff --git a/src/global/tok822_limit.in b/src/global/tok822_limit.in
new file mode 100644
index 0000000..02b5c9d
--- /dev/null
+++ b/src/global/tok822_limit.in
@@ -0,0 +1 @@
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
diff --git a/src/global/tok822_limit.ref b/src/global/tok822_limit.ref
new file mode 100644
index 0000000..9f56d04
--- /dev/null
+++ b/src/global/tok822_limit.ref
@@ -0,0 +1,91 @@
+>>>1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22<<<
+
+Parse tree:
+ address
+ atom "1"
+ OP ","
+ address
+ atom "2"
+ OP ","
+ address
+ atom "3"
+ OP ","
+ address
+ atom "4"
+ OP ","
+ address
+ atom "5"
+ OP ","
+ address
+ atom "6"
+ OP ","
+ address
+ atom "7"
+ OP ","
+ address
+ atom "8"
+ OP ","
+ address
+ atom "9"
+ OP ","
+ address
+ atom "10"
+ OP ","
+ address
+ atom "11"
+ OP ","
+ address
+ atom "12"
+ OP ","
+ address
+ atom "13"
+ OP ","
+ address
+ atom "14"
+ OP ","
+ address
+ atom "15"
+ OP ","
+ address
+ atom "16"
+ OP ","
+ address
+ atom "17"
+ OP ","
+ address
+ atom "18"
+ OP ","
+ address
+ atom "19"
+ OP ","
+ address
+ atom "20"
+
+Internalized:
+1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+
+Externalized, no newlines inserted:
+1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+
+Externalized, newlines inserted:
+1,
+2,
+3,
+4,
+5,
+6,
+7,
+8,
+9,
+10,
+11,
+12,
+13,
+14,
+15,
+16,
+17,
+18,
+19,
+20
+
diff --git a/src/global/tok822_node.c b/src/global/tok822_node.c
new file mode 100644
index 0000000..f02b574
--- /dev/null
+++ b/src/global/tok822_node.c
@@ -0,0 +1,79 @@
+/*++
+/* NAME
+/* tok822_node 3
+/* SUMMARY
+/* token memory management
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* TOK822 *tok822_alloc(type, strval)
+/* int type;
+/* const char *strval;
+/*
+/* TOK822 *tok822_free(tp)
+/* TOK822 *tp;
+/* DESCRIPTION
+/* This module implements memory management for token
+/* structures. A distinction is made between single-character
+/* tokens that have no string value, and string-valued tokens.
+/*
+/* tok822_alloc() allocates memory for a token structure of
+/* the named type, and initializes it properly. In case of
+/* a single-character token, no string memory is allocated.
+/* Otherwise, \fIstrval\fR is a null pointer or provides
+/* string data to initialize the token with.
+/*
+/* tok822_free() releases the memory used for the specified token
+/* and conveniently returns a null pointer value.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include "tok822.h"
+
+/* tok822_alloc - allocate and initialize token */
+
+TOK822 *tok822_alloc(int type, const char *strval)
+{
+ TOK822 *tp;
+
+#define CONTAINER_TOKEN(x) \
+ ((x) == TOK822_ADDR || (x) == TOK822_STARTGRP)
+
+ tp = (TOK822 *) mymalloc(sizeof(*tp));
+ tp->type = type;
+ tp->next = tp->prev = tp->head = tp->tail = tp->owner = 0;
+ tp->vstr = (type < TOK822_MINTOK || CONTAINER_TOKEN(type) ? 0 :
+ strval == 0 ? vstring_alloc(10) :
+ vstring_strcpy(vstring_alloc(strlen(strval) + 1), strval));
+ return (tp);
+}
+
+/* tok822_free - destroy token */
+
+TOK822 *tok822_free(TOK822 *tp)
+{
+ if (tp->vstr)
+ vstring_free(tp->vstr);
+ myfree((void *) tp);
+ return (0);
+}
diff --git a/src/global/tok822_parse.c b/src/global/tok822_parse.c
new file mode 100644
index 0000000..4376802
--- /dev/null
+++ b/src/global/tok822_parse.c
@@ -0,0 +1,726 @@
+/*++
+/* NAME
+/* tok822_parse 3
+/* SUMMARY
+/* RFC 822 address parser
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* TOK822 *tok822_scan_limit(str, tailp, limit)
+/* const char *str;
+/* TOK822 **tailp;
+/* int limit;
+/*
+/* TOK822 *tok822_scan(str, tailp)
+/* const char *str;
+/* TOK822 **tailp;
+/*
+/* TOK822 *tok822_parse_limit(str, limit)
+/* const char *str;
+/* int limit;
+/*
+/* TOK822 *tok822_parse(str)
+/* const char *str;
+/*
+/* TOK822 *tok822_scan_addr(str)
+/* const char *str;
+/*
+/* VSTRING *tok822_externalize(buffer, tree, flags)
+/* VSTRING *buffer;
+/* TOK822 *tree;
+/* int flags;
+/*
+/* VSTRING *tok822_internalize(buffer, tree, flags)
+/* VSTRING *buffer;
+/* TOK822 *tree;
+/* int flags;
+/* DESCRIPTION
+/* This module converts address lists between string form and parse
+/* tree formats. The string form can appear in two different ways:
+/* external (or quoted) form, as used in message headers, and internal
+/* (unquoted) form, as used internally by the mail software.
+/* Although RFC 822 expects 7-bit data, these routines pay no
+/* special attention to 8-bit characters.
+/*
+/* tok822_scan() converts the external-form string in \fIstr\fR
+/* to a linear token list. The \fItailp\fR argument is a null pointer
+/* or receives the pointer value of the last result list element.
+/*
+/* tok822_scan_limit() implements tok822_scan(), which is a macro.
+/* The \fIlimit\fR argument is either zero or an upper bound on the
+/* number of tokens produced.
+/*
+/* tok822_parse() converts the external-form address list in
+/* \fIstr\fR to the corresponding token tree. The parser is permissive
+/* and will not throw away information that it does not understand.
+/* The parser adds missing commas between addresses.
+/*
+/* tok822_parse_limit() implements tok822_parse(), which is a macro.
+/* The \fIlimit\fR argument is either zero or an upper bound on the
+/* number of tokens produced.
+/*
+/* tok822_scan_addr() converts the external-form string in
+/* \fIstr\fR to an address token tree. This is just string to
+/* token list conversion; no parsing is done. This routine is
+/* suitable for data that should contain just one address and no
+/* other information.
+/*
+/* tok822_externalize() converts a token list to external form.
+/* Where appropriate, characters and strings are quoted and white
+/* space is inserted. The \fIflags\fR argument is the binary OR of
+/* zero or more of the following:
+/* .IP TOK822_STR_WIPE
+/* Initially, truncate the result to zero length.
+/* .IP TOK822_STR_TERM
+/* Append a null terminator to the result when done.
+/* .IP TOK822_STR_LINE
+/* Append a line break after each comma token, instead of appending
+/* whitespace. It is up to the caller to concatenate short lines to
+/* produce longer ones.
+/* .IP TOK822_STR_TRNC
+/* Truncate non-address information to 250 characters per address, to
+/* protect Sendmail systems that are vulnerable to the problem in CERT
+/* advisory CA-2003-07.
+/* This flag has effect with tok822_externalize() only.
+/* .PP
+/* The macro TOK_822_NONE expresses that none of the above features
+/* should be activated.
+/*
+/* The macro TOK822_STR_DEFL combines the TOK822_STR_WIPE and
+/* TOK822_STR_TERM flags. This is useful for most token to string
+/* conversions.
+/*
+/* The macro TOK822_STR_HEAD combines the TOK822_STR_TERM,
+/* TOK822_STR_LINE and TOK822_STR_TRNC flags. This is useful for
+/* the special case of token to mail header conversion.
+/*
+/* tok822_internalize() converts a token list to string form,
+/* without quoting. White space is inserted where appropriate.
+/* The \fIflags\fR argument is as with tok822_externalize().
+/* STANDARDS
+/* .ad
+/* .fi
+/* RFC 822 (ARPA Internet Text Messages). In addition to this standard
+/* this module implements additional operators such as % and !. These
+/* are needed because the real world is not all RFC 822. Also, the ':'
+/* operator is allowed to appear inside addresses, to accommodate DECnet.
+/* In addition, 8-bit data is not given special treatment.
+/* 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 <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include "lex_822.h"
+#include "quote_822_local.h"
+#include "tok822.h"
+
+ /*
+ * I suppose this is my favorite macro. Used heavily for tokenizing.
+ */
+#define COLLECT(t,s,c,cond) { \
+ while ((c = *(unsigned char *) s) != 0) { \
+ if (c == '\\') { \
+ if ((c = *(unsigned char *)++s) == 0) \
+ break; \
+ } else if (!(cond)) { \
+ break; \
+ } \
+ VSTRING_ADDCH(t->vstr, IS_SPACE_TAB_CR_LF(c) ? ' ' : c); \
+ s++; \
+ } \
+ VSTRING_TERMINATE(t->vstr); \
+ }
+
+#define COLLECT_SKIP_LAST(t,s,c,cond) { COLLECT(t,s,c,cond); if (*s) s++; }
+
+ /*
+ * Not quite as complex. The parser depends heavily on it.
+ */
+#define SKIP(tp, cond) { \
+ while (tp->type && (cond)) \
+ tp = tp->prev; \
+ }
+
+#define MOVE_COMMENT_AND_CONTINUE(tp, right) { \
+ TOK822 *prev = tok822_unlink(tp); \
+ right = tok822_prepend(right, tp); \
+ tp = prev; \
+ continue; \
+ }
+
+#define SKIP_MOVE_COMMENT(tp, cond, right) { \
+ while (tp->type && (cond)) { \
+ if (tp->type == TOK822_COMMENT) \
+ MOVE_COMMENT_AND_CONTINUE(tp, right); \
+ tp = tp->prev; \
+ } \
+ }
+
+ /*
+ * Single-character operators. We include the % and ! operators because not
+ * all the world is RFC822. XXX Make this operator list configurable when we
+ * have a real rewriting language. Include | for aliases file parsing.
+ */
+static char tok822_opchar[] = "|%!" LEX_822_SPECIALS;
+static void tok822_quote_atom(TOK822 *);
+static const char *tok822_comment(TOK822 *, const char *);
+static TOK822 *tok822_group(int, TOK822 *, TOK822 *, int);
+static void tok822_copy_quoted(VSTRING *, char *, char *);
+static int tok822_append_space(TOK822 *);
+
+#define DO_WORD (1<<0) /* finding a word is ok here */
+#define DO_GROUP (1<<1) /* doing an address group */
+
+#define ADD_COMMA ',' /* resynchronize */
+#define NO_MISSING_COMMA 0
+
+/* tok822_internalize - token tree to string, internal form */
+
+VSTRING *tok822_internalize(VSTRING *vp, TOK822 *tree, int flags)
+{
+ TOK822 *tp;
+
+ if (flags & TOK822_STR_WIPE)
+ VSTRING_RESET(vp);
+
+ for (tp = tree; tp; tp = tp->next) {
+ switch (tp->type) {
+ case ',':
+ VSTRING_ADDCH(vp, tp->type);
+ if (flags & TOK822_STR_LINE) {
+ VSTRING_ADDCH(vp, '\n');
+ continue;
+ }
+ break;
+ case TOK822_ADDR:
+ tok822_internalize(vp, tp->head, TOK822_STR_NONE);
+ break;
+ case TOK822_COMMENT:
+ case TOK822_ATOM:
+ case TOK822_QSTRING:
+ vstring_strcat(vp, vstring_str(tp->vstr));
+ break;
+ case TOK822_DOMLIT:
+ VSTRING_ADDCH(vp, '[');
+ vstring_strcat(vp, vstring_str(tp->vstr));
+ VSTRING_ADDCH(vp, ']');
+ break;
+ case TOK822_STARTGRP:
+ VSTRING_ADDCH(vp, ':');
+ break;
+ default:
+ if (tp->type >= TOK822_MINTOK)
+ msg_panic("tok822_internalize: unknown operator %d", tp->type);
+ VSTRING_ADDCH(vp, tp->type);
+ }
+ if (tok822_append_space(tp))
+ VSTRING_ADDCH(vp, ' ');
+ }
+ if (flags & TOK822_STR_TERM)
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* strip_address - strip non-address text from address expression */
+
+static void strip_address(VSTRING *vp, ssize_t start, TOK822 *addr)
+{
+ VSTRING *tmp;
+
+ /*
+ * Emit plain <address>. Discard any comments or phrases.
+ */
+ VSTRING_TERMINATE(vp);
+ msg_warn("stripping too many comments from address: %.100s...",
+ printable(vstring_str(vp) + start, '?'));
+ vstring_truncate(vp, start);
+ VSTRING_ADDCH(vp, '<');
+ if (addr) {
+ tmp = vstring_alloc(100);
+ tok822_internalize(tmp, addr, TOK822_STR_TERM);
+ quote_822_local_flags(vp, vstring_str(tmp),
+ QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_APPEND);
+ vstring_free(tmp);
+ }
+ VSTRING_ADDCH(vp, '>');
+}
+
+/* tok822_externalize - token tree to string, external form */
+
+VSTRING *tok822_externalize(VSTRING *vp, TOK822 *tree, int flags)
+{
+ VSTRING *tmp;
+ TOK822 *tp;
+ ssize_t start;
+ TOK822 *addr;
+ ssize_t addr_len;
+
+ /*
+ * Guard against a Sendmail buffer overflow (CERT advisory CA-2003-07).
+ * The problem was that Sendmail could store too much non-address text
+ * (comments, phrases, etc.) into a static 256-byte buffer.
+ *
+ * When the buffer fills up, fixed Sendmail versions remove comments etc.
+ * and reduce the information to just <$g>, which expands to <address>.
+ * No change is made when an address expression (text separated by
+ * commas) contains no address. This fix reportedly also protects
+ * Sendmail systems that are still vulnerable to this problem.
+ *
+ * Postfix takes the same approach, grudgingly. To avoid unnecessary damage,
+ * Postfix removes comments etc. only when the amount of non-address text
+ * in an address expression (text separated by commas) exceeds 250 bytes.
+ *
+ * With Sendmail, the address part of an address expression is the
+ * right-most <> instance in that expression. If an address expression
+ * contains no <>, then Postfix guarantees that it contains at most one
+ * non-comment string; that string is the address part of the address
+ * expression, so there is no ambiguity.
+ *
+ * Finally, we note that stress testing shows that other code in Sendmail
+ * 8.12.8 bluntly truncates ``text <address>'' to 256 bytes even when
+ * this means chopping the <address> somewhere in the middle. This is a
+ * loss of control that we're not entirely comfortable with. However,
+ * unbalanced quotes and dangling backslash do not seem to influence the
+ * way that Sendmail parses headers, so this is not an urgent problem.
+ */
+#define MAX_NONADDR_LENGTH 250
+
+#define RESET_NONADDR_LENGTH { \
+ start = VSTRING_LEN(vp); \
+ addr = 0; \
+ addr_len = 0; \
+ }
+
+#define ENFORCE_NONADDR_LENGTH do { \
+ if (addr && VSTRING_LEN(vp) - addr_len > start + MAX_NONADDR_LENGTH) \
+ strip_address(vp, start, addr->head); \
+ } while(0)
+
+ if (flags & TOK822_STR_WIPE)
+ VSTRING_RESET(vp);
+
+ if (flags & TOK822_STR_TRNC)
+ RESET_NONADDR_LENGTH;
+
+ for (tp = tree; tp; tp = tp->next) {
+ switch (tp->type) {
+ case ',':
+ if (flags & TOK822_STR_TRNC)
+ ENFORCE_NONADDR_LENGTH;
+ VSTRING_ADDCH(vp, tp->type);
+ VSTRING_ADDCH(vp, (flags & TOK822_STR_LINE) ? '\n' : ' ');
+ if (flags & TOK822_STR_TRNC)
+ RESET_NONADDR_LENGTH;
+ continue;
+
+ /*
+ * XXX In order to correctly externalize an address, it is not
+ * sufficient to quote individual atoms. There are higher-level
+ * rules that say when an address localpart needs to be quoted.
+ * We wing it with the quote_822_local() routine, which ignores
+ * the issue of atoms in the domain part that would need quoting.
+ */
+ case TOK822_ADDR:
+ addr = tp;
+ tmp = vstring_alloc(100);
+ tok822_internalize(tmp, tp->head, TOK822_STR_TERM);
+ addr_len = VSTRING_LEN(vp);
+ quote_822_local_flags(vp, vstring_str(tmp),
+ QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_APPEND);
+ addr_len = VSTRING_LEN(vp) - addr_len;
+ vstring_free(tmp);
+ break;
+ case TOK822_ATOM:
+ case TOK822_COMMENT:
+ vstring_strcat(vp, vstring_str(tp->vstr));
+ break;
+ case TOK822_QSTRING:
+ VSTRING_ADDCH(vp, '"');
+ tok822_copy_quoted(vp, vstring_str(tp->vstr), "\"\\\r\n");
+ VSTRING_ADDCH(vp, '"');
+ break;
+ case TOK822_DOMLIT:
+ VSTRING_ADDCH(vp, '[');
+ tok822_copy_quoted(vp, vstring_str(tp->vstr), "\\\r\n");
+ VSTRING_ADDCH(vp, ']');
+ break;
+ case TOK822_STARTGRP:
+ VSTRING_ADDCH(vp, ':');
+ break;
+ case '<':
+ if (tp->next && tp->next->type == '>') {
+ addr = tp;
+ addr_len = 0;
+ }
+ VSTRING_ADDCH(vp, '<');
+ break;
+ default:
+ if (tp->type >= TOK822_MINTOK)
+ msg_panic("tok822_externalize: unknown operator %d", tp->type);
+ VSTRING_ADDCH(vp, tp->type);
+ }
+ if (tok822_append_space(tp))
+ VSTRING_ADDCH(vp, ' ');
+ }
+ if (flags & TOK822_STR_TRNC)
+ ENFORCE_NONADDR_LENGTH;
+
+ if (flags & TOK822_STR_TERM)
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* tok822_copy_quoted - copy a string while quoting */
+
+static void tok822_copy_quoted(VSTRING *vp, char *str, char *quote_set)
+{
+ int ch;
+
+ while ((ch = *(unsigned char *) str++) != 0) {
+ if (strchr(quote_set, ch))
+ VSTRING_ADDCH(vp, '\\');
+ VSTRING_ADDCH(vp, ch);
+ }
+}
+
+/* tok822_append_space - see if space is needed after this token */
+
+static int tok822_append_space(TOK822 *tp)
+{
+ TOK822 *next;
+
+ if (tp == 0 || (next = tp->next) == 0 || tp->owner != 0)
+ return (0);
+ if (tp->type == ',' || tp->type == TOK822_STARTGRP || next->type == '<')
+ return (1);
+
+#define NON_OPERATOR(x) \
+ (x->type == TOK822_ATOM || x->type == TOK822_QSTRING \
+ || x->type == TOK822_COMMENT || x->type == TOK822_DOMLIT \
+ || x->type == TOK822_ADDR)
+
+ return (NON_OPERATOR(tp) && NON_OPERATOR(next));
+}
+
+/* tok822_scan_limit - tokenize string */
+
+TOK822 *tok822_scan_limit(const char *str, TOK822 **tailp, int tok_count_limit)
+{
+ TOK822 *head = 0;
+ TOK822 *tail = 0;
+ TOK822 *tp;
+ int ch;
+ int tok_count = 0;
+
+ /*
+ * XXX 2822 new feature: Section 4.1 allows "." to appear in a phrase (to
+ * allow for forms such as: Johnny B. Goode <johhny@domain.org>. I cannot
+ * handle that at the tokenizer level - it is not context sensitive. And
+ * to fix this at the parser level requires radical changes to preserve
+ * white space as part of the token stream. Thanks a lot, people.
+ */
+ while ((ch = *(unsigned char *) str++) != 0) {
+ if (IS_SPACE_TAB_CR_LF(ch))
+ continue;
+ if (ch == '(') {
+ tp = tok822_alloc(TOK822_COMMENT, (char *) 0);
+ str = tok822_comment(tp, str);
+ } else if (ch == '[') {
+ tp = tok822_alloc(TOK822_DOMLIT, (char *) 0);
+ COLLECT_SKIP_LAST(tp, str, ch, ch != ']');
+ } else if (ch == '"') {
+ tp = tok822_alloc(TOK822_QSTRING, (char *) 0);
+ COLLECT_SKIP_LAST(tp, str, ch, ch != '"');
+ } else if (ch != '\\' && strchr(tok822_opchar, ch)) {
+ tp = tok822_alloc(ch, (char *) 0);
+ } else {
+ tp = tok822_alloc(TOK822_ATOM, (char *) 0);
+ str -= 1; /* \ may be first */
+ COLLECT(tp, str, ch, !IS_SPACE_TAB_CR_LF(ch) && !strchr(tok822_opchar, ch));
+ tok822_quote_atom(tp);
+ }
+ if (head == 0) {
+ head = tail = tp;
+ while (tail->next)
+ tail = tail->next;
+ } else {
+ tail = tok822_append(tail, tp);
+ }
+ if (tok_count_limit > 0 && ++tok_count >= tok_count_limit)
+ break;
+ }
+ if (tailp)
+ *tailp = tail;
+ return (head);
+}
+
+/* tok822_parse_limit - translate external string to token tree */
+
+TOK822 *tok822_parse_limit(const char *str, int tok_count_limit)
+{
+ TOK822 *head;
+ TOK822 *tail;
+ TOK822 *right;
+ TOK822 *first_token;
+ TOK822 *last_token;
+ TOK822 *tp;
+ int state;
+
+ /*
+ * First, tokenize the string, from left to right. We are not allowed to
+ * throw away any information that we do not understand. With a flat
+ * token list that contains all tokens, we can always convert back to
+ * string form.
+ */
+ if ((first_token = tok822_scan_limit(str, &last_token, tok_count_limit)) == 0)
+ return (0);
+
+ /*
+ * For convenience, sandwich the token list between two sentinel tokens.
+ */
+#define GLUE(left,rite) { left->next = rite; rite->prev = left; }
+
+ head = tok822_alloc(0, (char *) 0);
+ GLUE(head, first_token);
+ tail = tok822_alloc(0, (char *) 0);
+ GLUE(last_token, tail);
+
+ /*
+ * Next step is to transform the token list into a parse tree. This is
+ * done most conveniently from right to left. If there is something that
+ * we do not understand, just leave it alone, don't throw it away. The
+ * address information that we're looking for sits in-between the current
+ * node (tp) and the one called right. Add missing commas on the fly.
+ */
+ state = DO_WORD;
+ right = tail;
+ tp = tail->prev;
+ while (tp->type) {
+ if (tp->type == TOK822_COMMENT) { /* move comment to the side */
+ MOVE_COMMENT_AND_CONTINUE(tp, right);
+ } else if (tp->type == ';') { /* rh side of named group */
+ right = tok822_group(TOK822_ADDR, tp, right, ADD_COMMA);
+ state = DO_GROUP | DO_WORD;
+ } else if (tp->type == ':' && (state & DO_GROUP) != 0) {
+ tp->type = TOK822_STARTGRP;
+ (void) tok822_group(TOK822_ADDR, tp, right, NO_MISSING_COMMA);
+ SKIP(tp, tp->type != ',');
+ right = tp;
+ continue;
+ } else if (tp->type == '>') { /* rh side of <route> */
+ right = tok822_group(TOK822_ADDR, tp, right, ADD_COMMA);
+ SKIP_MOVE_COMMENT(tp, tp->type != '<', right);
+ (void) tok822_group(TOK822_ADDR, tp, right, NO_MISSING_COMMA);
+ SKIP(tp, tp->type > 0xff || strchr(">;,:", tp->type) == 0);
+ right = tp;
+ state |= DO_WORD;
+ continue;
+ } else if (tp->type == TOK822_ATOM || tp->type == TOK822_QSTRING
+ || tp->type == TOK822_DOMLIT) {
+ if ((state & DO_WORD) == 0)
+ right = tok822_group(TOK822_ADDR, tp, right, ADD_COMMA)->next;
+ state &= ~DO_WORD;
+ } else if (tp->type == ',') {
+ right = tok822_group(TOK822_ADDR, tp, right, NO_MISSING_COMMA);
+ state |= DO_WORD;
+ } else {
+ state |= DO_WORD;
+ }
+ tp = tp->prev;
+ }
+ (void) tok822_group(TOK822_ADDR, tp, right, NO_MISSING_COMMA);
+
+ /*
+ * Discard the sentinel tokens on the left and right extremes. Properly
+ * terminate the resulting list.
+ */
+ tp = (head->next != tail ? head->next : 0);
+ tok822_cut_before(head->next);
+ tok822_free(head);
+ tok822_cut_before(tail);
+ tok822_free(tail);
+ return (tp);
+}
+
+/* tok822_quote_atom - see if an atom needs quoting when externalized */
+
+static void tok822_quote_atom(TOK822 *tp)
+{
+ char *cp;
+ int ch;
+
+ /*
+ * RFC 822 expects 7-bit data. Rather than quoting every 8-bit character
+ * (and still passing it on as 8-bit data) we leave 8-bit data alone.
+ */
+ for (cp = vstring_str(tp->vstr); (ch = *(unsigned char *) cp) != 0; cp++) {
+ if ( /* !ISASCII(ch) || */ ch == ' '
+ || ISCNTRL(ch) || strchr(tok822_opchar, ch)) {
+ tp->type = TOK822_QSTRING;
+ break;
+ }
+ }
+}
+
+/* tok822_comment - tokenize comment */
+
+static const char *tok822_comment(TOK822 *tp, const char *str)
+{
+ int level = 1;
+ int ch;
+
+ /*
+ * XXX We cheat by storing comments in their external form. Otherwise it
+ * would be a royal pain to preserve \ before (. That would require a
+ * recursive parser; the easy to implement stack-based recursion would be
+ * too expensive.
+ */
+ VSTRING_ADDCH(tp->vstr, '(');
+
+ while ((ch = *(unsigned char *) str) != 0) {
+ VSTRING_ADDCH(tp->vstr, ch);
+ str++;
+ if (ch == '(') { /* comments can nest! */
+ level++;
+ } else if (ch == ')') {
+ if (--level == 0)
+ break;
+ } else if (ch == '\\') {
+ if ((ch = *(unsigned char *) str) == 0)
+ break;
+ VSTRING_ADDCH(tp->vstr, ch);
+ str++;
+ }
+ }
+ VSTRING_TERMINATE(tp->vstr);
+ return (str);
+}
+
+/* tok822_group - cluster a group of tokens */
+
+static TOK822 *tok822_group(int group_type, TOK822 *left, TOK822 *right, int sync_type)
+{
+ TOK822 *group;
+ TOK822 *sync;
+ TOK822 *first;
+
+ /*
+ * Cluster the tokens between left and right under their own parse tree
+ * node. Optionally insert a resync token.
+ */
+ if (left != right && (first = left->next) != right) {
+ tok822_cut_before(right);
+ tok822_cut_before(first);
+ group = tok822_alloc(group_type, (char *) 0);
+ tok822_sub_append(group, first);
+ tok822_append(left, group);
+ tok822_append(group, right);
+ if (sync_type) {
+ sync = tok822_alloc(sync_type, (char *) 0);
+ tok822_append(left, sync);
+ }
+ }
+ return (left);
+}
+
+/* tok822_scan_addr - convert external address string to address token */
+
+TOK822 *tok822_scan_addr(const char *addr)
+{
+ TOK822 *tree = tok822_alloc(TOK822_ADDR, (char *) 0);
+
+ tree->head = tok822_scan(addr, &tree->tail);
+ return (tree);
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <vstream.h>
+#include <readlline.h>
+
+/* tok822_print - display token */
+
+static void tok822_print(TOK822 *list, int indent)
+{
+ TOK822 *tp;
+
+ for (tp = list; tp; tp = tp->next) {
+ if (tp->type < TOK822_MINTOK) {
+ vstream_printf("%*s %s \"%c\"\n", indent, "", "OP", tp->type);
+ } else if (tp->type == TOK822_ADDR) {
+ vstream_printf("%*s %s\n", indent, "", "address");
+ tok822_print(tp->head, indent + 2);
+ } else if (tp->type == TOK822_STARTGRP) {
+ vstream_printf("%*s %s\n", indent, "", "group \":\"");
+ } else {
+ vstream_printf("%*s %s \"%s\"\n", indent, "",
+ tp->type == TOK822_COMMENT ? "comment" :
+ tp->type == TOK822_ATOM ? "atom" :
+ tp->type == TOK822_QSTRING ? "quoted string" :
+ tp->type == TOK822_DOMLIT ? "domain literal" :
+ tp->type == TOK822_ADDR ? "address" :
+ "unknown\n", vstring_str(tp->vstr));
+ }
+ }
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *vp = vstring_alloc(100);
+ TOK822 *list;
+ VSTRING *buf = vstring_alloc(100);
+
+#define TEST_TOKEN_LIMIT 20
+
+ while (readlline(buf, VSTREAM_IN, (int *) 0)) {
+ while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\n') {
+ vstring_end(buf)[-1] = 0;
+ vstring_truncate(buf, VSTRING_LEN(buf) - 1);
+ }
+ if (!isatty(vstream_fileno(VSTREAM_IN)))
+ vstream_printf(">>>%s<<<\n\n", vstring_str(buf));
+ list = tok822_parse_limit(vstring_str(buf), TEST_TOKEN_LIMIT);
+ vstream_printf("Parse tree:\n");
+ tok822_print(list, 0);
+ vstream_printf("\n");
+
+ vstream_printf("Internalized:\n%s\n\n",
+ vstring_str(tok822_internalize(vp, list, TOK822_STR_DEFL)));
+ vstream_fflush(VSTREAM_OUT);
+ vstream_printf("Externalized, no newlines inserted:\n%s\n\n",
+ vstring_str(tok822_externalize(vp, list,
+ TOK822_STR_DEFL | TOK822_STR_TRNC)));
+ vstream_fflush(VSTREAM_OUT);
+ vstream_printf("Externalized, newlines inserted:\n%s\n\n",
+ vstring_str(tok822_externalize(vp, list,
+ TOK822_STR_DEFL | TOK822_STR_LINE | TOK822_STR_TRNC)));
+ vstream_fflush(VSTREAM_OUT);
+ tok822_free_tree(list);
+ }
+ vstring_free(vp);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/global/tok822_parse.in b/src/global/tok822_parse.in
new file mode 100644
index 0000000..acb91db
--- /dev/null
+++ b/src/global/tok822_parse.in
@@ -0,0 +1,47 @@
+wietse@porcupine.org
+"wietse venema"@porcupine.org
+wietse@porcupine.org
+wietse @ porcupine.org
+"wietse venema"@porcupine.org ("wietse ) venema")
+"wietse venema" <wietse@porcupine.org>
+"wietse venema"@porcupine.org ( ("wietse ) venema") )
+"wietse venema"@porcupine.org
+wietse\ venema@porcupine.org
+"wietse venema
+wietse@[stuff
+wietse@["stuff]
+named group: foo@bar, baz@barf;
+wietse@foo (wietse
+ venema)
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+ .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ (yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
+<1111111111111111111111111111111111111111111111111111111111111111111111111111>
+ <2222222222222222222222222222222222222222222222222222222222222222222222222222>
+ <3333333333333333333333333333333333333333333333333333333333333333333333333333>
+ <4444444444444444444444444444444444444444444444444444444444444444444444444444>
+ <>
diff --git a/src/global/tok822_parse.ref b/src/global/tok822_parse.ref
new file mode 100644
index 0000000..e3a7ad5
--- /dev/null
+++ b/src/global/tok822_parse.ref
@@ -0,0 +1,417 @@
+>>>wietse@porcupine.org<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse@porcupine.org
+
+Externalized, no newlines inserted:
+wietse@porcupine.org
+
+Externalized, newlines inserted:
+wietse@porcupine.org
+
+>>>"wietse venema"@porcupine.org<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse venema@porcupine.org
+
+Externalized, no newlines inserted:
+"wietse venema"@porcupine.org
+
+Externalized, newlines inserted:
+"wietse venema"@porcupine.org
+
+>>>wietse@porcupine.org<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse@porcupine.org
+
+Externalized, no newlines inserted:
+wietse@porcupine.org
+
+Externalized, newlines inserted:
+wietse@porcupine.org
+
+>>>wietse @ porcupine.org<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse@porcupine.org
+
+Externalized, no newlines inserted:
+wietse@porcupine.org
+
+Externalized, newlines inserted:
+wietse@porcupine.org
+
+>>>"wietse venema"@porcupine.org ("wietse ) venema")<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+ OP ","
+ address
+ atom "venema"
+ comment "("wietse )"
+ OP ","
+ address
+ quoted string ")"
+
+Internalized:
+wietse venema@porcupine.org, venema ("wietse ), )
+
+Externalized, no newlines inserted:
+"wietse venema"@porcupine.org, venema ("wietse ), ")"
+
+Externalized, newlines inserted:
+"wietse venema"@porcupine.org,
+venema ("wietse ),
+")"
+
+>>>"wietse venema" <wietse@porcupine.org><<<
+
+Parse tree:
+ quoted string "wietse venema"
+ OP "<"
+ address
+ atom "wietse"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+ OP ">"
+
+Internalized:
+wietse venema <wietse@porcupine.org>
+
+Externalized, no newlines inserted:
+"wietse venema" <wietse@porcupine.org>
+
+Externalized, newlines inserted:
+"wietse venema" <wietse@porcupine.org>
+
+>>>"wietse venema"@porcupine.org ( ("wietse ) venema") )<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+ OP ")"
+ comment "( ("wietse ) venema")"
+
+Internalized:
+wietse venema@porcupine.org) ( ("wietse ) venema")
+
+Externalized, no newlines inserted:
+"wietse venema"@porcupine.org) ( ("wietse ) venema")
+
+Externalized, newlines inserted:
+"wietse venema"@porcupine.org) ( ("wietse ) venema")
+
+>>>"wietse venema"@porcupine.org<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse venema@porcupine.org
+
+Externalized, no newlines inserted:
+"wietse venema"@porcupine.org
+
+Externalized, newlines inserted:
+"wietse venema"@porcupine.org
+
+>>>wietse\ venema@porcupine.org<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+ OP "@"
+ atom "porcupine"
+ OP "."
+ atom "org"
+
+Internalized:
+wietse venema@porcupine.org
+
+Externalized, no newlines inserted:
+"wietse venema"@porcupine.org
+
+Externalized, newlines inserted:
+"wietse venema"@porcupine.org
+
+>>>"wietse venema<<<
+
+Parse tree:
+ address
+ quoted string "wietse venema"
+
+Internalized:
+wietse venema
+
+Externalized, no newlines inserted:
+"wietse venema"
+
+Externalized, newlines inserted:
+"wietse venema"
+
+>>>wietse@[stuff<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ domain literal "stuff"
+
+Internalized:
+wietse@[stuff]
+
+Externalized, no newlines inserted:
+wietse@[stuff]
+
+Externalized, newlines inserted:
+wietse@[stuff]
+
+>>>wietse@["stuff]<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ domain literal ""stuff"
+
+Internalized:
+wietse@["stuff]
+
+Externalized, no newlines inserted:
+wietse@["stuff]
+
+Externalized, newlines inserted:
+wietse@["stuff]
+
+>>>named group: foo@bar, baz@barf;<<<
+
+Parse tree:
+ atom "named"
+ atom "group"
+ group ":"
+ address
+ atom "foo"
+ OP "@"
+ atom "bar"
+ OP ","
+ address
+ atom "baz"
+ OP "@"
+ atom "barf"
+ OP ";"
+
+Internalized:
+named group: foo@bar, baz@barf;
+
+Externalized, no newlines inserted:
+named group: foo@bar, baz@barf;
+
+Externalized, newlines inserted:
+named group: foo@bar,
+baz@barf;
+
+>>>wietse@foo (wietse venema)<<<
+
+Parse tree:
+ address
+ atom "wietse"
+ OP "@"
+ atom "foo"
+ comment "(wietse venema)"
+
+Internalized:
+wietse@foo (wietse venema)
+
+Externalized, no newlines inserted:
+wietse@foo (wietse venema)
+
+Externalized, newlines inserted:
+wietse@foo (wietse venema)
+
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy<<<
+
+Parse tree:
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP "<"
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ">"
+ OP ","
+ address
+ atom "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
+ OP "."
+ atom "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
+ OP "."
+ atom "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
+ OP "."
+ atom "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
+
+Internalized:
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+
+Externalized, no newlines inserted:
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+
+Externalized, newlines inserted:
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+
+>>>"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz<<<
+
+Parse tree:
+ address
+ quoted string "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
+
+Internalized:
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+
+Externalized, no newlines inserted:
+"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
+
+Externalized, newlines inserted:
+"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
+
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy (yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)<<<
+
+Parse tree:
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP "<"
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ">"
+ OP ","
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ","
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ","
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ","
+ address
+ atom "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ OP ","
+ address
+ atom "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
+ comment "(yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)"
+
+Internalized:
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy (yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy)
+
+unknown: warning: stripping too many comments from address: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx...
+unknown: warning: stripping too many comments from address: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy (yyyyyyyyyyyyyyyyyyyy...
+Externalized, no newlines inserted:
+<xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, <yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy>
+
+unknown: warning: stripping too many comments from address: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx...
+unknown: warning: stripping too many comments from address: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy (yyyyyyyyyyyyyyyyyyyy...
+Externalized, newlines inserted:
+<xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>,
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+<yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy>
+
+>>>(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)<<<
+
+Parse tree:
+ comment "(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)"
+
+Internalized:
+(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
+
+Externalized, no newlines inserted:
+(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
+
+Externalized, newlines inserted:
+(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
+
+>>><1111111111111111111111111111111111111111111111111111111111111111111111111111> <2222222222222222222222222222222222222222222222222222222222222222222222222222> <3333333333333333333333333333333333333333333333333333333333333333333333333333> <4444444444444444444444444444444444444444444444444444444444444444444444444444> <><<<
+
+Parse tree:
+ OP "<"
+ address
+ atom "1111111111111111111111111111111111111111111111111111111111111111111111111111"
+ OP ">"
+ OP "<"
+ address
+ atom "2222222222222222222222222222222222222222222222222222222222222222222222222222"
+ OP ">"
+ OP "<"
+ address
+ atom "3333333333333333333333333333333333333333333333333333333333333333333333333333"
+ OP ">"
+ OP "<"
+ address
+ atom "4444444444444444444444444444444444444444444444444444444444444444444444444444"
+ OP ">"
+ OP "<"
+ OP ">"
+
+Internalized:
+<1111111111111111111111111111111111111111111111111111111111111111111111111111> <2222222222222222222222222222222222222222222222222222222222222222222222222222> <3333333333333333333333333333333333333333333333333333333333333333333333333333> <4444444444444444444444444444444444444444444444444444444444444444444444444444> <>
+
+unknown: warning: stripping too many comments from address: <1111111111111111111111111111111111111111111111111111111111111111111111111111> <22222222222222222222...
+Externalized, no newlines inserted:
+<>
+
+unknown: warning: stripping too many comments from address: <1111111111111111111111111111111111111111111111111111111111111111111111111111> <22222222222222222222...
+Externalized, newlines inserted:
+<>
+
diff --git a/src/global/tok822_resolve.c b/src/global/tok822_resolve.c
new file mode 100644
index 0000000..f178aaf
--- /dev/null
+++ b/src/global/tok822_resolve.c
@@ -0,0 +1,74 @@
+/*++
+/* NAME
+/* tok822_resolve 3
+/* SUMMARY
+/* address resolving, client interface
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* void tok822_resolve(addr, reply)
+/* TOK822 *addr;
+/* RESOLVE_REPLY *reply;
+/*
+/* void tok822_resolve_from(sender, addr, reply)
+/* const char *sender;
+/* TOK822 *addr;
+/* RESOLVE_REPLY *reply;
+/* DESCRIPTION
+/* tok822_resolve() takes an address token tree and finds out the
+/* transport to deliver via, the next-hop host on that transport,
+/* and the recipient relative to that host.
+/*
+/* tok822_resolve_from() allows the caller to specify sender context
+/* that will be used to look up sender-dependent relayhost information.
+/* SEE ALSO
+/* resolve_clnt(3) basic resolver client interface
+/* 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 <vstring.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include "resolve_clnt.h"
+#include "tok822.h"
+
+/* tok822_resolve - address rewriting interface */
+
+void tok822_resolve_from(const char *sender, TOK822 *addr,
+ RESOLVE_REPLY *reply)
+{
+ VSTRING *intern_form = vstring_alloc(100);
+
+ if (addr->type != TOK822_ADDR)
+ msg_panic("tok822_resolve: non-address token type: %d", addr->type);
+
+ /*
+ * Internalize the token tree and ship it to the resolve service.
+ * Shipping string forms is much simpler than shipping parse trees.
+ */
+ tok822_internalize(intern_form, addr->head, TOK822_STR_DEFL);
+ resolve_clnt_query_from(sender, vstring_str(intern_form), reply);
+ if (msg_verbose)
+ msg_info("tok822_resolve: from=%s addr=%s -> chan=%s, host=%s, rcpt=%s",
+ sender,
+ vstring_str(intern_form), vstring_str(reply->transport),
+ vstring_str(reply->nexthop), vstring_str(reply->recipient));
+
+ vstring_free(intern_form);
+}
diff --git a/src/global/tok822_rewrite.c b/src/global/tok822_rewrite.c
new file mode 100644
index 0000000..fd52abb
--- /dev/null
+++ b/src/global/tok822_rewrite.c
@@ -0,0 +1,68 @@
+/*++
+/* NAME
+/* tok822_rewrite 3
+/* SUMMARY
+/* address rewriting, client interface
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* TOK822 *tok822_rewrite(addr, how)
+/* TOK822 *addr;
+/* char *how;
+/* DESCRIPTION
+/* tok822_rewrite() takes an address token tree and transforms
+/* it according to the rule set specified via \fIhow\fR. The
+/* result is the \fIaddr\fR argument.
+/* 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 <vstring.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include "rewrite_clnt.h"
+#include "tok822.h"
+
+/* tok822_rewrite - address rewriting interface */
+
+TOK822 *tok822_rewrite(TOK822 *addr, const char *how)
+{
+ VSTRING *input_ext_form = vstring_alloc(100);
+ VSTRING *canon_ext_form = vstring_alloc(100);
+
+ if (addr->type != TOK822_ADDR)
+ msg_panic("tok822_rewrite: non-address token type: %d", addr->type);
+
+ /*
+ * Externalize the token tree, ship it to the rewrite service, and parse
+ * the result. Shipping external form is much simpler than shipping parse
+ * trees.
+ */
+ tok822_externalize(input_ext_form, addr->head, TOK822_STR_DEFL);
+ if (msg_verbose)
+ msg_info("tok822_rewrite: input: %s", vstring_str(input_ext_form));
+ rewrite_clnt(how, vstring_str(input_ext_form), canon_ext_form);
+ if (msg_verbose)
+ msg_info("tok822_rewrite: result: %s", vstring_str(canon_ext_form));
+ tok822_free_tree(addr->head);
+ addr->head = tok822_scan(vstring_str(canon_ext_form), &addr->tail);
+
+ vstring_free(input_ext_form);
+ vstring_free(canon_ext_form);
+ return (addr);
+}
diff --git a/src/global/tok822_tree.c b/src/global/tok822_tree.c
new file mode 100644
index 0000000..a66cf11
--- /dev/null
+++ b/src/global/tok822_tree.c
@@ -0,0 +1,310 @@
+/*++
+/* NAME
+/* tok822_tree 3
+/* SUMMARY
+/* assorted token tree operators
+/* SYNOPSIS
+/* #include <tok822.h>
+/*
+/* TOK822 *tok822_append(t1, t2)
+/* TOK822 *t1;
+/* TOK822 *t2;
+/*
+/* TOK822 *tok822_prepend(t1, t2)
+/* TOK822 *t1;
+/* TOK822 *t2;
+/*
+/* TOK822 *tok822_cut_before(tp)
+/* TOK822 *tp;
+/*
+/* TOK822 *tok822_cut_after(tp)
+/* TOK822 *tp;
+/*
+/* TOK822 *tok822_unlink(tp)
+/* TOK822 *tp;
+/*
+/* TOK822 *tok822_sub_append(t1, t2)
+/* TOK822 *t1;
+/*
+/* TOK822 *tok822_sub_prepend(t1, t2)
+/* TOK822 *t1;
+/* TOK822 *t2;
+/*
+/* TOK822 *tok822_sub_keep_before(t1, t2)
+/* TOK822 *tp;
+/*
+/* TOK822 *tok822_sub_keep_after(t1, t2)
+/* TOK822 *tp;
+/*
+/* int tok822_apply(list, type, action)
+/* TOK822 *list;
+/* int type;
+/* int (*action)(TOK822 *token);
+/*
+/* int tok822_grep(list, type)
+/* TOK822 *list;
+/* int type;
+/*
+/* TOK822 *tok822_free_tree(tp)
+/* TOK822 *tp;
+/* DESCRIPTION
+/* This module manipulates trees of token structures. Trees grow
+/* to the right or downwards. Operators are provided to cut and
+/* combine trees in various manners.
+/*
+/* tok822_append() appends the token list \fIt2\fR to the right
+/* of token list \fIt1\fR. The result is the last token in \fIt2\fR.
+/* The appended list inherits the \fIowner\fR attribute from \fIt1\fR.
+/* The parent node, if any, is not updated.
+/*
+/* tok822_prepend() inserts the token list \fIt2\fR to the left
+/* of token \fIt1\fR. The result is the last token in \fIt2\fR.
+/* The appended list inherits the \fIowner\fR attribute from \fIt1\fR.
+/* The parent node, if any, is not updated.
+/*
+/* tok822_cut_before() breaks a token list on the left side of \fItp\fR
+/* and returns the left neighbor of \tItp\fR.
+/*
+/* tok822_cut_after() breaks a token list on the right side of \fItp\fR
+/* and returns the right neighbor of \tItp\fR.
+/*
+/* tok822_unlink() disconnects a token from its left and right neighbors
+/* and returns the left neighbor of \tItp\fR.
+/*
+/* tok822_sub_append() appends the token list \fIt2\fR to the right
+/* of the token list below \fIt1\fR. The result is the last token
+/* in \fIt2\fR.
+/*
+/* tok822_sub_prepend() prepends the token list \fIt2\fR to the left
+/* of the token list below \fIt1\fR. The result is the last token
+/* in \fIt2\fR.
+/*
+/* tok822_sub_keep_before() keeps the token list below \fIt1\fR on the
+/* left side of \fIt2\fR and returns the tail of the disconnected list.
+/*
+/* tok822_sub_keep_after() keeps the token list below \fIt1\fR on the
+/* right side of \fIt2\fR and returns the head of the disconnected list.
+/*
+/* tok822_apply() applies the specified action routine to all tokens
+/* matching the given type (to all tokens when a null type is given).
+/* Processing terminates when the action routine returns a non-zero
+/* value. The result is the last result returned by the action routine.
+/* tok822_apply() does not traverse vertical links.
+/*
+/* tok822_grep() returns a null-terminated array of pointers to tokens
+/* matching the specified type (all tokens when a null type is given).
+/* tok822_grep() does not traverse vertical links. The result must be
+/* given to myfree().
+/*
+/* tok822_free_tree() destroys a tree of token structures and
+/* conveniently returns a null pointer.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include "tok822.h"
+
+/* tok822_append - insert token list, return end of inserted list */
+
+TOK822 *tok822_append(TOK822 *t1, TOK822 *t2)
+{
+ TOK822 *next = t1->next;
+
+ t1->next = t2;
+ t2->prev = t1;
+
+ t2->owner = t1->owner;
+ while (t2->next)
+ (t2 = t2->next)->owner = t1->owner;
+
+ t2->next = next;
+ if (next)
+ next->prev = t2;
+ return (t2);
+}
+
+/* tok822_prepend - insert token list, return end of inserted list */
+
+TOK822 *tok822_prepend(TOK822 *t1, TOK822 *t2)
+{
+ TOK822 *prev = t1->prev;
+
+ if (prev)
+ prev->next = t2;
+ t2->prev = prev;
+
+ t2->owner = t1->owner;
+ while (t2->next)
+ (t2 = t2->next)->owner = t1->owner;
+
+ t2->next = t1;
+ t1->prev = t2;
+ return (t2);
+}
+
+/* tok822_cut_before - split list before token, return predecessor token */
+
+TOK822 *tok822_cut_before(TOK822 *tp)
+{
+ TOK822 *prev = tp->prev;
+
+ if (prev) {
+ prev->next = 0;
+ tp->prev = 0;
+ }
+ return (prev);
+}
+
+/* tok822_cut_after - split list after token, return successor token */
+
+TOK822 *tok822_cut_after(TOK822 *tp)
+{
+ TOK822 *next = tp->next;
+
+ if (next) {
+ next->prev = 0;
+ tp->next = 0;
+ }
+ return (next);
+}
+
+/* tok822_unlink - take token away from list, return predecessor token */
+
+TOK822 *tok822_unlink(TOK822 *tp)
+{
+ TOK822 *prev = tp->prev;
+ TOK822 *next = tp->next;
+
+ if (prev)
+ prev->next = next;
+ if (next)
+ next->prev = prev;
+ tp->prev = tp->next = 0;
+ return (prev);
+}
+
+/* tok822_sub_append - append sublist, return end of appended list */
+
+TOK822 *tok822_sub_append(TOK822 *t1, TOK822 *t2)
+{
+ if (t1->head) {
+ return (t1->tail = tok822_append(t1->tail, t2));
+ } else {
+ t1->head = t2;
+ t2->owner = t1;
+ while (t2->next)
+ (t2 = t2->next)->owner = t1;
+ return (t1->tail = t2);
+ }
+}
+
+/* tok822_sub_prepend - prepend sublist, return end of prepended list */
+
+TOK822 *tok822_sub_prepend(TOK822 *t1, TOK822 *t2)
+{
+ TOK822 *tp;
+
+ if (t1->head) {
+ tp = tok822_prepend(t1->head, t2);
+ t1->head = t2;
+ return (tp);
+ } else {
+ t1->head = t2;
+ t2->owner = t1;
+ while (t2->next)
+ (t2 = t2->next)->owner = t1;
+ return (t1->tail = t2);
+ }
+}
+
+/* tok822_sub_keep_before - cut sublist, return tail of disconnected list */
+
+TOK822 *tok822_sub_keep_before(TOK822 *t1, TOK822 *t2)
+{
+ TOK822 *tail = t1->tail;
+
+ if ((t1->tail = tok822_cut_before(t2)) == 0)
+ t1->head = 0;
+ return (tail);
+}
+
+/* tok822_sub_keep_after - cut sublist, return head of disconnected list */
+
+TOK822 *tok822_sub_keep_after(TOK822 *t1, TOK822 *t2)
+{
+ TOK822 *head = t1->head;
+
+ if ((t1->head = tok822_cut_after(t2)) == 0)
+ t1->tail = 0;
+ return (head);
+}
+
+/* tok822_free_tree - destroy token tree */
+
+TOK822 *tok822_free_tree(TOK822 *tp)
+{
+ TOK822 *next;
+
+ for (/* void */; tp != 0; tp = next) {
+ if (tp->head)
+ tok822_free_tree(tp->head);
+ next = tp->next;
+ tok822_free(tp);
+ }
+ return (0);
+}
+
+/* tok822_apply - apply action to specified tokens */
+
+int tok822_apply(TOK822 *tree, int type, TOK822_ACTION action)
+{
+ TOK822 *tp;
+ int result = 0;
+
+ for (tp = tree; tp; tp = tp->next) {
+ if (type == 0 || tp->type == type)
+ if ((result = action(tp)) != 0)
+ break;
+ }
+ return (result);
+}
+
+/* tok822_grep - list matching tokens */
+
+TOK822 **tok822_grep(TOK822 *tree, int type)
+{
+ TOK822 **list;
+ TOK822 *tp;
+ int count;
+
+ for (count = 0, tp = tree; tp; tp = tp->next)
+ if (type == 0 || tp->type == type)
+ count++;
+
+ list = (TOK822 **) mymalloc(sizeof(*list) * (count + 1));
+
+ for (count = 0, tp = tree; tp; tp = tp->next)
+ if (type == 0 || tp->type == type)
+ list[count++] = tp;
+
+ list[count] = 0;
+ return (list);
+}
diff --git a/src/global/trace.c b/src/global/trace.c
new file mode 100644
index 0000000..d826a64
--- /dev/null
+++ b/src/global/trace.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* trace 3
+/* SUMMARY
+/* user requested delivery tracing
+/* SYNOPSIS
+/* #include <trace.h>
+/*
+/* int trace_append(flags, id, stats, rcpt, relay, dsn)
+/* int flags;
+/* const char *id;
+/* MSG_STATS *stats;
+/* RECIPIENT *rcpt;
+/* const char *relay;
+/* DSN *dsn;
+/*
+/* int trace_flush(flags, queue, id, encoding, sender,
+/* dsn_envid, dsn_ret)
+/* int flags;
+/* const char *queue;
+/* const char *id;
+/* const char *encoding;
+/* const char *sender;
+/* const char *dsn_envid;
+/* int dsn_ret;
+/* DESCRIPTION
+/* trace_append() updates the message delivery record that is
+/* mailed back to the originator. In case of a trace-only
+/* message, the recipient status is also written to the
+/* mailer logfile.
+/*
+/* trace_flush() returns the specified message to the specified
+/* sender, including the message delivery record log that was built
+/* with vtrace_append().
+/*
+/* Arguments:
+/* .IP flags
+/* The bitwise OR of zero or more of the following (specify
+/* BOUNCE_FLAG_NONE to request no special processing):
+/* .RS
+/* .IP BOUNCE_FLAG_CLEAN
+/* Delete the logfile in case of an error (as in: pretend
+/* that we never even tried to deliver this message).
+/* .RE
+/* .IP queue
+/* The message queue name of the original message file.
+/* .IP id
+/* The message queue id.
+/* .IP encoding
+/* The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
+/* .IP sender
+/* The sender envelope address.
+/* .IP dsn_envid
+/* Optional DSN envelope ID.
+/* .IP dsn_ret
+/* Optional DSN return full/headers option.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP rcpt
+/* Recipient information. See recipient_list(3).
+/* .IP relay
+/* The host we sent the mail to.
+/* .IP dsn
+/* Delivery status information. See dsn(3).
+/* DIAGNOSTICS
+/* A non-zero result means the operation failed.
+/*
+/* Fatal: out of memory.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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 <stdio.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <log_adhoc.h>
+#include <rcpt_print.h>
+#include <dsn_print.h>
+#include <trace.h>
+
+/* trace_append - append to message delivery record */
+
+int trace_append(int flags, const char *id, MSG_STATS *stats,
+ RECIPIENT *rcpt, const char *relay,
+ DSN *dsn)
+{
+ VSTRING *why = vstring_alloc(100);
+ DSN my_dsn = *dsn;
+ int req_stat;
+
+ /*
+ * User-requested address verification, verbose delivery, or DSN SUCCESS
+ * notification.
+ */
+ if (strcmp(relay, NO_RELAY_AGENT) != 0)
+ vstring_sprintf(why, "delivery via %s: ", relay);
+ vstring_strcat(why, my_dsn.reason);
+ my_dsn.reason = vstring_str(why);
+
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_trace_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt),
+ SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s: %s service failure", id, var_trace_service);
+ req_stat = -1;
+ } else {
+ if (flags & DEL_REQ_FLAG_USR_VRFY)
+ log_adhoc(id, stats, rcpt, relay, dsn, my_dsn.action);
+ req_stat = 0;
+ }
+ vstring_free(why);
+ return (req_stat);
+}
+
+/* trace_flush - deliver delivery record to the sender */
+
+int trace_flush(int flags, const char *queue, const char *id,
+ const char *encoding, const char *sender,
+ const char *dsn_envid, int dsn_ret)
+{
+ if (mail_command_client(MAIL_CLASS_PRIVATE, var_trace_service,
+ MAIL_ATTR_PROTO_BOUNCE,
+ SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_TRACE),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
+ ATTR_TYPE_END) == 0) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
diff --git a/src/global/trace.h b/src/global/trace.h
new file mode 100644
index 0000000..2013601
--- /dev/null
+++ b/src/global/trace.h
@@ -0,0 +1,38 @@
+#ifndef _TRACE_H_INCLUDED_
+#define _TRACE_H_INCLUDED_
+
+/*++
+/* NAME
+/* trace 3h
+/* SUMMARY
+/* update user message delivery record
+/* SYNOPSIS
+/* #include <trace.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <bounce.h>
+
+ /*
+ * External interface.
+ */
+extern int trace_append(int, const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *);
+extern int trace_flush(int, const char *, const char *, const char *,
+ const char *, const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/user_acl.c b/src/global/user_acl.c
new file mode 100644
index 0000000..a79b7d5
--- /dev/null
+++ b/src/global/user_acl.c
@@ -0,0 +1,118 @@
+/*++
+/* NAME
+/* user_acl 3
+/* SUMMARY
+/* user name based access control
+/* SYNOPSIS
+/* #include <user_acl.h>
+/*
+/* const char *check_user_acl_byuid(pname, acl, uid)
+/* const char *pname;
+/* const char *acl;
+/* uid_t uid;
+/* DESCRIPTION
+/* check_user_acl_byuid() converts the given uid into a user
+/* name, and checks the result against a user name matchlist.
+/* If the uid cannot be resolved to a user name, "unknown"
+/* is used as the lookup key instead.
+/* The result is NULL on success, the username upon failure.
+/* The error result lives in static storage and must be saved
+/* if it is to be used to across multiple check_user_acl_byuid()
+/* calls.
+/*
+/* Arguments:
+/* .IP pname
+/* The parameter name of the acl.
+/* .IP acl
+/* Authorized user name list suitable for input to string_list_init(3).
+/* .IP uid
+/* The uid to be checked against the access list.
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <dict_static.h>
+
+/* Global library. */
+
+#include <string_list.h>
+#include <mypwd.h>
+
+/* Application-specific. */
+
+#include "user_acl.h"
+
+/* check_user_acl_byuid - check user authorization */
+
+const char *check_user_acl_byuid(const char *pname, const char *acl, uid_t uid)
+{
+ struct mypasswd *mypwd;
+ STRING_LIST *list;
+ static VSTRING *who = 0;
+ int matched;
+ const char *name;
+
+ /*
+ * Optimize for the most common case. This also makes Postfix a little
+ * more robust in the face of local infrastructure failures. Note that we
+ * only need to match the "static:" substring, not the result value.
+ */
+ if (strncmp(acl, DICT_TYPE_STATIC ":", sizeof(DICT_TYPE_STATIC)) == 0)
+ return (0);
+
+ /*
+ * XXX: Substitute "unknown" for UIDs without username, so that
+ * static:anyone results in "permit" even when the uid is not found in
+ * the password file, and so that a pattern of !unknown can be used to
+ * block non-existent accounts.
+ *
+ * The alternative is to use the UID as a surrogate lookup key for
+ * non-existent accounts. There are several reasons why this is not a
+ * good idea. 1) An ACL with a numerical UID should work regardless of
+ * whether or not an account has a password file entry. Therefore we
+ * would always have search on the numerical UID whenever the username
+ * fails to produce a match. 2) The string-list infrastructure is not
+ * really suitable for mixing numerical and non-numerical user
+ * information, because the numerical match is done in a separate pass
+ * from the non-numerical match. This breaks when the ! operator is used.
+ *
+ * XXX To avoid waiting until the lookup completes (e.g., LDAP or NIS down)
+ * invoke mypwuid_err(), and either change the user_acl() API to
+ * propagate the error to the caller, or treat lookup errors as fatal.
+ */
+ if ((mypwd = mypwuid(uid)) == 0) {
+ name = "unknown";
+ } else {
+ name = mypwd->pw_name;
+ }
+
+ list = string_list_init(pname, MATCH_FLAG_NONE, acl);
+ if ((matched = string_list_match(list, name)) == 0) {
+ if (!who)
+ who = vstring_alloc(10);
+ vstring_strcpy(who, name);
+ }
+ string_list_free(list);
+ if (mypwd)
+ mypwfree(mypwd);
+
+ return (matched ? 0 : vstring_str(who));
+}
diff --git a/src/global/user_acl.h b/src/global/user_acl.h
new file mode 100644
index 0000000..4e36fbf
--- /dev/null
+++ b/src/global/user_acl.h
@@ -0,0 +1,39 @@
+#ifndef _USER_ACL_H_INCLUDED_
+#define _USER_ACL_H_INCLUDED_
+/*++
+/* NAME
+/* user_acl 3h
+/* SUMMARY
+/* Convert uid to username and check against given ACL.
+/* SYNOPSIS
+/* #include <user_acl.h>
+/*
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library
+ */
+#include <unistd.h> /* getuid()/geteuid() */
+#include <sys/types.h> /* uid_t */
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface
+ */
+extern const char *check_user_acl_byuid(const char *, const char *, uid_t);
+
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+#endif
diff --git a/src/global/uxtext.c b/src/global/uxtext.c
new file mode 100644
index 0000000..6dfe94c
--- /dev/null
+++ b/src/global/uxtext.c
@@ -0,0 +1,278 @@
+/*++
+/* NAME
+/* uxtext 3
+/* SUMMARY
+/* quote/unquote text, xtext style.
+/* SYNOPSIS
+/* #include <uxtext.h>
+/*
+/* VSTRING *uxtext_quote(quoted, unquoted, special)
+/* VSTRING *quoted;
+/* const char *unquoted;
+/* const char *special;
+/*
+/* VSTRING *uxtext_quote_append(unquoted, quoted, special)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/* const char *special;
+/*
+/* VSTRING *uxtext_unquote(unquoted, quoted)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/*
+/* VSTRING *uxtext_unquote_append(unquoted, quoted)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/* DESCRIPTION
+/* uxtext_quote() takes a null-terminated UTF8 string and
+/* replaces characters \, <33(10) and >126(10), as well as
+/* characters specified with "special" with \x{XX}, XX being
+/* a 2-6-digit uppercase hexadecimal equivalent.
+/*
+/* uxtext_quote_append() is like uxtext_quote(), but appends
+/* the conversion result to the result buffer.
+/*
+/* uxtext_unquote() performs the opposite transformation. This
+/* function understands lowercase, uppercase, and mixed case
+/* \x{XX...} sequences. The result value is the unquoted
+/* argument in case of success, a null pointer otherwise.
+/*
+/* uxtext_unquote_append() is like uxtext_unquote(), but appends
+/* the conversion result to the result buffer.
+/* BUGS
+/* This module cannot process null characters in data.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "uxtext.h"
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* uxtext_quote_append - append unquoted data to quoted data */
+
+VSTRING *uxtext_quote_append(VSTRING *quoted, const char *unquoted,
+ const char *special)
+{
+ unsigned const char *cp;
+ int ch;
+
+ for (cp = (unsigned const char *) unquoted; (ch = *cp) != 0; cp++) {
+ /* Fix 20140709: the '\' character must always be quoted. */
+ if (ch != '\\' && ch > 32 && ch < 127
+ && (*special == 0 || strchr(special, ch) == 0)) {
+ VSTRING_ADDCH(quoted, ch);
+ } else {
+
+ /*
+ * had RFC6533 been written like 6531 and 6532, this else clause
+ * would be one line long.
+ */
+ int unicode = 0;
+ int pick = 0;
+
+ if (ch < 0x80) {
+ //0000 0000 - 0000 007 F 0x xxxxxx
+ unicode = ch;
+ } else if ((ch & 0xe0) == 0xc0) {
+ //0000 0080 - 0000 07 FF 110 xxxxx 10 xxxxxx
+ unicode = (ch & 0x1f);
+ pick = 1;
+ } else if ((ch & 0xf0) == 0xe0) {
+ //0000 0800 - 0000 FFFF 1110 xxxx 10 xxxxxx 10 xxxxxx
+ unicode = (ch & 0x0f);
+ pick = 2;
+ } else if ((ch & 0xf8) == 0xf0) {
+ //0001 0000 - 001 F FFFF 11110 xxx 10 xxxxxx 10 xxxxxx 10 xxxxxx
+ unicode = (ch & 0x07);
+ pick = 3;
+ } else if ((ch & 0xfc) == 0xf8) {
+ //0020 0000 - 03 FF FFFF 111110 xx 10 xxxxxx 10 xxxxxx...10 xxxxxx
+ unicode = (ch & 0x03);
+ pick = 4;
+ } else if ((ch & 0xfe) == 0xfc) {
+ //0400 0000 - 7 FFF FFFF 1111110 x 10 xxxxxx...10 xxxxxx
+ unicode = (ch & 0x01);
+ pick = 5;
+ } else {
+ return (0);
+ }
+ while (pick > 0) {
+ ch = *++cp;
+ if ((ch & 0xc0) != 0x80)
+ return (0);
+ unicode = unicode << 6 | (ch & 0x3f);
+ pick--;
+ }
+ vstring_sprintf_append(quoted, "\\x{%02X}", unicode);
+ }
+ }
+ VSTRING_TERMINATE(quoted);
+ return (quoted);
+}
+
+/* uxtext_quote - unquoted data to quoted */
+
+VSTRING *uxtext_quote(VSTRING *quoted, const char *unquoted, const char *special)
+{
+ VSTRING_RESET(quoted);
+ uxtext_quote_append(quoted, unquoted, special);
+ return (quoted);
+}
+
+/* uxtext_unquote_append - quoted data to unquoted */
+
+VSTRING *uxtext_unquote_append(VSTRING *unquoted, const char *quoted)
+{
+ const unsigned char *cp;
+ int ch;
+
+ for (cp = (const unsigned char *) quoted; (ch = *cp) != 0; cp++) {
+ if (ch == '\\' && cp[1] == 'x' && cp[2] == '{') {
+ int unicode = 0;
+
+ cp += 2;
+ while ((ch = *++cp) != '}') {
+ if (ISDIGIT(ch))
+ unicode = (unicode << 4) + (ch - '0');
+ else if (ch >= 'a' && ch <= 'f')
+ unicode = (unicode << 4) + (ch - 'a' + 10);
+ else if (ch >= 'A' && ch <= 'F')
+ unicode = (unicode << 4) + (ch - 'A' + 10);
+ else
+ return (0); /* also covers the null
+ * terminator */
+ if (unicode > 0x10ffff)
+ return (0);
+ }
+
+ /*
+ * the following block is from
+ * https://github.com/aox/aox/blob/master/encodings/utf.cpp, with
+ * permission by the authors.
+ */
+ if (unicode < 0x80) {
+ VSTRING_ADDCH(unquoted, (char) unicode);
+ } else if (unicode < 0x800) {
+ VSTRING_ADDCH(unquoted, 0xc0 | ((char) (unicode >> 6)));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode & 0x3f)));
+ } else if (unicode < 0x10000) {
+ VSTRING_ADDCH(unquoted, 0xe0 | ((char) (unicode >> 12)));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 6) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode & 0x3f)));
+ } else if (unicode < 0x200000) {
+ VSTRING_ADDCH(unquoted, 0xf0 | ((char) (unicode >> 18)));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 12) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 6) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode & 0x3f)));
+ } else if (unicode < 0x4000000) {
+ VSTRING_ADDCH(unquoted, 0xf8 | ((char) (unicode >> 24)));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 18) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 12) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 6) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode & 0x3f)));
+ } else {
+ VSTRING_ADDCH(unquoted, 0xfc | ((char) (unicode >> 30)));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 24) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 18) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 12) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode >> 6) & 0x3f));
+ VSTRING_ADDCH(unquoted, 0x80 | ((char) (unicode & 0x3f)));
+ }
+ } else {
+ VSTRING_ADDCH(unquoted, ch);
+ }
+ }
+ VSTRING_TERMINATE(unquoted);
+ return (unquoted);
+}
+
+/* uxtext_unquote - quoted data to unquoted */
+
+VSTRING *uxtext_unquote(VSTRING *unquoted, const char *quoted)
+{
+ VSTRING_RESET(unquoted);
+ return (uxtext_unquote_append(unquoted, quoted) ? unquoted : 0);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to quoted and back.
+ */
+#include <vstream.h>
+
+#define BUFLEN 1024
+
+static ssize_t read_buf(VSTREAM *fp, VSTRING *buf)
+{
+ ssize_t len;
+
+ len = vstream_fread_buf(fp, buf, BUFLEN);
+ VSTRING_TERMINATE(buf);
+ return (len);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *unquoted = vstring_alloc(BUFLEN);
+ VSTRING *quoted = vstring_alloc(100);
+ ssize_t len;
+
+ /*
+ * Negative tests.
+ */
+ if (uxtext_unquote(unquoted, "\\x{x1}") != 0)
+ msg_warn("undetected error pattern 1");
+ if (uxtext_unquote(unquoted, "\\x{2x}") != 0)
+ msg_warn("undetected error pattern 2");
+ if (uxtext_unquote(unquoted, "\\x{33") != 0)
+ msg_warn("undetected error pattern 3");
+
+ /*
+ * Positive tests.
+ */
+ while ((len = read_buf(VSTREAM_IN, unquoted)) > 0) {
+ uxtext_quote(quoted, STR(unquoted), "+=");
+ if (uxtext_unquote(unquoted, STR(quoted)) == 0)
+ msg_fatal("bad input: %.100s", STR(quoted));
+ if (LEN(unquoted) != len)
+ msg_fatal("len %ld != unquoted len %ld",
+ (long) len, (long) LEN(unquoted));
+ if (vstream_fwrite(VSTREAM_OUT, STR(unquoted), LEN(unquoted)) != LEN(unquoted))
+ msg_fatal("write error: %m");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(unquoted);
+ vstring_free(quoted);
+ return (0);
+}
+
+#endif
diff --git a/src/global/uxtext.h b/src/global/uxtext.h
new file mode 100644
index 0000000..4c6824b
--- /dev/null
+++ b/src/global/uxtext.h
@@ -0,0 +1,40 @@
+#ifndef _UXTEXT_H_INCLUDED_
+#define _UXTEXT_H_INCLUDED_
+
+/*++
+/* NAME
+/* uxtext 3h
+/* SUMMARY
+/* quote/unquote text, RFC 6533 style.
+/* SYNOPSIS
+/* #include <uxtext.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *uxtext_quote(VSTRING *, const char *, const char *);
+extern VSTRING *uxtext_quote_append(VSTRING *, const char *, const char *);
+extern VSTRING *uxtext_unquote(VSTRING *, const char *);
+extern VSTRING *uxtext_unquote_append(VSTRING *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/global/valid_mailhost_addr.c b/src/global/valid_mailhost_addr.c
new file mode 100644
index 0000000..79a7900
--- /dev/null
+++ b/src/global/valid_mailhost_addr.c
@@ -0,0 +1,152 @@
+/*++
+/* NAME
+/* valid_mailhost_addr 3
+/* SUMMARY
+/* mailhost address syntax validation
+/* SYNOPSIS
+/* #include <valid_mailhost_addr.h>
+/*
+/* const char *valid_mailhost_addr(name, gripe)
+/* const char *name;
+/* int gripe;
+/*
+/* int valid_mailhost_literal(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/* DESCRIPTION
+/* valid_mailhost_addr() requires that the input is a valid
+/* RFC 2821 string representation of an IPv4 or IPv6 network
+/* address. A valid IPv4 address is in dotted quad decimal
+/* form. A valid IPv6 address includes the "IPV6:" prefix as
+/* required by RFC 2821, and is in valid hexadecimal form or
+/* in valid IPv4-in-IPv6 form. The result value is the bare
+/* address in the input argument (i.e. text after "IPV6:"
+/* prefix, if any) in case of success, a null pointer in case
+/* of failure.
+/*
+/* valid_mailhost_literal() requires an address enclosed in
+/* []. The result is non-zero in case of success, zero in
+/* case of failure.
+/*
+/* These routines operate silently unless the gripe parameter
+/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE
+/* provide suitable constants.
+/*
+/* The IPV6_COL macro defines the "IPv6:" prefix.
+/* DIAGNOSTICS
+/* Warnings are logged with msg_warn().
+/* SEE ALSO
+/* valid_hostname(3)
+/* RFC 952, RFC 1123, RFC 1035, RFC 2821
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+
+/* Global library. */
+
+#include <valid_mailhost_addr.h>
+
+/* Application-specific. */
+
+#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1)
+#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0)
+#define SKIP_IPV6_COL(str) (HAS_IPV6_COL(str) ? (str) + IPV6_COL_LEN : (str))
+
+/* valid_mailhost_addr - validate RFC 2821 numerical address form */
+
+const char *valid_mailhost_addr(const char *addr, int gripe)
+{
+ const char *bare_addr;
+
+ bare_addr = SKIP_IPV6_COL(addr);
+ return ((bare_addr != addr ? valid_ipv6_hostaddr : valid_ipv4_hostaddr)
+ (bare_addr, gripe) ? bare_addr : 0);
+}
+
+/* valid_mailhost_literal - validate [RFC 2821 numerical address] form */
+
+int valid_mailhost_literal(const char *addr, int gripe)
+{
+ const char *myname = "valid_mailhost_literal";
+ MAI_HOSTADDR_STR hostaddr;
+ const char *last;
+ size_t address_bytes;
+
+ if (*addr != '[') {
+ if (gripe)
+ msg_warn("%s: '[' expected at start: %.100s", myname, addr);
+ return (0);
+ }
+ if ((last = strchr(addr, ']')) == 0) {
+ if (gripe)
+ msg_warn("%s: ']' expected at end: %.100s", myname, addr);
+ return (0);
+ }
+ if (last[1]) {
+ if (gripe)
+ msg_warn("%s: unexpected text after ']': %.100s", myname, addr);
+ return (0);
+ }
+ if ((address_bytes = last - addr - 1) >= sizeof(hostaddr.buf)) {
+ if (gripe)
+ msg_warn("%s: too much text: %.100s", myname, addr);
+ return (0);
+ }
+ strncpy(hostaddr.buf, addr + 1, address_bytes);
+ hostaddr.buf[address_bytes] = 0;
+ return (valid_mailhost_addr(hostaddr.buf, gripe) != 0);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - reads hostnames from stdin, reports invalid hostnames to
+ * stderr.
+ */
+#include <stdlib.h>
+
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ msg_info("testing: \"%s\"", vstring_str(buffer));
+ if (vstring_str(buffer)[0] == '[')
+ valid_mailhost_literal(vstring_str(buffer), DO_GRIPE);
+ else
+ valid_mailhost_addr(vstring_str(buffer), DO_GRIPE);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/global/valid_mailhost_addr.h b/src/global/valid_mailhost_addr.h
new file mode 100644
index 0000000..95630ae
--- /dev/null
+++ b/src/global/valid_mailhost_addr.h
@@ -0,0 +1,38 @@
+#ifndef _VALID_MAILHOST_ADDR_H_INCLUDED_
+#define _VALID_MAILHOST_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_mailhost_addr 3h
+/* SUMMARY
+/* mailhost address syntax validation
+/* SYNOPSIS
+/* #include <valid_mailhost_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <valid_hostname.h>
+
+ /*
+ * External interface
+ */
+#define IPV6_COL "IPv6:" /* RFC 2821 */
+
+extern const char *valid_mailhost_addr(const char *, int);
+extern int valid_mailhost_literal(const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/verify.c b/src/global/verify.c
new file mode 100644
index 0000000..2ce091a
--- /dev/null
+++ b/src/global/verify.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* verify 3
+/* SUMMARY
+/* update verify database
+/* SYNOPSIS
+/* #include <verify.h>
+/*
+/* int verify_append(queue_id, stats, recipient, relay, dsn,
+/* verify_status)
+/* const char *queue_id;
+/* MSG_STATS *stats;
+/* RECIPIENT *recipient;
+/* const char *relay;
+/* DSN *dsn;
+/* int verify_status;
+/* DESCRIPTION
+/* This module implements an impedance adaptor between the
+/* verify_clnt interface and the interface expected by the
+/* bounce/defer/sent modules.
+/*
+/* verify_append() updates the address verification database
+/* and logs the action to the mailer logfile.
+/*
+/* Arguments:
+/* .IP queue_id
+/* The message queue id.
+/* .IP stats
+/* Time stamps from different message delivery stages
+/* and session reuse count.
+/* .IP recipient
+/* Recipient information. See recipient_list(3).
+/* .IP relay
+/* Name of the host we're talking to.
+/* .IP dsn
+/* Delivery status information. See dsn(3).
+/* The action is one of "deliverable" or "undeliverable".
+/* .IP verify_status
+/* One of the following recipient verification status codes:
+/* .RS
+/* .IP DEL_REQ_RCPT_STAT_OK
+/* Successful delivery.
+/* .IP DEL_REQ_RCPT_STAT_DEFER
+/* Temporary delivery error.
+/* .IP DEL_REQ_RCPT_STAT_BOUNCE
+/* Hard delivery error.
+/* .RE
+/* DIAGNOSTICS
+/* A non-zero result means the operation failed.
+/*
+/* Fatal: out of memory.
+/* BUGS
+/* Should be replaced by routines with an attribute-value based
+/* interface instead of an interface that uses a rigid argument list.
+/* 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 <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <verify_clnt.h>
+#include <log_adhoc.h>
+#include <verify.h>
+
+/* verify_append - update address verification database */
+
+int verify_append(const char *queue_id, MSG_STATS *stats,
+ RECIPIENT *recipient, const char *relay,
+ DSN *dsn, int vrfy_stat)
+{
+ int req_stat;
+ DSN my_dsn = *dsn;
+
+ /*
+ * Impedance adaptor between bounce/defer/sent and verify_clnt.
+ *
+ * XXX No DSN check; this routine is called from bounce/defer/sent, which
+ * know what the DSN initial digit should look like.
+ *
+ * XXX vrfy_stat is completely redundant because of dsn.
+ */
+ if (var_verify_neg_cache || vrfy_stat == DEL_RCPT_STAT_OK) {
+ if (recipient->orig_addr[0])
+ req_stat = verify_clnt_update(recipient->orig_addr, vrfy_stat,
+ my_dsn.reason);
+ else
+ req_stat = VRFY_STAT_OK;
+ /* Two verify updates for one verify request! */
+ if (req_stat == VRFY_STAT_OK
+ && strcmp(recipient->address, recipient->orig_addr) != 0)
+ req_stat = verify_clnt_update(recipient->address, vrfy_stat,
+ my_dsn.reason);
+ } else {
+ my_dsn.action = "undeliverable-but-not-cached";
+ req_stat = VRFY_STAT_OK;
+ }
+ if (req_stat == VRFY_STAT_OK) {
+ log_adhoc(queue_id, stats, recipient, relay, dsn, my_dsn.action);
+ req_stat = 0;
+ } else {
+ msg_warn("%s: %s service failure", queue_id, var_verify_service);
+ req_stat = -1;
+ }
+ return (req_stat);
+}
diff --git a/src/global/verify.h b/src/global/verify.h
new file mode 100644
index 0000000..250eb6d
--- /dev/null
+++ b/src/global/verify.h
@@ -0,0 +1,41 @@
+#ifndef _VERIFY_H_INCLUDED_
+#define _VERIFY_H_INCLUDED_
+
+/*++
+/* NAME
+/* verify 3h
+/* SUMMARY
+/* update user message delivery record
+/* SYNOPSIS
+/* #include <verify.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+
+ /*
+ * External interface.
+ */
+extern int verify_append(const char *, MSG_STATS *, RECIPIENT *,
+ const char *, DSN *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/verify_clnt.c b/src/global/verify_clnt.c
new file mode 100644
index 0000000..bda4eb0
--- /dev/null
+++ b/src/global/verify_clnt.c
@@ -0,0 +1,309 @@
+/*++
+/* NAME
+/* verify_clnt 3
+/* SUMMARY
+/* address verification client interface
+/* SYNOPSIS
+/* #include <verify_clnt.h>
+/*
+/* int verify_clnt_query(addr, status, why)
+/* const char *addr;
+/* int *status;
+/* VSTRING *why;
+/*
+/* int verify_clnt_update(addr, status, why)
+/* const char *addr;
+/* int status;
+/* const char *why;
+/* DESCRIPTION
+/* verify_clnt_query() requests information about the given address.
+/* The result value is one of the valid status values (see
+/* status description below).
+/* In all cases the \fBwhy\fR argument provides additional
+/* information.
+/*
+/* verify_clnt_update() requests that the status of the specified
+/* address be updated. The result status is DEL_REQ_RCPT_STAT_OK upon
+/* success, DEL_REQ_RCPT_STAT_DEFER upon failure.
+/*
+/* Arguments
+/* .IP addr
+/* The email address in question.
+/* .IP status
+/* One of the following status codes:
+/* .RS
+/* .IP DEL_REQ_RCPT_STAT_OK
+/* The mail system did not detect any problems.
+/* .IP DEL_REQ_RCPT_STAT_DEFER
+/* The status of the address is indeterminate.
+/* .IP DEL_REQ_RCPT_STAT_BOUNCE
+/* The address is permanently undeliverable.
+/* .RE
+/* .IP why
+/* textual description of the status.
+/* DIAGNOSTICS
+/* These functions return VRFY_STAT_OK in case of success,
+/* VRFY_STAT_BAD in case of a malformed request, and
+/* VRFY_STAT_FAIL when the operation failed.
+/* SEE ALSO
+/* verify(8) Postfix address verification server
+/* 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 <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <attr.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <clnt_stream.h>
+#include <verify_clnt.h>
+
+CLNT_STREAM *vrfy_clnt;
+
+/* verify_clnt_handshake - receive server protocol announcement */
+
+static int verify_clnt_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_VERIFY),
+ ATTR_TYPE_END));
+}
+
+/* verify_clnt_init - initialize */
+
+static void verify_clnt_init(void)
+{
+ if (vrfy_clnt != 0)
+ msg_panic("verify_clnt_init: multiple initialization");
+ vrfy_clnt = clnt_stream_create(MAIL_CLASS_PRIVATE, var_verify_service,
+ var_ipc_idle_limit, var_ipc_ttl_limit,
+ verify_clnt_handshake);
+}
+
+/* verify_clnt_query - request address verification status */
+
+int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why)
+{
+ VSTREAM *stream;
+ int request_status;
+ int count = 0;
+
+ /*
+ * Do client-server plumbing.
+ */
+ if (vrfy_clnt == 0)
+ verify_clnt_init();
+
+ /*
+ * Request status for this address.
+ */
+ for (;;) {
+ stream = clnt_stream_access(vrfy_clnt);
+ errno = 0;
+ count += 1;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, VRFY_REQ_QUERY),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream)
+ || attr_scan(stream, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &request_status),
+ RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, why),
+ ATTR_TYPE_END) != 3) {
+ if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ var_verify_service);
+ } else {
+ break;
+ }
+ sleep(1);
+ clnt_stream_recover(vrfy_clnt);
+ }
+ return (request_status);
+}
+
+/* verify_clnt_update - request address status update */
+
+int verify_clnt_update(const char *addr, int addr_status, const char *why)
+{
+ VSTREAM *stream;
+ int request_status;
+
+ /*
+ * Do client-server plumbing.
+ */
+ if (vrfy_clnt == 0)
+ verify_clnt_init();
+
+ /*
+ * Send status for this address. Supply a default status if the address
+ * verification service is unavailable.
+ */
+ for (;;) {
+ stream = clnt_stream_access(vrfy_clnt);
+ errno = 0;
+ if (stream == 0
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_REQ, VRFY_REQ_UPDATE),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, why),
+ ATTR_TYPE_END) != 0
+ || attr_scan(stream, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &request_status),
+ ATTR_TYPE_END) != 1) {
+ if (msg_verbose || (errno != EPIPE && errno != ENOENT))
+ msg_warn("problem talking to service %s: %m",
+ var_verify_service);
+ } else {
+ break;
+ }
+ sleep(1);
+ clnt_stream_recover(vrfy_clnt);
+ }
+ return (request_status);
+}
+
+ /*
+ * Proof-of-concept test client program.
+ */
+#ifdef TEST
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstring_vstream.h>
+#include <mail_conf.h>
+
+#define STR(x) vstring_str(x)
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-v]", myname);
+}
+
+static void query(char *query, VSTRING *buf)
+{
+ int status;
+
+ switch (verify_clnt_query(query, &status, buf)) {
+ case VRFY_STAT_OK:
+ vstream_printf("%-10s %d\n", "status", status);
+ vstream_printf("%-10s %s\n", "text", STR(buf));
+ vstream_fflush(VSTREAM_OUT);
+ break;
+ case VRFY_STAT_BAD:
+ msg_warn("bad request format");
+ break;
+ case VRFY_STAT_FAIL:
+ msg_warn("request failed");
+ break;
+ }
+}
+
+static void update(char *query)
+{
+ char *addr;
+ char *status_text;
+ char *cp = query;
+
+ if ((addr = mystrtok(&cp, CHARS_SPACE)) == 0
+ || (status_text = mystrtok(&cp, CHARS_SPACE)) == 0) {
+ msg_warn("bad request format");
+ return;
+ }
+ while (*cp && ISSPACE(*cp))
+ cp++;
+ if (*cp == 0) {
+ msg_warn("bad request format");
+ return;
+ }
+ switch (verify_clnt_update(query, atoi(status_text), cp)) {
+ case VRFY_STAT_OK:
+ vstream_printf("OK\n");
+ vstream_fflush(VSTREAM_OUT);
+ break;
+ case VRFY_STAT_BAD:
+ msg_warn("bad request format");
+ break;
+ case VRFY_STAT_FAIL:
+ msg_warn("request failed");
+ break;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ char *cp;
+ int ch;
+ char *command;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind > 1)
+ usage(argv[0]);
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ cp = STR(buffer);
+ if ((command = mystrtok(&cp, CHARS_SPACE)) == 0)
+ continue;
+ if (strcmp(command, "query") == 0)
+ query(cp, buffer);
+ else if (strcmp(command, "update") == 0)
+ update(cp);
+ else
+ msg_warn("unrecognized command: %s", command);
+ }
+ vstring_free(buffer);
+ return (0);
+}
+
+#endif
diff --git a/src/global/verify_clnt.h b/src/global/verify_clnt.h
new file mode 100644
index 0000000..6084581
--- /dev/null
+++ b/src/global/verify_clnt.h
@@ -0,0 +1,54 @@
+#ifndef _VRFY_CLNT_H_INCLUDED_
+#define _VRFY_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* verify_clnt 3h
+/* SUMMARY
+/* address verification client interface
+/* SYNOPSIS
+/* #include <verify_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+
+ /*
+ * Address verification requests.
+ */
+#define VRFY_REQ_QUERY "query"
+#define VRFY_REQ_UPDATE "update"
+
+ /*
+ * Request (NOT: address) status codes.
+ */
+#define VRFY_STAT_OK 0
+#define VRFY_STAT_FAIL (-1)
+#define VRFY_STAT_BAD (-2)
+
+ /*
+ * Functional interface.
+ */
+extern int verify_clnt_query(const char *, int *, VSTRING *);
+extern int verify_clnt_update(const char *, int, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/verify_sender_addr.c b/src/global/verify_sender_addr.c
new file mode 100644
index 0000000..a3eb1bb
--- /dev/null
+++ b/src/global/verify_sender_addr.c
@@ -0,0 +1,341 @@
+/*++
+/* NAME
+/* verify_sender_addr 3
+/* SUMMARY
+/* time-dependent probe sender addresses
+/* SYNOPSIS
+/* #include <verify_sender_addr.h>
+/*
+/* char *var_verify_sender;
+/* int var_verify_sender_ttl;
+/*
+/* const char *make_verify_sender_addr()
+/*
+/* const char *valid_verify_sender_addr(addr)
+/* const char *addr;
+/* DESCRIPTION
+/* This module computes or verifies a constant or time-dependent
+/* sender address for an address verification probe. The
+/* time-dependent portion is appended to the address localpart
+/* specified with the address_verify_sender parameter.
+/*
+/* When the address_verify_sender parameter is empty or <>,
+/* the sender address is always the empty address (i.e. always
+/* time-independent).
+/*
+/* The caller must initialize the address_verify_sender and
+/* address_verify_sender_ttl parameter values.
+/*
+/* make_verify_sender_addr() generates an envelope sender
+/* address for an address verification probe.
+/*
+/* valid_verify_sender_addr() verifies that the given address
+/* is a valid sender address for address verification probes.
+/* When probe sender addresses are configured to be time-dependent,
+/* the given address is allowed to differ by +/-1 TTL unit
+/* from the expected address. The result is a null pointer
+/* when no match is found. Otherwise, the result is the sender
+/* address without the time-dependent portion; this is the
+/* address that should be used for further delivery.
+/* DIAGNOSTICS
+/* Fatal errors: malformed address_verify_sender value; out
+/* of memory.
+/* 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 <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <events.h>
+#include <stringops.h>
+
+/* Global library */
+
+#include <mail_params.h>
+#include <rewrite_clnt.h>
+#include <safe_ultostr.h>
+#include <verify_sender_addr.h>
+
+/* Application-specific. */
+
+ /*
+ * We convert the time-dependent portion to a safe string (no vowels) in a
+ * reversible manner, so that we can check an incoming address against the
+ * current and +/-1 TTL time slot. This allows for some time slippage
+ * between multiple MTAs that handle mail for the same site. We use base 31
+ * so that the time stamp contains B-Z0-9. This simplifies regression tests.
+ */
+#define VERIFY_BASE 31
+
+ /*
+ * We append the time-dependent portion to the localpart of the address
+ * verification probe sender address, so that the result has the form
+ * ``fixed1variable@fixed2''. There is no delimiter between ``fixed1'' and
+ * ``variable'', because that could make "old" time stamps valid depending
+ * on how the recipient_delimiter feature is configured. The fixed text is
+ * taken from var_verify_sender with perhaps domain information appended
+ * during address canonicalization. The variable part of the address changes
+ * every var_verify_sender_ttl seconds.
+ */
+char *var_verify_sender; /* "bare" probe sender address */
+int var_verify_sender_ttl; /* time between address changes */
+
+ /*
+ * Scaffolding for stand-alone testing.
+ */
+#ifdef TEST
+#undef event_time
+#define event_time() verify_time
+static unsigned long verify_time;
+
+#endif
+
+#define VERIFY_SENDER_ADDR_EPOCH() (event_time() / var_verify_sender_ttl)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* make_verify_sender_addr - generate address_verify_sender address */
+
+const char *make_verify_sender_addr(void)
+{
+ static VSTRING *verify_sender_buf; /* the complete sender address */
+ static VSTRING *my_epoch_buf; /* scratch space */
+ char *my_at_domain;
+
+ /*
+ * The null sender is always time-independent.
+ */
+ if (*var_verify_sender == 0 || strcmp(var_verify_sender, "<>") == 0)
+ return ("");
+
+ /*
+ * Sanity check.
+ */
+ if (*var_verify_sender == '@')
+ msg_fatal("parameter %s: value \"%s\" must not start with '@'",
+ VAR_VERIFY_SENDER, var_verify_sender);
+ if ((my_at_domain = strchr(var_verify_sender, '@')) != 0 && my_at_domain[1] == 0)
+ msg_fatal("parameter %s: value \"%s\" must not end with '@'",
+ VAR_VERIFY_SENDER, var_verify_sender);
+
+ /*
+ * One-time initialization.
+ */
+ if (verify_sender_buf == 0) {
+ verify_sender_buf = vstring_alloc(10);
+ my_epoch_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Start with the bare sender address.
+ */
+ vstring_strcpy(verify_sender_buf, var_verify_sender);
+
+ /*
+ * Append the time stamp to the address localpart, encoded in some
+ * non-decimal form for obscurity.
+ *
+ * XXX It would be nice to have safe_ultostr() append-only support.
+ */
+ if (var_verify_sender_ttl > 0) {
+ /* Strip the @domain portion, if applicable. */
+ if (my_at_domain != 0)
+ vstring_truncate(verify_sender_buf,
+ (ssize_t) (my_at_domain - var_verify_sender));
+ /* Append the time stamp to the address localpart. */
+ vstring_sprintf_append(verify_sender_buf, "%s",
+ safe_ultostr(my_epoch_buf,
+ VERIFY_SENDER_ADDR_EPOCH(),
+ VERIFY_BASE, 0, 0));
+ /* Add back the @domain, if applicable. */
+ if (my_at_domain != 0)
+ vstring_sprintf_append(verify_sender_buf, "%s", my_at_domain);
+ }
+
+ /*
+ * Rewrite the address to canonical form.
+ */
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, STR(verify_sender_buf),
+ verify_sender_buf);
+
+ return (STR(verify_sender_buf));
+}
+
+/* valid_verify_sender_addr - decide if address matches time window +/-1 */
+
+const char *valid_verify_sender_addr(const char *their_addr)
+{
+ static VSTRING *time_indep_sender_buf; /* sender without time stamp */
+ ssize_t base_len;
+ unsigned long my_epoch;
+ unsigned long their_epoch;
+ char *my_at_domain;
+ char *their_at_domain;
+ char *cp;
+
+ /*
+ * The null address is always time-independent.
+ */
+ if (*var_verify_sender == 0 || strcmp(var_verify_sender, "<>") == 0)
+ return (*their_addr ? 0 : "");
+
+ /*
+ * One-time initialization. Generate the time-independent address that we
+ * will return if the match is successful. This address is also used as a
+ * matching template.
+ */
+ if (time_indep_sender_buf == 0) {
+ time_indep_sender_buf = vstring_alloc(10);
+ vstring_strcpy(time_indep_sender_buf, var_verify_sender);
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, STR(time_indep_sender_buf),
+ time_indep_sender_buf);
+ }
+
+ /*
+ * Check the time-independent sender localpart.
+ */
+ if ((my_at_domain = strchr(STR(time_indep_sender_buf), '@')) != 0)
+ base_len = my_at_domain - STR(time_indep_sender_buf);
+ else
+ base_len = LEN(time_indep_sender_buf);
+ if (strncasecmp_utf8(STR(time_indep_sender_buf), their_addr, base_len) != 0)
+ return (0); /* sender localpart mis-match */
+
+ /*
+ * Check the time-independent domain.
+ */
+ if ((their_at_domain = strchr(their_addr, '@')) == 0 && my_at_domain != 0)
+ return (0); /* sender domain mis-match */
+ if (their_at_domain != 0
+ && (my_at_domain == 0
+ || strcasecmp_utf8(their_at_domain, my_at_domain) != 0))
+ return (0); /* sender domain mis-match */
+
+ /*
+ * Check the time-dependent portion.
+ */
+ if (var_verify_sender_ttl > 0) {
+ their_epoch = safe_strtoul(their_addr + base_len, &cp, VERIFY_BASE);
+ if ((*cp != '@' && *cp != 0)
+ || (their_epoch == ULONG_MAX && errno == ERANGE))
+ return (0); /* malformed time stamp */
+ my_epoch = VERIFY_SENDER_ADDR_EPOCH();
+ if (their_epoch < my_epoch - 1 || their_epoch > my_epoch + 1)
+ return (0); /* outside time window */
+ }
+
+ /*
+ * No time-dependent portion.
+ */
+ else {
+ if (their_addr[base_len] != '@' && their_addr[base_len] != 0)
+ return (0); /* garbage after sender base */
+ }
+ return (STR(time_indep_sender_buf));
+}
+
+ /*
+ * Proof-of-concept test program. Read test address_verify_sender and
+ * address_verify_sender_ttl values from stdin, and report results that we
+ * would get on stdout.
+ */
+#ifdef TEST
+
+#include <stdlib.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <mail_conf.h>
+#include <conv_time.h>
+
+int main(int argc, char **argv)
+{
+ const char *verify_sender;
+ const char *valid_sender;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Prepare to talk to the address rewriting service.
+ */
+ mail_conf_read();
+ vstream_printf("using config files in %s\n", var_config_dir);
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ /*
+ * Parse JCL.
+ */
+ if (argc != 3)
+ msg_fatal("usage: %s address_verify_sender address_verify_sender_ttl",
+ argv[0]);
+ var_verify_sender = argv[1];
+ if (conv_time(argv[2], &var_verify_sender_ttl, 's') == 0)
+ msg_fatal("bad time value: %s", argv[2]);
+ verify_time = time((time_t *) 0);
+
+ /*
+ * Compute the current probe sender address.
+ */
+ verify_sender = make_verify_sender_addr();
+
+ /*
+ * Check two past time slots.
+ */
+ if (var_verify_sender_ttl > 0) {
+ verify_time -= 2 * var_verify_sender_ttl;
+ vstream_printf("\"%s\" matches prev2: \"%s\"\n", verify_sender,
+ (valid_sender = valid_verify_sender_addr(verify_sender)) != 0 ?
+ valid_sender : "nope");
+ verify_time += var_verify_sender_ttl;
+ vstream_printf("\"%s\" matches prev1: \"%s\"\n", verify_sender,
+ (valid_sender = valid_verify_sender_addr(verify_sender)) != 0 ?
+ valid_sender : "nope");
+ verify_time += var_verify_sender_ttl;
+ }
+
+ /*
+ * Check the current time slot.
+ */
+ vstream_printf("\"%s\" matches self: \"%s\"\n", verify_sender,
+ (valid_sender = valid_verify_sender_addr(verify_sender)) != 0 ?
+ valid_sender : "nope");
+
+ /*
+ * Check two future time slots.
+ */
+ if (var_verify_sender_ttl > 0) {
+ verify_time += var_verify_sender_ttl;
+ vstream_printf("\"%s\" matches next1: \"%s\"\n", verify_sender,
+ (valid_sender = valid_verify_sender_addr(verify_sender)) != 0 ?
+ valid_sender : "nope");
+ verify_time += var_verify_sender_ttl;
+ vstream_printf("\"%s\" matches next2: \"%s\"\n", verify_sender,
+ (valid_sender = valid_verify_sender_addr(verify_sender)) != 0 ?
+ valid_sender : "nope");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+}
+
+#endif
diff --git a/src/global/verify_sender_addr.h b/src/global/verify_sender_addr.h
new file mode 100644
index 0000000..abf7e24
--- /dev/null
+++ b/src/global/verify_sender_addr.h
@@ -0,0 +1,31 @@
+#ifndef _VERIFY_SENDER_ADDR_H_INCLUDED_
+#define _VERIFY_SENDER_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* verify_sender_addr 3h
+/* SUMMARY
+/* address verify sender utilities
+/* SYNOPSIS
+/* #include <verify_sender_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+const char *make_verify_sender_addr(void);
+const char *valid_verify_sender_addr(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/verify_sender_addr.ref b/src/global/verify_sender_addr.ref
new file mode 100644
index 0000000..78d55dd
--- /dev/null
+++ b/src/global/verify_sender_addr.ref
@@ -0,0 +1,24 @@
+using config files in CONFIGDIR
+"aa@bb.MYDOMAIN" matches self: "aa@bb.MYDOMAIN"
+using config files in CONFIGDIR
+"aaSTAMP@bb.MYDOMAIN" matches prev2: "nope"
+"aaSTAMP@bb.MYDOMAIN" matches prev1: "aa@bb.MYDOMAIN"
+"aaSTAMP@bb.MYDOMAIN" matches self: "aa@bb.MYDOMAIN"
+"aaSTAMP@bb.MYDOMAIN" matches next1: "aa@bb.MYDOMAIN"
+"aaSTAMP@bb.MYDOMAIN" matches next2: "nope"
+using config files in CONFIGDIR
+"aa@MYORIGIN" matches self: "aa@MYORIGIN"
+using config files in CONFIGDIR
+"aaSTAMP@MYORIGIN" matches prev2: "nope"
+"aaSTAMP@MYORIGIN" matches prev1: "aa@MYORIGIN"
+"aaSTAMP@MYORIGIN" matches self: "aa@MYORIGIN"
+"aaSTAMP@MYORIGIN" matches next1: "aa@MYORIGIN"
+"aaSTAMP@MYORIGIN" matches next2: "nope"
+using config files in CONFIGDIR
+"" matches self: ""
+using config files in CONFIGDIR
+"" matches prev2: ""
+"" matches prev1: ""
+"" matches self: ""
+"" matches next1: ""
+"" matches next2: ""
diff --git a/src/global/verp_sender.c b/src/global/verp_sender.c
new file mode 100644
index 0000000..1d1149f
--- /dev/null
+++ b/src/global/verp_sender.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* verp_sender 3
+/* SUMMARY
+/* quote local part of mailbox
+/* SYNOPSIS
+/* #include <verp_sender.h>
+/*
+/* VSTRING *verp_sender(dst, delims, sender, recipient)
+/* VSTRING *dst;
+/* const char *delims;
+/* const char *sender;
+/* const RECIPIENT *recipient;
+/*
+/* const char *verp_delims_verify(delims)
+/* const char *delims;
+/* DESCRIPTION
+/* verp_sender() encodes the recipient address in the sender
+/* address, using the specified delimiters. For example,
+/* with delims +=, sender \fIprefix@origin\fR, and
+/* recipient \fIuser@domain\fR the result is
+/* \fIprefix+user=domain@origin\fR.
+/*
+/* verp_delims_verify() determines if the specified VERP delimiters
+/* have reasonable values. What is reasonable is configured with
+/* the verp_delimiter_filter configuration parameter. The result
+/* is null in case of success, a description of the problem in
+/* case of error.
+/*
+/* Arguments:
+/* .IP dst
+/* The result. The buffer is null terminated.
+/* .IP delims
+/* VERP formatting characters.
+/* .IP sender
+/* Sender envelope address.
+/* .IP recipient
+/* Recipient envelope address.
+/* 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 <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <recipient_list.h>
+#include <verp_sender.h>
+
+/* verp_sender - encode recipient into envelope sender address */
+
+VSTRING *verp_sender(VSTRING *buf, const char *delimiters,
+ const char *sender, const RECIPIENT *rcpt_info)
+{
+ ssize_t send_local_len;
+ ssize_t rcpt_local_len;
+ const char *recipient;
+ const char *cp;
+
+ /*
+ * Change prefix@origin into prefix+user=domain@origin.
+ *
+ * Fix 20090115: Use the Postfix original recipient, because that is what
+ * the VERP consumer expects.
+ */
+ send_local_len = ((cp = strrchr(sender, '@')) != 0 ?
+ cp - sender : strlen(sender));
+ recipient = (rcpt_info->orig_addr[0] ?
+ rcpt_info->orig_addr : rcpt_info->address);
+ rcpt_local_len = ((cp = strrchr(recipient, '@')) != 0 ?
+ cp - recipient : strlen(recipient));
+ vstring_strncpy(buf, sender, send_local_len);
+ VSTRING_ADDCH(buf, delimiters[0] & 0xff);
+ vstring_strncat(buf, recipient, rcpt_local_len);
+ if (recipient[rcpt_local_len] && recipient[rcpt_local_len + 1]) {
+ VSTRING_ADDCH(buf, delimiters[1] & 0xff);
+ vstring_strcat(buf, recipient + rcpt_local_len + 1);
+ }
+ if (sender[send_local_len] && sender[send_local_len + 1]) {
+ VSTRING_ADDCH(buf, '@');
+ vstring_strcat(buf, sender + send_local_len + 1);
+ }
+ VSTRING_TERMINATE(buf);
+ return (buf);
+}
+
+/* verp_delims_verify - sanitize VERP delimiters */
+
+const char *verp_delims_verify(const char *delims)
+{
+ if (strlen(delims) != 2)
+ return ("bad VERP delimiter character count");
+ if (strchr(var_verp_filter, delims[0]) == 0)
+ return ("bad first VERP delimiter character");
+ if (strchr(var_verp_filter, delims[1]) == 0)
+ return ("bad second VERP delimiter character");
+ return (0);
+}
diff --git a/src/global/verp_sender.h b/src/global/verp_sender.h
new file mode 100644
index 0000000..f679424
--- /dev/null
+++ b/src/global/verp_sender.h
@@ -0,0 +1,41 @@
+#ifndef _VERP_SENDER_H_INCLUDED_
+#define _VERP_SENDER_H_INCLUDED_
+
+/*++
+/* NAME
+/* verp_sender 3h
+/* SUMMARY
+/* encode recipient into sender, VERP style
+/* SYNOPSIS
+/* #include "verp_sender.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *verp_sender(VSTRING *, const char *, const char *, const RECIPIENT *);
+extern const char *verp_delims_verify(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/global/wildcard_inet_addr.c b/src/global/wildcard_inet_addr.c
new file mode 100644
index 0000000..97f6c46
--- /dev/null
+++ b/src/global/wildcard_inet_addr.c
@@ -0,0 +1,68 @@
+/*++
+/* NAME
+/* wildcard_inet_addr 3
+/* SUMMARY
+/* expand wild-card address
+/* SYNOPSIS
+/* #include <wildcard_inet_addr.h>
+/*
+/* INET_ADDR_LIST *wildcard_inet_addr(void)
+/* DESCRIPTION
+/* wildcard_inet_addr() determines all wild-card addresses
+/* for all supported address families.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* inet_addr_list(3) address list management
+/* 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
+/*
+/* Dean C. Strik
+/* Department ICT
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven, Netherlands
+/* E-mail: <dean@ipnet6.org>
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <inet_addr_list.h>
+#include <inet_addr_host.h>
+
+/* Global library. */
+
+#include <wildcard_inet_addr.h>
+
+/* Application-specific. */
+
+static INET_ADDR_LIST wild_addr_list;
+
+static void wildcard_inet_addr_init(INET_ADDR_LIST *addr_list)
+{
+ inet_addr_list_init(addr_list);
+ if (inet_addr_host(addr_list, "") == 0)
+ msg_fatal("could not get list of wildcard addresses");
+}
+
+/* wildcard_inet_addr_list - return list of addresses */
+
+INET_ADDR_LIST *wildcard_inet_addr_list(void)
+{
+ if (wild_addr_list.used == 0)
+ wildcard_inet_addr_init(&wild_addr_list);
+
+ return (&wild_addr_list);
+}
diff --git a/src/global/wildcard_inet_addr.h b/src/global/wildcard_inet_addr.h
new file mode 100644
index 0000000..46b12e2
--- /dev/null
+++ b/src/global/wildcard_inet_addr.h
@@ -0,0 +1,33 @@
+#ifndef _WILDCARD_INET_ADDR_H_INCLUDED_
+#define _WILDCARD_INET_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* wildcard_inet_addr 3h
+/* SUMMARY
+/* grab the list of wildcard IP addresses.
+/* SYNOPSIS
+/* #include <wildcard_inet_addr.h>
+/* DESCRIPTION
+/* .nf
+/*--*/
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern struct INET_ADDR_LIST *wildcard_inet_addr_list(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* foo
+/* AUTHOR(S)
+/* Jun-ichiro itojun Hagino
+/*--*/
+
+#endif
diff --git a/src/global/xtext.c b/src/global/xtext.c
new file mode 100644
index 0000000..e0d3ed5
--- /dev/null
+++ b/src/global/xtext.c
@@ -0,0 +1,196 @@
+/*++
+/* NAME
+/* xtext 3
+/* SUMMARY
+/* quote/unquote text, xtext style.
+/* SYNOPSIS
+/* #include <xtext.h>
+/*
+/* VSTRING *xtext_quote(quoted, unquoted, special)
+/* VSTRING *quoted;
+/* const char *unquoted;
+/* const char *special;
+/*
+/* VSTRING *xtext_quote_append(unquoted, quoted, special)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/* const char *special;
+/*
+/* VSTRING *xtext_unquote(unquoted, quoted)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/*
+/* VSTRING *xtext_unquote_append(unquoted, quoted)
+/* VSTRING *unquoted;
+/* const char *quoted;
+/* DESCRIPTION
+/* xtext_quote() takes a null-terminated string and replaces characters
+/* +, <33(10) and >126(10), as well as characters specified with "special"
+/* by +XX, XX being the two-digit uppercase hexadecimal equivalent.
+/*
+/* xtext_quote_append() is like xtext_quote(), but appends the conversion
+/* result to the result buffer.
+/*
+/* xtext_unquote() performs the opposite transformation. This function
+/* understands lowercase, uppercase, and mixed case +XX sequences. The
+/* result value is the unquoted argument in case of success, a null pointer
+/* otherwise.
+/*
+/* xtext_unquote_append() is like xtext_unquote(), but appends
+/* the conversion result to the result buffer.
+/* BUGS
+/* This module cannot process null characters in data.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "xtext.h"
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* xtext_quote_append - append unquoted data to quoted data */
+
+VSTRING *xtext_quote_append(VSTRING *quoted, const char *unquoted,
+ const char *special)
+{
+ const char *cp;
+ int ch;
+
+ for (cp = unquoted; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ch != '+' && ch > 32 && ch < 127
+ && (*special == 0 || strchr(special, ch) == 0)) {
+ VSTRING_ADDCH(quoted, ch);
+ } else {
+ vstring_sprintf_append(quoted, "+%02X", ch);
+ }
+ }
+ VSTRING_TERMINATE(quoted);
+ return (quoted);
+}
+
+/* xtext_quote - unquoted data to quoted */
+
+VSTRING *xtext_quote(VSTRING *quoted, const char *unquoted, const char *special)
+{
+ VSTRING_RESET(quoted);
+ xtext_quote_append(quoted, unquoted, special);
+ return (quoted);
+}
+
+/* xtext_unquote_append - quoted data to unquoted */
+
+VSTRING *xtext_unquote_append(VSTRING *unquoted, const char *quoted)
+{
+ const unsigned char *cp;
+ int ch;
+
+ for (cp = (const unsigned char *) quoted; (ch = *cp) != 0; cp++) {
+ if (ch == '+') {
+ if (ISDIGIT(cp[1]))
+ ch = (cp[1] - '0') << 4;
+ else if (cp[1] >= 'a' && cp[1] <= 'f')
+ ch = (cp[1] - 'a' + 10) << 4;
+ else if (cp[1] >= 'A' && cp[1] <= 'F')
+ ch = (cp[1] - 'A' + 10) << 4;
+ else
+ return (0);
+ if (ISDIGIT(cp[2]))
+ ch |= (cp[2] - '0');
+ else if (cp[2] >= 'a' && cp[2] <= 'f')
+ ch |= (cp[2] - 'a' + 10);
+ else if (cp[2] >= 'A' && cp[2] <= 'F')
+ ch |= (cp[2] - 'A' + 10);
+ else
+ return (0);
+ cp += 2;
+ }
+ VSTRING_ADDCH(unquoted, ch);
+ }
+ VSTRING_TERMINATE(unquoted);
+ return (unquoted);
+}
+/* xtext_unquote - quoted data to unquoted */
+
+VSTRING *xtext_unquote(VSTRING *unquoted, const char *quoted)
+{
+ VSTRING_RESET(unquoted);
+ return (xtext_unquote_append(unquoted, quoted) ? unquoted : 0);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to quoted and back.
+ */
+#include <vstream.h>
+
+#define BUFLEN 1024
+
+static ssize_t read_buf(VSTREAM *fp, VSTRING *buf)
+{
+ ssize_t len;
+
+ len = vstream_fread_buf(fp, buf, BUFLEN);
+ VSTRING_TERMINATE(buf);
+ return (len);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *unquoted = vstring_alloc(BUFLEN);
+ VSTRING *quoted = vstring_alloc(100);
+ ssize_t len;
+
+ /*
+ * Negative tests.
+ */
+ if (xtext_unquote(unquoted, "++1") != 0)
+ msg_warn("undetected error pattern 1");
+ if (xtext_unquote(unquoted, "+2+") != 0)
+ msg_warn("undetected error pattern 2");
+
+ /*
+ * Positive tests.
+ */
+ while ((len = read_buf(VSTREAM_IN, unquoted)) > 0) {
+ xtext_quote(quoted, STR(unquoted), "+=");
+ if (xtext_unquote(unquoted, STR(quoted)) == 0)
+ msg_fatal("bad input: %.100s", STR(quoted));
+ if (LEN(unquoted) != len)
+ msg_fatal("len %ld != unquoted len %ld",
+ (long) len, (long) LEN(unquoted));
+ if (vstream_fwrite(VSTREAM_OUT, STR(unquoted), LEN(unquoted)) != LEN(unquoted))
+ msg_fatal("write error: %m");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(unquoted);
+ vstring_free(quoted);
+ return (0);
+}
+
+#endif
diff --git a/src/global/xtext.h b/src/global/xtext.h
new file mode 100644
index 0000000..c768062
--- /dev/null
+++ b/src/global/xtext.h
@@ -0,0 +1,38 @@
+#ifndef _XTEXT_H_INCLUDED_
+#define _XTEXT_H_INCLUDED_
+
+/*++
+/* NAME
+/* xtext 3h
+/* SUMMARY
+/* quote/unquote text, xtext style.
+/* SYNOPSIS
+/* #include <xtext.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *xtext_quote(VSTRING *, const char *, const char *);
+extern VSTRING *xtext_quote_append(VSTRING *, const char *, const char *);
+extern VSTRING *xtext_unquote(VSTRING *, const char *);
+extern VSTRING *xtext_unquote_append(VSTRING *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/local/.indent.pro b/src/local/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/local/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/local/.printfck b/src/local/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/local/.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/local/Makefile.in b/src/local/Makefile.in
new file mode 100644
index 0000000..648ad51
--- /dev/null
+++ b/src/local/Makefile.in
@@ -0,0 +1,686 @@
+SHELL = /bin/sh
+SRCS = alias.c command.c dotforward.c file.c forward.c \
+ include.c indirect.c local.c mailbox.c recipient.c resolve.c token.c \
+ deliver_attr.c maildir.c biff_notify.c unknown.c \
+ local_expand.c bounce_workaround.c
+OBJS = alias.o command.o dotforward.o file.o forward.o \
+ include.o indirect.o local.o mailbox.o recipient.o resolve.o token.o \
+ deliver_attr.o maildir.o biff_notify.o unknown.o \
+ local_expand.o bounce_workaround.c
+HDRS = local.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+PROG = local
+TESTPROG=
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+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
+
+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'
+alias.o: ../../include/argv.h
+alias.o: ../../include/attr.h
+alias.o: ../../include/been_here.h
+alias.o: ../../include/bounce.h
+alias.o: ../../include/canon_addr.h
+alias.o: ../../include/check_arg.h
+alias.o: ../../include/defer.h
+alias.o: ../../include/deliver_request.h
+alias.o: ../../include/delivered_hdr.h
+alias.o: ../../include/dict.h
+alias.o: ../../include/dsn.h
+alias.o: ../../include/dsn_buf.h
+alias.o: ../../include/dsn_mask.h
+alias.o: ../../include/fold_addr.h
+alias.o: ../../include/htable.h
+alias.o: ../../include/mail_params.h
+alias.o: ../../include/maps.h
+alias.o: ../../include/mbox_conf.h
+alias.o: ../../include/msg.h
+alias.o: ../../include/msg_stats.h
+alias.o: ../../include/myflock.h
+alias.o: ../../include/mymalloc.h
+alias.o: ../../include/mypwd.h
+alias.o: ../../include/nvtable.h
+alias.o: ../../include/recipient_list.h
+alias.o: ../../include/resolve_clnt.h
+alias.o: ../../include/sent.h
+alias.o: ../../include/stringops.h
+alias.o: ../../include/sys_defs.h
+alias.o: ../../include/tok822.h
+alias.o: ../../include/trace.h
+alias.o: ../../include/vbuf.h
+alias.o: ../../include/vstream.h
+alias.o: ../../include/vstring.h
+alias.o: alias.c
+alias.o: local.h
+biff_notify.o: ../../include/iostuff.h
+biff_notify.o: ../../include/msg.h
+biff_notify.o: ../../include/sys_defs.h
+biff_notify.o: biff_notify.c
+biff_notify.o: biff_notify.h
+bounce_workaround.o: ../../include/argv.h
+bounce_workaround.o: ../../include/attr.h
+bounce_workaround.o: ../../include/been_here.h
+bounce_workaround.o: ../../include/bounce.h
+bounce_workaround.o: ../../include/canon_addr.h
+bounce_workaround.o: ../../include/check_arg.h
+bounce_workaround.o: ../../include/defer.h
+bounce_workaround.o: ../../include/deliver_request.h
+bounce_workaround.o: ../../include/delivered_hdr.h
+bounce_workaround.o: ../../include/dict.h
+bounce_workaround.o: ../../include/dsn.h
+bounce_workaround.o: ../../include/dsn_buf.h
+bounce_workaround.o: ../../include/fold_addr.h
+bounce_workaround.o: ../../include/htable.h
+bounce_workaround.o: ../../include/mail_params.h
+bounce_workaround.o: ../../include/maps.h
+bounce_workaround.o: ../../include/mbox_conf.h
+bounce_workaround.o: ../../include/msg.h
+bounce_workaround.o: ../../include/msg_stats.h
+bounce_workaround.o: ../../include/myflock.h
+bounce_workaround.o: ../../include/mymalloc.h
+bounce_workaround.o: ../../include/nvtable.h
+bounce_workaround.o: ../../include/recipient_list.h
+bounce_workaround.o: ../../include/resolve_clnt.h
+bounce_workaround.o: ../../include/split_addr.h
+bounce_workaround.o: ../../include/split_at.h
+bounce_workaround.o: ../../include/stringops.h
+bounce_workaround.o: ../../include/strip_addr.h
+bounce_workaround.o: ../../include/sys_defs.h
+bounce_workaround.o: ../../include/tok822.h
+bounce_workaround.o: ../../include/vbuf.h
+bounce_workaround.o: ../../include/vstream.h
+bounce_workaround.o: ../../include/vstring.h
+bounce_workaround.o: bounce_workaround.c
+bounce_workaround.o: local.h
+command.o: ../../include/argv.h
+command.o: ../../include/attr.h
+command.o: ../../include/been_here.h
+command.o: ../../include/bounce.h
+command.o: ../../include/check_arg.h
+command.o: ../../include/defer.h
+command.o: ../../include/deliver_request.h
+command.o: ../../include/delivered_hdr.h
+command.o: ../../include/dict.h
+command.o: ../../include/dsn.h
+command.o: ../../include/dsn_buf.h
+command.o: ../../include/dsn_util.h
+command.o: ../../include/fold_addr.h
+command.o: ../../include/htable.h
+command.o: ../../include/mac_parse.h
+command.o: ../../include/mail_copy.h
+command.o: ../../include/mail_params.h
+command.o: ../../include/mail_parm_split.h
+command.o: ../../include/maps.h
+command.o: ../../include/mbox_conf.h
+command.o: ../../include/msg.h
+command.o: ../../include/msg_stats.h
+command.o: ../../include/myflock.h
+command.o: ../../include/mymalloc.h
+command.o: ../../include/nvtable.h
+command.o: ../../include/pipe_command.h
+command.o: ../../include/recipient_list.h
+command.o: ../../include/resolve_clnt.h
+command.o: ../../include/sent.h
+command.o: ../../include/sys_defs.h
+command.o: ../../include/tok822.h
+command.o: ../../include/vbuf.h
+command.o: ../../include/vstream.h
+command.o: ../../include/vstring.h
+command.o: command.c
+command.o: local.h
+deliver_attr.o: ../../include/argv.h
+deliver_attr.o: ../../include/attr.h
+deliver_attr.o: ../../include/been_here.h
+deliver_attr.o: ../../include/check_arg.h
+deliver_attr.o: ../../include/deliver_request.h
+deliver_attr.o: ../../include/delivered_hdr.h
+deliver_attr.o: ../../include/dict.h
+deliver_attr.o: ../../include/dsn.h
+deliver_attr.o: ../../include/dsn_buf.h
+deliver_attr.o: ../../include/fold_addr.h
+deliver_attr.o: ../../include/htable.h
+deliver_attr.o: ../../include/maps.h
+deliver_attr.o: ../../include/mbox_conf.h
+deliver_attr.o: ../../include/msg.h
+deliver_attr.o: ../../include/msg_stats.h
+deliver_attr.o: ../../include/myflock.h
+deliver_attr.o: ../../include/mymalloc.h
+deliver_attr.o: ../../include/nvtable.h
+deliver_attr.o: ../../include/recipient_list.h
+deliver_attr.o: ../../include/resolve_clnt.h
+deliver_attr.o: ../../include/sys_defs.h
+deliver_attr.o: ../../include/tok822.h
+deliver_attr.o: ../../include/vbuf.h
+deliver_attr.o: ../../include/vstream.h
+deliver_attr.o: ../../include/vstring.h
+deliver_attr.o: deliver_attr.c
+deliver_attr.o: local.h
+dotforward.o: ../../include/argv.h
+dotforward.o: ../../include/attr.h
+dotforward.o: ../../include/been_here.h
+dotforward.o: ../../include/bounce.h
+dotforward.o: ../../include/check_arg.h
+dotforward.o: ../../include/defer.h
+dotforward.o: ../../include/deliver_request.h
+dotforward.o: ../../include/delivered_hdr.h
+dotforward.o: ../../include/dict.h
+dotforward.o: ../../include/dsn.h
+dotforward.o: ../../include/dsn_buf.h
+dotforward.o: ../../include/dsn_mask.h
+dotforward.o: ../../include/ext_prop.h
+dotforward.o: ../../include/fold_addr.h
+dotforward.o: ../../include/htable.h
+dotforward.o: ../../include/iostuff.h
+dotforward.o: ../../include/lstat_as.h
+dotforward.o: ../../include/mac_expand.h
+dotforward.o: ../../include/mac_parse.h
+dotforward.o: ../../include/mail_conf.h
+dotforward.o: ../../include/mail_params.h
+dotforward.o: ../../include/maps.h
+dotforward.o: ../../include/mbox_conf.h
+dotforward.o: ../../include/msg.h
+dotforward.o: ../../include/msg_stats.h
+dotforward.o: ../../include/myflock.h
+dotforward.o: ../../include/mymalloc.h
+dotforward.o: ../../include/mypwd.h
+dotforward.o: ../../include/nvtable.h
+dotforward.o: ../../include/open_as.h
+dotforward.o: ../../include/recipient_list.h
+dotforward.o: ../../include/resolve_clnt.h
+dotforward.o: ../../include/sent.h
+dotforward.o: ../../include/stringops.h
+dotforward.o: ../../include/sys_defs.h
+dotforward.o: ../../include/tok822.h
+dotforward.o: ../../include/trace.h
+dotforward.o: ../../include/vbuf.h
+dotforward.o: ../../include/vstream.h
+dotforward.o: ../../include/vstring.h
+dotforward.o: dotforward.c
+dotforward.o: local.h
+file.o: ../../include/argv.h
+file.o: ../../include/attr.h
+file.o: ../../include/been_here.h
+file.o: ../../include/bounce.h
+file.o: ../../include/check_arg.h
+file.o: ../../include/defer.h
+file.o: ../../include/deliver_flock.h
+file.o: ../../include/deliver_request.h
+file.o: ../../include/delivered_hdr.h
+file.o: ../../include/dict.h
+file.o: ../../include/dsn.h
+file.o: ../../include/dsn_buf.h
+file.o: ../../include/dsn_util.h
+file.o: ../../include/fold_addr.h
+file.o: ../../include/htable.h
+file.o: ../../include/mail_copy.h
+file.o: ../../include/mail_params.h
+file.o: ../../include/maps.h
+file.o: ../../include/mbox_conf.h
+file.o: ../../include/mbox_open.h
+file.o: ../../include/msg.h
+file.o: ../../include/msg_stats.h
+file.o: ../../include/myflock.h
+file.o: ../../include/mymalloc.h
+file.o: ../../include/nvtable.h
+file.o: ../../include/recipient_list.h
+file.o: ../../include/resolve_clnt.h
+file.o: ../../include/safe_open.h
+file.o: ../../include/sent.h
+file.o: ../../include/set_eugid.h
+file.o: ../../include/sys_defs.h
+file.o: ../../include/tok822.h
+file.o: ../../include/vbuf.h
+file.o: ../../include/vstream.h
+file.o: ../../include/vstring.h
+file.o: file.c
+file.o: local.h
+forward.o: ../../include/argv.h
+forward.o: ../../include/attr.h
+forward.o: ../../include/been_here.h
+forward.o: ../../include/bounce.h
+forward.o: ../../include/check_arg.h
+forward.o: ../../include/cleanup_user.h
+forward.o: ../../include/deliver_request.h
+forward.o: ../../include/delivered_hdr.h
+forward.o: ../../include/dict.h
+forward.o: ../../include/dsn.h
+forward.o: ../../include/dsn_buf.h
+forward.o: ../../include/dsn_mask.h
+forward.o: ../../include/fold_addr.h
+forward.o: ../../include/htable.h
+forward.o: ../../include/iostuff.h
+forward.o: ../../include/mail_date.h
+forward.o: ../../include/mail_params.h
+forward.o: ../../include/mail_proto.h
+forward.o: ../../include/maps.h
+forward.o: ../../include/mark_corrupt.h
+forward.o: ../../include/mbox_conf.h
+forward.o: ../../include/msg.h
+forward.o: ../../include/msg_stats.h
+forward.o: ../../include/myflock.h
+forward.o: ../../include/mymalloc.h
+forward.o: ../../include/nvtable.h
+forward.o: ../../include/rec_type.h
+forward.o: ../../include/recipient_list.h
+forward.o: ../../include/record.h
+forward.o: ../../include/resolve_clnt.h
+forward.o: ../../include/sent.h
+forward.o: ../../include/smtputf8.h
+forward.o: ../../include/stringops.h
+forward.o: ../../include/sys_defs.h
+forward.o: ../../include/tok822.h
+forward.o: ../../include/vbuf.h
+forward.o: ../../include/vstream.h
+forward.o: ../../include/vstring.h
+forward.o: ../../include/vstring_vstream.h
+forward.o: forward.c
+forward.o: local.h
+include.o: ../../include/argv.h
+include.o: ../../include/attr.h
+include.o: ../../include/been_here.h
+include.o: ../../include/bounce.h
+include.o: ../../include/check_arg.h
+include.o: ../../include/defer.h
+include.o: ../../include/deliver_request.h
+include.o: ../../include/delivered_hdr.h
+include.o: ../../include/dict.h
+include.o: ../../include/dsn.h
+include.o: ../../include/dsn_buf.h
+include.o: ../../include/ext_prop.h
+include.o: ../../include/fold_addr.h
+include.o: ../../include/htable.h
+include.o: ../../include/iostuff.h
+include.o: ../../include/mail_params.h
+include.o: ../../include/maps.h
+include.o: ../../include/mbox_conf.h
+include.o: ../../include/msg.h
+include.o: ../../include/msg_stats.h
+include.o: ../../include/myflock.h
+include.o: ../../include/mymalloc.h
+include.o: ../../include/mypwd.h
+include.o: ../../include/nvtable.h
+include.o: ../../include/open_as.h
+include.o: ../../include/recipient_list.h
+include.o: ../../include/resolve_clnt.h
+include.o: ../../include/sent.h
+include.o: ../../include/stat_as.h
+include.o: ../../include/sys_defs.h
+include.o: ../../include/tok822.h
+include.o: ../../include/vbuf.h
+include.o: ../../include/vstream.h
+include.o: ../../include/vstring.h
+include.o: include.c
+include.o: local.h
+indirect.o: ../../include/argv.h
+indirect.o: ../../include/attr.h
+indirect.o: ../../include/been_here.h
+indirect.o: ../../include/bounce.h
+indirect.o: ../../include/check_arg.h
+indirect.o: ../../include/defer.h
+indirect.o: ../../include/deliver_request.h
+indirect.o: ../../include/delivered_hdr.h
+indirect.o: ../../include/dict.h
+indirect.o: ../../include/dsn.h
+indirect.o: ../../include/dsn_buf.h
+indirect.o: ../../include/fold_addr.h
+indirect.o: ../../include/htable.h
+indirect.o: ../../include/mail_params.h
+indirect.o: ../../include/maps.h
+indirect.o: ../../include/mbox_conf.h
+indirect.o: ../../include/msg.h
+indirect.o: ../../include/msg_stats.h
+indirect.o: ../../include/myflock.h
+indirect.o: ../../include/mymalloc.h
+indirect.o: ../../include/nvtable.h
+indirect.o: ../../include/recipient_list.h
+indirect.o: ../../include/resolve_clnt.h
+indirect.o: ../../include/sent.h
+indirect.o: ../../include/sys_defs.h
+indirect.o: ../../include/tok822.h
+indirect.o: ../../include/vbuf.h
+indirect.o: ../../include/vstream.h
+indirect.o: ../../include/vstring.h
+indirect.o: indirect.c
+indirect.o: local.h
+local.o: ../../include/argv.h
+local.o: ../../include/attr.h
+local.o: ../../include/been_here.h
+local.o: ../../include/check_arg.h
+local.o: ../../include/deliver_completed.h
+local.o: ../../include/deliver_request.h
+local.o: ../../include/delivered_hdr.h
+local.o: ../../include/dict.h
+local.o: ../../include/dsn.h
+local.o: ../../include/dsn_buf.h
+local.o: ../../include/ext_prop.h
+local.o: ../../include/flush_clnt.h
+local.o: ../../include/fold_addr.h
+local.o: ../../include/htable.h
+local.o: ../../include/iostuff.h
+local.o: ../../include/mail_addr.h
+local.o: ../../include/mail_conf.h
+local.o: ../../include/mail_params.h
+local.o: ../../include/mail_server.h
+local.o: ../../include/mail_version.h
+local.o: ../../include/maps.h
+local.o: ../../include/mbox_conf.h
+local.o: ../../include/msg.h
+local.o: ../../include/msg_stats.h
+local.o: ../../include/myflock.h
+local.o: ../../include/mymalloc.h
+local.o: ../../include/name_mask.h
+local.o: ../../include/nvtable.h
+local.o: ../../include/recipient_list.h
+local.o: ../../include/resolve_clnt.h
+local.o: ../../include/set_eugid.h
+local.o: ../../include/sys_defs.h
+local.o: ../../include/tok822.h
+local.o: ../../include/vbuf.h
+local.o: ../../include/vstream.h
+local.o: ../../include/vstring.h
+local.o: local.c
+local.o: local.h
+local_expand.o: ../../include/argv.h
+local_expand.o: ../../include/attr.h
+local_expand.o: ../../include/been_here.h
+local_expand.o: ../../include/check_arg.h
+local_expand.o: ../../include/deliver_request.h
+local_expand.o: ../../include/delivered_hdr.h
+local_expand.o: ../../include/dict.h
+local_expand.o: ../../include/dsn.h
+local_expand.o: ../../include/dsn_buf.h
+local_expand.o: ../../include/fold_addr.h
+local_expand.o: ../../include/htable.h
+local_expand.o: ../../include/mac_expand.h
+local_expand.o: ../../include/mac_parse.h
+local_expand.o: ../../include/mail_params.h
+local_expand.o: ../../include/maps.h
+local_expand.o: ../../include/mbox_conf.h
+local_expand.o: ../../include/msg_stats.h
+local_expand.o: ../../include/myflock.h
+local_expand.o: ../../include/mymalloc.h
+local_expand.o: ../../include/nvtable.h
+local_expand.o: ../../include/recipient_list.h
+local_expand.o: ../../include/resolve_clnt.h
+local_expand.o: ../../include/sys_defs.h
+local_expand.o: ../../include/tok822.h
+local_expand.o: ../../include/vbuf.h
+local_expand.o: ../../include/vstream.h
+local_expand.o: ../../include/vstring.h
+local_expand.o: local.h
+local_expand.o: local_expand.c
+mailbox.o: ../../include/argv.h
+mailbox.o: ../../include/attr.h
+mailbox.o: ../../include/been_here.h
+mailbox.o: ../../include/bounce.h
+mailbox.o: ../../include/check_arg.h
+mailbox.o: ../../include/defer.h
+mailbox.o: ../../include/deliver_pass.h
+mailbox.o: ../../include/deliver_request.h
+mailbox.o: ../../include/delivered_hdr.h
+mailbox.o: ../../include/dict.h
+mailbox.o: ../../include/dsn.h
+mailbox.o: ../../include/dsn_buf.h
+mailbox.o: ../../include/dsn_util.h
+mailbox.o: ../../include/fold_addr.h
+mailbox.o: ../../include/htable.h
+mailbox.o: ../../include/iostuff.h
+mailbox.o: ../../include/mail_copy.h
+mailbox.o: ../../include/mail_params.h
+mailbox.o: ../../include/mail_proto.h
+mailbox.o: ../../include/maps.h
+mailbox.o: ../../include/mbox_conf.h
+mailbox.o: ../../include/mbox_open.h
+mailbox.o: ../../include/msg.h
+mailbox.o: ../../include/msg_stats.h
+mailbox.o: ../../include/myflock.h
+mailbox.o: ../../include/mymalloc.h
+mailbox.o: ../../include/mypwd.h
+mailbox.o: ../../include/nvtable.h
+mailbox.o: ../../include/recipient_list.h
+mailbox.o: ../../include/resolve_clnt.h
+mailbox.o: ../../include/safe_open.h
+mailbox.o: ../../include/sent.h
+mailbox.o: ../../include/set_eugid.h
+mailbox.o: ../../include/stringops.h
+mailbox.o: ../../include/sys_defs.h
+mailbox.o: ../../include/tok822.h
+mailbox.o: ../../include/vbuf.h
+mailbox.o: ../../include/vstream.h
+mailbox.o: ../../include/vstring.h
+mailbox.o: ../../include/warn_stat.h
+mailbox.o: biff_notify.h
+mailbox.o: local.h
+mailbox.o: mailbox.c
+maildir.o: ../../include/argv.h
+maildir.o: ../../include/attr.h
+maildir.o: ../../include/been_here.h
+maildir.o: ../../include/bounce.h
+maildir.o: ../../include/check_arg.h
+maildir.o: ../../include/defer.h
+maildir.o: ../../include/deliver_request.h
+maildir.o: ../../include/delivered_hdr.h
+maildir.o: ../../include/dict.h
+maildir.o: ../../include/dsn.h
+maildir.o: ../../include/dsn_buf.h
+maildir.o: ../../include/dsn_util.h
+maildir.o: ../../include/fold_addr.h
+maildir.o: ../../include/get_hostname.h
+maildir.o: ../../include/htable.h
+maildir.o: ../../include/mail_copy.h
+maildir.o: ../../include/mail_params.h
+maildir.o: ../../include/make_dirs.h
+maildir.o: ../../include/maps.h
+maildir.o: ../../include/mbox_conf.h
+maildir.o: ../../include/mbox_open.h
+maildir.o: ../../include/msg.h
+maildir.o: ../../include/msg_stats.h
+maildir.o: ../../include/myflock.h
+maildir.o: ../../include/mymalloc.h
+maildir.o: ../../include/nvtable.h
+maildir.o: ../../include/recipient_list.h
+maildir.o: ../../include/resolve_clnt.h
+maildir.o: ../../include/safe_open.h
+maildir.o: ../../include/sane_fsops.h
+maildir.o: ../../include/sent.h
+maildir.o: ../../include/set_eugid.h
+maildir.o: ../../include/stringops.h
+maildir.o: ../../include/sys_defs.h
+maildir.o: ../../include/tok822.h
+maildir.o: ../../include/vbuf.h
+maildir.o: ../../include/vstream.h
+maildir.o: ../../include/vstring.h
+maildir.o: ../../include/warn_stat.h
+maildir.o: local.h
+maildir.o: maildir.c
+recipient.o: ../../include/argv.h
+recipient.o: ../../include/attr.h
+recipient.o: ../../include/been_here.h
+recipient.o: ../../include/bounce.h
+recipient.o: ../../include/canon_addr.h
+recipient.o: ../../include/check_arg.h
+recipient.o: ../../include/defer.h
+recipient.o: ../../include/deliver_request.h
+recipient.o: ../../include/delivered_hdr.h
+recipient.o: ../../include/dict.h
+recipient.o: ../../include/dsn.h
+recipient.o: ../../include/dsn_buf.h
+recipient.o: ../../include/ext_prop.h
+recipient.o: ../../include/fold_addr.h
+recipient.o: ../../include/htable.h
+recipient.o: ../../include/mail_params.h
+recipient.o: ../../include/maps.h
+recipient.o: ../../include/mbox_conf.h
+recipient.o: ../../include/msg.h
+recipient.o: ../../include/msg_stats.h
+recipient.o: ../../include/myflock.h
+recipient.o: ../../include/mymalloc.h
+recipient.o: ../../include/mypwd.h
+recipient.o: ../../include/nvtable.h
+recipient.o: ../../include/recipient_list.h
+recipient.o: ../../include/resolve_clnt.h
+recipient.o: ../../include/split_addr.h
+recipient.o: ../../include/split_at.h
+recipient.o: ../../include/stat_as.h
+recipient.o: ../../include/stringops.h
+recipient.o: ../../include/strip_addr.h
+recipient.o: ../../include/sys_defs.h
+recipient.o: ../../include/tok822.h
+recipient.o: ../../include/vbuf.h
+recipient.o: ../../include/vstream.h
+recipient.o: ../../include/vstring.h
+recipient.o: local.h
+recipient.o: recipient.c
+resolve.o: ../../include/argv.h
+resolve.o: ../../include/attr.h
+resolve.o: ../../include/been_here.h
+resolve.o: ../../include/bounce.h
+resolve.o: ../../include/check_arg.h
+resolve.o: ../../include/defer.h
+resolve.o: ../../include/deliver_request.h
+resolve.o: ../../include/delivered_hdr.h
+resolve.o: ../../include/dict.h
+resolve.o: ../../include/dsn.h
+resolve.o: ../../include/dsn_buf.h
+resolve.o: ../../include/fold_addr.h
+resolve.o: ../../include/htable.h
+resolve.o: ../../include/iostuff.h
+resolve.o: ../../include/mail_params.h
+resolve.o: ../../include/mail_proto.h
+resolve.o: ../../include/maps.h
+resolve.o: ../../include/mbox_conf.h
+resolve.o: ../../include/msg.h
+resolve.o: ../../include/msg_stats.h
+resolve.o: ../../include/myflock.h
+resolve.o: ../../include/mymalloc.h
+resolve.o: ../../include/nvtable.h
+resolve.o: ../../include/recipient_list.h
+resolve.o: ../../include/resolve_clnt.h
+resolve.o: ../../include/rewrite_clnt.h
+resolve.o: ../../include/sys_defs.h
+resolve.o: ../../include/tok822.h
+resolve.o: ../../include/vbuf.h
+resolve.o: ../../include/vstream.h
+resolve.o: ../../include/vstring.h
+resolve.o: local.h
+resolve.o: resolve.c
+token.o: ../../include/argv.h
+token.o: ../../include/attr.h
+token.o: ../../include/been_here.h
+token.o: ../../include/bounce.h
+token.o: ../../include/check_arg.h
+token.o: ../../include/defer.h
+token.o: ../../include/deliver_request.h
+token.o: ../../include/delivered_hdr.h
+token.o: ../../include/dict.h
+token.o: ../../include/dsn.h
+token.o: ../../include/dsn_buf.h
+token.o: ../../include/fold_addr.h
+token.o: ../../include/htable.h
+token.o: ../../include/mail_params.h
+token.o: ../../include/maps.h
+token.o: ../../include/mbox_conf.h
+token.o: ../../include/msg.h
+token.o: ../../include/msg_stats.h
+token.o: ../../include/myflock.h
+token.o: ../../include/mymalloc.h
+token.o: ../../include/nvtable.h
+token.o: ../../include/readlline.h
+token.o: ../../include/recipient_list.h
+token.o: ../../include/resolve_clnt.h
+token.o: ../../include/stringops.h
+token.o: ../../include/sys_defs.h
+token.o: ../../include/tok822.h
+token.o: ../../include/vbuf.h
+token.o: ../../include/vstream.h
+token.o: ../../include/vstring.h
+token.o: ../../include/vstring_vstream.h
+token.o: local.h
+token.o: token.c
+unknown.o: ../../include/argv.h
+unknown.o: ../../include/attr.h
+unknown.o: ../../include/been_here.h
+unknown.o: ../../include/bounce.h
+unknown.o: ../../include/canon_addr.h
+unknown.o: ../../include/check_arg.h
+unknown.o: ../../include/defer.h
+unknown.o: ../../include/deliver_pass.h
+unknown.o: ../../include/deliver_request.h
+unknown.o: ../../include/delivered_hdr.h
+unknown.o: ../../include/dict.h
+unknown.o: ../../include/dsn.h
+unknown.o: ../../include/dsn_buf.h
+unknown.o: ../../include/fold_addr.h
+unknown.o: ../../include/htable.h
+unknown.o: ../../include/iostuff.h
+unknown.o: ../../include/mail_addr.h
+unknown.o: ../../include/mail_params.h
+unknown.o: ../../include/mail_proto.h
+unknown.o: ../../include/maps.h
+unknown.o: ../../include/mbox_conf.h
+unknown.o: ../../include/msg.h
+unknown.o: ../../include/msg_stats.h
+unknown.o: ../../include/myflock.h
+unknown.o: ../../include/mymalloc.h
+unknown.o: ../../include/nvtable.h
+unknown.o: ../../include/recipient_list.h
+unknown.o: ../../include/resolve_clnt.h
+unknown.o: ../../include/sent.h
+unknown.o: ../../include/stringops.h
+unknown.o: ../../include/sys_defs.h
+unknown.o: ../../include/tok822.h
+unknown.o: ../../include/vbuf.h
+unknown.o: ../../include/vstream.h
+unknown.o: ../../include/vstring.h
+unknown.o: local.h
+unknown.o: unknown.c
diff --git a/src/local/Musings b/src/local/Musings
new file mode 100644
index 0000000..6149a2e
--- /dev/null
+++ b/src/local/Musings
@@ -0,0 +1,39 @@
+Local delivery models
+
+The "monolithic" model: recursively expand the complete initial
+recipient list (via aliases, mailing lists, .forward files) to one
+expanded recipient list (mail addresses, shell commands, files,
+mailboxes). Sort/uniq the expanded recipient list, and deliver.
+
+The "forward as if sent by recipient" model: each level of recursion
+(aliases, mailing lists, forward files) takes one entire iteration
+through the mail system. Non-recursively expand one local recipient
+(via alias, mailing list, the recipient's .forward file) to a list
+of expanded recipients. Sort/uniq the list and deliver by re-injecting
+messages into the mail system. Since recipient expansion uses a
+non-recursive algorithm, the mailer might loop indefinitely,
+re-injecting messages into itself. These local forwarding loops
+must be broken by stamping a message when it reaches the local
+delivery stage (e.g., by adding a Delivered-To: message header).
+
+The Postfix system uses a hybrid approach. It does recursive alias
+expansion, but only one initial recipient at a time. It delivers
+to expanded recipients by re-submitting the message into the mail
+system, so it can keep track of the delivery status for each expanded
+recipient. Because alias expansion does not look in .forward files,
+it cannot prevent local forwarding loops. The Postfix system adds
+Delivered: message headers to break local and external forwarding
+loops.
+
+Delivery status management
+
+The "exact" model: maintain on file the delivery status of each
+expanded recipient: remote recipients, shell commands and files,
+including the privileges for delivery to shell commands and files.
+
+The "safe" model: maintain on file only the delivery status of
+non-sensitive destinations (local or remote addresses). Deliver to
+sensitive destinations first (commands, files), but do not keep a
+record of their status on file (including privileges). This means
+that the mail system will occasionally deliver the same message
+more than once to a file or command.
diff --git a/src/local/alias.c b/src/local/alias.c
new file mode 100644
index 0000000..99e3dd6
--- /dev/null
+++ b/src/local/alias.c
@@ -0,0 +1,386 @@
+/*++
+/* NAME
+/* alias 3
+/* SUMMARY
+/* alias data base lookups
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_alias(state, usr_attr, name, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *name;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_alias() looks up the expansion of the recipient in
+/* the global alias database and delivers the message to the
+/* listed destinations. The result is zero when no alias was found
+/* or when the message should be delivered to the user instead.
+/*
+/* deliver_alias() has wired-in knowledge about a few reserved
+/* recipient names.
+/* .IP \(bu
+/* When no alias is found for the local \fIpostmaster\fR or
+/* \fImailer-daemon\fR a warning is issued and the message
+/* is discarded.
+/* .IP \(bu
+/* When an alias exists for recipient \fIname\fR, and an alias
+/* exists for \fIowner-name\fR, the sender address is changed
+/* to \fIowner-name\fR, and the owner delivery attribute is
+/* set accordingly. This feature is disabled with
+/* "owner_request_special = no".
+/* .PP
+/* Arguments:
+/* .IP state
+/* Attributes that specify the message, recipient and more.
+/* Expansion type (alias, include, .forward).
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* User attributes (rights, environment).
+/* .IP name
+/* The alias to be looked up.
+/* .IP statusp
+/* Delivery status. See below.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. The delivery status is non-zero
+/* when delivery should be tried again.
+/* 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>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <dict.h>
+#include <argv.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <defer.h>
+#include <maps.h>
+#include <bounce.h>
+#include <mypwd.h>
+#include <canon_addr.h>
+#include <sent.h>
+#include <trace.h>
+#include <dsn_mask.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* Application-specific. */
+
+#define NO 0
+#define YES 1
+
+/* deliver_alias - expand alias file entry */
+
+int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
+ char *name, int *statusp)
+{
+ const char *myname = "deliver_alias";
+ const char *alias_result;
+ char *saved_alias_result;
+ char *owner;
+ char **cpp;
+ struct mypasswd *alias_pwd;
+ VSTRING *canon_owner;
+ DICT *dict;
+ const char *owner_rhs; /* owner alias, RHS */
+ int alias_count;
+ int dsn_notify;
+ char *dsn_envid;
+ int dsn_ret;
+ const char *dsn_orcpt;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE/LOOP ELIMINATION
+ *
+ * We cannot do duplicate elimination here. Sendmail compatibility requires
+ * that we allow multiple deliveries to the same alias, even recursively!
+ * For example, we must deliver to mailbox any messages that are addressed
+ * to the alias of a user that lists that same alias in her own .forward
+ * file. Yuck! This is just an example of some really perverse semantics
+ * that people will expect Postfix to implement just like sendmail.
+ *
+ * We can recognize one special case: when an alias includes its own name,
+ * deliver to the user instead, just like sendmail. Otherwise, we just
+ * bail out when nesting reaches some unreasonable depth, and blame it on
+ * a possible alias loop.
+ */
+ if (state.msg_attr.exp_from != 0
+ && strcasecmp_utf8(state.msg_attr.exp_from, name) == 0)
+ return (NO);
+ if (state.level > 100) {
+ msg_warn("alias database loop for %s", name);
+ dsb_simple(state.msg_attr.why, "5.4.6",
+ "alias database loop for %s", name);
+ *statusp = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ state.msg_attr.exp_from = name;
+
+ /*
+ * There are a bunch of roles that we're trying to keep track of.
+ *
+ * First, there's the issue of whose rights should be used when delivering
+ * to "|command" or to /file/name. With alias databases, the rights are
+ * those of who owns the alias, i.e. the database owner. With aliases
+ * owned by root, a default user is used instead. When an alias with
+ * default rights references an include file owned by an ordinary user,
+ * we must use the rights of the include file owner, otherwise the
+ * include file owner could take control of the default account.
+ *
+ * Secondly, there's the question of who to notify of delivery problems.
+ * With aliases that have an owner- alias, the latter is used to set the
+ * sender and owner attributes. Otherwise, the owner attribute is reset
+ * (the alias is globally visible and could be sent to by anyone).
+ */
+ for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
+ if ((dict = dict_handle(*cpp)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *cpp);
+ if ((alias_result = dict_get(dict, name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);
+
+ /*
+ * Don't expand a verify-only request.
+ */
+ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
+ dsb_simple(state.msg_attr.why, "2.0.0",
+ "aliased to %s", alias_result);
+ *statusp = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ return (YES);
+ }
+
+ /*
+ * DELIVERY POLICY
+ *
+ * Update the expansion type attribute, so we can decide if
+ * deliveries to |command and /file/name are allowed at all.
+ */
+ state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * What rights to use for |command and /file/name deliveries? The
+ * command and file code will use default rights when the alias
+ * database is owned by root, otherwise it will use the rights of
+ * the alias database owner.
+ */
+ if (dict->owner.status == DICT_OWNER_TRUSTED) {
+ alias_pwd = 0;
+ RESET_USER_ATTR(usr_attr, state.level);
+ } else {
+ if (dict->owner.status == DICT_OWNER_UNKNOWN) {
+ msg_warn("%s: no owner UID for alias database %s",
+ myname, *cpp);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ if ((errno = mypwuid_err(dict->owner.uid, &alias_pwd)) != 0
+ || alias_pwd == 0) {
+ msg_warn(errno ?
+ "cannot find alias database owner for %s: %m" :
+ "cannot find alias database owner for %s", *cpp);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "cannot find alias database owner");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ SET_USER_ATTR(usr_attr, alias_pwd, state.level);
+ }
+
+ /*
+ * WHERE TO REPORT DELIVERY PROBLEMS.
+ *
+ * Use the owner- alias if one is specified, otherwise reset the
+ * owner attribute and use the include file ownership if we can.
+ * Save the dict_lookup() result before something clobbers it.
+ *
+ * Don't match aliases that are based on regexps.
+ */
+#define OWNER_ASSIGN(own) \
+ (own = (var_ownreq_special == 0 ? 0 : \
+ concatenate("owner-", name, (char *) 0)))
+
+ saved_alias_result = mystrdup(alias_result);
+ if (OWNER_ASSIGN(owner) != 0
+ && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
+ canon_owner = canon_addr_internal(vstring_alloc(10),
+ var_exp_own_alias ? owner_rhs : owner);
+ /* Set envelope sender and owner attribute. */
+ SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
+ } else {
+ canon_owner = 0;
+ /* Note: this does not reset the envelope sender. */
+ if (var_reset_owner_attr)
+ RESET_OWNER_ATTR(state.msg_attr, state.level);
+ }
+
+ /*
+ * EXTERNAL LOOP CONTROL
+ *
+ * Set the delivered message attribute to the recipient, so that
+ * this message will list the correct forwarding address.
+ */
+ if (var_frozen_delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+
+ /*
+ * Deliver.
+ */
+ alias_count = 0;
+ if (owner != 0 && alias_maps->error != 0) {
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+
+ /*
+ * XXX DSN
+ *
+ * When delivering to a mailing list (i.e. the envelope sender
+ * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters
+ * which accompany the redistributed message MUST NOT be
+ * derived from those of the original message.
+ *
+ * When delivering to an alias (i.e. the envelope sender is not
+ * replaced) any ENVID, RET, or ORCPT parameters are
+ * propagated to all forwarding addresses associated with
+ * that alias. The NOTIFY parameter is propagated to the
+ * forwarding addresses, except that any SUCCESS keyword is
+ * removed.
+ */
+#define DSN_SAVE_UPDATE(saved, old, new) do { \
+ saved = old; \
+ old = new; \
+ } while (0)
+
+ DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify,
+ dsn_notify == DSN_NOTIFY_SUCCESS ?
+ DSN_NOTIFY_NEVER :
+ dsn_notify & ~DSN_NOTIFY_SUCCESS);
+ if (canon_owner != 0) {
+ DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, "");
+ DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0);
+ DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, "");
+ state.msg_attr.rcpt.orig_addr = "";
+ }
+ *statusp =
+ deliver_token_string(state, usr_attr, saved_alias_result,
+ &alias_count);
+#if 0
+ if (var_ownreq_special
+ && strncmp("owner-", state.msg_attr.sender, 6) != 0
+ && alias_count > 10)
+ msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
+ name, name);
+#endif
+ if (alias_count < 1) {
+ msg_warn("no recipient in alias lookup result for %s", name);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+
+ /*
+ * XXX DSN
+ *
+ * When delivering to a mailing list (i.e. the envelope
+ * sender address is replaced) and NOTIFY=SUCCESS was
+ * specified, report a DSN of "delivered".
+ *
+ * When delivering to an alias (i.e. the envelope sender
+ * address is not replaced) and NOTIFY=SUCCESS was
+ * specified, report a DSN of "expanded".
+ */
+ if (dsn_notify & DSN_NOTIFY_SUCCESS) {
+ state.msg_attr.rcpt.dsn_notify = dsn_notify;
+ if (canon_owner != 0) {
+ state.msg_attr.dsn_envid = dsn_envid;
+ state.msg_attr.dsn_ret = dsn_ret;
+ state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt;
+ }
+ dsb_update(state.msg_attr.why, "2.0.0", canon_owner ?
+ "delivered" : "expanded",
+ DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "alias expanded");
+ (void) trace_append(BOUNCE_FLAG_NONE,
+ SENT_ATTR(state.msg_attr));
+ }
+ }
+ }
+ myfree(saved_alias_result);
+ if (owner)
+ myfree(owner);
+ if (canon_owner)
+ vstring_free(canon_owner);
+ if (alias_pwd)
+ mypwfree(alias_pwd);
+ return (YES);
+ }
+
+ /*
+ * If the alias database was inaccessible for some reason, defer
+ * further delivery for the current top-level recipient.
+ */
+ if (alias_result == 0 && dict->error != 0) {
+ msg_warn("%s:%s: lookup of '%s' failed",
+ dict->type, dict->name, name);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: %s: %s not found", myname, *cpp, name);
+ }
+ }
+
+ /*
+ * Try delivery to a local user instead.
+ */
+ return (NO);
+}
diff --git a/src/local/biff_notify.c b/src/local/biff_notify.c
new file mode 100644
index 0000000..a6a4925
--- /dev/null
+++ b/src/local/biff_notify.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* biff_notify 3
+/* SUMMARY
+/* send biff notification
+/* SYNOPSIS
+/* #include <biff_notify.h>
+/*
+/* void biff_notify(text, len)
+/* const char *text;
+/* ssize_t len;
+/* DESCRIPTION
+/* biff_notify() sends a \fBBIFF\fR notification request to the
+/* \fBcomsat\fR daemon.
+/*
+/* Arguments:
+/* .IP text
+/* Null-terminated text (username@mailbox-offset).
+/* .IP len
+/* Length of text, including null terminator.
+/* BUGS
+/* The \fBBIFF\fR "service" can be a noticeable load for
+/* systems that have many logged-in users.
+/* 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/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+#include <biff_notify.h>
+
+/* biff_notify - notify recipient via the biff "protocol" */
+
+void biff_notify(const char *text, ssize_t len)
+{
+ static struct sockaddr_in sin;
+ static int sock = -1;
+ struct hostent *hp;
+ struct servent *sp;
+
+ /*
+ * Initialize a socket address structure, or re-use an existing one.
+ */
+ if (sin.sin_family == 0) {
+ if ((sp = getservbyname("biff", "udp")) == 0) {
+ msg_warn("service not found: biff/udp");
+ return;
+ }
+ if ((hp = gethostbyname("localhost")) == 0) {
+ msg_warn("host not found: localhost");
+ return;
+ }
+ if ((int) hp->h_length > (int) sizeof(sin.sin_addr)) {
+ msg_warn("bad address size %d for localhost", hp->h_length);
+ return;
+ }
+ sin.sin_family = hp->h_addrtype;
+ sin.sin_port = sp->s_port;
+ memcpy((void *) &sin.sin_addr, hp->h_addr_list[0], hp->h_length);
+ }
+
+ /*
+ * Open a socket, or re-use an existing one.
+ */
+ if (sock < 0) {
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ msg_warn("socket: %m");
+ return;
+ }
+ close_on_exec(sock, CLOSE_ON_EXEC);
+ }
+
+ /*
+ * Biff!
+ */
+ if (sendto(sock, text, len, 0, (struct sockaddr *) &sin, sizeof(sin)) != len)
+ msg_warn("biff_notify: %m");
+}
diff --git a/src/local/biff_notify.h b/src/local/biff_notify.h
new file mode 100644
index 0000000..8b76f9d
--- /dev/null
+++ b/src/local/biff_notify.h
@@ -0,0 +1,30 @@
+#ifndef _BIFF_H_INCLUDED_
+#define _BIFF_H_INCLUDED_
+
+/*++
+/* NAME
+/* biff_notify 3h
+/* SUMMARY
+/* read logical line
+/* SYNOPSIS
+/* #include <biff_notify.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void biff_notify(const char *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/local/bounce_workaround.c b/src/local/bounce_workaround.c
new file mode 100644
index 0000000..7fe4aaa
--- /dev/null
+++ b/src/local/bounce_workaround.c
@@ -0,0 +1,159 @@
+/*++
+/* NAME
+/* bounce_workaround 3
+/* SUMMARY
+/* Send non-delivery notification with sender override
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int bounce_workaround(state)
+/* LOCAL_STATE state;
+/* DESCRIPTION
+/* This module works around a limitation in the bounce daemon
+/* protocol, namely, the assumption that the envelope sender
+/* address in a queue file is the delivery status notification
+/* address for all recipients in that queue file. The assumption
+/* is not valid when the local(8) delivery agent overrides the
+/* envelope sender address by an owner- alias, for one or more
+/* recipients in the queue file.
+/*
+/* Sender address override is a problem only when delivering
+/* to command or file, or when breaking a Delivered-To loop.
+/* The local(8) delivery agent saves normal recipients to a
+/* new queue file, together with the replacement envelope
+/* sender address; delivery then proceeds from that new queue
+/* file, and no workaround is needed.
+/*
+/* The workaround sends one non-delivery notification for each
+/* failed delivery that has a replacement sender address. The
+/* notifications are not aggregated, unlike notifications to
+/* non-replaced sender addresses. In practice, a local alias
+/* rarely has more than one file or command destination (if
+/* only because soft error handling is problematic).
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* The non-delivery status must be either 4.X.X or 5.X.X.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. The result is non-zero when
+/* the operation should be tried again. Warnings: malformed
+/* address.
+/* BUGS
+/* The proper fix is to record in the bounce logfile an error
+/* return address for each individual recipient. This would
+/* eliminate the need for VERP-specific bounce protocol code,
+/* and would move complexity from the bounce client side to
+/* the bounce server side where it more likely belongs.
+/* 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 <strings.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <strip_addr.h>
+#include <stringops.h>
+#include <bounce.h>
+#include <defer.h>
+#include <split_addr.h>
+#include <canon_addr.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+int bounce_workaround(LOCAL_STATE state)
+{
+ const char *myname = "bounce_workaround";
+ VSTRING *canon_owner = 0;
+ int rcpt_stat;
+
+ /*
+ * Look up the substitute sender address.
+ */
+ if (var_ownreq_special) {
+ char *stripped_recipient;
+ char *owner_alias;
+ const char *owner_expansion;
+
+#define FIND_OWNER(lhs, rhs, addr) { \
+ lhs = concatenate("owner-", addr, (char *) 0); \
+ (void) split_at_right(lhs, '@'); \
+ rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \
+ }
+
+ FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address);
+ if (alias_maps->error == 0 && owner_expansion == 0
+ && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address,
+ (char **) 0,
+ var_rcpt_delim)) != 0) {
+ myfree(owner_alias);
+ FIND_OWNER(owner_alias, owner_expansion, stripped_recipient);
+ myfree(stripped_recipient);
+ }
+ if (alias_maps->error == 0 && owner_expansion != 0) {
+ canon_owner = canon_addr_internal(vstring_alloc(10),
+ var_exp_own_alias ?
+ owner_expansion : owner_alias);
+ SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
+ }
+ myfree(owner_alias);
+ if (alias_maps->error != 0) {
+ /* At this point, canon_owner == 0. */
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ }
+
+ /*
+ * Send a delivery status notification with a single recipient to the
+ * substitute sender address, before completion of the delivery request.
+ */
+ if (canon_owner) {
+ rcpt_stat =
+ (STR(state.msg_attr.why->status)[0] == '4' ?
+ defer_one : bounce_one)
+ (BOUNCE_FLAGS(state.request),
+ BOUNCE_ONE_ATTR(state.msg_attr));
+ vstring_free(canon_owner);
+ }
+
+ /*
+ * Send a regular delivery status notification, after completion of the
+ * delivery request.
+ */
+ else {
+ rcpt_stat =
+ (STR(state.msg_attr.why->status)[0] == '4' ?
+ defer_append : bounce_append)
+ (BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ }
+ return (rcpt_stat);
+}
diff --git a/src/local/command.c b/src/local/command.c
new file mode 100644
index 0000000..4781daf
--- /dev/null
+++ b/src/local/command.c
@@ -0,0 +1,251 @@
+/*++
+/* NAME
+/* command 3
+/* SUMMARY
+/* message delivery to shell command
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_command(state, usr_attr, command)
+/* LOCAL_STATE state;
+/* USER_ATTR exp_attr;
+/* const char *command;
+/* DESCRIPTION
+/* deliver_command() runs a command with a message as standard
+/* input. A limited amount of standard output and standard error
+/* output is captured for diagnostics purposes.
+/* Duplicate commands for the same recipient are suppressed.
+/* A limited amount of information is exported via the environment:
+/* HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire
+/* address) LOCAL (just the local part) and SENDER. The exported
+/* information is censored with var_cmd_filter.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing the alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP command
+/* The shell command to be executed. If possible, the command is
+/* executed without actually invoking a shell. if the command is
+/* the mailbox_command, it is subjected to $name expansion.
+/* DIAGNOSTICS
+/* deliver_command() returns non-zero when delivery should be
+/* tried again,
+/* SEE ALSO
+/* mailbox(3) deliver to mailbox
+/* 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 <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <mac_parse.h>
+
+/* Global library. */
+
+#include <defer.h>
+#include <bounce.h>
+#include <sent.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <pipe_command.h>
+#include <mail_copy.h>
+#include <dsn_util.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_command - deliver to shell command */
+
+int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command)
+{
+ const char *myname = "deliver_command";
+ DSN_BUF *why = state.msg_attr.why;
+ int cmd_status;
+ int deliver_status;
+ ARGV *env;
+ int copy_flags;
+ char **cpp;
+ char *cp;
+ ARGV *export_env;
+ VSTRING *exec_dir;
+ int expand_status;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE ELIMINATION
+ *
+ * Skip this command if it was already delivered to as this user.
+ */
+ if (been_here(state.dup_filter, "command %s:%ld %s",
+ state.msg_attr.user, (long) usr_attr.uid, command))
+ return (0);
+
+ /*
+ * Don't deliver a trace-only request.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to command: %s", command);
+ return (sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * Choose a default uid and gid when none have been selected (i.e. values
+ * are still zero).
+ */
+ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0)
+ msg_panic("privileged default user id");
+ if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0)
+ msg_panic("privileged default group id");
+
+ /*
+ * Deliver.
+ */
+ copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH
+ | MAIL_COPY_ORIG_RCPT;
+ if (local_deliver_hdr_mask & DELIVER_HDR_CMD)
+ copy_flags |= MAIL_COPY_DELIVERED;
+
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("%s: seek queue file %s: %m",
+ myname, VSTREAM_PATH(state.msg_attr.fp));
+
+ /*
+ * Pass additional environment information. XXX This should be
+ * configurable. However, passing untrusted information via environment
+ * parameters opens up a whole can of worms. Lesson from web servers:
+ * don't let any network data even near a shell. It causes trouble.
+ */
+ env = argv_alloc(1);
+ if (usr_attr.home)
+ argv_add(env, "HOME", usr_attr.home, ARGV_END);
+ argv_add(env,
+ "LOGNAME", state.msg_attr.user,
+ "USER", state.msg_attr.user,
+ "SENDER", state.msg_attr.sender,
+ "RECIPIENT", state.msg_attr.rcpt.address,
+ "LOCAL", state.msg_attr.local,
+ ARGV_END);
+ if (usr_attr.shell)
+ argv_add(env, "SHELL", usr_attr.shell, ARGV_END);
+ if (state.msg_attr.domain)
+ argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END);
+ if (state.msg_attr.extension)
+ argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END);
+ if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0])
+ argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr,
+ ARGV_END);
+
+#define EXPORT_REQUEST(name, value) \
+ if ((value)[0]) argv_add(env, (name), (value), ARGV_END);
+
+ EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name);
+ EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr);
+ EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo);
+ EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto);
+ EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method);
+ EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender);
+ EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username);
+
+ argv_terminate(env);
+
+ /*
+ * Censor out undesirable characters from exported data.
+ */
+ for (cpp = env->argv; *cpp; cpp += 2)
+ for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;)
+ *cp++ = '_';
+
+ /*
+ * Evaluate the command execution directory. Defer delivery if expansion
+ * fails.
+ */
+ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
+ exec_dir = vstring_alloc(10);
+ expand_status = local_expand(exec_dir, var_exec_directory,
+ &state, &usr_attr, var_exec_exp_filter);
+
+ if (expand_status & MAC_PARSE_ERROR) {
+ cmd_status = PIPE_STAT_DEFER;
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ msg_warn("bad parameter value syntax for %s: %s",
+ VAR_EXEC_DIRECTORY, var_exec_directory);
+ } else {
+ cmd_status = pipe_command(state.msg_attr.fp, why,
+ CA_PIPE_CMD_UID(usr_attr.uid),
+ CA_PIPE_CMD_GID(usr_attr.gid),
+ CA_PIPE_CMD_COMMAND(command),
+ CA_PIPE_CMD_COPY_FLAGS(copy_flags),
+ CA_PIPE_CMD_SENDER(state.msg_attr.sender),
+ CA_PIPE_CMD_ORIG_RCPT(state.msg_attr.rcpt.orig_addr),
+ CA_PIPE_CMD_DELIVERED(state.msg_attr.delivered),
+ CA_PIPE_CMD_TIME_LIMIT(var_command_maxtime),
+ CA_PIPE_CMD_ENV(env->argv),
+ CA_PIPE_CMD_EXPORT(export_env->argv),
+ CA_PIPE_CMD_SHELL(var_local_cmd_shell),
+ CA_PIPE_CMD_CWD(*STR(exec_dir) ?
+ STR(exec_dir) : (char *) 0),
+ CA_PIPE_CMD_END);
+ }
+ vstring_free(exec_dir);
+ argv_free(export_env);
+ argv_free(env);
+
+ /*
+ * Depending on the result, bounce or defer the message.
+ */
+ switch (cmd_status) {
+ case PIPE_STAT_OK:
+ dsb_simple(why, "2.0.0", "delivered to command: %s", command);
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ break;
+ case PIPE_STAT_BOUNCE:
+ case PIPE_STAT_DEFER:
+ /* Account for possible owner- sender address override. */
+ deliver_status = bounce_workaround(state);
+ break;
+ case PIPE_STAT_CORRUPT:
+ deliver_status = DEL_STAT_DEFER;
+ break;
+ default:
+ msg_panic("%s: bad status %d", myname, cmd_status);
+ /* NOTREACHED */
+ }
+
+ return (deliver_status);
+}
diff --git a/src/local/deliver_attr.c b/src/local/deliver_attr.c
new file mode 100644
index 0000000..9ed18e2
--- /dev/null
+++ b/src/local/deliver_attr.c
@@ -0,0 +1,105 @@
+/*++
+/* NAME
+/* deliver_attr 3
+/* SUMMARY
+/* initialize message delivery attributes
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* void deliver_attr_init(attrp)
+/* DELIVER_ATTR *attrp;
+/*
+/* void deliver_attr_dump(attrp)
+/* DELIVER_ATTR *attrp;
+/*
+/* void deliver_attr_free(attrp)
+/* DELIVER_ATTR *attrp;
+/* DESCRIPTION
+/* deliver_attr_init() initializes a structure with message delivery
+/* attributes to a known initial state (all zeros).
+/*
+/* deliver_attr_dump() logs the contents of the given attribute list.
+/*
+/* deliver_attr_free() releases memory that was allocated by
+/* deliver_attr_init().
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_attr_init - set message delivery attributes to all-zero state */
+
+void deliver_attr_init(DELIVER_ATTR *attrp)
+{
+ attrp->level = 0;
+ attrp->fp = 0;
+ attrp->queue_name = 0;
+ attrp->queue_id = 0;
+ attrp->offset = 0;
+ attrp->sender = 0;
+ RECIPIENT_ASSIGN(&(attrp->rcpt), 0, 0, 0, 0, 0);
+ attrp->domain = 0;
+ attrp->local = 0;
+ attrp->user = 0;
+ attrp->extension = 0;
+ attrp->unmatched = 0;
+ attrp->owner = 0;
+ attrp->delivered = 0;
+ attrp->relay = 0;
+ attrp->exp_type = 0;
+ attrp->exp_from = 0;
+ attrp->why = dsb_create();
+}
+
+/* deliver_attr_dump - log message delivery attributes */
+
+void deliver_attr_dump(DELIVER_ATTR *attrp)
+{
+ msg_info("level: %d", attrp->level);
+ msg_info("path: %s", VSTREAM_PATH(attrp->fp));
+ msg_info("fp: 0x%lx", (long) attrp->fp);
+ msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null");
+ msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null");
+ msg_info("offset: %ld", attrp->rcpt.offset);
+ msg_info("sender: %s", attrp->sender ? attrp->sender : "null");
+ msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null");
+ msg_info("domain: %s", attrp->domain ? attrp->domain : "null");
+ msg_info("local: %s", attrp->local ? attrp->local : "null");
+ msg_info("user: %s", attrp->user ? attrp->user : "null");
+ msg_info("extension: %s", attrp->extension ? attrp->extension : "null");
+ msg_info("unmatched: %s", attrp->unmatched ? attrp->unmatched : "null");
+ msg_info("owner: %s", attrp->owner ? attrp->owner : "null");
+ msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null");
+ msg_info("relay: %s", attrp->relay ? attrp->relay : "null");
+ msg_info("exp_type: %d", attrp->exp_type);
+ msg_info("exp_from: %s", attrp->exp_from ? attrp->exp_from : "null");
+ msg_info("why: %s", attrp->why ? "buffer" : "null");
+}
+
+/* deliver_attr_free - release storage */
+
+void deliver_attr_free(DELIVER_ATTR *attrp)
+{
+ dsb_free(attrp->why);
+}
diff --git a/src/local/dotforward.c b/src/local/dotforward.c
new file mode 100644
index 0000000..3ce2cfc
--- /dev/null
+++ b/src/local/dotforward.c
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/* dotforward 3
+/* SUMMARY
+/* $HOME/.forward file expansion
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_dotforward(state, usr_attr, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_dotforward() delivers a message to the destinations
+/* listed in a recipient's .forward file(s) as specified through
+/* the forward_path configuration parameter. The result is
+/* zero when no acceptable .forward file was found, or when
+/* a recipient is listed in her own .forward file. Expansions
+/* are scrutinized with the forward_expansion_filter parameter.
+/*
+/* Arguments:
+/* .IP state
+/* Message delivery attributes (sender, recipient etc.).
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP statusp
+/* Message delivery status. See below.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Warnings: bad $HOME/.forward
+/* file type, permissions or ownership. The message delivery
+/* status is non-zero when delivery should be tried again.
+/* SEE ALSO
+/* include(3) include file processor.
+/* 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>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <htable.h>
+#include <open_as.h>
+#include <lstat_as.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <mac_expand.h>
+
+/* Global library. */
+
+#include <mypwd.h>
+#include <bounce.h>
+#include <defer.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <ext_prop.h>
+#include <sent.h>
+#include <dsn_mask.h>
+#include <trace.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+#define NO 0
+#define YES 1
+
+/* deliver_dotforward - expand contents of .forward file */
+
+int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
+{
+ const char *myname = "deliver_dotforward";
+ struct stat st;
+ VSTRING *path;
+ struct mypasswd *mypwd;
+ int fd;
+ VSTREAM *fp;
+ int status;
+ int forward_found = NO;
+ int lookup_status;
+ int addr_count;
+ char *saved_forward_path;
+ char *lhs;
+ char *next;
+ int expand_status;
+ int saved_notify;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Skip this module if per-user forwarding is disabled.
+ */
+ if (*var_forward_path == 0)
+ return (NO);
+
+ /*
+ * Skip non-existing users. The mailbox delivery routine will catch the
+ * error.
+ */
+ if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) {
+ msg_warn("error looking up passwd info for %s: %m",
+ state.msg_attr.user);
+ dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ if (mypwd == 0)
+ return (NO);
+
+ /*
+ * From here on no early returns or we have a memory leak.
+ */
+
+ /*
+ * EXTERNAL LOOP CONTROL
+ *
+ * Set the delivered message attribute to the recipient, so that this
+ * message will list the correct forwarding address.
+ */
+ if (var_frozen_delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * Do not inherit rights from the .forward file owner. Instead, use the
+ * recipient's rights, and insist that the .forward file is owned by the
+ * recipient. This is a small but significant difference. Use the
+ * recipient's rights for all /file and |command deliveries, and pass on
+ * these rights to command/file destinations in included files. When
+ * these are the rights of root, the /file and |command delivery routines
+ * will use unprivileged default rights instead. Better safe than sorry.
+ */
+ SET_USER_ATTR(usr_attr, mypwd, state.level);
+
+ /*
+ * DELIVERY POLICY
+ *
+ * Update the expansion type attribute so that we can decide if deliveries
+ * to |command and /file/name are allowed at all.
+ */
+ state.msg_attr.exp_type = EXPAND_TYPE_FWD;
+
+ /*
+ * WHERE TO REPORT DELIVERY PROBLEMS
+ *
+ * Set the owner attribute so that 1) include files won't set the sender to
+ * be this user and 2) mail forwarded to other local users will be
+ * resubmitted as a new queue file.
+ */
+ state.msg_attr.owner = state.msg_attr.user;
+
+ /*
+ * Search the forward_path for an existing forward file.
+ *
+ * If unmatched extensions should never be propagated, or if a forward file
+ * name includes the address extension, don't propagate the extension to
+ * the recipient addresses.
+ */
+ status = 0;
+ path = vstring_alloc(100);
+ saved_forward_path = mystrdup(var_forward_path);
+ next = saved_forward_path;
+ lookup_status = -1;
+
+ while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) {
+ expand_status = local_expand(path, lhs, &state, &usr_attr,
+ var_fwd_exp_filter);
+ if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
+ lookup_status =
+ lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
+ if (msg_verbose)
+ msg_info("%s: path %s expand_status %d look_status %d", myname,
+ STR(path), expand_status, lookup_status);
+ if (lookup_status >= 0) {
+ if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0
+ || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0)
+ state.msg_attr.unmatched = 0;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Process the forward file.
+ *
+ * Assume that usernames do not have file system meta characters. Open the
+ * .forward file as the user. Ignore files that aren't regular files,
+ * files that are owned by the wrong user, or files that have world write
+ * permission enabled.
+ *
+ * DUPLICATE/LOOP ELIMINATION
+ *
+ * If this user includes (an alias of) herself in her own .forward file,
+ * deliver to the user instead.
+ */
+ if (lookup_status >= 0) {
+
+ /*
+ * Don't expand a verify-only request.
+ */
+ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
+ dsb_simple(state.msg_attr.why, "2.0.0",
+ "forward via file: %s", STR(path));
+ *statusp = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ forward_found = YES;
+ } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) {
+ state.msg_attr.exp_from = state.msg_attr.local;
+ if (S_ISREG(st.st_mode) == 0) {
+ msg_warn("file %s is not a regular file", STR(path));
+ } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) {
+ msg_warn("file %s has bad owner uid %ld",
+ STR(path), (long) st.st_uid);
+ } else if (st.st_mode & 002) {
+ msg_warn("file %s is world writable", STR(path));
+ } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) {
+ msg_warn("cannot open file %s: %m", STR(path));
+ } else {
+
+ /*
+ * XXX DSN. When delivering to an alias (i.e. the envelope
+ * sender address is not replaced) any ENVID, RET, or ORCPT
+ * parameters are propagated to all forwarding addresses
+ * associated with that alias. The NOTIFY parameter is
+ * propagated to the forwarding addresses, except that any
+ * SUCCESS keyword is removed.
+ */
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ addr_count = 0;
+ fp = vstream_fdopen(fd, O_RDONLY);
+ saved_notify = state.msg_attr.rcpt.dsn_notify;
+ state.msg_attr.rcpt.dsn_notify =
+ (saved_notify == DSN_NOTIFY_SUCCESS ?
+ DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS);
+ status = deliver_token_stream(state, usr_attr, fp, &addr_count);
+ if (vstream_fclose(fp))
+ msg_warn("close file %s: %m", STR(path));
+ if (addr_count > 0) {
+ forward_found = YES;
+ been_here(state.dup_filter, "forward-done %s", STR(path));
+
+ /*
+ * XXX DSN. When delivering to an alias (i.e. the
+ * envelope sender address is not replaced) and the
+ * original NOTIFY parameter for the alias contained the
+ * SUCCESS keyword, an "expanded" DSN is issued for the
+ * alias.
+ */
+ if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) {
+ state.msg_attr.rcpt.dsn_notify = saved_notify;
+ dsb_update(state.msg_attr.why, "2.0.0", "expanded",
+ DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "alias expanded");
+ (void) trace_append(BOUNCE_FLAG_NONE,
+ SENT_ATTR(state.msg_attr));
+ }
+ }
+ }
+ } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0)
+ forward_found = YES; /* else we're recursive */
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(path);
+ myfree(saved_forward_path);
+ mypwfree(mypwd);
+
+ *statusp = status;
+ return (forward_found);
+}
diff --git a/src/local/file.c b/src/local/file.c
new file mode 100644
index 0000000..0cc4c18
--- /dev/null
+++ b/src/local/file.c
@@ -0,0 +1,195 @@
+/*++
+/* NAME
+/* file 3
+/* SUMMARY
+/* mail delivery to arbitrary file
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_file(state, usr_attr, path)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *path;
+/* DESCRIPTION
+/* deliver_file() appends a message to a file, UNIX mailbox format,
+/* or qmail maildir format,
+/* with duplicate suppression. It will deliver only to non-executable
+/* regular files.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* .IP usr_attr
+/* Attributes describing user rights and environment information.
+/* .IP path
+/* The file to deliver to. If the name ends in '/', delivery is done
+/* in qmail maildir format, otherwise delivery is done in UNIX mailbox
+/* format.
+/* DIAGNOSTICS
+/* deliver_file() returns non-zero when delivery should be tried again.
+/* SEE ALSO
+/* defer(3)
+/* bounce(3)
+/* 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>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <deliver_flock.h>
+#include <set_eugid.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <bounce.h>
+#include <defer.h>
+#include <sent.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <mbox_conf.h>
+#include <mbox_open.h>
+#include <dsn_util.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_file - deliver to file */
+
+int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path)
+{
+ const char *myname = "deliver_file";
+ struct stat st;
+ MBOX *mp;
+ DSN_BUF *why = state.msg_attr.why;
+ int mail_copy_status = MAIL_COPY_STAT_WRITE;
+ int deliver_status;
+ int copy_flags;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE ELIMINATION
+ *
+ * Skip this file if it was already delivered to as this user.
+ */
+ if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path))
+ return (0);
+
+ /*
+ * DELIVERY POLICY
+ *
+ * Do we allow delivery to files?
+ */
+ if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) {
+ dsb_simple(why, "5.7.1", "mail to file is restricted");
+ /* Account for possible owner- sender address override. */
+ return (bounce_workaround(state));
+ }
+
+ /*
+ * Don't deliver trace-only requests.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to file: %s", path);
+ return (sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * Use a default uid/gid when none are given.
+ */
+ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0)
+ msg_panic("privileged default user id");
+ if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0)
+ msg_panic("privileged default group id");
+
+ /*
+ * If the name ends in /, use maildir-style delivery instead.
+ */
+ if (path[strlen(path) - 1] == '/')
+ return (deliver_maildir(state, usr_attr, path));
+
+ /*
+ * Deliver. From here on, no early returns or we have a memory leak.
+ */
+ if (msg_verbose)
+ msg_info("deliver_file (%ld,%ld): %s",
+ (long) usr_attr.uid, (long) usr_attr.gid, path);
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id);
+
+ /*
+ * As the specified user, open or create the file, lock it, and append
+ * the message.
+ */
+ copy_flags = MAIL_COPY_MBOX;
+ if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
+ copy_flags &= ~MAIL_COPY_DELIVERED;
+
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY,
+ S_IRUSR | S_IWUSR, &st, -1, -1,
+ local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL,
+ "5.2.0", why);
+ if (mp != 0) {
+ if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ vstream_fclose(mp->fp);
+ dsb_simple(why, "5.7.1", "file is executable");
+ } else {
+ mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
+ S_ISREG(st.st_mode) ? copy_flags :
+ (copy_flags & ~MAIL_COPY_TOFILE),
+ "\n", why);
+ }
+ mbox_release(mp);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * As the mail system, bounce, defer delivery, or report success.
+ */
+ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
+ deliver_status = DEL_STAT_DEFER;
+ } else if (mail_copy_status != 0) {
+ vstring_sprintf_prepend(why->reason,
+ "cannot append message to file %s: ", path);
+ /* Account for possible owner- sender address override. */
+ deliver_status = bounce_workaround(state);
+ } else {
+ dsb_simple(why, "2.0.0", "delivered to file: %s", path);
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ }
+ return (deliver_status);
+}
diff --git a/src/local/forward.c b/src/local/forward.c
new file mode 100644
index 0000000..722dcf7
--- /dev/null
+++ b/src/local/forward.c
@@ -0,0 +1,393 @@
+/*++
+/* NAME
+/* forward 3
+/* SUMMARY
+/* message forwarding
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int forward_init()
+/*
+/* int forward_append(attr)
+/* DELIVER_ATTR attr;
+/*
+/* int forward_finish(request, attr, cancel)
+/* DELIVER_REQUEST *request;
+/* DELIVER_ATTR attr;
+/* int cancel;
+/* DESCRIPTION
+/* This module implements the client interface for message
+/* forwarding.
+/*
+/* forward_init() initializes internal data structures.
+/*
+/* forward_append() appends a recipient to the list of recipients
+/* that will receive a message with the specified message sender
+/* and delivered-to addresses.
+/*
+/* forward_finish() forwards the actual message contents and
+/* releases the memory allocated by forward_init() and by
+/* forward_append(). When the \fIcancel\fR argument is true, no
+/* messages will be forwarded. The \fIattr\fR argument specifies
+/* the original message delivery attributes as they were before
+/* alias or forward expansions.
+/* DIAGNOSTICS
+/* A non-zero result means that the requested operation should
+/* be tried again.
+/* Warnings: problems connecting to the forwarding service,
+/* corrupt message file. A corrupt message is saved to the
+/* "corrupt" queue for further inspection.
+/* Fatal: out of memory.
+/* Panic: missing forward_init() or forward_finish() call.
+/* 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/time.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <argv.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <iostuff.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <sent.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mark_corrupt.h>
+#include <mail_date.h>
+#include <mail_params.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+ /*
+ * Use one cleanup service connection for each (delivered to, sender) pair.
+ */
+static HTABLE *forward_dt;
+
+typedef struct FORWARD_INFO {
+ VSTREAM *cleanup; /* clean up service handle */
+ char *queue_id; /* forwarded message queue id */
+ struct timeval posting_time; /* posting time */
+} FORWARD_INFO;
+
+/* forward_init - prepare for forwarding */
+
+int forward_init(void)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (forward_dt != 0)
+ msg_panic("forward_init: missing forward_finish call");
+
+ forward_dt = htable_create(0);
+ return (0);
+}
+
+/* forward_open - open connection to cleanup service */
+
+static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender)
+{
+ VSTRING *buffer = vstring_alloc(100);
+ FORWARD_INFO *info;
+ VSTREAM *cleanup;
+
+#define FORWARD_OPEN_RETURN(res) do { \
+ vstring_free(buffer); \
+ return (res); \
+ } while (0)
+
+ /*
+ * Contact the cleanup service and save the new mail queue id. Request
+ * that the cleanup service bounces bad messages to the sender so that we
+ * can avoid the trouble of bounce management.
+ *
+ * In case you wonder what kind of bounces, examples are "too many hops",
+ * "message too large", perhaps some others. The reason not to bounce
+ * ourselves is that we don't really know who the recipients are.
+ */
+ cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING);
+ if (cleanup == 0) {
+ msg_warn("connect to %s/%s: %m",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ FORWARD_OPEN_RETURN(0);
+ }
+ close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC);
+ if (attr_scan(cleanup, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buffer),
+ ATTR_TYPE_END) != 1) {
+ vstream_fclose(cleanup);
+ FORWARD_OPEN_RETURN(0);
+ }
+ info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO));
+ info->cleanup = cleanup;
+ info->queue_id = mystrdup(STR(buffer));
+ GETTIMEOFDAY(&info->posting_time);
+
+#define FORWARD_CLEANUP_FLAGS \
+ (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL \
+ | smtputf8_autodetect(MAIL_SRC_MASK_FORWARD) \
+ | ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ? \
+ CLEANUP_FLAG_SMTPUTF8 : 0))
+
+ attr_print(cleanup, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS),
+ ATTR_TYPE_END);
+
+ /*
+ * Send initial message envelope information. For bounces, set the
+ * designated sender: mailing list owner, posting user, whatever.
+ */
+ rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(info->posting_time));
+ rec_fputs(cleanup, REC_TYPE_FROM, sender);
+
+ /*
+ * Don't send the original envelope ID or full/headers return mask if it
+ * was reset due to mailing list expansion.
+ */
+ if (request->dsn_ret)
+ rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, request->dsn_ret);
+ if (request->dsn_envid && *(request->dsn_envid))
+ rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, request->dsn_envid);
+
+ /*
+ * Zero-length attribute values are place holders for unavailable
+ * attribute values. See qmgr_message.c. They are not meant to be
+ * propagated to queue files.
+ */
+#define PASS_ATTR(fp, name, value) do { \
+ if ((value) && *(value)) \
+ rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \
+ } while (0)
+
+ /*
+ * XXX encapsulate these as one object.
+ */
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username);
+ PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender);
+ PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident);
+ PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context);
+
+ FORWARD_OPEN_RETURN(info);
+}
+
+/* forward_append - append recipient to message envelope */
+
+int forward_append(DELIVER_ATTR attr)
+{
+ FORWARD_INFO *info;
+ HTABLE *table_snd;
+
+ /*
+ * Sanity checks.
+ */
+ if (msg_verbose)
+ msg_info("forward delivered=%s sender=%s recip=%s",
+ attr.delivered, attr.sender, attr.rcpt.address);
+ if (forward_dt == 0)
+ msg_panic("forward_append: missing forward_init call");
+
+ /*
+ * In order to find the recipient list, first index a table by
+ * delivered-to header address, then by envelope sender address.
+ */
+ if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) {
+ table_snd = htable_create(0);
+ htable_enter(forward_dt, attr.delivered, (void *) table_snd);
+ }
+ if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) {
+ if ((info = forward_open(attr.request, attr.sender)) == 0)
+ return (-1);
+ htable_enter(table_snd, attr.sender, (void *) info);
+ }
+
+ /*
+ * Append the recipient to the message envelope. Don't send the original
+ * recipient or notification mask if it was reset due to mailing list
+ * expansion.
+ */
+ if (*attr.rcpt.dsn_orcpt)
+ rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt);
+ if (attr.rcpt.dsn_notify)
+ rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify);
+ if (*attr.rcpt.orig_addr)
+ rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr);
+ rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address);
+
+ return (vstream_ferror(info->cleanup));
+}
+
+/* forward_send - send forwarded message */
+
+static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request,
+ DELIVER_ATTR attr, char *delivered)
+{
+ const char *myname = "forward_send";
+ VSTRING *buffer = vstring_alloc(100);
+ VSTRING *folded;
+ int status;
+ int rec_type = 0;
+
+ /*
+ * Start the message content segment. Prepend our Delivered-To: header to
+ * the message data. Stop at the first error. XXX Rely on the front-end
+ * services to enforce record size limits.
+ */
+ rec_fputs(info->cleanup, REC_TYPE_MESG, "");
+ vstring_strcpy(buffer, delivered);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)",
+ var_myhostname, var_mail_name);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s",
+ info->queue_id, mail_date(info->posting_time.tv_sec));
+ if (local_deliver_hdr_mask & DELIVER_HDR_FWD) {
+ folded = vstring_alloc(100);
+ rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s",
+ casefold(folded, (STR(buffer))));
+ vstring_free(folded);
+ }
+ if ((status = vstream_ferror(info->cleanup)) == 0)
+ if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0)
+ msg_fatal("%s: seek queue file %s: %m:",
+ myname, VSTREAM_PATH(attr.fp));
+ while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) {
+ if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM)
+ break;
+ status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type);
+ }
+ if (status == 0 && rec_type != REC_TYPE_XTRA) {
+ msg_warn("%s: bad record type: %d in message content",
+ info->queue_id, rec_type);
+ status |= mark_corrupt(attr.fp);
+ }
+
+ /*
+ * Send the end-of-data marker only when there were no errors.
+ */
+ if (status == 0) {
+ rec_fputs(info->cleanup, REC_TYPE_XTRA, "");
+ rec_fputs(info->cleanup, REC_TYPE_END, "");
+ }
+
+ /*
+ * Retrieve the cleanup service completion status only if there are no
+ * problems.
+ */
+ if (status == 0)
+ if (vstream_fflush(info->cleanup)
+ || attr_scan(info->cleanup, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = 1;
+
+ /*
+ * Log successful forwarding.
+ *
+ * XXX DSN alias and .forward expansion already report SUCCESS, so don't do
+ * it again here.
+ */
+ if (status == 0) {
+ attr.rcpt.dsn_notify =
+ (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ?
+ DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS);
+ dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "forwarded as %s", info->queue_id);
+ status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr));
+ }
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buffer);
+ return (status);
+}
+
+/* forward_finish - complete message forwarding requests and clean up */
+
+int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel)
+{
+ HTABLE_INFO **dt_list;
+ HTABLE_INFO **dt;
+ HTABLE_INFO **sn_list;
+ HTABLE_INFO **sn;
+ HTABLE *table_snd;
+ char *delivered;
+ char *sender;
+ FORWARD_INFO *info;
+ int status = cancel;
+
+ /*
+ * Sanity checks.
+ */
+ if (forward_dt == 0)
+ msg_panic("forward_finish: missing forward_init call");
+
+ /*
+ * Walk over all delivered-to header addresses and over each envelope
+ * sender address.
+ */
+ for (dt = dt_list = htable_list(forward_dt); *dt; dt++) {
+ delivered = dt[0]->key;
+ table_snd = (HTABLE *) dt[0]->value;
+ for (sn = sn_list = htable_list(table_snd); *sn; sn++) {
+ sender = sn[0]->key;
+ info = (FORWARD_INFO *) sn[0]->value;
+ if (status == 0)
+ status |= forward_send(info, request, attr, delivered);
+ if (msg_verbose)
+ msg_info("forward_finish: delivered %s sender %s status %d",
+ delivered, sender, status);
+ (void) vstream_fclose(info->cleanup);
+ myfree(info->queue_id);
+ myfree((void *) info);
+ }
+ myfree((void *) sn_list);
+ htable_free(table_snd, (void (*) (void *)) 0);
+ }
+ myfree((void *) dt_list);
+ htable_free(forward_dt, (void (*) (void *)) 0);
+ forward_dt = 0;
+ return (status);
+}
diff --git a/src/local/include.c b/src/local/include.c
new file mode 100644
index 0000000..a213d3c
--- /dev/null
+++ b/src/local/include.c
@@ -0,0 +1,223 @@
+/*++
+/* NAME
+/* deliver_include 3
+/* SUMMARY
+/* deliver to addresses listed in include file
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_include(state, usr_attr, path)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *path;
+/* DESCRIPTION
+/* deliver_include() processes the contents of the named include
+/* file and delivers to each address listed. Some sanity checks
+/* are done on the include file permissions and type.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include, or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP path
+/* Pathname of the include file.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Warnings: bad include file type
+/* or permissions. The result is non-zero when delivery should be
+/* tried again.
+/* SEE ALSO
+/* token(3) tokenize list
+/* 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>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <open_as.h>
+#include <stat_as.h>
+#include <iostuff.h>
+#include <mypwd.h>
+
+/* Global library. */
+
+#include <bounce.h>
+#include <defer.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <ext_prop.h>
+#include <sent.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_include - open include file and deliver */
+
+int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path)
+{
+ const char *myname = "deliver_include";
+ struct stat st;
+ struct mypasswd *file_pwd = 0;
+ int status;
+ VSTREAM *fp;
+ int fd;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE ELIMINATION
+ *
+ * Don't process this include file more than once as this particular user.
+ */
+ if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path))
+ return (0);
+ state.msg_attr.exp_from = state.msg_attr.local;
+
+ /*
+ * Can of worms. Allow this include file to be symlinked, but disallow
+ * inclusion of special files or of files with world write permission
+ * enabled.
+ */
+ if (*path != '/') {
+ msg_warn(":include:%s uses a relative path", path);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) {
+ msg_warn("unable to lookup :include: file %s: %m", path);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (S_ISREG(st.st_mode) == 0) {
+ msg_warn(":include: file %s is not a regular file", path);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (st.st_mode & S_IWOTH) {
+ msg_warn(":include: file %s is world writable", path);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * DELIVERY POLICY
+ *
+ * Set the expansion type attribute so that we can decide if destinations
+ * such as /file/name and |command are allowed at all.
+ */
+ state.msg_attr.exp_type = EXPAND_TYPE_INCL;
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * When a non-root include file is listed in a root-owned alias, use the
+ * rights of the include file owner. We do not want to give the include
+ * file owner control of the default account.
+ *
+ * When an include file is listed in a user-owned alias or .forward file,
+ * leave the delivery rights alone. Users should not be able to make
+ * things happen with someone else's rights just by including some file
+ * that is owned by their victim.
+ */
+ if (usr_attr.uid == 0) {
+ if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) {
+ msg_warn(errno ? "cannot find username for uid %ld: %m" :
+ "cannot find username for uid %ld", (long) st.st_uid);
+ msg_warn("%s: cannot find :include: file owner username", path);
+ dsb_simple(state.msg_attr.why, "4.3.5",
+ "mail system configuration error");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (file_pwd->pw_uid != 0)
+ SET_USER_ATTR(usr_attr, file_pwd, state.level);
+ }
+
+ /*
+ * MESSAGE FORWARDING
+ *
+ * When no owner attribute is set (either via an owner- alias, or as part of
+ * .forward file processing), set the owner attribute, to disable direct
+ * delivery of local recipients. By now it is clear that the owner
+ * attribute should have been called forwarder instead.
+ */
+ if (state.msg_attr.owner == 0)
+ state.msg_attr.owner = state.msg_attr.rcpt.address;
+
+ /*
+ * From here on no early returns or we have a memory leak.
+ *
+ * FILE OPEN RIGHTS
+ *
+ * Use the delivery rights to open the include file. When no delivery rights
+ * were established sofar, the file containing the :include: is owned by
+ * root, so it should be OK to open any file that is accessible to root.
+ * The command and file delivery routines are responsible for setting the
+ * proper delivery rights. These are the rights of the default user, in
+ * case the :include: is in a root-owned alias.
+ *
+ * Don't propagate unmatched extensions unless permitted to do so.
+ */
+#define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \
+ vstream_fdopen(fd,O_RDONLY) : 0)
+
+ if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) {
+ msg_warn("cannot open include file %s: %m", path);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+ if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0)
+ state.msg_attr.unmatched = 0;
+ close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
+ status = deliver_token_stream(state, usr_attr, fp, (int *) 0);
+ if (vstream_fclose(fp))
+ msg_warn("close %s: %m", path);
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (file_pwd)
+ mypwfree(file_pwd);
+
+ return (status);
+}
diff --git a/src/local/indirect.c b/src/local/indirect.c
new file mode 100644
index 0000000..a9699a5
--- /dev/null
+++ b/src/local/indirect.c
@@ -0,0 +1,94 @@
+/*++
+/* NAME
+/* indirect 3
+/* SUMMARY
+/* indirect delivery
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* void deliver_indirect(state)
+/* LOCAL_STATE state;
+/* char *recipient;
+/* DESCRIPTION
+/* deliver_indirect() delivers a message via the message
+/* forwarding service, with duplicate filtering up to a
+/* configurable number of recipients.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender and more.
+/* A table with the results from expanding aliases or lists.
+/* CONFIGURATION VARIABLES
+/* duplicate_filter_limit, duplicate filter size limit
+/* DIAGNOSTICS
+/* The result is non-zero when the operation should be tried again.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <bounce.h>
+#include <defer.h>
+#include <been_here.h>
+#include <sent.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_indirect - deliver mail via forwarding service */
+
+int deliver_indirect(LOCAL_STATE state)
+{
+
+ /*
+ * Suppress duplicate expansion results. Add some sugar to the name to
+ * avoid collisions with other duplicate filters. Allow the user to
+ * specify an upper bound on the size of the duplicate filter, so that we
+ * can handle huge mailing lists with millions of recipients.
+ */
+ if (msg_verbose)
+ msg_info("deliver_indirect: %s", state.msg_attr.rcpt.address);
+ if (been_here(state.dup_filter, "indirect %s",
+ state.msg_attr.rcpt.address))
+ return (0);
+
+ /*
+ * Don't forward a trace-only request.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(state.msg_attr.why, "2.0.0", "forwards to %s",
+ state.msg_attr.rcpt.address);
+ return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Send the address to the forwarding service. Inherit the delivered
+ * attribute from the alias or from the .forward file owner.
+ */
+ if (forward_append(state.msg_attr)) {
+ dsb_simple(state.msg_attr.why, "4.3.0", "unable to forward message");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ return (0);
+}
diff --git a/src/local/local.c b/src/local/local.c
new file mode 100644
index 0000000..32bdea7
--- /dev/null
+++ b/src/local/local.c
@@ -0,0 +1,988 @@
+/*++
+/* NAME
+/* local 8
+/* SUMMARY
+/* Postfix local mail delivery
+/* SYNOPSIS
+/* \fBlocal\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBlocal\fR(8) daemon processes delivery requests from the
+/* Postfix queue manager to deliver mail to local recipients.
+/* Each delivery request specifies a queue file, a sender address,
+/* a domain or host to deliver to, and one or more recipients.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The \fBlocal\fR(8) daemon updates queue files and marks recipients
+/* as finished, or it informs the queue manager that delivery should
+/* be tried again at a later time. Delivery status reports are sent
+/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as
+/* appropriate.
+/* CASE FOLDING
+/* .ad
+/* .fi
+/* All delivery decisions are made using the bare recipient
+/* name (i.e. the address localpart), folded to lower case.
+/* See also under ADDRESS EXTENSION below for a few exceptions.
+/* SYSTEM-WIDE AND USER-LEVEL ALIASING
+/* .ad
+/* .fi
+/* The system administrator can set up one or more system-wide
+/* \fBsendmail\fR-style alias databases.
+/* Users can have \fBsendmail\fR-style ~/.\fBforward\fR files.
+/* Mail for \fIname\fR is delivered to the alias \fIname\fR, to
+/* destinations in ~\fIname\fR/.\fBforward\fR, to the mailbox owned
+/* by the user \fIname\fR, or it is sent back as undeliverable.
+/*
+/* The system administrator can specify a comma/space separated list
+/* of ~\fR/.\fBforward\fR like files through the \fBforward_path\fR
+/* configuration parameter. Upon delivery, the local delivery agent
+/* tries each pathname in the list until a file is found.
+/*
+/* Delivery via ~/.\fBforward\fR files is done with the privileges
+/* of the recipient.
+/* Thus, ~/.\fBforward\fR like files must be readable by the
+/* recipient, and their parent directory needs to have "execute"
+/* permission for the recipient.
+/*
+/* The \fBforward_path\fR parameter is subject to interpolation of
+/* \fB$user\fR (recipient username), \fB$home\fR (recipient home
+/* directory), \fB$shell\fR (recipient shell), \fB$recipient\fR
+/* (complete recipient address), \fB$extension\fR (recipient address
+/* extension), \fB$domain\fR (recipient domain), \fB$local\fR
+/* (entire recipient address localpart) and
+/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR
+/* and \fI${name?{value}}\fR (Postfix 3.0 and later) expand
+/* conditionally to \fIvalue\fR when \fI$name\fR is defined,
+/* and the forms \fI${name:value}\fR \fI${name:{value}}\fR
+/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR
+/* when \fI$name\fR is not defined. The form
+/* \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and later)
+/* expands conditionally to \fIvalue1\fR when \fI$name\fR is
+/* defined, or \fIvalue2\fR otherwise. Characters that may
+/* have special meaning to the shell or file system are replaced
+/* with underscores. The list of acceptable characters is
+/* specified with the \fBforward_expansion_filter\fR configuration
+/* parameter.
+/*
+/* An alias or ~/.\fBforward\fR file may list any combination of external
+/* commands, destination file names, \fB:include:\fR directives, or
+/* mail addresses.
+/* See \fBaliases\fR(5) for a precise description. Each line in a
+/* user's .\fBforward\fR file has the same syntax as the right-hand part
+/* of an alias.
+/*
+/* When an address is found in its own alias expansion, delivery is
+/* made to the user instead. When a user is listed in the user's own
+/* ~/.\fBforward\fR file, delivery is made to the user's mailbox instead.
+/* An empty ~/.\fBforward\fR file means do not forward mail.
+/*
+/* In order to prevent the mail system from using up unreasonable
+/* amounts of memory, input records read from \fB:include:\fR or from
+/* ~/.\fBforward\fR files are broken up into chunks of length
+/* \fBline_length_limit\fR.
+/*
+/* While expanding aliases, ~/.\fBforward\fR files, and so on, the
+/* program attempts to avoid duplicate deliveries. The
+/* \fBduplicate_filter_limit\fR configuration parameter limits the
+/* number of remembered recipients.
+/* MAIL FORWARDING
+/* .ad
+/* .fi
+/* For the sake of reliability, forwarded mail is re-submitted as
+/* a new message, so that each recipient has a separate on-file
+/* delivery status record.
+/*
+/* In order to stop mail forwarding loops early, the software adds an
+/* optional
+/* \fBDelivered-To:\fR header with the final envelope recipient address. If
+/* mail arrives for a recipient that is already listed in a
+/* \fBDelivered-To:\fR header, the message is bounced.
+/* MAILBOX DELIVERY
+/* .ad
+/* .fi
+/* The default per-user mailbox is a file in the UNIX mail spool
+/* directory (\fB/var/mail/\fIuser\fR or \fB/var/spool/mail/\fIuser\fR);
+/* the location can be specified with the \fBmail_spool_directory\fR
+/* configuration parameter. Specify a name ending in \fB/\fR for
+/* \fBqmail\fR-compatible \fBmaildir\fR delivery.
+/*
+/* Alternatively, the per-user mailbox can be a file in the user's home
+/* directory with a name specified via the \fBhome_mailbox\fR
+/* configuration parameter. Specify a relative path name. Specify a name
+/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery.
+/*
+/* Mailbox delivery can be delegated to an external command specified
+/* with the \fBmailbox_command_maps\fR and \fBmailbox_command\fR
+/* configuration parameters. The command
+/* executes with the privileges of the recipient user (exceptions:
+/* secondary groups are not enabled; in case of delivery as root,
+/* the command executes with the privileges of \fBdefault_privs\fR).
+/*
+/* Mailbox delivery can be delegated to alternative message transports
+/* specified in the \fBmaster.cf\fR file.
+/* The \fBmailbox_transport_maps\fR and \fBmailbox_transport\fR
+/* configuration parameters specify an optional
+/* message transport that is to be used for all local recipients,
+/* regardless of whether they are found in the UNIX passwd database.
+/* The \fBfallback_transport_maps\fR and
+/* \fBfallback_transport\fR parameters specify an optional
+/* message transport
+/* for recipients that are not found in the aliases(5) or UNIX
+/* passwd database.
+/*
+/* In the case of UNIX-style mailbox delivery,
+/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR"
+/* envelope header to each message, prepends an
+/* \fBX-Original-To:\fR header with the recipient address as given to
+/* Postfix, prepends an
+/* optional \fBDelivered-To:\fR header
+/* with the final envelope recipient address, prepends a \fBReturn-Path:\fR
+/* header with the envelope sender address, prepends a \fB>\fR character
+/* to lines beginning with "\fBFrom \fR", and appends an empty line.
+/* The mailbox is locked for exclusive access while delivery is in
+/* progress. In case of problems, an attempt is made to truncate the
+/* mailbox to its original length.
+/*
+/* In the case of \fBmaildir\fR delivery, the local daemon prepends
+/* an optional
+/* \fBDelivered-To:\fR header with the final envelope recipient address,
+/* prepends an
+/* \fBX-Original-To:\fR header with the recipient address as given to
+/* Postfix,
+/* and prepends a \fBReturn-Path:\fR header with the envelope sender
+/* address.
+/* EXTERNAL COMMAND DELIVERY
+/* .ad
+/* .fi
+/* The \fBallow_mail_to_commands\fR configuration parameter restricts
+/* delivery to external commands. The default setting (\fBalias,
+/* forward\fR) forbids command destinations in \fB:include:\fR files.
+/*
+/* Optionally, the process working directory is changed to the path
+/* specified with \fBcommand_execution_directory\fR (Postfix 2.2 and
+/* later). Failure to change directory causes mail to be deferred.
+/*
+/* The \fBcommand_execution_directory\fR parameter value is subject
+/* to interpolation of \fB$user\fR (recipient username),
+/* \fB$home\fR (recipient home directory), \fB$shell\fR
+/* (recipient shell), \fB$recipient\fR (complete recipient
+/* address), \fB$extension\fR (recipient address extension),
+/* \fB$domain\fR (recipient domain), \fB$local\fR (entire
+/* recipient address localpart) and \fB$recipient_delimiter.\fR
+/* The forms \fI${name?value}\fR and \fI${name?{value}}\fR
+/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR
+/* when \fI$name\fR is defined, and the forms \fI${name:value}\fR
+/* and \fI${name:{value}}\fR (Postfix 3.0 and later) expand
+/* conditionally to \fIvalue\fR when \fI$name\fR is not defined.
+/* The form \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and
+/* later) expands conditionally to \fIvalue1\fR when \fI$name\fR
+/* is defined, or \fIvalue2\fR otherwise. Characters that may
+/* have special meaning to the shell or file system are replaced
+/* with underscores. The list of acceptable characters
+/* is specified with the \fBexecution_directory_expansion_filter\fR
+/* configuration parameter.
+/*
+/* The command is executed directly where possible. Assistance by the
+/* shell (\fB/bin/sh\fR on UNIX systems) is used only when the command
+/* contains shell magic characters, or when the command invokes a shell
+/* built-in command.
+/*
+/* A limited amount of command output (standard output and standard
+/* error) is captured for inclusion with non-delivery status reports.
+/* A command is forcibly terminated if it does not complete within
+/* \fBcommand_time_limit\fR seconds. Command exit status codes are
+/* expected to follow the conventions defined in <\fBsysexits.h\fR>.
+/* Exit status 0 means normal successful completion.
+/*
+/* Postfix version 2.3 and later support RFC 3463-style enhanced
+/* status codes. If a command terminates with a non-zero exit
+/* status, and the command output begins with an enhanced
+/* status code, this status code takes precedence over the
+/* non-zero exit status.
+/*
+/* A limited amount of message context is exported via environment
+/* variables. Characters that may have special meaning to the shell
+/* are replaced with underscores. The list of acceptable characters
+/* is specified with the \fBcommand_expansion_filter\fR configuration
+/* parameter.
+/* .IP \fBSHELL\fR
+/* The recipient user's login shell.
+/* .IP \fBHOME\fR
+/* The recipient user's home directory.
+/* .IP \fBUSER\fR
+/* The bare recipient name.
+/* .IP \fBEXTENSION\fR
+/* The optional recipient address extension.
+/* .IP \fBDOMAIN\fR
+/* The recipient address domain part.
+/* .IP \fBLOGNAME\fR
+/* The bare recipient name.
+/* .IP \fBLOCAL\fR
+/* The entire recipient address localpart (text to the left of the
+/* rightmost @ character).
+/* .IP \fBORIGINAL_RECIPIENT\fR
+/* The entire recipient address, before any address rewriting
+/* or aliasing (Postfix 2.5 and later).
+/* .IP \fBRECIPIENT\fR
+/* The entire recipient address.
+/* .IP \fBSENDER\fR
+/* The entire sender address.
+/* .PP
+/* Additional remote client information is made available via
+/* the following environment variables:
+/* .IP \fBCLIENT_ADDRESS\fR
+/* Remote client network address. Available as of Postfix 2.2.
+/* .IP \fBCLIENT_HELO\fR
+/* Remote client EHLO command parameter. Available as of Postfix 2.2.
+/* .IP \fBCLIENT_HOSTNAME\fR
+/* Remote client hostname. Available as of Postfix 2.2.
+/* .IP \fBCLIENT_PROTOCOL\fR
+/* Remote client protocol. Available as of Postfix 2.2.
+/* .IP \fBSASL_METHOD\fR
+/* SASL authentication method specified in the
+/* remote client AUTH command. Available as of Postfix 2.2.
+/* .IP \fBSASL_SENDER\fR
+/* SASL sender address specified in the remote client MAIL
+/* FROM command. Available as of Postfix 2.2.
+/* .IP \fBSASL_USERNAME\fR
+/* SASL username specified in the remote client AUTH command.
+/* Available as of Postfix 2.2.
+/* .PP
+/* The \fBPATH\fR environment variable is always reset to a
+/* system-dependent default path, and environment variables
+/* whose names are blessed by the \fBexport_environment\fR
+/* configuration parameter are exported unchanged.
+/*
+/* The current working directory is the mail queue directory.
+/*
+/* The \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR"
+/* envelope header to each message, prepends an
+/* \fBX-Original-To:\fR header with the recipient address as given to
+/* Postfix, prepends an
+/* optional \fBDelivered-To:\fR
+/* header with the final recipient envelope address, prepends a
+/* \fBReturn-Path:\fR header with the sender envelope address,
+/* and appends no empty line.
+/* EXTERNAL FILE DELIVERY
+/* .ad
+/* .fi
+/* The delivery format depends on the destination filename syntax.
+/* The default is to use UNIX-style mailbox format. Specify a name
+/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery.
+/*
+/* The \fBallow_mail_to_files\fR configuration parameter restricts
+/* delivery to external files. The default setting (\fBalias,
+/* forward\fR) forbids file destinations in \fB:include:\fR files.
+/*
+/* In the case of UNIX-style mailbox delivery,
+/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR"
+/* envelope header to each message, prepends an
+/* \fBX-Original-To:\fR header with the recipient address as given to
+/* Postfix, prepends an
+/* optional \fBDelivered-To:\fR
+/* header with the final recipient envelope address, prepends a \fB>\fR
+/* character to lines beginning with "\fBFrom \fR", and appends an
+/* empty line.
+/* The envelope sender address is available in the \fBReturn-Path:\fR
+/* header.
+/* When the destination is a regular file, it is locked for exclusive
+/* access while delivery is in progress. In case of problems, an attempt
+/* is made to truncate a regular file to its original length.
+/*
+/* In the case of \fBmaildir\fR delivery, the local daemon prepends
+/* an optional
+/* \fBDelivered-To:\fR header with the final envelope recipient address,
+/* and prepends an
+/* \fBX-Original-To:\fR header with the recipient address as given to
+/* Postfix.
+/* The envelope sender address is available in the \fBReturn-Path:\fR
+/* header.
+/* ADDRESS EXTENSION
+/* .ad
+/* .fi
+/* The optional \fBrecipient_delimiter\fR configuration parameter
+/* specifies how to separate address extensions from local recipient
+/* names.
+/*
+/* For example, with "\fBrecipient_delimiter = +\fR", mail for
+/* \fIname\fR+\fIfoo\fR is delivered to the alias \fIname\fR+\fIfoo\fR
+/* or to the alias \fIname\fR, to the destinations listed in
+/* ~\fIname\fR/.\fBforward\fR+\fIfoo\fR or in ~\fIname\fR/.\fBforward\fR,
+/* to the mailbox owned by the user \fIname\fR, or it is sent back as
+/* undeliverable.
+/* DELIVERY RIGHTS
+/* .ad
+/* .fi
+/* Deliveries to external files and external commands are made with
+/* the rights of the receiving user on whose behalf the delivery is made.
+/* In the absence of a user context, the \fBlocal\fR(8) daemon uses the
+/* owner rights of the \fB:include:\fR file or alias database.
+/* When those files are owned by the superuser, delivery is made with
+/* the rights specified with the \fBdefault_privs\fR configuration
+/* parameter.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 3463 (Enhanced status codes)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are marked so that the queue
+/* manager can move them to the \fBcorrupt\fR queue afterwards.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBlocal\fR(8) delivery agent needs a dual personality
+/* 1) to access the private Postfix queue and IPC mechanisms,
+/* 2) to impersonate the recipient and deliver to recipient-specified
+/* files or commands. It is therefore security sensitive.
+/*
+/* The \fBlocal\fR(8) delivery agent disallows regular expression
+/* substitution of $1 etc. in \fBalias_maps\fR, because that
+/* would open a security hole.
+/*
+/* The \fBlocal\fR(8) delivery agent will silently ignore
+/* requests to use the \fBproxymap\fR(8) server within
+/* \fBalias_maps\fR. Instead it will open the table directly.
+/* Before Postfix version 2.2, the \fBlocal\fR(8) delivery
+/* agent will terminate with a fatal error.
+/* BUGS
+/* For security reasons, the message delivery status of external commands
+/* or of external files is never checkpointed to file. As a result,
+/* the program may occasionally deliver more than once to a command or
+/* external file. Better safe than sorry.
+/*
+/* Mutually-recursive aliases or ~/.\fBforward\fR files are not detected
+/* early. The resulting mail forwarding loop is broken by the use of the
+/* \fBDelivered-To:\fR message header.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBlocal\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 "\fBbiff (yes)\fR"
+/* Whether or not to use the local biff service.
+/* .IP "\fBexpand_owner_alias (no)\fR"
+/* When delivering to an alias "\fIaliasname\fR" that has an
+/* "owner-\fIaliasname\fR" companion alias, set the envelope sender
+/* address to the expansion of the "owner-\fIaliasname\fR" alias.
+/* .IP "\fBowner_request_special (yes)\fR"
+/* Enable special treatment for owner-\fIlistname\fR entries in the
+/* \fBaliases\fR(5) file, and don't split owner-\fIlistname\fR and
+/* \fIlistname\fR-request address localparts when the recipient_delimiter
+/* is set to "-".
+/* .IP "\fBsun_mailtool_compatibility (no)\fR"
+/* Obsolete SUN mailtool compatibility feature.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBfrozen_delivered_to (yes)\fR"
+/* Update the \fBlocal\fR(8) delivery agent's idea of the Delivered-To:
+/* address (see prepend_delivered_header) only once, at the start of
+/* a delivery attempt; do not update the Delivered-To: address while
+/* expanding aliases or .forward files.
+/* .PP
+/* Available in Postfix version 2.5.3 and later:
+/* .IP "\fBstrict_mailbox_ownership (yes)\fR"
+/* Defer delivery when a mailbox file is not owned by its recipient.
+/* .IP "\fBreset_owner_alias (no)\fR"
+/* Reset the \fBlocal\fR(8) delivery agent's idea of the owner-alias
+/* attribute, when delivering mail to a child alias that does not have
+/* its own owner alias.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBlocal_delivery_status_filter ($default_delivery_status_filter)\fR"
+/* Optional filter for the \fBlocal\fR(8) delivery agent to change the
+/* status code or explanatory text of successful or unsuccessful
+/* deliveries.
+/* DELIVERY METHOD CONTROLS
+/* .ad
+/* .fi
+/* The precedence of \fBlocal\fR(8) delivery methods from high to low is:
+/* aliases, .forward files, mailbox_transport_maps,
+/* mailbox_transport, mailbox_command_maps, mailbox_command,
+/* home_mailbox, mail_spool_directory, fallback_transport_maps,
+/* fallback_transport, and luser_relay.
+/* .IP "\fBalias_maps (see 'postconf -d' output)\fR"
+/* The alias databases that are used for \fBlocal\fR(8) delivery.
+/* .IP "\fBforward_path (see 'postconf -d' output)\fR"
+/* The \fBlocal\fR(8) delivery agent search list for finding a .forward
+/* file with user-specified delivery methods.
+/* .IP "\fBmailbox_transport_maps (empty)\fR"
+/* Optional lookup tables with per-recipient message delivery
+/* transports to use for \fBlocal\fR(8) mailbox delivery, whether or not the
+/* recipients are found in the UNIX passwd database.
+/* .IP "\fBmailbox_transport (empty)\fR"
+/* Optional message delivery transport that the \fBlocal\fR(8) delivery
+/* agent should use for mailbox delivery to all local recipients,
+/* whether or not they are found in the UNIX passwd database.
+/* .IP "\fBmailbox_command_maps (empty)\fR"
+/* Optional lookup tables with per-recipient external commands to use
+/* for \fBlocal\fR(8) mailbox delivery.
+/* .IP "\fBmailbox_command (empty)\fR"
+/* Optional external command that the \fBlocal\fR(8) delivery agent should
+/* use for mailbox delivery.
+/* .IP "\fBhome_mailbox (empty)\fR"
+/* Optional pathname of a mailbox file relative to a \fBlocal\fR(8) user's
+/* home directory.
+/* .IP "\fBmail_spool_directory (see 'postconf -d' output)\fR"
+/* The directory where \fBlocal\fR(8) UNIX-style mailboxes are kept.
+/* .IP "\fBfallback_transport_maps (empty)\fR"
+/* Optional lookup tables with per-recipient message delivery
+/* transports for recipients that the \fBlocal\fR(8) delivery agent could
+/* not find in the \fBaliases\fR(5) or UNIX password database.
+/* .IP "\fBfallback_transport (empty)\fR"
+/* Optional message delivery transport that the \fBlocal\fR(8) delivery
+/* agent should use for names that are not found in the \fBaliases\fR(5)
+/* or UNIX password database.
+/* .IP "\fBluser_relay (empty)\fR"
+/* Optional catch-all destination for unknown \fBlocal\fR(8) recipients.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBcommand_execution_directory (empty)\fR"
+/* The \fBlocal\fR(8) delivery agent working directory for delivery to
+/* external commands.
+/* MAILBOX LOCKING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdeliver_lock_attempts (20)\fR"
+/* The maximal number of attempts to acquire an exclusive lock on a
+/* mailbox file or \fBbounce\fR(8) logfile.
+/* .IP "\fBdeliver_lock_delay (1s)\fR"
+/* The time between attempts to acquire an exclusive lock on a mailbox
+/* file or \fBbounce\fR(8) logfile.
+/* .IP "\fBstale_lock_time (500s)\fR"
+/* The time after which a stale exclusive mailbox lockfile is removed.
+/* .IP "\fBmailbox_delivery_lock (see 'postconf -d' output)\fR"
+/* How to lock a UNIX-style \fBlocal\fR(8) mailbox before attempting delivery.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBcommand_time_limit (1000s)\fR"
+/* Time limit for delivery to external commands.
+/* .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 "\fBmailbox_size_limit (51200000)\fR"
+/* The maximal size of any \fBlocal\fR(8) individual mailbox or maildir
+/* file, or zero (no limit).
+/* .PP
+/* Implemented in the qmgr(8) daemon:
+/* .IP "\fBlocal_destination_concurrency_limit (2)\fR"
+/* The maximal number of parallel deliveries via the local mail
+/* delivery transport to the same recipient (when
+/* "local_destination_recipient_limit = 1") or the maximal number of
+/* parallel deliveries to the same local domain (when
+/* "local_destination_recipient_limit > 1").
+/* .IP "\fBlocal_destination_recipient_limit (1)\fR"
+/* The maximal number of recipients per message delivery via the
+/* local mail delivery transport.
+/* SECURITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBallow_mail_to_commands (alias, forward)\fR"
+/* Restrict \fBlocal\fR(8) mail delivery to external commands.
+/* .IP "\fBallow_mail_to_files (alias, forward)\fR"
+/* Restrict \fBlocal\fR(8) mail delivery to external files.
+/* .IP "\fBcommand_expansion_filter (see 'postconf -d' output)\fR"
+/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in
+/* $name expansions of $mailbox_command and $command_execution_directory.
+/* .IP "\fBdefault_privs (nobody)\fR"
+/* The default rights used by the \fBlocal\fR(8) delivery agent for delivery
+/* to an external file or command.
+/* .IP "\fBforward_expansion_filter (see 'postconf -d' output)\fR"
+/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in
+/* $name expansions of $forward_path.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBexecution_directory_expansion_filter (see 'postconf -d' output)\fR"
+/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows
+/* in $name expansions of $command_execution_directory.
+/* .PP
+/* Available in Postfix version 2.5.3 and later:
+/* .IP "\fBstrict_mailbox_ownership (yes)\fR"
+/* Defer delivery when a mailbox file is not owned by its recipient.
+/* 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 "\fBexport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a Postfix process will export
+/* to non-Postfix processes.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBlocal_command_shell (empty)\fR"
+/* Optional shell program for \fBlocal\fR(8) delivery to non-Postfix commands.
+/* .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 "\fBprepend_delivered_header (command, file, forward)\fR"
+/* The message delivery contexts where the Postfix \fBlocal\fR(8) delivery
+/* agent prepends a Delivered-To: message header with the address
+/* that the mail was 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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+/* What address lookup tables copy an address extension from the lookup
+/* key to the lookup result.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* .IP "\fBrequire_home_directory (no)\fR"
+/* Require that a \fBlocal\fR(8) recipient's home directory exists
+/* before mail delivery is attempted.
+/* .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 3.3 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).
+/* .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
+/* The following are examples; details differ between systems.
+/* $HOME/.forward, per-user aliasing
+/* /etc/aliases, system-wide alias database
+/* /var/spool/mail, system mailboxes
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* newaliases(1), create/update alias database
+/* postalias(1), create/update alias database
+/* aliases(5), format of alias database
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The \fBDelivered-To:\fR message header appears in the \fBqmail\fR
+/* system by Daniel Bernstein.
+/*
+/* The \fImaildir\fR structure appears in the \fBqmail\fR system
+/* by Daniel Bernstein.
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <name_mask.h>
+#include <set_eugid.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <deliver_completed.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <mail_conf.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <ext_prop.h>
+#include <maps.h>
+#include <flush_clnt.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+ /*
+ * Tunable parameters.
+ */
+char *var_allow_commands;
+char *var_allow_files;
+char *var_alias_maps;
+int var_dup_filter_limit;
+int var_command_maxtime; /* You can now leave this here. */
+char *var_home_mailbox;
+char *var_mailbox_command;
+char *var_mailbox_cmd_maps;
+char *var_rcpt_fdelim;
+char *var_local_cmd_shell;
+char *var_luser_relay;
+int var_biff;
+char *var_mail_spool_dir;
+char *var_mailbox_transport;
+char *var_mbox_transp_maps;
+char *var_fallback_transport;
+char *var_fbck_transp_maps;
+char *var_exec_directory;
+char *var_exec_exp_filter;
+char *var_forward_path;
+char *var_cmd_exp_filter;
+char *var_fwd_exp_filter;
+char *var_prop_extension;
+int var_exp_own_alias;
+char *var_deliver_hdr;
+int var_stat_home_dir;
+int var_mailtool_compat;
+char *var_mailbox_lock;
+long var_mailbox_limit;
+bool var_frozen_delivered;
+bool var_reset_owner_attr;
+bool var_strict_mbox_owner;
+
+int local_cmd_deliver_mask;
+int local_file_deliver_mask;
+int local_ext_prop_mask;
+int local_deliver_hdr_mask;
+int local_mbox_lock_mask;
+MAPS *alias_maps;
+char *var_local_dsn_filter;
+
+/* local_deliver - deliver message with extreme prejudice */
+
+static int local_deliver(DELIVER_REQUEST *rqst, char *service)
+{
+ const char *myname = "local_deliver";
+ RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len;
+ RECIPIENT *rcpt;
+ int rcpt_stat;
+ int msg_stat;
+ LOCAL_STATE state;
+ USER_ATTR usr_attr;
+
+ if (msg_verbose)
+ msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender);
+
+ /*
+ * Initialize the delivery attributes that are not recipient specific.
+ * While messages are being delivered and while aliases or forward files
+ * are being expanded, this attribute list is being changed constantly.
+ * For this reason, the list is passed on by value (except when it is
+ * being initialized :-), so that there is no need to undo attribute
+ * changes made by lower-level routines. The alias/include/forward
+ * expansion attribute list is part of a tree with self and parent
+ * references (see the EXPAND_ATTR definitions). The user-specific
+ * attributes are security sensitive, and are therefore kept separate.
+ * All this results in a noticeable level of clumsiness, but passing
+ * things around by value gives good protection against accidental change
+ * by subroutines.
+ */
+ state.level = 0;
+ deliver_attr_init(&state.msg_attr);
+ state.msg_attr.queue_name = rqst->queue_name;
+ state.msg_attr.queue_id = rqst->queue_id;
+ state.msg_attr.fp = rqst->fp;
+ state.msg_attr.offset = rqst->data_offset;
+ state.msg_attr.encoding = rqst->encoding;
+ state.msg_attr.smtputf8 = rqst->smtputf8;
+ state.msg_attr.sender = rqst->sender;
+ state.msg_attr.dsn_envid = rqst->dsn_envid;
+ state.msg_attr.dsn_ret = rqst->dsn_ret;
+ state.msg_attr.relay = service;
+ state.msg_attr.msg_stats = rqst->msg_stats;
+ state.msg_attr.request = rqst;
+ RESET_OWNER_ATTR(state.msg_attr, state.level);
+ RESET_USER_ATTR(usr_attr, state.level);
+ state.loop_info = delivered_hdr_init(rqst->fp, rqst->data_offset,
+ FOLD_ADDR_ALL);
+ state.request = rqst;
+
+ /*
+ * Iterate over each recipient named in the delivery request. When the
+ * mail delivery status for a given recipient is definite (i.e. bounced
+ * or delivered), update the message queue file and cross off the
+ * recipient. Update the per-message delivery status.
+ */
+ for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) {
+ state.dup_filter = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD);
+ forward_init();
+ state.msg_attr.rcpt = *rcpt;
+ rcpt_stat = deliver_recipient(state, usr_attr);
+ rcpt_stat |= forward_finish(rqst, state.msg_attr, rcpt_stat);
+ if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS))
+ deliver_completed(state.msg_attr.fp, rcpt->offset);
+ been_here_free(state.dup_filter);
+ msg_stat |= rcpt_stat;
+ }
+
+ /*
+ * Clean up.
+ */
+ delivered_hdr_free(state.loop_info);
+ deliver_attr_free(&state.msg_attr);
+
+ return (msg_stat);
+}
+
+/* local_service - perform service for client */
+
+static void local_service(VSTREAM *stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * that is dedicated to local mail delivery service. What we see below is
+ * a little protocol to (1) tell the client that we are ready, (2) read a
+ * delivery request from the client, and (3) report the completion status
+ * of that request.
+ */
+ if ((request = deliver_request_read(stream)) != 0) {
+ status = local_deliver(request, service);
+ deliver_request_done(stream, request, status);
+ }
+}
+
+/* local_mask_init - initialize delivery restrictions */
+
+static void local_mask_init(void)
+{
+ static const NAME_MASK file_mask[] = {
+ "alias", EXPAND_TYPE_ALIAS,
+ "forward", EXPAND_TYPE_FWD,
+ "include", EXPAND_TYPE_INCL,
+ 0,
+ };
+ static const NAME_MASK command_mask[] = {
+ "alias", EXPAND_TYPE_ALIAS,
+ "forward", EXPAND_TYPE_FWD,
+ "include", EXPAND_TYPE_INCL,
+ 0,
+ };
+ static const NAME_MASK deliver_mask[] = {
+ "command", DELIVER_HDR_CMD,
+ "file", DELIVER_HDR_FILE,
+ "forward", DELIVER_HDR_FWD,
+ 0,
+ };
+
+ local_file_deliver_mask = name_mask(VAR_ALLOW_FILES, file_mask,
+ var_allow_files);
+ local_cmd_deliver_mask = name_mask(VAR_ALLOW_COMMANDS, command_mask,
+ var_allow_commands);
+ local_ext_prop_mask =
+ ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension);
+ local_deliver_hdr_mask = name_mask(VAR_DELIVER_HDR, deliver_mask,
+ var_deliver_hdr);
+ local_mbox_lock_mask = mbox_lock_mask(var_mailbox_lock);
+ if (var_mailtool_compat) {
+ msg_warn("%s: deprecated parameter, use \"%s = dotlock\" instead",
+ VAR_MAILTOOL_COMPAT, VAR_MAILBOX_LOCK);
+ local_mbox_lock_mask &= MBOX_DOT_LOCK;
+ }
+ if (local_mbox_lock_mask == 0)
+ msg_fatal("parameter %s specifies no applicable mailbox locking method",
+ VAR_MAILBOX_LOCK);
+}
+
+/* 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);
+ }
+}
+
+/* post_init - post-jail initialization */
+
+static void post_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Drop privileges most of the time, and set up delivery restrictions.
+ */
+ set_eugid(var_owner_uid, var_owner_gid);
+ local_mask_init();
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Reset the file size limit from the message size limit to the mailbox
+ * size limit. XXX This still isn't accurate because the file size limit
+ * also affects delivery to command.
+ *
+ * A file size limit protects the machine against runaway software errors.
+ * It is not suitable to enforce mail quota, because users can get around
+ * mail quota by delivering to /file/name or to |command.
+ *
+ * We can't have mailbox size limit smaller than the message size limit,
+ * because that prohibits the delivery agent from updating the queue
+ * file.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_mailbox_limit)) {
+ if (!ENFORCING_SIZE_LIMIT(var_message_limit))
+ msg_fatal("configuration error: %s is limited but %s is "
+ "unlimited", VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ if (var_mailbox_limit < var_message_limit)
+ msg_fatal("configuration error: %s is smaller than %s",
+ VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ set_file_limit(var_mailbox_limit);
+ }
+ alias_maps = maps_create("aliases", var_alias_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_PARANOID
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ flush_init();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
+ 0,
+ };
+ static const CONFIG_LONG_TABLE long_table[] = {
+ VAR_MAILBOX_LIMIT, DEF_MAILBOX_LIMIT, &var_mailbox_limit, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
+ VAR_HOME_MAILBOX, DEF_HOME_MAILBOX, &var_home_mailbox, 0, 0,
+ VAR_ALLOW_COMMANDS, DEF_ALLOW_COMMANDS, &var_allow_commands, 0, 0,
+ VAR_ALLOW_FILES, DEF_ALLOW_FILES, &var_allow_files, 0, 0,
+ VAR_LOCAL_CMD_SHELL, DEF_LOCAL_CMD_SHELL, &var_local_cmd_shell, 0, 0,
+ VAR_MAIL_SPOOL_DIR, DEF_MAIL_SPOOL_DIR, &var_mail_spool_dir, 0, 0,
+ VAR_MAILBOX_TRANSP, DEF_MAILBOX_TRANSP, &var_mailbox_transport, 0, 0,
+ VAR_MBOX_TRANSP_MAPS, DEF_MBOX_TRANSP_MAPS, &var_mbox_transp_maps, 0, 0,
+ VAR_FALLBACK_TRANSP, DEF_FALLBACK_TRANSP, &var_fallback_transport, 0, 0,
+ VAR_FBCK_TRANSP_MAPS, DEF_FBCK_TRANSP_MAPS, &var_fbck_transp_maps, 0, 0,
+ VAR_CMD_EXP_FILTER, DEF_CMD_EXP_FILTER, &var_cmd_exp_filter, 1, 0,
+ VAR_FWD_EXP_FILTER, DEF_FWD_EXP_FILTER, &var_fwd_exp_filter, 1, 0,
+ VAR_EXEC_EXP_FILTER, DEF_EXEC_EXP_FILTER, &var_exec_exp_filter, 1, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_DELIVER_HDR, DEF_DELIVER_HDR, &var_deliver_hdr, 0, 0,
+ VAR_MAILBOX_LOCK, DEF_MAILBOX_LOCK, &var_mailbox_lock, 1, 0,
+ VAR_MAILBOX_CMD_MAPS, DEF_MAILBOX_CMD_MAPS, &var_mailbox_cmd_maps, 0, 0,
+ VAR_LOCAL_DSN_FILTER, DEF_LOCAL_DSN_FILTER, &var_local_dsn_filter, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_BIFF, DEF_BIFF, &var_biff,
+ VAR_EXP_OWN_ALIAS, DEF_EXP_OWN_ALIAS, &var_exp_own_alias,
+ VAR_STAT_HOME_DIR, DEF_STAT_HOME_DIR, &var_stat_home_dir,
+ VAR_MAILTOOL_COMPAT, DEF_MAILTOOL_COMPAT, &var_mailtool_compat,
+ VAR_FROZEN_DELIVERED, DEF_FROZEN_DELIVERED, &var_frozen_delivered,
+ VAR_RESET_OWNER_ATTR, DEF_RESET_OWNER_ATTR, &var_reset_owner_attr,
+ VAR_STRICT_MBOX_OWNER, DEF_STRICT_MBOX_OWNER, &var_strict_mbox_owner,
+ 0,
+ };
+
+ /* Suppress $name expansion upon loading. */
+ static const CONFIG_RAW_TABLE raw_table[] = {
+ VAR_EXEC_DIRECTORY, DEF_EXEC_DIRECTORY, &var_exec_directory, 0, 0,
+ VAR_FORWARD_PATH, DEF_FORWARD_PATH, &var_forward_path, 0, 0,
+ VAR_MAILBOX_COMMAND, DEF_MAILBOX_COMMAND, &var_mailbox_command, 0, 0,
+ VAR_LUSER_RELAY, DEF_LUSER_RELAY, &var_luser_relay, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, local_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_LONG_TABLE(long_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_RAW_TABLE(raw_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ CA_MAIL_SERVER_POST_INIT(post_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_PRIVILEGED,
+ CA_MAIL_SERVER_BOUNCE_INIT(VAR_LOCAL_DSN_FILTER,
+ &var_local_dsn_filter),
+ 0);
+}
diff --git a/src/local/local.h b/src/local/local.h
new file mode 100644
index 0000000..4052000
--- /dev/null
+++ b/src/local/local.h
@@ -0,0 +1,251 @@
+/*++
+/* NAME
+/* local 3h
+/* SUMMARY
+/* local mail delivery
+/* SYNOPSIS
+/* #include "local.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <been_here.h>
+#include <tok822.h>
+#include <deliver_request.h>
+#include <mbox_conf.h>
+#include <maps.h>
+#include <dsn_buf.h>
+#include <dsn.h>
+#include <delivered_hdr.h>
+
+ /*
+ * User attributes: these control the privileges for delivery to external
+ * commands, external files, or mailboxes, and the initial environment of
+ * external commands.
+ */
+typedef struct USER_ATTR {
+ uid_t uid; /* file/command access */
+ gid_t gid; /* file/command access */
+ char *home; /* null or home directory */
+ char *logname; /* null or login name */
+ char *shell; /* null or login shell */
+} USER_ATTR;
+
+ /*
+ * Critical macros. Not for obscurity, but to ensure consistency.
+ */
+#define RESET_USER_ATTR(usr_attr, level) { \
+ usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.home = 0; \
+ usr_attr.logname = 0; usr_attr.shell = 0; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: reset user_attr", myname, level); \
+ }
+
+#define SET_USER_ATTR(usr_attr, pwd, level) { \
+ usr_attr.uid = pwd->pw_uid; usr_attr.gid = pwd->pw_gid; \
+ usr_attr.home = pwd->pw_dir; usr_attr.logname = pwd->pw_name; \
+ usr_attr.shell = pwd->pw_shell; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: set user_attr: %s", \
+ myname, level, pwd->pw_name); \
+ }
+
+ /*
+ * The delivery attributes are inherited from files, from aliases, and from
+ * whatnot. Some of the information is changed on the fly. DELIVER_ATTR
+ * structures are therefore passed by value, so there is no need to undo
+ * changes.
+ */
+typedef struct DELIVER_ATTR {
+ int level; /* recursion level */
+ VSTREAM *fp; /* open queue file */
+ char *queue_name; /* mail queue id */
+ char *queue_id; /* mail queue id */
+ long offset; /* data offset */
+ char *encoding; /* MIME encoding */
+ int smtputf8; /* from delivery request */
+ const char *sender; /* taken from envelope */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN headers/full */
+ RECIPIENT rcpt; /* from delivery request */
+ char *domain; /* recipient domain */
+ char *local; /* recipient full localpart */
+ char *user; /* recipient localpart, base name */
+ char *extension; /* recipient localpart, extension */
+ char *unmatched; /* unmatched extension */
+ const char *owner; /* null or list owner */
+ const char *delivered; /* for loop detection */
+ char *relay; /* relay host */
+ MSG_STATS msg_stats; /* time profile */
+ int exp_type; /* expansion type. see below */
+ char *exp_from; /* expanded_from */
+ DELIVER_REQUEST *request; /* the kitchen sink */
+ DSN_BUF *why; /* delivery status */
+} DELIVER_ATTR;
+
+extern void deliver_attr_init(DELIVER_ATTR *);
+extern void deliver_attr_dump(DELIVER_ATTR *);
+extern void deliver_attr_free(DELIVER_ATTR *);
+
+#define EXPAND_TYPE_ALIAS (1<<0)
+#define EXPAND_TYPE_FWD (1<<1)
+#define EXPAND_TYPE_INCL (1<<2)
+
+ /*
+ * Rather than schlepping around dozens of arguments, here is one that has
+ * all. Well, almost. The user attributes are just a bit too sensitive, so
+ * they are passed around separately.
+ */
+typedef struct LOCAL_STATE {
+ int level; /* nesting level, for logging */
+ DELIVER_ATTR msg_attr; /* message attributes */
+ BH_TABLE *dup_filter; /* internal duplicate filter */
+ DELIVERED_HDR_INFO *loop_info; /* external loop filter */
+ DELIVER_REQUEST *request; /* as from queue manager */
+} LOCAL_STATE;
+
+#define RESET_OWNER_ATTR(msg_attr, level) { \
+ msg_attr.owner = 0; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: reset owner attr", myname, level); \
+ }
+
+#define SET_OWNER_ATTR(msg_attr, who, level) { \
+ msg_attr.sender = msg_attr.owner = who; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: set owner attr: %s", \
+ myname, level, who); \
+ }
+
+ /*
+ * Bundle up some often-user attributes.
+ */
+#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS((request)->flags)
+
+#define BOUNCE_ATTR(attr) \
+ attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \
+ DSN_FROM_DSN_BUF(attr.why)
+#define BOUNCE_ONE_ATTR(attr) \
+ attr.queue_name, attr.queue_id, attr.encoding, attr.smtputf8, \
+ attr.sender, attr.dsn_envid, attr.dsn_ret, \
+ &attr.msg_stats, &attr.rcpt, attr.relay, \
+ DSN_FROM_DSN_BUF(attr.why)
+#define SENT_ATTR(attr) \
+ attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \
+ DSN_FROM_DSN_BUF(attr.why)
+#define OPENED_ATTR(attr) \
+ attr.queue_id, attr.sender
+#define COPY_ATTR(attr) \
+ attr.sender, attr.rcpt.orig_addr, attr.delivered, attr.fp
+
+#define MSG_LOG_STATE(m, p) \
+ msg_info("%s[%d]: local %s recip %s exten %s deliver %s exp_from %s", \
+ m, \
+ p.level, \
+ p.msg_attr.local ? p.msg_attr.local : "" , \
+ p.msg_attr.rcpt.address ? p.msg_attr.rcpt.address : "", \
+ p.msg_attr.extension ? p.msg_attr.extension : "", \
+ p.msg_attr.delivered ? p.msg_attr.delivered : "", \
+ p.msg_attr.exp_from ? p.msg_attr.exp_from : "")
+
+ /*
+ * "inner" nodes of the delivery graph.
+ */
+extern int deliver_recipient(LOCAL_STATE, USER_ATTR);
+extern int deliver_alias(LOCAL_STATE, USER_ATTR, char *, int *);
+extern int deliver_dotforward(LOCAL_STATE, USER_ATTR, int *);
+extern int deliver_include(LOCAL_STATE, USER_ATTR, char *);
+extern int deliver_token(LOCAL_STATE, USER_ATTR, TOK822 *);
+extern int deliver_token_string(LOCAL_STATE, USER_ATTR, char *, int *);
+extern int deliver_token_stream(LOCAL_STATE, USER_ATTR, VSTREAM *, int *);
+extern int deliver_resolve_tree(LOCAL_STATE, USER_ATTR, TOK822 *);
+extern int deliver_resolve_addr(LOCAL_STATE, USER_ATTR, char *);
+
+ /*
+ * "leaf" nodes of the delivery graph.
+ */
+extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *);
+extern int deliver_command(LOCAL_STATE, USER_ATTR, const char *);
+extern int deliver_file(LOCAL_STATE, USER_ATTR, char *);
+extern int deliver_indirect(LOCAL_STATE);
+extern int deliver_maildir(LOCAL_STATE, USER_ATTR, char *);
+extern int deliver_unknown(LOCAL_STATE, USER_ATTR);
+
+ /*
+ * Restrictions on delivery to sensitive destinations.
+ */
+extern int local_file_deliver_mask;
+extern int local_cmd_deliver_mask;
+
+ /*
+ * Restrictions on extension propagation.
+ */
+extern int local_ext_prop_mask;
+
+ /*
+ * Mailbox lock protocol.
+ */
+extern int local_mbox_lock_mask;
+
+ /*
+ * When to prepend a Delivered-To: header upon external delivery.
+ */
+#define DELIVER_HDR_CMD (1<<0)
+#define DELIVER_HDR_FILE (1<<1)
+#define DELIVER_HDR_FWD (1<<2)
+
+extern int local_deliver_hdr_mask;
+
+ /*
+ * forward.c
+ */
+extern int forward_init(void);
+extern int forward_append(DELIVER_ATTR);
+extern int forward_finish(DELIVER_REQUEST *, DELIVER_ATTR, int);
+
+ /*
+ * feature.c
+ */
+extern int feature_control(const char *);
+
+ /*
+ * local_expand.c
+ */
+int local_expand(VSTRING *, const char *, LOCAL_STATE *, USER_ATTR *, const char *);
+
+#define LOCAL_EXP_EXTENSION_MATCHED (1<<MAC_PARSE_USER)
+
+ /*
+ * alias.c
+ */
+extern MAPS *alias_maps;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+ /*
+ * bounce_workaround.c
+ */
+int bounce_workaround(LOCAL_STATE);
+
+/* 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
+/*--*/
diff --git a/src/local/local_expand.c b/src/local/local_expand.c
new file mode 100644
index 0000000..ff9c3d6
--- /dev/null
+++ b/src/local/local_expand.c
@@ -0,0 +1,180 @@
+/*++
+/* NAME
+/* local_expand 3
+/* SUMMARY
+/* set up attribute list for $name expansion
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int local_expand(result, pattern, state, usr_attr, filter)
+/* VSTRING *result;
+/* const char *pattern;
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* const char *filter;
+/* DESCRIPTION
+/* local_expand() performs conditional and unconditional $name
+/* expansion based on message delivery attributes.
+/* The result is the bitwise OR or zero or more of the following:
+/* .IP LOCAL_EXP_EXTENSION_MATCHED
+/* The result of expansion contains the $extension attribute.
+/* .IP MAC_PARSE_XXX
+/* See mac_parse(3).
+/* .PP
+/* Attributes:
+/* .IP client_address
+/* The client network address.
+/* .IP client_helo
+/* The client HELO command parameter.
+/* .IP client_hostname
+/* The client hostname.
+/* .IP client_protocol
+/* The client protocol.
+/* .IP domain
+/* The recipient address domain.
+/* .IP extension
+/* The recipient address extension.
+/* .IP home
+/* The recipient home directory.
+/* .IP local
+/* The entire recipient address localpart.
+/* .IP recipient
+/* The entire recipient address.
+/* .IP recipient_delimiter
+/* The recipient delimiter.
+/* .IP shell
+/* The recipient shell program.
+/* .IP sasl_method
+/* The SASL authentication method.
+/* .IP sasl_sender
+/* The SASL MAIL FROM address.
+/* .IP sasl_username
+/* The SASL login name.
+/* .IP user
+/* The recipient user name.
+/* .PP
+/* Arguments:
+/* .IP result
+/* Storage for the result of expansion. The buffer is truncated
+/* upon entry.
+/* .IP pattern
+/* The string with unconditional and conditional macro expansions.
+/* .IP state
+/* Message delivery attributes (sender, recipient etc.).
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP filter
+/* A null pointer, or a string of allowed characters in $name
+/* expansions. Illegal characters are replaced by underscores.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* mac_expand(3) macro expansion
+/* 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 <vstring.h>
+#include <mac_expand.h>
+
+/* Global library */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+typedef struct {
+ LOCAL_STATE *state;
+ USER_ATTR *usr_attr;
+ int status;
+} LOCAL_EXP;
+
+/* local_expand_lookup - mac_expand() lookup routine */
+
+static const char *local_expand_lookup(const char *name, int mode, void *ptr)
+{
+ LOCAL_EXP *local = (LOCAL_EXP *) ptr;
+ static char rcpt_delim[2];
+
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+
+ if (STREQ(name, "user")) {
+ return (local->state->msg_attr.user);
+ } else if (STREQ(name, "home")) {
+ return (local->usr_attr->home);
+ } else if (STREQ(name, "shell")) {
+ return (local->usr_attr->shell);
+ } else if (STREQ(name, "domain")) {
+ return (local->state->msg_attr.domain);
+ } else if (STREQ(name, "local")) {
+ return (local->state->msg_attr.local);
+ } else if (STREQ(name, "mailbox")) {
+ return (local->state->msg_attr.local);
+ } else if (STREQ(name, "recipient")) {
+ return (local->state->msg_attr.rcpt.address);
+ } else if (STREQ(name, "extension")) {
+ if (mode == MAC_EXP_MODE_USE)
+ local->status |= LOCAL_EXP_EXTENSION_MATCHED;
+ return (local->state->msg_attr.extension);
+ } else if (STREQ(name, "recipient_delimiter")) {
+ rcpt_delim[0] =
+ local->state->msg_attr.local[strlen(local->state->msg_attr.user)];
+ if (rcpt_delim[0] == 0)
+ rcpt_delim[0] = var_rcpt_delim[0];
+ rcpt_delim[1] = 0;
+ return (rcpt_delim[0] ? rcpt_delim : 0);
+#if 0
+ } else if (STREQ(name, "client_hostname")) {
+ return (local->state->msg_attr.request->client_name);
+ } else if (STREQ(name, "client_address")) {
+ return (local->state->msg_attr.request->client_addr);
+ } else if (STREQ(name, "client_protocol")) {
+ return (local->state->msg_attr.request->client_proto);
+ } else if (STREQ(name, "client_helo")) {
+ return (local->state->msg_attr.request->client_helo);
+ } else if (STREQ(name, "sasl_method")) {
+ return (local->state->msg_attr.request->sasl_method);
+ } else if (STREQ(name, "sasl_sender")) {
+ return (local->state->msg_attr.request->sasl_sender);
+ } else if (STREQ(name, "sasl_username")) {
+ return (local->state->msg_attr.request->sasl_username);
+#endif
+ } else {
+ return (0);
+ }
+}
+
+/* local_expand - expand message delivery attributes */
+
+int local_expand(VSTRING *result, const char *pattern,
+ LOCAL_STATE *state, USER_ATTR *usr_attr, const char *filter)
+{
+ LOCAL_EXP local;
+ int expand_status;
+
+ local.state = state;
+ local.usr_attr = usr_attr;
+ local.status = 0;
+ expand_status = mac_expand(result, pattern, MAC_EXP_FLAG_NONE,
+ filter, local_expand_lookup, (void *) &local);
+ return (local.status | expand_status);
+}
diff --git a/src/local/mailbox.c b/src/local/mailbox.c
new file mode 100644
index 0000000..ed55291
--- /dev/null
+++ b/src/local/mailbox.c
@@ -0,0 +1,373 @@
+/*++
+/* NAME
+/* mailbox 3
+/* SUMMARY
+/* mailbox delivery
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_mailbox(state, usr_attr, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_mailbox() delivers to mailbox, with duplicate
+/* suppression. The default is direct mailbox delivery to
+/* /var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR
+/* has been configured, mail is delivered to ~/$\fIhome_mailbox\fR;
+/* and when a \fImailbox_command\fR has been configured, the message
+/* is piped into the command instead.
+/*
+/* A zero result means that the named user was not found.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP statusp
+/* Delivery status: see below.
+/* DIAGNOSTICS
+/* The message delivery status is non-zero when delivery should be tried
+/* again.
+/* 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/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <set_eugid.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <defer.h>
+#include <sent.h>
+#include <mypwd.h>
+#include <been_here.h>
+#include <mail_params.h>
+#include <deliver_pass.h>
+#include <mbox_open.h>
+#include <maps.h>
+#include <dsn_util.h>
+
+/* Application-specific. */
+
+#include "local.h"
+#include "biff_notify.h"
+
+#define YES 1
+#define NO 0
+
+/* deliver_mailbox_file - deliver to recipient mailbox */
+
+static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_mailbox_file";
+ char *spool_dir;
+ char *mailbox;
+ DSN_BUF *why = state.msg_attr.why;
+ MBOX *mp;
+ int mail_copy_status;
+ int deliver_status;
+ int copy_flags;
+ VSTRING *biff;
+ off_t end;
+ struct stat st;
+ uid_t spool_uid;
+ gid_t spool_gid;
+ uid_t chown_uid;
+ gid_t chown_gid;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Don't deliver trace-only requests.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to mailbox");
+ return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ if (var_frozen_delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+ if (*var_home_mailbox) {
+ spool_dir = 0;
+ mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
+ } else {
+ spool_dir = var_mail_spool_dir;
+ mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0);
+ }
+
+ /*
+ * Mailbox delivery with least privilege. As long as we do not use root
+ * privileges this code may also work over NFS.
+ *
+ * If delivering to the recipient's home directory, perform all operations
+ * (including file locking) as that user (Mike Muuss, Army Research
+ * Laboratory, USA).
+ *
+ * If delivering to the mail spool directory, and the spool directory is
+ * world-writable, deliver as the recipient; if the spool directory is
+ * group-writable, use the recipient user id and the mail spool group id.
+ *
+ * Otherwise, use root privileges and chown the mailbox if we create it.
+ */
+ if (spool_dir == 0
+ || stat(spool_dir, &st) < 0
+ || (st.st_mode & S_IWOTH) != 0) {
+ spool_uid = usr_attr.uid;
+ spool_gid = usr_attr.gid;
+ } else if ((st.st_mode & S_IWGRP) != 0) {
+ spool_uid = usr_attr.uid;
+ spool_gid = st.st_gid;
+ } else {
+ spool_uid = 0;
+ spool_gid = 0;
+ }
+ if (spool_uid == usr_attr.uid) {
+ chown_uid = -1;
+ chown_gid = -1;
+ } else {
+ chown_uid = usr_attr.uid;
+ chown_gid = usr_attr.gid;
+ }
+ if (msg_verbose)
+ msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld",
+ (long) spool_uid, (long) spool_gid,
+ (long) chown_uid, (long) chown_gid);
+
+ /*
+ * Lock the mailbox and open/create the mailbox file. Depending on the
+ * type of locking used, we lock first or we open first.
+ *
+ * Write the file as the recipient, so that file quota work.
+ */
+ copy_flags = MAIL_COPY_MBOX;
+ if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
+ copy_flags &= ~MAIL_COPY_DELIVERED;
+
+ set_eugid(spool_uid, spool_gid);
+ mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid,
+ local_mbox_lock_mask, "5.2.0", why);
+ if (mp != 0) {
+ if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ if (S_ISREG(st.st_mode) == 0) {
+ vstream_fclose(mp->fp);
+ dsb_simple(why, "5.2.0",
+ "destination %s is not a regular file", mailbox);
+ } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) {
+ vstream_fclose(mp->fp);
+ dsb_simple(why, "4.2.0",
+ "destination %s is not owned by recipient", mailbox);
+ msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
+ VAR_STRICT_MBOX_OWNER);
+ } else {
+ if ((end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END)) < 0)
+ msg_fatal("seek mailbox file %s: %m", mailbox);
+ mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
+ copy_flags, "\n", why);
+ }
+ if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
+ set_eugid(spool_uid, spool_gid);
+ mbox_release(mp);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * As the mail system, bounce, defer delivery, or report success.
+ */
+ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
+ deliver_status = DEL_STAT_DEFER;
+ } else if (mail_copy_status != 0) {
+ vstring_sprintf_prepend(why->reason,
+ "cannot update mailbox %s for user %s. ",
+ mailbox, state.msg_attr.user);
+ deliver_status =
+ (STR(why->status)[0] == '4' ?
+ defer_append : bounce_append)
+ (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr));
+ } else {
+ dsb_simple(why, "2.0.0", "delivered to mailbox");
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ if (var_biff) {
+ biff = vstring_alloc(100);
+ vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end);
+ biff_notify(STR(biff), VSTRING_LEN(biff) + 1);
+ vstring_free(biff);
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ myfree(mailbox);
+ return (deliver_status);
+}
+
+/* deliver_mailbox - deliver to recipient mailbox */
+
+int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
+{
+ const char *myname = "deliver_mailbox";
+ int status;
+ struct mypasswd *mbox_pwd;
+ char *path;
+ static MAPS *transp_maps;
+ const char *map_transport;
+ static MAPS *cmd_maps;
+ const char *map_command;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE ELIMINATION
+ *
+ * Don't come here more than once, whether or not the recipient exists.
+ */
+ if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local))
+ return (YES);
+
+ /*
+ * Delegate mailbox delivery to another message transport.
+ */
+ if (*var_mbox_transp_maps && transp_maps == 0)
+ transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB
+ | DICT_FLAG_UTF8_REQUEST);
+ /* The -1 is a hint for the down-stream deliver_completed() function. */
+ if (transp_maps
+ && (map_transport = maps_find(transp_maps, state.msg_attr.user,
+ DICT_FLAG_NONE)) != 0) {
+ state.msg_attr.rcpt.offset = -1L;
+ *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
+ state.request, &state.msg_attr.rcpt);
+ return (YES);
+ } else if (transp_maps && transp_maps->error != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ if (*var_mailbox_transport) {
+ state.msg_attr.rcpt.offset = -1L;
+ *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport,
+ state.request, &state.msg_attr.rcpt);
+ return (YES);
+ }
+
+ /*
+ * Skip delivery when this recipient does not exist.
+ */
+ if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) {
+ msg_warn("error looking up passwd info for %s: %m",
+ state.msg_attr.user);
+ dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ if (mbox_pwd == 0)
+ return (NO);
+
+ /*
+ * No early returns or we have a memory leak.
+ */
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * Use the rights of the recipient user.
+ */
+ SET_USER_ATTR(usr_attr, mbox_pwd, state.level);
+
+ /*
+ * Deliver to mailbox, maildir or to external command.
+ */
+#define LAST_CHAR(s) (s[strlen(s) - 1])
+
+ if (*var_mailbox_cmd_maps && cmd_maps == 0)
+ cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_PARANOID
+ | DICT_FLAG_UTF8_REQUEST);
+
+ if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user,
+ DICT_FLAG_NONE)) != 0) {
+ status = deliver_command(state, usr_attr, map_command);
+ } else if (cmd_maps && cmd_maps->error != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ status = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else if (*var_mailbox_command) {
+ status = deliver_command(state, usr_attr, var_mailbox_command);
+ } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') {
+ path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
+ status = deliver_maildir(state, usr_attr, path);
+ myfree(path);
+ } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') {
+ path = concatenate(var_mail_spool_dir, state.msg_attr.user,
+ "/", (char *) 0);
+ status = deliver_maildir(state, usr_attr, path);
+ myfree(path);
+ } else
+ status = deliver_mailbox_file(state, usr_attr);
+
+ /*
+ * Cleanup.
+ */
+ mypwfree(mbox_pwd);
+ *statusp = status;
+ return (YES);
+}
diff --git a/src/local/maildir.c b/src/local/maildir.c
new file mode 100644
index 0000000..46b8641
--- /dev/null
+++ b/src/local/maildir.c
@@ -0,0 +1,257 @@
+/*++
+/* NAME
+/* maildir 3
+/* SUMMARY
+/* delivery to maildir
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_maildir(state, usr_attr, path)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *path;
+/* DESCRIPTION
+/* deliver_maildir() delivers a message to a qmail maildir.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* .IP usr_attr
+/* Attributes describing user rights and environment information.
+/* .IP path
+/* The maildir to deliver to, including trailing slash.
+/* DIAGNOSTICS
+/* deliver_maildir() always succeeds or it bounces the message.
+/* SEE ALSO
+/* bounce(3)
+/* 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>
+#include <sys/time.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <make_dirs.h>
+#include <set_eugid.h>
+#include <get_hostname.h>
+#include <sane_fsops.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <bounce.h>
+#include <defer.h>
+#include <sent.h>
+#include <mail_params.h>
+#include <dsn_util.h>
+#include <mbox_open.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_maildir - delivery to maildir-style mailbox */
+
+int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path)
+{
+ const char *myname = "deliver_maildir";
+ char *newdir;
+ char *tmpdir;
+ char *curdir;
+ char *tmpfile;
+ char *newfile;
+ DSN_BUF *why = state.msg_attr.why;
+ VSTRING *buf;
+ VSTREAM *dst;
+ int mail_copy_status;
+ int deliver_status;
+ int copy_flags;
+ struct stat st;
+ struct timeval starttime;
+
+ GETTIMEOFDAY(&starttime);
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Don't deliver trace-only requests.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to maildir");
+ return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ if (var_frozen_delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+ buf = vstring_alloc(100);
+
+ copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT;
+ if (local_deliver_hdr_mask & DELIVER_HDR_FILE)
+ copy_flags |= MAIL_COPY_DELIVERED;
+
+ newdir = concatenate(path, "new/", (char *) 0);
+ tmpdir = concatenate(path, "tmp/", (char *) 0);
+ curdir = concatenate(path, "cur/", (char *) 0);
+
+ /*
+ * Create and write the file as the recipient, so that file quota work.
+ * Create any missing directories on the fly. The file name is chosen
+ * according to ftp://koobera.math.uic.edu/www/proto/maildir.html:
+ *
+ * "A unique name has three pieces, separated by dots. On the left is the
+ * result of time(). On the right is the result of gethostname(). In the
+ * middle is something that doesn't repeat within one second on a single
+ * host. I fork a new process for each delivery, so I just use the
+ * process ID. If you're delivering several messages from one process,
+ * use starttime.pid_count.host, where starttime is the time that your
+ * process started, and count is the number of messages you've
+ * delivered."
+ *
+ * Well, that stopped working on fast machines, and on operating systems
+ * that randomize process ID values. When creating a file in tmp/ we use
+ * the process ID because it still is an exclusive resource. When moving
+ * the file to new/ we use the device number and inode number. I do not
+ * care if this breaks on a remote AFS file system, because people should
+ * know better.
+ *
+ * On January 26, 2003, http://cr.yp.to/proto/maildir.html said:
+ *
+ * A unique name has three pieces, separated by dots. On the left is the
+ * result of time() or the second counter from gettimeofday(). On the
+ * right is the result of gethostname(). (To deal with invalid host
+ * names, replace / with \057 and : with \072.) In the middle is a
+ * delivery identifier, discussed below.
+ *
+ * [...]
+ *
+ * Modern delivery identifiers are created by concatenating enough of the
+ * following strings to guarantee uniqueness:
+ *
+ * [...]
+ *
+ * In, where n is (in hexadecimal) the UNIX inode number of this file.
+ * Unfortunately, inode numbers aren't always available through NFS.
+ *
+ * Vn, where n is (in hexadecimal) the UNIX device number of this file.
+ * Unfortunately, device numbers aren't always available through NFS.
+ * (Device numbers are also not helpful with the standard UNIX
+ * filesystem: a maildir has to be within a single UNIX device for link()
+ * and rename() to work.)
+ *
+ * Mn, where n is (in decimal) the microsecond counter from the same
+ * gettimeofday() used for the left part of the unique name.
+ *
+ * Pn, where n is (in decimal) the process ID.
+ *
+ * [...]
+ */
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ vstring_sprintf(buf, "%lu.P%d.%s",
+ (unsigned long) starttime.tv_sec, var_pid, get_hostname());
+ tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
+ newfile = 0;
+ if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
+ && (errno != ENOENT
+ || make_dirs(tmpdir, 0700) < 0
+ || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) {
+ dsb_simple(why, mbox_dsn(errno, "5.2.0"),
+ "create maildir file %s: %m", tmpfile);
+ } else if (fstat(vstream_fileno(dst), &st) < 0) {
+
+ /*
+ * Coverity 200604: file descriptor leak in code that never executes.
+ * Code replaced by msg_fatal(), as it is not worthwhile to continue
+ * after an impossible error condition.
+ */
+ msg_fatal("fstat %s: %m", tmpfile);
+ } else {
+ vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
+ (unsigned long) starttime.tv_sec,
+ (unsigned long) st.st_dev,
+ (unsigned long) st.st_ino,
+ (unsigned long) starttime.tv_usec,
+ get_hostname());
+ newfile = concatenate(newdir, STR(buf), (char *) 0);
+ if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
+ dst, copy_flags, "\n",
+ why)) == 0) {
+ if (sane_link(tmpfile, newfile) < 0
+ && (errno != ENOENT
+ || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0
+ || sane_link(tmpfile, newfile) < 0)) {
+ dsb_simple(why, mbox_dsn(errno, "5.2.0"),
+ "create maildir file %s: %m", newfile);
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+ }
+ }
+ if (unlink(tmpfile) < 0)
+ msg_warn("remove %s: %m", tmpfile);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * As the mail system, bounce or defer delivery.
+ */
+ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
+ deliver_status = DEL_STAT_DEFER;
+ } else if (mail_copy_status != 0) {
+ if (errno == EACCES) {
+ msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",
+ (long) usr_attr.uid, (long) usr_attr.gid,
+ STR(why->reason));
+ msg_warn("perhaps you need to create the maildirs in advance");
+ }
+ vstring_sprintf_prepend(why->reason, "maildir delivery failed: ");
+ deliver_status =
+ (STR(why->status)[0] == '4' ?
+ defer_append : bounce_append)
+ (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr));
+ } else {
+ dsb_simple(why, "2.0.0", "delivered to maildir");
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ }
+ vstring_free(buf);
+ myfree(newdir);
+ myfree(tmpdir);
+ myfree(curdir);
+ myfree(tmpfile);
+ if (newfile)
+ myfree(newfile);
+ return (deliver_status);
+}
diff --git a/src/local/recipient.c b/src/local/recipient.c
new file mode 100644
index 0000000..e3f4d1c
--- /dev/null
+++ b/src/local/recipient.c
@@ -0,0 +1,307 @@
+/*++
+/* NAME
+/* recipient 3
+/* SUMMARY
+/* deliver to one local recipient
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_recipient(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR *usr_attr;
+/* DESCRIPTION
+/* deliver_recipient() delivers a message to a local recipient.
+/* It is called initially when the queue manager requests
+/* delivery to a local recipient, and is called recursively
+/* when an alias or forward file expands to a local recipient.
+/*
+/* When called recursively with, for example, a result from alias
+/* or forward file expansion, aliases are expanded immediately,
+/* but mail for non-alias destinations is submitted as a new
+/* message, so that each recipient has a dedicated queue file
+/* message delivery status record (in a shared queue file).
+/*
+/* When the \fIrecipient_delimiter\fR configuration parameter
+/* is set, it is used to separate cookies off recipient names.
+/* A common setting is to have "recipient_delimiter = +"
+/* so that mail for \fIuser+foo\fR is delivered to \fIuser\fR,
+/* with a "Delivered-To: user+foo@domain" header line.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender, and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* DIAGNOSTICS
+/* deliver_recipient() returns non-zero when delivery should be
+/* tried again.
+/* BUGS
+/* Mutually-recursive aliases or $HOME/.forward files aren't
+/* detected when they could be. The resulting mail forwarding loop
+/* is broken by the use of the Delivered-To: message header.
+/* SEE ALSO
+/* alias(3) delivery to aliases
+/* mailbox(3) delivery to mailbox
+/* dotforward(3) delivery to destinations in .forward file
+/* 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>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <dict.h>
+#include <stat_as.h>
+
+/* Global library. */
+
+#include <bounce.h>
+#include <defer.h>
+#include <mail_params.h>
+#include <split_addr.h>
+#include <strip_addr.h>
+#include <ext_prop.h>
+#include <mypwd.h>
+#include <canon_addr.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_switch - branch on recipient type */
+
+static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_switch";
+ int status = 0;
+ struct stat st;
+ struct mypasswd *mypwd;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+
+ /*
+ * \user is special: it means don't do any alias or forward expansion.
+ *
+ * XXX This code currently does not work due to revision of the RFC822
+ * address parser. \user should be permitted only in locally specified
+ * aliases, includes or forward files.
+ *
+ * XXX Should test for presence of user home directory.
+ */
+ if (state.msg_attr.rcpt.address[0] == '\\') {
+ state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++;
+ if (deliver_mailbox(state, usr_attr, &status) == 0)
+ status = deliver_unknown(state, usr_attr);
+ return (status);
+ }
+
+ /*
+ * Otherwise, alias expansion has highest precedence. First look up the
+ * full localpart, then the bare user. Obey the address extension
+ * propagation policy.
+ */
+ state.msg_attr.unmatched = 0;
+ if (deliver_alias(state, usr_attr, state.msg_attr.local, &status))
+ return (status);
+ if (state.msg_attr.extension != 0) {
+ if (local_ext_prop_mask & EXT_PROP_ALIAS)
+ state.msg_attr.unmatched = state.msg_attr.extension;
+ if (deliver_alias(state, usr_attr, state.msg_attr.user, &status))
+ return (status);
+ state.msg_attr.unmatched = state.msg_attr.extension;
+ }
+
+ /*
+ * Special case for mail locally forwarded or aliased to a different
+ * local address. Resubmit the message via the cleanup service, so that
+ * each recipient gets a separate delivery queue file status record in
+ * the new queue file. The downside of this approach is that mutually
+ * recursive .forward files cause a mail forwarding loop. Fortunately,
+ * the loop can be broken by the use of the Delivered-To: message header.
+ *
+ * The code below must not trigger on mail sent to an alias that has no
+ * owner- companion, so that mail for an alias first.last->username is
+ * delivered directly, instead of going through username->first.last
+ * canonical mappings in the cleanup service. The downside of this
+ * approach is that recipients in the expansion of an alias without
+ * owner- won't have separate delivery queue file status records, because
+ * for them, the message won't be resubmitted as a new queue file.
+ *
+ * Do something sensible on systems that receive mail for multiple domains,
+ * such as primary.name and secondary.name. Don't resubmit the message
+ * when mail for `user@secondary.name' is delivered to a .forward file
+ * that lists `user' or `user@primary.name'. We already know that the
+ * recipient domain is local, so we only have to compare local parts.
+ */
+ if (state.msg_attr.owner != 0
+ && strcasecmp_utf8(state.msg_attr.owner, state.msg_attr.user) != 0)
+ return (deliver_indirect(state));
+
+ /*
+ * Always forward recipients in :include: files.
+ */
+ if (state.msg_attr.exp_type == EXPAND_TYPE_INCL)
+ return (deliver_indirect(state));
+
+ /*
+ * Delivery to local user. First try expansion of the recipient's
+ * $HOME/.forward file, then mailbox delivery. Back off when the user's
+ * home directory does not exist.
+ */
+ mypwd = 0;
+ if (var_stat_home_dir
+ && (errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) {
+ msg_warn("error looking up passwd info for %s: %m",
+ state.msg_attr.user);
+ dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (mypwd != 0) {
+ if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) {
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "cannot access home directory %s: %m", mypwd->pw_dir);
+ mypwfree(mypwd);
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ mypwfree(mypwd);
+ }
+ if (deliver_dotforward(state, usr_attr, &status) == 0
+ && deliver_mailbox(state, usr_attr, &status) == 0)
+ status = deliver_unknown(state, usr_attr);
+ return (status);
+}
+
+/* deliver_recipient - deliver one local recipient */
+
+int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_recipient";
+ VSTRING *folded;
+ int rcpt_stat;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Duplicate filter.
+ */
+ if (been_here(state.dup_filter, "recipient %d %s",
+ state.level, state.msg_attr.rcpt.address))
+ return (0);
+
+ /*
+ * With each level of recursion, detect and break external message
+ * forwarding loops.
+ *
+ * If the looping recipient address has an owner- alias, send the error
+ * report there instead.
+ *
+ * XXX A delivery agent cannot change the envelope sender address for
+ * bouncing. As a workaround we use a one-recipient bounce procedure.
+ *
+ * The proper fix would be to record in the bounce logfile an error return
+ * address for each individual recipient. This would also eliminate the
+ * need for VERP specific bouncing code, at the cost of complicating the
+ * normal bounce sending procedure, but would simplify the code below.
+ */
+ if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) {
+ dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s",
+ state.msg_attr.rcpt.address);
+ /* Account for possible owner- sender address override. */
+ return (bounce_workaround(state));
+ }
+
+ /*
+ * Set up the recipient-specific attributes. If this is forwarded mail,
+ * leave the delivered attribute alone, so that the forwarded message
+ * will show the correct forwarding recipient.
+ */
+ if (state.msg_attr.delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ folded = vstring_alloc(100);
+ state.msg_attr.local = casefold(folded, state.msg_attr.rcpt.address);
+ if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
+ msg_warn("no @ in recipient address: %s", state.msg_attr.local);
+
+ /*
+ * Address extension management.
+ *
+ * XXX Fix 20100422, finalized 20100529: it is too error-prone to
+ * distinguish between "no extension" and "no valid extension", so we
+ * drop an invalid extension from the recipient address local-part.
+ */
+ state.msg_attr.user = mystrdup(state.msg_attr.local);
+ if (*var_rcpt_delim) {
+ state.msg_attr.extension =
+ split_addr(state.msg_attr.user, var_rcpt_delim);
+ if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
+ msg_warn("%s: address with illegal extension: %s",
+ state.msg_attr.queue_id, state.msg_attr.local);
+ state.msg_attr.extension = 0;
+ /* XXX Can't myfree + mystrdup, must truncate instead. */
+ state.msg_attr.local[strlen(state.msg_attr.user)] = 0;
+ /* Truncating is safe. The code below rejects null usernames. */
+ }
+ } else
+ state.msg_attr.extension = 0;
+ state.msg_attr.unmatched = state.msg_attr.extension;
+
+ /*
+ * Do not allow null usernames.
+ */
+ if (state.msg_attr.user[0] == 0) {
+ dsb_simple(state.msg_attr.why, "5.1.3",
+ "null username in \"%s\"", state.msg_attr.rcpt.address);
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Run the recipient through the delivery switch.
+ */
+ if (msg_verbose)
+ deliver_attr_dump(&state.msg_attr);
+ rcpt_stat = deliver_switch(state, usr_attr);
+
+ /*
+ * Clean up.
+ */
+ vstring_free(folded);
+ myfree(state.msg_attr.user);
+
+ return (rcpt_stat);
+}
diff --git a/src/local/resolve.c b/src/local/resolve.c
new file mode 100644
index 0000000..a6aa9d0
--- /dev/null
+++ b/src/local/resolve.c
@@ -0,0 +1,170 @@
+/*++
+/* NAME
+/* resolve 3
+/* SUMMARY
+/* resolve recipient and deliver locally or remotely
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_resolve_tree(state, usr_attr, addr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* TOK822 *addr;
+/*
+/* int deliver_resolve_addr(state, usr_attr, addr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *addr;
+/* DESCRIPTION
+/* deliver_resolve_XXX() resolves a recipient that is the result from
+/* e.g., alias expansion, and delivers locally or via forwarding.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender and more.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP addr
+/* An address from, e.g., alias expansion.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. The result is non-zero when the
+/* operation should be tried again. Warnings: malformed address.
+/* SEE ALSO
+/* recipient(3) local delivery
+/* indirect(3) deliver via forwarding
+/* 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 <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <resolve_clnt.h>
+#include <rewrite_clnt.h>
+#include <tok822.h>
+#include <mail_params.h>
+#include <defer.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_resolve_addr - resolve and deliver */
+
+int deliver_resolve_addr(LOCAL_STATE state, USER_ATTR usr_attr, char *addr)
+{
+ TOK822 *tree;
+ int result;
+
+ tree = tok822_scan_addr(addr);
+ result = deliver_resolve_tree(state, usr_attr, tree);
+ tok822_free_tree(tree);
+ return (result);
+}
+
+/* deliver_resolve_tree - resolve and deliver */
+
+int deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr)
+{
+ const char *myname = "deliver_resolve_tree";
+ RESOLVE_REPLY reply;
+ int status;
+ ssize_t ext_len;
+ char *ratsign;
+ int rcpt_delim;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Initialize.
+ */
+ resolve_clnt_init(&reply);
+
+ /*
+ * Rewrite the address to canonical form, just like the cleanup service
+ * does. Then, resolve the address to (transport, nexhop, recipient),
+ * just like the queue manager does. The only part missing here is the
+ * virtual address substitution. Message forwarding fixes most of that.
+ */
+ tok822_rewrite(addr, REWRITE_CANON);
+ tok822_resolve(addr, &reply);
+
+ /*
+ * First, a healthy portion of error handling.
+ */
+ if (reply.flags & RESOLVE_FLAG_FAIL) {
+ dsb_simple(state.msg_attr.why, "4.3.0", "address resolver failure");
+ status = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else if (reply.flags & RESOLVE_FLAG_ERROR) {
+ dsb_simple(state.msg_attr.why, "5.1.3",
+ "bad recipient address syntax: %s", STR(reply.recipient));
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+
+ /*
+ * Splice in the optional unmatched address extension.
+ */
+ if (state.msg_attr.unmatched) {
+ rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)];
+ if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) {
+ VSTRING_ADDCH(reply.recipient, rcpt_delim);
+ vstring_strcat(reply.recipient, state.msg_attr.unmatched);
+ } else {
+ ext_len = strlen(state.msg_attr.unmatched);
+ VSTRING_SPACE(reply.recipient, ext_len + 2);
+ if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0)
+ msg_panic("%s: recipient @ botch", myname);
+ memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1);
+ *ratsign = rcpt_delim;
+ memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len);
+ VSTRING_SKIP(reply.recipient);
+ }
+ }
+ state.msg_attr.rcpt.address = STR(reply.recipient);
+
+ /*
+ * Delivery to a local or non-local address. For a while there was
+ * some ugly code to force local recursive alias expansions on a host
+ * with no authority over the local domain, but that code was just
+ * too unclean.
+ */
+ if (strcmp(state.msg_attr.relay, STR(reply.transport)) == 0) {
+ status = deliver_recipient(state, usr_attr);
+ } else {
+ status = deliver_indirect(state);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ resolve_clnt_free(&reply);
+
+ return (status);
+}
diff --git a/src/local/token.c b/src/local/token.c
new file mode 100644
index 0000000..2eb0c28
--- /dev/null
+++ b/src/local/token.c
@@ -0,0 +1,222 @@
+/*++
+/* NAME
+/* token 3
+/* SUMMARY
+/* tokenize alias/include/.forward entries and deliver
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_token(state, usr_attr, addr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* TOK822 *addr;
+/*
+/* int deliver_token_string(state, usr_attr, string, addr_count)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *string;
+/* int *addr_count;
+/*
+/* int deliver_token_stream(state, usr_attr, fp, addr_count)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* VSTREAM *fp;
+/* int *addr_count;
+/* DESCRIPTION
+/* This module delivers to addresses listed in an alias database
+/* entry, in an include file, or in a .forward file.
+/*
+/* deliver_token() delivers to the address in the given token:
+/* an absolute /path/name, a ~/path/name relative to the recipient's
+/* home directory, an :include:/path/name request, an external
+/* "|command", or a mail address.
+/*
+/* deliver_token_string() delivers to all addresses listed in
+/* the specified string.
+/*
+/* deliver_token_stream() delivers to all addresses listed in
+/* the specified stream. Input records > \fIline_length_limit\fR
+/* are broken up into multiple records, to prevent the mail
+/* system from using unreasonable amounts of memory.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* .IP addr
+/* A parsed address from an include file, alias file or .forward file.
+/* .IP string
+/* A null-terminated string.
+/* .IP fp
+/* A readable stream.
+/* .IP addr_count
+/* Null pointer, or the address of a counter that is incremented
+/* by the number of destinations found by the tokenizer.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. The result is non-zero when the
+/* operation should be tried again. Warnings: malformed address.
+/* SEE ALSO
+/* list_token(3) tokenize list
+/* 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 <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <htable.h>
+#include <readlline.h>
+#include <mymalloc.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <tok822.h>
+#include <mail_params.h>
+#include <bounce.h>
+#include <defer.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_token_home - expand ~token */
+
+static int deliver_token_home(LOCAL_STATE state, USER_ATTR usr_attr, char *addr)
+{
+ char *full_path;
+ int status;
+
+ if (addr[1] != '/') { /* disallow ~user */
+ msg_warn("bad home directory syntax for: %s", addr);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else if (usr_attr.home == 0) { /* require user context */
+ msg_warn("unknown home directory for: %s", addr);
+ dsb_simple(state.msg_attr.why, "5.3.5",
+ "mail system configuration error");
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else if (usr_attr.home[0] == '/' && usr_attr.home[1] == 0) {
+ status = deliver_file(state, usr_attr, addr + 1);
+ } else { /* expand ~ to home */
+ full_path = concatenate(usr_attr.home, addr + 1, (char *) 0);
+ status = deliver_file(state, usr_attr, full_path);
+ myfree(full_path);
+ }
+ return (status);
+}
+
+/* deliver_token - deliver to expansion of include file or alias */
+
+int deliver_token(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr)
+{
+ VSTRING *addr_buf = vstring_alloc(100);
+ static char include[] = ":include:";
+ int status;
+ char *path;
+
+ tok822_internalize(addr_buf, addr->head, TOK822_STR_DEFL);
+ if (msg_verbose)
+ msg_info("deliver_token: %s", STR(addr_buf));
+
+ if (*STR(addr_buf) == '/') {
+ status = deliver_file(state, usr_attr, STR(addr_buf));
+ } else if (*STR(addr_buf) == '~') {
+ status = deliver_token_home(state, usr_attr, STR(addr_buf));
+ } else if (*STR(addr_buf) == '|') {
+ if ((local_cmd_deliver_mask & state.msg_attr.exp_type) == 0) {
+ dsb_simple(state.msg_attr.why, "5.7.1",
+ "mail to command is restricted");
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else
+ status = deliver_command(state, usr_attr, STR(addr_buf) + 1);
+ } else if (strncasecmp(STR(addr_buf), include, sizeof(include) - 1) == 0) {
+ path = STR(addr_buf) + sizeof(include) - 1;
+ status = deliver_include(state, usr_attr, path);
+ } else {
+ status = deliver_resolve_tree(state, usr_attr, addr);
+ }
+ vstring_free(addr_buf);
+
+ return (status);
+}
+
+/* deliver_token_string - tokenize string and deliver */
+
+int deliver_token_string(LOCAL_STATE state, USER_ATTR usr_attr,
+ char *string, int *addr_count)
+{
+ TOK822 *tree;
+ TOK822 *addr;
+ int status = 0;
+
+ if (msg_verbose)
+ msg_info("deliver_token_string: %s", string);
+
+ tree = tok822_parse(string);
+ for (addr = tree; addr != 0; addr = addr->next) {
+ if (addr->type == TOK822_ADDR) {
+ if (addr_count)
+ (*addr_count)++;
+ status |= deliver_token(state, usr_attr, addr);
+ }
+ }
+ tok822_free_tree(tree);
+ return (status);
+}
+
+/* deliver_token_stream - tokenize stream and deliver */
+
+int deliver_token_stream(LOCAL_STATE state, USER_ATTR usr_attr,
+ VSTREAM *fp, int *addr_count)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int status = 0;
+
+ if (msg_verbose)
+ msg_info("deliver_token_stream: %s", VSTREAM_PATH(fp));
+
+ while (vstring_fgets_bound(buf, fp, var_line_limit)) {
+ if (*STR(buf) != '#') {
+ status = deliver_token_string(state, usr_attr, STR(buf), addr_count);
+ if (status != 0)
+ break;
+ }
+ }
+ if (vstream_ferror(fp)) {
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "error reading forwarding file: %m");
+ status = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ }
+ vstring_free(buf);
+ return (status);
+}
diff --git a/src/local/unknown.c b/src/local/unknown.c
new file mode 100644
index 0000000..96443e1
--- /dev/null
+++ b/src/local/unknown.c
@@ -0,0 +1,187 @@
+/*++
+/* NAME
+/* unknown 3
+/* SUMMARY
+/* delivery of unknown recipients
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_unknown(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* DESCRIPTION
+/* deliver_unknown() delivers a message for unknown recipients.
+/* .IP \(bu
+/* If an alternative message transport is specified via the
+/* fallback_transport parameter, delivery is delegated to the
+/* named transport.
+/* .IP \(bu
+/* If an alternative address is specified via the luser_relay
+/* configuration parameter, mail is forwarded to that address.
+/* .IP \(bu
+/* Otherwise the recipient is bounced.
+/* .PP
+/* The luser_relay parameter is subjected to $name expansion of
+/* the standard message attributes: $user, $home, $shell, $domain,
+/* $recipient, $mailbox, $extension, $recipient_delimiter, not
+/* all of which actually make sense.
+/*
+/* Arguments:
+/* .IP state
+/* Message delivery attributes (sender, recipient etc.).
+/* Attributes describing alias, include or forward expansion.
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* Attributes describing user rights and environment.
+/* DIAGNOSTICS
+/* The result status is non-zero when delivery should be tried again.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <been_here.h>
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <bounce.h>
+#include <mail_addr.h>
+#include <sent.h>
+#include <deliver_pass.h>
+#include <defer.h>
+#include <canon_addr.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+#define STREQ(x,y) (strcasecmp((x),(y)) == 0)
+
+/* deliver_unknown - delivery for unknown recipients */
+
+int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_unknown";
+ int status;
+ VSTRING *expand_luser;
+ VSTRING *canon_luser;
+ static MAPS *transp_maps;
+ const char *map_transport;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE/LOOP ELIMINATION
+ *
+ * Don't deliver the same user twice.
+ */
+ if (been_here(state.dup_filter, "%s %s", myname, state.msg_attr.local))
+ return (0);
+
+ /*
+ * The fall-back transport specifies a delivery mechanism that handles
+ * users not found in the aliases or UNIX passwd databases.
+ */
+ if (*var_fbck_transp_maps && transp_maps == 0)
+ transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB
+ | DICT_FLAG_UTF8_REQUEST);
+ /* The -1 is a hint for the down-stream deliver_completed() function. */
+ if (transp_maps
+ && (map_transport = maps_find(transp_maps, state.msg_attr.user,
+ DICT_FLAG_NONE)) != 0) {
+ state.msg_attr.rcpt.offset = -1L;
+ return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
+ state.request, &state.msg_attr.rcpt));
+ } else if (transp_maps && transp_maps->error != 0) {
+ /* Details in the logfile. */
+ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (*var_fallback_transport) {
+ state.msg_attr.rcpt.offset = -1L;
+ return (deliver_pass(MAIL_CLASS_PRIVATE, var_fallback_transport,
+ state.request, &state.msg_attr.rcpt));
+ }
+
+ /*
+ * Subject the luser_relay address to $name expansion, disable
+ * propagation of unmatched address extension, and re-inject the address
+ * into the delivery machinery. Do not give special treatment to "|stuff"
+ * or /stuff.
+ */
+ if (*var_luser_relay) {
+ state.msg_attr.unmatched = 0;
+ expand_luser = vstring_alloc(100);
+ canon_luser = vstring_alloc(100);
+ local_expand(expand_luser, var_luser_relay, &state, &usr_attr, (void *) 0);
+ /* In case luser_relay specifies a domain-less address. */
+ canon_addr_external(canon_luser, vstring_str(expand_luser));
+ /* Assumes that the address resolver won't change the address. */
+ if (STREQ(vstring_str(canon_luser), state.msg_attr.rcpt.address)) {
+ dsb_simple(state.msg_attr.why, "5.1.1",
+ "unknown user: \"%s\"", state.msg_attr.user);
+ status = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+ status = deliver_resolve_addr(state, usr_attr, STR(expand_luser));
+ }
+ vstring_free(canon_luser);
+ vstring_free(expand_luser);
+ return (status);
+ }
+
+ /*
+ * If no alias was found for a required reserved name, toss the message
+ * into the bit bucket, and issue a warning instead.
+ */
+ if (STREQ(state.msg_attr.user, MAIL_ADDR_MAIL_DAEMON)
+ || STREQ(state.msg_attr.user, MAIL_ADDR_POSTMASTER)) {
+ msg_warn("required alias not found: %s", state.msg_attr.user);
+ dsb_simple(state.msg_attr.why, "2.0.0", "discarded");
+ return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Bounce the message when no luser relay is specified.
+ */
+ dsb_simple(state.msg_attr.why, "5.1.1",
+ "unknown user: \"%s\"", state.msg_attr.user);
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+}
diff --git a/src/master/.indent.pro b/src/master/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/master/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/master/.printfck b/src/master/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/master/.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/master/Makefile.in b/src/master/Makefile.in
new file mode 100644
index 0000000..db67a68
--- /dev/null
+++ b/src/master/Makefile.in
@@ -0,0 +1,477 @@
+SHELL = /bin/sh
+SRCS = master.c master_conf.c master_ent.c master_sig.c master_avail.c \
+ master_spawn.c master_service.c master_status.c master_listen.c \
+ master_proto.c single_server.c multi_server.c master_vars.c \
+ master_wakeup.c master_flow.c master_watch.c mail_flow.c \
+ master_monitor.c dgram_server.c
+OBJS = master.o master_conf.o master_ent.o master_sig.o master_avail.o \
+ master_spawn.o master_service.o master_status.o master_listen.o \
+ master_vars.o master_wakeup.o master_watch.o master_flow.o \
+ master_monitor.o
+LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o \
+ mail_flow.o event_server.o dgram_server.o
+HDRS = mail_server.h master_proto.h mail_flow.h
+INT_HDR = master.h
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+LIB = lib$(LIB_PREFIX)master$(LIB_SUFFIX)
+PROG = master
+TESTPROG=
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+BIN_DIR = ../../libexec
+
+.c.o:; $(CC) `for i in $(LIB_OBJ); do if [ $$i = $@ ]; then echo $(SHLIB_CFLAGS); else true; fi; done` $(CFLAGS) -c $*.c
+
+all: $(PROG) $(LIB)
+
+$(OBJS) $(LIB_OBJ): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+$(LIB): $(LIB_OBJ)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(LIB_OBJ) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)/$(LIB)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+$(BIN_DIR)/$(PROG): $(PROG)
+ cp $(PROG) $(BIN_DIR)
+
+update: $(LIB_DIR)/$(LIB) $(BIN_DIR)/$(PROG) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+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) junk $(LIB)
+ rm -rf printfck
+
+tidy: clean
+
+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'
+dgram_server.o: ../../include/argv.h
+dgram_server.o: ../../include/attr.h
+dgram_server.o: ../../include/bounce.h
+dgram_server.o: ../../include/check_arg.h
+dgram_server.o: ../../include/chroot_uid.h
+dgram_server.o: ../../include/debug_process.h
+dgram_server.o: ../../include/deliver_request.h
+dgram_server.o: ../../include/dict.h
+dgram_server.o: ../../include/dsn.h
+dgram_server.o: ../../include/dsn_buf.h
+dgram_server.o: ../../include/events.h
+dgram_server.o: ../../include/htable.h
+dgram_server.o: ../../include/iostuff.h
+dgram_server.o: ../../include/listen.h
+dgram_server.o: ../../include/mail_conf.h
+dgram_server.o: ../../include/mail_dict.h
+dgram_server.o: ../../include/mail_params.h
+dgram_server.o: ../../include/mail_task.h
+dgram_server.o: ../../include/mail_version.h
+dgram_server.o: ../../include/maillog_client.h
+dgram_server.o: ../../include/msg.h
+dgram_server.o: ../../include/msg_stats.h
+dgram_server.o: ../../include/msg_vstream.h
+dgram_server.o: ../../include/myflock.h
+dgram_server.o: ../../include/mymalloc.h
+dgram_server.o: ../../include/nvtable.h
+dgram_server.o: ../../include/recipient_list.h
+dgram_server.o: ../../include/resolve_local.h
+dgram_server.o: ../../include/safe_open.h
+dgram_server.o: ../../include/sane_accept.h
+dgram_server.o: ../../include/split_at.h
+dgram_server.o: ../../include/stringops.h
+dgram_server.o: ../../include/sys_defs.h
+dgram_server.o: ../../include/vbuf.h
+dgram_server.o: ../../include/vstream.h
+dgram_server.o: ../../include/vstring.h
+dgram_server.o: ../../include/watchdog.h
+dgram_server.o: dgram_server.c
+dgram_server.o: mail_flow.h
+dgram_server.o: mail_server.h
+dgram_server.o: master_proto.h
+event_server.o: ../../include/argv.h
+event_server.o: ../../include/attr.h
+event_server.o: ../../include/bounce.h
+event_server.o: ../../include/check_arg.h
+event_server.o: ../../include/chroot_uid.h
+event_server.o: ../../include/debug_process.h
+event_server.o: ../../include/deliver_request.h
+event_server.o: ../../include/dict.h
+event_server.o: ../../include/dsn.h
+event_server.o: ../../include/dsn_buf.h
+event_server.o: ../../include/events.h
+event_server.o: ../../include/htable.h
+event_server.o: ../../include/iostuff.h
+event_server.o: ../../include/listen.h
+event_server.o: ../../include/mail_conf.h
+event_server.o: ../../include/mail_dict.h
+event_server.o: ../../include/mail_params.h
+event_server.o: ../../include/mail_task.h
+event_server.o: ../../include/mail_version.h
+event_server.o: ../../include/maillog_client.h
+event_server.o: ../../include/msg.h
+event_server.o: ../../include/msg_stats.h
+event_server.o: ../../include/msg_vstream.h
+event_server.o: ../../include/myflock.h
+event_server.o: ../../include/mymalloc.h
+event_server.o: ../../include/nvtable.h
+event_server.o: ../../include/recipient_list.h
+event_server.o: ../../include/resolve_local.h
+event_server.o: ../../include/safe_open.h
+event_server.o: ../../include/sane_accept.h
+event_server.o: ../../include/split_at.h
+event_server.o: ../../include/stringops.h
+event_server.o: ../../include/sys_defs.h
+event_server.o: ../../include/timed_ipc.h
+event_server.o: ../../include/vbuf.h
+event_server.o: ../../include/vstream.h
+event_server.o: ../../include/vstring.h
+event_server.o: ../../include/watchdog.h
+event_server.o: event_server.c
+event_server.o: mail_flow.h
+event_server.o: mail_server.h
+event_server.o: master_proto.h
+mail_flow.o: ../../include/iostuff.h
+mail_flow.o: ../../include/msg.h
+mail_flow.o: ../../include/sys_defs.h
+mail_flow.o: ../../include/warn_stat.h
+mail_flow.o: mail_flow.c
+mail_flow.o: mail_flow.h
+mail_flow.o: master_proto.h
+master.o: ../../include/argv.h
+master.o: ../../include/check_arg.h
+master.o: ../../include/clean_env.h
+master.o: ../../include/debug_process.h
+master.o: ../../include/events.h
+master.o: ../../include/inet_proto.h
+master.o: ../../include/iostuff.h
+master.o: ../../include/mail_conf.h
+master.o: ../../include/mail_params.h
+master.o: ../../include/mail_parm_split.h
+master.o: ../../include/mail_task.h
+master.o: ../../include/mail_version.h
+master.o: ../../include/maillog_client.h
+master.o: ../../include/msg.h
+master.o: ../../include/myflock.h
+master.o: ../../include/mymalloc.h
+master.o: ../../include/open_lock.h
+master.o: ../../include/safe.h
+master.o: ../../include/set_eugid.h
+master.o: ../../include/set_ugid.h
+master.o: ../../include/stringops.h
+master.o: ../../include/sys_defs.h
+master.o: ../../include/vbuf.h
+master.o: ../../include/vstream.h
+master.o: ../../include/vstring.h
+master.o: ../../include/watchdog.h
+master.o: master.c
+master.o: master.h
+master_avail.o: ../../include/events.h
+master_avail.o: ../../include/msg.h
+master_avail.o: ../../include/sys_defs.h
+master_avail.o: master.h
+master_avail.o: master_avail.c
+master_avail.o: master_proto.h
+master_conf.o: ../../include/argv.h
+master_conf.o: ../../include/msg.h
+master_conf.o: ../../include/sys_defs.h
+master_conf.o: master.h
+master_conf.o: master_conf.c
+master_ent.o: ../../include/argv.h
+master_ent.o: ../../include/attr.h
+master_ent.o: ../../include/check_arg.h
+master_ent.o: ../../include/compat_level.h
+master_ent.o: ../../include/host_port.h
+master_ent.o: ../../include/htable.h
+master_ent.o: ../../include/inet_addr_host.h
+master_ent.o: ../../include/inet_addr_list.h
+master_ent.o: ../../include/inet_proto.h
+master_ent.o: ../../include/iostuff.h
+master_ent.o: ../../include/mail_conf.h
+master_ent.o: ../../include/mail_params.h
+master_ent.o: ../../include/mail_proto.h
+master_ent.o: ../../include/match_service.h
+master_ent.o: ../../include/msg.h
+master_ent.o: ../../include/myaddrinfo.h
+master_ent.o: ../../include/mymalloc.h
+master_ent.o: ../../include/nvtable.h
+master_ent.o: ../../include/own_inet_addr.h
+master_ent.o: ../../include/readlline.h
+master_ent.o: ../../include/sock_addr.h
+master_ent.o: ../../include/stringops.h
+master_ent.o: ../../include/sys_defs.h
+master_ent.o: ../../include/vbuf.h
+master_ent.o: ../../include/vstream.h
+master_ent.o: ../../include/vstring.h
+master_ent.o: ../../include/wildcard_inet_addr.h
+master_ent.o: master.h
+master_ent.o: master_ent.c
+master_ent.o: master_proto.h
+master_flow.o: ../../include/iostuff.h
+master_flow.o: ../../include/msg.h
+master_flow.o: ../../include/sys_defs.h
+master_flow.o: master.h
+master_flow.o: master_flow.c
+master_flow.o: master_proto.h
+master_listen.o: ../../include/check_arg.h
+master_listen.o: ../../include/htable.h
+master_listen.o: ../../include/inet_addr_list.h
+master_listen.o: ../../include/iostuff.h
+master_listen.o: ../../include/listen.h
+master_listen.o: ../../include/mail_params.h
+master_listen.o: ../../include/msg.h
+master_listen.o: ../../include/myaddrinfo.h
+master_listen.o: ../../include/mymalloc.h
+master_listen.o: ../../include/set_eugid.h
+master_listen.o: ../../include/set_ugid.h
+master_listen.o: ../../include/sock_addr.h
+master_listen.o: ../../include/stringops.h
+master_listen.o: ../../include/sys_defs.h
+master_listen.o: ../../include/vbuf.h
+master_listen.o: ../../include/vstring.h
+master_listen.o: master.h
+master_listen.o: master_listen.c
+master_monitor.o: ../../include/iostuff.h
+master_monitor.o: ../../include/msg.h
+master_monitor.o: ../../include/sys_defs.h
+master_monitor.o: master.h
+master_monitor.o: master_monitor.c
+master_proto.o: ../../include/msg.h
+master_proto.o: ../../include/sys_defs.h
+master_proto.o: master_proto.c
+master_proto.o: master_proto.h
+master_service.o: ../../include/msg.h
+master_service.o: ../../include/mymalloc.h
+master_service.o: ../../include/sys_defs.h
+master_service.o: master.h
+master_service.o: master_service.c
+master_sig.o: ../../include/events.h
+master_sig.o: ../../include/iostuff.h
+master_sig.o: ../../include/killme_after.h
+master_sig.o: ../../include/msg.h
+master_sig.o: ../../include/posix_signals.h
+master_sig.o: ../../include/sys_defs.h
+master_sig.o: master.h
+master_sig.o: master_sig.c
+master_spawn.o: ../../include/argv.h
+master_spawn.o: ../../include/binhash.h
+master_spawn.o: ../../include/check_arg.h
+master_spawn.o: ../../include/events.h
+master_spawn.o: ../../include/mail_conf.h
+master_spawn.o: ../../include/msg.h
+master_spawn.o: ../../include/mymalloc.h
+master_spawn.o: ../../include/sys_defs.h
+master_spawn.o: ../../include/vbuf.h
+master_spawn.o: ../../include/vstring.h
+master_spawn.o: master.h
+master_spawn.o: master_proto.h
+master_spawn.o: master_spawn.c
+master_status.o: ../../include/binhash.h
+master_status.o: ../../include/events.h
+master_status.o: ../../include/iostuff.h
+master_status.o: ../../include/msg.h
+master_status.o: ../../include/sys_defs.h
+master_status.o: master.h
+master_status.o: master_proto.h
+master_status.o: master_status.c
+master_vars.o: ../../include/check_arg.h
+master_vars.o: ../../include/mail_conf.h
+master_vars.o: ../../include/mail_params.h
+master_vars.o: ../../include/msg.h
+master_vars.o: ../../include/mymalloc.h
+master_vars.o: ../../include/stringops.h
+master_vars.o: ../../include/sys_defs.h
+master_vars.o: ../../include/vbuf.h
+master_vars.o: ../../include/vstring.h
+master_vars.o: master.h
+master_vars.o: master_vars.c
+master_wakeup.o: ../../include/attr.h
+master_wakeup.o: ../../include/check_arg.h
+master_wakeup.o: ../../include/events.h
+master_wakeup.o: ../../include/htable.h
+master_wakeup.o: ../../include/iostuff.h
+master_wakeup.o: ../../include/mail_conf.h
+master_wakeup.o: ../../include/mail_params.h
+master_wakeup.o: ../../include/mail_proto.h
+master_wakeup.o: ../../include/msg.h
+master_wakeup.o: ../../include/mymalloc.h
+master_wakeup.o: ../../include/nvtable.h
+master_wakeup.o: ../../include/set_eugid.h
+master_wakeup.o: ../../include/set_ugid.h
+master_wakeup.o: ../../include/sys_defs.h
+master_wakeup.o: ../../include/trigger.h
+master_wakeup.o: ../../include/vbuf.h
+master_wakeup.o: ../../include/vstream.h
+master_wakeup.o: ../../include/vstring.h
+master_wakeup.o: mail_server.h
+master_wakeup.o: master.h
+master_wakeup.o: master_wakeup.c
+master_watch.o: ../../include/msg.h
+master_watch.o: ../../include/mymalloc.h
+master_watch.o: ../../include/sys_defs.h
+master_watch.o: master.h
+master_watch.o: master_watch.c
+multi_server.o: ../../include/argv.h
+multi_server.o: ../../include/attr.h
+multi_server.o: ../../include/bounce.h
+multi_server.o: ../../include/check_arg.h
+multi_server.o: ../../include/chroot_uid.h
+multi_server.o: ../../include/debug_process.h
+multi_server.o: ../../include/deliver_request.h
+multi_server.o: ../../include/dict.h
+multi_server.o: ../../include/dsn.h
+multi_server.o: ../../include/dsn_buf.h
+multi_server.o: ../../include/events.h
+multi_server.o: ../../include/htable.h
+multi_server.o: ../../include/iostuff.h
+multi_server.o: ../../include/listen.h
+multi_server.o: ../../include/mail_conf.h
+multi_server.o: ../../include/mail_dict.h
+multi_server.o: ../../include/mail_params.h
+multi_server.o: ../../include/mail_task.h
+multi_server.o: ../../include/mail_version.h
+multi_server.o: ../../include/maillog_client.h
+multi_server.o: ../../include/msg.h
+multi_server.o: ../../include/msg_stats.h
+multi_server.o: ../../include/msg_vstream.h
+multi_server.o: ../../include/myflock.h
+multi_server.o: ../../include/mymalloc.h
+multi_server.o: ../../include/nvtable.h
+multi_server.o: ../../include/recipient_list.h
+multi_server.o: ../../include/resolve_local.h
+multi_server.o: ../../include/safe_open.h
+multi_server.o: ../../include/sane_accept.h
+multi_server.o: ../../include/split_at.h
+multi_server.o: ../../include/stringops.h
+multi_server.o: ../../include/sys_defs.h
+multi_server.o: ../../include/timed_ipc.h
+multi_server.o: ../../include/vbuf.h
+multi_server.o: ../../include/vstream.h
+multi_server.o: ../../include/vstring.h
+multi_server.o: ../../include/watchdog.h
+multi_server.o: mail_flow.h
+multi_server.o: mail_server.h
+multi_server.o: master_proto.h
+multi_server.o: multi_server.c
+single_server.o: ../../include/argv.h
+single_server.o: ../../include/attr.h
+single_server.o: ../../include/bounce.h
+single_server.o: ../../include/check_arg.h
+single_server.o: ../../include/chroot_uid.h
+single_server.o: ../../include/debug_process.h
+single_server.o: ../../include/deliver_request.h
+single_server.o: ../../include/dict.h
+single_server.o: ../../include/dsn.h
+single_server.o: ../../include/dsn_buf.h
+single_server.o: ../../include/events.h
+single_server.o: ../../include/htable.h
+single_server.o: ../../include/iostuff.h
+single_server.o: ../../include/listen.h
+single_server.o: ../../include/mail_conf.h
+single_server.o: ../../include/mail_dict.h
+single_server.o: ../../include/mail_params.h
+single_server.o: ../../include/mail_task.h
+single_server.o: ../../include/mail_version.h
+single_server.o: ../../include/maillog_client.h
+single_server.o: ../../include/msg.h
+single_server.o: ../../include/msg_stats.h
+single_server.o: ../../include/msg_vstream.h
+single_server.o: ../../include/myflock.h
+single_server.o: ../../include/mymalloc.h
+single_server.o: ../../include/nvtable.h
+single_server.o: ../../include/recipient_list.h
+single_server.o: ../../include/resolve_local.h
+single_server.o: ../../include/safe_open.h
+single_server.o: ../../include/sane_accept.h
+single_server.o: ../../include/split_at.h
+single_server.o: ../../include/stringops.h
+single_server.o: ../../include/sys_defs.h
+single_server.o: ../../include/timed_ipc.h
+single_server.o: ../../include/vbuf.h
+single_server.o: ../../include/vstream.h
+single_server.o: ../../include/vstring.h
+single_server.o: ../../include/watchdog.h
+single_server.o: mail_flow.h
+single_server.o: mail_server.h
+single_server.o: master_proto.h
+single_server.o: single_server.c
+trigger_server.o: ../../include/argv.h
+trigger_server.o: ../../include/attr.h
+trigger_server.o: ../../include/bounce.h
+trigger_server.o: ../../include/check_arg.h
+trigger_server.o: ../../include/chroot_uid.h
+trigger_server.o: ../../include/debug_process.h
+trigger_server.o: ../../include/deliver_request.h
+trigger_server.o: ../../include/dict.h
+trigger_server.o: ../../include/dsn.h
+trigger_server.o: ../../include/dsn_buf.h
+trigger_server.o: ../../include/events.h
+trigger_server.o: ../../include/htable.h
+trigger_server.o: ../../include/iostuff.h
+trigger_server.o: ../../include/listen.h
+trigger_server.o: ../../include/mail_conf.h
+trigger_server.o: ../../include/mail_dict.h
+trigger_server.o: ../../include/mail_params.h
+trigger_server.o: ../../include/mail_task.h
+trigger_server.o: ../../include/mail_version.h
+trigger_server.o: ../../include/maillog_client.h
+trigger_server.o: ../../include/msg.h
+trigger_server.o: ../../include/msg_stats.h
+trigger_server.o: ../../include/msg_vstream.h
+trigger_server.o: ../../include/myflock.h
+trigger_server.o: ../../include/mymalloc.h
+trigger_server.o: ../../include/nvtable.h
+trigger_server.o: ../../include/recipient_list.h
+trigger_server.o: ../../include/resolve_local.h
+trigger_server.o: ../../include/safe_open.h
+trigger_server.o: ../../include/sane_accept.h
+trigger_server.o: ../../include/split_at.h
+trigger_server.o: ../../include/stringops.h
+trigger_server.o: ../../include/sys_defs.h
+trigger_server.o: ../../include/vbuf.h
+trigger_server.o: ../../include/vstream.h
+trigger_server.o: ../../include/vstring.h
+trigger_server.o: ../../include/watchdog.h
+trigger_server.o: mail_flow.h
+trigger_server.o: mail_server.h
+trigger_server.o: master_proto.h
+trigger_server.o: trigger_server.c
diff --git a/src/master/dgram_server.c b/src/master/dgram_server.c
new file mode 100644
index 0000000..e49500e
--- /dev/null
+++ b/src/master/dgram_server.c
@@ -0,0 +1,665 @@
+/*++
+/* NAME
+/* dgram_server 3
+/* SUMMARY
+/* skeleton datagram server subsystem
+/* SYNOPSIS
+/* #include <mail_server.h>
+/*
+/* NORETURN dgram_server_main(argc, argv, service, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*service)(char *buf, int len, char *service_name, char **argv);
+/* int key;
+/* DESCRIPTION
+/* This module implements a skeleton for mail subsystem programs
+/* that wake up on client request and perform some activity
+/* without further client interaction. This module supports
+/* local IPC via a UNIX-domain datagram socket. The resulting
+/* program expects to be run from the \fBmaster\fR process.
+/*
+/* dgram_server_main() is the skeleton entry point. It should
+/* be called from the application main program. The skeleton
+/* does the generic command-line options processing, initialization
+/* of configurable parameters, and receiving datagrams. The
+/* skeleton never returns.
+/*
+/* Arguments:
+/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)"
+/* A pointer to a function that is called by the skeleton each
+/* time a client sends a datagram to the program's service
+/* port. The function is run after the program has irrevocably
+/* dropped its privileges. The buffer argument specifies the
+/* data read from the datagram port; this data corresponds to
+/* request. The len argument specifies how much client data
+/* is available. The maximal size of the buffer is specified
+/* via the DGRAM_BUF_SIZE manifest constant. The service name
+/* argument corresponds to the service name in the master.cf
+/* file. The argv argument specifies command-line arguments
+/* left over after options processing.
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new request.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)"
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)"
+/* Override the default 1000s watchdog timeout. The value is
+/* used after command-line and main.cf file processing.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .PP
+/* The var_use_limit variable limits the number of clients that
+/* a server can service before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <chroot_uid.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <myflock.h>
+#include <safe_open.h>
+#include <listen.h>
+#include <watchdog.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_task.h>
+#include <debug_process.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <resolve_local.h>
+#include <mail_flow.h>
+#include <mail_version.h>
+#include <bounce.h>
+#include <maillog_client.h>
+
+/* Process manager. */
+
+#include "master_proto.h"
+
+/* Application-specific */
+
+#include "mail_server.h"
+
+ /*
+ * Global state.
+ */
+static int use_count;
+
+static DGRAM_SERVER_FN dgram_server_service;
+static char *dgram_server_name;
+static char **dgram_server_argv;
+static void (*dgram_server_accept) (int, void *);
+static void (*dgram_server_onexit) (char *, char **);
+static void (*dgram_server_pre_accept) (char *, char **);
+static VSTREAM *dgram_server_lock;
+static int dgram_server_in_flow_delay;
+static unsigned dgram_server_generation;
+static int dgram_server_watchdog = 1000;
+
+/* dgram_server_exit - normal termination */
+
+static NORETURN dgram_server_exit(void)
+{
+ if (dgram_server_onexit)
+ dgram_server_onexit(dgram_server_name, dgram_server_argv);
+ exit(0);
+}
+
+/* dgram_server_abort - terminate after abnormal master exit */
+
+static void dgram_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- exiting");
+ dgram_server_exit();
+}
+
+/* dgram_server_timeout - idle time exceeded */
+
+static void dgram_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ dgram_server_exit();
+}
+
+/* dgram_server_wakeup - wake up application */
+
+static void dgram_server_wakeup(int fd)
+{
+ char buf[DGRAM_BUF_SIZE];
+ ssize_t len;
+
+ /*
+ * Commit suicide when the master process disconnected from us, after
+ * handling the client request.
+ */
+ if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ if (dgram_server_in_flow_delay && mail_flow_get(1) < 0)
+ doze(var_in_flow_delay * 1000000);
+ if ((len = recv(fd, buf, sizeof(buf), 0)) >= 0)
+ dgram_server_service(buf, len, dgram_server_name, dgram_server_argv);
+ if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_AVAIL) < 0)
+ dgram_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ if (var_idle_limit > 0)
+ event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit);
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+}
+
+/* dgram_server_accept_unix - handle UNIX-domain socket event */
+
+static void dgram_server_accept_unix(int unused_event, void *context)
+{
+ const char *myname = "dgram_server_accept";
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+
+ if (dgram_server_lock != 0
+ && myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ if (msg_verbose)
+ msg_info("%s: request arrived", myname);
+
+ /*
+ * Read whatever the other side wrote. The socket is non-blocking so we
+ * won't get stuck when multiple processes wake up.
+ */
+ if (dgram_server_pre_accept)
+ dgram_server_pre_accept(dgram_server_name, dgram_server_argv);
+ dgram_server_wakeup(listen_fd);
+}
+
+/* dgram_server_main - the real main program */
+
+NORETURN dgram_server_main(int argc, char **argv, DGRAM_SERVER_FN service,...)
+{
+ const char *myname = "dgram_server_main";
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int socket_count = 1;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+ char *lock_path;
+ VSTRING *why;
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:t:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ dgram_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ dgram_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ dgram_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (!alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (!zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ dgram_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UXDG) == 0)
+ dgram_server_accept = dgram_server_accept_unix;
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(dgram_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, dgram_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (!alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((dgram_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(dgram_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ dgram_server_service = service;
+ dgram_server_name = service_name;
+ dgram_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(dgram_server_name, dgram_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(dgram_server_name, dgram_server_argv);
+
+ /*
+ * Running as a semi-resident server. Service requests. Terminate when we
+ * have serviced a sufficient number of requests, when no-one has been
+ * talking to us for a configurable amount of time, or when the master
+ * process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, dgram_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, dgram_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(dgram_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (dgram_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(dgram_server_name, dgram_server_argv) : -1;
+ event_loop(delay);
+ }
+ dgram_server_exit();
+}
diff --git a/src/master/event_server.c b/src/master/event_server.c
new file mode 100644
index 0000000..9802bdf
--- /dev/null
+++ b/src/master/event_server.c
@@ -0,0 +1,968 @@
+/*++
+/* NAME
+/* event_server 3
+/* SUMMARY
+/* skeleton multi-threaded mail subsystem
+/* SYNOPSIS
+/* #include <mail_server.h>
+/*
+/* NORETURN event_server_main(argc, argv, service, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*service)(VSTREAM *stream, char *service_name, char **argv);
+/* int key;
+/*
+/* void event_server_disconnect(fd)
+/* VSTREAM *stream;
+/*
+/* void event_server_drain()
+/* DESCRIPTION
+/* This module implements a skeleton for event-driven
+/* mail subsystems: mail subsystem programs that service multiple
+/* clients at the same time. The resulting program expects to be run
+/* from the \fBmaster\fR process.
+/*
+/* event_server_main() is the skeleton entry point. It should be
+/* called from the application main program. The skeleton does all
+/* the generic command-line processing, initialization of
+/* configurable parameters, and connection management.
+/* Unlike multi_server, this skeleton does not attempt to manage
+/* all the events on a client connection.
+/* The skeleton never returns.
+/*
+/* Arguments:
+/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)"
+/* A pointer to a function that is called by the skeleton each
+/* time a client connects to the program's service port. The
+/* function is run after the program has optionally dropped
+/* its privileges. The application is responsible for managing
+/* subsequent I/O events on the stream, and is responsible for
+/* calling event_server_disconnect() when the stream is closed.
+/* The stream initial state is non-blocking mode.
+/* Optional connection attributes are provided as a hash that
+/* is attached as stream context. NOTE: the attributes are
+/* destroyed after this function is called. The service
+/* name argument corresponds to the service name in the master.cf
+/* file. The argv argument specifies command-line arguments
+/* left over after options processing.
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_REQ_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_REQ_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_REQ_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_REQ_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_REQ_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new connection.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)"
+/* A pointer to a function that is called
+/* by the event_server_disconnect() function (see below).
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_SLOW_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called after "postfix reload"
+/* or "master exit". The application can call event_server_drain()
+/* (see below) to finish ongoing activities in the background.
+/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)"
+/* Override the default 1000s watchdog timeout. The value is
+/* used after command-line and main.cf file processing.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .IP "CA_MAIL_SERVER_RETIRE_ME"
+/* Prevent a process from being reused indefinitely. After
+/* (var_max_use * var_max_idle) seconds or some sane constant,
+/* stop accepting new connections and terminate voluntarily
+/* when the process becomes idle.
+/* .PP
+/* event_server_disconnect() should be called by the application
+/* to close a client connection.
+/*
+/* event_server_drain() should be called when the application
+/* no longer wishes to accept new client connections. Existing
+/* clients are handled in a background process, and the process
+/* terminates when the last client is disconnected. A non-zero
+/* result means this call should be tried again later.
+/*
+/* The var_use_limit variable limits the number of clients
+/* that a server can service before it commits suicide. This
+/* value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the
+/* client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits
+/* suicide. This value is taken from the global \fBmain.cf\fR
+/* configuration file. Setting \fBvar_idle_limit\fR to zero
+/* disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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>
+#include <sys/time.h> /* select() */
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h> /* select() */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <chroot_uid.h>
+#include <listen.h>
+#include <events.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <myflock.h>
+#include <safe_open.h>
+#include <listen.h>
+#include <watchdog.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_task.h>
+#include <debug_process.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <timed_ipc.h>
+#include <resolve_local.h>
+#include <mail_flow.h>
+#include <mail_version.h>
+#include <bounce.h>
+#include <maillog_client.h>
+
+/* Process manager. */
+
+#include "master_proto.h"
+
+/* Application-specific */
+
+#include "mail_server.h"
+
+ /*
+ * Global state.
+ */
+static int client_count;
+static int use_count;
+static int socket_count = 1;
+
+static void (*event_server_service) (VSTREAM *, char *, char **);
+static char *event_server_name;
+static char **event_server_argv;
+static void (*event_server_accept) (int, void *);
+static void (*event_server_onexit) (char *, char **);
+static void (*event_server_pre_accept) (char *, char **);
+static VSTREAM *event_server_lock;
+static int event_server_in_flow_delay;
+static unsigned event_server_generation;
+static void (*event_server_pre_disconn) (VSTREAM *, char *, char **);
+static void (*event_server_slow_exit) (char *, char **);
+static int event_server_watchdog = 1000;
+
+/* event_server_exit - normal termination */
+
+static NORETURN event_server_exit(void)
+{
+ if (event_server_onexit)
+ event_server_onexit(event_server_name, event_server_argv);
+ exit(0);
+}
+
+/* event_server_retire - retire when idle */
+
+static void event_server_retire(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("time to retire -- %s", event_server_slow_exit ?
+ "draining" : "exiting");
+ event_disable_readwrite(MASTER_STATUS_FD);
+ if (event_server_slow_exit)
+ event_server_slow_exit(event_server_name, event_server_argv);
+ else
+ event_server_exit();
+}
+
+/* event_server_abort - terminate after abnormal master exit */
+
+static void event_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- %s", event_server_slow_exit ?
+ "draining" : "exiting");
+ event_disable_readwrite(MASTER_STATUS_FD);
+ if (event_server_slow_exit)
+ event_server_slow_exit(event_server_name, event_server_argv);
+ else
+ event_server_exit();
+}
+
+/* event_server_timeout - idle time exceeded */
+
+static void event_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ event_server_exit();
+}
+
+/* event_server_drain - stop accepting new clients */
+
+int event_server_drain(void)
+{
+ const char *myname = "event_server_drain";
+ int fd;
+
+ switch (fork()) {
+ /* Try again later. */
+ case -1:
+ return (-1);
+ /* Finish existing clients in the background, then terminate. */
+ case 0:
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ event_fork();
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_disable_readwrite(fd);
+ (void) close(fd);
+ /* Play safe - don't reuse this file number. */
+ if (DUP2(STDIN_FILENO, fd) < 0)
+ msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
+ }
+ var_use_limit = 1;
+ return (0);
+ /* Let the master start a new process. */
+ default:
+ exit(0);
+ }
+}
+
+/* event_server_disconnect - terminate client session */
+
+void event_server_disconnect(VSTREAM *stream)
+{
+ if (msg_verbose)
+ msg_info("connection closed fd %d", vstream_fileno(stream));
+ if (event_server_pre_disconn)
+ event_server_pre_disconn(stream, event_server_name, event_server_argv);
+ (void) vstream_fclose(stream);
+ client_count--;
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+ if (client_count == 0 && var_idle_limit > 0)
+ event_request_timer(event_server_timeout, (void *) 0, var_idle_limit);
+}
+
+/* event_server_execute - in case (char *) != (struct *) */
+
+static void event_server_execute(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+ HTABLE *attr = (HTABLE *) vstream_context(stream);
+
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ /*
+ * Do bother the application when the client disconnected. Don't drop the
+ * already accepted client request after "postfix reload"; that would be
+ * rude.
+ */
+ if (master_notify(var_pid, event_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ event_server_service(stream, event_server_name, event_server_argv);
+ if (master_notify(var_pid, event_server_generation, MASTER_STAT_AVAIL) < 0)
+ event_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ if (attr)
+ htable_free(attr, myfree);
+}
+
+/* event_server_wakeup - wake up application */
+
+static void event_server_wakeup(int fd, HTABLE *attr)
+{
+ VSTREAM *stream;
+ char *tmp;
+
+#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ int new_fd;
+
+ /*
+ * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
+ * case of a multi-server with a thousand clients.
+ */
+ if (fd < THRESHOLD_FD_WORKAROUND) {
+ if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
+ msg_fatal("fcntl F_DUPFD: %m");
+ (void) close(fd);
+ fd = new_fd;
+ }
+#endif
+ if (msg_verbose)
+ msg_info("connection established fd %d", fd);
+ non_blocking(fd, BLOCKING);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ client_count++;
+ stream = vstream_fdopen(fd, O_RDWR);
+ tmp = concatenate(event_server_name, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(tmp),
+ CA_VSTREAM_CTL_CONTEXT((void *) attr),
+ CA_VSTREAM_CTL_END);
+ myfree(tmp);
+ timed_ipc_setup(stream);
+ if (event_server_in_flow_delay && mail_flow_get(1) < 0)
+ event_request_timer(event_server_execute, (void *) stream,
+ var_in_flow_delay);
+ else
+ event_server_execute(0, (void *) stream);
+}
+
+/* event_server_accept_local - accept client connection request */
+
+static void event_server_accept_local(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, (HTABLE *) 0);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* event_server_accept_pass - accept descriptor */
+
+static void event_server_accept_pass(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+ HTABLE *attr = 0;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = pass_accept_attr(listen_fd, &attr);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, attr);
+}
+
+#endif
+
+/* event_server_accept_inet - accept client connection request */
+
+static void event_server_accept_inet(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = inet_accept(listen_fd);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, (HTABLE *) 0);
+}
+
+/* event_server_main - the real main program */
+
+NORETURN event_server_main(int argc, char **argv, MULTI_SERVER_FN service,...)
+{
+ const char *myname = "event_server_main";
+ VSTREAM *stream = 0;
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+
+#if 0
+ char *lock_path;
+ VSTRING *why;
+
+#endif
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+ int retire_me_from_flags = 0;
+ int retire_me = 0;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 'r':
+ if ((retire_me_from_flags = atoi(optarg)) <= 0)
+ msg_fatal("invalid retirement time: %s", optarg);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ event_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ event_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_PRE_DISCONN:
+ event_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ event_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ event_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_SLOW_EXIT:
+ event_server_slow_exit = va_arg(ap, MAIL_SERVER_SLOW_EXIT_FN);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ case MAIL_SERVER_RETIRE_ME:
+ if (retire_me_from_flags > 0)
+ retire_me = retire_me_from_flags;
+ else if (var_idle_limit == 0 || var_use_limit == 0
+ || var_idle_limit > 18000 / var_use_limit)
+ retire_me = 18000;
+ else
+ retire_me = var_idle_limit * var_use_limit;
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ event_server_accept = event_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ event_server_accept = event_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ event_server_accept = event_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(event_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, event_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+
+ /*
+ * XXX Can't compete for exclusive access to the listen socket because we
+ * also have to monitor existing client connections for service requests.
+ */
+#if 0
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((event_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(event_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+#endif
+
+ /*
+ * Set up call-back info.
+ */
+ event_server_service = service;
+ event_server_name = service_name;
+ event_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(event_server_name, event_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(event_server_name, event_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, event_server_name, event_server_argv);
+ vstream_fflush(stream);
+ event_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(event_server_timeout, (void *) 0, var_idle_limit);
+ if (retire_me)
+ event_request_timer(event_server_retire, (void *) 0, retire_me);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, event_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, event_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(event_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) {
+ if (event_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(event_server_name, event_server_argv) : -1;
+ event_loop(delay);
+ }
+ event_server_exit();
+}
diff --git a/src/master/mail_flow.c b/src/master/mail_flow.c
new file mode 100644
index 0000000..2958500
--- /dev/null
+++ b/src/master/mail_flow.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* mail_flow 3
+/* SUMMARY
+/* global mail flow control
+/* SYNOPSIS
+/* #include <mail_flow.h>
+/*
+/* ssize_t mail_flow_get(count)
+/* ssize_t count;
+/*
+/* ssize_t mail_flow_put(count)
+/* ssize_t count;
+/*
+/* ssize_t mail_flow_count()
+/* DESCRIPTION
+/* This module implements a simple flow control mechanism that
+/* is based on tokens that are consumed by mail receiving processes
+/* and that are produced by mail sending processes.
+/*
+/* mail_flow_get() attempts to read specified number of tokens. The
+/* result is > 0 for success, < 0 for failure. In the latter case,
+/* the process is expected to slow down a little.
+/*
+/* mail_flow_put() produces the specified number of tokens. The
+/* token producing process is expected to produce new tokens
+/* whenever it falls idle and no more tokens are available.
+/*
+/* mail_flow_count() returns the number of available tokens.
+/* BUGS
+/* The producer needs to wake up periodically to ensure that
+/* tokens are not lost due to leakage.
+/* 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>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_flow.h>
+
+/* Master library. */
+
+#include <master_proto.h>
+
+#define BUFFER_SIZE 1024
+
+/* mail_flow_get - read N tokens */
+
+ssize_t mail_flow_get(ssize_t len)
+{
+ const char *myname = "mail_flow_get";
+ char buf[BUFFER_SIZE];
+ struct stat st;
+ ssize_t count;
+ ssize_t n = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (len <= 0)
+ msg_panic("%s: bad length %ld", myname, (long) len);
+
+ /*
+ * Silence some wild claims.
+ */
+ if (fstat(MASTER_FLOW_WRITE, &st) < 0)
+ msg_fatal("fstat flow pipe write descriptor: %m");
+
+ /*
+ * Read and discard N bytes. XXX AIX read() can return 0 when an open
+ * pipe is empty.
+ */
+ for (count = len; count > 0; count -= n)
+ if ((n = read(MASTER_FLOW_READ, buf, count > BUFFER_SIZE ?
+ BUFFER_SIZE : count)) <= 0)
+ return (-1);
+ if (msg_verbose)
+ msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count));
+ return (len - count);
+}
+
+/* mail_flow_put - put N tokens */
+
+ssize_t mail_flow_put(ssize_t len)
+{
+ const char *myname = "mail_flow_put";
+ char buf[BUFFER_SIZE];
+ ssize_t count;
+ ssize_t n = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (len <= 0)
+ msg_panic("%s: bad length %ld", myname, (long) len);
+
+ /*
+ * Write or discard N bytes.
+ */
+ memset(buf, 0, len > BUFFER_SIZE ? BUFFER_SIZE : len);
+
+ for (count = len; count > 0; count -= n)
+ if ((n = write(MASTER_FLOW_WRITE, buf, count > BUFFER_SIZE ?
+ BUFFER_SIZE : count)) < 0)
+ return (-1);
+ if (msg_verbose)
+ msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count));
+ return (len - count);
+}
+
+/* mail_flow_count - return number of available tokens */
+
+ssize_t mail_flow_count(void)
+{
+ const char *myname = "mail_flow_count";
+ ssize_t count;
+
+ if ((count = peekfd(MASTER_FLOW_READ)) < 0)
+ msg_warn("%s: %m", myname);
+ return (count);
+}
diff --git a/src/master/mail_flow.h b/src/master/mail_flow.h
new file mode 100644
index 0000000..3f7f7bd
--- /dev/null
+++ b/src/master/mail_flow.h
@@ -0,0 +1,32 @@
+#ifndef _MAIL_FLOW_H_INCLUDED_
+#define _MAIL_FLOW_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_flow 3h
+/* SUMMARY
+/* global mail flow control
+/* SYNOPSIS
+/* #include <mail_flow.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Functional interface.
+ */
+extern ssize_t mail_flow_get(ssize_t);
+extern ssize_t mail_flow_put(ssize_t);
+extern ssize_t mail_flow_count(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/master/mail_server.h b/src/master/mail_server.h
new file mode 100644
index 0000000..93703da
--- /dev/null
+++ b/src/master/mail_server.h
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* mail_server 3h
+/* SUMMARY
+/* skeleton servers
+/* SYNOPSIS
+/* #include <mail_server.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <htable.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_conf.h>
+
+ /*
+ * External interface. Tables are defined in mail_conf.h.
+ */
+#define MAIL_SERVER_INT_TABLE 1
+#define MAIL_SERVER_STR_TABLE 2
+#define MAIL_SERVER_BOOL_TABLE 3
+#define MAIL_SERVER_TIME_TABLE 4
+#define MAIL_SERVER_RAW_TABLE 5
+#define MAIL_SERVER_NINT_TABLE 6
+#define MAIL_SERVER_NBOOL_TABLE 7
+#define MAIL_SERVER_LONG_TABLE 8
+
+#define MAIL_SERVER_PRE_INIT 10
+#define MAIL_SERVER_POST_INIT 11
+#define MAIL_SERVER_LOOP 12
+#define MAIL_SERVER_EXIT 13
+#define MAIL_SERVER_PRE_ACCEPT 14
+#define MAIL_SERVER_SOLITARY 15
+#define MAIL_SERVER_UNLIMITED 16
+#define MAIL_SERVER_PRE_DISCONN 17
+#define MAIL_SERVER_PRIVILEGED 18
+#define MAIL_SERVER_WATCHDOG 19
+
+#define MAIL_SERVER_IN_FLOW_DELAY 20
+#define MAIL_SERVER_SLOW_EXIT 21
+#define MAIL_SERVER_BOUNCE_INIT 22
+#define MAIL_SERVER_RETIRE_ME 23
+#define MAIL_SERVER_POST_ACCEPT 24
+
+typedef void (*MAIL_SERVER_INIT_FN) (char *, char **);
+typedef int (*MAIL_SERVER_LOOP_FN) (char *, char **);
+typedef void (*MAIL_SERVER_EXIT_FN) (char *, char **);
+typedef void (*MAIL_SERVER_ACCEPT_FN) (char *, char **);
+typedef void (*MAIL_SERVER_POST_ACCEPT_FN) (VSTREAM *, char *, char **, HTABLE *);
+typedef void (*MAIL_SERVER_DISCONN_FN) (VSTREAM *, char *, char **);
+typedef void (*MAIL_SERVER_SLOW_EXIT_FN) (char *, char **);
+
+/* Type-checked API for external use. */
+#define CA_MAIL_SERVER_INT_TABLE(v) MAIL_SERVER_INT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_INT_TABLE, (v))
+#define CA_MAIL_SERVER_STR_TABLE(v) MAIL_SERVER_STR_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_STR_TABLE, (v))
+#define CA_MAIL_SERVER_BOOL_TABLE(v) MAIL_SERVER_BOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_BOOL_TABLE, (v))
+#define CA_MAIL_SERVER_TIME_TABLE(v) MAIL_SERVER_TIME_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_TIME_TABLE, (v))
+#define CA_MAIL_SERVER_RAW_TABLE(v) MAIL_SERVER_RAW_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_RAW_TABLE, (v))
+#define CA_MAIL_SERVER_NINT_TABLE(v) MAIL_SERVER_NINT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NINT_TABLE, (v))
+#define CA_MAIL_SERVER_NBOOL_TABLE(v) MAIL_SERVER_NBOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NBOOL_TABLE, (v))
+#define CA_MAIL_SERVER_LONG_TABLE(v) MAIL_SERVER_LONG_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_LONG_TABLE, (v))
+#define CA_MAIL_SERVER_PRE_INIT(v) MAIL_SERVER_PRE_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v))
+#define CA_MAIL_SERVER_POST_INIT(v) MAIL_SERVER_POST_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v))
+#define CA_MAIL_SERVER_LOOP(v) MAIL_SERVER_LOOP, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_LOOP_FN, (v))
+#define CA_MAIL_SERVER_EXIT(v) MAIL_SERVER_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_EXIT_FN, (v))
+#define CA_MAIL_SERVER_PRE_ACCEPT(v) MAIL_SERVER_PRE_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN, (v))
+#define CA_MAIL_SERVER_POST_ACCEPT(v) MAIL_SERVER_POST_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN, (v))
+#define CA_MAIL_SERVER_SOLITARY MAIL_SERVER_SOLITARY
+#define CA_MAIL_SERVER_UNLIMITED MAIL_SERVER_UNLIMITED
+#define CA_MAIL_SERVER_PRE_DISCONN(v) MAIL_SERVER_PRE_DISCONN, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN, (v))
+#define CA_MAIL_SERVER_PRIVILEGED MAIL_SERVER_PRIVILEGED
+#define CA_MAIL_SERVER_WATCHDOG(v) MAIL_SERVER_WATCHDOG, CHECK_PTR(MAIL_SERVER, int, (v))
+#define CA_MAIL_SERVER_IN_FLOW_DELAY MAIL_SERVER_IN_FLOW_DELAY
+#define CA_MAIL_SERVER_SLOW_EXIT(v) MAIL_SERVER_SLOW_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN, (v))
+#define CA_MAIL_SERVER_BOUNCE_INIT(v, w) MAIL_SERVER_BOUNCE_INIT, CHECK_PTR(MAIL_SERVER, char, (v)), CHECK_PPTR(MAIL_SERVER, char, (w))
+#define CA_MAIL_SERVER_RETIRE_ME MAIL_SERVER_RETIRE_ME
+
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_LOOP_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_INIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_EXIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN);
+CHECK_PTR_HELPER_DCL(MAIL_SERVER, int);
+CHECK_PTR_HELPER_DCL(MAIL_SERVER, char);
+CHECK_PPTR_HELPER_DCL(MAIL_SERVER, char);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_TIME_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_STR_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_RAW_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NINT_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NBOOL_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_LONG_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_INT_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_BOOL_TABLE);
+
+ /*
+ * single_server.c
+ */
+typedef void (*SINGLE_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN single_server_main(int, char **, SINGLE_SERVER_FN,...);
+
+ /*
+ * multi_server.c
+ */
+typedef void (*MULTI_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN multi_server_main(int, char **, MULTI_SERVER_FN,...);
+extern void multi_server_disconnect(VSTREAM *);
+extern int multi_server_drain(void);
+
+ /*
+ * event_server.c
+ */
+typedef void (*EVENT_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN event_server_main(int, char **, EVENT_SERVER_FN,...);
+extern void event_server_disconnect(VSTREAM *);
+extern int event_server_drain(void);
+
+ /*
+ * trigger_server.c
+ */
+typedef void (*TRIGGER_SERVER_FN) (char *, ssize_t, char *, char **);
+extern NORETURN trigger_server_main(int, char **, TRIGGER_SERVER_FN,...);
+
+#define TRIGGER_BUF_SIZE 1024
+
+ /*
+ * dgram_server.c
+ */
+typedef void (*DGRAM_SERVER_FN) (char *, ssize_t, char *, char **);
+extern NORETURN dgram_server_main(int, char **, DGRAM_SERVER_FN,...);
+
+#define DGRAM_BUF_SIZE 4096
+
+/* 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/master/master.c b/src/master/master.c
new file mode 100644
index 0000000..1fc3fe9
--- /dev/null
+++ b/src/master/master.c
@@ -0,0 +1,598 @@
+/*++
+/* NAME
+/* master 8
+/* SUMMARY
+/* Postfix master process
+/* SYNOPSIS
+/* \fBmaster\fR [\fB-Dditvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR]
+/* DESCRIPTION
+/* The \fBmaster\fR(8) daemon is the resident process that runs Postfix
+/* daemons on demand: daemons to send or receive messages via the
+/* network, daemons to deliver mail locally, etc. These daemons are
+/* created on demand up to a configurable maximum number per service.
+/*
+/* Postfix daemons terminate voluntarily, either after being idle for
+/* a configurable amount of time, or after having serviced a
+/* configurable number of requests. Exceptions to this rule are the
+/* resident queue manager, address verification server, and the TLS
+/* session cache and pseudo-random number server.
+/*
+/* The behavior of the \fBmaster\fR(8) daemon is controlled by the
+/* \fBmaster.cf\fR configuration file, as described in \fBmaster\fR(5).
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in
+/* the named directory instead of the default configuration directory.
+/* This also overrides the configuration files for other Postfix
+/* daemon processes.
+/* .IP \fB-D\fR
+/* After initialization, run a debugger on the master process. The
+/* debugging command is specified with the \fBdebugger_command\fR in
+/* the \fBmain.cf\fR global configuration file.
+/* .IP \fB-d\fR
+/* Do not redirect stdin, stdout or stderr to /dev/null, and
+/* do not discard the controlling terminal. This must be used
+/* for debugging only.
+/* .IP "\fB-e \fIexit_time\fR"
+/* Terminate the master process after \fIexit_time\fR seconds. Child
+/* processes terminate at their convenience.
+/* .IP \fB-i\fR
+/* Enable \fBinit\fR mode: do not become a session or process
+/* group leader; and similar to \fB-s\fR, do not redirect stdout
+/* to /dev/null, so that "maillog_file = /dev/stdout" works.
+/* This mode is allowed only if the process ID equals 1.
+/* .sp
+/* This feature is available in Postfix 3.3 and later.
+/* .IP \fB-s\fR
+/* Do not redirect stdout to /dev/null, so that "maillog_file
+/* = /dev/stdout" works.
+/* .sp
+/* This feature is available in Postfix 3.4 and later.
+/* .IP \fB-t\fR
+/* Test mode. Return a zero exit status when the \fBmaster.pid\fR lock
+/* file does not exist or when that file is not locked. This is evidence
+/* that the \fBmaster\fR(8) daemon is not running.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. This option
+/* is passed on to child processes. Multiple \fB-v\fR options
+/* make the software increasingly verbose.
+/* .IP \fB-w\fR
+/* Wait in a dummy foreground process, while the real master
+/* daemon initializes in a background process. The dummy
+/* foreground process returns a zero exit status only if the
+/* master daemon initialization is successful, and if it
+/* completes in a reasonable amount of time.
+/* .sp
+/* This feature is available in Postfix 2.10 and later.
+/* .PP
+/* Signals:
+/* .IP \fBSIGHUP\fR
+/* Upon receipt of a \fBHUP\fR signal (e.g., after "\fBpostfix reload\fR"),
+/* the master process re-reads its configuration files. If a service has
+/* been removed from the \fBmaster.cf\fR file, its running processes
+/* are terminated immediately.
+/* Otherwise, running processes are allowed to terminate as soon
+/* as is convenient, so that changes in configuration settings
+/* affect only new service requests.
+/* .IP \fBSIGTERM\fR
+/* Upon receipt of a \fBTERM\fR signal (e.g., after "\fBpostfix abort\fR"),
+/* the master process passes the signal on to its child processes and
+/* terminates.
+/* This is useful for an emergency shutdown. Normally one would
+/* terminate only the master ("\fBpostfix stop\fR") and allow running
+/* processes to finish what they are doing.
+/* DIAGNOSTICS
+/* Problems are reported to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* The exit status
+/* is non-zero in case of problems, including problems while
+/* initializing as a master daemon process in the background.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_DEBUG\fR
+/* After initialization, start a debugger as specified with the
+/* \fBdebugger_command\fR configuration parameter in the \fBmain.cf\fR
+/* configuration file.
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Unlike most Postfix daemon processes, the \fBmaster\fR(8) server does
+/* not automatically pick up changes to \fBmain.cf\fR. Changes
+/* to \fBmaster.cf\fR are never picked up automatically.
+/* Use the "\fBpostfix reload\fR" command after a configuration change.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdefault_process_limit (100)\fR"
+/* The default maximal number of Postfix child processes that provide
+/* a given service.
+/* .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 "\fBservice_throttle_time (60s)\fR"
+/* How long the Postfix \fBmaster\fR(8) waits before forking a server that
+/* appears to be malfunctioning.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBmaster_service_disable (empty)\fR"
+/* Selectively disable \fBmaster\fR(8) listener ports by service type
+/* or by service name and type.
+/* 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_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix support programs and daemon programs.
+/* .IP "\fBdebugger_command (empty)\fR"
+/* The external command to execute when a Postfix daemon program is
+/* invoked with the -D option.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .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 "\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 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.6 and later:
+/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR"
+/* Optional setting that avoids lookups in the \fBservices\fR(5) database.
+/* FILES
+/* .ad
+/* .fi
+/* To expand the directory names below into their actual values,
+/* use the command "\fBpostconf config_directory\fR" etc.
+/* .na
+/* .nf
+/*
+/* $config_directory/main.cf, global configuration file.
+/* $config_directory/master.cf, master server configuration file.
+/* $queue_directory/pid/master.pid, master lock file.
+/* $data_directory/master.lock, master lock file.
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* verify(8), address verification
+/* master(5), master.cf configuration file syntax
+/* postconf(5), main.cf configuration file syntax
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <events.h>
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <myflock.h>
+#include <watchdog.h>
+#include <clean_env.h>
+#include <argv.h>
+#include <safe.h>
+#include <set_eugid.h>
+#include <set_ugid.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <debug_process.h>
+#include <mail_task.h>
+#include <mail_conf.h>
+#include <open_lock.h>
+#include <inet_proto.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+int master_detach = 1;
+int init_mode = 0;
+
+/* master_exit_event - exit for memory leak testing purposes */
+
+static void master_exit_event(int unused_event, void *unused_context)
+{
+ msg_info("master exit time has arrived");
+ exit(0);
+}
+
+/* usage - show hint and terminate */
+
+static NORETURN usage(const char *me)
+{
+ msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - main program */
+
+int main(int argc, char **argv)
+{
+ static VSTREAM *lock_fp;
+ static VSTREAM *data_lock_fp;
+ VSTRING *lock_path;
+ VSTRING *data_lock_path;
+ off_t inherited_limit;
+ int debug_me = 0;
+ int keep_stdout = 0;
+ int ch;
+ int fd;
+ int n;
+ int test_lock = 0;
+ VSTRING *why;
+ WATCHDOG *watchdog;
+ ARGV *import_env;
+ int wait_flag = 0;
+ int monitor_fd = -1;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Initialize.
+ */
+ umask(077); /* never fails! */
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Strip and save the process name for diagnostics etc.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+
+ /*
+ * When running a child process, don't leak any open files that were
+ * leaked to us by our own (privileged) parent process. Descriptors 0-2
+ * are taken care of after we have initialized error logging.
+ *
+ * Some systems such as AIX have a huge per-process open file limit. In
+ * those cases, limit the search for potential file descriptor leaks to
+ * just the first couple hundred.
+ *
+ * The Debian post-installation script passes an open file descriptor into
+ * the master process and waits forever for someone to close it. Because
+ * of this we have to close descriptors > 2, and pray that doing so does
+ * not break things.
+ */
+ closefrom(3);
+
+ /*
+ * Initialize logging and exit handler.
+ */
+ maillog_client_init(mail_task(var_procname),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * The mail system must be run by the superuser so it can revoke
+ * privileges for selected operations. That's right - it takes privileges
+ * to toss privileges.
+ */
+ if (getuid() != 0)
+ msg_fatal("the master command is reserved for the superuser");
+ if (unsafe() != 0)
+ msg_fatal("the master command must not run as a set-uid process");
+
+ /*
+ * Process JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "c:Dde:istvw")) > 0) {
+ switch (ch) {
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'd':
+ master_detach = 0;
+ break;
+ case 'e':
+ event_request_timer(master_exit_event, (void *) 0, atoi(optarg));
+ break;
+ case 'i':
+ if (getpid() != 1)
+ msg_fatal("-i is allowed only for PID 1 process");
+ init_mode = 1;
+ keep_stdout = 1;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 's':
+ keep_stdout = 1;
+ break;
+ case 't':
+ test_lock = 1;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ wait_flag = 1;
+ break;
+ default:
+ usage(argv[0]);
+ /* NOTREACHED */
+ }
+ }
+
+ /*
+ * This program takes no other arguments.
+ */
+ if (argc > optind)
+ usage(argv[0]);
+
+ /*
+ * Sanity check.
+ */
+ if (test_lock && wait_flag)
+ msg_fatal("the -t and -w options cannot be used together");
+ if (init_mode && (debug_me || !master_detach || wait_flag))
+ msg_fatal("the -i option cannot be used with -D, -d, or -w");
+
+ /*
+ * Run a foreground monitor process that returns an exit status of 0 when
+ * the child background process reports successful initialization as a
+ * daemon process. We use a generous limit in case main/master.cf specify
+ * symbolic hosts/ports and the naming service is slow.
+ */
+#define MASTER_INIT_TIMEOUT 100 /* keep this limit generous */
+
+ if (wait_flag)
+ monitor_fd = master_monitor(MASTER_INIT_TIMEOUT);
+
+ /*
+ * If started from a terminal, get rid of any tty association. This also
+ * means that all errors and warnings must go to the syslog daemon.
+ * Some new world has no terminals and prefers logging to stdout.
+ */
+ if (master_detach)
+ for (fd = 0; fd < 3; fd++) {
+ if (fd == STDOUT_FILENO && keep_stdout)
+ continue;
+ (void) close(fd);
+ if (open("/dev/null", O_RDWR, 0) != fd)
+ msg_fatal("open /dev/null: %m");
+ }
+
+ /*
+ * Run in a separate process group, so that "postfix stop" can terminate
+ * all MTA processes cleanly. Give up if we can't separate from our
+ * parent process. We're not supposed to blow away the parent.
+ */
+ if (init_mode == 0 && debug_me == 0 && master_detach != 0
+ && setsid() == -1 && getsid(0) != getpid())
+ msg_fatal("unable to set session and process group ID: %m");
+
+ /*
+ * Make some room for plumbing with file descriptors. XXX This breaks
+ * when a service listens on many ports. In order to do this right we
+ * must change the master-child interface so that descriptors do not need
+ * to have fixed numbers.
+ *
+ * In a child we need two descriptors for the flow control pipe, one for
+ * child->master status updates and at least one for listening.
+ */
+ for (n = 0; n < 5; n++) {
+ if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0)
+ msg_fatal("dup(0): %m");
+ }
+
+ /*
+ * Final initializations. Unfortunately, we must read the global Postfix
+ * configuration file after doing command-line processing, so that we get
+ * consistent results when we SIGHUP the server to reload configuration
+ * files.
+ */
+ master_vars_init();
+
+ /*
+ * In case of multi-protocol support. This needs to be done because
+ * master does not invoke mail_params_init() (it was written before that
+ * code existed).
+ */
+ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Environment import filter, to enforce consistent behavior whether
+ * Postfix is started by hand, or at system boot time.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ if ((inherited_limit = get_file_limit()) < 0)
+ set_file_limit(OFF_T_MAX);
+
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ /*
+ * Lock down the master.pid file. In test mode, no file means that it
+ * isn't locked.
+ */
+ lock_path = vstring_alloc(10);
+ data_lock_path = vstring_alloc(10);
+ why = vstring_alloc(10);
+
+ vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname);
+ if (test_lock && access(vstring_str(lock_path), F_OK) < 0)
+ exit(0);
+ lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why);
+ if (test_lock)
+ exit(lock_fp ? 0 : 1);
+ if (lock_fp == 0)
+ msg_fatal("open lock file %s: %s",
+ vstring_str(lock_path), vstring_str(why));
+ vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4,
+ (unsigned long) var_pid);
+ if (vstream_fflush(lock_fp))
+ msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path));
+ close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC);
+
+ /*
+ * Lock down the Postfix-writable data directory.
+ */
+ vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname);
+ set_eugid(var_owner_uid, var_owner_gid);
+ data_lock_fp =
+ open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why);
+ set_ugid(getuid(), getgid());
+ if (data_lock_fp == 0)
+ msg_fatal("open lock file %s: %s",
+ vstring_str(data_lock_path), vstring_str(why));
+ vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4,
+ (unsigned long) var_pid);
+ if (vstream_fflush(data_lock_fp))
+ msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path));
+ close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC);
+
+ /*
+ * Clean up.
+ */
+ vstring_free(why);
+ vstring_free(lock_path);
+ vstring_free(data_lock_path);
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Finish initialization, last part. We must process configuration files
+ * after processing command-line parameters, so that we get consistent
+ * results when we SIGHUP the server to reload configuration files.
+ */
+ master_config();
+ master_sigsetup();
+ master_flow_init();
+ maillog_client_init(mail_task(var_procname),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ msg_info("daemon started -- version %s, configuration %s",
+ var_mail_version, var_config_dir);
+
+ /*
+ * Report successful initialization to the foreground monitor process.
+ */
+ if (monitor_fd >= 0) {
+ write(monitor_fd, "", 1);
+ (void) close(monitor_fd);
+ }
+
+ /*
+ * Process events. The event handler will execute the read/write/timer
+ * action routines. Whenever something has happened, see if we received
+ * any signal in the mean time. Although the master process appears to do
+ * multiple things at the same time, it really is all a single thread, so
+ * that there are no concurrency conflicts within the master process.
+ */
+#define MASTER_WATCHDOG_TIME 1000
+
+ watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (void *) 0);
+ for (;;) {
+#ifdef HAS_VOLATILE_LOCKS
+ if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("refresh exclusive lock: %m");
+ if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("refresh exclusive lock: %m");
+#endif
+ watchdog_start(watchdog); /* same as trigger servers */
+ event_loop(MASTER_WATCHDOG_TIME / 2);
+ if (master_gotsighup) {
+ msg_info("reload -- version %s, configuration %s",
+ var_mail_version, var_config_dir);
+ master_gotsighup = 0; /* this first */
+ master_vars_init(); /* then this */
+ master_refresh(); /* then this */
+ maillog_client_init(mail_task(var_procname),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ }
+ if (master_gotsigchld) {
+ if (msg_verbose)
+ msg_info("got sigchld");
+ master_gotsigchld = 0; /* this first */
+ master_reap_child(); /* then this */
+ }
+ }
+}
diff --git a/src/master/master.h b/src/master/master.h
new file mode 100644
index 0000000..ce07ab7
--- /dev/null
+++ b/src/master/master.h
@@ -0,0 +1,246 @@
+/*++
+/* NAME
+/* master 3h
+/* SUMMARY
+/* Postfix master - data structures and prototypes
+/* SYNOPSIS
+/* #include "master.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Server processes that provide the same service share a common "listen"
+ * socket to accept connection requests, and share a common pipe to the
+ * master process to send status reports. Server processes die voluntarily
+ * when idle for a configurable amount of time, or after servicing a
+ * configurable number of requests; the master process spawns new processes
+ * on demand up to a configurable concurrency limit and/or periodically.
+ *
+ * The canonical service name is what we use internally, so that we correctly
+ * handle a request to "reload" after someone changes "smtp" into "25".
+ *
+ * We use the external service name from master.cf when reporting problems, so
+ * that the user can figure out what we are talking about. Of course we also
+ * include the canonical service name so that the UNIX-domain smtp service
+ * can be distinguished from the Internet smtp service.
+ */
+typedef struct MASTER_SERV {
+ int flags; /* status, features, etc. */
+ char *ext_name; /* service endpoint name (master.cf) */
+ char *name; /* service endpoint name (canonical) */
+ int type; /* UNIX-domain, INET, etc. */
+ time_t busy_warn_time; /* limit "all servers busy" warning */
+ int wakeup_time; /* wakeup interval */
+ int *listen_fd; /* incoming requests */
+ int listen_fd_count; /* nr of descriptors */
+ union {
+ struct {
+ char *port; /* inet listen port */
+ struct INET_ADDR_LIST *addr;/* inet listen address */
+ } inet_ep;
+#define MASTER_INET_ADDRLIST(s) ((s)->endpoint.inet_ep.addr)
+#define MASTER_INET_PORT(s) ((s)->endpoint.inet_ep.port)
+ } endpoint;
+ int max_proc; /* upper bound on # processes */
+ char *path; /* command pathname */
+ struct ARGV *args; /* argument vector */
+ char *stress_param_val; /* stress value: "yes" or empty */
+ time_t stress_expire_time; /* stress pulse stretcher */
+ int avail_proc; /* idle processes */
+ int total_proc; /* number of processes */
+ int throttle_delay; /* failure recovery parameter */
+ int status_fd[2]; /* child status reports */
+ struct BINHASH *children; /* linkage */
+ struct MASTER_SERV *next; /* linkage */
+} MASTER_SERV;
+
+ /*
+ * Per-service flag bits. We assume trouble when a child process terminates
+ * before completing its first request: either the program is defective,
+ * some configuration is wrong, or the system is out of resources.
+ */
+#define MASTER_FLAG_THROTTLE (1<<0) /* we're having trouble */
+#define MASTER_FLAG_MARK (1<<1) /* garbage collection support */
+#define MASTER_FLAG_CONDWAKE (1<<2) /* wake up if actually used */
+#define MASTER_FLAG_INETHOST (1<<3) /* endpoint name specifies host */
+#define MASTER_FLAG_LOCAL_ONLY (1<<4) /* no remote clients */
+#define MASTER_FLAG_LISTEN (1<<5) /* monitor this port */
+
+#define MASTER_THROTTLED(f) ((f)->flags & MASTER_FLAG_THROTTLE)
+#define MASTER_MARKED_FOR_DELETION(f) ((f)->flags & MASTER_FLAG_MARK)
+#define MASTER_LISTENING(f) ((f)->flags & MASTER_FLAG_LISTEN)
+
+#define MASTER_LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit)))
+
+ /*
+ * Service types, stream sockets unless indicated otherwise.
+ */
+#define MASTER_SERV_TYPE_UNIX 1 /* AF_UNIX domain socket */
+#define MASTER_SERV_TYPE_INET 2 /* AF_INET domain socket */
+#define MASTER_SERV_TYPE_FIFO 3 /* fifo (named pipe) */
+#define MASTER_SERV_TYPE_PASS 4 /* AF_UNIX domain socket */
+#define MASTER_SERV_TYPE_UXDG 5 /* AF_UNIX domain datagram socket */
+
+ /*
+ * Default process management policy values. This is only the bare minimum.
+ * Most policy management is delegated to child processes. The process
+ * manager runs at high privilege level and has to be kept simple.
+ */
+#define MASTER_DEF_MIN_IDLE 1 /* preferred # of idle processes */
+
+ /*
+ * Structure of child process.
+ */
+typedef int MASTER_PID; /* pid is key into binhash table */
+
+typedef struct MASTER_PROC {
+ MASTER_PID pid; /* child process id */
+ unsigned gen; /* child generation number */
+ int avail; /* availability */
+ MASTER_SERV *serv; /* parent linkage */
+ int use_count; /* number of service requests */
+} MASTER_PROC;
+
+ /*
+ * Other manifest constants.
+ */
+#define MASTER_BUF_LEN 2048 /* logical config line length */
+
+ /*
+ * master.c
+ */
+extern int master_detach;
+extern int init_mode;
+
+ /*
+ * master_ent.c
+ */
+extern void fset_master_ent(char *);
+extern void set_master_ent(void);
+extern void end_master_ent(void);
+extern void print_master_ent(MASTER_SERV *);
+extern MASTER_SERV *get_master_ent(void);
+extern void free_master_ent(MASTER_SERV *);
+
+ /*
+ * master_conf.c
+ */
+extern void master_config(void);
+extern void master_refresh(void);
+
+ /*
+ * master_vars.c
+ */
+extern void master_vars_init(void);
+
+ /*
+ * master_service.c
+ */
+extern MASTER_SERV *master_head;
+extern void master_start_service(MASTER_SERV *);
+extern void master_stop_service(MASTER_SERV *);
+extern void master_restart_service(MASTER_SERV *, int);
+
+#define DO_CONF_RELOAD 1 /* config files were reloaded */
+#define NO_CONF_RELOAD 0 /* no config file was reloaded */
+
+ /*
+ * master_events.c
+ */
+extern int master_gotsighup;
+extern int master_gotsigchld;
+extern void master_sigsetup(void);
+
+ /*
+ * master_status.c
+ */
+extern void master_status_init(MASTER_SERV *);
+extern void master_status_cleanup(MASTER_SERV *);
+
+ /*
+ * master_wakeup.c
+ */
+extern void master_wakeup_init(MASTER_SERV *);
+extern void master_wakeup_cleanup(MASTER_SERV *);
+
+
+ /*
+ * master_listen.c
+ */
+extern void master_listen_init(MASTER_SERV *);
+extern void master_listen_cleanup(MASTER_SERV *);
+
+ /*
+ * master_avail.c
+ */
+extern void master_avail_listen(MASTER_SERV *);
+extern void master_avail_cleanup(MASTER_SERV *);
+extern void master_avail_more(MASTER_SERV *, MASTER_PROC *);
+extern void master_avail_less(MASTER_SERV *, MASTER_PROC *);
+
+ /*
+ * master_spawn.c
+ */
+extern struct BINHASH *master_child_table;
+extern void master_spawn(MASTER_SERV *);
+extern void master_reap_child(void);
+extern void master_delete_children(MASTER_SERV *);
+
+ /*
+ * master_flow.c
+ */
+extern void master_flow_init(void);
+extern int master_flow_pipe[2];
+
+ /*
+ * master_watch.c
+ *
+ * Support to warn about main.cf parameters that can only be initialized but
+ * not updated, and to initialize or update data structures that derive
+ * values from main.cf parameters.
+ */
+typedef struct {
+ const char *name; /* parameter name */
+ char **value; /* current main.cf value */
+ char **backup; /* actual value that is being used */
+ int flags; /* see below */
+ void (*notify) (void); /* init or update data structure */
+} MASTER_STR_WATCH;
+
+typedef struct {
+ const char *name; /* parameter name */
+ int *value; /* current main.cf value */
+ int backup; /* actual value that is being used */
+ int flags; /* see below */
+ void (*notify) (void); /* init or update data structure */
+} MASTER_INT_WATCH;
+
+#define MASTER_WATCH_FLAG_UPDATABLE (1<<0) /* support update after init */
+#define MASTER_WATCH_FLAG_ISSET (1<<1) /* backup is initialized */
+
+extern void master_str_watch(const MASTER_STR_WATCH *);
+extern void master_int_watch(MASTER_INT_WATCH *);
+
+ /*
+ * master_monitor.c
+ */
+extern int master_monitor(int);
+
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* 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/master/master_avail.c b/src/master/master_avail.c
new file mode 100644
index 0000000..046503d
--- /dev/null
+++ b/src/master/master_avail.c
@@ -0,0 +1,251 @@
+/*++
+/* NAME
+/* master_avail 3
+/* SUMMARY
+/* Postfix master - process creation policy
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_avail_listen(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_avail_cleanup(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_avail_more(serv, proc)
+/* MASTER_SERV *serv;
+/* MASTER_PROC *proc;
+/*
+/* void master_avail_less(serv, proc)
+/* MASTER_SERV *serv;
+/* MASTER_PROC *proc;
+/* DESCRIPTION
+/* This module implements the process creation policy. As long as
+/* the allowed number of processes for the given service is not
+/* exceeded, a connection request is either handled by an existing
+/* available process, or this module causes a new process to be
+/* created to service the request.
+/*
+/* When the service runs out of process slots, and the service
+/* is eligible for stress-mode operation, a warning is logged,
+/* servers are asked to restart at their convenience, and new
+/* servers are created with stress mode enabled.
+/*
+/* master_avail_listen() ensures that someone monitors the service's
+/* listen socket for connection requests (as long as resources
+/* to handle connection requests are available). This function may
+/* be called at random times, but it must be called after each status
+/* change of a service (throttled, process limit, etc.) or child
+/* process (taken, available, dead, etc.).
+/*
+/* master_avail_cleanup() should be called when the named service
+/* is taken out of operation. It terminates child processes by
+/* sending SIGTERM.
+/*
+/* master_avail_more() should be called when the named process
+/* has become available for servicing new connection requests.
+/* This function updates the process availability status and
+/* counter, and implicitly calls master_avail_listen().
+/*
+/* master_avail_less() should be called when the named process
+/* has become unavailable for servicing new connection requests.
+/* This function updates the process availability status and
+/* counter, and implicitly calls master_avail_listen().
+/* DIAGNOSTICS
+/* Panic: internal inconsistencies.
+/* BUGS
+/* SEE ALSO
+/* master_spawn(3), child process birth and death
+/* 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 libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <events.h>
+#include <msg.h>
+
+/* Application-specific. */
+
+#include "master_proto.h"
+#include "master.h"
+
+/* master_avail_event - create child process to handle connection request */
+
+static void master_avail_event(int event, void *context)
+{
+ MASTER_SERV *serv = (MASTER_SERV *) context;
+ time_t now;
+
+ if (event == 0) /* XXX Can this happen? */
+ msg_panic("master_avail_event: null event");
+ else {
+
+ /*
+ * When all servers for a public internet service are busy, we start
+ * creating server processes with "-o stress=yes" on the command
+ * line, and keep creating such processes until the process count is
+ * below the limit for at least 1000 seconds. This provides a minimal
+ * solution that can be adopted into legacy and stable Postfix
+ * releases.
+ *
+ * This is not the right place to update serv->stress_param_val in
+ * response to stress level changes. Doing so would would contaminate
+ * the "postfix reload" code with stress management implementation
+ * details, creating a source of future bugs. Instead, we update
+ * simple counters or flags here, and use their values to determine
+ * the proper serv->stress_param_val value when exec-ing a server
+ * process.
+ */
+ if (serv->stress_param_val != 0
+ && !MASTER_LIMIT_OK(serv->max_proc, serv->total_proc + 1)) {
+ now = event_time();
+ if (serv->stress_expire_time < now)
+ master_restart_service(serv, NO_CONF_RELOAD);
+ serv->stress_expire_time = now + 1000;
+ }
+ master_spawn(serv);
+ }
+}
+
+/* master_avail_listen - enforce the socket monitoring policy */
+
+void master_avail_listen(MASTER_SERV *serv)
+{
+ const char *myname = "master_avail_listen";
+ int listen_flag;
+ time_t now;
+ int n;
+
+ /*
+ * Caution: several other master_XXX modules call master_avail_listen(),
+ * master_avail_more() or master_avail_less(). To avoid mutual dependency
+ * problems, the code below invokes no code in other master_XXX modules,
+ * and modifies no data that is maintained by other master_XXX modules.
+ *
+ * When no-one else is monitoring the service's listen socket, start
+ * monitoring the socket for connection requests. All this under the
+ * restriction that we have sufficient resources to service a connection
+ * request.
+ */
+ if (msg_verbose)
+ msg_info("%s: %s avail %d total %d max %d", myname, serv->name,
+ serv->avail_proc, serv->total_proc, serv->max_proc);
+ if (MASTER_THROTTLED(serv) || serv->avail_proc > 0) {
+ listen_flag = 0;
+ } else if (MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) {
+ listen_flag = 1;
+ } else {
+ listen_flag = 0;
+ if (serv->stress_param_val != 0) {
+ now = event_time();
+ if (serv->busy_warn_time < now - 1000) {
+ serv->busy_warn_time = now;
+ msg_warn("service \"%s\" (%s) has reached its process limit \"%d\": "
+ "new clients may experience noticeable delays",
+ serv->ext_name, serv->name, serv->max_proc);
+ msg_warn("to avoid this condition, increase the process count "
+ "in master.cf or reduce the service time per client");
+ msg_warn("see http://www.postfix.org/STRESS_README.html for "
+ "examples of stress-adapting configuration settings");
+ }
+ }
+ }
+ if (listen_flag && !MASTER_LISTENING(serv)) {
+ if (msg_verbose)
+ msg_info("%s: enable events %s", myname, serv->name);
+ for (n = 0; n < serv->listen_fd_count; n++)
+ event_enable_read(serv->listen_fd[n], master_avail_event,
+ (void *) serv);
+ serv->flags |= MASTER_FLAG_LISTEN;
+ } else if (!listen_flag && MASTER_LISTENING(serv)) {
+ if (msg_verbose)
+ msg_info("%s: disable events %s", myname, serv->name);
+ for (n = 0; n < serv->listen_fd_count; n++)
+ event_disable_readwrite(serv->listen_fd[n]);
+ serv->flags &= ~MASTER_FLAG_LISTEN;
+ }
+}
+
+/* master_avail_cleanup - cleanup */
+
+void master_avail_cleanup(MASTER_SERV *serv)
+{
+ int n;
+
+ master_delete_children(serv); /* XXX calls
+ * master_avail_listen */
+
+ /*
+ * This code is redundant because master_delete_children() throttles the
+ * service temporarily before calling master_avail_listen/less(), which
+ * then turn off read events. This temporary throttling is not documented
+ * (it is only an optimization), and therefore we must not depend on it.
+ */
+ if (MASTER_LISTENING(serv)) {
+ for (n = 0; n < serv->listen_fd_count; n++)
+ event_disable_readwrite(serv->listen_fd[n]);
+ serv->flags &= ~MASTER_FLAG_LISTEN;
+ }
+}
+
+/* master_avail_more - one more available child process */
+
+void master_avail_more(MASTER_SERV *serv, MASTER_PROC *proc)
+{
+ const char *myname = "master_avail_more";
+
+ /*
+ * Caution: several other master_XXX modules call master_avail_listen(),
+ * master_avail_more() or master_avail_less(). To avoid mutual dependency
+ * problems, the code below invokes no code in other master_XXX modules,
+ * and modifies no data that is maintained by other master_XXX modules.
+ *
+ * This child process has become available for servicing connection
+ * requests, so we can stop monitoring the service's listen socket. The
+ * child will do it for us.
+ */
+ if (msg_verbose)
+ msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name);
+ if (proc->avail == MASTER_STAT_AVAIL)
+ msg_panic("%s: process already available", myname);
+ serv->avail_proc++;
+ proc->avail = MASTER_STAT_AVAIL;
+ master_avail_listen(serv);
+}
+
+/* master_avail_less - one less available child process */
+
+void master_avail_less(MASTER_SERV *serv, MASTER_PROC *proc)
+{
+ const char *myname = "master_avail_less";
+
+ /*
+ * Caution: several other master_XXX modules call master_avail_listen(),
+ * master_avail_more() or master_avail_less(). To avoid mutual dependency
+ * problems, the code below invokes no code in other master_XXX modules,
+ * and modifies no data that is maintained by other master_XXX modules.
+ *
+ * This child is no longer available for servicing connection requests. When
+ * no child processes are available, start monitoring the service's
+ * listen socket for new connection requests.
+ */
+ if (msg_verbose)
+ msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name);
+ if (proc->avail != MASTER_STAT_AVAIL)
+ msg_panic("%s: process not available", myname);
+ serv->avail_proc--;
+ proc->avail = MASTER_STAT_TAKEN;
+ master_avail_listen(serv);
+}
diff --git a/src/master/master_conf.c b/src/master/master_conf.c
new file mode 100644
index 0000000..37cad2a
--- /dev/null
+++ b/src/master/master_conf.c
@@ -0,0 +1,152 @@
+/*++
+/* NAME
+/* master_conf 3
+/* SUMMARY
+/* Postfix master - master.cf file processing
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_config(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_refresh(serv)
+/* MASTER_SERV *serv;
+/* DESCRIPTION
+/* Use master_config() to read the master.cf configuration file
+/* during program initialization.
+/*
+/* Use master_refresh() to re-read the master.cf configuration file
+/* when the process is already running.
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* master_ent(3), configuration file programmatic interface.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+/* master_refresh - re-read configuration table */
+
+void master_refresh(void)
+{
+ MASTER_SERV *serv;
+ MASTER_SERV **servp;
+
+ /*
+ * Mark all existing services.
+ */
+ for (serv = master_head; serv != 0; serv = serv->next)
+ serv->flags |= MASTER_FLAG_MARK;
+
+ /*
+ * Read the master.cf configuration file. The master_conf() routine
+ * unmarks services upon update. New services are born with the mark bit
+ * off. After this, anything with the mark bit on should be removed.
+ */
+ master_config();
+
+ /*
+ * Delete all services that are still marked - they disappeared from the
+ * configuration file and are therefore no longer needed.
+ */
+ for (servp = &master_head; (serv = *servp) != 0; /* void */ ) {
+ if ((serv->flags & MASTER_FLAG_MARK) != 0) {
+ *servp = serv->next;
+ master_stop_service(serv);
+ free_master_ent(serv);
+ } else {
+ servp = &serv->next;
+ }
+ }
+}
+
+/* master_config - read config file */
+
+void master_config(void)
+{
+ MASTER_SERV *entry;
+ MASTER_SERV *serv;
+
+#define STR_DIFF strcmp
+#define STR_SAME !strcmp
+#define SWAP(type,a,b) { type temp = a; a = b; b = temp; }
+
+ /*
+ * A service is identified by its endpoint name AND by its transport
+ * type, not just by its name alone. The name is unique within its
+ * transport type. XXX Service privacy is encoded in the service name.
+ */
+ set_master_ent();
+ while ((entry = get_master_ent()) != 0) {
+ if (msg_verbose)
+ print_master_ent(entry);
+ for (serv = master_head; serv != 0; serv = serv->next)
+ if (STR_SAME(serv->name, entry->name) && serv->type == entry->type)
+ break;
+
+ /*
+ * Add a new service entry. We do not really care in what order the
+ * service entries are kept in memory.
+ */
+ if (serv == 0) {
+ entry->next = master_head;
+ master_head = entry;
+ master_start_service(entry);
+ }
+
+ /*
+ * Update an existing service entry. Make the current generation of
+ * child processes commit suicide whenever it is convenient. The next
+ * generation of child processes will run with the new configuration
+ * settings.
+ */
+ else {
+ if ((serv->flags & MASTER_FLAG_MARK) == 0)
+ msg_warn("duplicate master.cf entry for service \"%s\" (%s) "
+ "-- using the last entry", serv->ext_name, serv->name);
+ else
+ serv->flags &= ~MASTER_FLAG_MARK;
+ if (entry->flags & MASTER_FLAG_CONDWAKE)
+ serv->flags |= MASTER_FLAG_CONDWAKE;
+ else
+ serv->flags &= ~MASTER_FLAG_CONDWAKE;
+ serv->wakeup_time = entry->wakeup_time;
+ serv->max_proc = entry->max_proc;
+ serv->throttle_delay = entry->throttle_delay;
+ SWAP(char *, serv->ext_name, entry->ext_name);
+ SWAP(char *, serv->path, entry->path);
+ SWAP(ARGV *, serv->args, entry->args);
+ SWAP(char *, serv->stress_param_val, entry->stress_param_val);
+ master_restart_service(serv, DO_CONF_RELOAD);
+ free_master_ent(entry);
+ }
+ }
+ end_master_ent();
+}
diff --git a/src/master/master_ent.c b/src/master/master_ent.c
new file mode 100644
index 0000000..5edc308
--- /dev/null
+++ b/src/master/master_ent.c
@@ -0,0 +1,648 @@
+/*++
+/* NAME
+/* master_ent 3
+/* SUMMARY
+/* Postfix master - config file access
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void fset_master_ent(path)
+/* char *path;
+/*
+/* void set_master_ent()
+/*
+/* MASTER_SERV *get_master_ent()
+/*
+/* void end_master_ent()
+/*
+/* void print_master_ent(entry)
+/* MASTER_SERV *entry;
+/*
+/* void free_master_ent(entry)
+/* MASTER_SERV *entry;
+/* DESCRIPTION
+/* This module implements a simple programmatic interface
+/* for accessing Postfix master process configuration files.
+/*
+/* fset_master_ent() specifies the location of the master process
+/* configuration file. The pathname is copied.
+/*
+/* set_master_ent() opens the configuration file. It is an error
+/* to call this routine while the configuration file is still open.
+/* It is an error to open a configuration file without specifying
+/* its name to fset_master_ent().
+/*
+/* get_master_ent() reads the next entry from an open configuration
+/* file and returns the parsed result. A null result means the end
+/* of file was reached.
+/*
+/* print_master_ent() prints the specified service entry.
+/*
+/* end_master_ent() closes an open configuration file. It is an error
+/* to call this routine when the configuration file is not open.
+/*
+/* free_master_ent() destroys the memory used for a parsed configuration
+/* file entry.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: memory allocation
+/* failure.
+/* BUGS
+/* SEE ALSO
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility libraries. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <inet_addr_list.h>
+#include <host_port.h>
+#include <inet_addr_host.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <match_service.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <wildcard_inet_addr.h>
+#include <mail_conf.h>
+#include <compat_level.h>
+
+/* Local stuff. */
+
+#include "master_proto.h"
+#include "master.h"
+
+static char *master_path; /* config file name */
+static VSTREAM *master_fp; /* config file pointer */
+static int master_line_last; /* config file line number */
+static int master_line; /* config file line number */
+static ARGV *master_disable; /* disabled service patterns */
+
+static char master_blanks[] = CHARS_SPACE; /* field delimiters */
+
+/* fset_master_ent - specify configuration file pathname */
+
+void fset_master_ent(char *path)
+{
+ if (master_path != 0)
+ myfree(master_path);
+ master_path = mystrdup(path);
+}
+
+/* set_master_ent - open configuration file */
+
+void set_master_ent()
+{
+ const char *myname = "set_master_ent";
+ char *disable;
+
+ if (master_fp != 0)
+ msg_panic("%s: configuration file still open", myname);
+ if (master_path == 0)
+ msg_panic("%s: no configuration file specified", myname);
+ if ((master_fp = vstream_fopen(master_path, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", master_path);
+ master_line_last = 0;
+ if (master_disable != 0)
+ msg_panic("%s: service disable list still exists", myname);
+ if (inet_proto_info()->ai_family_list[0] == 0) {
+ msg_warn("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+ msg_warn("disabling all type \"inet\" services in master.cf");
+ disable = concatenate(MASTER_XPORT_NAME_INET, ",",
+ var_master_disable, (char *) 0);
+ master_disable = match_service_init(disable);
+ myfree(disable);
+ } else
+ master_disable = match_service_init(var_master_disable);
+}
+
+/* end_master_ent - close configuration file */
+
+void end_master_ent()
+{
+ const char *myname = "end_master_ent";
+
+ if (master_fp == 0)
+ msg_panic("%s: configuration file not open", myname);
+ if (vstream_fclose(master_fp) != 0)
+ msg_fatal("%s: close configuration file: %m", myname);
+ master_fp = 0;
+ if (master_disable == 0)
+ msg_panic("%s: no service disable list", myname);
+ match_service_free(master_disable);
+ master_disable = 0;
+}
+
+/* master_conf_context - plot the target range */
+
+static const char *master_conf_context(void)
+{
+ static VSTRING *context_buf = 0;
+
+ if (context_buf == 0)
+ context_buf = vstring_alloc(100);
+ vstring_sprintf(context_buf, "%s: line %d", master_path, master_line);
+ return (vstring_str(context_buf));
+}
+
+/* fatal_with_context - print fatal error with file/line context */
+
+static NORETURN PRINTFLIKE(1, 2) fatal_with_context(char *format,...)
+{
+ const char *myname = "fatal_with_context";
+ VSTRING *vp = vstring_alloc(100);
+ va_list ap;
+
+ if (master_path == 0)
+ msg_panic("%s: no configuration file specified", myname);
+
+ va_start(ap, format);
+ vstring_vsprintf(vp, format, ap);
+ va_end(ap);
+ msg_fatal("%s: %s", master_conf_context(), vstring_str(vp));
+}
+
+/* fatal_invalid_field - report invalid field value */
+
+static NORETURN fatal_invalid_field(char *name, char *value)
+{
+ fatal_with_context("field \"%s\": bad value: \"%s\"", name, value);
+}
+
+/* get_str_ent - extract string field */
+
+static char *get_str_ent(char **bufp, char *name, char *def_val)
+{
+ char *value;
+
+ if ((value = mystrtok(bufp, master_blanks)) == 0)
+ fatal_with_context("missing \"%s\" field", name);
+ if (strcmp(value, "-") == 0) {
+ if (def_val == 0)
+ fatal_with_context("field \"%s\" has no default value", name);
+ if (warn_compat_break_chroot && strcmp(name, "chroot") == 0)
+ msg_info("%s: using backwards-compatible default setting "
+ "%s=%s", master_conf_context(), name, def_val);
+ return (def_val);
+ } else {
+ return (value);
+ }
+}
+
+/* get_bool_ent - extract boolean field */
+
+static int get_bool_ent(char **bufp, char *name, char *def_val)
+{
+ char *value;
+
+ value = get_str_ent(bufp, name, def_val);
+ if (strcmp("y", value) == 0) {
+ return (1);
+ } else if (strcmp("n", value) == 0) {
+ return (0);
+ } else {
+ fatal_invalid_field(name, value);
+ }
+ /* NOTREACHED */
+}
+
+/* get_int_ent - extract integer field */
+
+static int get_int_ent(char **bufp, char *name, char *def_val, int min_val)
+{
+ char *value;
+ int n;
+
+ value = get_str_ent(bufp, name, def_val);
+ if (!ISDIGIT(*value) || (n = atoi(value)) < min_val)
+ fatal_invalid_field(name, value);
+ return (n);
+}
+
+/* get_master_ent - read entry from configuration file */
+
+MASTER_SERV *get_master_ent()
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *junk = vstring_alloc(100);
+ MASTER_SERV *serv;
+ char *cp;
+ char *name;
+ char *host = 0;
+ char *port = 0;
+ char *transport;
+ int private;
+ int unprivileged; /* passed on to child */
+ int chroot; /* passed on to child */
+ char *command;
+ int n;
+ char *bufp;
+ char *atmp;
+ const char *parse_err;
+ static char *saved_interfaces = 0;
+ char *err;
+
+ if (master_fp == 0)
+ msg_panic("get_master_ent: config file not open");
+ if (master_disable == 0)
+ msg_panic("get_master_ent: no service disable list");
+
+ /*
+ * XXX We cannot change the inet_interfaces setting for a running master
+ * process. Listening sockets are inherited by child processes so that
+ * closing and reopening those sockets in the master does not work.
+ *
+ * Another problem is that library routines still cache results that are
+ * based on the old inet_interfaces setting. It is too much trouble to
+ * recompute everything.
+ *
+ * In order to keep our data structures consistent we ignore changes in
+ * inet_interfaces settings, and issue a warning instead.
+ */
+ if (saved_interfaces == 0)
+ saved_interfaces = mystrdup(var_inet_interfaces);
+
+ /*
+ * Skip blank lines and comment lines.
+ */
+ for (;;) {
+ if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) {
+ vstring_free(buf);
+ vstring_free(junk);
+ return (0);
+ }
+ bufp = vstring_str(buf);
+ if ((cp = mystrtok(&bufp, master_blanks)) == 0)
+ continue;
+ name = cp;
+ transport = get_str_ent(&bufp, "transport type", (char *) 0);
+ vstring_sprintf(junk, "%s/%s", name, transport);
+ if (match_service_match(master_disable, vstring_str(junk)) == 0)
+ break;
+ }
+
+ /*
+ * Parse one logical line from the configuration file. Initialize service
+ * structure members in order.
+ */
+ serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV));
+ serv->next = 0;
+
+ /*
+ * Flags member.
+ */
+ serv->flags = 0;
+
+ /*
+ * All servers busy warning timer.
+ */
+ serv->busy_warn_time = 0;
+
+ /*
+ * Service name. Syntax is transport-specific.
+ */
+ serv->ext_name = mystrdup(name);
+
+ /*
+ * Transport type: inet (wild-card listen or virtual) or unix.
+ */
+#define STR_SAME !strcmp
+
+ if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) {
+ if (!STR_SAME(saved_interfaces, var_inet_interfaces)) {
+ msg_warn("service %s: ignoring %s change",
+ serv->ext_name, VAR_INET_INTERFACES);
+ msg_warn("to change %s, stop and start Postfix",
+ VAR_INET_INTERFACES);
+ }
+ serv->type = MASTER_SERV_TYPE_INET;
+ atmp = mystrdup(name);
+ if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0)
+ fatal_with_context("%s in \"%s\"", parse_err, name);
+ if (*host) {
+ serv->flags |= MASTER_FLAG_INETHOST;/* host:port */
+ MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *)
+ mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv)));
+ inet_addr_list_init(MASTER_INET_ADDRLIST(serv));
+ if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0)
+ fatal_with_context("bad hostname or network address: %s", name);
+ inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv));
+ serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used;
+ } else {
+ MASTER_INET_ADDRLIST(serv) =
+ strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ?
+ own_inet_addr_list() : /* virtual */
+ wildcard_inet_addr_list(); /* wild-card */
+ inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv));
+ serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used;
+ }
+ MASTER_INET_PORT(serv) = mystrdup(port);
+ for (n = 0; /* see below */ ; n++) {
+ if (n >= MASTER_INET_ADDRLIST(serv)->used) {
+ serv->flags |= MASTER_FLAG_LOCAL_ONLY;
+ break;
+ }
+ if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n)))
+ break;
+ }
+ } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) {
+ serv->type = MASTER_SERV_TYPE_UNIX;
+ serv->listen_fd_count = 1;
+ serv->flags |= MASTER_FLAG_LOCAL_ONLY;
+ } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) {
+ serv->type = MASTER_SERV_TYPE_UXDG;
+ serv->listen_fd_count = 1;
+ serv->flags |= MASTER_FLAG_LOCAL_ONLY;
+ } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) {
+ serv->type = MASTER_SERV_TYPE_FIFO;
+ serv->listen_fd_count = 1;
+ serv->flags |= MASTER_FLAG_LOCAL_ONLY;
+#ifdef MASTER_SERV_TYPE_PASS
+ } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) {
+ serv->type = MASTER_SERV_TYPE_PASS;
+ serv->listen_fd_count = 1;
+ /* If this is a connection screener, remote clients are likely. */
+#endif
+ } else {
+ fatal_with_context("bad transport type: %s", transport);
+ }
+
+ /*
+ * Service class: public or private.
+ */
+ private = get_bool_ent(&bufp, "private", "y");
+
+ /*
+ * Derive an internal service name. The name may depend on service
+ * attributes such as privacy.
+ */
+ if (serv->type == MASTER_SERV_TYPE_INET) {
+ MAI_HOSTADDR_STR host_addr;
+ MAI_SERVPORT_STR serv_port;
+ struct addrinfo *res0;
+
+ if (private)
+ fatal_with_context("inet service cannot be private");
+
+ /*
+ * Canonicalize endpoint names so that we correctly handle "reload"
+ * requests after someone changes "25" into "smtp" or vice versa.
+ */
+ if (*host == 0)
+ host = 0;
+ /* Canonicalize numeric host and numeric or symbolic service. */
+ if (hostaddr_to_sockaddr(host, port, 0, &res0) == 0) {
+ SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen,
+ host ? &host_addr : (MAI_HOSTADDR_STR *) 0,
+ &serv_port, 0);
+ serv->name = (host ? concatenate("[", host_addr.buf, "]:",
+ serv_port.buf, (char *) 0) :
+ mystrdup(serv_port.buf));
+ freeaddrinfo(res0);
+ }
+ /* Canonicalize numeric or symbolic service. */
+ else if (hostaddr_to_sockaddr((char *) 0, port, 0, &res0) == 0) {
+ SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen,
+ (MAI_HOSTADDR_STR *) 0, &serv_port, 0);
+ serv->name = (host ? concatenate("[", host, "]:",
+ serv_port.buf, (char *) 0) :
+ mystrdup(serv_port.buf));
+ freeaddrinfo(res0);
+ }
+ /* Bad service name? */
+ else
+ serv->name = mystrdup(name);
+ myfree(atmp);
+ } else if (serv->type == MASTER_SERV_TYPE_UNIX) {
+ serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
+ MAIL_CLASS_PUBLIC, name);
+ } else if (serv->type == MASTER_SERV_TYPE_UXDG) {
+ serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
+ MAIL_CLASS_PUBLIC, name);
+ } else if (serv->type == MASTER_SERV_TYPE_FIFO) {
+ serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
+ MAIL_CLASS_PUBLIC, name);
+#ifdef MASTER_SERV_TYPE_PASS
+ } else if (serv->type == MASTER_SERV_TYPE_PASS) {
+ serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
+ MAIL_CLASS_PUBLIC, name);
+#endif
+ } else {
+ msg_panic("bad transport type: %d", serv->type);
+ }
+
+ /*
+ * Listen socket(s). XXX We pre-allocate storage because the number of
+ * sockets is frozen anyway once we build the command-line vector below.
+ */
+ if (serv->listen_fd_count == 0) {
+ fatal_with_context("no valid IP address found: %s", name);
+ }
+ serv->listen_fd = (int *) mymalloc(sizeof(int) * serv->listen_fd_count);
+ for (n = 0; n < serv->listen_fd_count; n++)
+ serv->listen_fd[n] = -1;
+
+ /*
+ * Privilege level. Default is to restrict process privileges to those of
+ * the mail owner.
+ */
+ unprivileged = get_bool_ent(&bufp, "unprivileged", "y");
+
+ /*
+ * Chroot. Default is to restrict file system access to the mail queue.
+ * XXX Chroot cannot imply unprivileged service (for example, the pickup
+ * service runs chrooted but needs privileges to open files as the user).
+ */
+ chroot = get_bool_ent(&bufp, "chroot", compat_level
+ < compat_level_from_string(COMPAT_LEVEL_1, msg_panic) ?
+ "y" : "n");
+
+ /*
+ * Wakeup timer. XXX should we require that var_proc_limit == 1? Right
+ * now, the only services that have a wakeup timer also happen to be the
+ * services that have at most one running instance: local pickup and
+ * local delivery.
+ */
+ serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0);
+
+ /*
+ * Find out if the wakeup time is conditional, i.e., wakeup triggers
+ * should not be sent until the service has actually been used.
+ */
+ if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?')
+ serv->flags |= MASTER_FLAG_CONDWAKE;
+
+ /*
+ * Concurrency limit. Zero means no limit.
+ */
+ vstring_sprintf(junk, "%d", var_proc_limit);
+ serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0);
+
+ /*
+ * Path to command,
+ */
+ command = get_str_ent(&bufp, "command", (char *) 0);
+ serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0);
+
+ /*
+ * Idle and total process count.
+ */
+ serv->avail_proc = 0;
+ serv->total_proc = 0;
+
+ /*
+ * Backoff time in case a service is broken.
+ */
+ serv->throttle_delay = var_throttle_time;
+
+ /*
+ * Shared channel for child status updates.
+ */
+ serv->status_fd[0] = serv->status_fd[1] = -1;
+
+ /*
+ * Child process structures.
+ */
+ serv->children = 0;
+
+ /*
+ * Command-line vector. Add "-n service_name" when the process name
+ * basename differs from the service name. Always add the transport.
+ */
+ serv->args = argv_alloc(0);
+ argv_add(serv->args, command, (char *) 0);
+ if (serv->max_proc == 1)
+ argv_add(serv->args, "-l", (char *) 0);
+ if (serv->max_proc == 0)
+ argv_add(serv->args, "-z", (char *) 0);
+ if (strcmp(basename(command), name) != 0)
+ argv_add(serv->args, "-n", name, (char *) 0);
+ argv_add(serv->args, "-t", transport, (char *) 0);
+ if (master_detach == 0)
+ argv_add(serv->args, "-d", (char *) 0);
+ if (msg_verbose)
+ argv_add(serv->args, "-v", (char *) 0);
+ if (unprivileged)
+ argv_add(serv->args, "-u", (char *) 0);
+ if (chroot)
+ argv_add(serv->args, "-c", (char *) 0);
+ if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) {
+ argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0);
+ serv->stress_param_val =
+ serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1;
+ serv->stress_param_val[0] = 0;
+ } else
+ serv->stress_param_val = 0;
+ serv->stress_expire_time = 0;
+ if (serv->listen_fd_count > 1)
+ argv_add(serv->args, "-s",
+ vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)),
+ (char *) 0);
+ while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) {
+ if (*cp == CHARS_BRACE[0]
+ && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0)
+ fatal_with_context("%s", err);
+ argv_add(serv->args, cp, (char *) 0);
+ }
+ argv_terminate(serv->args);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ vstring_free(junk);
+ return (serv);
+}
+
+/* print_master_ent - show service entry contents */
+
+void print_master_ent(MASTER_SERV *serv)
+{
+ char **cpp;
+
+ msg_info("====start service entry");
+ msg_info("flags: %d", serv->flags);
+ msg_info("name: %s", serv->name);
+ msg_info("type: %s",
+ serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX :
+ serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO :
+ serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET :
+#ifdef MASTER_SERV_TYPE_PASS
+ serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS :
+#endif
+ serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG :
+ "unknown transport type");
+ msg_info("listen_fd_count: %d", serv->listen_fd_count);
+ msg_info("wakeup: %d", serv->wakeup_time);
+ msg_info("max_proc: %d", serv->max_proc);
+ msg_info("path: %s", serv->path);
+ for (cpp = serv->args->argv; *cpp; cpp++)
+ msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp);
+ msg_info("avail_proc: %d", serv->avail_proc);
+ msg_info("total_proc: %d", serv->total_proc);
+ msg_info("throttle_delay: %d", serv->throttle_delay);
+ msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]);
+ msg_info("children: 0x%lx", (long) serv->children);
+ msg_info("next: 0x%lx", (long) serv->next);
+ msg_info("====end service entry");
+}
+
+/* free_master_ent - destroy process entry */
+
+void free_master_ent(MASTER_SERV *serv)
+{
+
+ /*
+ * Undo what get_master_ent() created.
+ */
+ if (serv->flags & MASTER_FLAG_INETHOST) {
+ inet_addr_list_free(MASTER_INET_ADDRLIST(serv));
+ myfree((void *) MASTER_INET_ADDRLIST(serv));
+ }
+ if (serv->type == MASTER_SERV_TYPE_INET)
+ myfree(MASTER_INET_PORT(serv));
+ myfree(serv->ext_name);
+ myfree(serv->name);
+ myfree(serv->path);
+ argv_free(serv->args);
+ myfree((void *) serv->listen_fd);
+ myfree((void *) serv);
+}
diff --git a/src/master/master_flow.c b/src/master/master_flow.c
new file mode 100644
index 0000000..68ae57d
--- /dev/null
+++ b/src/master/master_flow.c
@@ -0,0 +1,33 @@
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+#include <master.h>
+#include <master_proto.h>
+
+int master_flow_pipe[2];
+
+/* master_flow_init - initialize the flow control channel */
+
+void master_flow_init(void)
+{
+ const char *myname = "master_flow_init";
+
+ if (pipe(master_flow_pipe) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+
+ non_blocking(master_flow_pipe[0], NON_BLOCKING);
+ non_blocking(master_flow_pipe[1], NON_BLOCKING);
+
+ close_on_exec(master_flow_pipe[0], CLOSE_ON_EXEC);
+ close_on_exec(master_flow_pipe[1], CLOSE_ON_EXEC);
+}
diff --git a/src/master/master_listen.c b/src/master/master_listen.c
new file mode 100644
index 0000000..1e7f6fa
--- /dev/null
+++ b/src/master/master_listen.c
@@ -0,0 +1,186 @@
+/*++
+/* NAME
+/* master_listen 3
+/* SUMMARY
+/* Postfix master - start/stop listeners
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_listen_init(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_listen_cleanup(serv)
+/* MASTER_SERV *serv;
+/* DESCRIPTION
+/* master_listen_init() turns on the listener implemented by the
+/* named process. FIFOs and UNIX-domain sockets are created with
+/* mode 0622 and with ownership mail_owner.
+/*
+/* master_listen_cleanup() turns off the listener implemented by the
+/* named process.
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* inet_listen(3), internet-domain listener
+/* unix_listen(3), unix-domain listener
+/* fifo_listen(3), named-pipe listener
+/* upass_listen(3), file descriptor passing listener
+/* set_eugid(3), set effective user/group attributes
+/* 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <listen.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <inet_addr_list.h>
+#include <set_eugid.h>
+#include <set_ugid.h>
+#include <iostuff.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+/* master_listen_init - enable connection requests */
+
+void master_listen_init(MASTER_SERV *serv)
+{
+ const char *myname = "master_listen_init";
+ char *end_point;
+ int n;
+ MAI_HOSTADDR_STR hostaddr;
+ struct sockaddr *sa;
+
+ /*
+ * Find out what transport we should use, then create one or more
+ * listener sockets. Make the listener sockets non-blocking, so that
+ * child processes don't block in accept() when multiple processes are
+ * selecting on the same socket and only one of them gets the connection.
+ */
+ switch (serv->type) {
+
+ /*
+ * UNIX-domain or stream listener endpoints always come as singlets.
+ */
+ case MASTER_SERV_TYPE_UNIX:
+ set_eugid(var_owner_uid, var_owner_gid);
+ serv->listen_fd[0] =
+ LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ?
+ serv->max_proc : var_proc_limit, NON_BLOCKING);
+ close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC);
+ set_ugid(getuid(), getgid());
+ break;
+
+ /*
+ * UNIX-domain datagram listener endpoints always come as singlets.
+ */
+ case MASTER_SERV_TYPE_UXDG:
+ set_eugid(var_owner_uid, var_owner_gid);
+ serv->listen_fd[0] =
+ unix_dgram_listen(serv->name, NON_BLOCKING);
+ close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC);
+ set_ugid(getuid(), getgid());
+ break;
+
+ /*
+ * FIFO listener endpoints always come as singlets.
+ */
+ case MASTER_SERV_TYPE_FIFO:
+ set_eugid(var_owner_uid, var_owner_gid);
+ serv->listen_fd[0] = fifo_listen(serv->name, 0622, NON_BLOCKING);
+ close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC);
+ set_ugid(getuid(), getgid());
+ break;
+
+ /*
+ * INET-domain listener endpoints can be wildcarded (the default) or
+ * bound to specific interface addresses.
+ *
+ * With dual-stack IPv4/6 systems it does not matter, we have to specify
+ * the addresses anyway, either explicit or wild-card.
+ */
+ case MASTER_SERV_TYPE_INET:
+ for (n = 0; n < serv->listen_fd_count; n++) {
+ sa = SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n);
+ SOCKADDR_TO_HOSTADDR(sa, SOCK_ADDR_LEN(sa), &hostaddr,
+ (MAI_SERVPORT_STR *) 0, 0);
+ end_point = concatenate(hostaddr.buf,
+ ":", MASTER_INET_PORT(serv), (char *) 0);
+ serv->listen_fd[n]
+ = inet_listen(end_point, serv->max_proc > var_proc_limit ?
+ serv->max_proc : var_proc_limit, NON_BLOCKING);
+ close_on_exec(serv->listen_fd[n], CLOSE_ON_EXEC);
+ myfree(end_point);
+ }
+ break;
+
+ /*
+ * Descriptor passing endpoints always come as singlets.
+ */
+#ifdef MASTER_SERV_TYPE_PASS
+ case MASTER_SERV_TYPE_PASS:
+ set_eugid(var_owner_uid, var_owner_gid);
+ serv->listen_fd[0] =
+ LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ?
+ serv->max_proc : var_proc_limit, NON_BLOCKING);
+ close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC);
+ set_ugid(getuid(), getgid());
+ break;
+#endif
+ default:
+ msg_panic("%s: unknown service type: %d", myname, serv->type);
+ }
+}
+
+/* master_listen_cleanup - disable connection requests */
+
+void master_listen_cleanup(MASTER_SERV *serv)
+{
+ const char *myname = "master_listen_cleanup";
+ int n;
+
+ /*
+ * XXX The listen socket is shared with child processes. Closing the
+ * socket in the master process does not really disable listeners in
+ * child processes. There seems to be no documented way to turn off a
+ * listener. The 4.4BSD shutdown(2) man page promises an ENOTCONN error
+ * when shutdown(2) is applied to a socket that is not connected.
+ */
+ for (n = 0; n < serv->listen_fd_count; n++) {
+ if (close(serv->listen_fd[n]) < 0)
+ msg_warn("%s: close listener socket %d: %m",
+ myname, serv->listen_fd[n]);
+ serv->listen_fd[n] = -1;
+ }
+}
diff --git a/src/master/master_monitor.c b/src/master/master_monitor.c
new file mode 100644
index 0000000..6b2ca65
--- /dev/null
+++ b/src/master/master_monitor.c
@@ -0,0 +1,106 @@
+/*++
+/* NAME
+/* master_monitor 3
+/* SUMMARY
+/* Postfix master - start-up monitoring
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* int master_monitor(time_limit)
+/* int time_limit;
+/* DESCRIPTION
+/* master_monitor() forks off a background child process, and
+/* returns in the child. The result value is the file descriptor
+/* on which the child process must write one byte after it
+/* completes successful initialization as a daemon process.
+/*
+/* The foreground process waits for the child's completion for
+/* a limited amount of time. It terminates with exit status 0
+/* in case of success, non-zero otherwise.
+/* DIAGNOSTICS
+/* Fatal errors: system call failure.
+/* BUGS
+/* SEE ALSO
+/* 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 <iostuff.h>
+
+/* Application-specific. */
+
+#include <master.h>
+
+/* master_monitor - fork off a foreground monitor process */
+
+int master_monitor(int time_limit)
+{
+ pid_t pid;
+ int pipes[2];
+ char buf[1];
+
+ /*
+ * Sanity check.
+ */
+ if (time_limit <= 0)
+ msg_panic("master_monitor: bad time limit: %d", time_limit);
+
+ /*
+ * Set up the plumbing for child-to-parent communication.
+ */
+ if (pipe(pipes) < 0)
+ msg_fatal("pipe: %m");
+ close_on_exec(pipes[0], CLOSE_ON_EXEC);
+ close_on_exec(pipes[1], CLOSE_ON_EXEC);
+
+ /*
+ * Fork the child, and wait for it to report successful initialization.
+ */
+ switch (pid = fork()) {
+ case -1:
+ /* Error. */
+ msg_fatal("fork: %m");
+ case 0:
+ /* Child. Initialize as daemon in the background. */
+ close(pipes[0]);
+ return (pipes[1]);
+ default:
+ /* Parent. Monitor the child in the foreground. */
+ close(pipes[1]);
+ switch (timed_read(pipes[0], buf, 1, time_limit, (void *) 0)) {
+ default:
+ msg_warn("%m while waiting for daemon initialization");
+ /* The child process still runs, but something is wrong. */
+ (void) kill(pid, SIGKILL);
+ /* FALLTHROUGH */
+ case 0:
+ /* The child process exited prematurely. */
+ msg_fatal("daemon initialization failure -- see logs for details");
+ case 1:
+ /* The child process initialized successfully. */
+ exit(0);
+ }
+ }
+}
diff --git a/src/master/master_proto.c b/src/master/master_proto.c
new file mode 100644
index 0000000..e38d2e1
--- /dev/null
+++ b/src/master/master_proto.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* master_proto 3
+/* SUMMARY
+/* Postfix master - status notification protocol
+/* SYNOPSIS
+/* #include <master_proto.h>
+/*
+/* int master_notify(pid, generation, status)
+/* int pid;
+/* unsigned generation;
+/* int status;
+/* DESCRIPTION
+/* The master process provides a standard environment for its
+/* child processes. Part of this environment is a pair of file
+/* descriptors that the master process shares with all child
+/* processes that provide the same service.
+/* .IP MASTER_LISTEN_FD
+/* The shared file descriptor for accepting client connection
+/* requests. The master process listens on this socket or FIFO
+/* when all child processes are busy.
+/* .IP MASTER_STATUS_FD
+/* The shared file descriptor for sending child status updates to
+/* the master process.
+/* .PP
+/* A child process uses master_notify() to send a status notification
+/* message to the master process.
+/* .IP MASTER_STAT_AVAIL
+/* The child process is ready to accept client connections.
+/* .IP MASTER_STAT_TAKEN
+/* Until further notice, the child process is unavailable for
+/* accepting client connections.
+/* .PP
+/* When a child process terminates without sending a status update,
+/* the master process will figure out that the child is no longer
+/* available.
+/* DIAGNOSTICS
+/* The result is -1 in case of problems. This usually means that
+/* the parent disconnected after a reload request, in order to
+/* force children to commit suicide.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include "master_proto.h"
+
+int master_notify(int pid, unsigned generation, int status)
+{
+ const char *myname = "master_notify";
+ MASTER_STATUS stat;
+
+ /*
+ * We use a simple binary protocol to minimize security risks. Since this
+ * is local IPC, there are no byte order or word length issues. The
+ * server treats this information as gossip, so sending a bad PID or a
+ * bad status code will only have amusement value.
+ */
+ stat.pid = pid;
+ stat.gen = generation;
+ stat.avail = status;
+
+ if (write(MASTER_STATUS_FD, (void *) &stat, sizeof(stat)) != sizeof(stat)) {
+ if (msg_verbose)
+ msg_info("%s: status %d: %m", myname, status);
+ return (-1);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: status %d", myname, status);
+ return (0);
+ }
+}
diff --git a/src/master/master_proto.h b/src/master/master_proto.h
new file mode 100644
index 0000000..6084514
--- /dev/null
+++ b/src/master/master_proto.h
@@ -0,0 +1,75 @@
+/*++
+/* NAME
+/* master_proto 3h
+/* SUMMARY
+/* master process protocol
+/* SYNOPSIS
+/* #include <master_proto.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Transport names. The master passes the transport name on the command
+ * line, and thus the name is part of the master to child protocol.
+ */
+#define MASTER_XPORT_NAME_UNIX "unix" /* local IPC */
+#define MASTER_XPORT_NAME_FIFO "fifo" /* local IPC */
+#define MASTER_XPORT_NAME_INET "inet" /* non-local IPC */
+#define MASTER_XPORT_NAME_PASS "pass" /* local IPC */
+#define MASTER_XPORT_NAME_UXDG "unix-dgram" /* local IPC */
+
+ /*
+ * Format of a status message sent by a child process to the process
+ * manager. Since this is between processes on the same machine we need not
+ * worry about byte order and word length.
+ */
+typedef struct MASTER_STATUS {
+ int pid; /* process ID */
+ unsigned gen; /* child generation number */
+ int avail; /* availability */
+} MASTER_STATUS;
+
+#define MASTER_GEN_NAME "GENERATION" /* passed via environment */
+
+#define MASTER_STAT_TAKEN 0 /* this one is occupied */
+#define MASTER_STAT_AVAIL 1 /* this process is idle */
+
+extern int master_notify(int, unsigned, int); /* encapsulate status msg */
+
+ /*
+ * File descriptors inherited from the master process. The flow control pipe
+ * is read by receive processes and is written to by send processes. If
+ * receive processes get too far ahead they will pause for a brief moment.
+ */
+#define MASTER_FLOW_READ 3
+#define MASTER_FLOW_WRITE 4
+
+ /*
+ * File descriptors inherited from the master process. All processes that
+ * provide a given service share the same status file descriptor, and listen
+ * on the same service socket(s). The kernel decides what process gets the
+ * next connection. Usually the number of listening processes is small, so
+ * one connection will not cause a "thundering herd" effect. When no process
+ * listens on a given socket, the master process will. MASTER_LISTEN_FD is
+ * actually the lowest-numbered descriptor of a sequence of descriptors to
+ * listen on.
+ */
+#define MASTER_STATUS_FD 5 /* shared channel to parent */
+#define MASTER_LISTEN_FD 6 /* accept connections here */
+
+/* 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/master/master_service.c b/src/master/master_service.c
new file mode 100644
index 0000000..d5663b9
--- /dev/null
+++ b/src/master/master_service.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* master_service 3
+/* SUMMARY
+/* Postfix master - start/stop services
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_start_service(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_stop_service(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_restart_service(serv, conf_reload)
+/* MASTER_SERV *serv;
+/* int conf_reload;
+/* DESCRIPTION
+/* master_start_service() enables the named service.
+/*
+/* master_stop_service() disables named service.
+/*
+/* master_restart_service() requests all running child processes to
+/* commit suicide. The conf_reload argument is either DO_CONF_RELOAD
+/* (configuration files were reloaded, re-evaluate the child process
+/* creation policy) or NO_CONF_RELOAD.
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* master_avail(3), process creation policy
+/* master_wakeup(3), service automatic wakeup
+/* master_status(3), child status reports
+/* master_listen(3), unix/inet listeners
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+MASTER_SERV *master_head;
+
+/* master_start_service - activate service */
+
+void master_start_service(MASTER_SERV *serv)
+{
+
+ /*
+ * Enable connection requests, wakeup timers, and status updates from
+ * child processes.
+ */
+ master_listen_init(serv);
+ master_avail_listen(serv);
+ master_status_init(serv);
+ master_wakeup_init(serv);
+}
+
+/* master_stop_service - deactivate service */
+
+void master_stop_service(MASTER_SERV *serv)
+{
+
+ /*
+ * Undo the things that master_start_service() did.
+ */
+ master_wakeup_cleanup(serv);
+ master_status_cleanup(serv);
+ master_avail_cleanup(serv);
+ master_listen_cleanup(serv);
+}
+
+/* master_restart_service - restart service after configuration reload */
+
+void master_restart_service(MASTER_SERV *serv, int conf_reload)
+{
+
+ /*
+ * Undo some of the things that master_start_service() did.
+ */
+ master_wakeup_cleanup(serv);
+ master_status_cleanup(serv);
+
+ /*
+ * Now undo the undone.
+ */
+ master_status_init(serv);
+ master_wakeup_init(serv);
+
+ /*
+ * Respond to configuration change.
+ */
+ if (conf_reload)
+ master_avail_listen(serv);
+}
diff --git a/src/master/master_sig.c b/src/master/master_sig.c
new file mode 100644
index 0000000..db5e39d
--- /dev/null
+++ b/src/master/master_sig.c
@@ -0,0 +1,275 @@
+/*++
+/* NAME
+/* master_sig 3
+/* SUMMARY
+/* Postfix master - signal processing
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* int master_gotsighup;
+/* int master_gotsigchld;
+/*
+/* int master_sigsetup()
+/* DESCRIPTION
+/* This module implements the master process signal handling interface.
+/*
+/* master_gotsighup (master_gotsigchld) is set to SIGHUP (SIGCHLD)
+/* when the process receives a hangup (child death) signal.
+/*
+/* master_sigsetup() enables processing of hangup and child death signals.
+/* Receipt of SIGINT, SIGQUIT, SIGSEGV, SIGILL, or SIGTERM
+/* is interpreted as a request for termination. Child processes are
+/* notified of the master\'s demise by sending them a SIGTERM signal.
+/* DIAGNOSTICS
+/* BUGS
+/* Need a way to register cleanup actions.
+/* SEE ALSO
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <signal.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <posix_signals.h>
+#include <killme_after.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+#ifdef USE_SIG_RETURN
+#include <sys/syscall.h>
+#undef USE_SIG_PIPE
+#else
+#define USE_SIG_PIPE
+#endif
+
+/* Local stuff. */
+
+#ifdef USE_SIG_PIPE
+#include <errno.h>
+#include <fcntl.h>
+#include <iostuff.h>
+#include <events.h>
+
+int master_sig_pipe[2];
+
+#define SIG_PIPE_WRITE_FD master_sig_pipe[1]
+#define SIG_PIPE_READ_FD master_sig_pipe[0]
+#endif
+
+int master_gotsigchld;
+int master_gotsighup;
+
+#ifdef USE_SIG_RETURN
+
+/* master_sighup - register arrival of hangup signal */
+
+static void master_sighup(int sig)
+{
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. Don't put
+ * any code here other than for setting a global flag.
+ */
+ master_gotsighup = sig;
+}
+
+/* master_sigchld - register arrival of child death signal */
+
+static void master_sigchld(int sig, int code, struct sigcontext * scp)
+{
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. Don't put
+ * any code here other than for setting a global flag, or code that is
+ * intended to be run within a signal handler.
+ */
+ master_gotsigchld = sig;
+ if (scp != NULL && scp->sc_syscall == SYS_select) {
+ scp->sc_syscall_action = SIG_RETURN;
+#ifndef SA_RESTART
+ } else if (scp != NULL) {
+ scp->sc_syscall_action = SIG_RESTART;
+#endif
+ }
+}
+
+#else
+
+/* master_sighup - register arrival of hangup signal */
+
+static void master_sighup(int sig)
+{
+ int saved_errno = errno;
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. Don't put
+ * any code here other than for setting a global flag, or code that is
+ * intended to be run within a signal handler. Restore errno in case we
+ * are interrupting the epilog of a failed system call.
+ */
+ master_gotsighup = sig;
+ if (write(SIG_PIPE_WRITE_FD, "", 1) != 1)
+ msg_warn("write to SIG_PIPE_WRITE_FD failed: %m");
+ errno = saved_errno;
+}
+
+/* master_sigchld - force wakeup from select() */
+
+static void master_sigchld(int unused_sig)
+{
+ int saved_errno = errno;
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. Don't put
+ * any code here other than for setting a global flag, or code that is
+ * intended to be run within a signal handler. Restore errno in case we
+ * are interrupting the epilog of a failed system call.
+ */
+ master_gotsigchld = 1;
+ if (write(SIG_PIPE_WRITE_FD, "", 1) != 1)
+ msg_warn("write to SIG_PIPE_WRITE_FD failed: %m");
+ errno = saved_errno;
+}
+
+/* master_sig_event - called upon return from select() */
+
+static void master_sig_event(int unused_event, void *unused_context)
+{
+ char c[1];
+
+ while (read(SIG_PIPE_READ_FD, c, 1) > 0)
+ /* void */ ;
+}
+
+#endif
+
+/* master_sigdeath - die, women and children first */
+
+static void master_sigdeath(int sig)
+{
+ const char *myname = "master_sigdeath";
+ struct sigaction action;
+ pid_t pid = getpid();
+
+ /*
+ * Set alarm clock here for suicide after 5s.
+ */
+ killme_after(5);
+
+ /*
+ * Terminate all processes in our process group, except ourselves.
+ */
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ action.sa_handler = SIG_IGN;
+ if (sigaction(SIGTERM, &action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction: %m", myname);
+ if (kill(-pid, SIGTERM) < 0)
+ msg_fatal("%s: kill process group: %m", myname);
+
+ /*
+ * XXX We're running from a signal handler, and should not call complex
+ * routines at all, but it would be even worse to silently terminate
+ * without informing the sysadmin. For this reason, msg(3) was made safe
+ * for usage by signal handlers that terminate the process.
+ */
+ msg_info("terminating on signal %d", sig);
+
+ /*
+ * Undocumented: when a process runs with PID 1, Linux won't deliver a
+ * signal unless the process specifies a handler (i.e. SIG_DFL is treated
+ * as SIG_IGN).
+ */
+ if (init_mode)
+ /* Don't call exit() from a signal handler. */
+ _exit(0);
+
+ /*
+ * Deliver the signal to ourselves and clean up. XXX We're running as a
+ * signal handler and really should not be doing complicated things...
+ */
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ action.sa_handler = SIG_DFL;
+ if (sigaction(sig, &action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction: %m", myname);
+ if (kill(pid, sig) < 0)
+ msg_fatal("%s: kill myself: %m", myname);
+}
+
+/* master_sigsetup - set up signal handlers */
+
+void master_sigsetup(void)
+{
+ const char *myname = "master_sigsetup";
+ struct sigaction action;
+ static int sigs[] = {
+ SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGSEGV, SIGTERM,
+ };
+ unsigned i;
+
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ /*
+ * Prepare to kill our children when we receive any of the above signals.
+ */
+ action.sa_handler = master_sigdeath;
+ for (i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++)
+ if (sigaction(sigs[i], &action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(%d): %m", myname, sigs[i]);
+
+#ifdef USE_SIG_PIPE
+ if (pipe(master_sig_pipe))
+ msg_fatal("pipe: %m");
+ non_blocking(SIG_PIPE_WRITE_FD, NON_BLOCKING);
+ non_blocking(SIG_PIPE_READ_FD, NON_BLOCKING);
+ close_on_exec(SIG_PIPE_WRITE_FD, CLOSE_ON_EXEC);
+ close_on_exec(SIG_PIPE_READ_FD, CLOSE_ON_EXEC);
+ event_enable_read(SIG_PIPE_READ_FD, master_sig_event, (void *) 0);
+#endif
+
+ /*
+ * Intercept SIGHUP (re-read config file) and SIGCHLD (child exit).
+ */
+#ifdef SA_RESTART
+ action.sa_flags |= SA_RESTART;
+#endif
+ action.sa_handler = master_sighup;
+ if (sigaction(SIGHUP, &action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(%d): %m", myname, SIGHUP);
+
+ action.sa_flags |= SA_NOCLDSTOP;
+ action.sa_handler = master_sigchld;
+ if (sigaction(SIGCHLD, &action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(%d): %m", myname, SIGCHLD);
+}
diff --git a/src/master/master_spawn.c b/src/master/master_spawn.c
new file mode 100644
index 0000000..c3b70f2
--- /dev/null
+++ b/src/master/master_spawn.c
@@ -0,0 +1,371 @@
+/*++
+/* NAME
+/* master_spawn 3
+/* SUMMARY
+/* Postfix master - child process birth and death
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_spawn(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_reap_child()
+/*
+/* void master_delete_children(serv)
+/* MASTER_SERV *serv;
+/* DESCRIPTION
+/* This module creates and cleans up child processes, and applies
+/* a process creation throttle in case of serious trouble.
+/* This module is the working horse for the master_avail(3) process
+/* creation policy module.
+/*
+/* master_spawn() spawns off a child process for the specified service,
+/* making the child process available for servicing connection requests.
+/* It is an error to call this function then the specified service is
+/* throttled.
+/*
+/* master_reap_child() cleans up all dead child processes. One typically
+/* runs this function at a convenient moment after receiving a SIGCHLD
+/* signal. When a child process terminates abnormally after being used
+/* for the first time, process creation for that service is throttled
+/* for a configurable amount of time.
+/*
+/* master_delete_children() deletes all child processes that provide
+/* the named service. Upon exit, the process creation throttle for that
+/* service is released.
+/* DIAGNOSTICS
+/* Panic: interface violations, internal inconsistencies.
+/* Fatal errors: out of memory. Warnings: throttle on/off.
+/* BUGS
+/* SEE ALSO
+/* master_avail(3), process creation policy.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h> /* closelog() */
+#include <signal.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+/* Utility libraries. */
+
+#include <msg.h>
+#include <binhash.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstring.h>
+#include <argv.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include "master_proto.h"
+#include "master.h"
+
+BINHASH *master_child_table;
+static void master_unthrottle(MASTER_SERV *serv);
+
+/* master_unthrottle_wrapper - in case (char *) != (struct *) */
+
+static void master_unthrottle_wrapper(int unused_event, void *ptr)
+{
+ MASTER_SERV *serv = (MASTER_SERV *) ptr;
+
+ /*
+ * This routine runs after expiry of the timer set in master_throttle(),
+ * which gets called when it appears that the world is falling apart.
+ */
+ master_unthrottle(serv);
+}
+
+/* master_unthrottle - enable process creation */
+
+static void master_unthrottle(MASTER_SERV *serv)
+{
+
+ /*
+ * Enable process creation within this class. Disable the "unthrottle"
+ * timer just in case we're being called directly from the cleanup
+ * routine, instead of from the event manager.
+ */
+ if ((serv->flags & MASTER_FLAG_THROTTLE) != 0) {
+ serv->flags &= ~MASTER_FLAG_THROTTLE;
+ event_cancel_timer(master_unthrottle_wrapper, (void *) serv);
+ if (msg_verbose)
+ msg_info("throttle released for command %s", serv->path);
+ master_avail_listen(serv);
+ }
+}
+
+/* master_throttle - suspend process creation */
+
+static void master_throttle(MASTER_SERV *serv)
+{
+
+ /*
+ * Perhaps the command to be run is defective, perhaps some configuration
+ * is wrong, or perhaps the system is out of resources. Disable further
+ * process creation attempts for a while.
+ */
+ if ((serv->flags & MASTER_FLAG_THROTTLE) == 0) {
+ serv->flags |= MASTER_FLAG_THROTTLE;
+ event_request_timer(master_unthrottle_wrapper, (void *) serv,
+ serv->throttle_delay);
+ if (msg_verbose)
+ msg_info("throttling command %s", serv->path);
+ master_avail_listen(serv);
+ }
+}
+
+/* master_spawn - spawn off new child process if we can */
+
+void master_spawn(MASTER_SERV *serv)
+{
+ const char *myname = "master_spawn";
+ MASTER_PROC *proc;
+ MASTER_PID pid;
+ int n;
+ static unsigned master_generation = 0;
+ static VSTRING *env_gen = 0;
+
+ if (master_child_table == 0)
+ master_child_table = binhash_create(0);
+ if (env_gen == 0)
+ env_gen = vstring_alloc(100);
+
+ /*
+ * Sanity checks. The master_avail module is supposed to know what it is
+ * doing.
+ */
+ if (!MASTER_LIMIT_OK(serv->max_proc, serv->total_proc))
+ msg_panic("%s: at process limit %d", myname, serv->total_proc);
+ if (serv->avail_proc > 0)
+ msg_panic("%s: processes available: %d", myname, serv->avail_proc);
+ if (serv->flags & MASTER_FLAG_THROTTLE)
+ msg_panic("%s: throttled service: %s", myname, serv->path);
+
+ /*
+ * Create a child process and connect parent and child via the status
+ * pipe.
+ */
+ master_generation += 1;
+ switch (pid = fork()) {
+
+ /*
+ * Error. We're out of some essential resource. Best recourse is to
+ * try again later.
+ */
+ case -1:
+ msg_warn("%s: fork: %m -- throttling", myname);
+ master_throttle(serv);
+ return;
+
+ /*
+ * Child process. Redirect child stdin/stdout to the parent-child
+ * connection and run the requested command. Leave child stderr
+ * alone. Disable exit handlers: they should be executed by the
+ * parent only.
+ *
+ * When we reach the process limit on a public internet service, we
+ * create stress-mode processes until the process count stays below
+ * the limit for some amount of time. See master_avail_listen().
+ */
+ case 0:
+ msg_cleanup((void (*) (void)) 0); /* disable exit handler */
+ closelog(); /* avoid filedes leak */
+
+ if (master_flow_pipe[0] <= MASTER_FLOW_READ)
+ msg_fatal("%s: flow pipe read descriptor <= %d",
+ myname, MASTER_FLOW_READ);
+ if (DUP2(master_flow_pipe[0], MASTER_FLOW_READ) < 0)
+ msg_fatal("%s: dup2: %m", myname);
+ if (close(master_flow_pipe[0]) < 0)
+ msg_fatal("close %d: %m", master_flow_pipe[0]);
+
+ if (master_flow_pipe[1] <= MASTER_FLOW_WRITE)
+ msg_fatal("%s: flow pipe read descriptor <= %d",
+ myname, MASTER_FLOW_WRITE);
+ if (DUP2(master_flow_pipe[1], MASTER_FLOW_WRITE) < 0)
+ msg_fatal("%s: dup2: %m", myname);
+ if (close(master_flow_pipe[1]) < 0)
+ msg_fatal("close %d: %m", master_flow_pipe[1]);
+
+ close(serv->status_fd[0]); /* status channel */
+ if (serv->status_fd[1] <= MASTER_STATUS_FD)
+ msg_fatal("%s: status file descriptor collision", myname);
+ if (DUP2(serv->status_fd[1], MASTER_STATUS_FD) < 0)
+ msg_fatal("%s: dup2 status_fd: %m", myname);
+ (void) close(serv->status_fd[1]);
+
+ for (n = 0; n < serv->listen_fd_count; n++) {
+ if (serv->listen_fd[n] <= MASTER_LISTEN_FD + n)
+ msg_fatal("%s: listen file descriptor collision", myname);
+ if (DUP2(serv->listen_fd[n], MASTER_LISTEN_FD + n) < 0)
+ msg_fatal("%s: dup2 listen_fd %d: %m",
+ myname, serv->listen_fd[n]);
+ (void) close(serv->listen_fd[n]);
+ }
+ vstring_sprintf(env_gen, "%s=%o", MASTER_GEN_NAME, master_generation);
+ if (putenv(vstring_str(env_gen)) < 0)
+ msg_fatal("%s: putenv: %m", myname);
+ if (serv->stress_param_val && serv->stress_expire_time > event_time())
+ serv->stress_param_val[0] = CONFIG_BOOL_YES[0];
+
+ execvp(serv->path, serv->args->argv);
+ msg_fatal("%s: exec %s: %m", myname, serv->path);
+ /* NOTREACHED */
+
+ /*
+ * Parent. Fill in a process member data structure and set up links
+ * between child and process. Say this process has become available.
+ * If this service has a wakeup timer that is turned on only when the
+ * service is actually used, turn on the wakeup timer.
+ */
+ default:
+ if (msg_verbose)
+ msg_info("spawn command %s; pid %d", serv->path, pid);
+ proc = (MASTER_PROC *) mymalloc(sizeof(MASTER_PROC));
+ proc->serv = serv;
+ proc->pid = pid;
+ proc->gen = master_generation;
+ proc->use_count = 0;
+ proc->avail = 0;
+ binhash_enter(master_child_table, (void *) &pid,
+ sizeof(pid), (void *) proc);
+ serv->total_proc++;
+ master_avail_more(serv, proc);
+ if (serv->flags & MASTER_FLAG_CONDWAKE) {
+ serv->flags &= ~MASTER_FLAG_CONDWAKE;
+ master_wakeup_init(serv);
+ if (msg_verbose)
+ msg_info("start conditional timer for %s", serv->name);
+ }
+ return;
+ }
+}
+
+/* master_delete_child - destroy child process info */
+
+static void master_delete_child(MASTER_PROC *proc)
+{
+ MASTER_SERV *serv;
+
+ /*
+ * Undo the things that master_spawn did. Stop the process if it still
+ * exists, and remove it from the lookup tables. Update the number of
+ * available processes.
+ */
+ serv = proc->serv;
+ serv->total_proc--;
+ if (proc->avail == MASTER_STAT_AVAIL)
+ master_avail_less(serv, proc);
+ else
+ master_avail_listen(serv);
+ binhash_delete(master_child_table, (void *) &proc->pid,
+ sizeof(proc->pid), (void (*) (void *)) 0);
+ myfree((void *) proc);
+}
+
+/* master_reap_child - reap dead children */
+
+void master_reap_child(void)
+{
+ MASTER_SERV *serv;
+ MASTER_PROC *proc;
+ MASTER_PID pid;
+ WAIT_STATUS_T status;
+
+ /*
+ * Pick up termination status of all dead children. When a process failed
+ * on its first job, assume we see the symptom of a structural problem
+ * (configuration problem, system running out of resources) and back off.
+ */
+ while ((pid = waitpid((pid_t) - 1, &status, WNOHANG)) > 0) {
+ if (msg_verbose)
+ msg_info("master_reap_child: pid %d", pid);
+ if ((proc = (MASTER_PROC *) binhash_find(master_child_table,
+ (void *) &pid, sizeof(pid))) == 0) {
+ if (init_mode)
+ continue; /* non-Postfix process */
+ msg_panic("master_reap: unknown pid: %d", pid);
+ }
+ serv = proc->serv;
+
+#define MASTER_KILL_SIGNAL SIGTERM
+#define MASTER_SENT_SIGNAL(serv, status) \
+ (MASTER_MARKED_FOR_DELETION(serv) \
+ && WTERMSIG(status) == MASTER_KILL_SIGNAL)
+
+ /*
+ * XXX The code for WIFSTOPPED() is here in case some buggy kernel
+ * reports WIFSTOPPED() events to a Postfix daemon's parent process
+ * (the master(8) daemon) instead of the tracing process (e.g., gdb).
+ *
+ * The WIFSTOPPED() test prevents master(8) from deleting its record of
+ * a child process that is stopped. That would cause a master(8)
+ * panic (unknown child) when the child terminates.
+ */
+ if (!NORMAL_EXIT_STATUS(status)) {
+ if (WIFSTOPPED(status)) {
+ msg_warn("process %s pid %d stopped by signal %d",
+ serv->path, pid, WSTOPSIG(status));
+ continue;
+ }
+ if (WIFEXITED(status))
+ msg_warn("process %s pid %d exit status %d",
+ serv->path, pid, WEXITSTATUS(status));
+ if (WIFSIGNALED(status) && !MASTER_SENT_SIGNAL(serv, status))
+ msg_warn("process %s pid %d killed by signal %d",
+ serv->path, pid, WTERMSIG(status));
+ /* master_delete_children() throttles first, then kills. */
+ if (proc->use_count == 0
+ && (serv->flags & MASTER_FLAG_THROTTLE) == 0) {
+ msg_warn("%s: bad command startup -- throttling", serv->path);
+ master_throttle(serv);
+ }
+ }
+ master_delete_child(proc);
+ }
+}
+
+/* master_delete_children - delete all child processes of service */
+
+void master_delete_children(MASTER_SERV *serv)
+{
+ BINHASH_INFO **list;
+ BINHASH_INFO **info;
+ MASTER_PROC *proc;
+
+ /*
+ * XXX turn on the throttle so that master_reap_child() doesn't. Someone
+ * has to turn off the throttle in order to stop the associated timer
+ * request, so we might just as well do it at the end.
+ */
+ master_throttle(serv);
+ for (info = list = binhash_list(master_child_table); *info; info++) {
+ proc = (MASTER_PROC *) info[0]->value;
+ if (proc->serv == serv)
+ (void) kill(proc->pid, MASTER_KILL_SIGNAL);
+ }
+ while (serv->total_proc > 0)
+ master_reap_child();
+ myfree((void *) list);
+ master_unthrottle(serv);
+}
diff --git a/src/master/master_status.c b/src/master/master_status.c
new file mode 100644
index 0000000..fb3bb73
--- /dev/null
+++ b/src/master/master_status.c
@@ -0,0 +1,198 @@
+/*++
+/* NAME
+/* master_status 3
+/* SUMMARY
+/* Postfix master - process child status reports
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_status_init(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_status_cleanup(serv)
+/* MASTER_SERV *serv;
+/* DESCRIPTION
+/* This module reads and processes status reports from child processes.
+/*
+/* master_status_init() enables the processing of child status updates
+/* for the specified service. Child process status updates (process
+/* available, process taken) are passed on to the master_avail_XXX()
+/* routines.
+/*
+/* master_status_cleanup() disables child status update processing
+/* for the specified service.
+/* DIAGNOSTICS
+/* Panic: internal inconsistency. Warnings: a child process sends
+/* incomplete or incorrect information.
+/* BUGS
+/* SEE ALSO
+/* master_avail(3)
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <binhash.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+#include "master_proto.h"
+#include "master.h"
+
+/* master_status_event - status read event handler */
+
+static void master_status_event(int event, void *context)
+{
+ const char *myname = "master_status_event";
+ MASTER_SERV *serv = (MASTER_SERV *) context;
+ MASTER_STATUS stat;
+ MASTER_PROC *proc;
+ MASTER_PID pid;
+ int n;
+
+ if (event == 0) /* XXX Can this happen? */
+ return;
+
+ /*
+ * We always keep the child end of the status pipe open, so an EOF read
+ * condition means that we're seriously confused. We use non-blocking
+ * reads so that we don't get stuck when someone sends a partial message.
+ * Messages are short, so a partial read means someone wrote less than a
+ * whole status message. Hopefully the next read will be in sync again...
+ * We use a global child process status table because when a child dies
+ * only its pid is known - we do not know what service it came from.
+ */
+ switch (n = read(serv->status_fd[0], (void *) &stat, sizeof(stat))) {
+
+ case -1:
+ msg_warn("%s: read: %m", myname);
+ return;
+
+ case 0:
+ msg_panic("%s: read EOF status", myname);
+ /* NOTREACHED */
+
+ default:
+ msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)",
+ serv->ext_name, serv->name, stat.pid, n);
+ return;
+
+ case sizeof(stat):
+ pid = stat.pid;
+ if (msg_verbose)
+ msg_info("%s: pid %d gen %u avail %d",
+ myname, stat.pid, stat.gen, stat.avail);
+ }
+
+ /*
+ * Sanity checks. Do not freak out when the child sends garbage because
+ * it is confused or for other reasons. However, be sure to freak out
+ * when our own data structures are inconsistent. A process not found
+ * condition can happen when we reap a process before receiving its
+ * status update, so this is not an error.
+ */
+ if ((proc = (MASTER_PROC *) binhash_find(master_child_table,
+ (void *) &pid, sizeof(pid))) == 0) {
+ if (msg_verbose)
+ msg_info("%s: process id not found: %d", myname, stat.pid);
+ return;
+ }
+ if (proc->gen != stat.gen) {
+ msg_info("ignoring status update from child pid %d generation %u",
+ pid, stat.gen);
+ return;
+ }
+ if (proc->serv != serv)
+ msg_panic("%s: pointer corruption: %p != %p",
+ myname, (void *) proc->serv, (void *) serv);
+
+ /*
+ * Update our idea of the child process status. Allow redundant status
+ * updates, because different types of events may be processed out of
+ * order. Otherwise, warn about weird status updates but do not take
+ * action. It's all gossip after all.
+ */
+ if (proc->avail == stat.avail)
+ return;
+ switch (stat.avail) {
+ case MASTER_STAT_AVAIL:
+ proc->use_count++;
+ master_avail_more(serv, proc);
+ break;
+ case MASTER_STAT_TAKEN:
+ master_avail_less(serv, proc);
+ break;
+ default:
+ msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d",
+ myname, stat.pid, stat.avail);
+ break;
+ }
+}
+
+/* master_status_init - start status event processing for this service */
+
+void master_status_init(MASTER_SERV *serv)
+{
+ const char *myname = "master_status_init";
+
+ /*
+ * Sanity checks.
+ */
+ if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0)
+ msg_panic("%s: status events already enabled", myname);
+ if (msg_verbose)
+ msg_info("%s: %s", myname, serv->name);
+
+ /*
+ * Make the read end of this service's status pipe non-blocking so that
+ * we can detect partial writes on the child side. We use a duplex pipe
+ * so that the child side becomes readable when the master goes away.
+ */
+ if (duplex_pipe(serv->status_fd) < 0)
+ msg_fatal("pipe: %m");
+ non_blocking(serv->status_fd[0], BLOCKING);
+ close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC);
+ close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC);
+ event_enable_read(serv->status_fd[0], master_status_event, (void *) serv);
+}
+
+/* master_status_cleanup - stop status event processing for this service */
+
+void master_status_cleanup(MASTER_SERV *serv)
+{
+ const char *myname = "master_status_cleanup";
+
+ /*
+ * Sanity checks.
+ */
+ if (serv->status_fd[0] < 0 || serv->status_fd[1] < 0)
+ msg_panic("%s: status events not enabled", myname);
+ if (msg_verbose)
+ msg_info("%s: %s", myname, serv->name);
+
+ /*
+ * Dispose of this service's status pipe after disabling read events.
+ */
+ event_disable_readwrite(serv->status_fd[0]);
+ if (close(serv->status_fd[0]) != 0)
+ msg_warn("%s: close status descriptor (read side): %m", myname);
+ if (close(serv->status_fd[1]) != 0)
+ msg_warn("%s: close status descriptor (write side): %m", myname);
+ serv->status_fd[0] = serv->status_fd[1] = -1;
+}
diff --git a/src/master/master_vars.c b/src/master/master_vars.c
new file mode 100644
index 0000000..a2d5441
--- /dev/null
+++ b/src/master/master_vars.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* master_vars 3
+/* SUMMARY
+/* Postfix master - global configuration file access
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_vars_init()
+/* DESCRIPTION
+/* master_vars_init() reads values from the global Postfix configuration
+/* file and assigns them to tunable program parameters. Where no value
+/* is specified, a compiled-in default value is used.
+/* 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 <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+ /*
+ * Tunable parameters.
+ */
+int var_throttle_time;
+char *var_master_disable;
+
+/* master_vars_init - initialize from global Postfix configuration file */
+
+void master_vars_init(void)
+{
+ char *path;
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_MASTER_DISABLE, DEF_MASTER_DISABLE, &var_master_disable, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_THROTTLE_TIME, DEF_THROTTLE_TIME, &var_throttle_time, 1, 0,
+ 0,
+ };
+ static char *saved_inet_protocols;
+ static char *saved_queue_dir;
+ static char *saved_config_dir;
+ static const MASTER_STR_WATCH str_watch_table[] = {
+ VAR_CONFIG_DIR, &var_config_dir, &saved_config_dir, 0, 0,
+ VAR_QUEUE_DIR, &var_queue_dir, &saved_queue_dir, 0, 0,
+ VAR_INET_PROTOCOLS, &var_inet_protocols, &saved_inet_protocols, 0, 0,
+ /* XXX Add inet_interfaces here after this code is burned in. */
+ 0,
+ };
+
+ /*
+ * Flush existing main.cf settings, so that we handle deleted main.cf
+ * settings properly.
+ */
+ mail_conf_flush();
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ mail_conf_read();
+ get_mail_conf_str_table(str_table);
+ get_mail_conf_time_table(time_table);
+ path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (void *) 0);
+ fset_master_ent(path);
+ myfree(path);
+
+ /*
+ * Look for parameter changes that require special attention.
+ */
+ master_str_watch(str_watch_table);
+}
diff --git a/src/master/master_wakeup.c b/src/master/master_wakeup.c
new file mode 100644
index 0000000..cc50924
--- /dev/null
+++ b/src/master/master_wakeup.c
@@ -0,0 +1,192 @@
+/*++
+/* NAME
+/* master_wakeup 3
+/* SUMMARY
+/* Postfix master - start/stop service wakeup timers
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_wakeup_init(serv)
+/* MASTER_SERV *serv;
+/*
+/* void master_wakeup_cleanup(serv)
+/* MASTER_SERV *serv;
+/* DESCRIPTION
+/* This module implements automatic service wakeup. In order to
+/* wakeup a service, a wakeup trigger is sent to the corresponding
+/* service port or FIFO, and a timer is started to repeat this sequence
+/* after a configurable amount of time.
+/*
+/* master_wakeup_init() wakes up the named service. No wakeup
+/* is done or scheduled when a zero wakeup time is given, or when
+/* the service has been throttled in the mean time.
+/* It is OK to call master_wakeup_init() while a timer is already
+/* running for the named service. The effect is to restart the
+/* wakeup timer.
+/*
+/* master_wakeup_cleanup() cancels the wakeup timer for the named
+/* service. It is an error to disable a service while it still has
+/* an active wakeup timer (doing so would cause a dangling reference
+/* to a non-existent service).
+/* It is OK to call master_wakeup_cleanup() even when no timer is
+/* active for the named service.
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* inet_trigger(3), internet-domain client
+/* unix_trigger(3), unix-domain client
+/* fifo_trigger(3), fifo client
+/* upass_trigger(3), file descriptor passing client
+/* 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 <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <trigger.h>
+#include <events.h>
+#include <set_eugid.h>
+#include <set_ugid.h>
+
+/* Global library. */
+
+#include <mail_proto.h> /* triggers */
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "mail_server.h"
+#include "master.h"
+
+/* master_wakeup_timer_event - wakeup event handler */
+
+static void master_wakeup_timer_event(int unused_event, void *context)
+{
+ const char *myname = "master_wakeup_timer_event";
+ MASTER_SERV *serv = (MASTER_SERV *) context;
+ int status;
+ static char wakeup = TRIGGER_REQ_WAKEUP;
+
+ /*
+ * Don't wakeup services whose automatic wakeup feature was turned off in
+ * the mean time.
+ */
+ if (serv->wakeup_time == 0)
+ return;
+
+ /*
+ * Don't wake up services that are throttled. Find out what transport to
+ * use. We can't block here so we choose a short timeout.
+ */
+#define BRIEFLY 1
+
+ if (MASTER_THROTTLED(serv) == 0) {
+ if (msg_verbose)
+ msg_info("%s: service %s", myname, serv->name);
+
+ switch (serv->type) {
+ case MASTER_SERV_TYPE_INET:
+ status = inet_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY);
+ break;
+ case MASTER_SERV_TYPE_UNIX:
+ status = LOCAL_TRIGGER(serv->name, &wakeup, sizeof(wakeup), BRIEFLY);
+ break;
+ case MASTER_SERV_TYPE_UXDG:
+ status = -1;
+ errno = EOPNOTSUPP;
+ break;
+#ifdef MASTER_SERV_TYPE_PASS
+ case MASTER_SERV_TYPE_PASS:
+ status = pass_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY);
+ break;
+#endif
+
+ /*
+ * If someone compromises the postfix account then this must not
+ * overwrite files outside the chroot jail. Countermeasures:
+ *
+ * - Limit the damage by accessing the FIFO as postfix not root.
+ *
+ * - Have fifo_trigger() call safe_open() so we won't follow
+ * arbitrary hard/symlinks to files in/outside the chroot jail.
+ *
+ * - All non-chroot postfix-related files must be root owned (or
+ * postfix check complains).
+ *
+ * - The postfix user and group ID must not be shared with other
+ * applications (says the INSTALL documentation).
+ *
+ * Result of a discussion with Michael Tokarev, who received his
+ * insights from Solar Designer, who tested Postfix with a kernel
+ * module that is paranoid about open() calls.
+ */
+ case MASTER_SERV_TYPE_FIFO:
+ set_eugid(var_owner_uid, var_owner_gid);
+ status = fifo_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY);
+ set_ugid(getuid(), getgid());
+ break;
+ default:
+ msg_panic("%s: unknown service type: %d", myname, serv->type);
+ }
+ if (status < 0)
+ msg_warn("%s: service %s(%s): %m",
+ myname, serv->ext_name, serv->name);
+ }
+
+ /*
+ * Schedule another wakeup event.
+ */
+ event_request_timer(master_wakeup_timer_event, (void *) serv,
+ serv->wakeup_time);
+}
+
+/* master_wakeup_init - start automatic service wakeup */
+
+void master_wakeup_init(MASTER_SERV *serv)
+{
+ const char *myname = "master_wakeup_init";
+
+ if (serv->wakeup_time == 0 || (serv->flags & MASTER_FLAG_CONDWAKE))
+ return;
+ if (msg_verbose)
+ msg_info("%s: service %s time %d",
+ myname, serv->name, serv->wakeup_time);
+ master_wakeup_timer_event(0, (void *) serv);
+}
+
+/* master_wakeup_cleanup - cancel wakeup timer */
+
+void master_wakeup_cleanup(MASTER_SERV *serv)
+{
+ const char *myname = "master_wakeup_cleanup";
+
+ /*
+ * Cleanup, even when the wakeup feature has been turned off. There might
+ * still be a pending timer. Don't depend on the code that reloads the
+ * config file to reset the wakeup timer when things change.
+ */
+ if (msg_verbose)
+ msg_info("%s: service %s", myname, serv->name);
+
+ event_cancel_timer(master_wakeup_timer_event, (void *) serv);
+}
diff --git a/src/master/master_watch.c b/src/master/master_watch.c
new file mode 100644
index 0000000..1af26fe
--- /dev/null
+++ b/src/master/master_watch.c
@@ -0,0 +1,151 @@
+/*++
+/* NAME
+/* master_watch 3
+/* SUMMARY
+/* Postfix master - monitor main.cf changes
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_str_watch(str_watch_table)
+/* const MASTER_STR_WATCH *str_watch_table;
+/*
+/* void master_int_watch(int_watch_table)
+/* MASTER_INT_WATCH *int_watch_table;
+/* DESCRIPTION
+/* The Postfix master daemon is a long-running process. After
+/* main.cf is changed, some parameter changes may require that
+/* master data structures be recomputed.
+/*
+/* Unfortunately, some main.cf changes cannot be applied
+/* on-the-fly, either because they require killing off existing
+/* child processes and thus disrupt service, or because the
+/* necessary support for on-the-fly data structure update has
+/* not yet been implemented. Such main.cf changes trigger a
+/* warning that they require that Postfix be stopped and
+/* restarted.
+/*
+/* This module provides functions that monitor selected main.cf
+/* parameters for change. The operation of these functions is
+/* controlled by tables that specify the parameter name, the
+/* current parameter value, a historical parameter value,
+/* optional flags, and an optional notify call-back function.
+/*
+/* master_str_watch() monitors string-valued parameters for
+/* change, and master_int_watch() does the same for integer-valued
+/* parameters. Note that master_int_watch() needs read-write
+/* access to its argument table, while master_str_watch() needs
+/* read-only access only.
+/*
+/* The functions log a warning when a parameter value has
+/* changed after re-reading main.cf, but the parameter is not
+/* flagged in the MASTER_*_WATCH table as "updatable" with
+/* MASTER_WATCH_FLAG_UPDATABLE.
+/*
+/* If the parameter has a notify call-back function, then the
+/* function is called after main.cf is read for the first time.
+/* If the parameter is flagged as "updatable", then the function
+/* is also called when the parameter value changes after
+/* re-reading main.cf.
+/* 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>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Application-specific. */
+
+#include "master.h"
+
+/* master_str_watch - watch string-valued parameters for change */
+
+void master_str_watch(const MASTER_STR_WATCH *str_watch_table)
+{
+ const MASTER_STR_WATCH *wp;
+
+ for (wp = str_watch_table; wp->name != 0; wp++) {
+
+ /*
+ * Detect changes to monitored parameter values. If a change is
+ * supported, we discard the backed up value and update it to the
+ * current value later. Otherwise we complain.
+ */
+ if (wp->backup[0] != 0
+ && strcmp(wp->backup[0], wp->value[0]) != 0) {
+ if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) {
+ msg_warn("ignoring %s parameter value change", wp->name);
+ msg_warn("old value: \"%s\", new value: \"%s\"",
+ wp->backup[0], wp->value[0]);
+ msg_warn("to change %s, stop and start Postfix", wp->name);
+ } else {
+ myfree(wp->backup[0]);
+ wp->backup[0] = 0;
+ }
+ }
+
+ /*
+ * Initialize the backed up parameter value, or update it if this
+ * parameter supports updates after initialization. Optionally
+ * notify the application that this parameter has changed.
+ */
+ if (wp->backup[0] == 0) {
+ if (wp->notify != 0)
+ wp->notify();
+ wp->backup[0] = mystrdup(wp->value[0]);
+ }
+ }
+}
+
+/* master_int_watch - watch integer-valued parameters for change */
+
+void master_int_watch(MASTER_INT_WATCH *int_watch_table)
+{
+ MASTER_INT_WATCH *wp;
+
+ for (wp = int_watch_table; wp->name != 0; wp++) {
+
+ /*
+ * Detect changes to monitored parameter values. If a change is
+ * supported, we discard the backed up value and update it to the
+ * current value later. Otherwise we complain.
+ */
+ if ((wp->flags & MASTER_WATCH_FLAG_ISSET) != 0
+ && wp->backup != wp->value[0]) {
+ if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) {
+ msg_warn("ignoring %s parameter value change", wp->name);
+ msg_warn("old value: \"%d\", new value: \"%d\"",
+ wp->backup, wp->value[0]);
+ msg_warn("to change %s, stop and start Postfix", wp->name);
+ } else {
+ wp->flags &= ~MASTER_WATCH_FLAG_ISSET;
+ }
+ }
+
+ /*
+ * Initialize the backed up parameter value, or update if it this
+ * parameter supports updates after initialization. Optionally
+ * notify the application that this parameter has changed.
+ */
+ if ((wp->flags & MASTER_WATCH_FLAG_ISSET) == 0) {
+ if (wp->notify != 0)
+ wp->notify();
+ wp->flags |= MASTER_WATCH_FLAG_ISSET;
+ wp->backup = wp->value[0];
+ }
+ }
+}
diff --git a/src/master/multi_server.c b/src/master/multi_server.c
new file mode 100644
index 0000000..6150f22
--- /dev/null
+++ b/src/master/multi_server.c
@@ -0,0 +1,931 @@
+/*++
+/* NAME
+/* multi_server 3
+/* SUMMARY
+/* skeleton multi-threaded mail subsystem
+/* SYNOPSIS
+/* #include <mail_server.h>
+/*
+/* NORETURN multi_server_main(argc, argv, service, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*service)(VSTREAM *stream, char *service_name, char **argv);
+/* int key;
+/*
+/* void multi_server_disconnect(stream)
+/* VSTREAM *stream;
+/*
+/* void multi_server_drain()
+/* DESCRIPTION
+/* This module implements a skeleton for multi-threaded
+/* mail subsystems: mail subsystem programs that service multiple
+/* clients at the same time. The resulting program expects to be run
+/* from the \fBmaster\fR process.
+/*
+/* multi_server_main() is the skeleton entry point. It should be
+/* called from the application main program. The skeleton does all
+/* the generic command-line processing, initialization of
+/* configurable parameters, and connection management.
+/* The skeleton never returns.
+/*
+/* Arguments:
+/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)"
+/* A pointer to a function that is called by the skeleton each
+/* time a client sends data to the program's service port. The
+/* function is run after the program has optionally dropped its
+/* privileges. This function should not attempt to preserve state
+/* across calls. The stream initial state is non-blocking mode.
+/* The service name argument corresponds to the service name in the
+/* master.cf file.
+/* The argv argument specifies command-line arguments left over
+/* after options processing.
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new connection.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_ACCEPT(void *(VSTREAM *stream, char *service_name, char **argv, HTABLE *attr))"
+/* Function to be executed after accepting a new connection.
+/* The stream, service_name and argv arguments are the same
+/* as with the "service" argument. The attr argument is null
+/* or a pointer to a table with 'pass' connection attributes.
+/* The table is destroyed after the function returns.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)"
+/* A pointer to a function that is called
+/* by the multi_server_disconnect() function (see below).
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .PP
+/* multi_server_disconnect() should be called by the application
+/* to close a client connection.
+/*
+/* multi_server_drain() should be called when the application
+/* no longer wishes to accept new client connections. Existing
+/* clients are handled in a background process, and the process
+/* terminates when the last client is disconnected. A non-zero
+/* result means this call should be tried again later.
+/*
+/* The var_use_limit variable limits the number of clients that
+/* a server can service before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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>
+#include <sys/time.h> /* select() */
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h> /* select() */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <chroot_uid.h>
+#include <listen.h>
+#include <events.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <myflock.h>
+#include <safe_open.h>
+#include <listen.h>
+#include <watchdog.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_task.h>
+#include <debug_process.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <timed_ipc.h>
+#include <resolve_local.h>
+#include <mail_flow.h>
+#include <mail_version.h>
+#include <bounce.h>
+#include <maillog_client.h>
+
+/* Process manager. */
+
+#include "master_proto.h"
+
+/* Application-specific */
+
+#include "mail_server.h"
+
+ /*
+ * Global state.
+ */
+static int client_count;
+static int use_count;
+static int socket_count = 1;
+
+static void (*multi_server_service) (VSTREAM *, char *, char **);
+static char *multi_server_name;
+static char **multi_server_argv;
+static void (*multi_server_accept) (int, void *);
+static void (*multi_server_onexit) (char *, char **);
+static void (*multi_server_pre_accept) (char *, char **);
+static void (*multi_server_post_accept) (VSTREAM *, char *, char **, HTABLE *);
+static VSTREAM *multi_server_lock;
+static int multi_server_in_flow_delay;
+static unsigned multi_server_generation;
+static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **);
+
+/* multi_server_exit - normal termination */
+
+static NORETURN multi_server_exit(void)
+{
+ if (multi_server_onexit)
+ multi_server_onexit(multi_server_name, multi_server_argv);
+ exit(0);
+}
+
+/* multi_server_abort - terminate after abnormal master exit */
+
+static void multi_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- exiting");
+ multi_server_exit();
+}
+
+/* multi_server_timeout - idle time exceeded */
+
+static void multi_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ multi_server_exit();
+}
+
+/* multi_server_drain - stop accepting new clients */
+
+int multi_server_drain(void)
+{
+ const char *myname = "multi_server_drain";
+ int fd;
+
+ switch (fork()) {
+ /* Try again later. */
+ case -1:
+ return (-1);
+ /* Finish existing clients in the background, then terminate. */
+ case 0:
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ event_fork();
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_disable_readwrite(fd);
+ (void) close(fd);
+ /* Play safe - don't reuse this file number. */
+ if (DUP2(STDIN_FILENO, fd) < 0)
+ msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
+ }
+ var_use_limit = 1;
+ return (0);
+ /* Let the master start a new process. */
+ default:
+ exit(0);
+ }
+}
+
+/* multi_server_disconnect - terminate client session */
+
+void multi_server_disconnect(VSTREAM *stream)
+{
+ if (msg_verbose)
+ msg_info("connection closed fd %d", vstream_fileno(stream));
+ if (multi_server_pre_disconn)
+ multi_server_pre_disconn(stream, multi_server_name, multi_server_argv);
+ event_disable_readwrite(vstream_fileno(stream));
+ (void) vstream_fclose(stream);
+ client_count--;
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+ if (client_count == 0 && var_idle_limit > 0)
+ event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
+}
+
+/* multi_server_execute - in case (char *) != (struct *) */
+
+static void multi_server_execute(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ /*
+ * Do not bother the application when the client disconnected. Don't drop
+ * the already accepted client request after "postfix reload"; that would
+ * be rude.
+ */
+ if (peekfd(vstream_fileno(stream)) > 0) {
+ if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ multi_server_service(stream, multi_server_name, multi_server_argv);
+ if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0)
+ multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ } else {
+ multi_server_disconnect(stream);
+ }
+}
+
+/* multi_server_enable_read - enable read events */
+
+static void multi_server_enable_read(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ event_enable_read(vstream_fileno(stream), multi_server_execute, (void *) stream);
+}
+
+/* multi_server_wakeup - wake up application */
+
+static void multi_server_wakeup(int fd, HTABLE *attr)
+{
+ VSTREAM *stream;
+ char *tmp;
+
+#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ int new_fd;
+
+ /*
+ * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
+ * case of a multi-server with a thousand clients.
+ */
+ if (fd < THRESHOLD_FD_WORKAROUND) {
+ if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
+ msg_fatal("fcntl F_DUPFD: %m");
+ (void) close(fd);
+ fd = new_fd;
+ }
+#endif
+ if (msg_verbose)
+ msg_info("connection established fd %d", fd);
+ non_blocking(fd, BLOCKING);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ client_count++;
+ stream = vstream_fdopen(fd, O_RDWR);
+ tmp = concatenate(multi_server_name, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(tmp),
+ CA_VSTREAM_CTL_END);
+ myfree(tmp);
+ timed_ipc_setup(stream);
+ if (multi_server_in_flow_delay && mail_flow_get(1) < 0)
+ event_request_timer(multi_server_enable_read, (void *) stream,
+ var_in_flow_delay);
+ else
+ multi_server_enable_read(0, (void *) stream);
+ if (multi_server_post_accept)
+ multi_server_post_accept(stream, multi_server_name, multi_server_argv, attr);
+ else if (attr)
+ msg_warn("service ignores 'pass' connection attributes");
+ if (attr)
+ htable_free(attr, myfree);
+}
+
+/* multi_server_accept_local - accept client connection request */
+
+static void multi_server_accept_local(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, (HTABLE *) 0);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* multi_server_accept_pass - accept descriptor */
+
+static void multi_server_accept_pass(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+ HTABLE *attr = 0;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = pass_accept_attr(listen_fd, &attr);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, attr);
+}
+
+#endif
+
+/* multi_server_accept_inet - accept client connection request */
+
+static void multi_server_accept_inet(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = inet_accept(listen_fd);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, (HTABLE *) 0);
+}
+
+/* multi_server_main - the real main program */
+
+NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...)
+{
+ const char *myname = "multi_server_main";
+ VSTREAM *stream = 0;
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+
+#if 0
+ char *lock_path;
+ VSTRING *why;
+
+#endif
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_POST_ACCEPT:
+ multi_server_post_accept = va_arg(ap, MAIL_SERVER_POST_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_PRE_DISCONN:
+ multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ multi_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ multi_server_accept = multi_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ multi_server_accept = multi_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ multi_server_accept = multi_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(multi_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, multi_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+
+ /*
+ * XXX Can't compete for exclusive access to the listen socket because we
+ * also have to monitor existing client connections for service requests.
+ */
+#if 0
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+#endif
+
+ /*
+ * Set up call-back info.
+ */
+ multi_server_service = service;
+ multi_server_name = service_name;
+ multi_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(multi_server_name, multi_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(multi_server_name, multi_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, multi_server_name, multi_server_argv);
+ vstream_fflush(stream);
+ multi_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, multi_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, multi_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) {
+ if (multi_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(multi_server_name, multi_server_argv) : -1;
+ event_loop(delay);
+ }
+ multi_server_exit();
+}
diff --git a/src/master/single_server.c b/src/master/single_server.c
new file mode 100644
index 0000000..38f22b7
--- /dev/null
+++ b/src/master/single_server.c
@@ -0,0 +1,822 @@
+/*++
+/* NAME
+/* single_server 3
+/* SUMMARY
+/* skeleton single-threaded mail subsystem
+/* SYNOPSIS
+/* #include <mail_server.h>
+/*
+/* NORETURN single_server_main(argc, argv, service, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*service)(VSTREAM *stream, char *service_name, char **argv);
+/* int key;
+/* DESCRIPTION
+/* This module implements a skeleton for single-threaded
+/* mail subsystems: mail subsystem programs that service one
+/* client at a time. The resulting program expects to be run
+/* from the \fBmaster\fR process.
+/*
+/* single_server_main() is the skeleton entry point. It should be
+/* called from the application main program. The skeleton does the
+/* generic command-line options processing, initialization of
+/* configurable parameters, and connection management.
+/* The skeleton never returns.
+/*
+/* Arguments:
+/* .IP "void (*service)(VSTREAM *fp, char *service_name, char **argv)"
+/* A pointer to a function that is called by the skeleton each time
+/* a client connects to the program's service port. The function is
+/* run after the program has irrevocably dropped its privileges.
+/* The stream initial state is non-blocking mode.
+/* Optional connection attributes are provided as a hash that
+/* is attached as stream context.
+/* The service name argument corresponds to the service name in the
+/* master.cf file.
+/* The argv argument specifies command-line arguments left over
+/* after options processing.
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(void))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new connection.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)"
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .IP "CA_MAIL_SERVER_RETIRE_ME"
+/* Prevent a process from being reused indefinitely. After
+/* (var_max_use * var_max_idle) seconds or some sane constant,
+/* terminate voluntarily when the process becomes idle.
+/* .PP
+/* The var_use_limit variable limits the number of clients
+/* that a server can service before it commits suicide. This
+/* value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the
+/* client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits suicide.
+/* Do not change this setting before calling single_server_main().
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <chroot_uid.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <myflock.h>
+#include <safe_open.h>
+#include <listen.h>
+#include <watchdog.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_task.h>
+#include <debug_process.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <timed_ipc.h>
+#include <resolve_local.h>
+#include <mail_flow.h>
+#include <mail_version.h>
+#include <bounce.h>
+#include <maillog_client.h>
+
+/* Process manager. */
+
+#include "master_proto.h"
+
+/* Application-specific */
+
+#include "mail_server.h"
+
+ /*
+ * Global state.
+ */
+static int use_count;
+
+static void (*single_server_service) (VSTREAM *, char *, char **);
+static char *single_server_name;
+static char **single_server_argv;
+static void (*single_server_accept) (int, void *);
+static void (*single_server_onexit) (char *, char **);
+static void (*single_server_pre_accept) (char *, char **);
+static VSTREAM *single_server_lock;
+static int single_server_in_flow_delay;
+static unsigned single_server_generation;
+
+/* single_server_exit - normal termination */
+
+static NORETURN single_server_exit(void)
+{
+ if (single_server_onexit)
+ single_server_onexit(single_server_name, single_server_argv);
+ exit(0);
+}
+
+/* single_server_retire - retire when idle */
+
+static NORETURN single_server_retire(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("time to retire -- exiting");
+ single_server_exit();
+}
+
+/* single_server_abort - terminate after abnormal master exit */
+
+static void single_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- exiting");
+ single_server_exit();
+}
+
+/* single_server_timeout - idle time exceeded */
+
+static void single_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ single_server_exit();
+}
+
+/* single_server_wakeup - wake up application */
+
+static void single_server_wakeup(int fd, HTABLE *attr)
+{
+ VSTREAM *stream;
+ char *tmp;
+
+ /*
+ * If the accept() succeeds, be sure to disable non-blocking I/O, because
+ * the application is supposed to be single-threaded. Notice the master
+ * of our (un)availability to service connection requests. Commit suicide
+ * when the master process disconnected from us. Don't drop the already
+ * accepted client request after "postfix reload"; that would be rude.
+ */
+ if (msg_verbose)
+ msg_info("connection established");
+ non_blocking(fd, BLOCKING);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ stream = vstream_fdopen(fd, O_RDWR);
+ tmp = concatenate(single_server_name, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(tmp),
+ CA_VSTREAM_CTL_CONTEXT((void *) attr),
+ CA_VSTREAM_CTL_END);
+ myfree(tmp);
+ timed_ipc_setup(stream);
+ if (master_notify(var_pid, single_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ if (single_server_in_flow_delay && mail_flow_get(1) < 0)
+ doze(var_in_flow_delay * 1000000);
+ single_server_service(stream, single_server_name, single_server_argv);
+ (void) vstream_fclose(stream);
+ if (master_notify(var_pid, single_server_generation, MASTER_STAT_AVAIL) < 0)
+ single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ if (msg_verbose)
+ msg_info("connection closed");
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+ if (var_idle_limit > 0)
+ event_request_timer(single_server_timeout, (void *) 0, var_idle_limit);
+ if (attr)
+ htable_free(attr, myfree);
+}
+
+/* single_server_accept_local - accept client connection request */
+
+static void single_server_accept_local(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection. We use select() + accept(), instead of simply
+ * blocking in accept(), because we must be able to detect that the
+ * master process has gone away unexpectedly.
+ */
+ if (var_idle_limit > 0)
+ time_left = event_cancel_timer(single_server_timeout, (void *) 0);
+
+ if (single_server_pre_accept)
+ single_server_pre_accept(single_server_name, single_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (single_server_lock != 0
+ && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(single_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ single_server_wakeup(fd, (HTABLE *) 0);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* single_server_accept_pass - accept descriptor */
+
+static void single_server_accept_pass(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+ HTABLE *attr = 0;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection. We use select() + accept(), instead of simply
+ * blocking in accept(), because we must be able to detect that the
+ * master process has gone away unexpectedly.
+ */
+ if (var_idle_limit > 0)
+ time_left = event_cancel_timer(single_server_timeout, (void *) 0);
+
+ if (single_server_pre_accept)
+ single_server_pre_accept(single_server_name, single_server_argv);
+ fd = pass_accept_attr(listen_fd, &attr);
+ if (single_server_lock != 0
+ && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(single_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ single_server_wakeup(fd, attr);
+}
+
+#endif
+
+/* single_server_accept_inet - accept client connection request */
+
+static void single_server_accept_inet(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection. We use select() + accept(), instead of simply
+ * blocking in accept(), because we must be able to detect that the
+ * master process has gone away unexpectedly.
+ */
+ if (var_idle_limit > 0)
+ time_left = event_cancel_timer(single_server_timeout, (void *) 0);
+
+ if (single_server_pre_accept)
+ single_server_pre_accept(single_server_name, single_server_argv);
+ fd = inet_accept(listen_fd);
+ if (single_server_lock != 0
+ && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(single_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ single_server_wakeup(fd, (HTABLE *) 0);
+}
+
+/* single_server_main - the real main program */
+
+NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...)
+{
+ const char *myname = "single_server_main";
+ VSTREAM *stream = 0;
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int socket_count = 1;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+ char *lock_path;
+ VSTRING *why;
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+ int retire_me_from_flags = 0;
+ int retire_me = 0;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 'r':
+ if ((retire_me_from_flags = atoi(optarg)) <= 0)
+ msg_fatal("invalid retirement time: %s", optarg);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ single_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ case MAIL_SERVER_RETIRE_ME:
+ if (retire_me_from_flags > 0)
+ retire_me = retire_me_from_flags;
+ else if (var_idle_limit == 0 || var_use_limit == 0
+ || var_idle_limit > 18000 / var_use_limit)
+ retire_me = 18000;
+ else
+ retire_me = var_idle_limit * var_use_limit;
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ single_server_accept = single_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ single_server_accept = single_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ single_server_accept = single_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(single_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, single_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (void *) 0);
+ why = vstring_alloc(1);
+ if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ single_server_service = service;
+ single_server_name = service_name;
+ single_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(single_server_name, single_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(single_server_name, single_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, single_server_name, single_server_argv);
+ vstream_fflush(stream);
+ single_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(single_server_timeout, (void *) 0, var_idle_limit);
+ if (retire_me)
+ event_request_timer(single_server_retire, (void *) 0, retire_me);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (single_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(single_server_name, single_server_argv) : -1;
+ event_loop(delay);
+ }
+ single_server_exit();
+}
diff --git a/src/master/trigger_server.c b/src/master/trigger_server.c
new file mode 100644
index 0000000..c483a9e
--- /dev/null
+++ b/src/master/trigger_server.c
@@ -0,0 +1,809 @@
+/*++
+/* NAME
+/* trigger_server 3
+/* SUMMARY
+/* skeleton triggered mail subsystem
+/* SYNOPSIS
+/* #include <mail_server.h>
+/*
+/* NORETURN trigger_server_main(argc, argv, service, key, value, ...)
+/* int argc;
+/* char **argv;
+/* void (*service)(char *buf, int len, char *service_name, char **argv);
+/* int key;
+/* DESCRIPTION
+/* This module implements a skeleton for triggered
+/* mail subsystems: mail subsystem programs that wake up on
+/* client request and perform some activity without further
+/* client interaction. This module supports local IPC via FIFOs
+/* and via UNIX-domain sockets. The resulting program expects to be
+/* run from the \fBmaster\fR process.
+/*
+/* trigger_server_main() is the skeleton entry point. It should be
+/* called from the application main program. The skeleton does the
+/* generic command-line options processing, initialization of
+/* configurable parameters, and connection management.
+/* The skeleton never returns.
+/*
+/* Arguments:
+/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)"
+/* A pointer to a function that is called by the skeleton each time
+/* a client connects to the program's service port. The function is
+/* run after the program has irrevocably dropped its privileges.
+/* The buffer argument specifies the data read from the trigger port;
+/* this data corresponds to one or more trigger requests.
+/* The len argument specifies how much client data is available.
+/* The maximal size of the buffer is specified via the
+/* TRIGGER_BUF_SIZE manifest constant.
+/* The service name argument corresponds to the service name in the
+/* master.cf file.
+/* The argv argument specifies command-line arguments left over
+/* after options processing.
+/* The \fBserver\fR argument provides the following information:
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new request.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)"
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)"
+/* Override the default 1000s watchdog timeout. The value is
+/* used after command-line and main.cf file processing.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .PP
+/* The var_use_limit variable limits the number of clients that
+/* a server can service before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* Works with FIFO-based services only.
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <chroot_uid.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <myflock.h>
+#include <safe_open.h>
+#include <listen.h>
+#include <watchdog.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_task.h>
+#include <debug_process.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <resolve_local.h>
+#include <mail_flow.h>
+#include <mail_version.h>
+#include <bounce.h>
+#include <maillog_client.h>
+
+/* Process manager. */
+
+#include "master_proto.h"
+
+/* Application-specific */
+
+#include "mail_server.h"
+
+ /*
+ * Global state.
+ */
+static int use_count;
+
+static TRIGGER_SERVER_FN trigger_server_service;
+static char *trigger_server_name;
+static char **trigger_server_argv;
+static void (*trigger_server_accept) (int, void *);
+static void (*trigger_server_onexit) (char *, char **);
+static void (*trigger_server_pre_accept) (char *, char **);
+static VSTREAM *trigger_server_lock;
+static int trigger_server_in_flow_delay;
+static unsigned trigger_server_generation;
+static int trigger_server_watchdog = 1000;
+
+/* trigger_server_exit - normal termination */
+
+static NORETURN trigger_server_exit(void)
+{
+ if (trigger_server_onexit)
+ trigger_server_onexit(trigger_server_name, trigger_server_argv);
+ exit(0);
+}
+
+/* trigger_server_abort - terminate after abnormal master exit */
+
+static void trigger_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- exiting");
+ trigger_server_exit();
+}
+
+/* trigger_server_timeout - idle time exceeded */
+
+static void trigger_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ trigger_server_exit();
+}
+
+/* trigger_server_wakeup - wake up application */
+
+static void trigger_server_wakeup(int fd)
+{
+ char buf[TRIGGER_BUF_SIZE];
+ ssize_t len;
+
+ /*
+ * Commit suicide when the master process disconnected from us. Don't
+ * drop the already accepted client request after "postfix reload"; that
+ * would be rude.
+ */
+ if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ if (trigger_server_in_flow_delay && mail_flow_get(1) < 0)
+ doze(var_in_flow_delay * 1000000);
+ if ((len = read(fd, buf, sizeof(buf))) >= 0)
+ trigger_server_service(buf, len, trigger_server_name,
+ trigger_server_argv);
+ if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_AVAIL) < 0)
+ trigger_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ if (var_idle_limit > 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit);
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+}
+
+/* trigger_server_accept_fifo - accept fifo client request */
+
+static void trigger_server_accept_fifo(int unused_event, void *context)
+{
+ const char *myname = "trigger_server_accept_fifo";
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+
+ if (trigger_server_lock != 0
+ && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ if (msg_verbose)
+ msg_info("%s: trigger arrived", myname);
+
+ /*
+ * Read whatever the other side wrote into the FIFO. The FIFO read end is
+ * non-blocking so we won't get stuck when multiple processes wake up.
+ */
+ if (trigger_server_pre_accept)
+ trigger_server_pre_accept(trigger_server_name, trigger_server_argv);
+ trigger_server_wakeup(listen_fd);
+}
+
+/* trigger_server_accept_local - accept socket client request */
+
+static void trigger_server_accept_local(int unused_event, void *context)
+{
+ const char *myname = "trigger_server_accept_local";
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = 0;
+ int fd;
+
+ if (msg_verbose)
+ msg_info("%s: trigger arrived", myname);
+
+ /*
+ * Read a message from a socket. Be prepared for accept() to fail because
+ * some other process already got the connection. The socket is
+ * non-blocking so we won't get stuck when multiple processes wake up.
+ * Don't get stuck when the client connects but sends no data. Restart
+ * the idle timer if this was a false alarm.
+ */
+ if (var_idle_limit > 0)
+ time_left = event_cancel_timer(trigger_server_timeout, (void *) 0);
+
+ if (trigger_server_pre_accept)
+ trigger_server_pre_accept(trigger_server_name, trigger_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (trigger_server_lock != 0
+ && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ if (read_wait(fd, 10) == 0)
+ trigger_server_wakeup(fd);
+ else if (time_left >= 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, time_left);
+ close(fd);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* trigger_server_accept_pass - accept descriptor */
+
+static void trigger_server_accept_pass(int unused_event, void *context)
+{
+ const char *myname = "trigger_server_accept_pass";
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = 0;
+ int fd;
+
+ if (msg_verbose)
+ msg_info("%s: trigger arrived", myname);
+
+ /*
+ * Read a message from a socket. Be prepared for accept() to fail because
+ * some other process already got the connection. The socket is
+ * non-blocking so we won't get stuck when multiple processes wake up.
+ * Don't get stuck when the client connects but sends no data. Restart
+ * the idle timer if this was a false alarm.
+ */
+ if (var_idle_limit > 0)
+ time_left = event_cancel_timer(trigger_server_timeout, (void *) 0);
+
+ if (trigger_server_pre_accept)
+ trigger_server_pre_accept(trigger_server_name, trigger_server_argv);
+ fd = pass_accept(listen_fd);
+ if (trigger_server_lock != 0
+ && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ if (read_wait(fd, 10) == 0)
+ trigger_server_wakeup(fd);
+ else if (time_left >= 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, time_left);
+ close(fd);
+}
+
+#endif
+
+/* trigger_server_main - the real main program */
+
+NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...)
+{
+ const char *myname = "trigger_server_main";
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ VSTREAM *stream = 0;
+ int delay;
+ int c;
+ int socket_count = 1;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char buf[TRIGGER_BUF_SIZE];
+ ssize_t len;
+ char *transport = 0;
+ char *lock_path;
+ VSTRING *why;
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ trigger_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ trigger_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ *
+ * XXX Initially this code was implemented with UNIX-domain sockets, but
+ * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the
+ * client disconnects before the server has accepted the connection.
+ * Symptom: the server accept() fails with EPIPE or EPROTO, but the
+ * socket stays readable, so that the program goes into a wasteful loop.
+ *
+ * The initial fix was to use FIFOs, but those turn out to have their own
+ * problems, witness the workarounds in the fifo_listen() routine.
+ * Therefore we support both FIFOs and UNIX-domain sockets, so that the
+ * user can choose whatever works best.
+ *
+ * Well, I give up. Solaris UNIX-domain sockets still don't work properly,
+ * so it will have to limp along with a streams-specific alternative.
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ trigger_server_accept = trigger_server_accept_local;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0)
+ trigger_server_accept = trigger_server_accept_fifo;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ trigger_server_accept = trigger_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(trigger_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, trigger_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ trigger_server_service = service;
+ trigger_server_name = service_name;
+ trigger_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(trigger_server_name, trigger_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(trigger_server_name, trigger_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input?
+ */
+ if (stream != 0) {
+ if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0)
+ msg_fatal("read: %m");
+ service(buf, len, trigger_server_name, trigger_server_argv);
+ vstream_fflush(stream);
+ trigger_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, trigger_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(trigger_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (trigger_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1;
+ event_loop(delay);
+ }
+ trigger_server_exit();
+}
diff --git a/src/milter/.indent.pro b/src/milter/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/milter/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/milter/Makefile.in b/src/milter/Makefile.in
new file mode 100644
index 0000000..98e3ba0
--- /dev/null
+++ b/src/milter/Makefile.in
@@ -0,0 +1,145 @@
+SHELL = /bin/sh
+SRCS = milter.c milter8.c milter_macros.c
+OBJS = milter.o milter8.o milter_macros.o
+HDRS = milter.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = libmilter.a
+TESTPROG= milter test-milter
+
+LIBS = ../../$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(_AR) $(ARFL) $(LIB) $?
+ $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(_RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+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 $(LIB) *core $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+milter: milter.c $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+test-milter: test-milter.c
+ cc -g -I/usr/local/include -o $@ $? -L/usr/local/lib -lmilter -lpthread
+
+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'
+milter.o: ../../include/argv.h
+milter.o: ../../include/attr.h
+milter.o: ../../include/attr_override.h
+milter.o: ../../include/check_arg.h
+milter.o: ../../include/htable.h
+milter.o: ../../include/iostuff.h
+milter.o: ../../include/mail_params.h
+milter.o: ../../include/mail_proto.h
+milter.o: ../../include/msg.h
+milter.o: ../../include/mymalloc.h
+milter.o: ../../include/nvtable.h
+milter.o: ../../include/rec_type.h
+milter.o: ../../include/record.h
+milter.o: ../../include/stringops.h
+milter.o: ../../include/sys_defs.h
+milter.o: ../../include/vbuf.h
+milter.o: ../../include/vstream.h
+milter.o: ../../include/vstring.h
+milter.o: milter.c
+milter.o: milter.h
+milter8.o: ../../include/argv.h
+milter8.o: ../../include/attr.h
+milter8.o: ../../include/check_arg.h
+milter8.o: ../../include/compat_va_copy.h
+milter8.o: ../../include/connect.h
+milter8.o: ../../include/header_opts.h
+milter8.o: ../../include/htable.h
+milter8.o: ../../include/iostuff.h
+milter8.o: ../../include/is_header.h
+milter8.o: ../../include/mail_params.h
+milter8.o: ../../include/mail_proto.h
+milter8.o: ../../include/mime_state.h
+milter8.o: ../../include/msg.h
+milter8.o: ../../include/mymalloc.h
+milter8.o: ../../include/name_code.h
+milter8.o: ../../include/name_mask.h
+milter8.o: ../../include/nvtable.h
+milter8.o: ../../include/rec_type.h
+milter8.o: ../../include/record.h
+milter8.o: ../../include/split_at.h
+milter8.o: ../../include/stringops.h
+milter8.o: ../../include/sys_defs.h
+milter8.o: ../../include/vbuf.h
+milter8.o: ../../include/vstream.h
+milter8.o: ../../include/vstring.h
+milter8.o: milter.h
+milter8.o: milter8.c
+milter_macros.o: ../../include/argv.h
+milter_macros.o: ../../include/attr.h
+milter_macros.o: ../../include/check_arg.h
+milter_macros.o: ../../include/htable.h
+milter_macros.o: ../../include/iostuff.h
+milter_macros.o: ../../include/mail_proto.h
+milter_macros.o: ../../include/msg.h
+milter_macros.o: ../../include/mymalloc.h
+milter_macros.o: ../../include/nvtable.h
+milter_macros.o: ../../include/sys_defs.h
+milter_macros.o: ../../include/vbuf.h
+milter_macros.o: ../../include/vstream.h
+milter_macros.o: ../../include/vstring.h
+milter_macros.o: milter.h
+milter_macros.o: milter_macros.c
+test-milter.o: test-milter.c
diff --git a/src/milter/milter.c b/src/milter/milter.c
new file mode 100644
index 0000000..dfd5e1c
--- /dev/null
+++ b/src/milter/milter.c
@@ -0,0 +1,1121 @@
+/*++
+/* NAME
+/* milter 3
+/* SUMMARY
+/* generic MTA-side mail filter interface
+/* SYNOPSIS
+/* #include <milter.h>
+/*
+/* MILTERS *milter_create(milter_names, conn_timeout, cmd_timeout,
+/* msg_timeout, protocol, def_action,
+/* conn_macros, helo_macros,
+/* mail_macros, rcpt_macros,
+/* data_macros, eoh_macros,
+/* eod_macros, unk_macros,
+/* macro_deflts)
+/* const char *milter_names;
+/* int conn_timeout;
+/* int cmd_timeout;
+/* int msg_timeout;
+/* const char *protocol;
+/* const char *def_action;
+/* const char *conn_macros;
+/* const char *helo_macros;
+/* const char *mail_macros;
+/* const char *rcpt_macrps;
+/* const char *data_macros;
+/* const char *eoh_macros;
+/* const char *eod_macros;
+/* const char *unk_macros;
+/* const char *macro_deflts;
+/*
+/* void milter_free(milters)
+/* MILTERS *milters;
+/*
+/* void milter_macro_callback(milters, mac_lookup, mac_context)
+/* const char *(*mac_lookup)(const char *name, void *context);
+/* void *mac_context;
+/*
+/* void milter_edit_callback(milters, add_header, upd_header,
+/* ins_header, del_header, chg_from,
+/* add_rcpt, add_rcpt_par, del_rcpt,
+/* repl_body, context)
+/* MILTERS *milters;
+/* MILTER_ADD_HEADER_FN add_header;
+/* MILTER_EDIT_HEADER_FN upd_header;
+/* MILTER_EDIT_HEADER_FN ins_header;
+/* MILTER_DEL_HEADER_FN del_header;
+/* MILTER_EDIT_FROM_FN chg_from;
+/* MILTER_EDIT_RCPT_FN add_rcpt;
+/* MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
+/* MILTER_EDIT_RCPT_FN del_rcpt;
+/* MILTER_EDIT_BODY_FN repl_body;
+/* void *context;
+/*
+/* const char *milter_conn_event(milters, client_name, client_addr,
+/* client_port, addr_family)
+/* MILTERS *milters;
+/* const char *client_name;
+/* const char *client_addr;
+/* const char *client_port;
+/* int addr_family;
+/*
+/* const char *milter_disc_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_helo_event(milters, helo_name, esmtp_flag)
+/* MILTERS *milters;
+/* const char *helo_name;
+/* int esmtp_flag;
+/*
+/* const char *milter_mail_event(milters, argv)
+/* MILTERS *milters;
+/* const char **argv;
+/*
+/* const char *milter_rcpt_event(milters, flags, argv)
+/* MILTERS *milters;
+/* int flags;
+/* const char **argv;
+/*
+/* const char *milter_data_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_unknown_event(milters, command)
+/* MILTERS *milters;
+/* const char *command;
+/*
+/* const char *milter_other_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_message(milters, qfile, data_offset, auto_hdrs)
+/* MILTERS *milters;
+/* VSTREAM *qfile;
+/* off_t data_offset;
+/* ARGV *auto_hdrs;
+/*
+/* const char *milter_abort(milters)
+/* MILTERS *milters;
+/*
+/* int milter_send(milters, fp)
+/* MILTERS *milters;
+/* VSTREAM *fp;
+/*
+/* MILTERS *milter_receive(fp, count)
+/* VSTREAM *fp;
+/* int count;
+/*
+/* int milter_dummy(milters, fp)
+/* MILTERS *milters;
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* The functions in this module manage one or more milter (mail
+/* filter) clients. Currently, only the Sendmail 8 filter
+/* protocol is supported.
+/*
+/* The functions that inspect content or envelope commands
+/* return either an SMTP reply ([45]XX followed by enhanced
+/* status code and text), "D" (discard), "H" (quarantine),
+/* "S" (shutdown connection), or a null pointer, which means
+/* "no news is good news".
+/*
+/* milter_create() instantiates the milter clients specified
+/* with the milter_names argument. The conn_macros etc.
+/* arguments specify the names of macros that are sent to the
+/* mail filter applications upon a connect etc. event, and the
+/* macro_deflts argument specifies macro defaults that will be used
+/* only if the application's lookup call-back returns null. This
+/* function should be called during process initialization,
+/* before entering a chroot jail. The timeout parameters specify
+/* time limits for the completion of the specified request
+/* classes. The protocol parameter specifies a protocol version
+/* and optional extensions. When the milter application is
+/* unavailable, the milter client will go into a suitable error
+/* state as specified with the def_action parameter (i.e.
+/* reject, tempfail or accept all subsequent events).
+/*
+/* milter_free() disconnects from the milter instances that
+/* are still opened, and destroys the data structures created
+/* by milter_create(). This function is safe to call at any
+/* point after milter_create().
+/*
+/* milter_macro_callback() specifies a call-back function and
+/* context for macro lookup. This function must be called
+/* before milter_conn_event().
+/*
+/* milter_edit_callback() specifies call-back functions and
+/* context for editing the queue file after the end-of-data
+/* is received. This function must be called before milter_message();
+/*
+/* milter_conn_event() reports an SMTP client connection event
+/* to the specified milter instances, after sending the macros
+/* specified with the milter_create() conn_macros argument.
+/* This function must be called before reporting any other
+/* events.
+/*
+/* milter_disc_event() reports an SMTP client disconnection
+/* event to the specified milter instances. No events can
+/* reported after this call. To simplify usage, redundant calls
+/* of this function are NO-OPs and don't raise a run-time
+/* error.
+/*
+/* milter_helo_event() reports a HELO or EHLO event to the
+/* specified milter instances, after sending the macros that
+/* were specified with the milter_create() helo_macros argument.
+/*
+/* milter_mail_event() reports a MAIL FROM event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() mail_macros argument.
+/*
+/* milter_rcpt_event() reports an RCPT TO event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() rcpt_macros argument. The flags
+/* argument supports the following:
+/* .IP MILTER_FLAG_WANT_RCPT_REJ
+/* When this flag is cleared, invoke all milters. When this
+/* flag is set, invoke only milters that want to receive
+/* rejected recipients; with Sendmail V8 Milters, {rcpt_mailer}
+/* is set to "error", {rcpt_host} is set to an enhanced status
+/* code, and {rcpt_addr} is set to descriptive text.
+/* .PP
+/* milter_data_event() reports a DATA event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() data_macros argument.
+/*
+/* milter_unknown_event() reports an unknown command event to
+/* the specified milter instances, after sending the macros
+/* that were specified with the milter_create() unk_macros
+/* argument.
+/*
+/* milter_other_event() returns the current default mail filter
+/* reply for the current SMTP connection state; it does not
+/* change milter states. A null pointer result means that all
+/* is well. This function can be used for SMTP commands such
+/* as AUTH, STARTTLS that don't have their own milter event
+/* routine.
+/*
+/* milter_message() sends the message header and body to the
+/* to the specified milter instances, and sends the macros
+/* specified with the milter_create() eoh_macros after the
+/* message header, and with the eod_macros argument at
+/* the end. Each milter sees the result of any changes made
+/* by a preceding milter. This function must be called with
+/* as argument an open Postfix queue file.
+/*
+/* milter_abort() cancels a mail transaction in progress. To
+/* simplify usage, redundant calls of this function are NO-OPs
+/* and don't raise a run-time error.
+/*
+/* milter_send() sends a list of mail filters over the specified
+/* stream. When given a null list pointer, a "no filter"
+/* indication is sent. The result is non-zero in case of
+/* error.
+/*
+/* milter_receive() receives the specified number of mail
+/* filters over the specified stream. The result is a null
+/* pointer when no milters were sent, or when an error happened.
+/*
+/* milter_dummy() is like milter_send(), except that it sends
+/* a dummy, but entirely valid, mail filter list.
+/* SEE ALSO
+/* milter8(3) Sendmail 8 Milter protocol
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/* Fatal errors: memory allocation problem.
+/* 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 <stringops.h>
+#include <argv.h>
+#include <attr.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_params.h>
+#include <attr_override.h>
+
+/* Postfix Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* milter_macro_defaults_create - parse default macro entries */
+
+HTABLE *milter_macro_defaults_create(const char *macro_defaults)
+{
+ const char myname[] = "milter_macro_defaults_create";
+ char *saved_defaults = mystrdup(macro_defaults);
+ char *cp = saved_defaults;
+ HTABLE *table = 0;
+ VSTRING *canon_buf = 0;
+ char *nameval;
+
+ while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ const char *err;
+ char *name;
+ char *value;
+
+ /*
+ * Split the input into (name, value) pairs. Allow the forms
+ * name=value and { name = value }, where the last form ignores
+ * whitespace after the opening "{", around the "=", and before the
+ * closing "}". A name may also be specified as {name}.
+ *
+ * Use the form {name} for table lookups, because that is the form of
+ * the S8_MAC_* macro names.
+ */
+ if (*nameval == CHARS_BRACE[0]
+ && nameval[balpar(nameval, CHARS_BRACE)] != '='
+ && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if ((err = split_nameval(nameval, &name, &value)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if (*name != '{') /* } */
+ name = STR(vstring_sprintf(canon_buf ? canon_buf :
+ (canon_buf = vstring_alloc(20)), "{%s}", name));
+ if (table == 0)
+ table = htable_create(1);
+ if (htable_find(table, name) != 0) {
+ msg_warn("ignoring multiple default macro entries for %s in \"%s\"",
+ name, macro_defaults);
+ } else {
+ (void) htable_enter(table, name, mystrdup(value));
+ if (msg_verbose)
+ msg_info("%s: add name=%s default=%s", myname, name, value);
+ }
+ }
+ myfree(saved_defaults);
+ if (canon_buf)
+ vstring_free(canon_buf);
+ return (table);
+}
+
+/* milter_macro_lookup - look up macros */
+
+static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names)
+{
+ const char *myname = "milter_macro_lookup";
+ char *saved_names = mystrdup(macro_names);
+ char *cp = saved_names;
+ ARGV *argv = argv_alloc(10);
+ VSTRING *canon_buf = vstring_alloc(20);
+ const char *value;
+ const char *name;
+ const char *cname;
+
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: \"%s\"", myname, name);
+ if (*name != '{') /* } */
+ cname = STR(vstring_sprintf(canon_buf, "{%s}", name));
+ else
+ cname = name;
+ if ((value = milters->mac_lookup(cname, milters->mac_context)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: result \"%s\"", myname, value);
+ argv_add(argv, name, value, (char *) 0);
+ } else if (milters->macro_defaults != 0
+ && (value = htable_find(milters->macro_defaults, cname)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: using default \"%s\"", myname, value);
+ argv_add(argv, name, value, (char *) 0);
+ }
+ }
+ myfree(saved_names);
+ vstring_free(canon_buf);
+ return (argv);
+}
+
+/* milter_macro_callback - specify macro lookup */
+
+void milter_macro_callback(MILTERS *milters,
+ const char *(*mac_lookup) (const char *, void *),
+ void *mac_context)
+{
+ milters->mac_lookup = mac_lookup;
+ milters->mac_context = mac_context;
+}
+
+/* milter_edit_callback - specify queue file edit call-back information */
+
+void milter_edit_callback(MILTERS *milters,
+ MILTER_ADD_HEADER_FN add_header,
+ MILTER_EDIT_HEADER_FN upd_header,
+ MILTER_EDIT_HEADER_FN ins_header,
+ MILTER_DEL_HEADER_FN del_header,
+ MILTER_EDIT_FROM_FN chg_from,
+ MILTER_EDIT_RCPT_FN add_rcpt,
+ MILTER_EDIT_RCPT_PAR_FN add_rcpt_par,
+ MILTER_EDIT_RCPT_FN del_rcpt,
+ MILTER_EDIT_BODY_FN repl_body,
+ void *chg_context)
+{
+ milters->add_header = add_header;
+ milters->upd_header = upd_header;
+ milters->ins_header = ins_header;
+ milters->del_header = del_header;
+ milters->chg_from = chg_from;
+ milters->add_rcpt = add_rcpt;
+ milters->add_rcpt_par = add_rcpt_par;
+ milters->del_rcpt = del_rcpt;
+ milters->repl_body = repl_body;
+ milters->chg_context = chg_context;
+}
+
+/* milter_conn_event - report connect event */
+
+const char *milter_conn_event(MILTERS *milters,
+ const char *client_name,
+ const char *client_addr,
+ const char *client_port,
+ unsigned addr_family)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+#define MILTER_MACRO_EVAL(global_macros, m, milters, member) \
+ ((m->macros && m->macros->member[0]) ? \
+ milter_macro_lookup(milters, m->macros->member) : \
+ global_macros ? global_macros : \
+ (global_macros = \
+ milter_macro_lookup(milters, milters->macros->member)))
+
+ if (msg_verbose)
+ msg_info("report connect to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ if (m->connect_on_demand != 0)
+ m->connect_on_demand(m);
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros);
+ resp = m->conn_event(m, client_name, client_addr, client_port,
+ addr_family, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_helo_event - report helo event */
+
+const char *milter_helo_event(MILTERS *milters, const char *helo_name,
+ int esmtp_flag)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report helo to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros);
+ resp = m->helo_event(m, helo_name, esmtp_flag, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_mail_event - report mail from event */
+
+const char *milter_mail_event(MILTERS *milters, const char **argv)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report sender to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros);
+ resp = m->mail_event(m, argv, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_rcpt_event - report rcpt to event */
+
+const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report recipient to all milters (flags=0x%x)", flags);
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0
+ || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) {
+ any_macros =
+ MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros);
+ resp = m->rcpt_event(m, argv, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_data_event - report data event */
+
+const char *milter_data_event(MILTERS *milters)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report data to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros);
+ resp = m->data_event(m, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_unknown_event - report unknown command */
+
+const char *milter_unknown_event(MILTERS *milters, const char *command)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report unknown command to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros);
+ resp = m->unknown_event(m, command, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_other_event - other SMTP event */
+
+const char *milter_other_event(MILTERS *milters)
+{
+ const char *resp;
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("query milter states for other event");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next)
+ resp = m->other_event(m);
+ return (resp);
+}
+
+/* milter_message - inspect message content */
+
+const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset,
+ ARGV *auto_hdrs)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_eoh_macros = 0;
+ ARGV *global_eod_macros = 0;
+ ARGV *any_eoh_macros;
+ ARGV *any_eod_macros;
+
+ if (msg_verbose)
+ msg_info("inspect content by all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros);
+ any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros);
+ resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros,
+ auto_hdrs);
+ if (any_eoh_macros != global_eoh_macros)
+ argv_free(any_eoh_macros);
+ if (any_eod_macros != global_eod_macros)
+ argv_free(any_eod_macros);
+ }
+ if (global_eoh_macros)
+ argv_free(global_eoh_macros);
+ if (global_eod_macros)
+ argv_free(global_eod_macros);
+ return (resp);
+}
+
+/* milter_abort - cancel message receiving state, all milters */
+
+void milter_abort(MILTERS *milters)
+{
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("abort all milters");
+ for (m = milters->milter_list; m != 0; m = m->next)
+ m->abort(m);
+}
+
+/* milter_disc_event - report client disconnect event to all milters */
+
+void milter_disc_event(MILTERS *milters)
+{
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("disconnect event to all milters");
+ for (m = milters->milter_list; m != 0; m = m->next)
+ m->disc_event(m);
+}
+
+ /*
+ * Table-driven parsing of main.cf parameter overrides for specific Milters.
+ * We derive the override names from the corresponding main.cf parameter
+ * names by skipping the redundant "milter_" prefix.
+ */
+static ATTR_OVER_TIME time_table[] = {
+ 7 + (const char *) VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_STR str_table[] = {
+ 7 + (const char *) VAR_MILT_PROTOCOL, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_DEF_ACTION, 0, 1, 0,
+ 0,
+};
+
+#define link_override_table_to_variable(table, var) \
+ do { table[var##_offset].target = &var; } while (0)
+
+#define my_conn_timeout_offset 0
+#define my_cmd_timeout_offset 1
+#define my_msg_timeout_offset 2
+
+#define my_protocol_offset 0
+#define my_def_action_offset 1
+
+/* milter_new - create milter list */
+
+MILTERS *milter_new(const char *names,
+ int conn_timeout,
+ int cmd_timeout,
+ int msg_timeout,
+ const char *protocol,
+ const char *def_action,
+ MILTER_MACROS *macros,
+ HTABLE *macro_defaults)
+{
+ MILTERS *milters;
+ MILTER *head = 0;
+ MILTER *tail = 0;
+ char *name;
+ MILTER *milter;
+ const char *sep = CHARS_COMMA_SP;
+ const char *parens = CHARS_BRACE;
+ int my_conn_timeout;
+ int my_cmd_timeout;
+ int my_msg_timeout;
+ const char *my_protocol;
+ const char *my_def_action;
+
+ /*
+ * Initialize.
+ */
+ link_override_table_to_variable(time_table, my_conn_timeout);
+ link_override_table_to_variable(time_table, my_cmd_timeout);
+ link_override_table_to_variable(time_table, my_msg_timeout);
+ link_override_table_to_variable(str_table, my_protocol);
+ link_override_table_to_variable(str_table, my_def_action);
+
+ /*
+ * Parse the milter list.
+ */
+ milters = (MILTERS *) mymalloc(sizeof(*milters));
+ if (names != 0 && *names != 0) {
+ char *saved_names = mystrdup(names);
+ char *cp = saved_names;
+ char *op;
+ char *err;
+
+ /*
+ * Instantiate Milters, allowing for per-Milter overrides.
+ */
+ while ((name = mystrtokq(&cp, sep, parens)) != 0) {
+ my_conn_timeout = conn_timeout;
+ my_cmd_timeout = cmd_timeout;
+ my_msg_timeout = msg_timeout;
+ my_protocol = protocol;
+ my_def_action = def_action;
+ if (name[0] == parens[0]) {
+ op = name;
+ if ((err = extpar(&op, parens, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("milter service syntax error: %s", err);
+ if ((name = mystrtok(&op, sep)) == 0)
+ msg_fatal("empty milter definition: \"%s\"", names);
+ attr_override(op, sep, parens,
+ CA_ATTR_OVER_STR_TABLE(str_table),
+ CA_ATTR_OVER_TIME_TABLE(time_table),
+ CA_ATTR_OVER_END);
+ }
+ milter = milter8_create(name, my_conn_timeout, my_cmd_timeout,
+ my_msg_timeout, my_protocol,
+ my_def_action, milters);
+ if (head == 0) {
+ head = milter;
+ } else {
+ tail->next = milter;
+ }
+ tail = milter;
+ }
+ myfree(saved_names);
+ }
+ milters->milter_list = head;
+ milters->mac_lookup = 0;
+ milters->mac_context = 0;
+ milters->macros = macros;
+ milters->macro_defaults = macro_defaults;
+ milters->add_header = 0;
+ milters->upd_header = milters->ins_header = 0;
+ milters->del_header = 0;
+ milters->add_rcpt = milters->del_rcpt = 0;
+ milters->repl_body = 0;
+ milters->chg_context = 0;
+ return (milters);
+}
+
+/* milter_free - destroy all milters */
+
+void milter_free(MILTERS *milters)
+{
+ MILTER *m;
+ MILTER *next;
+
+ if (msg_verbose)
+ msg_info("free all milters");
+ for (m = milters->milter_list; m != 0; m = next)
+ next = m->next, m->free(m);
+ if (milters->macros)
+ milter_macros_free(milters->macros);
+ if (milters->macro_defaults)
+ htable_free(milters->macro_defaults, myfree);
+ myfree((void *) milters);
+}
+
+/* milter_dummy - send empty milter list */
+
+int milter_dummy(MILTERS *milters, VSTREAM *stream)
+{
+ MILTERS dummy = *milters;
+
+ dummy.milter_list = 0;
+ return (milter_send(&dummy, stream));
+}
+
+/* milter_send - send Milter instances over stream */
+
+int milter_send(MILTERS *milters, VSTREAM *stream)
+{
+ MILTER *m;
+ int status = 0;
+ int count = 0;
+
+ /*
+ * XXX Optimization: send only the filters that are actually used in the
+ * remote process. No point sending a filter that looks at HELO commands
+ * to a cleanup server. For now we skip only the filters that are known
+ * to be disabled (either in global error state or in global accept
+ * state).
+ *
+ * XXX We must send *some* information, even when there are no active
+ * filters, otherwise the cleanup server would try to apply its own
+ * non_smtpd_milters settings.
+ */
+ if (milters != 0)
+ for (m = milters->milter_list; m != 0; m = m->next)
+ if (m->active(m))
+ count++;
+ (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count);
+
+ if (msg_verbose)
+ msg_info("send %d milters", count);
+
+ /*
+ * XXX Optimization: don't send or receive further information when there
+ * aren't any active filters.
+ */
+ if (count <= 0)
+ return (0);
+
+ /*
+ * Send the filter macro name lists.
+ */
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_FUNC(milter_macros_print,
+ (const void *) milters->macros),
+ ATTR_TYPE_END);
+
+ /*
+ * Send the filter macro defaults.
+ */
+ count = milters->macro_defaults ? milters->macro_defaults->used : 0;
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_INT(MAIL_ATTR_SIZE, count),
+ ATTR_TYPE_END);
+ if (count > 0)
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_HASH(milters->macro_defaults),
+ ATTR_TYPE_END);
+
+ /*
+ * Send the filter instances.
+ */
+ for (m = milters->milter_list; m != 0; m = m->next)
+ if (m->active(m) && (status = m->send(m, stream)) != 0)
+ break;
+
+ /*
+ * Over to you.
+ */
+ if (status != 0
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1
+ || status != 0) {
+ msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream));
+ return (-1);
+ }
+ return (0);
+}
+
+/* milter_receive - receive milters from stream */
+
+MILTERS *milter_receive(VSTREAM *stream, int count)
+{
+ MILTERS *milters;
+ MILTER *head = 0;
+ MILTER *tail = 0;
+ MILTER *milter = 0;
+ int macro_default_count;
+
+ if (msg_verbose)
+ msg_info("receive %d milters", count);
+
+ /*
+ * XXX We must instantiate a MILTERS structure even when the sender has
+ * no active filters, otherwise the cleanup server would try to use its
+ * own non_smtpd_milters settings.
+ */
+#define NO_MILTERS ((char *) 0)
+#define NO_TIMEOUTS 0, 0, 0
+#define NO_PROTOCOL ((char *) 0)
+#define NO_ACTION ((char *) 0)
+#define NO_MACROS ((MILTER_MACROS *) 0)
+#define NO_MACRO_DEFLTS ((HTABLE *) 0)
+
+ milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
+ NO_MACROS, NO_MACRO_DEFLTS);
+
+ /*
+ * XXX Optimization: don't send or receive further information when there
+ * aren't any active filters.
+ */
+ if (count <= 0)
+ return (milters);
+
+ /*
+ * Receive the global macro name lists.
+ */
+ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO);
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_FUNC(milter_macros_scan,
+ (void *) milters->macros),
+ ATTR_TYPE_END) != 1) {
+ milter_free(milters);
+ return (0);
+ }
+
+ /*
+ * Receive the filter macro defaults.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(MAIL_ATTR_SIZE, &macro_default_count),
+ ATTR_TYPE_END) != 1
+ || (macro_default_count > 0
+ && attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_HASH(milters->macro_defaults
+ = htable_create(1)),
+ ATTR_TYPE_END) != macro_default_count)) {
+ milter_free(milters);
+ return (0);
+ }
+
+ /*
+ * Receive the filters.
+ */
+ for (; count > 0; count--) {
+ if ((milter = milter8_receive(stream, milters)) == 0) {
+ msg_warn("cannot receive milters via service %s socket",
+ VSTREAM_PATH(stream));
+ milter_free(milters);
+ return (0);
+ }
+ if (head == 0) {
+ /* Coverity: milter_free() depends on milters->milter_list. */
+ milters->milter_list = head = milter;
+ } else {
+ tail->next = milter;
+ }
+ tail = milter;
+ }
+
+ /*
+ * Over to you.
+ */
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, 0),
+ ATTR_TYPE_END);
+ return (milters);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. This can be used interactively, but is
+ * typically used for automated regression tests from a script.
+ */
+
+/* System library. */
+
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "msg_vstream.h"
+#include "vstring_vstream.h"
+
+/* Global library. */
+
+#include <mail_params.h>
+
+int var_milt_conn_time = 10;
+int var_milt_cmd_time = 10;
+int var_milt_msg_time = 100;
+char *var_milt_protocol = DEF_MILT_PROTOCOL;
+char *var_milt_def_action = DEF_MILT_DEF_ACTION;
+
+static void usage(void)
+{
+ vstream_fprintf(VSTREAM_ERR, "usage: \n"
+ " create names... create and connect\n"
+#if 0
+ " conn_macros names... define connect macros\n"
+ " helo_macros names... define helo command macros\n"
+ " mail_macros names... define mail command macros\n"
+ " rcpt_macros names... define rcpt command macros\n"
+ " data_macros names... define data command macros\n"
+ " unk_macros names... unknown command macros\n"
+ " message_macros names... define message macros\n"
+#endif
+ " free disconnect and destroy\n"
+ " connect name addr port family\n"
+ " helo hostname\n"
+ " ehlo hostname\n"
+ " mail from sender...\n"
+ " rcpt to recipient...\n"
+ " data\n"
+ " disconnect\n"
+ " unknown command\n");
+ vstream_fflush(VSTREAM_ERR);
+}
+
+int main(int argc, char **argv)
+{
+ MILTERS *milters = 0;
+ char *conn_macros, *helo_macros, *mail_macros, *rcpt_macros;
+ char *data_macros, *eoh_macros, *eod_macros, *unk_macros;
+ char *macro_deflts;
+ VSTRING *inbuf = vstring_alloc(100);
+ char *bufp;
+ char *cmd;
+ int ch;
+ int istty = isatty(vstream_fileno(VSTREAM_IN));
+
+ conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros
+ = eoh_macros = eod_macros = unk_macros = macro_deflts = "";
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "V:v")) > 0) {
+ switch (ch) {
+ default:
+ msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]);
+ case 'a':
+ var_milt_def_action = optarg;
+ break;
+ case 'p':
+ var_milt_protocol = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ optind = OPTIND;
+
+ for (;;) {
+ const char *resp = 0;
+ ARGV *argv;
+ char **args;
+
+ 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 == '#')
+ continue;
+ cmd = mystrtok(&bufp, " ");
+ if (cmd == 0) {
+ usage();
+ continue;
+ }
+ argv = argv_split(bufp, " ");
+ args = argv->argv;
+ if (strcmp(cmd, "create") == 0 && argv->argc == 1) {
+ if (milters != 0) {
+ msg_warn("deleting existing milters");
+ milter_free(milters);
+ }
+ milters = milter_create(args[0], var_milt_conn_time,
+ var_milt_cmd_time, var_milt_msg_time,
+ var_milt_protocol, var_milt_def_action,
+ conn_macros, helo_macros, mail_macros,
+ rcpt_macros, data_macros, eoh_macros,
+ eod_macros, unk_macros, macro_deflts);
+ } else if (strcmp(cmd, "free") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ milter_free(milters);
+ milters = 0;
+ } else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_conn_event(milters, args[0], args[1], args[2],
+ strcmp(args[3], "AF_INET") == 0 ? AF_INET :
+ strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 :
+ strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX :
+ AF_UNSPEC);
+ } else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_helo_event(milters, args[0], 0);
+ } else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_helo_event(milters, args[0], 1);
+ } else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_mail_event(milters, (const char **) args);
+ } else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_rcpt_event(milters, 0, (const char **) args);
+ } else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_unknown_event(milters, args[0]);
+ } else if (strcmp(cmd, "data") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_data_event(milters);
+ } else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ milter_disc_event(milters);
+ } else {
+ usage();
+ }
+ if (resp != 0)
+ msg_info("%s", resp);
+ argv_free(argv);
+ }
+ if (milters != 0)
+ milter_free(milters);
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif
diff --git a/src/milter/milter.h b/src/milter/milter.h
new file mode 100644
index 0000000..3a1e3f9
--- /dev/null
+++ b/src/milter/milter.h
@@ -0,0 +1,223 @@
+#ifndef _MILTER_H_INCLUDED_
+#define _MILTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* milter 3h
+/* SUMMARY
+/* smtp server
+/* SYNOPSIS
+/* Postfix MTA-side Milter implementation
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+
+ /*
+ * Global library.
+ */
+#include <attr.h>
+
+ /*
+ * Each Milter handle is an element of a null-terminated linked list. The
+ * functions are virtual so that we can support multiple MTA-side Milter
+ * implementations. The Sendmail 8 and Sendmail X Milter-side APIs are too
+ * different to implement the MTA side as a single hybrid.
+ */
+typedef struct MILTER {
+ char *name; /* full name including transport */
+ int flags; /* see below */
+ struct MILTER *next; /* linkage */
+ struct MILTERS *parent; /* parent information */
+ struct MILTER_MACROS *macros; /* private macros */
+ void (*connect_on_demand) (struct MILTER *);
+ const char *(*conn_event) (struct MILTER *, const char *, const char *, const char *, unsigned, ARGV *);
+ const char *(*helo_event) (struct MILTER *, const char *, int, ARGV *);
+ const char *(*mail_event) (struct MILTER *, const char **, ARGV *);
+ const char *(*rcpt_event) (struct MILTER *, const char **, ARGV *);
+ const char *(*data_event) (struct MILTER *, ARGV *);
+ const char *(*message) (struct MILTER *, VSTREAM *, off_t, ARGV *, ARGV *, ARGV *);
+ const char *(*unknown_event) (struct MILTER *, const char *, ARGV *);
+ const char *(*other_event) (struct MILTER *);
+ void (*abort) (struct MILTER *);
+ void (*disc_event) (struct MILTER *);
+ int (*active) (struct MILTER *);
+ int (*send) (struct MILTER *, VSTREAM *);
+ void (*free) (struct MILTER *);
+} MILTER;
+
+#define MILTER_FLAG_NONE (0)
+#define MILTER_FLAG_WANT_RCPT_REJ (1<<0) /* see S8_RCPT_MAILER_ERROR */
+
+extern MILTER *milter8_create(const char *, int, int, int, const char *, const char *, struct MILTERS *);
+extern MILTER *milter8_receive(VSTREAM *, struct MILTERS *);
+
+ /*
+ * As of Sendmail 8.14 each milter can override the default macro list. If a
+ * Milter has its own macro list, a null member means use the global
+ * definition.
+ */
+typedef struct MILTER_MACROS {
+ char *conn_macros; /* macros for connect event */
+ char *helo_macros; /* macros for HELO/EHLO command */
+ char *mail_macros; /* macros for MAIL FROM command */
+ char *rcpt_macros; /* macros for RCPT TO command */
+ char *data_macros; /* macros for DATA command */
+ char *eoh_macros; /* macros for end-of-headers */
+ char *eod_macros; /* macros for END-OF-DATA command */
+ char *unk_macros; /* macros for unknown command */
+} MILTER_MACROS;
+
+extern MILTER_MACROS *milter_macros_create(const char *, const char *,
+ const char *, const char *,
+ const char *, const char *,
+ const char *, const char *);
+extern MILTER_MACROS *milter_macros_alloc(int);
+extern void milter_macros_free(MILTER_MACROS *);
+extern int milter_macros_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int milter_macros_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+#define MILTER_MACROS_ALLOC_ZERO 1 /* null pointer */
+#define MILTER_MACROS_ALLOC_EMPTY 2 /* mystrdup(""); */
+
+ /*
+ * Helper to parse list of name=value default macro settings.
+ */
+extern struct HTABLE *milter_macro_defaults_create(const char *);
+
+ /*
+ * A bunch of Milters.
+ */
+typedef const char *(*MILTER_MAC_LOOKUP_FN) (const char *, void *);
+typedef const char *(*MILTER_ADD_HEADER_FN) (void *, const char *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_HEADER_FN) (void *, ssize_t, const char *, const char *, const char *);
+typedef const char *(*MILTER_DEL_HEADER_FN) (void *, ssize_t, const char *);
+typedef const char *(*MILTER_EDIT_FROM_FN) (void *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_RCPT_FN) (void *, const char *);
+typedef const char *(*MILTER_EDIT_RCPT_PAR_FN) (void *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_BODY_FN) (void *, int, int, VSTRING *);
+
+typedef struct MILTERS {
+ MILTER *milter_list; /* linked list of Milters */
+ MILTER_MAC_LOOKUP_FN mac_lookup;
+ void *mac_context; /* macro lookup context */
+ struct MILTER_MACROS *macros;
+ struct HTABLE *macro_defaults;
+ void *chg_context; /* context for queue file changes */
+ MILTER_ADD_HEADER_FN add_header;
+ MILTER_EDIT_HEADER_FN upd_header;
+ MILTER_DEL_HEADER_FN del_header;
+ MILTER_EDIT_HEADER_FN ins_header;
+ MILTER_EDIT_FROM_FN chg_from;
+ MILTER_EDIT_RCPT_FN add_rcpt;
+ MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
+ MILTER_EDIT_RCPT_FN del_rcpt;
+ MILTER_EDIT_BODY_FN repl_body;
+} MILTERS;
+
+#define milter_create(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
+ protocol, def_action, conn_macros, helo_macros, \
+ mail_macros, rcpt_macros, data_macros, eoh_macros, \
+ eod_macros, unk_macros, macro_deflts) \
+ milter_new(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
+ protocol, def_action, milter_macros_create(conn_macros, \
+ helo_macros, mail_macros, rcpt_macros, data_macros, \
+ eoh_macros, eod_macros, unk_macros), \
+ milter_macro_defaults_create(macro_deflts))
+
+extern MILTERS *milter_new(const char *, int, int, int, const char *,
+ const char *, MILTER_MACROS *,
+ struct HTABLE *);
+extern void milter_macro_callback(MILTERS *, MILTER_MAC_LOOKUP_FN, void *);
+extern void milter_edit_callback(MILTERS *milters, MILTER_ADD_HEADER_FN,
+ MILTER_EDIT_HEADER_FN, MILTER_EDIT_HEADER_FN,
+ MILTER_DEL_HEADER_FN, MILTER_EDIT_FROM_FN,
+ MILTER_EDIT_RCPT_FN, MILTER_EDIT_RCPT_PAR_FN,
+ MILTER_EDIT_RCPT_FN, MILTER_EDIT_BODY_FN,
+ void *);
+extern const char *milter_conn_event(MILTERS *, const char *, const char *, const char *, unsigned);
+extern const char *milter_helo_event(MILTERS *, const char *, int);
+extern const char *milter_mail_event(MILTERS *, const char **);
+extern const char *milter_rcpt_event(MILTERS *, int, const char **);
+extern const char *milter_data_event(MILTERS *);
+extern const char *milter_message(MILTERS *, VSTREAM *, off_t, ARGV *);
+extern const char *milter_unknown_event(MILTERS *, const char *);
+extern const char *milter_other_event(MILTERS *);
+extern void milter_abort(MILTERS *);
+extern void milter_disc_event(MILTERS *);
+extern int milter_dummy(MILTERS *, VSTREAM *);
+extern int milter_send(MILTERS *, VSTREAM *);
+extern MILTERS *milter_receive(VSTREAM *, int);
+extern void milter_free(MILTERS *);
+
+ /*
+ * Milter body edit commands.
+ */
+#define MILTER_BODY_START 1 /* start message body */
+#define MILTER_BODY_LINE 2 /* message body line */
+#define MILTER_BODY_END 3 /* end message body */
+
+ /*
+ * Sendmail 8 macro names. We support forms with and without the {}.
+ */
+#define S8_MAC__ "{_}" /* sender host, see client_resolve */
+#define S8_MAC_J "{j}" /* myhostname */
+#define S8_MAC_V "{v}" /* mail_name + mail_version */
+
+#define S8_MAC_DAEMON_NAME "{daemon_name}"
+#define S8_MAC_IF_NAME "{if_name}"
+#define S8_MAC_IF_ADDR "{if_addr}"
+
+#define S8_MAC_CLIENT_ADDR "{client_addr}"
+#define S8_MAC_CLIENT_CONN "{client_connections}"
+#define S8_MAC_CLIENT_NAME "{client_name}"
+#define S8_MAC_CLIENT_PORT "{client_port}"
+#define S8_MAC_CLIENT_PTR "{client_ptr}"
+#define S8_MAC_CLIENT_RES "{client_resolve}"
+
+#define S8_MAC_DAEMON_ADDR "{daemon_addr}"
+#define S8_MAC_DAEMON_PORT "{daemon_port}"
+
+#define S8_MAC_TLS_VERSION "{tls_version}"
+#define S8_MAC_CIPHER "{cipher}"
+#define S8_MAC_CIPHER_BITS "{cipher_bits}"
+#define S8_MAC_CERT_SUBJECT "{cert_subject}"
+#define S8_MAC_CERT_ISSUER "{cert_issuer}"
+
+#define S8_MAC_I "{i}" /* queue ID */
+#define S8_MAC_AUTH_TYPE "{auth_type}" /* SASL method */
+#define S8_MAC_AUTH_AUTHEN "{auth_authen}" /* SASL username */
+#define S8_MAC_AUTH_AUTHOR "{auth_author}" /* SASL sender */
+
+#define S8_MAC_MAIL_MAILER "{mail_mailer}" /* sender transport */
+#define S8_MAC_MAIL_HOST "{mail_host}" /* sender nexthop */
+#define S8_MAC_MAIL_ADDR "{mail_addr}" /* sender address */
+
+#define S8_MAC_RCPT_MAILER "{rcpt_mailer}" /* recip transport */
+#define S8_MAC_RCPT_HOST "{rcpt_host}" /* recip nexthop */
+#define S8_MAC_RCPT_ADDR "{rcpt_addr}" /* recip address */
+
+#define S8_RCPT_MAILER_ERROR "error" /* see MILTER_FLAG_WANT_RCPT_REJ */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/milter/milter8.c b/src/milter/milter8.c
new file mode 100644
index 0000000..ab2bc86
--- /dev/null
+++ b/src/milter/milter8.c
@@ -0,0 +1,2911 @@
+/*++
+/* NAME
+/* milter8 3
+/* SUMMARY
+/* MTA-side Sendmail 8 Milter protocol
+/* SYNOPSIS
+/* #include <milter8.h>
+/*
+/* MILTER *milter8_create(name, conn_timeout, cmd_timeout, msg_timeout,
+/* protocol, def_action, parent)
+/* const char *name;
+/* int conn_timeout;
+/* int cmd_timeout;
+/* int msg_timeout;
+/* const char *protocol;
+/* const char *def_action;
+/* MILTERS *parent;
+/*
+/* MILTER *milter8_receive(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the MTA side of the Sendmail 8 mail
+/* filter protocol.
+/*
+/* milter8_create() creates a MILTER data structure with virtual
+/* functions that implement a client for the Sendmail 8 Milter
+/* protocol. These virtual functions are then invoked via the
+/* milter(3) interface. The *timeout, protocol and def_action
+/* arguments come directly from milter_create(). The parent
+/* argument specifies a context for content editing.
+/*
+/* milter8_receive() receives a mail filter definition from the
+/* specified stream. The result is zero in case of success.
+/*
+/* Arguments:
+/* .IP name
+/* The Milter application endpoint, either inet:host:port or
+/* unix:/pathname.
+/* DIAGNOSTICS
+/* Panic: interface violation. Fatal errors: out of memory.
+/* CONFIGURATION PARAMETERS
+/* milter8_protocol, protocol version and extensions
+/* SEE ALSO
+/* milter(3) generic Milter interface
+/* 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stddef.h> /* offsetof() */
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h> /* INT_MAX */
+
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <connect.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <compat_va_copy.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <rec_type.h>
+#include <record.h>
+#include <mime_state.h>
+#include <is_header.h>
+
+/* Postfix Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+ /*
+ * Use our own protocol definitions, so that Postfix can be built even when
+ * libmilter is not installed. This means that we must specify the libmilter
+ * protocol version in main.cf, and that we must send only the commands that
+ * are supported for that protocol version.
+ */
+
+ /*
+ * Commands from MTA to filter.
+ */
+#define SMFIC_ABORT 'A' /* Abort */
+#define SMFIC_BODY 'B' /* Body chunk */
+#define SMFIC_CONNECT 'C' /* Connection information */
+#define SMFIC_MACRO 'D' /* Define macro */
+#define SMFIC_BODYEOB 'E' /* final body chunk (End) */
+#define SMFIC_HELO 'H' /* HELO/EHLO */
+#define SMFIC_HEADER 'L' /* Header */
+#define SMFIC_MAIL 'M' /* MAIL from */
+#define SMFIC_EOH 'N' /* EOH */
+#define SMFIC_OPTNEG 'O' /* Option negotiation */
+#define SMFIC_QUIT 'Q' /* QUIT */
+#define SMFIC_RCPT 'R' /* RCPT to */
+#define SMFIC_DATA 'T' /* DATA */
+#define SMFIC_UNKNOWN 'U' /* Any unknown command */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIC_QUIT_NC 'K' /* Quit + new connection */
+
+static const NAME_CODE smfic_table[] = {
+ "SMFIC_ABORT", SMFIC_ABORT,
+ "SMFIC_BODY", SMFIC_BODY,
+ "SMFIC_CONNECT", SMFIC_CONNECT,
+ "SMFIC_MACRO", SMFIC_MACRO,
+ "SMFIC_BODYEOB", SMFIC_BODYEOB,
+ "SMFIC_HELO", SMFIC_HELO,
+ "SMFIC_HEADER", SMFIC_HEADER,
+ "SMFIC_MAIL", SMFIC_MAIL,
+ "SMFIC_EOH", SMFIC_EOH,
+ "SMFIC_OPTNEG", SMFIC_OPTNEG,
+ "SMFIC_QUIT", SMFIC_QUIT,
+ "SMFIC_RCPT", SMFIC_RCPT,
+ "SMFIC_DATA", SMFIC_DATA,
+ "SMFIC_UNKNOWN", SMFIC_UNKNOWN,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIC_QUIT_NC", SMFIC_QUIT_NC,
+ 0, 0,
+};
+
+ /*
+ * Responses from filter to MTA.
+ */
+#define SMFIR_ADDRCPT '+' /* add recipient */
+#define SMFIR_DELRCPT '-' /* remove recipient */
+#define SMFIR_ACCEPT 'a' /* accept */
+#define SMFIR_REPLBODY 'b' /* replace body (chunk) */
+#define SMFIR_CONTINUE 'c' /* continue */
+#define SMFIR_DISCARD 'd' /* discard */
+#define SMFIR_CONN_FAIL 'f' /* cause a connection failure */
+#define SMFIR_CHGHEADER 'm' /* change header */
+#define SMFIR_PROGRESS 'p' /* progress */
+#define SMFIR_REJECT 'r' /* reject */
+#define SMFIR_TEMPFAIL 't' /* tempfail */
+#define SMFIR_SHUTDOWN '4' /* 421: shutdown (internal to MTA) */
+#define SMFIR_ADDHEADER 'h' /* add header */
+#define SMFIR_INSHEADER 'i' /* insert header */
+#define SMFIR_REPLYCODE 'y' /* reply code etc */
+#define SMFIR_QUARANTINE 'q' /* quarantine */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIR_SKIP 's' /* skip further events of this type */
+#define SMFIR_CHGFROM 'e' /* change sender (incl. ESMTP args) */
+#define SMFIR_ADDRCPT_PAR '2' /* add recipient (incl. ESMTP args) */
+#define SMFIR_SETSYMLIST 'l' /* set list of symbols (macros) */
+
+static const NAME_CODE smfir_table[] = {
+ "SMFIR_ADDRCPT", SMFIR_ADDRCPT,
+ "SMFIR_DELRCPT", SMFIR_DELRCPT,
+ "SMFIR_ACCEPT", SMFIR_ACCEPT,
+ "SMFIR_REPLBODY", SMFIR_REPLBODY,
+ "SMFIR_CONTINUE", SMFIR_CONTINUE,
+ "SMFIR_DISCARD", SMFIR_DISCARD,
+ "SMFIR_CONN_FAIL", SMFIR_CONN_FAIL,
+ "SMFIR_CHGHEADER", SMFIR_CHGHEADER,
+ "SMFIR_PROGRESS", SMFIR_PROGRESS,
+ "SMFIR_REJECT", SMFIR_REJECT,
+ "SMFIR_TEMPFAIL", SMFIR_TEMPFAIL,
+ "SMFIR_SHUTDOWN", SMFIR_SHUTDOWN,
+ "SMFIR_ADDHEADER", SMFIR_ADDHEADER,
+ "SMFIR_INSHEADER", SMFIR_INSHEADER,
+ "SMFIR_REPLYCODE", SMFIR_REPLYCODE,
+ "SMFIR_QUARANTINE", SMFIR_QUARANTINE,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIR_SKIP", SMFIR_SKIP,
+ "SMFIR_CHGFROM", SMFIR_CHGFROM,
+ "SMFIR_ADDRCPT_PAR", SMFIR_ADDRCPT_PAR,
+ "SMFIR_SETSYMLIST", SMFIR_SETSYMLIST,
+ 0, 0,
+};
+
+ /*
+ * Commands that the filter does not want to receive, and replies that the
+ * filter will not send. Plus some other random stuff.
+ */
+#define SMFIP_NOCONNECT (1L<<0) /* filter does not want connect info */
+#define SMFIP_NOHELO (1L<<1) /* filter does not want HELO info */
+#define SMFIP_NOMAIL (1L<<2) /* filter does not want MAIL info */
+#define SMFIP_NORCPT (1L<<3) /* filter does not want RCPT info */
+#define SMFIP_NOBODY (1L<<4) /* filter does not want body */
+#define SMFIP_NOHDRS (1L<<5) /* filter does not want headers */
+#define SMFIP_NOEOH (1L<<6) /* filter does not want EOH */
+#define SMFIP_NR_HDR (1L<<7) /* filter won't reply for header */
+#define SMFIP_NOHREPL SMFIP_NR_HDR
+#define SMFIP_NOUNKNOWN (1L<<8) /* filter does not want unknown cmd */
+#define SMFIP_NODATA (1L<<9) /* filter does not want DATA */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIP_SKIP (1L<<10)/* MTA supports SMFIR_SKIP */
+#define SMFIP_RCPT_REJ (1L<<11)/* filter wants rejected RCPTs */
+#define SMFIP_NR_CONN (1L<<12)/* filter won't reply for connect */
+#define SMFIP_NR_HELO (1L<<13)/* filter won't reply for HELO */
+#define SMFIP_NR_MAIL (1L<<14)/* filter won't reply for MAIL */
+#define SMFIP_NR_RCPT (1L<<15)/* filter won't reply for RCPT */
+#define SMFIP_NR_DATA (1L<<16)/* filter won't reply for DATA */
+#define SMFIP_NR_UNKN (1L<<17)/* filter won't reply for UNKNOWN */
+#define SMFIP_NR_EOH (1L<<18)/* filter won't reply for eoh */
+#define SMFIP_NR_BODY (1L<<19)/* filter won't reply for body chunk */
+#define SMFIP_HDR_LEADSPC (1L<<20)/* header value has leading space */
+
+#define SMFIP_NOSEND_MASK \
+ (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT \
+ | SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH | SMFIP_NOUNKNOWN \
+ | SMFIP_NODATA)
+
+#define SMFIP_NOREPLY_MASK \
+ (SMFIP_NR_CONN | SMFIP_NR_HELO | SMFIP_NR_MAIL | SMFIP_NR_RCPT \
+ | SMFIP_NR_DATA | SMFIP_NR_UNKN | SMFIP_NR_HDR | SMFIP_NR_EOH | \
+ SMFIP_NR_BODY)
+
+static const NAME_MASK smfip_table[] = {
+ "SMFIP_NOCONNECT", SMFIP_NOCONNECT,
+ "SMFIP_NOHELO", SMFIP_NOHELO,
+ "SMFIP_NOMAIL", SMFIP_NOMAIL,
+ "SMFIP_NORCPT", SMFIP_NORCPT,
+ "SMFIP_NOBODY", SMFIP_NOBODY,
+ "SMFIP_NOHDRS", SMFIP_NOHDRS,
+ "SMFIP_NOEOH", SMFIP_NOEOH,
+ "SMFIP_NR_HDR", SMFIP_NR_HDR,
+ "SMFIP_NOUNKNOWN", SMFIP_NOUNKNOWN,
+ "SMFIP_NODATA", SMFIP_NODATA,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIP_SKIP", SMFIP_SKIP,
+ "SMFIP_RCPT_REJ", SMFIP_RCPT_REJ,
+ "SMFIP_NR_CONN", SMFIP_NR_CONN,
+ "SMFIP_NR_HELO", SMFIP_NR_HELO,
+ "SMFIP_NR_MAIL", SMFIP_NR_MAIL,
+ "SMFIP_NR_RCPT", SMFIP_NR_RCPT,
+ "SMFIP_NR_DATA", SMFIP_NR_DATA,
+ "SMFIP_NR_UNKN", SMFIP_NR_UNKN,
+ "SMFIP_NR_EOH", SMFIP_NR_EOH,
+ "SMFIP_NR_BODY", SMFIP_NR_BODY,
+ "SMFIP_HDR_LEADSPC", SMFIP_HDR_LEADSPC,
+ 0, 0,
+};
+
+ /*
+ * Options that the filter may send at initial handshake time, and message
+ * modifications that the filter may request at the end of the message body.
+ */
+#define SMFIF_ADDHDRS (1L<<0) /* filter may add headers */
+#define SMFIF_CHGBODY (1L<<1) /* filter may replace body */
+#define SMFIF_ADDRCPT (1L<<2) /* filter may add recipients */
+#define SMFIF_DELRCPT (1L<<3) /* filter may delete recipients */
+#define SMFIF_CHGHDRS (1L<<4) /* filter may change/delete headers */
+#define SMFIF_QUARANTINE (1L<<5) /* filter may quarantine envelope */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIF_CHGFROM (1L<<6) /* filter may replace sender */
+#define SMFIF_ADDRCPT_PAR (1L<<7) /* filter may add recipients + args */
+#define SMFIF_SETSYMLIST (1L<<8) /* filter may send macro names */
+
+static const NAME_MASK smfif_table[] = {
+ "SMFIF_ADDHDRS", SMFIF_ADDHDRS,
+ "SMFIF_CHGBODY", SMFIF_CHGBODY,
+ "SMFIF_ADDRCPT", SMFIF_ADDRCPT,
+ "SMFIF_DELRCPT", SMFIF_DELRCPT,
+ "SMFIF_CHGHDRS", SMFIF_CHGHDRS,
+ "SMFIF_QUARANTINE", SMFIF_QUARANTINE,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIF_CHGFROM", SMFIF_CHGFROM,
+ "SMFIF_ADDRCPT_PAR", SMFIF_ADDRCPT_PAR,
+ "SMFIF_SETSYMLIST", SMFIF_SETSYMLIST,
+ 0, 0,
+};
+
+ /*
+ * Network protocol families, used when sending CONNECT information.
+ */
+#define SMFIA_UNKNOWN 'U' /* unknown */
+#define SMFIA_UNIX 'L' /* unix/local */
+#define SMFIA_INET '4' /* inet */
+#define SMFIA_INET6 '6' /* inet6 */
+
+ /*
+ * External macro class numbers, to identify the optional macro name lists
+ * that may be sent after the initial negotiation header.
+ */
+#define SMFIM_CONNECT 0 /* macros for connect */
+#define SMFIM_HELO 1 /* macros for HELO */
+#define SMFIM_ENVFROM 2 /* macros for MAIL */
+#define SMFIM_ENVRCPT 3 /* macros for RCPT */
+#define SMFIM_DATA 4 /* macros for DATA */
+#define SMFIM_EOM 5 /* macros for end-of-message */
+#define SMFIM_EOH 6 /* macros for end-of-header */
+
+static const NAME_CODE smfim_table[] = {
+ "SMFIM_CONNECT", SMFIM_CONNECT,
+ "SMFIM_HELO", SMFIM_HELO,
+ "SMFIM_ENVFROM", SMFIM_ENVFROM,
+ "SMFIM_ENVRCPT", SMFIM_ENVRCPT,
+ "SMFIM_DATA", SMFIM_DATA,
+ "SMFIM_EOM", SMFIM_EOM,
+ "SMFIM_EOH", SMFIM_EOH,
+ 0, 0,
+};
+
+ /*
+ * Mapping from external macro class numbers to our internal MILTER_MACROS
+ * structure members, without using a switch statement.
+ */
+static const size_t milter8_macro_offsets[] = {
+ offsetof(MILTER_MACROS, conn_macros), /* SMFIM_CONNECT */
+ offsetof(MILTER_MACROS, helo_macros), /* SMFIM_HELO */
+ offsetof(MILTER_MACROS, mail_macros), /* SMFIM_ENVFROM */
+ offsetof(MILTER_MACROS, rcpt_macros), /* SMFIM_ENVRCPT */
+ offsetof(MILTER_MACROS, data_macros), /* SMFIM_DATA */
+ offsetof(MILTER_MACROS, eod_macros),/* Note: SMFIM_EOM < SMFIM_EOH */
+ offsetof(MILTER_MACROS, eoh_macros),/* Note: SMFIM_EOH > SMFIM_EOM */
+};
+
+#define MILTER8_MACRO_PTR(__macros, __class) \
+ ((char **) (((char *) (__macros)) + milter8_macro_offsets[(__class)]))
+
+ /*
+ * How much buffer space is available for sending body content.
+ */
+#define MILTER_CHUNK_SIZE 65535 /* body chunk size */
+
+/*#define msg_verbose 2*/
+
+ /*
+ * Sendmail 8 mail filter client.
+ */
+typedef struct {
+ MILTER m; /* parent class */
+ int conn_timeout; /* connect timeout */
+ int cmd_timeout; /* per-command timeout */
+ int msg_timeout; /* content inspection timeout */
+ char *protocol; /* protocol version/extension */
+ char *def_action; /* action if unavailable */
+ int version; /* application protocol version */
+ int rq_mask; /* application requests (SMFIF_*) */
+ int ev_mask; /* application events (SMFIP_*) */
+ int np_mask; /* events outside my protocol version */
+ VSTRING *buf; /* I/O buffer */
+ VSTRING *body; /* I/O buffer */
+ VSTREAM *fp; /* stream or null (closed) */
+
+ /*
+ * Following fields must be reset after successful CONNECT, to avoid
+ * leakage from one use to another.
+ */
+ int state; /* MILTER8_STAT_mumble */
+ char *def_reply; /* error response or null */
+ int skip_event_type; /* skip operations of this type */
+} MILTER8;
+
+ /*
+ * XXX Sendmail 8 libmilter automatically closes the MTA-to-filter socket
+ * when it finds out that the SMTP client has disconnected. Because of this
+ * behavior, Postfix has to open a new MTA-to-filter socket each time an
+ * SMTP client connects.
+ */
+#define LIBMILTER_AUTO_DISCONNECT
+
+ /*
+ * Milter internal state. For the external representation we use SMTP
+ * replies (4XX X.Y.Z text, 5XX X.Y.Z text) and one-letter strings
+ * (H=quarantine, D=discard, S=shutdown).
+ */
+#define MILTER8_STAT_ERROR 1 /* error, must be non-zero */
+#define MILTER8_STAT_CLOSED 2 /* no connection */
+#define MILTER8_STAT_READY 3 /* wait for connect event */
+#define MILTER8_STAT_ENVELOPE 4 /* in envelope */
+#define MILTER8_STAT_MESSAGE 5 /* in message */
+#define MILTER8_STAT_ACCEPT_CON 6 /* accept all commands */
+#define MILTER8_STAT_ACCEPT_MSG 7 /* accept one message */
+#define MILTER8_STAT_REJECT_CON 8 /* reject all commands */
+
+ /*
+ * Protocol formatting requests. Note: the terms "long" and "short" refer to
+ * the data types manipulated by htonl(), htons() and friends. These types
+ * are network specific, not host platform specific.
+ */
+#define MILTER8_DATA_END 0 /* no more arguments */
+#define MILTER8_DATA_HLONG 1 /* host long */
+#define MILTER8_DATA_BUFFER 2 /* network-formatted buffer */
+#define MILTER8_DATA_STRING 3 /* null-terminated string */
+#define MILTER8_DATA_NSHORT 4 /* network short */
+#define MILTER8_DATA_ARGV 5 /* array of null-terminated strings */
+#define MILTER8_DATA_OCTET 6 /* byte */
+#define MILTER8_DATA_MORE 7 /* more arguments in next call */
+
+ /*
+ * We don't accept insane amounts of data.
+ */
+#define XXX_MAX_DATA (INT_MAX / 2)
+#define XXX_TIMEOUT 10
+
+ /*
+ * We implement the protocol up to and including version 6, and configure in
+ * main.cf what protocol version we will use. The version is the first data
+ * item in the SMFIC_OPTNEG packet.
+ *
+ * We must send only events that are defined for the specified protocol
+ * version. Libmilter may disconnect when we send unexpected events.
+ *
+ * The following events are supported in all our milter protocol versions.
+ */
+#define MILTER8_V2_PROTO_MASK \
+ (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT | \
+ SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH)
+
+ /*
+ * Events supported by later versions.
+ */
+#define MILTER8_V3_PROTO_MASK (MILTER8_V2_PROTO_MASK | SMFIP_NOUNKNOWN)
+#define MILTER8_V4_PROTO_MASK (MILTER8_V3_PROTO_MASK | SMFIP_NODATA)
+#define MILTER8_V6_PROTO_MASK \
+ (MILTER8_V4_PROTO_MASK | SMFIP_SKIP | SMFIP_RCPT_REJ \
+ | SMFIP_NOREPLY_MASK | SMFIP_HDR_LEADSPC)
+
+ /*
+ * What events we can send to the milter application. The milter8_protocol
+ * parameter can specify a protocol version as well as protocol extensions
+ * such as "no_header_reply", a feature that speeds up the protocol by not
+ * sending a filter reply for every individual message header.
+ *
+ * This looks unclean because the user can specify multiple protocol versions,
+ * but that is taken care of by the table that follows this one.
+ *
+ * XXX Is this still needed? Sendmail 8.14 provides a proper way to negotiate
+ * what replies the mail filter will send.
+ *
+ * XXX Keep this table in reverse numerical order. This is needed by the code
+ * that implements compatibility with older Milter protocol versions.
+ */
+static const NAME_CODE milter8_event_masks[] = {
+ "6", MILTER8_V6_PROTO_MASK,
+ "4", MILTER8_V4_PROTO_MASK,
+ "3", MILTER8_V3_PROTO_MASK,
+ "2", MILTER8_V2_PROTO_MASK,
+ "no_header_reply", SMFIP_NOHREPL,
+ 0, -1,
+};
+
+ /*
+ * The following table lets us use the same milter8_protocol parameter
+ * setting to derive the protocol version number. In this case we ignore
+ * protocol extensions such as "no_header_reply", and require that exactly
+ * one version number is specified.
+ */
+static const NAME_CODE milter8_versions[] = {
+ "2", 2,
+ "3", 3,
+ "4", 4,
+ "6", 6,
+ "no_header_reply", 0,
+ 0, -1,
+};
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* milter8_def_reply - set persistent response */
+
+static const char *milter8_def_reply(MILTER8 *milter, const char *reply)
+{
+ if (milter->def_reply)
+ myfree(milter->def_reply);
+ milter->def_reply = reply ? mystrdup(reply) : 0;
+ return (milter->def_reply);
+}
+
+/* milter8_conf_error - local/remote configuration error */
+
+static int milter8_conf_error(MILTER8 *milter)
+{
+ const char *reply;
+
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
+ if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ if (strcasecmp(milter->def_action, "accept") == 0) {
+ reply = 0;
+ } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
+ reply = "H";
+ } else {
+ reply = "451 4.3.5 Server configuration problem - try again later";
+ }
+ milter8_def_reply(milter, reply);
+ return (milter->state = MILTER8_STAT_ERROR);
+}
+
+/* milter8_comm_error - read/write/format communication error */
+
+static int milter8_comm_error(MILTER8 *milter)
+{
+ const char *reply;
+
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
+ if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ if (strcasecmp(milter->def_action, "accept") == 0) {
+ reply = 0;
+ } else if (strcasecmp(milter->def_action, "reject") == 0) {
+ reply = "550 5.5.0 Service unavailable";
+ } else if (strcasecmp(milter->def_action, "tempfail") == 0) {
+ reply = "451 4.7.1 Service unavailable - try again later";
+ } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
+ reply = "H";
+ } else {
+ msg_warn("milter %s: unrecognized default action: %s",
+ milter->m.name, milter->def_action);
+ reply = "451 4.3.5 Server configuration problem - try again later";
+ }
+ milter8_def_reply(milter, reply);
+ return (milter->state = MILTER8_STAT_ERROR);
+}
+
+/* milter8_close_stream - close stream to milter application */
+
+static void milter8_close_stream(MILTER8 *milter)
+{
+ if (milter->fp != 0) {
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ milter->state = MILTER8_STAT_CLOSED;
+}
+
+/* milter8_read_resp - receive command code now, receive data later */
+
+static int milter8_read_resp(MILTER8 *milter, int event, unsigned char *command,
+ ssize_t *data_len)
+{
+ UINT32_TYPE len;
+ ssize_t pkt_len;
+ const char *smfic_name;
+ int cmd;
+
+ /*
+ * Receive the packet length.
+ */
+ if ((vstream_fread(milter->fp, (void *) &len, UINT32_SIZE))
+ != UINT32_SIZE) {
+ smfic_name = str_name_code(smfic_table, event);
+ msg_warn("milter %s: can't read %s reply packet header: %m",
+ milter->m.name, smfic_name != 0 ?
+ smfic_name : "(unknown MTA event)");
+ return (milter8_comm_error(milter));
+ } else if ((pkt_len = ntohl(len)) < 1) {
+ msg_warn("milter %s: bad packet length: %ld",
+ milter->m.name, (long) pkt_len);
+ return (milter8_comm_error(milter));
+ } else if (pkt_len > XXX_MAX_DATA) {
+ msg_warn("milter %s: unreasonable packet length: %ld > %ld",
+ milter->m.name, (long) pkt_len, (long) XXX_MAX_DATA);
+ return (milter8_comm_error(milter));
+ }
+
+ /*
+ * Receive the command code.
+ */
+ else if ((cmd = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
+ msg_warn("milter %s: EOF while reading command code: %m",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+
+ /*
+ * All is well.
+ */
+ else {
+ *command = cmd;
+ *data_len = pkt_len - 1;
+ return (0);
+ }
+}
+
+static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...);
+
+/* vmilter8_read_data - read command data */
+
+static int vmilter8_read_data(MILTER8 *milter, ssize_t *data_len, va_list ap)
+{
+ const char *myname = "milter8_read_data";
+ int arg_type;
+ UINT32_TYPE net_long;
+ UINT32_TYPE *host_long_ptr;
+ VSTRING *buf;
+ int ch;
+
+ while ((arg_type = va_arg(ap, int)) > 0 && arg_type != MILTER8_DATA_MORE) {
+ switch (arg_type) {
+
+ /*
+ * Host order long.
+ */
+ case MILTER8_DATA_HLONG:
+ if (*data_len < UINT32_SIZE) {
+ msg_warn("milter %s: input packet too short for network long",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ host_long_ptr = va_arg(ap, UINT32_TYPE *);
+ if (vstream_fread(milter->fp, (void *) &net_long, UINT32_SIZE)
+ != UINT32_SIZE) {
+ msg_warn("milter %s: EOF while reading network long: %m",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len -= UINT32_SIZE;
+ *host_long_ptr = ntohl(net_long);
+ break;
+
+ /*
+ * Raw on-the-wire format, without explicit null terminator.
+ */
+ case MILTER8_DATA_BUFFER:
+ if (*data_len < 0) {
+ msg_warn("milter %s: no data in input packet", milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ buf = va_arg(ap, VSTRING *);
+ if (vstream_fread_buf(milter->fp, buf, *data_len)
+ != *data_len) {
+ msg_warn("milter %s: EOF while reading data: %m", milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len = 0;
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ if (*data_len < 1) {
+ msg_warn("milter %s: packet too short for string",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ buf = va_arg(ap, VSTRING *);
+ VSTRING_RESET(buf);
+ for (;;) {
+ if ((ch = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
+ msg_warn("%s: milter %s: EOF while reading string: %m",
+ myname, milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len -= 1;
+ if (ch == 0)
+ break;
+ VSTRING_ADDCH(buf, ch);
+ if (*data_len <= 0) {
+ msg_warn("%s: milter %s: missing string null termimator",
+ myname, milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ }
+ VSTRING_TERMINATE(buf);
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, arg_type);
+ }
+ }
+
+ /*
+ * Sanity checks. We may have excess data when the sender is confused. We
+ * may have a negative count when we're confused ourselves.
+ */
+ if (arg_type != MILTER8_DATA_MORE && *data_len > 0) {
+ msg_warn("%s: left-over data %ld bytes", myname, (long) *data_len);
+ return (milter8_comm_error(milter));
+ }
+ if (*data_len < 0)
+ msg_panic("%s: bad left-over data count %ld",
+ myname, (long) *data_len);
+ return (0);
+}
+
+/* milter8_read_data - read command data */
+
+static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, data_len);
+ ret = vmilter8_read_data(milter, data_len, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* vmilter8_size_data - compute command data length */
+
+static ssize_t vmilter8_size_data(va_list ap)
+{
+ const char *myname = "vmilter8_size_data";
+ ssize_t data_len;
+ int arg_type;
+ VSTRING *buf;
+ const char *str;
+ const char **cpp;
+
+ /*
+ * Compute data size.
+ */
+ for (data_len = 0; (arg_type = va_arg(ap, int)) > 0; /* void */ ) {
+ switch (arg_type) {
+
+ /*
+ * Host order long.
+ */
+ case MILTER8_DATA_HLONG:
+ (void) va_arg(ap, UINT32_TYPE);
+ data_len += UINT32_SIZE;
+ break;
+
+ /*
+ * Raw on-the-wire format.
+ */
+ case MILTER8_DATA_BUFFER:
+ buf = va_arg(ap, VSTRING *);
+ data_len += LEN(buf);
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ str = va_arg(ap, char *);
+ data_len += strlen(str) + 1;
+ break;
+
+ /*
+ * Array of pointers to null-terminated strings.
+ */
+ case MILTER8_DATA_ARGV:
+ for (cpp = va_arg(ap, const char **); *cpp; cpp++)
+ data_len += strlen(*cpp) + 1;
+ break;
+
+ /*
+ * Network order short, promoted to int.
+ */
+ case MILTER8_DATA_NSHORT:
+ (void) va_arg(ap, unsigned);
+ data_len += UINT16_SIZE;
+ break;
+
+ /*
+ * Octet, promoted to int.
+ */
+ case MILTER8_DATA_OCTET:
+ (void) va_arg(ap, unsigned);
+ data_len += 1;
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: bad argument type: %d", myname, arg_type);
+ }
+ }
+ va_end(ap);
+ return (data_len);
+}
+
+/* vmilter8_write_cmd - write command to Sendmail 8 Milter */
+
+static int vmilter8_write_cmd(MILTER8 *milter, int command, ssize_t data_len,
+ va_list ap)
+{
+ const char *myname = "vmilter8_write_cmd";
+ int arg_type;
+ UINT32_TYPE pkt_len;
+ UINT32_TYPE host_long;
+ UINT32_TYPE net_long;
+ UINT16_TYPE net_short;
+ VSTRING *buf;
+ const char *str;
+ const char **cpp;
+ char ch;
+
+ /*
+ * Deliver the packet.
+ */
+ if ((pkt_len = 1 + data_len) < 1)
+ msg_panic("%s: bad packet length %d", myname, pkt_len);
+ pkt_len = htonl(pkt_len);
+ (void) vstream_fwrite(milter->fp, (void *) &pkt_len, UINT32_SIZE);
+ (void) VSTREAM_PUTC(command, milter->fp);
+ while ((arg_type = va_arg(ap, int)) > 0) {
+ switch (arg_type) {
+
+ /*
+ * Network long.
+ */
+ case MILTER8_DATA_HLONG:
+ host_long = va_arg(ap, UINT32_TYPE);
+ net_long = htonl(host_long);
+ (void) vstream_fwrite(milter->fp, (void *) &net_long, UINT32_SIZE);
+ break;
+
+ /*
+ * Raw on-the-wire format.
+ */
+ case MILTER8_DATA_BUFFER:
+ buf = va_arg(ap, VSTRING *);
+ (void) vstream_fwrite(milter->fp, STR(buf), LEN(buf));
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ str = va_arg(ap, char *);
+ (void) vstream_fwrite(milter->fp, str, strlen(str) + 1);
+ break;
+
+ /*
+ * Octet, promoted to int.
+ */
+ case MILTER8_DATA_OCTET:
+ ch = va_arg(ap, unsigned);
+ (void) vstream_fwrite(milter->fp, &ch, 1);
+ break;
+
+ /*
+ * Array of pointers to null-terminated strings.
+ */
+ case MILTER8_DATA_ARGV:
+ for (cpp = va_arg(ap, const char **); *cpp; cpp++)
+ (void) vstream_fwrite(milter->fp, *cpp, strlen(*cpp) + 1);
+ break;
+
+ /*
+ * Network order short, promoted to int.
+ */
+ case MILTER8_DATA_NSHORT:
+ net_short = va_arg(ap, unsigned);
+ (void) vstream_fwrite(milter->fp, (void *) &net_short, UINT16_SIZE);
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: bad argument type: %d", myname, arg_type);
+ }
+
+ /*
+ * Report errors immediately.
+ */
+ if (vstream_ferror(milter->fp)) {
+ msg_warn("milter %s: error writing command: %m", milter->m.name);
+ milter8_comm_error(milter);
+ break;
+ }
+ }
+ va_end(ap);
+ return (milter->state == MILTER8_STAT_ERROR);
+}
+
+/* milter8_write_cmd - write command to Sendmail 8 Milter */
+
+static int milter8_write_cmd(MILTER8 *milter, int command,...)
+{
+ va_list ap;
+ va_list ap2;
+ ssize_t data_len;
+ int err;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, command);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Size the command data.
+ */
+ data_len = vmilter8_size_data(ap);
+ va_end(ap);
+
+ /*
+ * Send the command and data.
+ */
+ err = vmilter8_write_cmd(milter, command, data_len, ap2);
+ va_end(ap2);
+
+ return (err);
+}
+
+/* milter8_event - report event and receive reply */
+
+static const char *milter8_event(MILTER8 *milter, int event,
+ int skip_event_flag,
+ int skip_reply,
+ ARGV *macros,...)
+{
+ const char *myname = "milter8_event";
+ va_list ap;
+ va_list ap2;
+ ssize_t data_len;
+ int err;
+ unsigned char cmd;
+ ssize_t data_size;
+ const char *smfic_name;
+ const char *smfir_name;
+ MILTERS *parent = milter->m.parent;
+ UINT32_TYPE index;
+ const char *edit_resp = 0;
+ const char *retval = 0;
+ VSTRING *body_line_buf = 0;
+ int done = 0;
+ int body_edit_lockout = 0;
+
+#define DONT_SKIP_REPLY 0
+
+ /*
+ * Sanity check.
+ */
+ if (milter->fp == 0 || milter->def_reply != 0) {
+ msg_warn("%s: attempt to send event %s to milter %s after error",
+ myname,
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip this event if it doesn't exist in the protocol that I announced.
+ */
+ if ((skip_event_flag & milter->np_mask) != 0) {
+ if (msg_verbose)
+ msg_info("skipping non-protocol event %s for milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip further events of this type if the filter told us so.
+ */
+ if (milter->skip_event_type != 0) {
+ if (event == milter->skip_event_type) {
+ if (msg_verbose)
+ msg_info("skipping event %s after SMFIR_SKIP from milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ } else {
+ milter->skip_event_type = 0;
+ }
+ }
+
+ /*
+ * Send the macros for this event, even when we're not reporting the
+ * event itself. This does not introduce a performance problem because
+ * we're sending macros and event parameters in one VSTREAM transaction.
+ *
+ * XXX Is this still necessary?
+ */
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ if (macros) {
+ if (macros->argc > 0) {
+ char **cpp;
+
+ for (cpp = macros->argv; *cpp && cpp[1]; cpp += 2)
+ vstring_sprintf_append(buf, " %s=%s", *cpp, cpp[1]);
+ }
+ }
+ msg_info("event: %s; macros:%s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", *STR(buf) ?
+ STR(buf) : " (none)");
+ vstring_free(buf);
+ }
+ if (macros) {
+ if (milter8_write_cmd(milter, SMFIC_MACRO,
+ MILTER8_DATA_OCTET, event,
+ MILTER8_DATA_ARGV, macros->argv,
+ MILTER8_DATA_END) != 0)
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip this event if the Milter told us not to send it.
+ */
+ if ((skip_event_flag & milter->ev_mask) != 0) {
+ if (msg_verbose)
+ msg_info("skipping event %s for milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, macros);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Compute the command data size. This is necessary because the protocol
+ * sends length before content.
+ */
+ data_len = vmilter8_size_data(ap);
+ va_end(ap);
+
+ /*
+ * Send the command and data.
+ */
+ err = vmilter8_write_cmd(milter, event, data_len, ap2);
+ va_end(ap2);
+
+ /*
+ * C99 requires that we finalize argument lists before returning.
+ */
+ if (err != 0)
+ return (milter->def_reply);
+
+ /*
+ * Special feature: don't wait for one reply per header. This allows us
+ * to send multiple headers in one VSTREAM transaction, and improves
+ * over-all performance.
+ */
+ if (skip_reply) {
+ if (msg_verbose)
+ msg_info("skipping reply for event %s from milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Receive the reply or replies.
+ *
+ * Intercept all loop exits so that we can do post header/body edit
+ * processing.
+ *
+ * XXX Bound the loop iteration count.
+ *
+ * In the end-of-body stage, the Milter may reply with one or more queue
+ * file edit requests before it replies with its final decision: accept,
+ * reject, etc. After a local queue file edit error (file too big, media
+ * write error), do not close the Milter socket in the cleanup server.
+ * Instead skip all further Milter replies until the final decision. This
+ * way the Postfix SMTP server stays in sync with the Milter, and Postfix
+ * doesn't have to lose the ability to handle multiple deliveries within
+ * the same SMTP session. This requires that the Postfix SMTP server uses
+ * something other than CLEANUP_STAT_WRITE when it loses contact with the
+ * cleanup server.
+ */
+#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)
+
+ /*
+ * XXX Don't evaluate this macro's argument multiple times. Since we use
+ * "continue" the macro can't be enclosed in do .. while (0).
+ */
+#define MILTER8_EVENT_BREAK(s) { \
+ retval = (s); \
+ done = 1; \
+ continue; \
+ }
+
+ while (done == 0) {
+ char *cp;
+ char *rp;
+ char ch;
+ char *next;
+
+ if (milter8_read_resp(milter, event, &cmd, &data_size) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (msg_verbose)
+ msg_info("reply: %s data %ld bytes",
+ (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "unknown", (long) data_size);
+
+ /*
+ * Handle unfinished message body replacement first.
+ *
+ * XXX When SMFIR_REPLBODY is followed by some different request, we
+ * assume that the body replacement operation is complete. The queue
+ * file editing implementation currently does not support sending
+ * part 1 of the body replacement text, doing some other queue file
+ * updates, and then sending part 2 of the body replacement text. To
+ * avoid loss of data, we log an error when SMFIR_REPLBODY requests
+ * are alternated with other requests.
+ */
+ if (body_line_buf != 0 && cmd != SMFIR_REPLBODY) {
+ /* In case the last body replacement line didn't end in CRLF. */
+ if (edit_resp == 0 && LEN(body_line_buf) > 0)
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_NORM,
+ body_line_buf);
+ if (edit_resp == 0)
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_END,
+ /* unused*/ 0,
+ (VSTRING *) 0);
+ body_edit_lockout = 1;
+ vstring_free(body_line_buf);
+ body_line_buf = 0;
+ }
+ switch (cmd) {
+
+ /*
+ * Still working on it.
+ */
+ case SMFIR_PROGRESS:
+ if (data_size != 0)
+ break;
+ continue;
+
+ /*
+ * Decision: continue processing.
+ */
+ case SMFIR_CONTINUE:
+ if (data_size != 0)
+ break;
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Decision: accept this message, or accept all further commands
+ * in this SMTP connection. This decision is final (i.e. Sendmail
+ * 8 changes receiver state).
+ */
+ case SMFIR_ACCEPT:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ /* No more events for this SMTP connection. */
+ milter->state = MILTER8_STAT_ACCEPT_CON;
+ } else {
+ /* No more events for this message. */
+ milter->state = MILTER8_STAT_ACCEPT_MSG;
+ }
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Decision: accept and silently discard this message. According
+ * to the milter API documentation there will be no action when
+ * this is requested by a connection-level function. This
+ * decision is final (i.e. Sendmail 8 changes receiver state).
+ */
+ case SMFIR_DISCARD:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+ msg_warn("milter %s: DISCARD action is not allowed "
+ "for connect or helo", milter->m.name);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ /* No more events for this message. */
+ milter->state = MILTER8_STAT_ACCEPT_MSG;
+ MILTER8_EVENT_BREAK("D");
+ }
+
+ /*
+ * Decision: reject connection, message or recipient. This
+ * decision is final (i.e. Sendmail 8 changes receiver state).
+ */
+ case SMFIR_REJECT:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected"));
+ } else {
+ MILTER8_EVENT_BREAK("550 5.7.1 Command rejected");
+ }
+
+ /*
+ * Decision: tempfail. This decision is final (i.e. Sendmail 8
+ * changes receiver state).
+ */
+ case SMFIR_TEMPFAIL:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter,
+ "451 4.7.1 Service unavailable - try again later"));
+ } else {
+ MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later");
+ }
+
+ /*
+ * Decision: disconnect. This decision is final (i.e. Sendmail 8
+ * changes receiver state).
+ */
+ case SMFIR_SHUTDOWN:
+ if (data_size != 0)
+ break;
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S"));
+
+ /*
+ * Decision: "ddd d.d+.d+ text". This decision is final (i.e.
+ * Sendmail 8 changes receiver state). Note: the reply may be in
+ * multi-line SMTP format.
+ *
+ * XXX Sendmail compatibility: sendmail 8 uses the reply as a format
+ * string; therefore any '%' characters in the reply are doubled.
+ * Postfix doesn't use replies as format strings; we replace '%%'
+ * by '%', and remove single (i.e. invalid) '%' characters.
+ */
+ case SMFIR_REPLYCODE:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* XXX Enforce this for each line of a multi-line reply. */
+ if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5')
+ || !ISDIGIT(STR(milter->buf)[1])
+ || !ISDIGIT(STR(milter->buf)[2])
+ || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-')
+ || (ISDIGIT(STR(milter->buf)[4])
+ && (STR(milter->buf)[4] != STR(milter->buf)[0]))) {
+ msg_warn("milter %s: malformed reply: %s",
+ milter->m.name, STR(milter->buf));
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) {
+ for (;;) {
+ if ((ch = *cp++) == '%')
+ ch = *cp++;
+ *rp++ = ch;
+ if (ch == 0)
+ break;
+ }
+ }
+ if (var_soft_bounce) {
+ for (cp = STR(milter->buf); /* void */ ; cp = next) {
+ if (cp[0] == '5') {
+ cp[0] = '4';
+ if (cp[4] == '5')
+ cp[4] = '4';
+ }
+ if ((next = strstr(cp, "\r\n")) == 0)
+ break;
+ next += 2;
+ }
+ }
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf)));
+ } else {
+ MILTER8_EVENT_BREAK(STR(milter->buf));
+ }
+
+ /*
+ * Decision: quarantine. In Sendmail 8.13 this does not imply a
+ * transition in the receiver state (reply, reject, tempfail,
+ * accept, discard). We should not transition, either, otherwise
+ * we get out of sync.
+ */
+ case SMFIR_QUARANTINE:
+ /* XXX What to do with the "reason" text? */
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ milter8_def_reply(milter, "H");
+ continue;
+
+ /*
+ * Decision: skip further events of this type.
+ */
+ case SMFIR_SKIP:
+ if (data_size != 0)
+ break;
+ milter->skip_event_type = event;
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Modification request or error.
+ */
+ default:
+ if (event == SMFIC_BODYEOB) {
+ switch (cmd) {
+
+#define MILTER8_HDR_SPACE(m) (((m)->ev_mask & SMFIP_HDR_LEADSPC) ? "" : " ")
+
+ /*
+ * Modification request: replace, insert or delete
+ * header. Index 1 means the first instance.
+ */
+ case SMFIR_CHGHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_HLONG, &index,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ /* XXX Sendmail 8 compatibility. */
+ if (index == 0)
+ index = 1;
+ if ((ssize_t) index < 1) {
+ msg_warn("milter %s: bad change header index: %ld",
+ milter->m.name, (long) index);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (LEN(milter->buf) == 0) {
+ msg_warn("milter %s: null change header name",
+ milter->m.name);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (STR(milter->body)[0])
+ edit_resp = parent->upd_header(parent->chg_context,
+ (ssize_t) index,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ else
+ edit_resp = parent->del_header(parent->chg_context,
+ (ssize_t) index,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: append header.
+ */
+ case SMFIR_ADDHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_header(parent->chg_context,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: insert header. With Sendmail 8,
+ * index 0 means the top-most header. We use 1-based
+ * indexing for consistency with header change
+ * operations.
+ */
+ case SMFIR_INSHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_HLONG, &index,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ if ((ssize_t) index + 1 < 1) {
+ msg_warn("milter %s: bad insert header index: %ld",
+ milter->m.name, (long) index);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ edit_resp = parent->ins_header(parent->chg_context,
+ (ssize_t) index + 1,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: replace sender, with optional
+ * ESMTP args.
+ */
+ case SMFIR_CHGFROM:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_MORE) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (data_size > 0) {
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ VSTRING_RESET(milter->body);
+ VSTRING_TERMINATE(milter->body);
+ }
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->chg_from(parent->chg_context,
+ STR(milter->buf),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: append recipient.
+ */
+ case SMFIR_ADDRCPT:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_rcpt(parent->chg_context,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: append recipient, with optional
+ * ESMTP args.
+ */
+ case SMFIR_ADDRCPT_PAR:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_MORE) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (data_size > 0) {
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ VSTRING_RESET(milter->body);
+ VSTRING_TERMINATE(milter->body);
+ }
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_rcpt_par(parent->chg_context,
+ STR(milter->buf),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: delete (expansion of) recipient.
+ */
+ case SMFIR_DELRCPT:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->del_rcpt(parent->chg_context,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: replace the message body, and
+ * update the message size.
+ */
+ case SMFIR_REPLBODY:
+ if (body_edit_lockout) {
+ msg_warn("milter %s: body replacement requests can't "
+ "currently be mixed with other requests",
+ milter->m.name);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ /* Start body replacement. */
+ if (body_line_buf == 0) {
+ body_line_buf = vstring_alloc(var_line_limit);
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_START,
+ /* unused */ 0,
+ (VSTRING *) 0);
+ }
+ /* Extract lines from the on-the-wire CRLF format. */
+ for (cp = STR(milter->body); edit_resp == 0
+ && cp < vstring_end(milter->body); cp++) {
+ ch = *(unsigned char *) cp;
+ if (ch == '\n') {
+ if (LEN(body_line_buf) > 0
+ && vstring_end(body_line_buf)[-1] == '\r')
+ vstring_truncate(body_line_buf,
+ LEN(body_line_buf) - 1);
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_NORM,
+ body_line_buf);
+ VSTRING_RESET(body_line_buf);
+ } else {
+ /* Preserves \r if not followed by \n. */
+ if (LEN(body_line_buf) == var_line_limit) {
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_CONT,
+ body_line_buf);
+ VSTRING_RESET(body_line_buf);
+ }
+ VSTRING_ADDCH(body_line_buf, ch);
+ }
+ }
+ continue;
+ }
+ }
+ msg_warn("milter %s: unexpected filter response %s after event %s",
+ milter->m.name,
+ (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "(unknown filter reply)",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)");
+ milter8_comm_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+
+ /*
+ * Get here when the reply was followed by data bytes that weren't
+ * supposed to be there.
+ */
+ msg_warn("milter %s: reply %s was followed by %ld data bytes",
+ milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "unknown", (long) data_len);
+ milter8_comm_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+
+ /*
+ * Clean up after aborted message body replacement.
+ */
+ if (body_line_buf)
+ vstring_free(body_line_buf);
+
+ /*
+ * XXX Some cleanup clients ask the cleanup server to bounce mail for
+ * them. In that case we must override a hard reject retval result after
+ * queue file update failure. This is not a big problem; the odds are
+ * small that a Milter application sends a hard reject after replacing
+ * the message body.
+ */
+ if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0))
+ retval = edit_resp;
+ return (retval);
+}
+
+/* milter8_connect - connect to filter */
+
+static void milter8_connect(MILTER8 *milter)
+{
+ const char *myname = "milter8_connect";
+ ssize_t data_len;
+ unsigned char cmd;
+ char *transport;
+ char *endpoint;
+ int (*connect_fn) (const char *, int, int);
+ int fd;
+ const UINT32_TYPE my_actions = (SMFIF_ADDHDRS | SMFIF_ADDRCPT
+ | SMFIF_DELRCPT | SMFIF_CHGHDRS
+ | SMFIF_CHGBODY
+ | SMFIF_QUARANTINE
+ | SMFIF_CHGFROM
+ | SMFIF_ADDRCPT_PAR
+ | SMFIF_SETSYMLIST
+ );
+ UINT32_TYPE my_version = 0;
+ UINT32_TYPE my_events = 0;
+ char *saved_version;
+ char *cp;
+ char *name;
+
+ /*
+ * Sanity check.
+ */
+ if (milter->fp != 0)
+ msg_panic("%s: milter %s: socket is not closed",
+ myname, milter->m.name);
+
+ /*
+ * For user friendliness reasons the milter_protocol configuration
+ * parameter can specify both the protocol version and protocol
+ * extensions (e.g., don't reply for each individual message header).
+ *
+ * The protocol version is sent as is to the milter application.
+ *
+ * The version and extensions determine what events we can send to the
+ * milter application.
+ *
+ * We don't announce support for events that aren't defined for my protocol
+ * version. Today's libmilter implementations don't seem to care, but we
+ * don't want to take the risk that a future version will be more picky.
+ */
+ cp = saved_version = mystrdup(milter->protocol);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ int mask;
+ int vers;
+
+ if ((mask = name_code(milter8_event_masks,
+ NAME_CODE_FLAG_NONE, name)) == -1
+ || (vers = name_code(milter8_versions,
+ NAME_CODE_FLAG_NONE, name)) == -1
+ || (vers != 0 && my_version != 0)) {
+ msg_warn("milter %s: bad protocol information: %s",
+ milter->m.name, name);
+ milter8_conf_error(milter);
+ return;
+ }
+ if (vers != 0)
+ my_version = vers;
+ my_events |= mask;
+ }
+ myfree(saved_version);
+ if (my_events == 0 || my_version == 0) {
+ msg_warn("milter %s: no protocol version information", milter->m.name);
+ milter8_conf_error(milter);
+ return;
+ }
+
+ /*
+ * Don't send events that aren't defined for my protocol version.
+ */
+ milter->np_mask = (SMFIP_NOSEND_MASK & ~my_events);
+ if (msg_verbose)
+ msg_info("%s: non-protocol events for protocol version %d: %s",
+ myname, my_version,
+ str_name_mask_opt(milter->buf, "non-protocol event mask",
+ smfip_table, milter->np_mask, NAME_MASK_NUMBER));
+
+ /*
+ * Parse the Milter application endpoint.
+ */
+#define FREE_TRANSPORT_AND_BAIL_OUT(milter, milter_error) do { \
+ myfree(transport); \
+ milter_error(milter); \
+ return; \
+ } while (0);
+
+ transport = mystrdup(milter->m.name);
+ if ((endpoint = split_at(transport, ':')) == 0
+ || *endpoint == 0 || *transport == 0) {
+ msg_warn("Milter service needs transport:endpoint instead of \"%s\"",
+ milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
+ }
+ if (msg_verbose)
+ msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
+ if (strcmp(transport, "inet") == 0) {
+ connect_fn = inet_connect;
+ } else if (strcmp(transport, "unix") == 0) {
+ connect_fn = unix_connect;
+ } else if (strcmp(transport, "local") == 0) {
+ connect_fn = LOCAL_CONNECT;
+ } else {
+ msg_warn("invalid transport name: %s in Milter service: %s",
+ transport, milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
+ }
+
+ /*
+ * Connect to the Milter application.
+ */
+ if ((fd = connect_fn(endpoint, BLOCKING, milter->conn_timeout)) < 0) {
+ msg_warn("connect to Milter service %s: %m", milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_comm_error);
+ }
+ myfree(transport);
+ milter->fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
+ CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (connect_fn == inet_connect)
+ vstream_tweak_tcp(milter->fp);
+
+ /*
+ * Open the negotiations by sending what actions the Milter may request
+ * and what events the Milter can receive.
+ */
+ if (msg_verbose) {
+ msg_info("%s: my_version=0x%lx", myname, (long) my_version);
+ msg_info("%s: my_actions=0x%lx %s", myname, (long) my_actions,
+ str_name_mask_opt(milter->buf, "request mask",
+ smfif_table, my_actions, NAME_MASK_NUMBER));
+ msg_info("%s: my_events=0x%lx %s", myname, (long) my_events,
+ str_name_mask_opt(milter->buf, "event mask",
+ smfip_table, my_events, NAME_MASK_NUMBER));
+ }
+ errno = 0;
+ if (milter8_write_cmd(milter, SMFIC_OPTNEG,
+ MILTER8_DATA_HLONG, my_version,
+ MILTER8_DATA_HLONG, my_actions,
+ MILTER8_DATA_HLONG, my_events,
+ MILTER8_DATA_END) != 0) {
+ msg_warn("milter %s: write error in initial handshake",
+ milter->m.name);
+ /* milter8_write_cmd() called milter8_comm_error() */
+ return;
+ }
+
+ /*
+ * Receive the filter's response and verify that we are compatible.
+ */
+ if (milter8_read_resp(milter, SMFIC_OPTNEG, &cmd, &data_len) != 0) {
+ msg_warn("milter %s: read error in initial handshake", milter->m.name);
+ /* milter8_read_resp() called milter8_comm_error() */
+ return;
+ }
+ if (cmd != SMFIC_OPTNEG) {
+ msg_warn("milter %s: unexpected reply \"%c\" in initial handshake",
+ milter->m.name, cmd);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if (milter8_read_data(milter, &data_len,
+ MILTER8_DATA_HLONG, &milter->version,
+ MILTER8_DATA_HLONG, &milter->rq_mask,
+ MILTER8_DATA_HLONG, &milter->ev_mask,
+ MILTER8_DATA_MORE) != 0) {
+ msg_warn("milter %s: read error in initial handshake", milter->m.name);
+ /* milter8_read_data() called milter8_comm_error() */
+ return;
+ }
+ if (milter->version > my_version) {
+ msg_warn("milter %s: protocol version %d conflict"
+ " with MTA protocol version %d",
+ milter->m.name, milter->version, my_version);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if ((milter->rq_mask & my_actions) != milter->rq_mask) {
+ msg_warn("milter %s: request mask 0x%x conflict"
+ " with MTA request mask 0x%lx",
+ milter->m.name, milter->rq_mask, (long) my_actions);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if (milter->ev_mask & SMFIP_RCPT_REJ)
+ milter->m.flags |= MILTER_FLAG_WANT_RCPT_REJ;
+
+ /*
+ * Allow the remote application to run an older protocol version, but
+ * don't them send events that their protocol version doesn't support.
+ * Based on a suggestion by Kouhei Sutou.
+ *
+ * XXX When the Milter sends a protocol version that we don't have
+ * information for, use the information for the next-lower protocol
+ * version instead. This code assumes that the milter8_event_masks table
+ * is organized in reverse numerical order.
+ */
+ if (milter->version < my_version) {
+ const NAME_CODE *np;
+ int version;
+
+ for (np = milter8_event_masks; /* see below */ ; np++) {
+ if (np->name == 0) {
+ msg_warn("milter %s: unexpected protocol version %d",
+ milter->m.name, milter->version);
+ break;
+ }
+ if ((version = atoi(np->name)) > 0 && version <= milter->version) {
+ milter->np_mask |= (SMFIP_NOSEND_MASK & ~np->code);
+ if (msg_verbose)
+ msg_info("%s: non-protocol events for milter %s"
+ " protocol version %d: %s",
+ myname, milter->m.name, milter->version,
+ str_name_mask_opt(milter->buf,
+ "non-protocol event mask",
+ smfip_table, milter->np_mask,
+ NAME_MASK_NUMBER));
+ break;
+ }
+ }
+ }
+
+ /*
+ * Initial negotiations completed.
+ */
+ if (msg_verbose) {
+ if ((milter->ev_mask & my_events) != milter->ev_mask)
+ msg_info("milter %s: event mask 0x%x includes features not"
+ " offered in MTA event mask 0x%lx",
+ milter->m.name, milter->ev_mask, (long) my_events);
+ msg_info("%s: milter %s version %d",
+ myname, milter->m.name, milter->version);
+ msg_info("%s: events %s", myname,
+ str_name_mask_opt(milter->buf, "event mask",
+ smfip_table, milter->ev_mask, NAME_MASK_NUMBER));
+ msg_info("%s: requests %s", myname,
+ str_name_mask_opt(milter->buf, "request mask",
+ smfif_table, milter->rq_mask, NAME_MASK_NUMBER));
+ }
+ milter->state = MILTER8_STAT_READY;
+ milter8_def_reply(milter, 0);
+ milter->skip_event_type = 0;
+
+ /*
+ * Secondary negotiations: override lists of macro names.
+ */
+ if (data_len > 0) {
+ VSTRING *buf = vstring_alloc(100);
+ UINT32_TYPE mac_type;
+ const char *smfim_name;
+ char **mac_value_ptr;
+
+ milter->m.macros = milter_macros_alloc(MILTER_MACROS_ALLOC_EMPTY);
+
+ while (data_len > 0
+ && milter8_read_data(milter, &data_len,
+ MILTER8_DATA_HLONG, &mac_type,
+ MILTER8_DATA_STRING, buf,
+ MILTER8_DATA_MORE) == 0) {
+ smfim_name = str_name_code(smfim_table, mac_type);
+ if (smfim_name == 0) {
+ msg_warn("milter %s: ignoring unknown macro type %u",
+ milter->m.name, (unsigned) mac_type);
+ } else {
+ if (msg_verbose)
+ msg_info("override %s macro list with \"%s\"",
+ smfim_name, STR(buf));
+ mac_value_ptr = MILTER8_MACRO_PTR(milter->m.macros, mac_type);
+ myfree(*mac_value_ptr);
+ *mac_value_ptr = mystrdup(STR(buf));
+ }
+ }
+ /* milter8_read_data() calls milter8_comm_error() after error. */
+ vstring_free(buf);
+ /* At this point the filter state is either READY or ERROR. */
+ }
+}
+
+/* milter8_conn_event - report connect event to Sendmail 8 milter */
+
+static const char *milter8_conn_event(MILTER *m,
+ const char *client_name,
+ const char *client_addr,
+ const char *client_port,
+ unsigned addr_family,
+ ARGV *macros)
+{
+ const char *myname = "milter8_conn_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int port;
+ int skip_reply;
+ const char *sm_name;
+ char *ptr = 0;
+ const char *resp;
+
+ /*
+ * Need a global definition for "unknown" host name or address that is
+ * shared by smtpd, cleanup and libmilter.
+ */
+#define XXX_UNKNOWN "unknown"
+#define STR_EQ(x,y) (strcmp((x), (y)) == 0)
+#define STR_NE(x,y) (strcmp((x), (y)) != 0)
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_READY:
+ if (msg_verbose)
+ msg_info("%s: milter %s: connect %s/%s",
+ myname, milter->m.name, client_name, client_addr);
+ if (client_port == 0) {
+ port = 0;
+ } else if (!alldig(client_port) || (port = atoi(client_port)) < 0
+ || port > 65535) {
+ msg_warn("milter %s: bad client port number %s",
+ milter->m.name, client_port);
+ port = 0;
+ }
+ milter->state = MILTER8_STAT_ENVELOPE;
+ skip_reply = ((milter->ev_mask & SMFIP_NR_CONN) != 0);
+ /* Transform unknown hostname from Postfix to Sendmail form. */
+ sm_name = (STR_NE(client_name, XXX_UNKNOWN) ? client_name :
+ STR_EQ(client_addr, XXX_UNKNOWN) ? client_name :
+ (ptr = concatenate("[", client_addr, "]", (char *) 0)));
+ switch (addr_family) {
+ case AF_INET:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_INET,
+ MILTER8_DATA_NSHORT, htons(port),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_INET6,
+ MILTER8_DATA_NSHORT, htons(port),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+#endif
+ case AF_UNIX:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_UNIX,
+ MILTER8_DATA_NSHORT, htons(0),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+ default:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_UNKNOWN,
+ MILTER8_DATA_END);
+ break;
+ }
+ if (ptr != 0)
+ myfree(ptr);
+ return (resp);
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_helo_event - report HELO/EHLO command to Sendmail 8 milter */
+
+static const char *milter8_helo_event(MILTER *m, const char *helo_name,
+ int unused_esmtp,
+ ARGV *macros)
+{
+ const char *myname = "milter8_helo_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_ACCEPT_MSG:
+ /* With HELO after MAIL, smtpd(8) calls milter8_abort() next. */
+ if (msg_verbose)
+ msg_info("%s: milter %s: helo %s",
+ myname, milter->m.name, helo_name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_HELO) != 0);
+ return (milter8_event(milter, SMFIC_HELO, SMFIP_NOHELO,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, helo_name,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_mail_event - report MAIL command to Sendmail 8 milter */
+
+static const char *milter8_mail_event(MILTER *m, const char **argv,
+ ARGV *macros)
+{
+ const char *myname = "milter8_mail_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ const char **cpp;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ for (cpp = argv; *cpp; cpp++)
+ vstring_sprintf_append(buf, " %s", *cpp);
+ msg_info("%s: milter %s: mail%s",
+ myname, milter->m.name, STR(buf));
+ vstring_free(buf);
+ }
+ skip_reply = ((milter->ev_mask & SMFIP_NR_MAIL) != 0);
+ return (milter8_event(milter, SMFIC_MAIL, SMFIP_NOMAIL,
+ skip_reply, macros,
+ MILTER8_DATA_ARGV, argv,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_rcpt_event - report RCPT command to Sendmail 8 milter */
+
+static const char *milter8_rcpt_event(MILTER *m, const char **argv,
+ ARGV *macros)
+{
+ const char *myname = "milter8_rcpt_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ const char **cpp;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ for (cpp = argv; *cpp; cpp++)
+ vstring_sprintf_append(buf, " %s", *cpp);
+ msg_info("%s: milter %s: rcpt%s",
+ myname, milter->m.name, STR(buf));
+ vstring_free(buf);
+ }
+ skip_reply = ((milter->ev_mask & SMFIP_NR_RCPT) != 0);
+ return (milter8_event(milter, SMFIC_RCPT, SMFIP_NORCPT,
+ skip_reply, macros,
+ MILTER8_DATA_ARGV, argv,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_data_event - report DATA command to Sendmail 8 milter */
+
+static const char *milter8_data_event(MILTER *m, ARGV *macros)
+{
+ const char *myname = "milter8_data_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: milter %s: data command", myname, milter->m.name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_DATA) != 0);
+ return (milter8_event(milter, SMFIC_DATA, SMFIP_NODATA,
+ skip_reply, macros,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_unknown_event - report unknown SMTP command to Sendmail 8 milter */
+
+static const char *milter8_unknown_event(MILTER *m, const char *command,
+ ARGV *macros)
+{
+ const char *myname = "milter8_unknown_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: milter %s: unknown command: %s",
+ myname, milter->m.name, command);
+ /* XXX Sendmail doesn't send macros (checked with 8.6.13). */
+ skip_reply = ((milter->ev_mask & SMFIP_NR_UNKN) != 0);
+ return (milter8_event(milter, SMFIC_UNKNOWN, SMFIP_NOUNKNOWN,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, command,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_other_event - reply for other event */
+
+static const char *milter8_other_event(MILTER *m)
+{
+ const char *myname = "milter8_other_event";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * Return the default reply.
+ */
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+}
+
+/* milter8_abort - cancel one milter's message receiving state */
+
+static void milter8_abort(MILTER *m)
+{
+ const char *myname = "milter8_abort";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_CLOSED:
+ case MILTER8_STAT_READY:
+ return;
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ break;
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_MESSAGE:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: abort milter %s", myname, milter->m.name);
+ (void) milter8_write_cmd(milter, SMFIC_ABORT, MILTER8_DATA_END);
+ if (milter->state != MILTER8_STAT_ERROR)
+ milter->state = MILTER8_STAT_ENVELOPE;
+ break;
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_disc_event - report client disconnect event */
+
+static void milter8_disc_event(MILTER *m)
+{
+ const char *myname = "milter8_disc_event";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_CLOSED:
+ case MILTER8_STAT_READY:
+ return;
+ case MILTER8_STAT_ERROR:
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+#endif
+ if (msg_verbose)
+ msg_info("%s: skip quit milter %s", myname, milter->m.name);
+ break;
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_MESSAGE:
+#ifndef LIBMILTER_AUTO_DISCONNECT
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+#endif
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: quit milter %s", myname, milter->m.name);
+ (void) milter8_write_cmd(milter, SMFIC_QUIT, MILTER8_DATA_END);
+ break;
+ }
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#else
+ if (milter->state != MILTER8_STAT_ERROR)
+ milter->state = MILTER8_STAT_READY;
+#endif
+ milter8_def_reply(milter, 0);
+}
+
+ /*
+ * Structure to ship context across the MIME_STATE engine.
+ */
+typedef struct {
+ MILTER8 *milter; /* milter client */
+ ARGV *eoh_macros; /* end-of-header macros */
+ ARGV *eod_macros; /* end-of-body macros */
+ ARGV *auto_hdrs; /* auto-generated headers */
+ int auto_done; /* good enough for now */
+ int first_header; /* first header */
+ int first_body; /* first body line */
+ const char *resp; /* milter application response */
+} MILTER_MSG_CONTEXT;
+
+/* milter8_header - milter8_message call-back for message header */
+
+static void milter8_header(void *ptr, int unused_header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t unused_offset)
+{
+ const char *myname = "milter8_header";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ char *cp;
+ int skip_reply;
+ char **cpp;
+ unsigned done;
+
+ /*
+ * XXX Workaround: mime_state_update() may invoke multiple call-backs
+ * before returning to the caller.
+ */
+#define MILTER8_MESSAGE_DONE(milter, msg_ctx) \
+ ((milter)->state != MILTER8_STAT_MESSAGE || (msg_ctx)->resp != 0)
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+
+ /*
+ * XXX Sendmail compatibility. Don't expose our first (received) header
+ * to mail filter applications. See also cleanup_milter.c for code to
+ * ensure that header replace requests are relative to the message
+ * content as received, that is, without our own first (received) header,
+ * while header insert requests are relative to the message as delivered,
+ * that is, including our own first (received) header.
+ *
+ * XXX But this breaks when they delete our own Received: header with
+ * header_checks before it reaches the queue file. Even then we must not
+ * expose the first header to mail filter applications, otherwise the
+ * dk-filter signature will be inserted at the wrong position. It should
+ * precede the headers that it signs.
+ *
+ * XXX Sendmail compatibility. It eats the first space (not tab) after the
+ * header label and ":".
+ */
+ for (cpp = msg_ctx->auto_hdrs->argv, done = 1; *cpp; cpp++, done <<= 1)
+ if ((msg_ctx->auto_done & done) == 0 && strcmp(*cpp, STR(buf)) == 0) {
+ msg_ctx->auto_done |= done;
+ return;
+ }
+
+ /*
+ * Sendmail 8 sends multi-line headers as text separated by newline.
+ *
+ * We destroy the header buffer to split it into label and value. Changing
+ * the buffer is explicitly allowed by the mime_state(3) interface.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: header milter %s: %.100s",
+ myname, milter->m.name, STR(buf));
+ cp = STR(buf) + (header_info ? strlen(header_info->name) :
+ is_header(STR(buf)));
+ /* XXX Following matches is_header.c */
+ while (*cp == ' ' || *cp == '\t')
+ *cp++ = 0;
+ if (*cp != ':')
+ msg_panic("%s: header label not followed by ':'", myname);
+ *cp++ = 0;
+ /* XXX Sendmail by default eats one space (not tab) after the colon. */
+ if ((milter->ev_mask & SMFIP_HDR_LEADSPC) == 0 && *cp == ' ')
+ cp++;
+ skip_reply = ((milter->ev_mask & SMFIP_NOHREPL) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_HEADER, SMFIP_NOHDRS,
+ skip_reply, msg_ctx->eoh_macros,
+ MILTER8_DATA_STRING, STR(buf),
+ MILTER8_DATA_STRING, cp,
+ MILTER8_DATA_END);
+}
+
+/* milter8_eoh - milter8_message call-back for end-of-header */
+
+static void milter8_eoh(void *ptr)
+{
+ const char *myname = "milter8_eoh";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ if (msg_verbose)
+ msg_info("%s: eoh milter %s", myname, milter->m.name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_EOH) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_EOH, SMFIP_NOEOH,
+ skip_reply, msg_ctx->eoh_macros,
+ MILTER8_DATA_END);
+}
+
+/* milter8_body - milter8_message call-back for body content */
+
+static void milter8_body(void *ptr, int rec_type,
+ const char *buf, ssize_t len,
+ off_t offset)
+{
+ const char *myname = "milter8_body";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ ssize_t todo = len;
+ const char *bp = buf;
+ ssize_t space;
+ ssize_t count;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+
+ /*
+ * XXX Sendmail compatibility: don't expose our first body line.
+ */
+ if (msg_ctx->first_body) {
+ msg_ctx->first_body = 0;
+ return;
+ }
+
+ /*
+ * XXX I thought I was going to delegate all the on-the-wire formatting
+ * to a common lower layer, but unfortunately it's not practical. If we
+ * were to do MILTER_CHUNK_SIZE buffering in a common lower layer, then
+ * we would have to pass along call-backs and state, so that the
+ * call-back can invoke milter8_event() with the right arguments when the
+ * MILTER_CHUNK_SIZE buffer reaches capacity. That's just too ugly.
+ *
+ * To recover the cost of making an extra copy of body content from Milter
+ * buffer to VSTREAM buffer, we could make vstream_fwrite() a little
+ * smarter so that it does large transfers directly from the user buffer
+ * instead of copying the data one block at a time into a VSTREAM buffer.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: body milter %s: %.100s", myname, milter->m.name, buf);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
+ /* To append \r\n, simply redirect input to another buffer. */
+ if (rec_type == REC_TYPE_NORM && todo == 0) {
+ bp = "\r\n";
+ todo = 2;
+ rec_type = REC_TYPE_EOF;
+ }
+ while (todo > 0) {
+ /* Append one REC_TYPE_NORM or REC_TYPE_CONT to body chunk buffer. */
+ space = MILTER_CHUNK_SIZE - LEN(milter->body);
+ if (space <= 0)
+ msg_panic("%s: bad buffer size: %ld",
+ myname, (long) LEN(milter->body));
+ count = (todo > space ? space : todo);
+ vstring_memcat(milter->body, bp, count);
+ bp += count;
+ todo -= count;
+ /* Flush body chunk buffer when full. See also milter8_eob(). */
+ if (LEN(milter->body) == MILTER_CHUNK_SIZE) {
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
+ skip_reply, msg_ctx->eod_macros,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END);
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ break;
+ VSTRING_RESET(milter->body);
+ }
+ /* To append \r\n, simply redirect input to another buffer. */
+ if (rec_type == REC_TYPE_NORM && todo == 0) {
+ bp = "\r\n";
+ todo = 2;
+ rec_type = REC_TYPE_EOF;
+ }
+ }
+}
+
+/* milter8_eob - milter8_message call-back for end-of-body */
+
+static void milter8_eob(void *ptr)
+{
+ const char *myname = "milter8_eob";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ if (msg_verbose)
+ msg_info("%s: eob milter %s", myname, milter->m.name);
+
+ /*
+ * Flush partial body chunk buffer. See also milter8_body().
+ *
+ * XXX Sendmail 8 libmilter accepts SMFIC_EOB+data, and delivers it to the
+ * application as two events: SMFIC_BODY+data followed by SMFIC_EOB. This
+ * breaks with the PMilter 0.95 protocol re-implementation, which
+ * delivers the SMFIC_EOB event and ignores the data. To avoid such
+ * compatibility problems we separate the events in the client. With
+ * this, we also prepare for a future where different event types can
+ * have different macro lists.
+ */
+ if (LEN(milter->body) > 0) {
+ skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
+ skip_reply, msg_ctx->eod_macros,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END);
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ }
+ msg_ctx->resp =
+ milter8_event(msg_ctx->milter, SMFIC_BODYEOB, 0,
+ DONT_SKIP_REPLY, msg_ctx->eod_macros,
+ MILTER8_DATA_END);
+}
+
+/* milter8_message - send message content and receive reply */
+
+static const char *milter8_message(MILTER *m, VSTREAM *qfile,
+ off_t data_offset,
+ ARGV *eoh_macros,
+ ARGV *eod_macros,
+ ARGV *auto_hdrs)
+{
+ const char *myname = "milter8_message";
+ MILTER8 *milter = (MILTER8 *) m;
+ MIME_STATE *mime_state;
+ int rec_type;
+ const MIME_STATE_DETAIL *detail;
+ int mime_errs = 0;
+ MILTER_MSG_CONTEXT msg_ctx;
+ VSTRING *buf;
+ int saved_errno;
+
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip message to milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: message to milter %s", myname, milter->m.name);
+ if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) {
+ saved_errno = errno;
+ msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile));
+ /* XXX This should be available from cleanup_strerror.c. */
+ return (saved_errno == EFBIG ?
+ "552 5.3.4 Message file too big" :
+ "451 4.3.0 Queue file write error");
+ }
+ msg_ctx.milter = milter;
+ msg_ctx.eoh_macros = eoh_macros;
+ msg_ctx.eod_macros = eod_macros;
+ msg_ctx.auto_hdrs = auto_hdrs;
+ msg_ctx.auto_done = 0;
+ msg_ctx.first_header = 1;
+ msg_ctx.first_body = 1;
+ msg_ctx.resp = 0;
+ mime_state =
+ mime_state_alloc(MIME_OPT_DISABLE_MIME,
+ (milter->ev_mask & SMFIP_NOHDRS) ?
+ (MIME_STATE_HEAD_OUT) 0 : milter8_header,
+ (milter->ev_mask & SMFIP_NOEOH) ?
+ (MIME_STATE_ANY_END) 0 : milter8_eoh,
+ (milter->ev_mask & SMFIP_NOBODY) ?
+ (MIME_STATE_BODY_OUT) 0 : milter8_body,
+ milter8_eob,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) &msg_ctx);
+ buf = vstring_alloc(100);
+ milter->state = MILTER8_STAT_MESSAGE;
+ VSTRING_RESET(milter->body);
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->msg_timeout),
+ CA_VSTREAM_CTL_END);
+
+ /*
+ * XXX When the message (not MIME body part) does not end in CRLF
+ * (i.e. the last record was REC_TYPE_CONT), do we send a CRLF
+ * terminator before triggering the end-of-body condition?
+ */
+ for (;;) {
+ if ((rec_type = rec_get(qfile, buf, 0)) < 0) {
+ msg_warn("%s: error reading %s: %m",
+ myname, VSTREAM_PATH(qfile));
+ msg_ctx.resp = "450 4.3.0 Queue file write error";
+ break;
+ }
+ /* Invoke the appropriate call-back routine. */
+ mime_errs = mime_state_update(mime_state, rec_type,
+ STR(buf), LEN(buf));
+ if (mime_errs) {
+ detail = mime_state_detail(mime_errs);
+ msg_warn("%s: MIME problem %s in %s",
+ myname, detail->text, VSTREAM_PATH(qfile));
+ msg_ctx.resp = "450 4.3.0 Queue file write error";
+ break;
+ }
+ if (MILTER8_MESSAGE_DONE(milter, &msg_ctx))
+ break;
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ }
+ mime_state_free(mime_state);
+ vstring_free(buf);
+ if (milter->fp)
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
+ CA_VSTREAM_CTL_END);
+ if (milter->state == MILTER8_STAT_MESSAGE
+ || milter->state == MILTER8_STAT_ACCEPT_MSG)
+ milter->state = MILTER8_STAT_ENVELOPE;
+ return (msg_ctx.resp);
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+ /*
+ * Preliminary protocol to send/receive milter instances. This needs to be
+ * extended with type information once we support multiple milter protocols.
+ */
+#define MAIL_ATTR_MILT_NAME "milter_name"
+#define MAIL_ATTR_MILT_VERS "milter_version"
+#define MAIL_ATTR_MILT_ACTS "milter_actions"
+#define MAIL_ATTR_MILT_EVTS "milter_events"
+#define MAIL_ATTR_MILT_NPTS "milter_non_events"
+#define MAIL_ATTR_MILT_STAT "milter_state"
+#define MAIL_ATTR_MILT_CONN "milter_conn_timeout"
+#define MAIL_ATTR_MILT_CMD "milter_cmd_timeout"
+#define MAIL_ATTR_MILT_MSG "milter_msg_timeout"
+#define MAIL_ATTR_MILT_ACT "milter_action"
+#define MAIL_ATTR_MILT_MAC "milter_macro_list"
+
+/* milter8_active - report if this milter still wants events */
+
+static int milter8_active(MILTER *m)
+{
+ MILTER8 *milter = (MILTER8 *) m;
+
+ return (milter->fp != 0
+ && (milter->state == MILTER8_STAT_ENVELOPE
+ || milter->state == MILTER8_STAT_READY));
+}
+
+/* milter8_send - send milter instance */
+
+static int milter8_send(MILTER *m, VSTREAM *stream)
+{
+ const char *myname = "milter8_send";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, milter->m.name);
+
+ /*
+ * The next read on this Milter socket happens in a different process. It
+ * will not automatically flush the output buffer in this process.
+ */
+ if (milter->fp)
+ vstream_fflush(milter->fp);
+
+ if (attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_MILT_NAME, milter->m.name),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_VERS, milter->version),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_ACTS, milter->rq_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_EVTS, milter->ev_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_NPTS, milter->np_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_STAT, milter->state),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_CONN, milter->conn_timeout),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_CMD, milter->cmd_timeout),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_MSG, milter->msg_timeout),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_ACT, milter->def_action),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_MAC, milter->m.macros != 0),
+ ATTR_TYPE_END) != 0
+ || (milter->m.macros != 0
+ && attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(milter_macros_print,
+ (const void *) milter->m.macros),
+ ATTR_TYPE_END) != 0)
+ || (milter->m.macros == 0
+ && attr_print(stream, ATTR_FLAG_NONE,
+ ATTR_TYPE_END) != 0)
+ || vstream_fflush(stream) != 0) {
+ return (-1);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+#endif
+ } else if (LOCAL_SEND_FD(vstream_fileno(stream),
+ vstream_fileno(milter->fp)) < 0) {
+ return (-1);
+#ifdef MUST_READ_AFTER_SENDING_FD
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+#endif
+ } else {
+ return (0);
+ }
+}
+
+static MILTER8 *milter8_alloc(const char *, int, int, int, const char *,
+ const char *, MILTERS *);
+
+/* milter8_receive - receive milter instance */
+
+MILTER *milter8_receive(VSTREAM *stream, MILTERS *parent)
+{
+ const char *myname = "milter8_receive";
+ static VSTRING *name_buf;
+ static VSTRING *act_buf;
+ MILTER8 *milter;
+ int version;
+ int rq_mask;
+ int ev_mask;
+ int np_mask;
+ int state;
+ int conn_timeout;
+ int cmd_timeout;
+ int msg_timeout;
+ int fd;
+ int has_macros;
+ MILTER_MACROS *macros = 0;
+
+#define FREE_MACROS_AND_RETURN(x) do { \
+ if (macros) \
+ milter_macros_free(macros); \
+ return (x); \
+ } while (0)
+
+ if (name_buf == 0) {
+ name_buf = vstring_alloc(10);
+ act_buf = vstring_alloc(10);
+ }
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_MILT_NAME, name_buf),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_VERS, &version),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_ACTS, &rq_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_EVTS, &ev_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_NPTS, &np_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_STAT, &state),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_CONN, &conn_timeout),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_CMD, &cmd_timeout),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_MSG, &msg_timeout),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_ACT, act_buf),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_MAC, &has_macros),
+ ATTR_TYPE_END) < 10
+ || (has_macros != 0
+ && attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(milter_macros_scan,
+ (void *) (macros =
+ milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO))),
+ ATTR_TYPE_END) < 1)
+ || (has_macros == 0
+ && attr_scan(stream, ATTR_FLAG_STRICT,
+ ATTR_TYPE_END) < 0)) {
+ FREE_MACROS_AND_RETURN(0);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ } else if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream) != 0) {
+ FREE_MACROS_AND_RETURN(0);
+#endif
+ } else if ((fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
+ FREE_MACROS_AND_RETURN(0);
+ } else {
+#ifdef MUST_READ_AFTER_SENDING_FD
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END);
+#endif
+#define NO_PROTOCOL ((char *) 0)
+
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, STR(name_buf));
+
+ milter = milter8_alloc(STR(name_buf), conn_timeout, cmd_timeout,
+ msg_timeout, NO_PROTOCOL, STR(act_buf), parent);
+ milter->fp = vstream_fdopen(fd, O_RDWR);
+ milter->m.macros = macros;
+ vstream_control(milter->fp, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ vstream_tweak_sock(milter->fp);
+ milter->version = version;
+ milter->rq_mask = rq_mask;
+ milter->ev_mask = ev_mask;
+ milter->np_mask = np_mask;
+ milter->state = state;
+ return (&milter->m);
+ }
+}
+
+/* milter8_free - destroy Milter instance */
+
+static void milter8_free(MILTER *m)
+{
+ MILTER8 *milter = (MILTER8 *) m;
+
+ if (msg_verbose)
+ msg_info("free milter %s", milter->m.name);
+ if (milter->fp)
+ (void) vstream_fclose(milter->fp);
+ myfree(milter->m.name);
+ vstring_free(milter->buf);
+ vstring_free(milter->body);
+ if (milter->protocol)
+ myfree(milter->protocol);
+ myfree(milter->def_action);
+ if (milter->def_reply)
+ myfree(milter->def_reply);
+ if (milter->m.macros)
+ milter_macros_free(milter->m.macros);
+ myfree((void *) milter);
+}
+
+/* milter8_alloc - create MTA-side Sendmail 8 Milter instance */
+
+static MILTER8 *milter8_alloc(const char *name, int conn_timeout,
+ int cmd_timeout, int msg_timeout,
+ const char *protocol,
+ const char *def_action,
+ MILTERS *parent)
+{
+ MILTER8 *milter;
+
+ /*
+ * Fill in the structure. Note: all strings must be copied.
+ *
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ milter = (MILTER8 *) mymalloc(sizeof(*milter));
+ milter->m.name = mystrdup(name);
+ milter->m.flags = 0;
+ milter->m.next = 0;
+ milter->m.parent = parent;
+ milter->m.macros = 0;
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter->m.connect_on_demand = (void (*) (struct MILTER *)) milter8_connect;
+#else
+ milter->m.connect_on_demand = 0;
+#endif
+ milter->m.conn_event = milter8_conn_event;
+ milter->m.helo_event = milter8_helo_event;
+ milter->m.mail_event = milter8_mail_event;
+ milter->m.rcpt_event = milter8_rcpt_event;
+ milter->m.data_event = milter8_data_event; /* may be null */
+ milter->m.message = milter8_message;
+ milter->m.unknown_event = milter8_unknown_event; /* may be null */
+ milter->m.other_event = milter8_other_event;
+ milter->m.abort = milter8_abort;
+ milter->m.disc_event = milter8_disc_event;
+ milter->m.active = milter8_active;
+ milter->m.send = milter8_send;
+ milter->m.free = milter8_free;
+ milter->fp = 0;
+ milter->buf = vstring_alloc(100);
+ milter->body = vstring_alloc(100);
+ milter->version = 0;
+ milter->rq_mask = 0;
+ milter->ev_mask = 0;
+ milter->state = MILTER8_STAT_CLOSED;
+ milter->conn_timeout = conn_timeout;
+ milter->cmd_timeout = cmd_timeout;
+ milter->msg_timeout = msg_timeout;
+ milter->protocol = (protocol ? mystrdup(protocol) : 0);
+ milter->def_action = mystrdup(def_action);
+ milter->def_reply = 0;
+ milter->skip_event_type = 0;
+
+ return (milter);
+}
+
+/* milter8_create - create MTA-side Sendmail 8 Milter instance */
+
+MILTER *milter8_create(const char *name, int conn_timeout, int cmd_timeout,
+ int msg_timeout, const char *protocol,
+ const char *def_action, MILTERS *parent)
+{
+ MILTER8 *milter;
+
+ /*
+ * Fill in the structure.
+ */
+ milter = milter8_alloc(name, conn_timeout, cmd_timeout, msg_timeout,
+ protocol, def_action, parent);
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+#ifndef LIBMILTER_AUTO_DISCONNECT
+ milter8_connect(milter);
+#endif
+ return (&milter->m);
+}
diff --git a/src/milter/milter_macros.c b/src/milter/milter_macros.c
new file mode 100644
index 0000000..27f5509
--- /dev/null
+++ b/src/milter/milter_macros.c
@@ -0,0 +1,303 @@
+/*++
+/* NAME
+/* milter_macros
+/* SUMMARY
+/* manipulate MILTER_MACROS structures
+/* SYNOPSIS
+/* #include <milter.h>
+/*
+/* MILTER_MACROS *milter_macros_create(conn_macros, helo_macros,
+/* mail_macros, rcpt_macros,
+/* data_macros, eoh_macros,
+/* eod_macros, unk_macros)
+/* const char *conn_macros;
+/* const char *helo_macros;
+/* const char *mail_macros;
+/* const char *rcpt_macrps;
+/* const char *data_macros;
+/* const char *eoh_macros;
+/* const char *eod_macros;
+/* const char *unk_macros;
+/*
+/* MILTER_MACROS *milter_macros_alloc(init_mode)
+/* int init_mode;
+/*
+/* void milter_macros_free(mp)
+/* MILTER_MACROS *mp;
+/*
+/* int milter_macros_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int milter_macros_scan(scan_fn, fp, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *fp;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* Sendmail mail filter (Milter) applications receive sets of
+/* macro name=value pairs with each SMTP or content event.
+/* In Postfix, these macro names are stored in MILTER_MACROS
+/* structures, as one list for each event type. By default,
+/* the same structure is shared by all Milter applications;
+/* it is initialized with information from main.cf. With
+/* Sendmail 8.14 a Milter can override one or more lists of
+/* macro names. Postfix implements this by giving the Milter
+/* its own MILTER_MACROS structure and by storing the per-Milter
+/* information there.
+/*
+/* This module maintains per-event macro name lists as
+/* mystrdup()'ed values. The user is explicitly allowed to
+/* update these values directly, as long as the result is
+/* compatible with mystrdup().
+/*
+/* milter_macros_create() creates a MILTER_MACROS structure
+/* and initializes it with copies of its string arguments.
+/* Null pointers are not valid as input.
+/*
+/* milter_macros_alloc() creates am empty MILTER_MACROS structure
+/* that is initialized according to its init_mode argument.
+/* .IP MILTER_MACROS_ALLOC_ZERO
+/* Initialize all structure members as null pointers. This
+/* mode must be used with milter_macros_scan(), because that
+/* function blindly overwrites all structure members. No other
+/* function except milter_macros_free() allows structure members
+/* with null pointer values.
+/* .IP MILTER_MACROS_ALLOC_EMPTY
+/* Initialize all structure members with mystrdup(""). This
+/* is not as expensive as it appears to be.
+/* .PP
+/* milter_macros_free() destroys a MILTER_MACROS structure and
+/* frees any strings referenced by it.
+/*
+/* milter_macros_print() writes the contents of a MILTER_MACROS
+/* structure to the named stream using the specified attribute
+/* print routine. milter_macros_print() is meant to be passed
+/* as a call-back to attr_print*(), thusly:
+/*
+/* SEND_ATTR_FUNC(milter_macros_print, (const void *) macros),
+/*
+/* milter_macros_scan() reads a MILTER_MACROS structure from
+/* the named stream using the specified attribute scan routine.
+/* No attempt is made to free the memory of existing structure
+/* members. milter_macros_scan() is meant to be passed as a
+/* call-back to attr_scan*(), thusly:
+/*
+/* RECV_ATTR_FUNC(milter_macros_scan, (void *) macros),
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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 <attr.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <milter.h>
+
+ /*
+ * Ad-hoc protocol to send/receive milter macro name lists.
+ */
+#define MAIL_ATTR_MILT_MAC_CONN "conn_macros"
+#define MAIL_ATTR_MILT_MAC_HELO "helo_macros"
+#define MAIL_ATTR_MILT_MAC_MAIL "mail_macros"
+#define MAIL_ATTR_MILT_MAC_RCPT "rcpt_macros"
+#define MAIL_ATTR_MILT_MAC_DATA "data_macros"
+#define MAIL_ATTR_MILT_MAC_EOH "eoh_macros"
+#define MAIL_ATTR_MILT_MAC_EOD "eod_macros"
+#define MAIL_ATTR_MILT_MAC_UNK "unk_macros"
+
+/* milter_macros_print - write macros structure to stream */
+
+int milter_macros_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ MILTER_MACROS *mp = (MILTER_MACROS *) ptr;
+ int ret;
+
+ /*
+ * The attribute order does not matter, except that it must be the same
+ * as in the milter_macros_scan() function.
+ */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, mp->conn_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, mp->helo_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mp->mail_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, mp->rcpt_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, mp->data_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, mp->eoh_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, mp->eod_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, mp->unk_macros),
+ ATTR_TYPE_END);
+ return (ret);
+}
+
+/* milter_macros_scan - receive macros structure from stream */
+
+int milter_macros_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ MILTER_MACROS *mp = (MILTER_MACROS *) ptr;
+ int ret;
+
+ /*
+ * We could simplify this by moving memory allocation into attr_scan*().
+ */
+ VSTRING *conn_macros = vstring_alloc(10);
+ VSTRING *helo_macros = vstring_alloc(10);
+ VSTRING *mail_macros = vstring_alloc(10);
+ VSTRING *rcpt_macros = vstring_alloc(10);
+ VSTRING *data_macros = vstring_alloc(10);
+ VSTRING *eoh_macros = vstring_alloc(10);
+ VSTRING *eod_macros = vstring_alloc(10);
+ VSTRING *unk_macros = vstring_alloc(10);
+
+ /*
+ * The attribute order does not matter, except that it must be the same
+ * as in the milter_macros_print() function.
+ */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, conn_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, helo_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mail_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, rcpt_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, data_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, eoh_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, eod_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, unk_macros),
+ ATTR_TYPE_END);
+
+ /*
+ * Don't optimize for error.
+ */
+ mp->conn_macros = vstring_export(conn_macros);
+ mp->helo_macros = vstring_export(helo_macros);
+ mp->mail_macros = vstring_export(mail_macros);
+ mp->rcpt_macros = vstring_export(rcpt_macros);
+ mp->data_macros = vstring_export(data_macros);
+ mp->eoh_macros = vstring_export(eoh_macros);
+ mp->eod_macros = vstring_export(eod_macros);
+ mp->unk_macros = vstring_export(unk_macros);
+
+ return (ret == 8 ? 1 : -1);
+}
+
+/* milter_macros_create - create and initialize macros structure */
+
+MILTER_MACROS *milter_macros_create(const char *conn_macros,
+ const char *helo_macros,
+ const char *mail_macros,
+ const char *rcpt_macros,
+ const char *data_macros,
+ const char *eoh_macros,
+ const char *eod_macros,
+ const char *unk_macros)
+{
+ MILTER_MACROS *mp;
+
+ mp = (MILTER_MACROS *) mymalloc(sizeof(*mp));
+ mp->conn_macros = mystrdup(conn_macros);
+ mp->helo_macros = mystrdup(helo_macros);
+ mp->mail_macros = mystrdup(mail_macros);
+ mp->rcpt_macros = mystrdup(rcpt_macros);
+ mp->data_macros = mystrdup(data_macros);
+ mp->eoh_macros = mystrdup(eoh_macros);
+ mp->eod_macros = mystrdup(eod_macros);
+ mp->unk_macros = mystrdup(unk_macros);
+
+ return (mp);
+}
+
+/* milter_macros_alloc - allocate macros structure with simple initialization */
+
+MILTER_MACROS *milter_macros_alloc(int mode)
+{
+ MILTER_MACROS *mp;
+
+ /*
+ * This macro was originally in milter.h, but no-one else needed it.
+ */
+#define milter_macros_init(mp, expr) do { \
+ MILTER_MACROS *__mp = (mp); \
+ char *__expr = (expr); \
+ __mp->conn_macros = __expr; \
+ __mp->helo_macros = __expr; \
+ __mp->mail_macros = __expr; \
+ __mp->rcpt_macros = __expr; \
+ __mp->data_macros = __expr; \
+ __mp->eoh_macros = __expr; \
+ __mp->eod_macros = __expr; \
+ __mp->unk_macros = __expr; \
+ } while (0)
+
+ mp = (MILTER_MACROS *) mymalloc(sizeof(*mp));
+ switch (mode) {
+ case MILTER_MACROS_ALLOC_ZERO:
+ milter_macros_init(mp, 0);
+ break;
+ case MILTER_MACROS_ALLOC_EMPTY:
+ milter_macros_init(mp, mystrdup(""));
+ break;
+ default:
+ msg_panic("milter_macros_alloc: unknown mode %d", mode);
+ }
+ return (mp);
+}
+
+/* milter_macros_free - destroy memory for MILTER_MACROS structure */
+
+void milter_macros_free(MILTER_MACROS *mp)
+{
+
+ /*
+ * This macro was originally in milter.h, but no-one else needed it.
+ */
+#define milter_macros_wipe(mp) do { \
+ MILTER_MACROS *__mp = mp; \
+ if (__mp->conn_macros) \
+ myfree(__mp->conn_macros); \
+ if (__mp->helo_macros) \
+ myfree(__mp->helo_macros); \
+ if (__mp->mail_macros) \
+ myfree(__mp->mail_macros); \
+ if (__mp->rcpt_macros) \
+ myfree(__mp->rcpt_macros); \
+ if (__mp->data_macros) \
+ myfree(__mp->data_macros); \
+ if (__mp->eoh_macros) \
+ myfree(__mp->eoh_macros); \
+ if (__mp->eod_macros) \
+ myfree(__mp->eod_macros); \
+ if (__mp->unk_macros) \
+ myfree(__mp->unk_macros); \
+ } while (0)
+
+ milter_macros_wipe(mp);
+ myfree((void *) mp);
+}
diff --git a/src/milter/test-list b/src/milter/test-list
new file mode 100644
index 0000000..d4cef7a
--- /dev/null
+++ b/src/milter/test-list
@@ -0,0 +1,49 @@
+# Reject with text
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eom -p inet:9999@0.0.0.0
+
+# Tempfail tests
+./test-milter -C 1 -a tempfail -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c eom -p inet:9999@0.0.0.0
+
+# Reject tests
+./test-milter -C 1 -a reject -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c eom -p inet:9999@0.0.0.0
+
+# Accept tests
+./test-milter -C 1 -a accept -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c eom -p inet:9999@0.0.0.0
+
+# discard tests
+./test-milter -C 1 -a discard -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c eom -p inet:9999@0.0.0.0
diff --git a/src/milter/test-milter.c b/src/milter/test-milter.c
new file mode 100644
index 0000000..0494ff0
--- /dev/null
+++ b/src/milter/test-milter.c
@@ -0,0 +1,840 @@
+/*++
+/* NAME
+/* test-milter 1
+/* SUMMARY
+/* Simple test mail filter program.
+/* SYNOPSIS
+/* .fi
+/* \fBtest-milter\fR [\fIoptions\fR] -p \fBinet:\fIport\fB@\fIhost\fR
+/*
+/* \fBtest-milter\fR [\fIoptions\fR] -p \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBtest-milter\fR is a Milter (mail filter) application that
+/* exercises selected features.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments (multiple alternatives are separated by "\fB|\fR"):
+/* .IP "\fB-a accept|tempfail|reject|discard|skip|\fIddd x.y.z text\fR"
+/* Specifies a non-default reply for the MTA command specified
+/* with \fB-c\fR. The default is \fBtempfail\fR. The \fItext\fR
+/* is repeated once, to produce multi-line reply text.
+/* .IP "\fB-A address\fR"
+/* Add the specified recipient address (specify ESMTP parameters
+/* separated by space). Multiple -A options are supported.
+/* .IP "\fB-b pathname\fR"
+/* Replace the message body by the content of the specified file.
+/* .IP "\fB-c connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown|close|abort\fR"
+/* When to send the non-default reply specified with \fB-a\fR.
+/* The default protocol stage is \fBconnect\fR.
+/* .IP "\fB-C\fI count\fR"
+/* Terminate after \fIcount\fR connections.
+/* .IP "\fB-d\fI level\fR"
+/* Enable libmilter debugging at the specified level.
+/* .IP "\fB-D\fI address\fR"
+/* Delete the specified recipient address. Multiple -D options
+/* are supported.
+/* .IP "\fB-f \fIsender\fR"
+/* Replace the sender by the specified address.
+/* .IP "\fB-h \fI'index header-label header-value'\fR"
+/* Replace the message header at the specified position.
+/* .IP "\fB-i \fI'index header-label header-value'\fR"
+/* Insert header at specified position.
+/* .IP "\fB-l\fR"
+/* Header values include leading space. Specify this option
+/* before \fB-i\fR or \fB-h\fR.
+/* .IP "\fB-m connect|helo|mail|rcpt|data|eoh|eom\fR"
+/* The protocol stage that receives the list of macros specified
+/* with \fB-M\fR. The default protocol stage is \fBconnect\fR.
+/* .IP "\fB-M \fIset_macro_list\fR"
+/* A non-default list of macros that the MTA should send at
+/* the protocol stage specified with \fB-m\fR.
+/* .IP "\fB-n connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
+/* The event that the MTA should not send.
+/* .IP "\fB-N connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
+/* The event for which the filter will not reply.
+/* .IP "\fB-p inet:\fIport\fB@\fIhost\fB|unix:\fIpathname\fR"
+/* The mail filter listen endpoint.
+/* .IP "\fB-r\fR"
+/* Request rejected recipients from the MTA.
+/* .IP "\fB-v\fR"
+/* Make the program more verbose.
+/* 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
+/*--*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "libmilter/mfapi.h"
+#include "libmilter/mfdef.h"
+
+static int conn_count;
+static int verbose;
+
+static int test_connect_reply = SMFIS_CONTINUE;
+static int test_helo_reply = SMFIS_CONTINUE;
+static int test_mail_reply = SMFIS_CONTINUE;
+static int test_rcpt_reply = SMFIS_CONTINUE;
+
+#if SMFI_VERSION > 3
+static int test_data_reply = SMFIS_CONTINUE;
+
+#endif
+static int test_header_reply = SMFIS_CONTINUE;
+static int test_eoh_reply = SMFIS_CONTINUE;
+static int test_body_reply = SMFIS_CONTINUE;
+static int test_eom_reply = SMFIS_CONTINUE;
+
+#if SMFI_VERSION > 2
+static int test_unknown_reply = SMFIS_CONTINUE;
+
+#endif
+static int test_close_reply = SMFIS_CONTINUE;
+static int test_abort_reply = SMFIS_CONTINUE;
+
+struct command_map {
+ const char *name;
+ int *reply;
+};
+
+static const struct command_map command_map[] = {
+ "connect", &test_connect_reply,
+ "helo", &test_helo_reply,
+ "mail", &test_mail_reply,
+ "rcpt", &test_rcpt_reply,
+ "header", &test_header_reply,
+ "eoh", &test_eoh_reply,
+ "body", &test_body_reply,
+ "eom", &test_eom_reply,
+ "abort", &test_abort_reply,
+ "close", &test_close_reply,
+#if SMFI_VERSION > 2
+ "unknown", &test_unknown_reply,
+#endif
+#if SMFI_VERSION > 3
+ "data", &test_data_reply,
+#endif
+ 0, 0,
+};
+
+static char *reply_code;
+static char *reply_dsn;
+static char *reply_message;
+
+#ifdef SMFIR_CHGFROM
+static char *chg_from;
+
+#endif
+
+#ifdef SMFIR_INSHEADER
+static char *ins_hdr;
+static int ins_idx;
+static char *ins_val;
+
+#endif
+
+#ifdef SMFIR_CHGHEADER
+static char *chg_hdr;
+static int chg_idx;
+static char *chg_val;
+
+#endif
+
+#ifdef SMFIR_REPLBODY
+static char *body_file;
+
+#endif
+
+#define MAX_RCPT 10
+int add_rcpt_count = 0;
+char *add_rcpt[MAX_RCPT];
+int del_rcpt_count = 0;
+char *del_rcpt[MAX_RCPT];
+
+static const char *macro_names[] = {
+ "_",
+ "i",
+ "j",
+ "v",
+ "{auth_authen}",
+ "{auth_author}",
+ "{auth_type}",
+ "{cert_issuer}",
+ "{cert_subject}",
+ "{cipher}",
+ "{cipher_bits}",
+ "{client_addr}",
+ "{client_connections}",
+ "{client_name}",
+ "{client_port}",
+ "{client_ptr}",
+ "{client_resolve}",
+ "{daemon_addr}",
+ "{daemon_name}",
+ "{daemon_port}",
+ "{if_addr}",
+ "{if_name}",
+ "{mail_addr}",
+ "{mail_host}",
+ "{mail_mailer}",
+ "{rcpt_addr}",
+ "{rcpt_host}",
+ "{rcpt_mailer}",
+ "{tls_version}",
+ 0,
+};
+
+static int test_reply(SMFICTX *ctx, int code)
+{
+ const char **cpp;
+ const char *symval;
+
+ for (cpp = macro_names; *cpp; cpp++)
+ if ((symval = smfi_getsymval(ctx, (char *) *cpp)) != 0)
+ printf("macro: %s=\"%s\"\n", *cpp, symval);
+ (void) fflush(stdout); /* In case output redirected. */
+
+ if (code == SMFIR_REPLYCODE) {
+ if (smfi_setmlreply(ctx, reply_code, reply_dsn, reply_message, reply_message, (char *) 0) == MI_FAILURE)
+ fprintf(stderr, "smfi_setmlreply failed\n");
+ printf("test_reply %s\n\n", reply_code);
+ return (reply_code[0] == '4' ? SMFIS_TEMPFAIL : SMFIS_REJECT);
+ } else {
+ printf("test_reply %d\n\n", code);
+ return (code);
+ }
+}
+
+static sfsistat test_connect(SMFICTX *ctx, char *name, struct sockaddr * sa)
+{
+ const char *print_addr;
+ char buf[BUFSIZ];
+
+ printf("test_connect %s ", name);
+ switch (sa->sa_family) {
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *) sa;
+
+ print_addr = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
+ if (print_addr == 0)
+ print_addr = strerror(errno);
+ printf("AF_INET (%s:%d)\n", print_addr, ntohs(sin->sin_port));
+ }
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+
+ print_addr = inet_ntop(AF_INET, &sin6->sin6_addr, buf, sizeof(buf));
+ if (print_addr == 0)
+ print_addr = strerror(errno);
+ printf("AF_INET6 (%s:%d)\n", print_addr, ntohs(sin6->sin6_port));
+ }
+ break;
+#endif
+ case AF_UNIX:
+ {
+#undef sun
+ struct sockaddr_un *sun = (struct sockaddr_un *) sa;
+
+ printf("AF_UNIX (%s)\n", sun->sun_path);
+ }
+ break;
+ default:
+ printf(" [unknown address family]\n");
+ break;
+ }
+ return (test_reply(ctx, test_connect_reply));
+}
+
+static sfsistat test_helo(SMFICTX *ctx, char *arg)
+{
+ printf("test_helo \"%s\"\n", arg ? arg : "NULL");
+ return (test_reply(ctx, test_helo_reply));
+}
+
+static sfsistat test_mail(SMFICTX *ctx, char **argv)
+{
+ char **cpp;
+
+ printf("test_mail");
+ for (cpp = argv; *cpp; cpp++)
+ printf(" \"%s\"", *cpp);
+ printf("\n");
+ return (test_reply(ctx, test_mail_reply));
+}
+
+static sfsistat test_rcpt(SMFICTX *ctx, char **argv)
+{
+ char **cpp;
+
+ printf("test_rcpt");
+ for (cpp = argv; *cpp; cpp++)
+ printf(" \"%s\"", *cpp);
+ printf("\n");
+ return (test_reply(ctx, test_rcpt_reply));
+}
+
+
+sfsistat test_header(SMFICTX *ctx, char *name, char *value)
+{
+ printf("test_header \"%s\" \"%s\"\n", name, value);
+ return (test_reply(ctx, test_header_reply));
+}
+
+static sfsistat test_eoh(SMFICTX *ctx)
+{
+ printf("test_eoh\n");
+ return (test_reply(ctx, test_eoh_reply));
+}
+
+static sfsistat test_body(SMFICTX *ctx, unsigned char *data, size_t data_len)
+{
+ if (verbose == 0)
+ printf("test_body %ld bytes\n", (long) data_len);
+ else
+ printf("%.*s", (int) data_len, data);
+ return (test_reply(ctx, test_body_reply));
+}
+
+static sfsistat test_eom(SMFICTX *ctx)
+{
+ printf("test_eom\n");
+#ifdef SMFIR_REPLBODY
+ if (body_file) {
+ char buf[BUFSIZ + 2];
+ FILE *fp;
+ size_t len;
+ int count;
+
+ if ((fp = fopen(body_file, "r")) == 0) {
+ perror(body_file);
+ } else {
+ printf("replace body with content of %s\n", body_file);
+ for (count = 0; fgets(buf, BUFSIZ, fp) != 0; count++) {
+ len = strcspn(buf, "\n");
+ buf[len + 0] = '\r';
+ buf[len + 1] = '\n';
+ if (smfi_replacebody(ctx, buf, len + 2) == MI_FAILURE) {
+ fprintf(stderr, "body replace failure\n");
+ exit(1);
+ }
+ if (verbose)
+ printf("%.*s\n", (int) len, buf);
+ }
+ if (count == 0)
+ perror("fgets");
+ (void) fclose(fp);
+ }
+ }
+#endif
+#ifdef SMFIR_CHGFROM
+ if (chg_from != 0 && smfi_chgfrom(ctx, chg_from, "whatever") == MI_FAILURE)
+ fprintf(stderr, "smfi_chgfrom failed\n");
+#endif
+#ifdef SMFIR_INSHEADER
+ if (ins_hdr && smfi_insheader(ctx, ins_idx, ins_hdr, ins_val) == MI_FAILURE)
+ fprintf(stderr, "smfi_insheader failed\n");
+#endif
+#ifdef SMFIR_CHGHEADER
+ if (chg_hdr && smfi_chgheader(ctx, chg_hdr, chg_idx, chg_val) == MI_FAILURE)
+ fprintf(stderr, "smfi_chgheader failed\n");
+#endif
+ {
+ int count;
+ char *args;
+
+ for (count = 0; count < add_rcpt_count; count++) {
+ if ((args = strchr(add_rcpt[count], ' ')) != 0) {
+ *args++ = 0;
+ if (smfi_addrcpt_par(ctx, add_rcpt[count], args) == MI_FAILURE)
+ fprintf(stderr, "smfi_addrcpt_par `%s' `%s' failed\n",
+ add_rcpt[count], args);
+ } else {
+ if (smfi_addrcpt(ctx, add_rcpt[count]) == MI_FAILURE)
+ fprintf(stderr, "smfi_addrcpt `%s' failed\n",
+ add_rcpt[count]);
+ }
+ }
+
+ for (count = 0; count < del_rcpt_count; count++)
+ if (smfi_delrcpt(ctx, del_rcpt[count]) == MI_FAILURE)
+ fprintf(stderr, "smfi_delrcpt `%s' failed\n", del_rcpt[count]);
+ }
+ return (test_reply(ctx, test_eom_reply));
+}
+
+static sfsistat test_abort(SMFICTX *ctx)
+{
+ printf("test_abort\n");
+ return (test_reply(ctx, test_abort_reply));
+}
+
+static sfsistat test_close(SMFICTX *ctx)
+{
+ printf("test_close\n");
+ if (verbose)
+ printf("conn_count %d\n", conn_count);
+ if (conn_count > 0 && --conn_count == 0)
+ exit(0);
+ return (test_reply(ctx, test_close_reply));
+}
+
+#if SMFI_VERSION > 3
+
+static sfsistat test_data(SMFICTX *ctx)
+{
+ printf("test_data\n");
+ return (test_reply(ctx, test_data_reply));
+}
+
+#endif
+
+#if SMFI_VERSION > 2
+
+static sfsistat test_unknown(SMFICTX *ctx, const char *what)
+{
+ printf("test_unknown %s\n", what);
+ return (test_reply(ctx, test_unknown_reply));
+}
+
+#endif
+
+#if SMFI_VERSION > 5
+
+static sfsistat test_negotiate(SMFICTX *, unsigned long, unsigned long,
+ unsigned long, unsigned long,
+ unsigned long *, unsigned long *,
+ unsigned long *, unsigned long *);
+
+#endif
+
+#ifndef SMFIF_CHGFROM
+#define SMFIF_CHGFROM 0
+#endif
+#ifndef SMFIP_HDR_LEADSPC
+#define SMFIP_HDR_LEADSPC 0
+#define misc_mask 0
+#endif
+
+static struct smfiDesc smfilter =
+{
+ "test-milter",
+ SMFI_VERSION,
+ SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_CHGBODY | SMFIF_CHGFROM,
+ test_connect,
+ test_helo,
+ test_mail,
+ test_rcpt,
+ test_header,
+ test_eoh,
+ test_body,
+ test_eom,
+ test_abort,
+ test_close,
+#if SMFI_VERSION > 2
+ test_unknown,
+#endif
+#if SMFI_VERSION > 3
+ test_data,
+#endif
+#if SMFI_VERSION > 5
+ test_negotiate,
+#endif
+};
+
+#if SMFI_VERSION > 5
+
+static const char *macro_states[] = {
+ "connect", /* SMFIM_CONNECT */
+ "helo", /* SMFIM_HELO */
+ "mail", /* SMFIM_ENVFROM */
+ "rcpt", /* SMFIM_ENVRCPT */
+ "data", /* SMFIM_DATA */
+ "eom", /* SMFIM_EOM < SMFIM_EOH */
+ "eoh", /* SMFIM_EOH > SMFIM_EOM */
+ 0,
+};
+
+static int set_macro_state;
+static char *set_macro_list;
+
+typedef sfsistat (*FILTER_ACTION) ();
+
+struct noproto_map {
+ const char *name;
+ int send_mask;
+ int reply_mask;
+ int *reply;
+ FILTER_ACTION *action;
+};
+
+static const struct noproto_map noproto_map[] = {
+ "connect", SMFIP_NOCONNECT, SMFIP_NR_CONN, &test_connect_reply, &smfilter.xxfi_connect,
+ "helo", SMFIP_NOHELO, SMFIP_NR_HELO, &test_helo_reply, &smfilter.xxfi_helo,
+ "mail", SMFIP_NOMAIL, SMFIP_NR_MAIL, &test_mail_reply, &smfilter.xxfi_envfrom,
+ "rcpt", SMFIP_NORCPT, SMFIP_NR_RCPT, &test_rcpt_reply, &smfilter.xxfi_envrcpt,
+ "data", SMFIP_NODATA, SMFIP_NR_DATA, &test_data_reply, &smfilter.xxfi_data,
+ "header", SMFIP_NOHDRS, SMFIP_NR_HDR, &test_header_reply, &smfilter.xxfi_header,
+ "eoh", SMFIP_NOEOH, SMFIP_NR_EOH, &test_eoh_reply, &smfilter.xxfi_eoh,
+ "body", SMFIP_NOBODY, SMFIP_NR_BODY, &test_body_reply, &smfilter.xxfi_body,
+ "unknown", SMFIP_NOUNKNOWN, SMFIP_NR_UNKN, &test_connect_reply, &smfilter.xxfi_unknown,
+ 0,
+};
+
+static int nosend_mask;
+static int noreply_mask;
+static int misc_mask;
+
+static sfsistat test_negotiate(SMFICTX *ctx,
+ unsigned long f0,
+ unsigned long f1,
+ unsigned long f2,
+ unsigned long f3,
+ unsigned long *pf0,
+ unsigned long *pf1,
+ unsigned long *pf2,
+ unsigned long *pf3)
+{
+ if (set_macro_list) {
+ if (verbose)
+ printf("set symbol list %s to \"%s\"\n",
+ macro_states[set_macro_state], set_macro_list);
+ smfi_setsymlist(ctx, set_macro_state, set_macro_list);
+ }
+ if (verbose)
+ printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n",
+ f0, *pf0, f1, *pf1, (long) nosend_mask, (long) noreply_mask, (long) misc_mask);
+ *pf0 = f0;
+ *pf1 = f1 & (nosend_mask | noreply_mask | misc_mask);
+ return (SMFIS_CONTINUE);
+}
+
+#endif
+
+static void parse_hdr_info(const char *optarg, int *idx,
+ char **hdr, char **value)
+{
+ int len;
+
+ len = strlen(optarg) + 1;
+ if ((*hdr = malloc(len)) == 0 || (*value = malloc(len)) == 0) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ if ((misc_mask & SMFIP_HDR_LEADSPC) == 0 ?
+ sscanf(optarg, "%d %s %[^\n]", idx, *hdr, *value) != 3 :
+ sscanf(optarg, "%d %[^ ]%[^\n]", idx, *hdr, *value) != 3) {
+ fprintf(stderr, "bad header info: %s\n", optarg);
+ exit(1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *action = 0;
+ char *command = 0;
+ const struct command_map *cp;
+ int ch;
+ int code;
+ const char **cpp;
+ char *set_macro_state_arg = 0;
+ char *nosend = 0;
+ char *noreply = 0;
+ const struct noproto_map *np;
+
+ while ((ch = getopt(argc, argv, "a:A:b:c:C:d:D:f:h:i:lm:M:n:N:p:rv")) > 0) {
+ switch (ch) {
+ case 'a':
+ action = optarg;
+ break;
+ case 'A':
+ if (add_rcpt_count >= MAX_RCPT) {
+ fprintf(stderr, "too many -A options\n");
+ exit(1);
+ }
+ add_rcpt[add_rcpt_count++] = optarg;
+ break;
+ case 'b':
+#ifdef SMFIR_REPLBODY
+ if (body_file) {
+ fprintf(stderr, "too many -b options\n");
+ exit(1);
+ }
+ body_file = optarg;
+#else
+ fprintf(stderr, "no libmilter support to replace body\n");
+#endif
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'd':
+ if (smfi_setdbg(atoi(optarg)) == MI_FAILURE) {
+ fprintf(stderr, "smfi_setdbg failed\n");
+ exit(1);
+ }
+ break;
+ case 'D':
+ if (del_rcpt_count >= MAX_RCPT) {
+ fprintf(stderr, "too many -D options\n");
+ exit(1);
+ }
+ del_rcpt[del_rcpt_count++] = optarg;
+ break;
+ case 'f':
+#ifdef SMFIR_CHGFROM
+ if (chg_from) {
+ fprintf(stderr, "too many -f options\n");
+ exit(1);
+ }
+ chg_from = optarg;
+#else
+ fprintf(stderr, "no libmilter support to change sender\n");
+ exit(1);
+#endif
+ break;
+ case 'h':
+#ifdef SMFIR_CHGHEADER
+ if (chg_hdr) {
+ fprintf(stderr, "too many -h options\n");
+ exit(1);
+ }
+ parse_hdr_info(optarg, &chg_idx, &chg_hdr, &chg_val);
+#else
+ fprintf(stderr, "no libmilter support to change header\n");
+ exit(1);
+#endif
+ break;
+ case 'i':
+#ifdef SMFIR_INSHEADER
+ if (ins_hdr) {
+ fprintf(stderr, "too many -i options\n");
+ exit(1);
+ }
+ parse_hdr_info(optarg, &ins_idx, &ins_hdr, &ins_val);
+#else
+ fprintf(stderr, "no libmilter support to insert header\n");
+ exit(1);
+#endif
+ break;
+ case 'l':
+#if SMFI_VERSION > 5
+ if (ins_hdr || chg_hdr) {
+ fprintf(stderr, "specify -l before -i or -r\n");
+ exit(1);
+ }
+ misc_mask |= SMFIP_HDR_LEADSPC;
+#else
+ fprintf(stderr, "no libmilter support for leading space\n");
+ exit(1);
+#endif
+ break;
+ case 'm':
+#if SMFI_VERSION > 5
+ if (set_macro_state_arg) {
+ fprintf(stderr, "too many -m options\n");
+ exit(1);
+ }
+ set_macro_state_arg = optarg;
+#else
+ fprintf(stderr, "no libmilter support to specify macro list\n");
+ exit(1);
+#endif
+ break;
+ case 'M':
+#if SMFI_VERSION > 5
+ if (set_macro_list) {
+ fprintf(stderr, "too many -M options\n");
+ exit(1);
+ }
+ set_macro_list = optarg;
+#else
+ fprintf(stderr, "no libmilter support to specify macro list\n");
+#endif
+ break;
+ case 'n':
+#if SMFI_VERSION > 5
+ if (nosend) {
+ fprintf(stderr, "too many -n options\n");
+ exit(1);
+ }
+ nosend = optarg;
+#else
+ fprintf(stderr, "no libmilter support for negotiate callback\n");
+#endif
+ break;
+ case 'N':
+#if SMFI_VERSION > 5
+ if (noreply) {
+ fprintf(stderr, "too many -n options\n");
+ exit(1);
+ }
+ noreply = optarg;
+#else
+ fprintf(stderr, "no libmilter support for negotiate callback\n");
+#endif
+ break;
+ case 'p':
+ if (smfi_setconn(optarg) == MI_FAILURE) {
+ fprintf(stderr, "smfi_setconn failed\n");
+ exit(1);
+ }
+ break;
+ case 'r':
+#ifdef SMFIP_RCPT_REJ
+ misc_mask |= SMFIP_RCPT_REJ;
+#else
+ fprintf(stderr, "no libmilter support for rejected recipients\n");
+#endif
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ conn_count = atoi(optarg);
+ break;
+ default:
+ fprintf(stderr,
+ "usage: %s [-dv] \n"
+ "\t[-a action] non-default action\n"
+ "\t[-b body_text] replace body\n"
+ "\t[-c command] non-default action trigger\n"
+ "\t[-h 'index label value'] replace header\n"
+ "\t[-i 'index label value'] insert header\n"
+ "\t[-m macro_state] non-default macro state\n"
+ "\t[-M macro_list] non-default macro list\n"
+ "\t[-n events] don't receive these events\n"
+ "\t[-N events] don't reply to these events\n"
+ "\t-p port milter application\n"
+ "\t-r request rejected recipients\n"
+ "\t[-C conn_count] when to exit\n",
+ argv[0]);
+ exit(1);
+ }
+ }
+ if (command) {
+ for (cp = command_map; /* see below */ ; cp++) {
+ if (cp->name == 0) {
+ fprintf(stderr, "bad -c argument: %s\n", command);
+ exit(1);
+ }
+ if (strcmp(command, cp->name) == 0)
+ break;
+ }
+ }
+ if (action) {
+ if (command == 0)
+ cp = command_map;
+ if (strcmp(action, "tempfail") == 0) {
+ cp->reply[0] = SMFIS_TEMPFAIL;
+ } else if (strcmp(action, "reject") == 0) {
+ cp->reply[0] = SMFIS_REJECT;
+ } else if (strcmp(action, "accept") == 0) {
+ cp->reply[0] = SMFIS_ACCEPT;
+ } else if (strcmp(action, "discard") == 0) {
+ cp->reply[0] = SMFIS_DISCARD;
+#ifdef SMFIS_SKIP
+ } else if (strcmp(action, "skip") == 0) {
+ cp->reply[0] = SMFIS_SKIP;
+#endif
+ } else if ((code = atoi(action)) >= 400
+ && code <= 599
+ && action[3] == ' ') {
+ cp->reply[0] = SMFIR_REPLYCODE;
+ reply_code = action;
+ reply_dsn = action + 3;
+ if (*reply_dsn != 0) {
+ *reply_dsn++ = 0;
+ reply_dsn += strspn(reply_dsn, " ");
+ }
+ if (*reply_dsn == 0) {
+ reply_dsn = reply_message = 0;
+ } else {
+ reply_message = reply_dsn + strcspn(reply_dsn, " ");
+ if (*reply_message != 0) {
+ *reply_message++ = 0;
+ reply_message += strspn(reply_message, " ");
+ }
+ if (*reply_message == 0)
+ reply_message = 0;
+ }
+ } else {
+ fprintf(stderr, "bad -a argument: %s\n", action);
+ exit(1);
+ }
+ if (verbose) {
+ printf("command %s action %d\n", cp->name, cp->reply[0]);
+ if (reply_code)
+ printf("reply code %s dsn %s message %s\n",
+ reply_code, reply_dsn ? reply_dsn : "(null)",
+ reply_message ? reply_message : "(null)");
+ }
+ }
+#if SMFI_VERSION > 5
+ if (set_macro_state_arg) {
+ for (cpp = macro_states; /* see below */ ; cpp++) {
+ if (*cpp == 0) {
+ fprintf(stderr, "bad -m argument: %s\n", set_macro_state_arg);
+ exit(1);
+ }
+ if (strcmp(set_macro_state_arg, *cpp) == 0)
+ break;
+ }
+ set_macro_state = cpp - macro_states;
+ }
+ if (nosend) {
+ for (np = noproto_map; /* see below */ ; np++) {
+ if (np->name == 0) {
+ fprintf(stderr, "bad -n argument: %s\n", nosend);
+ exit(1);
+ }
+ if (strcmp(nosend, np->name) == 0)
+ break;
+ }
+ nosend_mask = np->send_mask;
+ np->action[0] = 0;
+ }
+ if (noreply) {
+ for (np = noproto_map; /* see below */ ; np++) {
+ if (np->name == 0) {
+ fprintf(stderr, "bad -N argument: %s\n", noreply);
+ exit(1);
+ }
+ if (strcmp(noreply, np->name) == 0)
+ break;
+ }
+ noreply_mask = np->reply_mask;
+ *np->reply = SMFIS_NOREPLY;
+ }
+#endif
+ if (smfi_register(smfilter) == MI_FAILURE) {
+ fprintf(stderr, "smfi_register failed\n");
+ exit(1);
+ }
+ return (smfi_main());
+}
diff --git a/src/oqmgr/.indent.pro b/src/oqmgr/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/oqmgr/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/oqmgr/.printfck b/src/oqmgr/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/oqmgr/.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/oqmgr/Makefile.in b/src/oqmgr/Makefile.in
new file mode 100644
index 0000000..593042b
--- /dev/null
+++ b/src/oqmgr/Makefile.in
@@ -0,0 +1,362 @@
+SHELL = /bin/sh
+SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \
+ qmgr_message.c qmgr_deliver.c qmgr_move.c \
+ qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c qmgr_error.c \
+ qmgr_feedback.c
+OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \
+ qmgr_message.o qmgr_deliver.o qmgr_move.o \
+ qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o qmgr_error.o \
+ qmgr_feedback.o
+HDRS = qmgr.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = qmgr
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/o$(PROG)
+
+../../libexec/o$(PROG): $(PROG)
+ cp $(PROG) ../../libexec/o$(PROG)
+
+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
+
+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'
+qmgr.o: ../../include/argv.h
+qmgr.o: ../../include/attr.h
+qmgr.o: ../../include/check_arg.h
+qmgr.o: ../../include/dict.h
+qmgr.o: ../../include/dsn.h
+qmgr.o: ../../include/events.h
+qmgr.o: ../../include/flush_clnt.h
+qmgr.o: ../../include/htable.h
+qmgr.o: ../../include/iostuff.h
+qmgr.o: ../../include/mail_conf.h
+qmgr.o: ../../include/mail_flow.h
+qmgr.o: ../../include/mail_params.h
+qmgr.o: ../../include/mail_proto.h
+qmgr.o: ../../include/mail_queue.h
+qmgr.o: ../../include/mail_server.h
+qmgr.o: ../../include/mail_version.h
+qmgr.o: ../../include/master_proto.h
+qmgr.o: ../../include/msg.h
+qmgr.o: ../../include/myflock.h
+qmgr.o: ../../include/mymalloc.h
+qmgr.o: ../../include/nvtable.h
+qmgr.o: ../../include/recipient_list.h
+qmgr.o: ../../include/scan_dir.h
+qmgr.o: ../../include/sys_defs.h
+qmgr.o: ../../include/vbuf.h
+qmgr.o: ../../include/vstream.h
+qmgr.o: ../../include/vstring.h
+qmgr.o: qmgr.c
+qmgr.o: qmgr.h
+qmgr_active.o: ../../include/abounce.h
+qmgr_active.o: ../../include/attr.h
+qmgr_active.o: ../../include/bounce.h
+qmgr_active.o: ../../include/check_arg.h
+qmgr_active.o: ../../include/defer.h
+qmgr_active.o: ../../include/deliver_request.h
+qmgr_active.o: ../../include/dsn.h
+qmgr_active.o: ../../include/dsn_buf.h
+qmgr_active.o: ../../include/dsn_mask.h
+qmgr_active.o: ../../include/events.h
+qmgr_active.o: ../../include/htable.h
+qmgr_active.o: ../../include/info_log_addr_form.h
+qmgr_active.o: ../../include/mail_open_ok.h
+qmgr_active.o: ../../include/mail_params.h
+qmgr_active.o: ../../include/mail_queue.h
+qmgr_active.o: ../../include/msg.h
+qmgr_active.o: ../../include/msg_stats.h
+qmgr_active.o: ../../include/mymalloc.h
+qmgr_active.o: ../../include/nvtable.h
+qmgr_active.o: ../../include/qmgr_user.h
+qmgr_active.o: ../../include/rec_type.h
+qmgr_active.o: ../../include/recipient_list.h
+qmgr_active.o: ../../include/scan_dir.h
+qmgr_active.o: ../../include/sys_defs.h
+qmgr_active.o: ../../include/trace.h
+qmgr_active.o: ../../include/vbuf.h
+qmgr_active.o: ../../include/vstream.h
+qmgr_active.o: ../../include/vstring.h
+qmgr_active.o: ../../include/warn_stat.h
+qmgr_active.o: qmgr.h
+qmgr_active.o: qmgr_active.c
+qmgr_bounce.o: ../../include/attr.h
+qmgr_bounce.o: ../../include/bounce.h
+qmgr_bounce.o: ../../include/check_arg.h
+qmgr_bounce.o: ../../include/deliver_completed.h
+qmgr_bounce.o: ../../include/deliver_request.h
+qmgr_bounce.o: ../../include/dsn.h
+qmgr_bounce.o: ../../include/dsn_buf.h
+qmgr_bounce.o: ../../include/htable.h
+qmgr_bounce.o: ../../include/msg_stats.h
+qmgr_bounce.o: ../../include/mymalloc.h
+qmgr_bounce.o: ../../include/nvtable.h
+qmgr_bounce.o: ../../include/recipient_list.h
+qmgr_bounce.o: ../../include/scan_dir.h
+qmgr_bounce.o: ../../include/sys_defs.h
+qmgr_bounce.o: ../../include/vbuf.h
+qmgr_bounce.o: ../../include/vstream.h
+qmgr_bounce.o: ../../include/vstring.h
+qmgr_bounce.o: qmgr.h
+qmgr_bounce.o: qmgr_bounce.c
+qmgr_defer.o: ../../include/attr.h
+qmgr_defer.o: ../../include/bounce.h
+qmgr_defer.o: ../../include/check_arg.h
+qmgr_defer.o: ../../include/defer.h
+qmgr_defer.o: ../../include/deliver_request.h
+qmgr_defer.o: ../../include/dsn.h
+qmgr_defer.o: ../../include/dsn_buf.h
+qmgr_defer.o: ../../include/htable.h
+qmgr_defer.o: ../../include/iostuff.h
+qmgr_defer.o: ../../include/mail_proto.h
+qmgr_defer.o: ../../include/msg.h
+qmgr_defer.o: ../../include/msg_stats.h
+qmgr_defer.o: ../../include/mymalloc.h
+qmgr_defer.o: ../../include/nvtable.h
+qmgr_defer.o: ../../include/recipient_list.h
+qmgr_defer.o: ../../include/scan_dir.h
+qmgr_defer.o: ../../include/sys_defs.h
+qmgr_defer.o: ../../include/vbuf.h
+qmgr_defer.o: ../../include/vstream.h
+qmgr_defer.o: ../../include/vstring.h
+qmgr_defer.o: qmgr.h
+qmgr_defer.o: qmgr_defer.c
+qmgr_deliver.o: ../../include/attr.h
+qmgr_deliver.o: ../../include/check_arg.h
+qmgr_deliver.o: ../../include/deliver_request.h
+qmgr_deliver.o: ../../include/dsb_scan.h
+qmgr_deliver.o: ../../include/dsn.h
+qmgr_deliver.o: ../../include/dsn_buf.h
+qmgr_deliver.o: ../../include/dsn_util.h
+qmgr_deliver.o: ../../include/events.h
+qmgr_deliver.o: ../../include/htable.h
+qmgr_deliver.o: ../../include/iostuff.h
+qmgr_deliver.o: ../../include/mail_params.h
+qmgr_deliver.o: ../../include/mail_proto.h
+qmgr_deliver.o: ../../include/mail_queue.h
+qmgr_deliver.o: ../../include/msg.h
+qmgr_deliver.o: ../../include/msg_stats.h
+qmgr_deliver.o: ../../include/mymalloc.h
+qmgr_deliver.o: ../../include/nvtable.h
+qmgr_deliver.o: ../../include/rcpt_print.h
+qmgr_deliver.o: ../../include/recipient_list.h
+qmgr_deliver.o: ../../include/scan_dir.h
+qmgr_deliver.o: ../../include/smtputf8.h
+qmgr_deliver.o: ../../include/stringops.h
+qmgr_deliver.o: ../../include/sys_defs.h
+qmgr_deliver.o: ../../include/vbuf.h
+qmgr_deliver.o: ../../include/verp_sender.h
+qmgr_deliver.o: ../../include/vstream.h
+qmgr_deliver.o: ../../include/vstring.h
+qmgr_deliver.o: ../../include/vstring_vstream.h
+qmgr_deliver.o: qmgr.h
+qmgr_deliver.o: qmgr_deliver.c
+qmgr_enable.o: ../../include/check_arg.h
+qmgr_enable.o: ../../include/dsn.h
+qmgr_enable.o: ../../include/msg.h
+qmgr_enable.o: ../../include/recipient_list.h
+qmgr_enable.o: ../../include/scan_dir.h
+qmgr_enable.o: ../../include/sys_defs.h
+qmgr_enable.o: ../../include/vbuf.h
+qmgr_enable.o: ../../include/vstream.h
+qmgr_enable.o: qmgr.h
+qmgr_enable.o: qmgr_enable.c
+qmgr_entry.o: ../../include/attr.h
+qmgr_entry.o: ../../include/check_arg.h
+qmgr_entry.o: ../../include/deliver_request.h
+qmgr_entry.o: ../../include/dsn.h
+qmgr_entry.o: ../../include/events.h
+qmgr_entry.o: ../../include/htable.h
+qmgr_entry.o: ../../include/mail_params.h
+qmgr_entry.o: ../../include/msg.h
+qmgr_entry.o: ../../include/msg_stats.h
+qmgr_entry.o: ../../include/mymalloc.h
+qmgr_entry.o: ../../include/nvtable.h
+qmgr_entry.o: ../../include/recipient_list.h
+qmgr_entry.o: ../../include/scan_dir.h
+qmgr_entry.o: ../../include/sys_defs.h
+qmgr_entry.o: ../../include/vbuf.h
+qmgr_entry.o: ../../include/vstream.h
+qmgr_entry.o: ../../include/vstring.h
+qmgr_entry.o: qmgr.h
+qmgr_entry.o: qmgr_entry.c
+qmgr_error.o: ../../include/check_arg.h
+qmgr_error.o: ../../include/dsn.h
+qmgr_error.o: ../../include/mymalloc.h
+qmgr_error.o: ../../include/recipient_list.h
+qmgr_error.o: ../../include/scan_dir.h
+qmgr_error.o: ../../include/stringops.h
+qmgr_error.o: ../../include/sys_defs.h
+qmgr_error.o: ../../include/vbuf.h
+qmgr_error.o: ../../include/vstream.h
+qmgr_error.o: ../../include/vstring.h
+qmgr_error.o: qmgr.h
+qmgr_error.o: qmgr_error.c
+qmgr_feedback.o: ../../include/check_arg.h
+qmgr_feedback.o: ../../include/dsn.h
+qmgr_feedback.o: ../../include/mail_conf.h
+qmgr_feedback.o: ../../include/mail_params.h
+qmgr_feedback.o: ../../include/msg.h
+qmgr_feedback.o: ../../include/mymalloc.h
+qmgr_feedback.o: ../../include/name_code.h
+qmgr_feedback.o: ../../include/recipient_list.h
+qmgr_feedback.o: ../../include/scan_dir.h
+qmgr_feedback.o: ../../include/stringops.h
+qmgr_feedback.o: ../../include/sys_defs.h
+qmgr_feedback.o: ../../include/vbuf.h
+qmgr_feedback.o: ../../include/vstream.h
+qmgr_feedback.o: ../../include/vstring.h
+qmgr_feedback.o: qmgr.h
+qmgr_feedback.o: qmgr_feedback.c
+qmgr_message.o: ../../include/argv.h
+qmgr_message.o: ../../include/attr.h
+qmgr_message.o: ../../include/bounce.h
+qmgr_message.o: ../../include/canon_addr.h
+qmgr_message.o: ../../include/check_arg.h
+qmgr_message.o: ../../include/deliver_completed.h
+qmgr_message.o: ../../include/deliver_request.h
+qmgr_message.o: ../../include/dict.h
+qmgr_message.o: ../../include/dsn.h
+qmgr_message.o: ../../include/dsn_buf.h
+qmgr_message.o: ../../include/dsn_mask.h
+qmgr_message.o: ../../include/htable.h
+qmgr_message.o: ../../include/iostuff.h
+qmgr_message.o: ../../include/mail_params.h
+qmgr_message.o: ../../include/mail_proto.h
+qmgr_message.o: ../../include/mail_queue.h
+qmgr_message.o: ../../include/msg.h
+qmgr_message.o: ../../include/msg_stats.h
+qmgr_message.o: ../../include/myflock.h
+qmgr_message.o: ../../include/mymalloc.h
+qmgr_message.o: ../../include/nvtable.h
+qmgr_message.o: ../../include/opened.h
+qmgr_message.o: ../../include/qmgr_user.h
+qmgr_message.o: ../../include/rec_attr_map.h
+qmgr_message.o: ../../include/rec_type.h
+qmgr_message.o: ../../include/recipient_list.h
+qmgr_message.o: ../../include/record.h
+qmgr_message.o: ../../include/resolve_clnt.h
+qmgr_message.o: ../../include/rewrite_clnt.h
+qmgr_message.o: ../../include/scan_dir.h
+qmgr_message.o: ../../include/sent.h
+qmgr_message.o: ../../include/split_addr.h
+qmgr_message.o: ../../include/split_at.h
+qmgr_message.o: ../../include/stringops.h
+qmgr_message.o: ../../include/sys_defs.h
+qmgr_message.o: ../../include/valid_hostname.h
+qmgr_message.o: ../../include/vbuf.h
+qmgr_message.o: ../../include/verp_sender.h
+qmgr_message.o: ../../include/vstream.h
+qmgr_message.o: ../../include/vstring.h
+qmgr_message.o: qmgr.h
+qmgr_message.o: qmgr_message.c
+qmgr_move.o: ../../include/check_arg.h
+qmgr_move.o: ../../include/dsn.h
+qmgr_move.o: ../../include/mail_queue.h
+qmgr_move.o: ../../include/mail_scan_dir.h
+qmgr_move.o: ../../include/msg.h
+qmgr_move.o: ../../include/recipient_list.h
+qmgr_move.o: ../../include/scan_dir.h
+qmgr_move.o: ../../include/sys_defs.h
+qmgr_move.o: ../../include/vbuf.h
+qmgr_move.o: ../../include/vstream.h
+qmgr_move.o: ../../include/vstring.h
+qmgr_move.o: qmgr.h
+qmgr_move.o: qmgr_move.c
+qmgr_queue.o: ../../include/attr.h
+qmgr_queue.o: ../../include/check_arg.h
+qmgr_queue.o: ../../include/dsn.h
+qmgr_queue.o: ../../include/events.h
+qmgr_queue.o: ../../include/htable.h
+qmgr_queue.o: ../../include/iostuff.h
+qmgr_queue.o: ../../include/mail_params.h
+qmgr_queue.o: ../../include/mail_proto.h
+qmgr_queue.o: ../../include/msg.h
+qmgr_queue.o: ../../include/mymalloc.h
+qmgr_queue.o: ../../include/nvtable.h
+qmgr_queue.o: ../../include/recipient_list.h
+qmgr_queue.o: ../../include/scan_dir.h
+qmgr_queue.o: ../../include/sys_defs.h
+qmgr_queue.o: ../../include/vbuf.h
+qmgr_queue.o: ../../include/vstream.h
+qmgr_queue.o: ../../include/vstring.h
+qmgr_queue.o: qmgr.h
+qmgr_queue.o: qmgr_queue.c
+qmgr_scan.o: ../../include/check_arg.h
+qmgr_scan.o: ../../include/dsn.h
+qmgr_scan.o: ../../include/mail_scan_dir.h
+qmgr_scan.o: ../../include/msg.h
+qmgr_scan.o: ../../include/mymalloc.h
+qmgr_scan.o: ../../include/recipient_list.h
+qmgr_scan.o: ../../include/scan_dir.h
+qmgr_scan.o: ../../include/sys_defs.h
+qmgr_scan.o: ../../include/vbuf.h
+qmgr_scan.o: ../../include/vstream.h
+qmgr_scan.o: qmgr.h
+qmgr_scan.o: qmgr_scan.c
+qmgr_transport.o: ../../include/attr.h
+qmgr_transport.o: ../../include/check_arg.h
+qmgr_transport.o: ../../include/dsn.h
+qmgr_transport.o: ../../include/events.h
+qmgr_transport.o: ../../include/htable.h
+qmgr_transport.o: ../../include/iostuff.h
+qmgr_transport.o: ../../include/mail_conf.h
+qmgr_transport.o: ../../include/mail_params.h
+qmgr_transport.o: ../../include/mail_proto.h
+qmgr_transport.o: ../../include/msg.h
+qmgr_transport.o: ../../include/mymalloc.h
+qmgr_transport.o: ../../include/nvtable.h
+qmgr_transport.o: ../../include/recipient_list.h
+qmgr_transport.o: ../../include/scan_dir.h
+qmgr_transport.o: ../../include/sys_defs.h
+qmgr_transport.o: ../../include/vbuf.h
+qmgr_transport.o: ../../include/vstream.h
+qmgr_transport.o: ../../include/vstring.h
+qmgr_transport.o: qmgr.h
+qmgr_transport.o: qmgr_transport.c
diff --git a/src/oqmgr/qmgr.c b/src/oqmgr/qmgr.c
new file mode 100644
index 0000000..02573f1
--- /dev/null
+++ b/src/oqmgr/qmgr.c
@@ -0,0 +1,736 @@
+/*++
+/* NAME
+/* qmgr 8
+/* SUMMARY
+/* old Postfix queue manager
+/* SYNOPSIS
+/* \fBqmgr\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBqmgr\fR(8) daemon awaits the arrival of incoming mail
+/* and arranges for its delivery via Postfix delivery processes.
+/* The actual mail routing strategy is delegated to the
+/* \fBtrivial-rewrite\fR(8) daemon.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* Mail addressed to the local \fBdouble-bounce\fR address is
+/* logged and discarded. This stops potential loops caused by
+/* undeliverable bounce notifications.
+/* MAIL QUEUES
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon maintains the following queues:
+/* .IP \fBincoming\fR
+/* Inbound mail from the network, or mail picked up by the
+/* local \fBpickup\fR(8) agent from the \fBmaildrop\fR directory.
+/* .IP \fBactive\fR
+/* Messages that the queue manager has opened for delivery. Only
+/* a limited number of messages is allowed to enter the \fBactive\fR
+/* queue (leaky bucket strategy, for a fixed delivery rate).
+/* .IP \fBdeferred\fR
+/* Mail that could not be delivered upon the first attempt. The queue
+/* manager implements exponential backoff by doubling the time between
+/* delivery attempts.
+/* .IP \fBcorrupt\fR
+/* Unreadable or damaged queue files are moved here for inspection.
+/* .IP \fBhold\fR
+/* Messages that are kept "on hold" are kept here until someone
+/* sets them free.
+/* DELIVERY STATUS REPORTS
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon keeps an eye on per-message delivery status
+/* reports in the following directories. Each status report file has
+/* the same name as the corresponding message file:
+/* .IP \fBbounce\fR
+/* Per-recipient status information about why mail is bounced.
+/* These files are maintained by the \fBbounce\fR(8) daemon.
+/* .IP \fBdefer\fR
+/* Per-recipient status information about why mail is delayed.
+/* These files are maintained by the \fBdefer\fR(8) daemon.
+/* .IP \fBtrace\fR
+/* Per-recipient status information as requested with the
+/* Postfix "\fBsendmail -v\fR" or "\fBsendmail -bv\fR" command.
+/* These files are maintained by the \fBtrace\fR(8) daemon.
+/* .PP
+/* The \fBqmgr\fR(8) daemon is responsible for asking the
+/* \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemons to
+/* send delivery reports.
+/* STRATEGIES
+/* .ad
+/* .fi
+/* The queue manager implements a variety of strategies for
+/* either opening queue files (input) or for message delivery (output).
+/* .IP "\fBleaky bucket\fR"
+/* This strategy limits the number of messages in the \fBactive\fR queue
+/* and prevents the queue manager from running out of memory under
+/* heavy load.
+/* .IP \fBfairness\fR
+/* When the \fBactive\fR queue has room, the queue manager takes one
+/* message from the \fBincoming\fR queue and one from the \fBdeferred\fR
+/* queue. This prevents a large mail backlog from blocking the delivery
+/* of new mail.
+/* .IP "\fBslow start\fR"
+/* This strategy eliminates "thundering herd" problems by slowly
+/* adjusting the number of parallel deliveries to the same destination.
+/* .IP "\fBround robin\fR"
+/* The queue manager sorts delivery requests by destination.
+/* Round-robin selection prevents one destination from dominating
+/* deliveries to other destinations.
+/* .IP "\fBexponential backoff\fR"
+/* Mail that cannot be delivered upon the first attempt is deferred.
+/* The time interval between delivery attempts is doubled after each
+/* attempt.
+/* .IP "\fBdestination status cache\fR"
+/* The queue manager avoids unnecessary delivery attempts by
+/* maintaining a short-term, in-memory list of unreachable destinations.
+/* TRIGGERS
+/* .ad
+/* .fi
+/* On an idle system, the queue manager waits for the arrival of
+/* trigger events, or it waits for a timer to go off. A trigger
+/* is a one-byte message.
+/* Depending on the message received, the queue manager performs
+/* one of the following actions (the message is followed by the
+/* symbolic constant used internally by the software):
+/* .IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR"
+/* Start a deferred queue scan. If a deferred queue scan is already
+/* in progress, that scan will be restarted as soon as it finishes.
+/* .IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR"
+/* Start an incoming queue scan. If an incoming queue scan is already
+/* in progress, that scan will be restarted as soon as it finishes.
+/* .IP "\fBA (QMGR_REQ_SCAN_ALL)\fR"
+/* Ignore deferred queue file time stamps. The request affects
+/* the next deferred queue scan.
+/* .IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR"
+/* Purge all information about dead transports and destinations.
+/* .IP "\fBW (TRIGGER_REQ_WAKEUP)\fR"
+/* Wakeup call, This is used by the master server to instantiate
+/* servers that should not go away forever. The action is to start
+/* an incoming queue scan.
+/* .PP
+/* The \fBqmgr\fR(8) daemon reads an entire buffer worth of triggers.
+/* Multiple identical trigger requests are collapsed into one, and
+/* trigger requests are sorted so that \fBA\fR and \fBF\fR precede
+/* \fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run,
+/* one would request \fBA F D\fR; in order to notify the queue manager
+/* of the arrival of new mail one would request \fBI\fR.
+/* STANDARDS
+/* RFC 3463 (Enhanced status codes)
+/* RFC 3464 (Delivery status notifications)
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon is not security sensitive. It reads
+/* single-character messages from untrusted local users, and thus may
+/* be susceptible to denial of service attacks. The \fBqmgr\fR(8) daemon
+/* does not talk to the outside world, and it can be run at fixed low
+/* privilege in a chrooted environment.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to the \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8) daemon.
+/* Corrupted message files are saved to the \fBcorrupt\fR queue
+/* for further inspection.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* BUGS
+/* A single queue manager process has to compete for disk access with
+/* multiple front-end processes such as \fBcleanup\fR(8). A sudden burst of
+/* inbound mail can negatively impact outbound delivery rates.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* as \fBqmgr\fR(8)
+/* is a persistent process. Use the command "\fBpostfix reload\fR" after
+/* a configuration change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/*
+/* In the text below, \fItransport\fR is the first field in a
+/* \fBmaster.cf\fR entry.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* Available before Postfix version 2.5:
+/* .IP "\fBallow_min_user (no)\fR"
+/* Allow a sender or recipient address to have `-' as the first
+/* character.
+/* .PP
+/* Available with Postfix version 2.7 and later:
+/* .IP "\fBdefault_filter_nexthop (empty)\fR"
+/* When a content_filter or FILTER request specifies no explicit
+/* next-hop destination, use $default_filter_nexthop instead; when
+/* that value is empty, use the domain in the recipient address.
+/* ACTIVE QUEUE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmgr_clog_warn_time (300s)\fR"
+/* The minimal delay between warnings that a specific destination is
+/* clogging up the Postfix active queue.
+/* .IP "\fBqmgr_message_active_limit (20000)\fR"
+/* The maximal number of messages in the active queue.
+/* .IP "\fBqmgr_message_recipient_limit (20000)\fR"
+/* The maximal number of recipients held in memory by the Postfix
+/* queue manager, and the maximal size of the short-term,
+/* in-memory "dead" destination status cache.
+/* DELIVERY CONCURRENCY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmgr_fudge_factor (100)\fR"
+/* Obsolete feature: the percentage of delivery resources that a busy
+/* mail system will use up for delivery of a large mailing list
+/* message.
+/* .IP "\fBinitial_destination_concurrency (5)\fR"
+/* The initial per-destination concurrency level for parallel delivery
+/* to the same destination.
+/* .IP "\fBdefault_destination_concurrency_limit (20)\fR"
+/* The default maximal number of parallel deliveries to the same
+/* destination.
+/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBtransport_initial_destination_concurrency ($initial_destination_concurrency)\fR"
+/* A transport-specific override for the initial_destination_concurrency
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_destination_concurrency_failed_cohort_limit (1)\fR"
+/* How many pseudo-cohorts must suffer connection or handshake
+/* failure before a specific destination is considered unavailable
+/* (and further delivery is suspended).
+/* .IP "\fBtransport_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_failed_cohort_limit parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR"
+/* The per-destination amount of delivery concurrency negative
+/* feedback, after a delivery completes with a connection or handshake
+/* failure.
+/* .IP "\fBtransport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_negative_feedback parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR"
+/* The per-destination amount of delivery concurrency positive
+/* feedback, after a delivery completes without connection or handshake
+/* failure.
+/* .IP "\fBtransport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_positive_feedback parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdestination_concurrency_feedback_debug (no)\fR"
+/* Make the queue manager's feedback algorithm verbose for performance
+/* analysis purposes.
+/* RECIPIENT SCHEDULING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdefault_destination_recipient_limit (50)\fR"
+/* The default maximal number of recipients per message delivery.
+/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_recipient_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* OTHER RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBminimal_backoff_time (300s)\fR"
+/* The minimal time between attempts to deliver a deferred message;
+/* prior to Postfix 2.4 the default value was 1000s.
+/* .IP "\fBmaximal_backoff_time (4000s)\fR"
+/* The maximal time between attempts to deliver a deferred message.
+/* .IP "\fBmaximal_queue_lifetime (5d)\fR"
+/* Consider a message as undeliverable, when delivery fails with a
+/* temporary error, and the time in the queue has reached the
+/* maximal_queue_lifetime limit.
+/* .IP "\fBqueue_run_delay (300s)\fR"
+/* The time between deferred queue scans by the queue manager;
+/* prior to Postfix 2.4 the default value was 1000s.
+/* .IP "\fBtransport_retry_time (60s)\fR"
+/* The time between attempts by the Postfix queue manager to contact
+/* a malfunctioning message delivery transport.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBbounce_queue_lifetime (5d)\fR"
+/* Consider a bounce message as undeliverable, when delivery fails
+/* with a temporary error, and the time in the queue has reached the
+/* bounce_queue_lifetime limit.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBdefault_destination_rate_delay (0s)\fR"
+/* The default amount of delay that is inserted between individual
+/* message deliveries to the same destination and over the same message
+/* delivery transport.
+/* .IP "\fBtransport_destination_rate_delay ($default_destination_rate_delay)\fR"
+/* A transport-specific override for the default_destination_rate_delay
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBdefault_transport_rate_delay (0s)\fR"
+/* The default amount of delay that is inserted between individual
+/* message deliveries over the same message delivery transport,
+/* regardless of destination.
+/* .IP "\fBtransport_transport_rate_delay ($default_transport_rate_delay)\fR"
+/* A transport-specific override for the default_transport_rate_delay
+/* parameter value, where the initial \fItransport\fR in the parameter
+/* name is the master.cf name of the message delivery transport.
+/* SAFETY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmgr_daemon_timeout (1000s)\fR"
+/* How much time a Postfix queue manager process may take to handle
+/* a request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBqmgr_ipc_timeout (60s)\fR"
+/* The time limit for the queue manager to send or receive information
+/* over an internal communication channel.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBaddress_verify_pending_request_limit (see 'postconf -d' output)\fR"
+/* A safety limit that prevents address verification requests from
+/* overwhelming the Postfix queue.
+/* 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 "\fBdefer_transports (empty)\fR"
+/* The names of message delivery transports that should not deliver mail
+/* unless someone issues "\fBsendmail -q\fR" or equivalent.
+/* .IP "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBhelpful_warnings (yes)\fR"
+/* Log warnings about problematic configuration settings, and provide
+/* helpful suggestions.
+/* .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 "\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 3.0 and later:
+/* .IP "\fBconfirm_delay_cleared (no)\fR"
+/* After sending a "your message is delayed" notification, inform
+/* the sender when the delay clears up.
+/* .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
+/* /var/spool/postfix/incoming, incoming queue
+/* /var/spool/postfix/active, active queue
+/* /var/spool/postfix/deferred, deferred queue
+/* /var/spool/postfix/bounce, non-delivery status
+/* /var/spool/postfix/defer, non-delivery status
+/* /var/spool/postfix/trace, delivery status
+/* SEE ALSO
+/* trivial-rewrite(8), address routing
+/* bounce(8), delivery status reports
+/* 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
+/* QSHAPE_README, Postfix queue analysis
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <vstream.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h> /* QMGR_SCAN constants */
+#include <mail_flow.h>
+#include <flush_clnt.h>
+
+/* Master process interface */
+
+#include <master_proto.h>
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Tunables.
+ */
+int var_queue_run_delay;
+int var_min_backoff_time;
+int var_max_backoff_time;
+int var_max_queue_time;
+int var_dsn_queue_time;
+int var_qmgr_active_limit;
+int var_qmgr_rcpt_limit;
+int var_init_dest_concurrency;
+int var_transport_retry_time;
+int var_dest_con_limit;
+int var_dest_rcpt_limit;
+char *var_defer_xports;
+int var_qmgr_fudge;
+int var_local_rcpt_lim; /* XXX */
+int var_local_con_lim; /* XXX */
+bool var_verp_bounce_off;
+int var_qmgr_clog_warn_time;
+char *var_conc_pos_feedback;
+char *var_conc_neg_feedback;
+int var_conc_cohort_limit;
+int var_conc_feedback_debug;
+int var_xport_rate_delay;
+int var_dest_rate_delay;
+char *var_def_filter_nexthop;
+int var_qmgr_daemon_timeout;
+int var_qmgr_ipc_timeout;
+int var_dsn_delay_cleared;
+int var_vrfy_pend_limit;
+
+static QMGR_SCAN *qmgr_scans[2];
+
+#define QMGR_SCAN_IDX_INCOMING 0
+#define QMGR_SCAN_IDX_DEFERRED 1
+#define QMGR_SCAN_IDX_COUNT (sizeof(qmgr_scans) / sizeof(qmgr_scans[0]))
+
+/* qmgr_deferred_run_event - queue manager heartbeat */
+
+static void qmgr_deferred_run_event(int unused_event, void *dummy)
+{
+
+ /*
+ * This routine runs when it is time for another deferred queue scan.
+ * Make sure this routine gets called again in the future.
+ */
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], QMGR_SCAN_START);
+ event_request_timer(qmgr_deferred_run_event, dummy, var_queue_run_delay);
+}
+
+/* qmgr_trigger_event - respond to external trigger(s) */
+
+static void qmgr_trigger_event(char *buf, ssize_t len,
+ char *unused_service, char **argv)
+{
+ int incoming_flag = 0;
+ int deferred_flag = 0;
+ int i;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Collapse identical requests that have arrived since we looked last
+ * time. There is no client feedback so there is no need to process each
+ * request in order. And as long as we don't have conflicting requests we
+ * are free to sort them into the most suitable order.
+ */
+#define QMGR_FLUSH_BEFORE (QMGR_FLUSH_ONCE | QMGR_FLUSH_DFXP)
+
+ for (i = 0; i < len; i++) {
+ if (msg_verbose)
+ msg_info("request: %d (%c)",
+ buf[i], ISALNUM(buf[i]) ? buf[i] : '?');
+ switch (buf[i]) {
+ case TRIGGER_REQ_WAKEUP:
+ case QMGR_REQ_SCAN_INCOMING:
+ incoming_flag |= QMGR_SCAN_START;
+ break;
+ case QMGR_REQ_SCAN_DEFERRED:
+ deferred_flag |= QMGR_SCAN_START;
+ break;
+ case QMGR_REQ_FLUSH_DEAD:
+ deferred_flag |= QMGR_FLUSH_BEFORE;
+ incoming_flag |= QMGR_FLUSH_BEFORE;
+ break;
+ case QMGR_REQ_SCAN_ALL:
+ deferred_flag |= QMGR_SCAN_ALL;
+ incoming_flag |= QMGR_SCAN_ALL;
+ break;
+ default:
+ if (msg_verbose)
+ msg_info("request ignored");
+ break;
+ }
+ }
+
+ /*
+ * Process each request type at most once. Modifiers take effect upon the
+ * next queue run. If no queue run is in progress, and a queue scan is
+ * requested, the request takes effect immediately.
+ */
+ if (incoming_flag != 0)
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], incoming_flag);
+ if (deferred_flag != 0)
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], deferred_flag);
+}
+
+/* qmgr_loop - queue manager main loop */
+
+static int qmgr_loop(char *unused_name, char **unused_argv)
+{
+ char *path;
+ ssize_t token_count;
+ int feed = 0;
+ int scan_idx; /* Priority order scan index */
+ static int first_scan_idx = QMGR_SCAN_IDX_INCOMING;
+ int last_scan_idx = QMGR_SCAN_IDX_COUNT - 1;
+ int delay;
+
+ /*
+ * This routine runs as part of the event handling loop, after the event
+ * manager has delivered a timer or I/O event (including the completion
+ * of a connection to a delivery process), or after it has waited for a
+ * specified amount of time. The result value of qmgr_loop() specifies
+ * how long the event manager should wait for the next event.
+ */
+#define DONT_WAIT 0
+#define WAIT_FOR_EVENT (-1)
+
+ /*
+ * Attempt to drain the active queue by allocating a suitable delivery
+ * process and by delivering mail via it. Delivery process allocation and
+ * mail delivery are asynchronous.
+ */
+ qmgr_active_drain();
+
+ /*
+ * Let some new blood into the active queue when the queue size is
+ * smaller than some configurable limit, and when the number of in-core
+ * recipients does not exceed some configurable limit.
+ *
+ * We import one message per interrupt, to optimally tune the input count
+ * for the number of delivery agent protocol wait states, as explained in
+ * qmgr_transport.c.
+ */
+ delay = WAIT_FOR_EVENT;
+ for (scan_idx = 0; qmgr_message_count < var_qmgr_active_limit
+ && qmgr_recipient_count < var_qmgr_rcpt_limit
+ && scan_idx < QMGR_SCAN_IDX_COUNT; ++scan_idx) {
+ last_scan_idx = (scan_idx + first_scan_idx) % QMGR_SCAN_IDX_COUNT;
+ if ((path = qmgr_scan_next(qmgr_scans[last_scan_idx])) != 0) {
+ delay = DONT_WAIT;
+ if ((feed = qmgr_active_feed(qmgr_scans[last_scan_idx], path)) != 0)
+ break;
+ }
+ }
+
+ /*
+ * Round-robin the queue scans. When the active queue becomes full,
+ * prefer new mail over deferred mail.
+ */
+ if (qmgr_message_count < var_qmgr_active_limit
+ && qmgr_recipient_count < var_qmgr_rcpt_limit) {
+ first_scan_idx = (last_scan_idx + 1) % QMGR_SCAN_IDX_COUNT;
+ } else if (first_scan_idx != QMGR_SCAN_IDX_INCOMING) {
+ first_scan_idx = QMGR_SCAN_IDX_INCOMING;
+ }
+
+ /*
+ * Global flow control. If enabled, slow down receiving processes that
+ * get ahead of the queue manager, but don't block them completely.
+ */
+ if (var_in_flow_delay > 0) {
+ token_count = mail_flow_count();
+ if (token_count < var_proc_limit) {
+ if (feed != 0 && last_scan_idx == QMGR_SCAN_IDX_INCOMING)
+ mail_flow_put(1);
+ else if (qmgr_scans[QMGR_SCAN_IDX_INCOMING]->handle == 0)
+ mail_flow_put(var_proc_limit - token_count);
+ } else if (token_count > var_proc_limit) {
+ mail_flow_get(token_count - var_proc_limit);
+ }
+ }
+ return (delay);
+}
+
+/* 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);
+ }
+}
+
+/* qmgr_pre_init - pre-jail initialization */
+
+static void qmgr_pre_init(char *unused_name, char **unused_argv)
+{
+ flush_init();
+}
+
+/* qmgr_post_init - post-jail initialization */
+
+static void qmgr_post_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (var_qmgr_rcpt_limit < var_qmgr_active_limit) {
+ msg_warn("%s is smaller than %s - adjusting %s",
+ VAR_QMGR_RCPT_LIMIT, VAR_QMGR_ACT_LIMIT, VAR_QMGR_RCPT_LIMIT);
+ var_qmgr_rcpt_limit = var_qmgr_active_limit;
+ }
+ if (var_dsn_queue_time > var_max_queue_time) {
+ msg_warn("%s is larger than %s - adjusting %s",
+ VAR_DSN_QUEUE_TIME, VAR_MAX_QUEUE_TIME, VAR_DSN_QUEUE_TIME);
+ var_dsn_queue_time = var_max_queue_time;
+ }
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail.
+ * Prevent automatic process suicide after a limited number of client
+ * requests or after a limited amount of idle time. Move any left-over
+ * entries from the active queue to the incoming queue, and give them a
+ * time stamp into the future, in order to allow ongoing deliveries to
+ * finish first. Start scanning the incoming and deferred queues.
+ * Left-over active queue entries are moved to the incoming queue because
+ * the incoming queue has priority; moving left-overs to the deferred
+ * queue could cause anomalous delays when "postfix reload/start" are
+ * issued often. Override the IPC timeout (default 3600s) so that the
+ * queue manager can reset a broken IPC channel before the watchdog timer
+ * goes off.
+ */
+ var_ipc_timeout = var_qmgr_ipc_timeout;
+ var_use_limit = 0;
+ var_idle_limit = 0;
+ qmgr_move(MAIL_QUEUE_ACTIVE, MAIL_QUEUE_INCOMING, event_time());
+ qmgr_scans[QMGR_SCAN_IDX_INCOMING] = qmgr_scan_create(MAIL_QUEUE_INCOMING);
+ qmgr_scans[QMGR_SCAN_IDX_DEFERRED] = qmgr_scan_create(MAIL_QUEUE_DEFERRED);
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], QMGR_SCAN_START);
+ qmgr_deferred_run_event(0, (void *) 0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_DEFER_XPORTS, DEF_DEFER_XPORTS, &var_defer_xports, 0, 0,
+ VAR_CONC_POS_FDBACK, DEF_CONC_POS_FDBACK, &var_conc_pos_feedback, 1, 0,
+ VAR_CONC_NEG_FDBACK, DEF_CONC_NEG_FDBACK, &var_conc_neg_feedback, 1, 0,
+ VAR_DEF_FILTER_NEXTHOP, DEF_DEF_FILTER_NEXTHOP, &var_def_filter_nexthop, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0,
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000,
+ VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0,
+ VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0,
+ VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0,
+ VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0,
+ VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0,
+ VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
+ VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0,
+ VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0,
+ VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0,
+ VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0,
+ VAR_QMGR_FUDGE, DEF_QMGR_FUDGE, &var_qmgr_fudge, 10, 100,
+ VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0,
+ VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0,
+ VAR_CONC_COHORT_LIM, DEF_CONC_COHORT_LIM, &var_conc_cohort_limit, 0, 0,
+ VAR_VRFY_PEND_LIMIT, DEF_VRFY_PEND_LIMIT, &var_vrfy_pend_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off,
+ VAR_CONC_FDBACK_DEBUG, DEF_CONC_FDBACK_DEBUG, &var_conc_feedback_debug,
+ VAR_DSN_DELAY_CLEARED, DEF_DSN_DELAY_CLEARED, &var_dsn_delay_cleared,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Use the trigger service skeleton, because no-one else should be
+ * monitoring our service port while this process runs, and because we do
+ * not talk back to the client.
+ */
+ trigger_server_main(argc, argv, qmgr_trigger_event,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(qmgr_pre_init),
+ CA_MAIL_SERVER_POST_INIT(qmgr_post_init),
+ CA_MAIL_SERVER_LOOP(qmgr_loop),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_WATCHDOG(&var_qmgr_daemon_timeout),
+ 0);
+}
diff --git a/src/oqmgr/qmgr.h b/src/oqmgr/qmgr.h
new file mode 100644
index 0000000..cc22ca5
--- /dev/null
+++ b/src/oqmgr/qmgr.h
@@ -0,0 +1,432 @@
+/*++
+/* NAME
+/* qmgr 3h
+/* SUMMARY
+/* queue manager data structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <scan_dir.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+#include <dsn.h>
+
+ /*
+ * The queue manager is built around lots of mutually-referring structures.
+ * These typedefs save some typing.
+ */
+typedef struct QMGR_TRANSPORT QMGR_TRANSPORT;
+typedef struct QMGR_QUEUE QMGR_QUEUE;
+typedef struct QMGR_ENTRY QMGR_ENTRY;
+typedef struct QMGR_MESSAGE QMGR_MESSAGE;
+typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST;
+typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST;
+typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST;
+typedef struct QMGR_SCAN QMGR_SCAN;
+typedef struct QMGR_FEEDBACK QMGR_FEEDBACK;
+
+ /*
+ * Hairy macros to update doubly-linked lists.
+ */
+#define QMGR_LIST_ROTATE(head, object) { \
+ head.next->peers.prev = head.prev; \
+ head.prev->peers.next = head.next; \
+ head.next = object->peers.next; \
+ if (object->peers.next) \
+ head.next->peers.prev = 0; \
+ head.prev = object; \
+ object->peers.next = 0; \
+}
+
+#define QMGR_LIST_UNLINK(head, type, object) { \
+ type next = object->peers.next; \
+ type prev = object->peers.prev; \
+ if (prev) prev->peers.next = next; \
+ else head.next = next; \
+ if (next) next->peers.prev = prev; \
+ else head.prev = prev; \
+ object->peers.next = object->peers.prev = 0; \
+}
+
+#define QMGR_LIST_APPEND(head, object) { \
+ object->peers.next = head.next; \
+ object->peers.prev = 0; \
+ if (head.next) { \
+ head.next->peers.prev = object; \
+ } else { \
+ head.prev = object; \
+ } \
+ head.next = object; \
+}
+
+#define QMGR_LIST_PREPEND(head, object) { \
+ object->peers.prev = head.prev; \
+ object->peers.next = 0; \
+ if (head.prev) { \
+ head.prev->peers.next = object; \
+ } else { \
+ head.next = object; \
+ } \
+ head.prev = object; \
+}
+
+#define QMGR_LIST_INIT(head) { \
+ head.prev = 0; \
+ head.next = 0; \
+}
+
+ /*
+ * Transports are looked up by name (when we have resolved a message), or
+ * round-robin wise (when we want to distribute resources fairly).
+ */
+struct QMGR_TRANSPORT_LIST {
+ QMGR_TRANSPORT *next;
+ QMGR_TRANSPORT *prev;
+};
+
+extern struct HTABLE *qmgr_transport_byname; /* transport by name */
+extern QMGR_TRANSPORT_LIST qmgr_transport_list; /* transports, round robin */
+
+ /*
+ * Delivery agents provide feedback, as hints that Postfix should expend
+ * more or fewer resources on a specific destination domain. The main.cf
+ * file specifies how feedback affects delivery concurrency: add/subtract a
+ * constant, a ratio of constants, or a constant divided by the delivery
+ * concurrency; and it specifies how much feedback must accumulate between
+ * concurrency updates.
+ */
+struct QMGR_FEEDBACK {
+ int hysteresis; /* to pass, need to be this tall */
+ double base; /* pre-computed from main.cf */
+ int index; /* none, window, sqrt(window) */
+};
+
+#define QMGR_FEEDBACK_IDX_NONE 0 /* no window dependence */
+#define QMGR_FEEDBACK_IDX_WIN 1 /* 1/window dependence */
+#if 0
+#define QMGR_FEEDBACK_IDX_SQRT_WIN 2 /* 1/sqrt(window) dependence */
+#endif
+
+#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN
+#include <math.h>
+#endif
+
+extern void qmgr_feedback_init(QMGR_FEEDBACK *, const char *, const char *, const char *, const char *);
+
+#ifndef QMGR_FEEDBACK_IDX_SQRT_WIN
+#define QMGR_FEEDBACK_VAL(fb, win) \
+ ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : (fb).base / (win))
+#else
+#define QMGR_FEEDBACK_VAL(fb, win) \
+ ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : \
+ (fb).index == QMGR_FEEDBACK_IDX_WIN ? (fb).base / (win) : \
+ (fb).base / sqrt(win))
+#endif
+
+ /*
+ * Each transport (local, smtp-out, bounce) can have one queue per next hop
+ * name. Queues are looked up by next hop name (when we have resolved a
+ * message destination), or round-robin wise (when we want to deliver
+ * messages fairly).
+ */
+struct QMGR_QUEUE_LIST {
+ QMGR_QUEUE *next;
+ QMGR_QUEUE *prev;
+};
+
+struct QMGR_TRANSPORT {
+ int flags; /* blocked, etc. */
+ int pending; /* incomplete DA connections */
+ char *name; /* transport name */
+ int dest_concurrency_limit; /* concurrency per domain */
+ int init_dest_concurrency; /* init. per-domain concurrency */
+ int recipient_limit; /* recipients per transaction */
+ struct HTABLE *queue_byname; /* queues indexed by domain */
+ QMGR_QUEUE_LIST queue_list; /* queues, round robin order */
+ QMGR_TRANSPORT_LIST peers; /* linkage */
+ DSN *dsn; /* why unavailable */
+ QMGR_FEEDBACK pos_feedback; /* positive feedback control */
+ QMGR_FEEDBACK neg_feedback; /* negative feedback control */
+ int fail_cohort_limit; /* flow shutdown control */
+ int xport_rate_delay; /* suspend per delivery */
+ int rate_delay; /* suspend per delivery */
+};
+
+#define QMGR_TRANSPORT_STAT_DEAD (1<<1)
+#define QMGR_TRANSPORT_STAT_RATE_LOCK (1<<2)
+
+typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *);
+extern QMGR_TRANSPORT *qmgr_transport_select(void);
+extern void qmgr_transport_alloc(QMGR_TRANSPORT *, QMGR_TRANSPORT_ALLOC_NOTIFY);
+extern void qmgr_transport_throttle(QMGR_TRANSPORT *, DSN *);
+extern void qmgr_transport_unthrottle(QMGR_TRANSPORT *);
+extern QMGR_TRANSPORT *qmgr_transport_create(const char *);
+extern QMGR_TRANSPORT *qmgr_transport_find(const char *);
+
+#define QMGR_TRANSPORT_THROTTLED(t) ((t)->flags & QMGR_TRANSPORT_STAT_DEAD)
+
+ /*
+ * Each next hop (e.g., a domain name) has its own queue of pending message
+ * transactions. The "todo" queue contains messages that are to be delivered
+ * to this next hop. When a message is elected for transmission, it is moved
+ * from the "todo" queue to the "busy" queue. Messages are taken from the
+ * "todo" queue in sequence. An initial destination delivery concurrency > 1
+ * ensures that one problematic message will not block all other traffic to
+ * that next hop.
+ */
+struct QMGR_ENTRY_LIST {
+ QMGR_ENTRY *next;
+ QMGR_ENTRY *prev;
+};
+
+struct QMGR_QUEUE {
+ int dflags; /* delivery request options */
+ time_t last_done; /* last delivery completion */
+ char *name; /* domain name or address */
+ char *nexthop; /* domain name */
+ int todo_refcount; /* queue entries (todo list) */
+ int busy_refcount; /* queue entries (busy list) */
+ int window; /* slow open algorithm */
+ double success; /* accumulated positive feedback */
+ double failure; /* accumulated negative feedback */
+ double fail_cohorts; /* pseudo-cohort failure count */
+ QMGR_TRANSPORT *transport; /* transport linkage */
+ QMGR_ENTRY_LIST todo; /* todo queue entries */
+ QMGR_ENTRY_LIST busy; /* messages on the wire */
+ QMGR_QUEUE_LIST peers; /* neighbor queues */
+ DSN *dsn; /* why unavailable */
+ time_t clog_time_to_warn; /* time of next warning */
+};
+
+#define QMGR_QUEUE_TODO 1 /* waiting for service */
+#define QMGR_QUEUE_BUSY 2 /* recipients on the wire */
+
+extern int qmgr_queue_count;
+
+extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *);
+extern QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *);
+extern void qmgr_queue_done(QMGR_QUEUE *);
+extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *);
+extern void qmgr_queue_unthrottle(QMGR_QUEUE *);
+extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *);
+extern void qmgr_queue_suspend(QMGR_QUEUE *, int);
+
+ /*
+ * Exclusive queue states. Originally there were only two: "throttled" and
+ * "not throttled". It was natural to encode these in the queue window size.
+ * After 10 years it's not practical to rip out all the working code and
+ * change representations, so we just clean up the names a little.
+ *
+ * Note: only the "ready" state can reach every state (including itself);
+ * non-ready states can reach only the "ready" state. Other transitions are
+ * forbidden, because they would result in dangling event handlers.
+ */
+#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */
+#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */
+#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */
+#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */
+
+#define QMGR_QUEUE_READY(q) ((q)->window > 0)
+#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED)
+#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED)
+#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED)
+#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD)
+
+#define QMGR_QUEUE_STATUS(q) ( \
+ QMGR_QUEUE_READY(q) ? "ready" : \
+ QMGR_QUEUE_THROTTLED(q) ? "throttled" : \
+ QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \
+ QMGR_QUEUE_SAVED(q) ? "saved" : \
+ "invalid queue status" \
+ )
+
+ /*
+ * Structure of one next-hop queue entry. In order to save some copying
+ * effort we allow multiple recipients per transaction.
+ */
+struct QMGR_ENTRY {
+ VSTREAM *stream; /* delivery process */
+ QMGR_MESSAGE *message; /* message info */
+ RECIPIENT_LIST rcpt_list; /* as many as it takes */
+ QMGR_QUEUE *queue; /* parent linkage */
+ QMGR_ENTRY_LIST peers; /* neighbor entries */
+};
+
+extern QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *);
+extern void qmgr_entry_unselect(QMGR_QUEUE *, QMGR_ENTRY *);
+extern void qmgr_entry_move_todo(QMGR_QUEUE *, QMGR_ENTRY *);
+extern void qmgr_entry_done(QMGR_ENTRY *, int);
+extern QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *, QMGR_MESSAGE *);
+
+ /*
+ * All common in-core information about a message is kept here. When all
+ * recipients have been tried the message file is linked to the "deferred"
+ * queue (some hosts not reachable), to the "bounce" queue (some recipients
+ * were rejected), and is then removed from the "active" queue.
+ */
+struct QMGR_MESSAGE {
+ int flags; /* delivery problems */
+ int qflags; /* queuing flags */
+ int tflags; /* tracing flags */
+ long tflags_offset; /* offset for killing */
+ int rflags; /* queue file read flags */
+ VSTREAM *fp; /* open queue file or null */
+ int refcount; /* queue entries */
+ int single_rcpt; /* send one rcpt at a time */
+ struct timeval arrival_time; /* start of receive transaction */
+ time_t create_time; /* queue file create time */
+ struct timeval active_time; /* time of entry into active queue */
+ long warn_offset; /* warning bounce flag offset */
+ time_t warn_time; /* time next warning to be sent */
+ long data_offset; /* data seek offset */
+ char *queue_name; /* queue name */
+ char *queue_id; /* queue file */
+ char *encoding; /* content encoding */
+ char *sender; /* complete address */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN headers/full */
+ int smtputf8; /* requires unicode */
+ char *verp_delims; /* VERP delimiters */
+ char *filter_xport; /* filtering transport */
+ char *inspect_xport; /* inspecting transport */
+ char *redirect_addr; /* info@spammer.tld */
+ long data_size; /* data segment size */
+ long cont_length; /* message content length */
+ long rcpt_offset; /* more recipients here */
+ char *client_name; /* client hostname */
+ char *client_addr; /* client address */
+ char *client_port; /* client port */
+ char *client_proto; /* client protocol */
+ char *client_helo; /* helo parameter */
+ char *sasl_method; /* SASL method */
+ char *sasl_username; /* SASL user name */
+ char *sasl_sender; /* SASL sender */
+ char *log_ident; /* up-stream queue ID */
+ char *rewrite_context; /* address qualification */
+ RECIPIENT_LIST rcpt_list; /* complete addresses */
+};
+
+ /*
+ * Flags 0-15 are reserved for qmgr_user.h.
+ */
+#define QMGR_READ_FLAG_SEEN_ALL_NON_RCPT (1<<16)
+
+#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1)
+
+extern int qmgr_message_count;
+extern int qmgr_recipient_count;
+extern int qmgr_vrfy_pend_count;
+
+extern void qmgr_message_free(QMGR_MESSAGE *);
+extern void qmgr_message_update_warn(QMGR_MESSAGE *);
+extern void qmgr_message_kill_record(QMGR_MESSAGE *, long);
+extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int, mode_t);
+extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *);
+
+#define QMGR_MSG_STATS(stats, message) \
+ MSG_STATS_INIT2(stats, \
+ incoming_arrival, message->arrival_time, \
+ active_arrival, message->active_time)
+
+ /*
+ * qmgr_defer.c
+ */
+extern void qmgr_defer_transport(QMGR_TRANSPORT *, DSN *);
+extern void qmgr_defer_todo(QMGR_QUEUE *, DSN *);
+extern void qmgr_defer_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *);
+
+ /*
+ * qmgr_bounce.c
+ */
+extern void qmgr_bounce_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *);
+
+ /*
+ * qmgr_deliver.c
+ */
+extern int qmgr_deliver_concurrency;
+extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *);
+
+ /*
+ * qmgr_active.c
+ */
+extern int qmgr_active_feed(QMGR_SCAN *, const char *);
+extern void qmgr_active_drain(void);
+extern void qmgr_active_done(QMGR_MESSAGE *);
+
+ /*
+ * qmgr_move.c
+ */
+extern void qmgr_move(const char *, const char *, time_t);
+
+ /*
+ * qmgr_enable.c
+ */
+extern void qmgr_enable_all(void);
+extern void qmgr_enable_transport(QMGR_TRANSPORT *);
+extern void qmgr_enable_queue(QMGR_QUEUE *);
+
+ /*
+ * Queue scan context.
+ */
+struct QMGR_SCAN {
+ char *queue; /* queue name */
+ int flags; /* private, this run */
+ int nflags; /* private, next run */
+ struct SCAN_DIR *handle; /* scan */
+};
+
+ /*
+ * Flags that control queue scans or destination selection. These are
+ * similar to the QMGR_REQ_XXX request codes.
+ */
+#define QMGR_SCAN_START (1<<0) /* start now/restart when done */
+#define QMGR_SCAN_ALL (1<<1) /* all queue file time stamps */
+#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */
+#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */
+#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */
+#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */
+
+ /*
+ * qmgr_scan.c
+ */
+extern QMGR_SCAN *qmgr_scan_create(const char *);
+extern void qmgr_scan_request(QMGR_SCAN *, int);
+extern char *qmgr_scan_next(QMGR_SCAN *);
+
+ /*
+ * qmgr_error.c
+ */
+extern QMGR_TRANSPORT *qmgr_error_transport(const char *);
+extern QMGR_QUEUE *qmgr_error_queue(const char *, DSN *);
+extern char *qmgr_error_nexthop(DSN *);
+
+/* 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/oqmgr/qmgr_active.c b/src/oqmgr/qmgr_active.c
new file mode 100644
index 0000000..b1c1a4a
--- /dev/null
+++ b/src/oqmgr/qmgr_active.c
@@ -0,0 +1,607 @@
+/*++
+/* NAME
+/* qmgr_active 3
+/* SUMMARY
+/* active queue management
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_active_feed(scan_info, queue_id)
+/* QMGR_SCAN *scan_info;
+/* const char *queue_id;
+/*
+/* void qmgr_active_drain()
+/*
+/* int qmgr_active_done(message)
+/* QMGR_MESSAGE *message;
+/* DESCRIPTION
+/* These functions maintain the active message queue: the set
+/* of messages that the queue manager is actually working on.
+/* The active queue is limited in size. Messages are drained
+/* from the active queue by allocating a delivery process and
+/* by delivering mail via that process. Messages leak into the
+/* active queue only when the active queue is small enough.
+/* Damaged message files are saved to the "corrupt" directory.
+/*
+/* qmgr_active_feed() inserts the named message file into
+/* the active queue. Message files with the wrong name or
+/* with other wrong properties are skipped but not removed.
+/* The following queue flags are recognized, other flags being
+/* ignored:
+/* .IP QMGR_SCAN_ALL
+/* Examine all queue files. Normally, deferred queue files with
+/* future time stamps are ignored, and incoming queue files with
+/* future time stamps are frowned upon.
+/* .PP
+/* qmgr_active_drain() allocates one delivery process.
+/* Process allocation is asynchronous. Once the delivery
+/* process is available, an attempt is made to deliver
+/* a message via it. Message delivery is asynchronous, too.
+/*
+/* qmgr_active_done() deals with a message after delivery
+/* has been tried for all in-core recipients. If the message
+/* was bounced, a bounce message is sent to the sender, or
+/* to the Errors-To: address if one was specified.
+/* If there are more on-file recipients, a new batch of
+/* in-core recipients is read from the queue file. Otherwise,
+/* if a delivery agent marked the queue file as corrupt,
+/* the queue file is moved to the "corrupt" queue (surprise);
+/* if at least one delivery failed, the message is moved
+/* to the deferred queue. The time stamps of a deferred queue
+/* file are set to the nearest wakeup time of its recipient
+/* sites (if delivery failed due to a problem with a next-hop
+/* host), are set into the future by the amount of time the
+/* message was queued (per-message exponential backoff), or are set
+/* into the future by a minimal backoff time, whichever is more.
+/* The minimal_backoff_time parameter specifies the minimal
+/* amount of time between delivery attempts; maximal_backoff_time
+/* specifies an upper limit.
+/* DIAGNOSTICS
+/* Fatal: queue file access failures, out of memory.
+/* Panic: interface violations, internal consistency errors.
+/* Warnings: corrupt message file. A corrupt message is saved
+/* to the "corrupt" queue for further 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 <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <utime.h>
+#include <errno.h>
+
+#ifndef S_IRWXU /* What? no POSIX system? */
+#define S_IRWXU 0700
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_open_ok.h>
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <bounce.h>
+#include <defer.h>
+#include <trace.h>
+#include <abounce.h>
+#include <rec_type.h>
+#include <qmgr_user.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * A bunch of call-back routines.
+ */
+static void qmgr_active_done_2_bounce_flush(int, void *);
+static void qmgr_active_done_2_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_25_trace_flush(int, void *);
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_3_defer_flush(int, void *);
+static void qmgr_active_done_3_defer_warn(int, void *);
+static void qmgr_active_done_3_generic(QMGR_MESSAGE *);
+
+/* qmgr_active_corrupt - move corrupted file out of the way */
+
+static void qmgr_active_corrupt(const char *queue_id)
+{
+ const char *myname = "qmgr_active_corrupt";
+
+ if (mail_queue_rename(queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: save corrupt file queue %s id %s: %m",
+ myname, MAIL_QUEUE_ACTIVE, queue_id);
+ } else {
+ msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"",
+ queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT);
+ }
+}
+
+/* qmgr_active_defer - defer queue file */
+
+static void qmgr_active_defer(const char *queue_name, const char *queue_id,
+ const char *dest_queue, int delay)
+{
+ const char *myname = "qmgr_active_defer";
+ const char *path;
+ struct utimbuf tbuf;
+
+ if (msg_verbose)
+ msg_info("wakeup %s after %ld secs", queue_id, (long) delay);
+
+ tbuf.actime = tbuf.modtime = event_time() + delay;
+ path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
+ if (utime(path, &tbuf) < 0 && errno != ENOENT)
+ msg_fatal("%s: update %s time stamps: %m", myname, path);
+ if (mail_queue_rename(queue_id, queue_name, dest_queue)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: rename %s from %s to %s: %m", myname,
+ queue_id, queue_name, dest_queue);
+ msg_warn("%s: rename %s from %s to %s: %m", myname,
+ queue_id, queue_name, dest_queue);
+ } else if (msg_verbose) {
+ msg_info("%s: defer %s", myname, queue_id);
+ }
+}
+
+/* qmgr_active_feed - feed one message into active queue */
+
+int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id)
+{
+ const char *myname = "qmgr_active_feed";
+ QMGR_MESSAGE *message;
+ struct stat st;
+ const char *path;
+
+ if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0)
+ msg_panic("%s: bad queue %s", myname, scan_info->queue);
+ if (msg_verbose)
+ msg_info("%s: queue %s", myname, scan_info->queue);
+
+ /*
+ * Make sure this is something we are willing to open.
+ */
+ if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO)
+ return (0);
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, path);
+
+ /*
+ * Skip files that have time stamps into the future. They need to cool
+ * down. Incoming and deferred files can have future time stamps.
+ */
+ if ((scan_info->flags & QMGR_SCAN_ALL) == 0
+ && st.st_mtime > time((time_t *) 0) + 1) {
+ if (msg_verbose)
+ msg_info("%s: skip %s (%ld seconds)", myname, queue_id,
+ (long) (st.st_mtime - event_time()));
+ return (0);
+ }
+
+ /*
+ * Move the message to the active queue. File access errors are fatal.
+ */
+ if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: %s: rename from %s to %s: %m", myname,
+ queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE);
+ msg_warn("%s: %s: rename from %s to %s: %m", myname,
+ queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE);
+ return (0);
+ }
+
+ /*
+ * Extract envelope information: sender and recipients. At this point,
+ * mail addresses have been processed by the cleanup service so they
+ * should be in canonical form. Generate requests to deliver this
+ * message.
+ *
+ * Throwing away queue files seems bad, especially when they made it this
+ * far into the mail system. Therefore we save bad files to a separate
+ * directory for further inspection.
+ *
+ * After queue manager restart it is possible that a queue file is still
+ * being delivered. In that case (the file is locked), defer delivery by
+ * a minimal amount of time.
+ */
+#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP)
+#define MAYBE_FLUSH_AFTER(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0)
+#define MAYBE_FORCE_EXPIRE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0)
+#define MAYBE_UPDATE_MODE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \
+ (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0)
+
+ if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id,
+ scan_info->flags
+ | MAYBE_FLUSH_AFTER(st.st_mode)
+ | MAYBE_FORCE_EXPIRE(st.st_mode),
+ MAYBE_UPDATE_MODE(st.st_mode))) == 0) {
+ qmgr_active_corrupt(queue_id);
+ return (0);
+ } else if (message == QMGR_MESSAGE_LOCKED) {
+ qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60);
+ return (0);
+ } else {
+
+ /*
+ * Special case if all recipients were already delivered. Send any
+ * bounces and clean up.
+ */
+ if (message->refcount == 0)
+ qmgr_active_done(message);
+ return (1);
+ }
+}
+
+/* qmgr_active_done - dispose of message after recipients have been tried */
+
+void qmgr_active_done(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done";
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, message->queue_id);
+
+ /*
+ * During a previous iteration, an attempt to bounce this message may
+ * have failed, so there may still be a bounce log lying around. XXX By
+ * groping around in the bounce queue, we're trespassing on the bounce
+ * service's territory. But doing so is more robust than depending on the
+ * bounce daemon to do the lookup for us, and for us to do the deleting
+ * after we have received a successful status from the bounce service.
+ * The bounce queue directory blocks are most likely in memory anyway. If
+ * these lookups become a performance problem we will have to build an
+ * in-core cache into the bounce daemon.
+ *
+ * Don't bounce when the bounce log is empty. The bounce process obviously
+ * failed, and the delivery agent will have requested that the message be
+ * deferred.
+ *
+ * Bounces are sent asynchronously to avoid stalling while the cleanup
+ * daemon waits for the qmgr to accept the "new mail" trigger.
+ *
+ * See also code in cleanup_bounce.c.
+ */
+ if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) {
+ if (st.st_size == 0) {
+ if (mail_queue_remove(MAIL_QUEUE_BOUNCE, message->queue_id))
+ msg_fatal("remove %s %s: %m",
+ MAIL_QUEUE_BOUNCE, message->queue_id);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: bounce %s", myname, message->queue_id);
+ if (message->verp_delims == 0 || var_verp_bounce_off)
+ abounce_flush(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_2_bounce_flush,
+ (void *) message);
+ else
+ abounce_flush_verp(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ message->verp_delims,
+ qmgr_active_done_2_bounce_flush,
+ (void *) message);
+ return;
+ }
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_2_generic(message);
+}
+
+/* qmgr_active_done_2_bounce_flush - process abounce_flush() status */
+
+static void qmgr_active_done_2_bounce_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process abounce_flush() status and continue processing.
+ */
+ message->flags |= status;
+ qmgr_active_done_2_generic(message);
+}
+
+/* qmgr_active_done_2_generic - continue processing */
+
+static void qmgr_active_done_2_generic(QMGR_MESSAGE *message)
+{
+ const char *path;
+ struct stat st;
+
+ /*
+ * A delivery agent marks a queue file as corrupt by changing its
+ * attributes, and by pretending that delivery was deferred.
+ */
+ if (message->flags
+ && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) {
+ qmgr_active_corrupt(message->queue_id);
+ qmgr_message_free(message);
+ return;
+ }
+
+ /*
+ * If we did not read all recipients from this file, go read some more,
+ * but remember whether some recipients have to be tried again.
+ *
+ * Throwing away queue files seems bad, especially when they made it this
+ * far into the mail system. Therefore we save bad files to a separate
+ * directory for further inspection by a human being.
+ */
+ if (message->rcpt_offset > 0) {
+ if (qmgr_message_realloc(message) == 0) {
+ qmgr_active_corrupt(message->queue_id);
+ qmgr_message_free(message);
+ } else {
+ if (message->refcount == 0)
+ qmgr_active_done(message); /* recurse for consistency */
+ }
+ return;
+ }
+
+ /*
+ * XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS
+ * and others not. Depending on what subset of recipients are delivered,
+ * a trace file may or may not be created. Even when the last partial
+ * delivery attempt had no NOTIFY=SUCCESS recipients, a trace file may
+ * still exist from a previous partial delivery attempt. So as long as
+ * any recipient has NOTIFY=SUCCESS we have to always look for the trace
+ * file and be prepared for the file not to exist.
+ *
+ * See also comments in bounce/bounce_notify_util.c.
+ */
+ if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD
+ | DEL_REQ_FLAG_REC_DLY_SENT))
+ || (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) {
+ atrace_flush(message->tflags,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_25_trace_flush,
+ (void *) message);
+ return;
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */
+
+static void qmgr_active_done_25_trace_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process atrace_flush() status and continue processing.
+ */
+ if (status == 0 && message->tflags_offset)
+ qmgr_message_kill_record(message, message->tflags_offset);
+ message->flags |= status;
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_generic - continue processing */
+
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_25_generic";
+ const char *expire_status = 0;
+
+ /*
+ * If we get to this point we have tried all recipients for this message.
+ * If the message is too old, try to bounce it.
+ *
+ * Bounces are sent asynchronously to avoid stalling while the cleanup
+ * daemon waits for the qmgr to accept the "new mail" trigger.
+ */
+ if (message->flags) {
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ expire_status = "force-expired";
+ } else if (event_time() >= message->create_time +
+ (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
+ expire_status = "expired";
+ } else {
+ expire_status = 0;
+ }
+ if (expire_status != 0) {
+ msg_info("%s: from=<%s>, status=%s, returned to sender",
+ message->queue_id, info_log_addr_form_sender(message->sender),
+ expire_status);
+ if (message->verp_delims == 0 || var_verp_bounce_off)
+ adefer_flush(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_3_defer_flush,
+ (void *) message);
+ else
+ adefer_flush_verp(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ message->verp_delims,
+ qmgr_active_done_3_defer_flush,
+ (void *) message);
+ return;
+ } else if (message->warn_time > 0
+ && event_time() >= message->warn_time - 1) {
+ if (msg_verbose)
+ msg_info("%s: sending defer warning for %s", myname, message->queue_id);
+ adefer_warn(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_3_defer_warn,
+ (void *) message);
+ return;
+ }
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */
+
+static void qmgr_active_done_3_defer_warn(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process adefer_warn() completion status and continue processing.
+ */
+ if (status == 0)
+ qmgr_message_update_warn(message);
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */
+
+static void qmgr_active_done_3_defer_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process adefer_flush() status and continue processing.
+ */
+ message->flags = status;
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_generic - continue processing */
+
+static void qmgr_active_done_3_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_3_generic";
+ int delay;
+
+ /*
+ * Some recipients need to be tried again. Move the queue file time
+ * stamps into the future by the amount of time that the message is
+ * delayed, and move the message to the deferred queue. Impose minimal
+ * and maximal backoff times.
+ *
+ * Since we look at actual time in queue, not time since last delivery
+ * attempt, backoff times will be distributed. However, we can still see
+ * spikes in delivery activity because the interval between deferred
+ * queue scans is finite.
+ */
+ if (message->flags) {
+ if (message->create_time > 0) {
+ delay = event_time() - message->create_time;
+ if (delay > var_max_backoff_time)
+ delay = var_max_backoff_time;
+ if (delay < var_min_backoff_time)
+ delay = var_min_backoff_time;
+ } else {
+ delay = var_min_backoff_time;
+ }
+ qmgr_active_defer(message->queue_name, message->queue_id,
+ MAIL_QUEUE_DEFERRED, delay);
+ }
+
+ /*
+ * All recipients done. Remove the queue file.
+ */
+ else {
+ if (mail_queue_remove(message->queue_name, message->queue_id)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: remove %s from %s: %m", myname,
+ message->queue_id, message->queue_name);
+ msg_warn("%s: remove %s from %s: %m", myname,
+ message->queue_id, message->queue_name);
+ } else {
+ /* Same format as logged by postsuper. */
+ msg_info("%s: removed", message->queue_id);
+ }
+ }
+
+ /*
+ * Finally, delete the in-core message structure.
+ */
+ qmgr_message_free(message);
+}
+
+/* qmgr_active_drain - drain active queue by allocating a delivery process */
+
+void qmgr_active_drain(void)
+{
+ QMGR_TRANSPORT *transport;
+
+ /*
+ * Allocate one delivery process for every transport with pending mail.
+ * The process allocation completes asynchronously.
+ */
+ while ((transport = qmgr_transport_select()) != 0) {
+ if (msg_verbose)
+ msg_info("qmgr_active_drain: allocate %s", transport->name);
+ qmgr_transport_alloc(transport, qmgr_deliver);
+ }
+}
diff --git a/src/oqmgr/qmgr_bounce.c b/src/oqmgr/qmgr_bounce.c
new file mode 100644
index 0000000..00ba885
--- /dev/null
+++ b/src/oqmgr/qmgr_bounce.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* qmgr_bounce
+/* SUMMARY
+/* deal with mail that will not be delivered
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_QUEUE *qmgr_bounce_recipient(message, recipient, dsn)
+/* QMGR_MESSAGE *message;
+/* RECIPIENT *recipient;
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_bounce_recipient() produces a bounce log record.
+/* Once the bounce record is written successfully, the recipient
+/* is marked as done. When the bounce record cannot be written,
+/* the message structure is updated to reflect that the mail is
+/* deferred.
+/*
+/* Arguments:
+/* .IP message
+/* Open queue file with the message being bounced.
+/* .IP recipient
+/* The recipient that will not be delivered.
+/* .IP dsn
+/* Delivery status information. See dsn(3).
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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. */
+
+/* Global library. */
+
+#include <bounce.h>
+#include <deliver_completed.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_bounce_recipient - bounce one message recipient */
+
+void qmgr_bounce_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+ int status;
+
+ status = bounce_append(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", dsn);
+
+ if (status == 0)
+ deliver_completed(message->fp, recipient->offset);
+ else
+ message->flags |= status;
+}
diff --git a/src/oqmgr/qmgr_defer.c b/src/oqmgr/qmgr_defer.c
new file mode 100644
index 0000000..dc0319e
--- /dev/null
+++ b/src/oqmgr/qmgr_defer.c
@@ -0,0 +1,158 @@
+/*++
+/* NAME
+/* qmgr_defer
+/* SUMMARY
+/* deal with mail that must be delivered later
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_defer_recipient(message, recipient, dsn)
+/* QMGR_MESSAGE *message;
+/* RECIPIENT *recipient;
+/* DSN *dsn;
+/*
+/* void qmgr_defer_todo(queue, dsn)
+/* QMGR_QUEUE *queue;
+/* DSN *dsn;
+/*
+/* void qmgr_defer_transport(transport, dsn)
+/* QMGR_TRANSPORT *transport;
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_defer_recipient() defers delivery of the named message to
+/* the named recipient. It updates the message structure and writes
+/* a log entry.
+/*
+/* qmgr_defer_todo() iterates over all "todo" deliveries queued for
+/* the named site, and calls qmgr_defer_recipient() for each recipient
+/* found. Side effects caused by qmgr_entry_done(), qmgr_queue_done(),
+/* and by qmgr_active_done(): in-core queue entries will disappear,
+/* in-core queues may disappear, in-core and on-disk messages may
+/* disappear, bounces may be sent, new in-core queues, queue entries
+/* and recipients may appear.
+/*
+/* qmgr_defer_transport() calls qmgr_defer_todo() for each queue
+/* that depends on the named transport. See there for side effects.
+/*
+/* Arguments:
+/* .IP recipient
+/* A recipient address; used for logging purposes, and for updating
+/* the message-specific \fIdefer\fR log.
+/* .IP queue
+/* Specifies a queue with delivery requests for a specific next-hop
+/* host (or local user).
+/* .IP transport
+/* Specifies a message delivery transport.
+/* .IP dsn
+/* See dsn(3).
+/* BUGS
+/* The side effects of calling this routine are quite dramatic.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <vstream.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <defer.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_defer_transport - defer todo entries for named transport */
+
+void qmgr_defer_transport(QMGR_TRANSPORT *transport, DSN *dsn)
+{
+ QMGR_QUEUE *queue;
+ QMGR_QUEUE *next;
+
+ if (msg_verbose)
+ msg_info("defer transport %s: %s %s",
+ transport->name, dsn->status, dsn->reason);
+
+ /*
+ * Proceed carefully. Queues may disappear as a side effect.
+ */
+ for (queue = transport->queue_list.next; queue; queue = next) {
+ next = queue->peers.next;
+ qmgr_defer_todo(queue, dsn);
+ }
+}
+
+/* qmgr_defer_todo - defer all todo queue entries for specific site */
+
+void qmgr_defer_todo(QMGR_QUEUE *queue, DSN *dsn)
+{
+ QMGR_ENTRY *entry;
+ QMGR_ENTRY *next;
+ QMGR_MESSAGE *message;
+ RECIPIENT *recipient;
+ int nrcpt;
+ QMGR_QUEUE *retry_queue;
+
+ /*
+ * Sanity checks.
+ */
+ if (msg_verbose)
+ msg_info("defer site %s: %s %s",
+ queue->name, dsn->status, dsn->reason);
+
+ /*
+ * See if we can redirect the deliveries to the retry(8) delivery agent,
+ * so that they can be handled asynchronously. If the retry(8) service is
+ * unavailable, use the synchronous defer(8) server. With a large todo
+ * queue, this blocks the queue manager for a significant time.
+ */
+ retry_queue = qmgr_error_queue(MAIL_SERVICE_RETRY, dsn);
+
+ /*
+ * Proceed carefully. Queue entries may disappear as a side effect.
+ */
+ for (entry = queue->todo.next; entry != 0; entry = next) {
+ next = entry->peers.next;
+ if (retry_queue != 0) {
+ qmgr_entry_move_todo(retry_queue, entry);
+ continue;
+ }
+ message = entry->message;
+ for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) {
+ recipient = entry->rcpt_list.info + nrcpt;
+ qmgr_defer_recipient(message, recipient, dsn);
+ }
+ qmgr_entry_done(entry, QMGR_QUEUE_TODO);
+ }
+}
+
+/* qmgr_defer_recipient - defer delivery of specific recipient */
+
+void qmgr_defer_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+
+ /*
+ * Update the message structure and log the message disposition.
+ */
+ message->flags |= defer_append(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", dsn);
+}
diff --git a/src/oqmgr/qmgr_deliver.c b/src/oqmgr/qmgr_deliver.c
new file mode 100644
index 0000000..100ccc7
--- /dev/null
+++ b/src/oqmgr/qmgr_deliver.c
@@ -0,0 +1,453 @@
+/*++
+/* NAME
+/* qmgr_deliver 3
+/* SUMMARY
+/* deliver one per-site queue entry to that site
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_deliver_concurrency;
+/*
+/* int qmgr_deliver(transport, fp)
+/* QMGR_TRANSPORT *transport;
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* This module implements the client side of the `queue manager
+/* to delivery agent' protocol. The queue manager uses
+/* asynchronous I/O so that it can drive multiple delivery
+/* agents in parallel. Depending on the outcome of a delivery
+/* attempt, the status of messages, queues and transports is
+/* updated.
+/*
+/* qmgr_deliver_concurrency is a global counter that says how
+/* many delivery processes are in use. This can be used, for
+/* example, to control the size of the `active' message queue.
+/*
+/* qmgr_deliver() executes when a delivery process announces its
+/* availability for the named transport. It arranges for delivery
+/* of a suitable queue entry. The \fIfp\fR argument specifies a
+/* stream that is connected to the delivery process, or a null
+/* pointer if the transport accepts no connection. Upon completion
+/* of delivery (successful or not), the stream is closed, so that the
+/* delivery process is released.
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <recipient_list.h>
+#include <mail_params.h>
+#include <deliver_request.h>
+#include <verp_sender.h>
+#include <dsn_util.h>
+#include <dsn_buf.h>
+#include <dsb_scan.h>
+#include <rcpt_print.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Important note on the _transport_rate_delay implementation: after
+ * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+ * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+ * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+ * _transport_rate_delay will become stuck.
+ */
+
+int qmgr_deliver_concurrency;
+
+ /*
+ * Message delivery status codes.
+ */
+#define DELIVER_STAT_OK 0 /* all recipients delivered */
+#define DELIVER_STAT_DEFER 1 /* try some recipients later */
+#define DELIVER_STAT_CRASH 2 /* mailer internal problem */
+
+/* qmgr_deliver_initial_reply - retrieve initial delivery process response */
+
+static int qmgr_deliver_initial_reply(VSTREAM *stream)
+{
+ if (peekfd(vstream_fileno(stream)) < 0) {
+ msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_DEFER);
+ } else {
+ return (0);
+ }
+}
+
+/* qmgr_deliver_final_reply - retrieve final delivery process response */
+
+static int qmgr_deliver_final_reply(VSTREAM *stream, DSN_BUF *dsb)
+{
+ int stat;
+
+ if (peekfd(vstream_fileno(stream)) < 0) {
+ msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsb),
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &stat),
+ ATTR_TYPE_END) != 2) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else {
+ return (stat ? DELIVER_STAT_DEFER : 0);
+ }
+}
+
+/* qmgr_deliver_send_request - send delivery request to delivery process */
+
+static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream)
+{
+ RECIPIENT_LIST list = entry->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_MESSAGE *message = entry->message;
+ VSTRING *sender_buf = 0;
+ MSG_STATS stats;
+ char *sender;
+ int flags;
+ int smtputf8 = message->smtputf8;
+ const char *addr;
+
+ /*
+ * Todo: integrate with code up-stream that builds the delivery request.
+ */
+ for (recipient = list.info; recipient < list.info + list.len; recipient++)
+ if (var_smtputf8_enable && (addr = recipient->address)[0]
+ && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) {
+ smtputf8 |= SMTPUTF8_FLAG_RECIPIENT;
+ if (message->verp_delims)
+ smtputf8 |= SMTPUTF8_FLAG_SENDER;
+ }
+
+ /*
+ * If variable envelope return path is requested, change prefix+@origin
+ * into prefix+user=domain@origin. Note that with VERP there is only one
+ * recipient per delivery.
+ */
+ if (message->verp_delims == 0) {
+ sender = message->sender;
+ } else {
+ sender_buf = vstring_alloc(100);
+ verp_sender(sender_buf, message->verp_delims,
+ message->sender, list.info);
+ sender = vstring_str(sender_buf);
+ }
+
+ flags = message->tflags
+ | entry->queue->dflags
+ | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT);
+ (void) QMGR_MSG_STATS(&stats, message);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, message->queue_name),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, message->queue_id),
+ SEND_ATTR_LONG(MAIL_ATTR_OFFSET, message->data_offset),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE, message->cont_length),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, entry->queue->nexthop),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, message->encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, message->dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, message->dsn_ret),
+ SEND_ATTR_FUNC(msg_stats_print, (const void *) &stats),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, message->client_name),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, message->client_addr),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, message->client_port),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, message->client_proto),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, message->client_helo),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, message->sasl_method),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, message->sasl_username),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, message->sasl_sender),
+ /* XXX Ditto if we want to pass TLS certificate info. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_IDENT, message->log_ident),
+ SEND_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, message->rewrite_context),
+ SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, list.len),
+ ATTR_TYPE_END);
+ if (sender_buf != 0)
+ vstring_free(sender_buf);
+ for (recipient = list.info; recipient < list.info + list.len; recipient++)
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(rcpt_print, (const void *) recipient),
+ ATTR_TYPE_END);
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write to process (%s): %m", entry->queue->transport->name);
+ return (-1);
+ } else {
+ if (msg_verbose)
+ msg_info("qmgr_deliver: site `%s'", entry->queue->name);
+ return (0);
+ }
+}
+
+/* qmgr_deliver_abort - transport response watchdog */
+
+static void qmgr_deliver_abort(int unused_event, void *context)
+{
+ QMGR_ENTRY *entry = (QMGR_ENTRY *) context;
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_TRANSPORT *transport = queue->transport;
+ QMGR_MESSAGE *message = entry->message;
+
+ msg_fatal("%s: timeout receiving delivery status from transport: %s",
+ message->queue_id, transport->name);
+}
+
+/* qmgr_deliver_update - process delivery status report */
+
+static void qmgr_deliver_update(int unused_event, void *context)
+{
+ QMGR_ENTRY *entry = (QMGR_ENTRY *) context;
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_TRANSPORT *transport = queue->transport;
+ QMGR_MESSAGE *message = entry->message;
+ static DSN_BUF *dsb;
+ int status;
+
+ /*
+ * Release the delivery agent from a "hot" queue entry.
+ */
+#define QMGR_DELIVER_RELEASE_AGENT(entry) do { \
+ event_disable_readwrite(vstream_fileno(entry->stream)); \
+ (void) vstream_fclose(entry->stream); \
+ entry->stream = 0; \
+ qmgr_deliver_concurrency--; \
+ } while (0)
+
+ if (dsb == 0)
+ dsb = dsb_create();
+
+ /*
+ * The message transport has responded. Stop the watchdog timer.
+ */
+ event_cancel_timer(qmgr_deliver_abort, context);
+
+ /*
+ * Retrieve the delivery agent status report. The numerical status code
+ * indicates if delivery should be tried again. The reason text is sent
+ * only when a site should be avoided for a while, so that the queue
+ * manager can log why it does not even try to schedule delivery to the
+ * affected recipients.
+ */
+ status = qmgr_deliver_final_reply(entry->stream, dsb);
+
+ /*
+ * The mail delivery process failed for some reason (although delivery
+ * may have been successful). Back off with this transport type for a
+ * while. Dispose of queue entries for this transport that await
+ * selection (the todo lists). Stay away from queue entries that have
+ * been selected (the busy lists), or we would have dangling pointers.
+ * The queue itself won't go away before we dispose of the current queue
+ * entry.
+ */
+ if (status == DELIVER_STAT_CRASH) {
+ message->flags |= DELIVER_STAT_DEFER;
+#if 0
+ whatsup = concatenate("unknown ", transport->name,
+ " mail transport error", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsb->dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsb->dsn, "4.3.0",
+ "unknown mail transport error"));
+#endif
+ msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description",
+ transport->name);
+
+ /*
+ * Assume the worst and write a defer logfile record for each
+ * recipient. This omission was already present in the first queue
+ * manager implementation of 199703, and was fixed 200511.
+ *
+ * To avoid the synchronous qmgr_defer_recipient() operation for each
+ * recipient of this queue entry, release the delivery process and
+ * move the entry back to the todo queue. Let qmgr_defer_transport()
+ * log the recipient asynchronously if possible, and get out of here.
+ * Note: if asynchronous logging is not possible,
+ * qmgr_defer_transport() eventually invokes qmgr_entry_done() and
+ * the entry becomes a dangling pointer.
+ */
+ QMGR_DELIVER_RELEASE_AGENT(entry);
+ qmgr_entry_unselect(queue, entry);
+ qmgr_defer_transport(transport, &dsb->dsn);
+ return;
+ }
+
+ /*
+ * This message must be tried again.
+ *
+ * If we have a problem talking to this site, back off with this site for a
+ * while; dispose of queue entries for this site that await selection
+ * (the todo list); stay away from queue entries that have been selected
+ * (the busy list), or we would have dangling pointers. The queue itself
+ * won't go away before we dispose of the current queue entry.
+ *
+ * XXX Caution: DSN_COPY() will panic on empty status or reason.
+ */
+#define SUSPENDED "delivery temporarily suspended: "
+
+ if (status == DELIVER_STAT_DEFER) {
+ message->flags |= DELIVER_STAT_DEFER;
+ if (VSTRING_LEN(dsb->status)) {
+ /* Sanitize the DSN status/reason from the delivery agent. */
+ if (!dsn_valid(vstring_str(dsb->status)))
+ vstring_strcpy(dsb->status, "4.0.0");
+ if (VSTRING_LEN(dsb->reason) == 0)
+ vstring_strcpy(dsb->reason, "unknown error");
+ vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1);
+ if (QMGR_QUEUE_READY(queue)) {
+ qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb));
+ if (QMGR_QUEUE_THROTTLED(queue))
+ qmgr_defer_todo(queue, &dsb->dsn);
+ }
+ }
+ }
+
+ /*
+ * No problems detected. Mark the transport and queue as alive. The queue
+ * itself won't go away before we dispose of the current queue entry.
+ */
+ if (status != DELIVER_STAT_CRASH) {
+ qmgr_transport_unthrottle(transport);
+ if (VSTRING_LEN(dsb->reason) == 0)
+ qmgr_queue_unthrottle(queue);
+ }
+
+ /*
+ * Release the delivery process, and give some other queue entry a chance
+ * to be delivered. When all recipients for a message have been tried,
+ * decide what to do next with this message: defer, bounce, delete.
+ */
+ QMGR_DELIVER_RELEASE_AGENT(entry);
+ qmgr_entry_done(entry, QMGR_QUEUE_BUSY);
+}
+
+/* qmgr_deliver - deliver one per-site queue entry */
+
+void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
+{
+ QMGR_QUEUE *queue;
+ QMGR_ENTRY *entry;
+ DSN dsn;
+
+ /*
+ * Find out if this delivery process is really available. Once elected,
+ * the delivery process is supposed to express its happiness. If there is
+ * a problem, wipe the pending deliveries for this transport. This
+ * routine runs in response to an external event, so it does not run
+ * while some other queue manipulation is happening.
+ */
+ if (stream == 0 || qmgr_deliver_initial_reply(stream) != 0) {
+#if 0
+ whatsup = concatenate(transport->name,
+ " mail transport unavailable", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0",
+ "mail transport unavailable"));
+#endif
+ qmgr_defer_transport(transport, &dsn);
+ if (stream)
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * Find a suitable queue entry. Things may have changed since this
+ * transport was allocated. If no suitable entry is found,
+ * unceremoniously disconnect from the delivery process. The delivery
+ * agent request reading routine is prepared for the queue manager to
+ * change its mind for no apparent reason.
+ */
+ if ((queue = qmgr_queue_select(transport)) == 0
+ || (entry = qmgr_entry_select(queue)) == 0) {
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * Send the queue file info and recipient info to the delivery process.
+ * If there is a problem, wipe the pending deliveries for this transport.
+ * This routine runs in response to an external event, so it does not run
+ * while some other queue manipulation is happening.
+ */
+ if (qmgr_deliver_send_request(entry, stream) < 0) {
+ qmgr_entry_unselect(queue, entry);
+#if 0
+ whatsup = concatenate(transport->name,
+ " mail transport unavailable", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0",
+ "mail transport unavailable"));
+#endif
+ qmgr_defer_transport(transport, &dsn);
+ /* warning: entry and queue may be dangling pointers here */
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * If we get this far, go wait for the delivery status report.
+ */
+ qmgr_deliver_concurrency++;
+ entry->stream = stream;
+ event_enable_read(vstream_fileno(stream),
+ qmgr_deliver_update, (void *) entry);
+
+ /*
+ * Guard against broken systems.
+ */
+ event_request_timer(qmgr_deliver_abort, (void *) entry, var_daemon_timeout);
+}
diff --git a/src/oqmgr/qmgr_enable.c b/src/oqmgr/qmgr_enable.c
new file mode 100644
index 0000000..a35e46e
--- /dev/null
+++ b/src/oqmgr/qmgr_enable.c
@@ -0,0 +1,107 @@
+/*++
+/* NAME
+/* qmgr_enable
+/* SUMMARY
+/* enable dead transports or sites
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_enable_queue(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_QUEUE *qmgr_enable_transport(transport)
+/* QMGR_TRANSPORT *transport;
+/*
+/* void qmgr_enable_all(void)
+/* DESCRIPTION
+/* This module purges dead in-core state information, effectively
+/* re-enabling delivery.
+/*
+/* qmgr_enable_queue() enables deliveries to the named dead site.
+/* Empty queues are destroyed. The existed solely to indicate that
+/* a site is dead.
+/*
+/* qmgr_enable_transport() enables deliveries via the specified
+/* transport, and calls qmgr_enable_queue() for each destination
+/* on that transport. Empty queues are destroyed.
+/*
+/* qmgr_enable_all() enables all transports and queues.
+/* See above for the side effects caused by doing this.
+/* BUGS
+/* The side effects of calling this module can be quite dramatic.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <vstream.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_enable_all - enable transports and queues */
+
+void qmgr_enable_all(void)
+{
+ QMGR_TRANSPORT *xport;
+
+ if (msg_verbose)
+ msg_info("qmgr_enable_all");
+
+ /*
+ * The number of transports does not change as a side effect, so this can
+ * be a straightforward loop.
+ */
+ for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next)
+ qmgr_enable_transport(xport);
+}
+
+/* qmgr_enable_transport - defer todo entries for named transport */
+
+void qmgr_enable_transport(QMGR_TRANSPORT *transport)
+{
+ QMGR_QUEUE *queue;
+ QMGR_QUEUE *next;
+
+ /*
+ * Proceed carefully. Queues may disappear as a side effect.
+ */
+ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) {
+ if (msg_verbose)
+ msg_info("enable transport %s", transport->name);
+ qmgr_transport_unthrottle(transport);
+ }
+ for (queue = transport->queue_list.next; queue; queue = next) {
+ next = queue->peers.next;
+ qmgr_enable_queue(queue);
+ }
+}
+
+/* qmgr_enable_queue - enable and possibly delete queue */
+
+void qmgr_enable_queue(QMGR_QUEUE *queue)
+{
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ if (msg_verbose)
+ msg_info("enable site %s/%s", queue->transport->name, queue->name);
+ qmgr_queue_unthrottle(queue);
+ }
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+}
diff --git a/src/oqmgr/qmgr_entry.c b/src/oqmgr/qmgr_entry.c
new file mode 100644
index 0000000..d5f3264
--- /dev/null
+++ b/src/oqmgr/qmgr_entry.c
@@ -0,0 +1,391 @@
+/*++
+/* NAME
+/* qmgr_entry 3
+/* SUMMARY
+/* per-site queue entries
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_ENTRY *qmgr_entry_create(queue, message)
+/* QMGR_QUEUE *queue;
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_entry_done(entry, which)
+/* QMGR_ENTRY *entry;
+/* int which;
+/*
+/* QMGR_ENTRY *qmgr_entry_select(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* void qmgr_entry_unselect(queue, entry)
+/* QMGR_QUEUE *queue;
+/* QMGR_ENTRY *entry;
+/*
+/* void qmgr_entry_move_todo(dst, entry)
+/* QMGR_QUEUE *dst;
+/* QMGR_ENTRY *entry;
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-site message
+/* delivery requests.
+/*
+/* qmgr_entry_create() creates an entry for the named queue and
+/* message, and appends the entry to the queue's todo list.
+/* Filling in and cleaning up the recipients is the responsibility
+/* of the caller.
+/*
+/* qmgr_entry_done() discards a per-site queue entry. The
+/* \fIwhich\fR argument is either QMGR_QUEUE_BUSY for an entry
+/* of the site's `busy' list (i.e. queue entries that have been
+/* selected for actual delivery), or QMGR_QUEUE_TODO for an entry
+/* of the site's `todo' list (i.e. queue entries awaiting selection
+/* for actual delivery).
+/*
+/* qmgr_entry_done() triggers cleanup of the per-site queue when
+/* the site has no pending deliveries, and the site is either
+/* alive, or the site is dead and the number of in-core queues
+/* exceeds a configurable limit (see qmgr_queue_done()).
+/*
+/* qmgr_entry_done() triggers special action when the last in-core
+/* queue entry for a message is done with: either read more
+/* recipients from the queue file, delete the queue file, or move
+/* the queue file to the deferred queue; send bounce reports to the
+/* message originator (see qmgr_active_done()).
+/*
+/* qmgr_entry_select() selects the next entry from the named
+/* per-site queue's `todo' list for actual delivery. The entry is
+/* moved to the queue's `busy' list: the list of messages being
+/* delivered.
+/*
+/* qmgr_entry_unselect() takes the named entry off the named
+/* per-site queue's `busy' list and moves it to the queue's
+/* `todo' list.
+/*
+/* qmgr_entry_move_todo() moves the specified "todo" queue entry
+/* to the specified "todo" queue.
+/* DIAGNOSTICS
+/* Panic: interface violations, internal inconsistencies.
+/* 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 <stdlib.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <deliver_request.h> /* opportunistic session caching */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_entry_select - select queue entry for delivery */
+
+QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *queue)
+{
+ const char *myname = "qmgr_entry_select";
+ QMGR_ENTRY *entry;
+
+ if ((entry = queue->todo.prev) != 0) {
+ QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
+ queue->todo_refcount--;
+ QMGR_LIST_APPEND(queue->busy, entry);
+ queue->busy_refcount++;
+
+ /*
+ * With opportunistic session caching, the delivery agent must not
+ * only 1) save a session upon completion, but also 2) reuse a cached
+ * session upon the next delivery request. In order to not miss out
+ * on 2), we have to make caching sticky or else we get silly
+ * behavior when the in-memory queue drains. Specifically, new
+ * connections must not be made as long as cached connections exist.
+ *
+ * Safety: don't enable opportunistic session caching unless the queue
+ * manager is able to schedule concurrent or back-to-back deliveries
+ * (we need to recognize back-to-back deliveries for transports with
+ * concurrency 1).
+ *
+ * If caching has previously been enabled, but is not now, fetch any
+ * existing entries from the cache, but don't add new ones.
+ */
+#define CONCURRENT_OR_BACK_TO_BACK_DELIVERY() \
+ (queue->busy_refcount > 1 || BACK_TO_BACK_DELIVERY())
+
+#define BACK_TO_BACK_DELIVERY() \
+ (queue->last_done + 1 >= event_time())
+
+ /*
+ * Turn on session caching after we get up to speed. Don't enable
+ * session caching just because we have concurrent deliveries. This
+ * prevents unnecessary session caching when we have a burst of mail
+ * <= the initial concurrency limit.
+ */
+ if ((queue->dflags & DEL_REQ_FLAG_CONN_STORE) == 0) {
+ if (BACK_TO_BACK_DELIVERY()) {
+ if (msg_verbose)
+ msg_info("%s: allowing on-demand session caching for %s",
+ myname, queue->name);
+ queue->dflags |= DEL_REQ_FLAG_CONN_MASK;
+ }
+ }
+
+ /*
+ * Turn off session caching when concurrency drops and we're running
+ * out of steam. This is what prevents from turning off session
+ * caching too early, and from making new connections while old ones
+ * are still cached.
+ */
+ else {
+ if (!CONCURRENT_OR_BACK_TO_BACK_DELIVERY()) {
+ if (msg_verbose)
+ msg_info("%s: disallowing on-demand session caching for %s",
+ myname, queue->name);
+ queue->dflags &= ~DEL_REQ_FLAG_CONN_STORE;
+ }
+ }
+ }
+ return (entry);
+}
+
+/* qmgr_entry_unselect - unselect queue entry for delivery */
+
+void qmgr_entry_unselect(QMGR_QUEUE *queue, QMGR_ENTRY *entry)
+{
+ QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
+ queue->busy_refcount--;
+ QMGR_LIST_APPEND(queue->todo, entry);
+ queue->todo_refcount++;
+}
+
+/* qmgr_entry_move_todo - move entry between todo queues */
+
+void qmgr_entry_move_todo(QMGR_QUEUE *dst, QMGR_ENTRY *entry)
+{
+ const char *myname = "qmgr_entry_move_todo";
+ QMGR_MESSAGE *message = entry->message;
+ QMGR_QUEUE *src = entry->queue;
+ QMGR_ENTRY *new_entry;
+
+ if (entry->stream != 0)
+ msg_panic("%s: queue %s entry is busy", myname, src->name);
+ if (QMGR_QUEUE_THROTTLED(dst))
+ msg_panic("%s: destination queue %s is throttled", myname, dst->name);
+ if (QMGR_TRANSPORT_THROTTLED(dst->transport))
+ msg_panic("%s: destination transport %s is throttled",
+ myname, dst->transport->name);
+
+ /*
+ * Create new entry, swap the recipients between the old and new entries,
+ * then dispose of the old entry. This gives us any end-game actions that
+ * are implemented by qmgr_entry_done(), so we don't have to duplicate
+ * those actions here.
+ *
+ * XXX This does not enforce the per-entry recipient limit, but that is not
+ * a problem as long as qmgr_entry_move_todo() is called only to bounce
+ * or defer mail.
+ */
+ new_entry = qmgr_entry_create(dst, message);
+ recipient_list_swap(&entry->rcpt_list, &new_entry->rcpt_list);
+ qmgr_entry_done(entry, QMGR_QUEUE_TODO);
+}
+
+/* qmgr_entry_done - dispose of queue entry */
+
+void qmgr_entry_done(QMGR_ENTRY *entry, int which)
+{
+ const char *myname = "qmgr_entry_done";
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_MESSAGE *message = entry->message;
+ QMGR_TRANSPORT *transport = queue->transport;
+
+ /*
+ * Take this entry off the in-core queue.
+ */
+ if (entry->stream != 0)
+ msg_panic("%s: file is open", myname);
+ if (which == QMGR_QUEUE_BUSY) {
+ QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
+ queue->busy_refcount--;
+ } else if (which == QMGR_QUEUE_TODO) {
+ QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
+ queue->todo_refcount--;
+ } else {
+ msg_panic("%s: bad queue spec: %d", myname, which);
+ }
+
+ /*
+ * Free the recipient list and decrease the in-core recipient count
+ * accordingly.
+ */
+ qmgr_recipient_count -= entry->rcpt_list.len;
+ recipient_list_free(&entry->rcpt_list);
+
+ myfree((void *) entry);
+
+ /*
+ * Maintain back-to-back delivery status.
+ */
+ if (which == QMGR_QUEUE_BUSY)
+ queue->last_done = event_time();
+
+ /*
+ * Suspend a rate-limited queue, so that mail trickles out.
+ */
+ if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) {
+ if (queue->window > 1)
+ msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service",
+ myname, transport->name, queue->name, queue->window);
+ if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue))
+ qmgr_queue_suspend(queue, transport->rate_delay);
+ }
+
+ /*
+ * When the in-core queue for this site is empty and when this site is
+ * not dead, discard the in-core queue. When this site is dead, but the
+ * number of in-core queues exceeds some threshold, get rid of this
+ * in-core queue anyway, in order to avoid running out of memory.
+ *
+ * See also: qmgr_entry_move_todo().
+ */
+ if (queue->todo.next == 0 && queue->busy.next == 0) {
+ if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit)
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue))
+ qmgr_queue_done(queue);
+ }
+
+ /*
+ * Update the in-core message reference count. When the in-core message
+ * structure has no more references, dispose of the message.
+ *
+ * When the in-core recipient count falls below a threshold, and this
+ * message has more recipients, read more recipients now. If we read more
+ * recipients as soon as the recipient count falls below the in-core
+ * recipient limit, we do not give other messages a chance until this
+ * message is delivered. That's good for mailing list deliveries, bad for
+ * one-to-one mail. If we wait until the in-core recipient count drops
+ * well below the in-core recipient limit, we give other mail a chance,
+ * but we also allow list deliveries to become interleaved. In the worst
+ * case, people near the start of a mailing list get a burst of postings
+ * today, while people near the end of the list get that same burst of
+ * postings a whole day later.
+ */
+#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0))
+ message->refcount--;
+ if (message->rcpt_offset > 0
+ && qmgr_recipient_count < FUDGE(var_qmgr_rcpt_limit) - 100)
+ qmgr_message_realloc(message);
+ if (message->refcount == 0)
+ qmgr_active_done(message);
+}
+
+/* qmgr_entry_create - create queue todo entry */
+
+QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message)
+{
+ QMGR_ENTRY *entry;
+
+ /*
+ * Sanity check.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue))
+ msg_panic("qmgr_entry_create: dead queue: %s", queue->name);
+
+ /*
+ * Create the delivery request.
+ */
+ entry = (QMGR_ENTRY *) mymalloc(sizeof(QMGR_ENTRY));
+ entry->stream = 0;
+ entry->message = message;
+ recipient_list_init(&entry->rcpt_list, RCPT_LIST_INIT_QUEUE);
+ message->refcount++;
+ entry->queue = queue;
+ QMGR_LIST_APPEND(queue->todo, entry);
+ queue->todo_refcount++;
+
+ /*
+ * Warn if a destination is falling behind while the active queue
+ * contains a non-trivial amount of single-recipient email. When a
+ * destination takes up more and more space in the active queue, then
+ * other mail will not get through and delivery performance will suffer.
+ *
+ * XXX At this point in the code, the busy reference count is still less
+ * than the concurrency limit (otherwise this code would not be invoked
+ * in the first place) so we have to make some awkward adjustments
+ * below.
+ *
+ * XXX The queue length test below looks at the active queue share of an
+ * individual destination. This catches the case where mail for one
+ * destination is falling behind because it has to round-robin compete
+ * with many other destinations. However, Postfix will also perform
+ * poorly when most of the active queue is tied up by a small number of
+ * concurrency limited destinations. The queue length test below detects
+ * such conditions only indirectly.
+ *
+ * XXX This code does not detect the case that the active queue is being
+ * starved because incoming mail is pounding the disk.
+ */
+ if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) {
+ int queue_length = queue->todo_refcount + queue->busy_refcount;
+ time_t now;
+ QMGR_TRANSPORT *transport;
+ double active_share;
+
+ if (queue_length > var_qmgr_active_limit / 5
+ && (now = event_time()) >= queue->clog_time_to_warn) {
+ active_share = queue_length / (double) qmgr_message_count;
+ msg_warn("mail for %s is using up %d of %d active queue entries",
+ queue->nexthop, queue_length, qmgr_message_count);
+ if (active_share < 0.9)
+ msg_warn("this may slow down other mail deliveries");
+ transport = queue->transport;
+ if (transport->dest_concurrency_limit > 0
+ && transport->dest_concurrency_limit <= queue->busy_refcount + 1)
+ msg_warn("you may need to increase the main.cf %s%s from %d",
+ transport->name, _DEST_CON_LIMIT,
+ transport->dest_concurrency_limit);
+ else if (queue->window > var_qmgr_active_limit * active_share)
+ msg_warn("you may need to increase the main.cf %s from %d",
+ VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit);
+ else if (queue->peers.next != queue->peers.prev)
+ msg_warn("you may need a separate master.cf transport for %s",
+ queue->nexthop);
+ else {
+ msg_warn("you may need to reduce %s connect and helo timeouts",
+ transport->name);
+ msg_warn("so that Postfix quickly skips unavailable hosts");
+ msg_warn("you may need to increase the main.cf %s and %s",
+ VAR_MIN_BACKOFF_TIME, VAR_MAX_BACKOFF_TIME);
+ msg_warn("so that Postfix wastes less time on undeliverable mail");
+ msg_warn("you may need to increase the master.cf %s process limit",
+ transport->name);
+ }
+ msg_warn("please avoid flushing the whole queue when you have");
+ msg_warn("lots of deferred mail, that is bad for performance");
+ msg_warn("to turn off these warnings specify: %s = 0",
+ VAR_QMGR_CLOG_WARN_TIME);
+ queue->clog_time_to_warn = now + var_qmgr_clog_warn_time;
+ }
+ }
+ return (entry);
+}
diff --git a/src/oqmgr/qmgr_error.c b/src/oqmgr/qmgr_error.c
new file mode 100644
index 0000000..6541c35
--- /dev/null
+++ b/src/oqmgr/qmgr_error.c
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* qmgr_error 3
+/* SUMMARY
+/* look up/create error/retry queue
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_TRANSPORT *qmgr_error_transport(service)
+/* const char *service;
+/*
+/* QMGR_QUEUE *qmgr_error_queue(service, dsn)
+/* const char *service;
+/* DSN *dsn;
+/*
+/* char *qmgr_error_nexthop(dsn)
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_error_transport() looks up the error transport for the
+/* specified service. The result is null if the transport is
+/* not available.
+/*
+/* qmgr_error_queue() looks up an error queue for the specified
+/* service and problem. The result is null if the queue is not
+/* available.
+/*
+/* qmgr_error_nexthop() computes the next-hop information for
+/* the specified problem. The result must be passed to myfree().
+/*
+/* Arguments:
+/* .IP dsn
+/* See dsn(3).
+/* .IP service
+/* One of MAIL_SERVICE_ERROR or MAIL_SERVICE_RETRY.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_error_transport - look up error transport for specified service */
+
+QMGR_TRANSPORT *qmgr_error_transport(const char *service)
+{
+ QMGR_TRANSPORT *transport;
+
+ /*
+ * Find or create retry transport.
+ */
+ if ((transport = qmgr_transport_find(service)) == 0)
+ transport = qmgr_transport_create(service);
+ if (QMGR_TRANSPORT_THROTTLED(transport))
+ return (0);
+
+ /*
+ * Done.
+ */
+ return (transport);
+}
+
+/* qmgr_error_queue - look up error queue for specified service and problem */
+
+QMGR_QUEUE *qmgr_error_queue(const char *service, DSN *dsn)
+{
+ QMGR_TRANSPORT *transport;
+ QMGR_QUEUE *queue;
+ char *nexthop;
+
+ /*
+ * Find or create transport.
+ */
+ if ((transport = qmgr_error_transport(service)) == 0)
+ return (0);
+
+ /*
+ * Find or create queue.
+ */
+ nexthop = qmgr_error_nexthop(dsn);
+ if ((queue = qmgr_queue_find(transport, nexthop)) == 0)
+ queue = qmgr_queue_create(transport, nexthop, nexthop);
+ myfree(nexthop);
+ if (QMGR_QUEUE_THROTTLED(queue))
+ return (0);
+
+ /*
+ * Done.
+ */
+ return (queue);
+}
+
+/* qmgr_error_nexthop - compute next-hop information from problem description */
+
+char *qmgr_error_nexthop(DSN *dsn)
+{
+ char *nexthop;
+
+ nexthop = concatenate(dsn->status, " ", dsn->reason, (char *) 0);
+ return (nexthop);
+}
diff --git a/src/oqmgr/qmgr_feedback.c b/src/oqmgr/qmgr_feedback.c
new file mode 100644
index 0000000..f8019f8
--- /dev/null
+++ b/src/oqmgr/qmgr_feedback.c
@@ -0,0 +1,182 @@
+/*++
+/* NAME
+/* qmgr_feedback 3
+/* SUMMARY
+/* delivery agent feedback management
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_feedback_init(fbck_ctl, name_prefix, name_tail,
+/* def_name, def_value)
+/* QMGR_FEEDBACK *fbck_ctl;
+/* const char *name_prefix;
+/* const char *name_tail;
+/* const char *def_name;
+/* const char *def_value;
+/*
+/* double QMGR_FEEDBACK_VAL(fbck_ctl, concurrency)
+/* QMGR_FEEDBACK *fbck_ctl;
+/* const int concurrency;
+/* DESCRIPTION
+/* Upon completion of a delivery request, a delivery agent
+/* provides a hint that the scheduler should dedicate fewer or
+/* more resources to a specific destination.
+/*
+/* qmgr_feedback_init() looks up transport-dependent positive
+/* or negative concurrency feedback control information from
+/* main.cf, and converts it to internal form.
+/*
+/* QMGR_FEEDBACK_VAL() computes a concurrency adjustment based
+/* on a preprocessed feedback control information and the
+/* current concurrency window. This is an "unsafe" macro that
+/* evaluates some arguments multiple times.
+/*
+/* Arguments:
+/* .IP fbck_ctl
+/* Pointer to QMGR_FEEDBACK structure where the result will
+/* be stored.
+/* .IP name_prefix
+/* Mail delivery transport name, used as the initial portion
+/* of a transport-dependent concurrency feedback parameter
+/* name.
+/* .IP name_tail
+/* The second, and fixed, portion of a transport-dependent
+/* concurrency feedback parameter.
+/* .IP def_name
+/* The name of a default feedback parameter.
+/* .IP def_val
+/* The value of the default feedback parameter.
+/* .IP concurrency
+/* Delivery concurrency for concurrency-dependent feedback calculation.
+/* DIAGNOSTICS
+/* Warning: configuration error or unreasonable input. The program
+/* uses name_tail feedback instead.
+/* Panic: consistency check failure.
+/* 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 <stdlib.h>
+#include <limits.h> /* INT_MAX */
+#include <stdio.h> /* sscanf() */
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Lookup tables for main.cf feedback method names.
+ */
+const NAME_CODE qmgr_feedback_map[] = {
+ CONC_FDBACK_NAME_WIN, QMGR_FEEDBACK_IDX_WIN,
+#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN
+ CONC_FDBACK_NAME_SQRT_WIN, QMGR_FEEDBACK_IDX_SQRT_WIN,
+#endif
+ 0, QMGR_FEEDBACK_IDX_NONE,
+};
+
+/* qmgr_feedback_init - initialize feedback control */
+
+void qmgr_feedback_init(QMGR_FEEDBACK *fb,
+ const char *name_prefix,
+ const char *name_tail,
+ const char *def_name,
+ const char *def_val)
+{
+ double enum_val;
+ char denom_str[30 + 1];
+ double denom_val;
+ char slash[1 + 1];
+ char junk;
+ char *fbck_name;
+ char *fbck_val;
+
+ /*
+ * Look up the transport-dependent feedback value.
+ */
+ fbck_name = concatenate(name_prefix, name_tail, (char *) 0);
+ fbck_val = get_mail_conf_str(fbck_name, def_val, 1, 0);
+
+ /*
+ * We allow users to express feedback as 1/8, as a more user-friendly
+ * alternative to 0.125 (or worse, having users specify the number of
+ * events in a feedback hysteresis cycle).
+ *
+ * We use some sscanf() fu to parse the value into numerator and optional
+ * "/" followed by denominator. We're doing this only a few times during
+ * the process life time, so we strive for convenience instead of speed.
+ */
+#define INCLUSIVE_BOUNDS(val, low, high) ((val) >= (low) && (val) <= (high))
+
+ fb->hysteresis = 1; /* legacy */
+ fb->base = -1; /* assume error */
+
+ switch (sscanf(fbck_val, "%lf %1[/] %30s%c",
+ &enum_val, slash, denom_str, &junk)) {
+ case 1:
+ fb->index = QMGR_FEEDBACK_IDX_NONE;
+ fb->base = enum_val;
+ break;
+ case 3:
+ if ((fb->index = name_code(qmgr_feedback_map, NAME_CODE_FLAG_NONE,
+ denom_str)) != QMGR_FEEDBACK_IDX_NONE) {
+ fb->base = enum_val;
+ } else if (INCLUSIVE_BOUNDS(enum_val, 0, INT_MAX)
+ && sscanf(denom_str, "%lf%c", &denom_val, &junk) == 1
+ && INCLUSIVE_BOUNDS(denom_val, 1.0 / INT_MAX, INT_MAX)) {
+ fb->base = enum_val / denom_val;
+ }
+ break;
+ }
+
+ /*
+ * Sanity check. If input is bad, we just warn and use a reasonable
+ * default.
+ */
+ if (!INCLUSIVE_BOUNDS(fb->base, 0, 1)) {
+ msg_warn("%s: ignoring malformed or unreasonable feedback: %s",
+ strcmp(fbck_val, def_val) ? fbck_name : def_name, fbck_val);
+ fb->index = QMGR_FEEDBACK_IDX_NONE;
+ fb->base = 1;
+ }
+
+ /*
+ * Performance debugging/analysis.
+ */
+ if (var_conc_feedback_debug)
+ msg_info("%s: %s feedback type %d value at %d: %g",
+ name_prefix, strcmp(fbck_val, def_val) ?
+ fbck_name : def_name, fb->index, var_init_dest_concurrency,
+ QMGR_FEEDBACK_VAL(*fb, var_init_dest_concurrency));
+
+ myfree(fbck_name);
+ myfree(fbck_val);
+}
diff --git a/src/oqmgr/qmgr_message.c b/src/oqmgr/qmgr_message.c
new file mode 100644
index 0000000..b885264
--- /dev/null
+++ b/src/oqmgr/qmgr_message.c
@@ -0,0 +1,1497 @@
+/*++
+/* NAME
+/* qmgr_message 3
+/* SUMMARY
+/* in-core message structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_message_count;
+/* int qmgr_recipient_count;
+/* int qmgr_vrfy_pend_count;
+/*
+/* QMGR_MESSAGE *qmgr_message_alloc(class, name, qflags, mode)
+/* const char *class;
+/* const char *name;
+/* int qflags;
+/* mode_t mode;
+/*
+/* QMGR_MESSAGE *qmgr_message_realloc(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_free(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_update_warn(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_kill_record(message, offset)
+/* QMGR_MESSAGE *message;
+/* long offset;
+/* DESCRIPTION
+/* This module performs en-gross operations on queue messages.
+/*
+/* qmgr_message_count is a global counter for the total number
+/* of in-core message structures (i.e. the total size of the
+/* `active' message queue).
+/*
+/* qmgr_recipient_count is a global counter for the total number
+/* of in-core recipient structures (i.e. the sum of all recipients
+/* in all in-core message structures).
+/*
+/* qmgr_vrfy_pend_count is a global counter for the total
+/* number of in-core message structures that are associated
+/* with an address verification request. Requests that exceed
+/* the address_verify_pending_limit are deferred immediately.
+/* This is a backup mechanism for a more refined enforcement
+/* mechanism in the verify(8) daemon.
+/*
+/* qmgr_message_alloc() creates an in-core message structure
+/* with sender and recipient information taken from the named queue
+/* file. A null result means the queue file could not be read or
+/* that the queue file contained incorrect information. A result
+/* QMGR_MESSAGE_LOCKED means delivery must be deferred. The number
+/* of recipients read from a queue file is limited by the global
+/* var_qmgr_rcpt_limit configuration parameter. When the limit
+/* is reached, the \fIrcpt_offset\fR structure member is set to
+/* the position where the read was terminated. Recipients are
+/* run through the resolver, and are assigned to destination
+/* queues. Recipients that cannot be assigned are deferred or
+/* bounced. Mail that has bounced twice is silently absorbed.
+/* A non-zero mode means change the queue file permissions.
+/*
+/* qmgr_message_realloc() resumes reading recipients from the queue
+/* file, and updates the recipient list and \fIrcpt_offset\fR message
+/* structure members. A null result means that the file could not be
+/* read or that the file contained incorrect information.
+/*
+/* qmgr_message_free() destroys an in-core message structure and makes
+/* the resources available for reuse. It is an error to destroy
+/* a message structure that is still referenced by queue entry structures.
+/*
+/* qmgr_message_update_warn() takes a closed message, opens it, updates
+/* the warning field, and closes it again.
+/*
+/* qmgr_message_kill_record() takes a closed message, opens it, updates
+/* the record type at the given offset to "killed", and closes the file.
+/* A killed envelope record is ignored. Killed records are not allowed
+/* inside the message content.
+/* DIAGNOSTICS
+/* Warnings: malformed message file. Fatal errors: out of memory.
+/* SEE ALSO
+/* envelope(3) message envelope parser
+/* 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/stat.h>
+#include <stdlib.h>
+#include <stdio.h> /* sscanf() */
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <split_at.h>
+#include <valid_hostname.h>
+#include <argv.h>
+#include <stringops.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include <dict.h>
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <canon_addr.h>
+#include <record.h>
+#include <rec_type.h>
+#include <sent.h>
+#include <deliver_completed.h>
+#include <opened.h>
+#include <verp_sender.h>
+#include <mail_proto.h>
+#include <qmgr_user.h>
+#include <split_addr.h>
+#include <dsn_mask.h>
+#include <rec_attr_map.h>
+
+/* Client stubs. */
+
+#include <rewrite_clnt.h>
+#include <resolve_clnt.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+int qmgr_message_count;
+int qmgr_recipient_count;
+int qmgr_vrfy_pend_count;
+
+/* qmgr_message_create - create in-core message structure */
+
+static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
+ const char *queue_id, int qflags)
+{
+ QMGR_MESSAGE *message;
+
+ message = (QMGR_MESSAGE *) mymalloc(sizeof(QMGR_MESSAGE));
+ qmgr_message_count++;
+ message->flags = 0;
+ message->qflags = qflags;
+ message->tflags = 0;
+ message->tflags_offset = 0;
+ message->rflags = QMGR_READ_FLAG_DEFAULT;
+ message->fp = 0;
+ message->refcount = 0;
+ message->single_rcpt = 0;
+ message->arrival_time.tv_sec = message->arrival_time.tv_usec = 0;
+ message->create_time = 0;
+ GETTIMEOFDAY(&message->active_time);
+ message->data_offset = 0;
+ message->queue_id = mystrdup(queue_id);
+ message->queue_name = mystrdup(queue_name);
+ message->encoding = 0;
+ message->sender = 0;
+ message->dsn_envid = 0;
+ message->dsn_ret = 0;
+ message->smtputf8 = 0;
+ message->filter_xport = 0;
+ message->inspect_xport = 0;
+ message->redirect_addr = 0;
+ message->data_size = 0;
+ message->cont_length = 0;
+ message->warn_offset = 0;
+ message->warn_time = 0;
+ message->rcpt_offset = 0;
+ message->verp_delims = 0;
+ message->client_name = 0;
+ message->client_addr = 0;
+ message->client_port = 0;
+ message->client_proto = 0;
+ message->client_helo = 0;
+ message->sasl_method = 0;
+ message->sasl_username = 0;
+ message->sasl_sender = 0;
+ message->log_ident = 0;
+ message->rewrite_context = 0;
+ recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
+ return (message);
+}
+
+/* qmgr_message_close - close queue file */
+
+static void qmgr_message_close(QMGR_MESSAGE *message)
+{
+ vstream_fclose(message->fp);
+ message->fp = 0;
+}
+
+/* qmgr_message_open - open queue file */
+
+static int qmgr_message_open(QMGR_MESSAGE *message)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (message->fp)
+ msg_panic("%s: queue file is open", message->queue_id);
+
+ /*
+ * Open this queue file. Skip files that we cannot open. Back off when
+ * the system appears to be running out of resources.
+ */
+ if ((message->fp = mail_queue_open(message->queue_name,
+ message->queue_id,
+ O_RDWR, 0)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", message->queue_name, message->queue_id);
+ msg_warn("open %s %s: %m", message->queue_name, message->queue_id);
+ return (-1);
+ }
+ return (0);
+}
+
+/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */
+
+static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
+{
+ VSTRING *buf;
+ long orig_offset, extra_offset;
+ int rec_type;
+ char *start;
+
+ /*
+ * Initialize. No early returns or we have a memory leak.
+ */
+ buf = vstring_alloc(100);
+ if ((orig_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * Rewind to the very beginning to make sure we see all records.
+ */
+ if (vstream_fseek(message->fp, 0, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * Scan through the old style queue file. Count the total number of
+ * recipients and find the data/extra sections offsets. Note that the new
+ * queue files require that data_size equals extra_offset - data_offset,
+ * so we set data_size to this as well and ignore the size record itself
+ * completely.
+ */
+ for (;;) {
+ rec_type = rec_get(message->fp, buf, 0);
+ if (rec_type <= 0)
+ /* Report missing end record later. */
+ break;
+ start = vstring_str(buf);
+ if (msg_verbose > 1)
+ msg_info("old-style scan record %c %s", rec_type, start);
+ if (rec_type == REC_TYPE_END)
+ break;
+ if (rec_type == REC_TYPE_MESG) {
+ if (message->data_offset == 0) {
+ if ((message->data_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+ if ((extra_offset = atol(start)) <= message->data_offset)
+ msg_fatal("bad extra offset %s file %s",
+ start, VSTREAM_PATH(message->fp));
+ if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ message->data_size = extra_offset - message->data_offset;
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ vstring_free(buf);
+
+ /*
+ * Sanity checks. Verify that all required information was found,
+ * including the queue file end marker.
+ */
+ if (message->data_offset == 0 || rec_type != REC_TYPE_END)
+ msg_fatal("%s: envelope records out of order", message->queue_id);
+}
+
+/* qmgr_message_read - read envelope records */
+
+static int qmgr_message_read(QMGR_MESSAGE *message)
+{
+ VSTRING *buf;
+ int rec_type;
+ long curr_offset;
+ long save_offset = message->rcpt_offset; /* save a flag */
+ char *start;
+ int nrcpt = 0;
+ const char *error_text;
+ char *name;
+ char *value;
+ char *orig_rcpt = 0;
+ int count;
+ int dsn_notify = 0;
+ char *dsn_orcpt = 0;
+ int n;
+ int have_log_client_attr = 0;
+ static const char env_rec_types[] = REC_TYPE_ENVELOPE REC_TYPE_EXTRACT;
+ static const char extra_rec_type[] = {REC_TYPE_XTRA, 0};
+ const char *expected_rec_types;
+
+ /*
+ * Initialize. No early returns or we have a memory leak.
+ */
+ buf = vstring_alloc(100);
+
+ /*
+ * If we re-open this file, skip over on-file recipient records that we
+ * already looked at, and refill the in-core recipient address list.
+ */
+ if (message->rcpt_offset) {
+ if (message->rcpt_list.len)
+ msg_panic("%s: recipient list not empty on recipient reload",
+ message->queue_id);
+ if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ message->rcpt_offset = 0;
+ }
+
+ /*
+ * Read envelope records. XXX Rely on the front-end programs to enforce
+ * record size limits. Read up to var_qmgr_rcpt_limit recipients from the
+ * queue file, to protect against memory exhaustion. Recipient records
+ * may appear before or after the message content, so we keep reading
+ * from the queue file until we have enough recipients (rcpt_offset != 0)
+ * and until we know all the non-recipient information.
+ *
+ * When reading recipients from queue file, stop reading when we reach a
+ * per-message in-core recipient limit rather than a global in-core
+ * recipient limit. Use the global recipient limit only in order to stop
+ * opening queue files. The purpose is to achieve equal delay for
+ * messages with recipient counts up to var_qmgr_rcpt_limit recipients.
+ *
+ * If we would read recipients up to a global recipient limit, the average
+ * number of in-core recipients per message would asymptotically approach
+ * (global recipient limit)/(active queue size limit), which gives equal
+ * delay per recipient rather than equal delay per message.
+ *
+ * On the first open, we must examine all non-recipient records.
+ *
+ * Optimization: when we know that recipient records are not mixed with
+ * non-recipient records, as is typical with mailing list mail, then we
+ * can avoid having to examine all the queue file records before we can
+ * start deliveries. This avoids some file system thrashing with huge
+ * mailing lists.
+ */
+ for (;;) {
+ expected_rec_types = env_rec_types;
+ if ((curr_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+ if (curr_offset == message->data_offset && curr_offset > 0) {
+ if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ curr_offset += message->data_size;
+ expected_rec_types = extra_rec_type;
+ }
+ rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE);
+ start = vstring_str(buf);
+ if (msg_verbose > 1)
+ msg_info("record %c %s", rec_type, start);
+ if (rec_type == REC_TYPE_PTR) {
+ if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR)
+ break;
+ /* Need to update curr_offset after pointer jump. */
+ continue;
+ }
+ if (rec_type <= 0) {
+ msg_warn("%s: message rejected: missing end record",
+ message->queue_id);
+ break;
+ }
+ if (strchr(expected_rec_types, rec_type) == 0) {
+ msg_warn("Unexpected record type '%c' at offset %ld",
+ rec_type, (long) curr_offset);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if (rec_type == REC_TYPE_END) {
+ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
+ break;
+ }
+
+ /*
+ * Map named attributes to pseudo record types, 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 (rec_type == REC_TYPE_ATTR) {
+ if ((error_text = split_nameval(start, &name, &value)) != 0) {
+ msg_warn("%s: bad attribute record: %s: %.200s",
+ message->queue_id, error_text, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if ((n = rec_attr_map(name)) != 0) {
+ start = value;
+ rec_type = n;
+ }
+ }
+
+ /*
+ * Process recipient records.
+ */
+ if (rec_type == REC_TYPE_RCPT) {
+ /* See also below for code setting orig_rcpt etc. */
+#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0))
+ if (message->rcpt_offset == 0) {
+ recipient_list_add(&message->rcpt_list, curr_offset,
+ dsn_orcpt ? dsn_orcpt : "",
+ dsn_notify ? dsn_notify : 0,
+ orig_rcpt ? orig_rcpt : "", start);
+ if (dsn_orcpt) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (orig_rcpt) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_notify)
+ dsn_notify = 0;
+ if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) {
+ if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m",
+ VSTREAM_PATH(message->fp));
+ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
+ /* We already examined all non-recipient records. */
+ break;
+ if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER)
+ /* Examine all remaining non-recipient records. */
+ continue;
+ /* Optimizations for "pure recipient" record sections. */
+ if (curr_offset > message->data_offset) {
+ /* We already examined all non-recipient records. */
+ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
+ break;
+ }
+
+ /*
+ * Examine non-recipient records in the extracted
+ * segment. Note that this skips to the message start
+ * record, because the handler for that record changes
+ * the expectations for allowed record types.
+ */
+ if (vstream_fseek(message->fp, message->data_offset,
+ SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ continue;
+ }
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) {
+ if (message->rcpt_offset == 0) {
+ if (dsn_orcpt) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (orig_rcpt) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_notify)
+ dsn_notify = 0;
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_ORCPT) {
+ /* See also above for code clearing dsn_orcpt. */
+ if (dsn_orcpt != 0) {
+ msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>",
+ message->queue_id, dsn_orcpt);
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (message->rcpt_offset == 0)
+ dsn_orcpt = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_NOTIFY) {
+ /* See also above for code clearing dsn_notify. */
+ if (dsn_notify != 0) {
+ msg_warn("%s: ignoring out-of-order DSN notify flags <%d>",
+ message->queue_id, dsn_notify);
+ dsn_notify = 0;
+ }
+ if (message->rcpt_offset == 0) {
+ if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n))
+ msg_warn("%s: ignoring malformed DSN notify flags <%.200s>",
+ message->queue_id, start);
+ else
+ dsn_notify = n;
+ continue;
+ }
+ }
+ if (rec_type == REC_TYPE_ORCP) {
+ /* See also above for code clearing orig_rcpt. */
+ if (orig_rcpt != 0) {
+ msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
+ message->queue_id, orig_rcpt);
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (message->rcpt_offset == 0)
+ orig_rcpt = mystrdup(start);
+ continue;
+ }
+
+ /*
+ * Process non-recipient records.
+ */
+ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
+ /* We already examined all non-recipient records. */
+ continue;
+ if (rec_type == REC_TYPE_SIZE) {
+ if (message->data_offset == 0) {
+ if ((count = sscanf(start, "%ld %ld %d %d %ld %d",
+ &message->data_size, &message->data_offset,
+ &nrcpt, &message->rflags,
+ &message->cont_length,
+ &message->smtputf8)) >= 3) {
+ /* Postfix >= 1.0 (a.k.a. 20010228). */
+ if (message->data_offset <= 0 || message->data_size <= 0) {
+ msg_warn("%s: invalid size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if (message->rflags & ~QMGR_READ_FLAG_USER) {
+ msg_warn("%s: invalid flags in size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ } else if (count == 1) {
+ /* Postfix < 1.0 (a.k.a. 20010228). */
+ qmgr_message_oldstyle_scan(message);
+ } else {
+ /* Can't happen. */
+ msg_warn("%s: message rejected: weird size record",
+ message->queue_id);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ }
+ /* Postfix < 2.4 compatibility. */
+ if (message->cont_length == 0) {
+ message->cont_length = message->data_size;
+ } else if (message->cont_length < 0) {
+ msg_warn("%s: invalid size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_TIME) {
+ if (message->arrival_time.tv_sec == 0)
+ REC_TYPE_TIME_SCAN(start, message->arrival_time);
+ continue;
+ }
+ if (rec_type == REC_TYPE_CTIME) {
+ if (message->create_time == 0)
+ message->create_time = atol(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_FILT) {
+ if (message->filter_xport != 0)
+ myfree(message->filter_xport);
+ message->filter_xport = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_INSP) {
+ if (message->inspect_xport != 0)
+ myfree(message->inspect_xport);
+ message->inspect_xport = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_RDR) {
+ if (message->redirect_addr != 0)
+ myfree(message->redirect_addr);
+ message->redirect_addr = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_FROM) {
+ if (message->sender == 0) {
+ message->sender = mystrdup(start);
+ opened(message->queue_id, message->sender,
+ message->cont_length, nrcpt,
+ "queue %s", message->queue_name);
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_ENVID) {
+ /* Allow Milter override. */
+ if (message->dsn_envid != 0)
+ myfree(message->dsn_envid);
+ message->dsn_envid = mystrdup(start);
+ }
+ if (rec_type == REC_TYPE_DSN_RET) {
+ /* Allow Milter override. */
+ if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n))
+ msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s",
+ message->queue_id, start);
+ else
+ message->dsn_ret = n;
+ }
+ if (rec_type == REC_TYPE_ATTR) {
+ /* Allow extra segment to override envelope segment info. */
+ if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
+ if (message->encoding != 0)
+ myfree(message->encoding);
+ message->encoding = mystrdup(value);
+ }
+
+ /*
+ * Backwards compatibility. Before Postfix 2.3, the logging
+ * attributes were called client_name, etc. Now they are called
+ * log_client_name. etc., and client_name is used for the actual
+ * client information. To support old queue files, we accept both
+ * names for the purpose of logging; the new name overrides the
+ * old one.
+ *
+ * XXX Do not use the "legacy" client_name etc. attribute values for
+ * initializing the logging attributes, when this file already
+ * contains the "modern" log_client_name etc. logging attributes.
+ * Otherwise, logging attributes that are not present in the
+ * queue file would be set with information from the real client.
+ */
+ else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_name == 0)
+ message->client_name = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) {
+ if (have_log_client_attr == 0 && message->client_addr == 0)
+ message->client_addr = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) {
+ if (have_log_client_attr == 0 && message->client_port == 0)
+ message->client_port = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_proto == 0)
+ message->client_proto = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_helo == 0)
+ message->client_helo = mystrdup(value);
+ }
+ /* Original client attributes. */
+ else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) {
+ if (message->client_name != 0)
+ myfree(message->client_name);
+ message->client_name = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) {
+ if (message->client_addr != 0)
+ myfree(message->client_addr);
+ message->client_addr = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) {
+ if (message->client_port != 0)
+ myfree(message->client_port);
+ message->client_port = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) {
+ if (message->client_proto != 0)
+ myfree(message->client_proto);
+ message->client_proto = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) {
+ if (message->client_helo != 0)
+ myfree(message->client_helo);
+ message->client_helo = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) {
+ if (message->sasl_method == 0)
+ message->sasl_method = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_METHOD, value);
+ } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) {
+ if (message->sasl_username == 0)
+ message->sasl_username = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_USERNAME, value);
+ } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) {
+ if (message->sasl_sender == 0)
+ message->sasl_sender = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_SENDER, value);
+ } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) {
+ if (message->log_ident == 0)
+ message->log_ident = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_LOG_IDENT, value);
+ } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) {
+ if (message->rewrite_context == 0)
+ message->rewrite_context = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_RWR_CONTEXT, value);
+ }
+
+ /*
+ * Optional tracing flags (verify, sendmail -v, sendmail -bv).
+ * This record is killed after a trace logfile report is sent and
+ * after the logfile is deleted.
+ */
+ else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
+ if (message->tflags == 0) {
+ message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
+ if (message->tflags == DEL_REQ_FLAG_RECORD)
+ message->tflags_offset = curr_offset;
+ else
+ message->tflags_offset = 0;
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0)
+ qmgr_vrfy_pend_count++;
+ }
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_WARN) {
+ if (message->warn_offset == 0) {
+ message->warn_offset = curr_offset;
+ REC_TYPE_WARN_SCAN(start, message->warn_time);
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_VERP) {
+ if (message->verp_delims == 0) {
+ if (message->sender == 0 || message->sender[0] == 0) {
+ msg_warn("%s: ignoring VERP request for null sender",
+ message->queue_id);
+ } else if (verp_delims_verify(start) != 0) {
+ msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
+ message->queue_id, start);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: enabling VERP for sender \"%.100s\"",
+ message->queue_id, message->sender);
+ message->single_rcpt = 1;
+ message->verp_delims = mystrdup(start);
+ }
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Grr.
+ */
+ if (dsn_orcpt != 0) {
+ if (rec_type > 0)
+ msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>",
+ message->queue_id, dsn_orcpt);
+ myfree(dsn_orcpt);
+ }
+ if (orig_rcpt != 0) {
+ if (rec_type > 0)
+ msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
+ message->queue_id, orig_rcpt);
+ myfree(orig_rcpt);
+ }
+
+ /*
+ * After sending a "delayed" warning, request sender notification when
+ * message delivery is completed. While "mail delayed" notifications are
+ * bad enough because they multiply the amount of email traffic, "delay
+ * cleared" notifications are even worse because they come in a sudden
+ * burst when the queue drains after a network outage.
+ */
+ if (var_dsn_delay_cleared && message->warn_time < 0)
+ message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT;
+
+ /*
+ * Avoid clumsiness elsewhere in the program. When sending data across an
+ * IPC channel, sending an empty string is more convenient than sending a
+ * null pointer.
+ */
+ if (message->dsn_envid == 0)
+ message->dsn_envid = mystrdup("");
+ if (message->encoding == 0)
+ message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
+ if (message->client_name == 0)
+ message->client_name = mystrdup("");
+ if (message->client_addr == 0)
+ message->client_addr = mystrdup("");
+ if (message->client_port == 0)
+ message->client_port = mystrdup("");
+ if (message->client_proto == 0)
+ message->client_proto = mystrdup("");
+ if (message->client_helo == 0)
+ message->client_helo = mystrdup("");
+ if (message->sasl_method == 0)
+ message->sasl_method = mystrdup("");
+ if (message->sasl_username == 0)
+ message->sasl_username = mystrdup("");
+ if (message->sasl_sender == 0)
+ message->sasl_sender = mystrdup("");
+ if (message->log_ident == 0)
+ message->log_ident = mystrdup("");
+ if (message->rewrite_context == 0)
+ message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL);
+ /* Postfix < 2.3 compatibility. */
+ if (message->create_time == 0)
+ message->create_time = message->arrival_time.tv_sec;
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+
+ /*
+ * Sanity checks. Verify that all required information was found,
+ * including the queue file end marker.
+ */
+ if (rec_type <= 0) {
+ /* Already logged warning. */
+ } else if (message->arrival_time.tv_sec == 0) {
+ msg_warn("%s: message rejected: missing arrival time record",
+ message->queue_id);
+ } else if (message->sender == 0) {
+ msg_warn("%s: message rejected: missing sender record",
+ message->queue_id);
+ } else if (message->data_offset == 0) {
+ msg_warn("%s: message rejected: missing size record",
+ message->queue_id);
+ } else {
+ return (0);
+ }
+ message->rcpt_offset = save_offset; /* restore flag */
+ return (-1);
+}
+
+/* qmgr_message_update_warn - update the time of next delay warning */
+
+void qmgr_message_update_warn(QMGR_MESSAGE *message)
+{
+
+ /*
+ * After the "mail delayed" warning, optionally send a "delay cleared"
+ * notification.
+ */
+ if (qmgr_message_open(message)
+ || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0
+ || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
+ REC_TYPE_WARN_ARG(-1)) < 0
+ || vstream_fflush(message->fp))
+ msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
+ qmgr_message_close(message);
+}
+
+/* qmgr_message_kill_record - mark one message record as killed */
+
+void qmgr_message_kill_record(QMGR_MESSAGE *message, long offset)
+{
+ if (offset <= 0)
+ msg_panic("qmgr_message_kill_record: bad offset 0x%lx", offset);
+ if (qmgr_message_open(message)
+ || rec_put_type(message->fp, REC_TYPE_KILL, offset) < 0
+ || vstream_fflush(message->fp))
+ msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
+ qmgr_message_close(message);
+}
+
+/* qmgr_message_sort_compare - compare recipient information */
+
+static int qmgr_message_sort_compare(const void *p1, const void *p2)
+{
+ RECIPIENT *rcpt1 = (RECIPIENT *) p1;
+ RECIPIENT *rcpt2 = (RECIPIENT *) p2;
+ QMGR_QUEUE *queue1;
+ QMGR_QUEUE *queue2;
+ char *at1;
+ char *at2;
+ int result;
+
+ /*
+ * Compare most significant to least significant recipient attributes.
+ * The comparison function must be transitive, so NULL values need to be
+ * assigned an ordinal (we set NULL last).
+ */
+
+ queue1 = rcpt1->u.queue;
+ queue2 = rcpt2->u.queue;
+ if (queue1 != 0 && queue2 == 0)
+ return (-1);
+ if (queue1 == 0 && queue2 != 0)
+ return (1);
+ if (queue1 != 0 && queue2 != 0) {
+
+ /*
+ * Compare message transport.
+ */
+ if ((result = strcmp(queue1->transport->name,
+ queue2->transport->name)) != 0)
+ return (result);
+
+ /*
+ * Compare queue name (nexthop or recipient@nexthop).
+ */
+ if ((result = strcmp(queue1->name, queue2->name)) != 0)
+ return (result);
+ }
+
+ /*
+ * Compare recipient domain.
+ */
+ at1 = strrchr(rcpt1->address, '@');
+ at2 = strrchr(rcpt2->address, '@');
+ if (at1 == 0 && at2 != 0)
+ return (1);
+ if (at1 != 0 && at2 == 0)
+ return (-1);
+ if (at1 != 0 && at2 != 0
+ && (result = strcasecmp_utf8(at1, at2)) != 0)
+ return (result);
+
+ /*
+ * Compare recipient address.
+ */
+ return (strcasecmp_utf8(rcpt1->address, rcpt2->address));
+}
+
+/* qmgr_message_sort - sort message recipient addresses by domain */
+
+static void qmgr_message_sort(QMGR_MESSAGE *message)
+{
+ qsort((void *) message->rcpt_list.info, message->rcpt_list.len,
+ sizeof(message->rcpt_list.info[0]), qmgr_message_sort_compare);
+ if (msg_verbose) {
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *rcpt;
+
+ msg_info("start sorted recipient list");
+ for (rcpt = list.info; rcpt < list.info + list.len; rcpt++)
+ msg_info("qmgr_message_sort: %s", rcpt->address);
+ msg_info("end sorted recipient list");
+ }
+}
+
+/* qmgr_resolve_one - resolve or skip one recipient */
+
+static int qmgr_resolve_one(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ const char *addr, RESOLVE_REPLY *reply)
+{
+#define QMGR_REDIRECT(rp, tp, np) do { \
+ (rp)->flags = 0; \
+ vstring_strcpy((rp)->transport, (tp)); \
+ vstring_strcpy((rp)->nexthop, (np)); \
+ } while (0)
+
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) == 0)
+ resolve_clnt_query_from(message->sender, addr, reply);
+ else
+ resolve_clnt_verify_from(message->sender, addr, reply);
+ if (reply->flags & RESOLVE_FLAG_FAIL) {
+ QMGR_REDIRECT(reply, MAIL_SERVICE_RETRY,
+ "4.3.0 address resolver failure");
+ return (0);
+ } else if (reply->flags & RESOLVE_FLAG_ERROR) {
+ QMGR_REDIRECT(reply, MAIL_SERVICE_ERROR,
+ "5.1.3 bad address syntax");
+ return (0);
+ } else {
+ return (0);
+ }
+}
+
+/* qmgr_message_resolve - resolve recipients */
+
+static void qmgr_message_resolve(QMGR_MESSAGE *message)
+{
+ static ARGV *defer_xport_argv;
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_TRANSPORT *transport = 0;
+ QMGR_QUEUE *queue = 0;
+ RESOLVE_REPLY reply;
+ VSTRING *queue_name;
+ char *at;
+ char **cpp;
+ char *nexthop;
+ ssize_t len;
+ int status;
+ DSN dsn;
+ MSG_STATS stats;
+ DSN *saved_dsn;
+
+#define STREQ(x,y) (strcmp(x,y) == 0)
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+ resolve_clnt_init(&reply);
+ queue_name = vstring_alloc(1);
+ for (recipient = list.info; recipient < list.info + list.len; recipient++) {
+
+ /*
+ * Redirect overrides all else. But only once (per entire message).
+ * For consistency with the remainder of Postfix, rewrite the address
+ * to canonical form before resolving it.
+ */
+ if (message->redirect_addr) {
+ if (recipient > list.info) {
+ recipient->u.queue = 0;
+ continue;
+ }
+ message->rcpt_offset = 0;
+ rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr,
+ reply.recipient);
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ if (qmgr_resolve_one(message, recipient,
+ recipient->address, &reply) < 0)
+ continue;
+ if (!STREQ(recipient->address, STR(reply.recipient)))
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ }
+
+ /*
+ * Content filtering overrides the address resolver.
+ *
+ * XXX Bypass content_filter inspection for user-generated probes
+ * (sendmail -bv). MTA-generated probes never have the "please filter
+ * me" bits turned on, but we handle them here anyway for the sake of
+ * future proofing.
+ */
+#define FILTER_WITHOUT_NEXTHOP(filter, next) \
+ (((next) = split_at((filter), ':')) == 0 || *(next) == 0)
+
+#define RCPT_WITHOUT_DOMAIN(rcpt, next) \
+ ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0)
+
+ else if (message->filter_xport
+ && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) {
+ reply.flags = 0;
+ vstring_strcpy(reply.transport, message->filter_xport);
+ if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop)
+ && *(nexthop = var_def_filter_nexthop) == 0
+ && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop))
+ nexthop = var_myhostname;
+ vstring_strcpy(reply.nexthop, nexthop);
+ vstring_strcpy(reply.recipient, recipient->address);
+ }
+
+ /*
+ * Resolve the destination to (transport, nexthop, address). The
+ * result address may differ from the one specified by the sender.
+ */
+ else {
+ if (qmgr_resolve_one(message, recipient,
+ recipient->address, &reply) < 0)
+ continue;
+ if (!STREQ(recipient->address, STR(reply.recipient)))
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ }
+
+ /*
+ * Bounce null recipients. This should never happen, but is most
+ * likely the result of a fault in a different program, so aborting
+ * the queue manager process does not help.
+ */
+ if (recipient->address[0] == 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR,
+ "5.1.3 null recipient address");
+ }
+
+ /*
+ * Redirect a forced-to-expire message without defer log to the retry
+ * service, so that its defer log will contain an appropriate reason.
+ * Do not redirect such a message to the error service, because if
+ * that request fails, a defer log would be created with reason
+ * "bounce or trace service failure" which would make no sense. Note
+ * that if the bounce service fails to create a defer log, the
+ * message will be returned as undeliverable anyway, because it is
+ * expired.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.7.0 message is administratively expired");
+ }
+
+ /*
+ * Discard mail to the local double bounce address here, so this
+ * system can run without a local delivery agent. They'd still have
+ * to configure something for mail directed to the local postmaster,
+ * though, but that is an RFC requirement anyway.
+ *
+ * XXX This lookup should be done in the resolver, and the mail should
+ * be directed to a general-purpose null delivery agent.
+ */
+ if (reply.flags & RESOLVE_CLASS_LOCAL) {
+ at = strrchr(STR(reply.recipient), '@');
+ len = (at ? (at - STR(reply.recipient))
+ : strlen(STR(reply.recipient)));
+ if (strncasecmp_utf8(STR(reply.recipient),
+ var_double_bounce_sender, len) == 0
+ && !var_double_bounce_sender[len]) {
+ status = sent(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", DSN_SIMPLE(&dsn, "2.0.0",
+ "undeliverable postmaster notification discarded"));
+ if (status == 0) {
+ deliver_completed(message->fp, recipient->offset);
+#if 0
+ /* It's the default verification probe sender address. */
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ message->queue_id);
+#endif
+ } else
+ message->flags |= status;
+ continue;
+ }
+ }
+
+ /*
+ * Optionally defer deliveries over specific transports, unless the
+ * restriction is lifted temporarily.
+ */
+ if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) {
+ if (defer_xport_argv == 0)
+ defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP);
+ for (cpp = defer_xport_argv->argv; *cpp; cpp++)
+ if (strcmp(*cpp, STR(reply.transport)) == 0)
+ break;
+ if (*cpp) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.3.2 deferred transport");
+ }
+ }
+
+ /*
+ * Safety: defer excess address verification requests.
+ */
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0
+ && qmgr_vrfy_pend_count > var_vrfy_pend_limit)
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.3.2 Too many address verification requests");
+
+ /*
+ * Look up or instantiate the proper transport.
+ */
+ if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) {
+ if ((transport = qmgr_transport_find(STR(reply.transport))) == 0)
+ transport = qmgr_transport_create(STR(reply.transport));
+ queue = 0;
+ }
+
+ /*
+ * This message is being flushed. If need-be unthrottle the
+ * transport.
+ */
+ if ((message->qflags & QMGR_FLUSH_EACH) != 0
+ && QMGR_TRANSPORT_THROTTLED(transport))
+ qmgr_transport_unthrottle(transport);
+
+ /*
+ * This transport is dead. Defer delivery to this recipient.
+ */
+ if (QMGR_TRANSPORT_THROTTLED(transport)) {
+ saved_dsn = transport->dsn;
+ if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) {
+ nexthop = qmgr_error_nexthop(saved_dsn);
+ vstring_strcpy(reply.nexthop, nexthop);
+ myfree(nexthop);
+ queue = 0;
+ } else {
+ qmgr_defer_recipient(message, recipient, saved_dsn);
+ continue;
+ }
+ }
+
+ /*
+ * The nexthop destination provides the default name for the
+ * per-destination queue. When the delivery agent accepts only one
+ * recipient per delivery, give each recipient its own queue, so that
+ * deliveries to different recipients of the same message can happen
+ * in parallel, and so that we can enforce per-recipient concurrency
+ * limits and prevent one recipient from tying up all the delivery
+ * agent resources. We use recipient@nexthop as queue name rather
+ * than the actual recipient domain name, so that one recipient in
+ * multiple equivalent domains cannot evade the per-recipient
+ * concurrency limit. Split the address on the recipient delimiter if
+ * one is defined, so that extended addresses don't get extra
+ * delivery slots.
+ *
+ * Fold the result to lower case so that we don't have multiple queues
+ * for the same name.
+ *
+ * Important! All recipients in a queue must have the same nexthop
+ * value. It is OK to have multiple queues with the same nexthop
+ * value, but only when those queues are named after recipients.
+ *
+ * The single-recipient code below was written for local(8) like
+ * delivery agents, and assumes that all domains that deliver to the
+ * same (transport + nexthop) are aliases for $nexthop. Delivery
+ * concurrency is changed from per-domain into per-recipient, by
+ * changing the queue name from nexthop into localpart@nexthop.
+ *
+ * XXX This assumption is incorrect when different destinations share
+ * the same (transport + nexthop). In reality, such transports are
+ * rarely configured to use single-recipient deliveries. The fix is
+ * to decouple the per-destination recipient limit from the
+ * per-destination concurrency.
+ */
+ vstring_strcpy(queue_name, STR(reply.nexthop));
+ if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0
+ && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0
+ && transport->recipient_limit == 1) {
+ /* Copy the recipient localpart. */
+ at = strrchr(STR(reply.recipient), '@');
+ len = (at ? (at - STR(reply.recipient))
+ : strlen(STR(reply.recipient)));
+ vstring_strncpy(queue_name, STR(reply.recipient), len);
+ /* Remove the address extension from the recipient localpart. */
+ if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim))
+ vstring_truncate(queue_name, strlen(STR(queue_name)));
+ /* Assume the recipient domain is equivalent to nexthop. */
+ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
+ }
+ lowercase(STR(queue_name));
+
+ /*
+ * This transport is alive. Find or instantiate a queue for this
+ * recipient.
+ */
+ if (queue == 0 || !STREQ(queue->name, STR(queue_name))) {
+ if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0)
+ queue = qmgr_queue_create(transport, STR(queue_name),
+ STR(reply.nexthop));
+ }
+
+ /*
+ * This message is being flushed. If need-be unthrottle the queue.
+ */
+ if ((message->qflags & QMGR_FLUSH_EACH) != 0
+ && QMGR_QUEUE_THROTTLED(queue))
+ qmgr_queue_unthrottle(queue);
+
+ /*
+ * This queue is dead. Defer delivery to this recipient.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ saved_dsn = queue->dsn;
+ if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) {
+ qmgr_defer_recipient(message, recipient, saved_dsn);
+ continue;
+ }
+ }
+
+ /*
+ * This queue is alive. Bind this recipient to this queue instance.
+ */
+ recipient->u.queue = queue;
+ }
+ resolve_clnt_free(&reply);
+ vstring_free(queue_name);
+}
+
+/* qmgr_message_assign - assign recipients to specific delivery requests */
+
+static void qmgr_message_assign(QMGR_MESSAGE *message)
+{
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_ENTRY *entry = 0;
+ QMGR_QUEUE *queue;
+
+ /*
+ * Try to bundle as many recipients in a delivery request as we can. When
+ * the recipient resolves to the same site and transport as the previous
+ * recipient, do not create a new queue entry, just move that recipient
+ * to the recipient list of the existing queue entry. All this provided
+ * that we do not exceed the transport-specific limit on the number of
+ * recipients per transaction. Skip recipients with a dead transport or
+ * destination.
+ */
+#define LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit)))
+
+ for (recipient = list.info; recipient < list.info + list.len; recipient++) {
+ if ((queue = recipient->u.queue) != 0) {
+ if (message->single_rcpt || entry == 0 || entry->queue != queue
+ || !LIMIT_OK(entry->queue->transport->recipient_limit,
+ entry->rcpt_list.len)) {
+ entry = qmgr_entry_create(queue, message);
+ }
+ recipient_list_add(&entry->rcpt_list, recipient->offset,
+ recipient->dsn_orcpt, recipient->dsn_notify,
+ recipient->orig_addr, recipient->address);
+ qmgr_recipient_count++;
+ }
+ }
+ recipient_list_free(&message->rcpt_list);
+ recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
+}
+
+/* qmgr_message_free - release memory for in-core message structure */
+
+void qmgr_message_free(QMGR_MESSAGE *message)
+{
+ if (message->refcount != 0)
+ msg_panic("qmgr_message_free: reference len: %d", message->refcount);
+ if (message->fp)
+ msg_panic("qmgr_message_free: queue file is open");
+ myfree(message->queue_id);
+ myfree(message->queue_name);
+ if (message->dsn_envid)
+ myfree(message->dsn_envid);
+ if (message->encoding)
+ myfree(message->encoding);
+ if (message->sender)
+ myfree(message->sender);
+ if (message->verp_delims)
+ myfree(message->verp_delims);
+ if (message->filter_xport)
+ myfree(message->filter_xport);
+ if (message->inspect_xport)
+ myfree(message->inspect_xport);
+ if (message->redirect_addr)
+ myfree(message->redirect_addr);
+ if (message->client_name)
+ myfree(message->client_name);
+ if (message->client_addr)
+ myfree(message->client_addr);
+ if (message->client_port)
+ myfree(message->client_port);
+ if (message->client_proto)
+ myfree(message->client_proto);
+ if (message->client_helo)
+ myfree(message->client_helo);
+ if (message->sasl_method)
+ myfree(message->sasl_method);
+ if (message->sasl_username)
+ myfree(message->sasl_username);
+ if (message->sasl_sender)
+ myfree(message->sasl_sender);
+ if (message->log_ident)
+ myfree(message->log_ident);
+ if (message->rewrite_context)
+ myfree(message->rewrite_context);
+ recipient_list_free(&message->rcpt_list);
+ qmgr_message_count--;
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0)
+ qmgr_vrfy_pend_count--;
+ myfree((void *) message);
+}
+
+/* qmgr_message_alloc - create in-core message structure */
+
+QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
+ int qflags, mode_t mode)
+{
+ const char *myname = "qmgr_message_alloc";
+ QMGR_MESSAGE *message;
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, queue_name, queue_id);
+
+ /*
+ * Create an in-core message structure.
+ */
+ message = qmgr_message_create(queue_name, queue_id, qflags);
+
+ /*
+ * Extract message envelope information: time of arrival, sender address,
+ * recipient addresses. Skip files with malformed envelope information.
+ */
+#define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT)
+
+ if (qmgr_message_open(message) < 0) {
+ qmgr_message_free(message);
+ return (0);
+ }
+ if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) {
+ msg_info("%s: skipped, still being delivered", queue_id);
+ qmgr_message_close(message);
+ qmgr_message_free(message);
+ return (QMGR_MESSAGE_LOCKED);
+ }
+ if (qmgr_message_read(message) < 0) {
+ qmgr_message_close(message);
+ qmgr_message_free(message);
+ return (0);
+ } else {
+
+ /*
+ * We have validated the queue file content, so it is safe to modify
+ * the file properties now.
+ */
+ if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0)
+ msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * If this message is forced to expire, use the existing defer
+ * logfile records and do not assign any deliveries, leaving the
+ * refcount at zero. If this message is forced to expire, but no
+ * defer logfile records are available, assign deliveries to the
+ * retry transport so that the sender will still find out what
+ * recipients are affected and why. Either way, do not assign normal
+ * deliveries because that would be undesirable especially with mail
+ * that was expired in the 'hold' queue.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0
+ && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER,
+ queue_id), &st) == 0 && st.st_size > 0) {
+ /* Use this defer log; don't assign deliveries (refcount == 0). */
+ message->flags = 1; /* simplify downstream code */
+ qmgr_message_close(message);
+ return (message);
+ }
+
+ /*
+ * Reset the defer log. This code should not be here, but we must
+ * reset the defer log *after* acquiring the exclusive lock on the
+ * queue file and *before* resolving new recipients. Since all those
+ * operations are encapsulated so nicely by this routine, the defer
+ * log reset has to be done here as well.
+ *
+ * Note: it is safe to remove the defer logfile from a previous queue
+ * run of this queue file, because the defer log contains information
+ * about recipients that still exist in this queue file.
+ */
+ if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT)
+ msg_fatal("%s: %s: remove %s %s: %m", myname,
+ queue_id, MAIL_QUEUE_DEFER, queue_id);
+ qmgr_message_sort(message);
+ qmgr_message_resolve(message);
+ qmgr_message_sort(message);
+ qmgr_message_assign(message);
+ qmgr_message_close(message);
+ return (message);
+ }
+}
+
+/* qmgr_message_realloc - refresh in-core message structure */
+
+QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_message_realloc";
+
+ /*
+ * Sanity checks.
+ */
+ if (message->rcpt_offset <= 0)
+ msg_panic("%s: invalid offset: %ld", myname, message->rcpt_offset);
+ if (msg_verbose)
+ msg_info("%s: %s %s offset %ld", myname, message->queue_name,
+ message->queue_id, message->rcpt_offset);
+
+ /*
+ * Extract recipient addresses. Skip files with malformed envelope
+ * information.
+ */
+ if (qmgr_message_open(message) < 0)
+ return (0);
+ if (qmgr_message_read(message) < 0) {
+ qmgr_message_close(message);
+ return (0);
+ } else {
+ qmgr_message_sort(message);
+ qmgr_message_resolve(message);
+ qmgr_message_sort(message);
+ qmgr_message_assign(message);
+ qmgr_message_close(message);
+ return (message);
+ }
+}
diff --git a/src/oqmgr/qmgr_move.c b/src/oqmgr/qmgr_move.c
new file mode 100644
index 0000000..e68f803
--- /dev/null
+++ b/src/oqmgr/qmgr_move.c
@@ -0,0 +1,104 @@
+/*++
+/* NAME
+/* qmgr_move 3
+/* SUMMARY
+/* move queue entries to another queue
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_move(from, to, time_stamp)
+/* const char *from;
+/* const char *to;
+/* time_t time_stamp;
+/* DESCRIPTION
+/* The \fBqmgr_move\fR routine scans the \fIfrom\fR queue for entries
+/* with valid queue names and moves them to the \fIto\fR queue.
+/* If \fItime_stamp\fR is non-zero, the queue file time stamps are
+/* set to the specified value.
+/* Entries with invalid names are left alone. No attempt is made to
+/* look for other badness such as multiple links or weird file types.
+/* These issues are dealt with when a queue file is actually opened.
+/* 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>
+#include <string.h>
+#include <utime.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <scan_dir.h>
+#include <recipient_list.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_scan_dir.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_move - move queue entries to another queue, leave bad files alone */
+
+void qmgr_move(const char *src_queue, const char *dst_queue,
+ time_t time_stamp)
+{
+ const char *myname = "qmgr_move";
+ SCAN_DIR *queue_dir;
+ char *queue_id;
+ struct utimbuf tbuf;
+ const char *path;
+
+ if (strcmp(src_queue, dst_queue) == 0)
+ msg_panic("%s: source queue %s is destination", myname, src_queue);
+ if (msg_verbose)
+ msg_info("start move queue %s -> %s", src_queue, dst_queue);
+
+ queue_dir = scan_dir_open(src_queue);
+ while ((queue_id = mail_scan_dir_next(queue_dir)) != 0) {
+ if (mail_queue_id_ok(queue_id)) {
+ if (time_stamp > 0) {
+ tbuf.actime = tbuf.modtime = time_stamp;
+ path = mail_queue_path((VSTRING *) 0, src_queue, queue_id);
+ if (utime(path, &tbuf) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("%s: update %s time stamps: %m", myname, path);
+ msg_warn("%s: update %s time stamps: %m", myname, path);
+ continue;
+ }
+ }
+ if (mail_queue_rename(queue_id, src_queue, dst_queue)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: rename %s from %s to %s: %m",
+ myname, queue_id, src_queue, dst_queue);
+ msg_warn("%s: rename %s from %s to %s: %m",
+ myname, queue_id, src_queue, dst_queue);
+ continue;
+ }
+ if (msg_verbose)
+ msg_info("%s: moved %s from %s to %s",
+ myname, queue_id, src_queue, dst_queue);
+ } else {
+ msg_warn("%s: ignored: queue %s id %s",
+ myname, src_queue, queue_id);
+ }
+ }
+ scan_dir_close(queue_dir);
+
+ if (msg_verbose)
+ msg_info("end move queue %s -> %s", src_queue, dst_queue);
+}
diff --git a/src/oqmgr/qmgr_queue.c b/src/oqmgr/qmgr_queue.c
new file mode 100644
index 0000000..a127c6b
--- /dev/null
+++ b/src/oqmgr/qmgr_queue.c
@@ -0,0 +1,442 @@
+/*++
+/* NAME
+/* qmgr_queue 3
+/* SUMMARY
+/* per-destination queues
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_queue_count;
+/*
+/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop)
+/* QMGR_TRANSPORT *transport;
+/* const char *name;
+/* const char *nexthop;
+/*
+/* void qmgr_queue_done(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_QUEUE *qmgr_queue_find(transport, name)
+/* QMGR_TRANSPORT *transport;
+/* const char *name;
+/*
+/* QMGR_QUEUE *qmgr_queue_select(transport)
+/* QMGR_TRANSPORT *transport;
+/*
+/* void qmgr_queue_throttle(queue, dsn)
+/* QMGR_QUEUE *queue;
+/* DSN *dsn;
+/*
+/* void qmgr_queue_unthrottle(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* void qmgr_queue_suspend(queue, delay)
+/* QMGR_QUEUE *queue;
+/* int delay;
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-destination queues.
+/* Each queue corresponds to a specific transport and destination.
+/* Each queue has a `todo' list of delivery requests for that
+/* destination, and a `busy' list of delivery requests in progress.
+/*
+/* qmgr_queue_count is a global counter for the total number
+/* of in-core queue structures.
+/*
+/* qmgr_queue_create() creates an empty named queue for the named
+/* transport and destination. The queue is given an initial
+/* concurrency limit as specified with the
+/* \fIinitial_destination_concurrency\fR configuration parameter,
+/* provided that it does not exceed the transport-specific
+/* concurrency limit.
+/*
+/* qmgr_queue_done() disposes of a per-destination queue after all
+/* its entries have been taken care of. It is an error to dispose
+/* of a dead queue.
+/*
+/* qmgr_queue_find() looks up the named queue for the named
+/* transport. A null result means that the queue was not found.
+/*
+/* qmgr_queue_select() uses a round-robin strategy to select
+/* from the named transport one per-destination queue with a
+/* non-empty `todo' list.
+/*
+/* qmgr_queue_throttle() handles a delivery error, and decrements the
+/* concurrency limit for the destination, with a lower bound of 1.
+/* When the cohort failure bound is reached, qmgr_queue_throttle()
+/* sets the concurrency limit to zero and starts a timer
+/* to re-enable delivery to the destination after a configurable delay.
+/*
+/* qmgr_queue_unthrottle() undoes qmgr_queue_throttle()'s effects.
+/* The concurrency limit for the destination is incremented,
+/* provided that it does not exceed the destination concurrency
+/* limit specified for the transport. This routine implements
+/* "slow open" mode, and eliminates the "thundering herd" problem.
+/*
+/* qmgr_queue_suspend() suspends delivery for this destination
+/* briefly.
+/* DIAGNOSTICS
+/* Panic: consistency check failure.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <recipient_list.h>
+#include <mail_proto.h> /* QMGR_LOG_WINDOW */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+int qmgr_queue_count;
+
+#define QMGR_ERROR_OR_RETRY_QUEUE(queue) \
+ (strcmp(queue->transport->name, MAIL_SERVICE_RETRY) == 0 \
+ || strcmp(queue->transport->name, MAIL_SERVICE_ERROR) == 0)
+
+#define QMGR_LOG_FEEDBACK(feedback) \
+ if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \
+ msg_info("%s: feedback %g", myname, feedback);
+
+#define QMGR_LOG_WINDOW(queue) \
+ if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \
+ msg_info("%s: queue %s: limit %d window %d success %g failure %g fail_cohorts %g", \
+ myname, queue->name, queue->transport->dest_concurrency_limit, \
+ queue->window, queue->success, queue->failure, queue->fail_cohorts);
+
+/* qmgr_queue_resume - resume delivery to destination */
+
+static void qmgr_queue_resume(int event, void *context)
+{
+ QMGR_QUEUE *queue = (QMGR_QUEUE *) context;
+ const char *myname = "qmgr_queue_resume";
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_SUSPENDED(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+
+ /*
+ * We can't simply force delivery on this queue: the transport's pending
+ * count may already be maxed out, and there may be other constraints
+ * that definitely should be none of our business. The best we can do is
+ * to play by the same rules as everyone else: let qmgr_active_drain()
+ * and round-robin selection take care of message selection.
+ */
+ queue->window = 1;
+
+ /*
+ * Every event handler that leaves a queue in the "ready" state should
+ * remove the queue when it is empty.
+ */
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+}
+
+/* qmgr_queue_suspend - briefly suspend a destination */
+
+void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay)
+{
+ const char *myname = "qmgr_queue_suspend";
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->busy_refcount > 0)
+ msg_panic("%s: queue is busy", myname);
+
+ /*
+ * Set the queue status to "suspended". No-one is supposed to remove a
+ * queue in suspended state.
+ */
+ queue->window = QMGR_QUEUE_STAT_SUSPENDED;
+ event_request_timer(qmgr_queue_resume, (void *) queue, delay);
+}
+
+/* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */
+
+static void qmgr_queue_unthrottle_wrapper(int unused_event, void *context)
+{
+ QMGR_QUEUE *queue = (QMGR_QUEUE *) context;
+
+ /*
+ * This routine runs when a wakeup timer goes off; it does not run in the
+ * context of some queue manipulation. Therefore, it is safe to discard
+ * this in-core queue when it is empty and when this site is not dead.
+ */
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+}
+
+/* qmgr_queue_unthrottle - give this destination another chance */
+
+void qmgr_queue_unthrottle(QMGR_QUEUE *queue)
+{
+ const char *myname = "qmgr_queue_unthrottle";
+ QMGR_TRANSPORT *transport = queue->transport;
+ double feedback;
+
+ if (msg_verbose)
+ msg_info("%s: queue %s", myname, queue->name);
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_THROTTLED(queue) && !QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+
+ /*
+ * Don't restart the negative feedback hysteresis cycle with every
+ * positive feedback. Restart it only when we make a positive concurrency
+ * adjustment (i.e. at the end of a positive feedback hysteresis cycle).
+ * Otherwise negative feedback would be too aggressive: negative feedback
+ * takes effect immediately at the start of its hysteresis cycle.
+ */
+ queue->fail_cohorts = 0;
+
+ /*
+ * Special case when this site was dead.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ event_cancel_timer(qmgr_queue_unthrottle_wrapper, (void *) queue);
+ if (queue->dsn == 0)
+ msg_panic("%s: queue %s: window 0 status 0", myname, queue->name);
+ dsn_free(queue->dsn);
+ queue->dsn = 0;
+ /* Back from the almost grave, best concurrency is anyone's guess. */
+ if (queue->busy_refcount > 0)
+ queue->window = queue->busy_refcount;
+ else
+ queue->window = transport->init_dest_concurrency;
+ queue->success = queue->failure = 0;
+ QMGR_LOG_WINDOW(queue);
+ return;
+ }
+
+ /*
+ * Increase the destination's concurrency limit until we reach the
+ * transport's concurrency limit. Allow for a margin the size of the
+ * initial destination concurrency, so that we're not too gentle.
+ *
+ * Why is the concurrency increment based on preferred concurrency and not
+ * on the number of outstanding delivery requests? The latter fluctuates
+ * wildly when deliveries complete in bursts (artificial benchmark
+ * measurements), and does not account for cached connections.
+ *
+ * Keep the window within reasonable distance from actual concurrency
+ * otherwise negative feedback will be ineffective. This expression
+ * assumes that busy_refcount changes gradually. This is invalid when
+ * deliveries complete in bursts (artificial benchmark measurements).
+ */
+ if (transport->dest_concurrency_limit == 0
+ || transport->dest_concurrency_limit > queue->window)
+ if (queue->window < queue->busy_refcount + transport->init_dest_concurrency) {
+ feedback = QMGR_FEEDBACK_VAL(transport->pos_feedback, queue->window);
+ QMGR_LOG_FEEDBACK(feedback);
+ queue->success += feedback;
+ /* Prepare for overshoot (feedback > hysteresis, rounding error). */
+ while (queue->success + feedback / 2 >= transport->pos_feedback.hysteresis) {
+ queue->window += transport->pos_feedback.hysteresis;
+ queue->success -= transport->pos_feedback.hysteresis;
+ queue->failure = 0;
+ }
+ /* Prepare for overshoot. */
+ if (transport->dest_concurrency_limit > 0
+ && queue->window > transport->dest_concurrency_limit)
+ queue->window = transport->dest_concurrency_limit;
+ }
+ QMGR_LOG_WINDOW(queue);
+}
+
+/* qmgr_queue_throttle - handle destination delivery failure */
+
+void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn)
+{
+ const char *myname = "qmgr_queue_throttle";
+ QMGR_TRANSPORT *transport = queue->transport;
+ double feedback;
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->dsn)
+ msg_panic("%s: queue %s: spurious reason %s",
+ myname, queue->name, queue->dsn->reason);
+ if (msg_verbose)
+ msg_info("%s: queue %s: %s %s",
+ myname, queue->name, dsn->status, dsn->reason);
+
+ /*
+ * Don't restart the positive feedback hysteresis cycle with every
+ * negative feedback. Restart it only when we make a negative concurrency
+ * adjustment (i.e. at the start of a negative feedback hysteresis
+ * cycle). Otherwise positive feedback would be too weak (positive
+ * feedback does not take effect until the end of its hysteresis cycle).
+ */
+
+ /*
+ * This queue is declared dead after a configurable number of
+ * pseudo-cohort failures.
+ */
+ if (QMGR_QUEUE_READY(queue)) {
+ queue->fail_cohorts += 1.0 / queue->window;
+ if (transport->fail_cohort_limit > 0
+ && queue->fail_cohorts >= transport->fail_cohort_limit)
+ queue->window = QMGR_QUEUE_STAT_THROTTLED;
+ }
+
+ /*
+ * Decrease the destination's concurrency limit until we reach 1. Base
+ * adjustments on the concurrency limit itself, instead of using the
+ * actual concurrency. The latter fluctuates wildly when deliveries
+ * complete in bursts (artificial benchmark measurements).
+ *
+ * Even after reaching 1, we maintain the negative hysteresis cycle so that
+ * negative feedback can cancel out positive feedback.
+ */
+ if (QMGR_QUEUE_READY(queue)) {
+ feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window);
+ QMGR_LOG_FEEDBACK(feedback);
+ queue->failure -= feedback;
+ /* Prepare for overshoot (feedback > hysteresis, rounding error). */
+ while (queue->failure - feedback / 2 < 0) {
+ queue->window -= transport->neg_feedback.hysteresis;
+ queue->success = 0;
+ queue->failure += transport->neg_feedback.hysteresis;
+ }
+ /* Prepare for overshoot. */
+ if (queue->window < 1)
+ queue->window = 1;
+ }
+
+ /*
+ * Special case for a site that just was declared dead.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ queue->dsn = DSN_COPY(dsn);
+ event_request_timer(qmgr_queue_unthrottle_wrapper,
+ (void *) queue, var_min_backoff_time);
+ queue->dflags = 0;
+ }
+ QMGR_LOG_WINDOW(queue);
+}
+
+/* qmgr_queue_select - select in-core queue for delivery */
+
+QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *transport)
+{
+ QMGR_QUEUE *queue;
+
+ /*
+ * If we find a suitable site, rotate the list to enforce round-robin
+ * selection. See similar selection code in qmgr_transport_select().
+ */
+ for (queue = transport->queue_list.next; queue; queue = queue->peers.next) {
+ if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
+ QMGR_LIST_ROTATE(transport->queue_list, queue);
+ if (msg_verbose)
+ msg_info("qmgr_queue_select: %s", queue->name);
+ return (queue);
+ }
+ }
+ return (0);
+}
+
+/* qmgr_queue_done - delete in-core queue for site */
+
+void qmgr_queue_done(QMGR_QUEUE *queue)
+{
+ const char *myname = "qmgr_queue_done";
+ QMGR_TRANSPORT *transport = queue->transport;
+
+ /*
+ * Sanity checks. It is an error to delete an in-core queue with pending
+ * messages or timers.
+ */
+ if (queue->busy_refcount != 0 || queue->todo_refcount != 0)
+ msg_panic("%s: refcount: %d", myname,
+ queue->busy_refcount + queue->todo_refcount);
+ if (queue->todo.next || queue->busy.next)
+ msg_panic("%s: queue not empty: %s", myname, queue->name);
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->dsn)
+ msg_panic("%s: queue %s: spurious reason %s",
+ myname, queue->name, queue->dsn->reason);
+
+ /*
+ * Clean up this in-core queue.
+ */
+ QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue);
+ htable_delete(transport->queue_byname, queue->name, (void (*) (void *)) 0);
+ myfree(queue->name);
+ myfree(queue->nexthop);
+ qmgr_queue_count--;
+ myfree((void *) queue);
+}
+
+/* qmgr_queue_create - create in-core queue for site */
+
+QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name,
+ const char *nexthop)
+{
+ QMGR_QUEUE *queue;
+
+ /*
+ * If possible, choose an initial concurrency of > 1 so that one bad
+ * message or one bad network won't slow us down unnecessarily.
+ */
+
+ queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE));
+ qmgr_queue_count++;
+ queue->dflags = 0;
+ queue->last_done = 0;
+ queue->name = mystrdup(name);
+ queue->nexthop = mystrdup(nexthop);
+ queue->todo_refcount = 0;
+ queue->busy_refcount = 0;
+ queue->transport = transport;
+ queue->window = transport->init_dest_concurrency;
+ queue->success = queue->failure = queue->fail_cohorts = 0;
+ QMGR_LIST_INIT(queue->todo);
+ QMGR_LIST_INIT(queue->busy);
+ queue->dsn = 0;
+ queue->clog_time_to_warn = 0;
+ QMGR_LIST_PREPEND(transport->queue_list, queue);
+ htable_enter(transport->queue_byname, name, (void *) queue);
+ return (queue);
+}
+
+/* qmgr_queue_find - find in-core named queue */
+
+QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name)
+{
+ return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name));
+}
diff --git a/src/oqmgr/qmgr_scan.c b/src/oqmgr/qmgr_scan.c
new file mode 100644
index 0000000..0665a23
--- /dev/null
+++ b/src/oqmgr/qmgr_scan.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* qmgr_scan 3
+/* SUMMARY
+/* queue scanning
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_SCAN *qmgr_scan_create(queue_name)
+/* const char *queue_name;
+/*
+/* char *qmgr_scan_next(scan_info)
+/* QMGR_SCAN *scan_info;
+/*
+/* void qmgr_scan_request(scan_info, flags)
+/* QMGR_SCAN *scan_info;
+/* int flags;
+/* DESCRIPTION
+/* This module implements queue scans. A queue scan always runs
+/* to completion, so that all files get a fair chance. The caller
+/* can request that a queue scan be restarted once it completes.
+/*
+/* qmgr_scan_create() creates a context for scanning the named queue,
+/* but does not start a queue scan.
+/*
+/* qmgr_scan_next() returns the base name of the next queue file.
+/* A null pointer means that no file was found. qmgr_scan_next()
+/* automagically restarts a queue scan when a scan request had
+/* arrived while the scan was in progress.
+/*
+/* qmgr_scan_request() records a request for the next queue scan. The
+/* flags argument is the bit-wise OR of zero or more of the following,
+/* unrecognized flags being ignored:
+/* .IP QMGR_FLUSH_ONCE
+/* Forget state information about dead hosts or transports.
+/* This request takes effect immediately.
+/* .IP QMGR_FLUSH_DFXP
+/* Override the defer_transports setting. This takes effect
+/* immediately when a queue scan is in progress, and affects
+/* the next queue scan.
+/* .IP QMGR_SCAN_ALL
+/* Ignore queue file time stamps. This takes effect immediately
+/* when a queue scan is in progress, and affects the next queue
+/* scan.
+/* .IP QMGR_SCAN_START
+/* Start a queue scan when none is in progress, or restart the
+/* current scan upon completion.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* Panic: interface violations, internal consistency errors.
+/* 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 <mymalloc.h>
+#include <scan_dir.h>
+
+/* Global library. */
+
+#include <mail_scan_dir.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_scan_start - start queue scan */
+
+static void qmgr_scan_start(QMGR_SCAN *scan_info)
+{
+ const char *myname = "qmgr_scan_start";
+
+ /*
+ * Sanity check.
+ */
+ if (scan_info->handle)
+ msg_panic("%s: %s queue scan in progress",
+ myname, scan_info->queue);
+
+ /*
+ * Give the poor tester a clue.
+ */
+ if (msg_verbose)
+ msg_info("%s: %sstart %s queue scan",
+ myname,
+ scan_info->nflags & QMGR_SCAN_START ? "re" : "",
+ scan_info->queue);
+
+ /*
+ * Start or restart the scan.
+ */
+ scan_info->flags = scan_info->nflags;
+ scan_info->nflags = 0;
+ scan_info->handle = scan_dir_open(scan_info->queue);
+}
+
+/* qmgr_scan_request - request for future scan */
+
+void qmgr_scan_request(QMGR_SCAN *scan_info, int flags)
+{
+
+ /*
+ * Apply "forget all dead destinations" requests immediately. Throttle
+ * dead transports and queues at the earliest opportunity: preferably
+ * during an already ongoing queue scan, otherwise the throttling will
+ * have to wait until a "start scan" trigger arrives.
+ *
+ * The QMGR_FLUSH_ONCE request always comes with QMGR_FLUSH_DFXP, and
+ * sometimes it also comes with QMGR_SCAN_ALL. It becomes a completely
+ * different story when a flush request is encoded in file permissions.
+ */
+ if (flags & QMGR_FLUSH_ONCE)
+ qmgr_enable_all();
+
+ /*
+ * Apply "ignore time stamp" requests also towards the scan that is
+ * already in progress.
+ */
+ if (scan_info->handle != 0 && (flags & QMGR_SCAN_ALL))
+ scan_info->flags |= QMGR_SCAN_ALL;
+
+ /*
+ * Apply "override defer_transports" requests also towards the scan that
+ * is already in progress.
+ */
+ if (scan_info->handle != 0 && (flags & QMGR_FLUSH_DFXP))
+ scan_info->flags |= QMGR_FLUSH_DFXP;
+
+ /*
+ * If a scan is in progress, just record the request.
+ */
+ scan_info->nflags |= flags;
+ if (scan_info->handle == 0 && (flags & QMGR_SCAN_START) != 0) {
+ scan_info->nflags &= ~QMGR_SCAN_START;
+ qmgr_scan_start(scan_info);
+ }
+}
+
+/* qmgr_scan_next - look for next queue file */
+
+char *qmgr_scan_next(QMGR_SCAN *scan_info)
+{
+ char *path = 0;
+
+ /*
+ * Restart the scan if we reach the end and a queue scan request has
+ * arrived in the mean time.
+ */
+ if (scan_info->handle && (path = mail_scan_dir_next(scan_info->handle)) == 0) {
+ scan_info->handle = scan_dir_close(scan_info->handle);
+ if (msg_verbose && (scan_info->nflags & QMGR_SCAN_START) == 0)
+ msg_info("done %s queue scan", scan_info->queue);
+ }
+ if (!scan_info->handle && (scan_info->nflags & QMGR_SCAN_START)) {
+ qmgr_scan_start(scan_info);
+ path = mail_scan_dir_next(scan_info->handle);
+ }
+ return (path);
+}
+
+/* qmgr_scan_create - create queue scan context */
+
+QMGR_SCAN *qmgr_scan_create(const char *queue)
+{
+ QMGR_SCAN *scan_info;
+
+ scan_info = (QMGR_SCAN *) mymalloc(sizeof(*scan_info));
+ scan_info->queue = mystrdup(queue);
+ scan_info->flags = scan_info->nflags = 0;
+ scan_info->handle = 0;
+ return (scan_info);
+}
diff --git a/src/oqmgr/qmgr_transport.c b/src/oqmgr/qmgr_transport.c
new file mode 100644
index 0000000..ed780db
--- /dev/null
+++ b/src/oqmgr/qmgr_transport.c
@@ -0,0 +1,472 @@
+/*++
+/* NAME
+/* qmgr_transport 3
+/* SUMMARY
+/* per-transport data structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_TRANSPORT *qmgr_transport_create(name)
+/* const char *name;
+/*
+/* QMGR_TRANSPORT *qmgr_transport_find(name)
+/* const char *name;
+/*
+/* QMGR_TRANSPORT *qmgr_transport_select()
+/*
+/* void qmgr_transport_alloc(transport, notify)
+/* QMGR_TRANSPORT *transport;
+/* void (*notify)(QMGR_TRANSPORT *transport, VSTREAM *fp);
+/*
+/* void qmgr_transport_throttle(transport, dsn)
+/* QMGR_TRANSPORT *transport;
+/* DSN *dsn;
+/*
+/* void qmgr_transport_unthrottle(transport)
+/* QMGR_TRANSPORT *transport;
+/* DESCRIPTION
+/* This module organizes the world by message transport type.
+/* Each transport can have zero or more destination queues
+/* associated with it.
+/*
+/* qmgr_transport_create() instantiates a data structure for the
+/* named transport type.
+/*
+/* qmgr_transport_find() looks up an existing message transport
+/* data structure.
+/*
+/* qmgr_transport_select() attempts to find a transport that
+/* has messages pending delivery. This routine implements
+/* round-robin search among transports.
+/*
+/* qmgr_transport_alloc() allocates a delivery process for the
+/* specified transport type. Allocation is performed asynchronously.
+/* When a process becomes available, the application callback routine
+/* is invoked with as arguments the transport and a stream that
+/* is connected to a delivery process. It is an error to call
+/* qmgr_transport_alloc() while delivery process allocation for
+/* the same transport is in progress.
+/*
+/* qmgr_transport_throttle blocks further allocation of delivery
+/* processes for the named transport. Attempts to throttle a
+/* throttled transport are ignored.
+/*
+/* qmgr_transport_unthrottle() undoes qmgr_transport_throttle().
+/* Attempts to unthrottle a non-throttled transport are ignored.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <unistd.h>
+
+#include <sys/time.h> /* FD_SETSIZE */
+#include <sys/types.h> /* FD_SETSIZE */
+#include <unistd.h> /* FD_SETSIZE */
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h> /* FD_SETSIZE */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <recipient_list.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+HTABLE *qmgr_transport_byname; /* transport by name */
+QMGR_TRANSPORT_LIST qmgr_transport_list;/* transports, round robin */
+
+ /*
+ * A local structure to remember a delivery process allocation request.
+ */
+typedef struct QMGR_TRANSPORT_ALLOC QMGR_TRANSPORT_ALLOC;
+
+struct QMGR_TRANSPORT_ALLOC {
+ QMGR_TRANSPORT *transport; /* transport context */
+ VSTREAM *stream; /* delivery service stream */
+ QMGR_TRANSPORT_ALLOC_NOTIFY notify; /* application call-back routine */
+};
+
+ /*
+ * Connections to delivery agents are managed asynchronously. Each delivery
+ * agent connection goes through multiple wait states:
+ *
+ * - With Linux/Solaris and old queue manager implementations only, wait for
+ * the server to invoke accept().
+ *
+ * - Wait for the delivery agent's announcement that it is ready to receive a
+ * delivery request.
+ *
+ * - Wait for the delivery request completion status.
+ *
+ * Older queue manager implementations had only one pending delivery agent
+ * connection per transport. With low-latency destinations, the output rates
+ * were reduced on Linux/Solaris systems that had the extra wait state.
+ *
+ * To maximize delivery agent output rates with low-latency destinations, the
+ * following changes were made to the queue manager by the end of the 2.4
+ * development cycle:
+ *
+ * - The Linux/Solaris accept() wait state was eliminated.
+ *
+ * - A pipeline was implemented for pending delivery agent connections. The
+ * number of pending delivery agent connections was increased from one to
+ * two: the number of before-delivery wait states, plus one extra pipeline
+ * slot to prevent the pipeline from stalling easily. Increasing the
+ * pipeline much further actually hurt performance.
+ *
+ * - To reduce queue manager disk competition with delivery agents, the queue
+ * scanning algorithm was modified to import only one message per interrupt.
+ * The incoming and deferred queue scans now happen on alternate interrupts.
+ *
+ * Simplistically reasoned, a non-zero (incoming + active) queue length is
+ * equivalent to a time shift for mail deliveries; this is undesirable when
+ * delivery agents are not fully utilized.
+ *
+ * On the other hand a non-empty active queue is what allows us to do clever
+ * things such as queue file prefetch, concurrency windows, and connection
+ * caching; the idea is that such "thinking time" is affordable only after
+ * the output channels are maxed out.
+ */
+#ifndef QMGR_TRANSPORT_MAX_PEND
+#define QMGR_TRANSPORT_MAX_PEND 2
+#endif
+
+ /*
+ * Important note on the _transport_rate_delay implementation: after
+ * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+ * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+ * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+ * _transport_rate_delay will become stuck.
+ */
+
+/* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */
+
+static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context)
+{
+ qmgr_transport_unthrottle((QMGR_TRANSPORT *) context);
+}
+
+/* qmgr_transport_unthrottle - open the throttle */
+
+void qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
+{
+ const char *myname = "qmgr_transport_unthrottle";
+
+ /*
+ * This routine runs after expiration of the timer set by
+ * qmgr_transport_throttle(), or whenever a delivery transport has been
+ * used without malfunction. In either case, we enable delivery again if
+ * the transport was throttled. We always reset the transport rate lock.
+ */
+ if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) {
+ if (msg_verbose)
+ msg_info("%s: transport %s", myname, transport->name);
+ transport->flags &= ~QMGR_TRANSPORT_STAT_DEAD;
+ if (transport->dsn == 0)
+ msg_panic("%s: transport %s: null reason",
+ myname, transport->name);
+ dsn_free(transport->dsn);
+ transport->dsn = 0;
+ event_cancel_timer(qmgr_transport_unthrottle_wrapper,
+ (void *) transport);
+ }
+ if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+ transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK;
+}
+
+/* qmgr_transport_throttle - disable delivery process allocation */
+
+void qmgr_transport_throttle(QMGR_TRANSPORT *transport, DSN *dsn)
+{
+ const char *myname = "qmgr_transport_throttle";
+
+ /*
+ * We are unable to connect to a deliver process for this type of message
+ * transport. Instead of hosing the system by retrying in a tight loop,
+ * back off and disable this transport type for a while.
+ */
+ if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) == 0) {
+ if (msg_verbose)
+ msg_info("%s: transport %s: status: %s reason: %s",
+ myname, transport->name, dsn->status, dsn->reason);
+ transport->flags |= QMGR_TRANSPORT_STAT_DEAD;
+ if (transport->dsn)
+ msg_panic("%s: transport %s: spurious reason: %s",
+ myname, transport->name, transport->dsn->reason);
+ transport->dsn = DSN_COPY(dsn);
+ event_request_timer(qmgr_transport_unthrottle_wrapper,
+ (void *) transport, var_transport_retry_time);
+ }
+}
+
+/* qmgr_transport_abort - transport connect watchdog */
+
+static void qmgr_transport_abort(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ msg_fatal("timeout connecting to transport: %s", alloc->transport->name);
+}
+
+/* qmgr_transport_rate_event - delivery process availability notice */
+
+static void qmgr_transport_rate_event(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ alloc->notify(alloc->transport, alloc->stream);
+ myfree((void *) alloc);
+}
+
+/* qmgr_transport_event - delivery process availability notice */
+
+static void qmgr_transport_event(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ /*
+ * This routine notifies the application when the request given to
+ * qmgr_transport_alloc() completes.
+ */
+ if (msg_verbose)
+ msg_info("transport_event: %s", alloc->transport->name);
+
+ /*
+ * Connection request completed. Stop the watchdog timer.
+ */
+ event_cancel_timer(qmgr_transport_abort, context);
+
+ /*
+ * Disable further read events that end up calling this function, and
+ * free up this pending connection pipeline slot.
+ */
+ if (alloc->stream) {
+ event_disable_readwrite(vstream_fileno(alloc->stream));
+ non_blocking(vstream_fileno(alloc->stream), BLOCKING);
+ }
+ alloc->transport->pending -= 1;
+
+ /*
+ * Notify the requestor.
+ */
+ if (alloc->transport->xport_rate_delay > 0) {
+ if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0)
+ msg_panic("transport_event: missing rate lock for transport %s",
+ alloc->transport->name);
+ event_request_timer(qmgr_transport_rate_event, (void *) alloc,
+ alloc->transport->xport_rate_delay);
+ } else {
+ alloc->notify(alloc->transport, alloc->stream);
+ myfree((void *) alloc);
+ }
+}
+
+/* qmgr_transport_select - select transport for allocation */
+
+QMGR_TRANSPORT *qmgr_transport_select(void)
+{
+ QMGR_TRANSPORT *xport;
+ QMGR_QUEUE *queue;
+ int need;
+
+ /*
+ * If we find a suitable transport, rotate the list of transports to
+ * effectuate round-robin selection. See similar selection code in
+ * qmgr_queue_select().
+ *
+ * This function is called repeatedly until all transports have maxed out
+ * the number of pending delivery agent connections, until all delivery
+ * agent concurrency windows are maxed out, or until we run out of "todo"
+ * queue entries.
+ */
+#define MIN5af51743e4eef(x, y) ((x) < (y) ? (x) : (y))
+
+ for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) {
+ if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0
+ || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0
+ || xport->pending >= QMGR_TRANSPORT_MAX_PEND)
+ continue;
+ need = xport->pending + 1;
+ for (queue = xport->queue_list.next; queue; queue = queue->peers.next) {
+ if (QMGR_QUEUE_READY(queue) == 0)
+ continue;
+ if ((need -= MIN5af51743e4eef(queue->window - queue->busy_refcount,
+ queue->todo_refcount)) <= 0) {
+ QMGR_LIST_ROTATE(qmgr_transport_list, xport);
+ if (msg_verbose)
+ msg_info("qmgr_transport_select: %s", xport->name);
+ return (xport);
+ }
+ }
+ }
+ return (0);
+}
+
+/* qmgr_transport_alloc - allocate delivery process */
+
+void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify)
+{
+ QMGR_TRANSPORT_ALLOC *alloc;
+
+ /*
+ * Sanity checks.
+ */
+ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD)
+ msg_panic("qmgr_transport: dead transport: %s", transport->name);
+ if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+ msg_panic("qmgr_transport: rate-locked transport: %s", transport->name);
+ if (transport->pending >= QMGR_TRANSPORT_MAX_PEND)
+ msg_panic("qmgr_transport: excess allocation: %s", transport->name);
+
+ /*
+ * When this message delivery transport is rate-limited, do not select it
+ * again before the end of a message delivery transaction.
+ */
+ if (transport->xport_rate_delay > 0)
+ transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK;
+
+ /*
+ * Connect to the well-known port for this delivery service, and wake up
+ * when a process announces its availability. Allow only a limited number
+ * of delivery process allocation attempts for this transport. In case of
+ * problems, back off. Do not hose the system when it is in trouble
+ * already.
+ *
+ * Use non-blocking connect(), so that Linux won't block the queue manager
+ * until the delivery agent calls accept().
+ *
+ * When the connection to delivery agent cannot be completed, notify the
+ * event handler so that it can throttle the transport and defer the todo
+ * queues, just like it does when communication fails *after* connection
+ * completion.
+ *
+ * Before Postfix 2.4, the event handler was not invoked after connect()
+ * error, and mail was not deferred. Because of this, mail would be stuck
+ * in the active queue after triggering a "connection refused" condition.
+ */
+ alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc));
+ alloc->transport = transport;
+ alloc->notify = notify;
+ transport->pending += 1;
+ if ((alloc->stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name,
+ NON_BLOCKING)) == 0) {
+ msg_warn("connect to transport %s/%s: %m",
+ MAIL_CLASS_PRIVATE, transport->name);
+ event_request_timer(qmgr_transport_event, (void *) alloc, 0);
+ return;
+ }
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) && defined(CA_VSTREAM_CTL_DUPFD)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ vstream_control(alloc->stream,
+ CA_VSTREAM_CTL_DUPFD(THRESHOLD_FD_WORKAROUND),
+ CA_VSTREAM_CTL_END);
+#endif
+ event_enable_read(vstream_fileno(alloc->stream), qmgr_transport_event,
+ (void *) alloc);
+
+ /*
+ * Guard against broken systems.
+ */
+ event_request_timer(qmgr_transport_abort, (void *) alloc,
+ var_daemon_timeout);
+}
+
+/* qmgr_transport_create - create transport instance */
+
+QMGR_TRANSPORT *qmgr_transport_create(const char *name)
+{
+ QMGR_TRANSPORT *transport;
+
+ if (htable_find(qmgr_transport_byname, name) != 0)
+ msg_panic("qmgr_transport_create: transport exists: %s", name);
+ transport = (QMGR_TRANSPORT *) mymalloc(sizeof(QMGR_TRANSPORT));
+ transport->flags = 0;
+ transport->pending = 0;
+ transport->name = mystrdup(name);
+
+ /*
+ * Use global configuration settings or transport-specific settings.
+ */
+ transport->dest_concurrency_limit =
+ get_mail_conf_int2(name, _DEST_CON_LIMIT,
+ var_dest_con_limit, 0, 0);
+ transport->recipient_limit =
+ get_mail_conf_int2(name, _DEST_RCPT_LIMIT,
+ var_dest_rcpt_limit, 0, 0);
+ transport->init_dest_concurrency =
+ get_mail_conf_int2(name, _INIT_DEST_CON,
+ var_init_dest_concurrency, 1, 0);
+ transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY,
+ var_xport_rate_delay,
+ 's', 0, 0);
+ transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY,
+ var_dest_rate_delay,
+ 's', 0, 0);
+
+ if (transport->rate_delay > 0)
+ transport->dest_concurrency_limit = 1;
+ if (transport->dest_concurrency_limit != 0
+ && transport->dest_concurrency_limit < transport->init_dest_concurrency)
+ transport->init_dest_concurrency = transport->dest_concurrency_limit;
+
+ transport->queue_byname = htable_create(0);
+ QMGR_LIST_INIT(transport->queue_list);
+ transport->dsn = 0;
+ qmgr_feedback_init(&transport->pos_feedback, name, _CONC_POS_FDBACK,
+ VAR_CONC_POS_FDBACK, var_conc_pos_feedback);
+ qmgr_feedback_init(&transport->neg_feedback, name, _CONC_NEG_FDBACK,
+ VAR_CONC_NEG_FDBACK, var_conc_neg_feedback);
+ transport->fail_cohort_limit =
+ get_mail_conf_int2(name, _CONC_COHORT_LIM,
+ var_conc_cohort_limit, 0, 0);
+ if (qmgr_transport_byname == 0)
+ qmgr_transport_byname = htable_create(10);
+ htable_enter(qmgr_transport_byname, name, (void *) transport);
+ QMGR_LIST_APPEND(qmgr_transport_list, transport);
+ if (msg_verbose)
+ msg_info("qmgr_transport_create: %s concurrency %d recipients %d",
+ transport->name, transport->dest_concurrency_limit,
+ transport->recipient_limit);
+ return (transport);
+}
+
+/* qmgr_transport_find - find transport instance */
+
+QMGR_TRANSPORT *qmgr_transport_find(const char *name)
+{
+ return ((QMGR_TRANSPORT *) htable_find(qmgr_transport_byname, name));
+}
diff --git a/src/pickup/.indent.pro b/src/pickup/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/pickup/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/pickup/.printfck b/src/pickup/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/pickup/.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/pickup/Makefile.in b/src/pickup/Makefile.in
new file mode 100644
index 0000000..9c8766f
--- /dev/null
+++ b/src/pickup/Makefile.in
@@ -0,0 +1,94 @@
+SHELL = /bin/sh
+SRCS = pickup.c
+OBJS = pickup.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = pickup
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+pickup.o: ../../include/attr.h
+pickup.o: ../../include/check_arg.h
+pickup.o: ../../include/cleanup_user.h
+pickup.o: ../../include/htable.h
+pickup.o: ../../include/info_log_addr_form.h
+pickup.o: ../../include/input_transp.h
+pickup.o: ../../include/iostuff.h
+pickup.o: ../../include/lex_822.h
+pickup.o: ../../include/mail_conf.h
+pickup.o: ../../include/mail_date.h
+pickup.o: ../../include/mail_open_ok.h
+pickup.o: ../../include/mail_params.h
+pickup.o: ../../include/mail_proto.h
+pickup.o: ../../include/mail_queue.h
+pickup.o: ../../include/mail_server.h
+pickup.o: ../../include/mail_version.h
+pickup.o: ../../include/msg.h
+pickup.o: ../../include/mymalloc.h
+pickup.o: ../../include/nvtable.h
+pickup.o: ../../include/rec_attr_map.h
+pickup.o: ../../include/rec_type.h
+pickup.o: ../../include/record.h
+pickup.o: ../../include/safe_open.h
+pickup.o: ../../include/scan_dir.h
+pickup.o: ../../include/set_ugid.h
+pickup.o: ../../include/smtputf8.h
+pickup.o: ../../include/stringops.h
+pickup.o: ../../include/sys_defs.h
+pickup.o: ../../include/vbuf.h
+pickup.o: ../../include/vstream.h
+pickup.o: ../../include/vstring.h
+pickup.o: ../../include/watchdog.h
+pickup.o: pickup.c
diff --git a/src/pickup/pickup.c b/src/pickup/pickup.c
new file mode 100644
index 0000000..4a77a47
--- /dev/null
+++ b/src/pickup/pickup.c
@@ -0,0 +1,631 @@
+/*++
+/* NAME
+/* pickup 8
+/* SUMMARY
+/* Postfix local mail pickup
+/* SYNOPSIS
+/* \fBpickup\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBpickup\fR(8) daemon waits for hints that new mail has been
+/* dropped into the \fBmaildrop\fR directory, and feeds it into the
+/* \fBcleanup\fR(8) daemon.
+/* Ill-formatted files are deleted without notifying the originator.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/* STANDARDS
+/* .ad
+/* .fi
+/* None. The \fBpickup\fR(8) daemon does not interact with
+/* the outside world.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBpickup\fR(8) daemon is moderately security sensitive. It runs
+/* with fixed low privilege and can run in a chrooted environment.
+/* However, the program reads files from potentially hostile users.
+/* The \fBpickup\fR(8) daemon opens no files for writing, is careful about
+/* what files it opens for reading, and does not actually touch any data
+/* that is sent to its public service endpoint.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The \fBpickup\fR(8) daemon copies mail from file to the \fBcleanup\fR(8)
+/* daemon. It could avoid message copying overhead by sending a file
+/* descriptor instead of file data, but then the already complex
+/* \fBcleanup\fR(8) daemon would have to deal with unfiltered user data.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* As the \fBpickup\fR(8) daemon is a relatively long-running process, up
+/* to an hour may pass before a \fBmain.cf\fR change takes effect.
+/* Use the command "\fBpostfix reload\fR" command to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBcontent_filter (empty)\fR"
+/* After the message is queued, send the entire message to the
+/* specified \fItransport:destination\fR.
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* 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 "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .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 "\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 "\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 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.).
+/* SEE ALSO
+/* cleanup(8), message canonicalization
+/* sendmail(1), Sendmail-compatible interface
+/* postdrop(1), mail posting agent
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <scan_dir.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <set_ugid.h>
+#include <safe_open.h>
+#include <watchdog.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_open_ok.h>
+#include <mymalloc.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <record.h>
+#include <rec_type.h>
+#include <lex_822.h>
+#include <input_transp.h>
+#include <rec_attr_map.h>
+#include <mail_version.h>
+#include <smtputf8.h>
+#include <info_log_addr_form.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+char *var_filter_xport;
+char *var_input_transp;
+
+ /*
+ * Structure to bundle a bunch of information about a queue file.
+ */
+typedef struct {
+ char *id; /* queue file basename */
+ struct stat st; /* queue file status */
+ char *path; /* name for open/remove */
+ char *sender; /* sender address */
+} PICKUP_INFO;
+
+ /*
+ * What action should be taken after attempting to deliver a message: remove
+ * the file from the maildrop, or leave it alone. The latter is also used
+ * for files that are still being written to.
+ */
+#define REMOVE_MESSAGE_FILE 1
+#define KEEP_MESSAGE_FILE 2
+
+ /*
+ * Transparency: before mail is queued, do we allow address mapping,
+ * automatic bcc, header/body checks?
+ */
+int pickup_input_transp_mask;
+
+/* file_read_error - handle error while reading queue file */
+
+static int file_read_error(PICKUP_INFO *info, int type)
+{
+ msg_warn("uid=%ld: unexpected or malformed record type %d",
+ (long) info->st.st_uid, type);
+ return (REMOVE_MESSAGE_FILE);
+}
+
+/* cleanup_service_error_reason - handle error writing to cleanup service. */
+
+static int cleanup_service_error_reason(PICKUP_INFO *info, int status,
+ const char *reason)
+{
+
+ /*
+ * XXX If the cleanup server gave a reason, then it was already logged.
+ * Don't bother logging it another time.
+ *
+ * XXX Discard a message without recipient. This can happen with "postsuper
+ * -r" when a message is already delivered (or bounced). The Postfix
+ * sendmail command rejects submissions without recipients.
+ */
+ if (reason == 0 || *reason == 0)
+ msg_warn("%s: error writing %s: %s",
+ info->path, info->id, cleanup_strerror(status));
+ return ((status & (CLEANUP_STAT_BAD | CLEANUP_STAT_RCPT)) ?
+ REMOVE_MESSAGE_FILE : KEEP_MESSAGE_FILE);
+}
+
+#define cleanup_service_error(info, status) \
+ cleanup_service_error_reason((info), (status), (char *) 0)
+
+/* copy_segment - copy a record group */
+
+static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info,
+ VSTRING *buf, char *expected)
+{
+ int type;
+ int check_first = (*expected == REC_TYPE_CONTENT[0]);
+ int time_seen = 0;
+ char *attr_name;
+ char *attr_value;
+ char *saved_attr;
+ int skip_attr;
+
+ /*
+ * Limit the input record size. All front-end programs should protect the
+ * mail system against unreasonable inputs. This also requires that we
+ * limit the size of envelope records written by the local posting agent.
+ *
+ * Records with named attributes are filtered by postdrop(1).
+ *
+ * We must allow PTR records here because of "postsuper -r".
+ */
+ for (;;) {
+ if ((type = rec_get(qfile, buf, var_line_limit)) < 0
+ || strchr(expected, type) == 0)
+ return (file_read_error(info, type));
+ if (msg_verbose)
+ msg_info("%s: read %c %s", info->id, type, vstring_str(buf));
+ if (type == *expected)
+ break;
+ if (type == REC_TYPE_FROM) {
+ if (info->sender == 0)
+ info->sender = mystrdup(vstring_str(buf));
+ /* Compatibility with Postfix < 2.3. */
+ if (time_seen == 0)
+ rec_fprintf(cleanup, REC_TYPE_TIME, "%ld",
+ (long) info->st.st_mtime);
+ }
+ if (type == REC_TYPE_TIME)
+ time_seen = 1;
+
+ /*
+ * XXX Workaround: REC_TYPE_FILT (used in envelopes) == REC_TYPE_CONT
+ * (used in message content).
+ *
+ * As documented in postsuper(1), ignore content filter record.
+ */
+ if (*expected != REC_TYPE_CONTENT[0]) {
+ if (type == REC_TYPE_FILT)
+ /* Discard FILTER record after "postsuper -r". */
+ continue;
+ if (type == REC_TYPE_RDR)
+ /* Discard REDIRECT record after "postsuper -r". */
+ continue;
+ }
+ if (*expected == REC_TYPE_EXTRACT[0]) {
+ if (type == REC_TYPE_RRTO)
+ /* Discard return-receipt record after "postsuper -r". */
+ continue;
+ if (type == REC_TYPE_ERTO)
+ /* Discard errors-to record after "postsuper -r". */
+ continue;
+ if (type == REC_TYPE_ATTR) {
+ saved_attr = mystrdup(vstring_str(buf));
+ skip_attr = (split_nameval(saved_attr,
+ &attr_name, &attr_value) == 0
+ && rec_attr_map(attr_name) == 0);
+ myfree(saved_attr);
+ /* Discard other/header/body action after "postsuper -r". */
+ if (skip_attr)
+ continue;
+ }
+ }
+
+ /*
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our
+ * own Received: header. What an ugly Kluge.
+ */
+ if (check_first
+ && (type == REC_TYPE_NORM || type == REC_TYPE_CONT)) {
+ check_first = 0;
+ if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0]))
+ rec_put(cleanup, REC_TYPE_NORM, "", 0);
+ }
+ if ((REC_PUT_BUF(cleanup, type, buf)) < 0)
+ return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
+ }
+ return (0);
+}
+
+/* pickup_copy - copy message to cleanup service */
+
+static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup,
+ PICKUP_INFO *info, VSTRING *buf)
+{
+ time_t now = time((time_t *) 0);
+ int status;
+ char *name;
+
+ /*
+ * Protect against time-warped time stamps. Warn about mail that has been
+ * queued for an excessive amount of time. Allow for some time drift with
+ * network clients that mount the maildrop remotely - especially clients
+ * that can't get their daylight savings offsets right.
+ */
+#define DAY_SECONDS 86400
+#define HOUR_SECONDS 3600
+
+ if (info->st.st_mtime > now + 2 * HOUR_SECONDS) {
+ msg_warn("%s: message dated %ld seconds into the future",
+ info->id, (long) (info->st.st_mtime - now));
+ info->st.st_mtime = now;
+ } else if (info->st.st_mtime < now - DAY_SECONDS) {
+ msg_warn("%s: message has been queued for %d days",
+ info->id, (int) ((now - info->st.st_mtime) / DAY_SECONDS));
+ }
+
+ /*
+ * Add content inspection transport. See also postsuper(1).
+ */
+ if (*var_filter_xport)
+ rec_fprintf(cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+
+ /*
+ * Copy the message envelope segment. Allow only those records that we
+ * expect to see in the envelope section. The envelope segment must
+ * contain an envelope sender address.
+ */
+ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_ENVELOPE)) != 0)
+ return (status);
+ if (info->sender == 0) {
+ msg_warn("%s: uid=%ld: no envelope sender",
+ info->id, (long) info->st.st_uid);
+ return (REMOVE_MESSAGE_FILE);
+ }
+
+ /*
+ * For messages belonging to $mail_owner also log the maildrop queue id.
+ * This supports message tracking for mail requeued via "postsuper -r".
+ */
+#define MAIL_IS_REQUEUED(info) \
+ ((info)->st.st_uid == var_owner_uid && ((info)->st.st_mode & S_IROTH) == 0)
+
+ if (MAIL_IS_REQUEUED(info)) {
+ msg_info("%s: uid=%d from=<%s> orig_id=%s", info->id,
+ (int) info->st.st_uid, info_log_addr_form_sender(info->sender),
+ ((name = strrchr(info->path, '/')) != 0 ?
+ name + 1 : info->path));
+ } else {
+ msg_info("%s: uid=%d from=<%s>", info->id,
+ (int) info->st.st_uid, info_log_addr_form_sender(info->sender));
+ }
+
+ /*
+ * Message content segment. Send a dummy message length. Prepend a
+ * Received: header to the message contents. For tracing purposes,
+ * include the message file ownership, without revealing the login name.
+ */
+ rec_fputs(cleanup, REC_TYPE_MESG, "");
+ rec_fprintf(cleanup, REC_TYPE_NORM, "Received: by %s (%s, from userid %ld)",
+ var_myhostname, var_mail_name, (long) info->st.st_uid);
+ rec_fprintf(cleanup, REC_TYPE_NORM, "\tid %s; %s", info->id,
+ mail_date(info->st.st_mtime));
+
+ /*
+ * Copy the message content segment. Allow only those records that we
+ * expect to see in the message content section.
+ */
+ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_CONTENT)) != 0)
+ return (status);
+
+ /*
+ * Send the segment with information extracted from message headers.
+ * Permit a non-empty extracted segment, so that list manager software
+ * can to output recipients after the message, and so that sysadmins can
+ * re-inject messages after a change of configuration.
+ */
+ rec_fputs(cleanup, REC_TYPE_XTRA, "");
+ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0)
+ return (status);
+
+ /*
+ * There are no errors. Send the end-of-data marker, and get the cleanup
+ * service completion status. XXX Since the pickup service is unable to
+ * bounce, the cleanup service can report only soft errors here.
+ */
+ rec_fputs(cleanup, REC_TYPE_END, "");
+ if (attr_scan(cleanup, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, buf),
+ ATTR_TYPE_END) != 2)
+ return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
+
+ /*
+ * Depending on the cleanup service completion status, delete the message
+ * file, or try again later. Bounces are dealt with by the cleanup
+ * service itself. The master process wakes up the cleanup service every
+ * now and then.
+ */
+ if (status) {
+ return (cleanup_service_error_reason(info, status, vstring_str(buf)));
+ } else {
+ return (REMOVE_MESSAGE_FILE);
+ }
+}
+
+/* pickup_file - initialize for file copy and cleanup */
+
+static int pickup_file(PICKUP_INFO *info)
+{
+ VSTRING *buf = vstring_alloc(100);
+ int status;
+ VSTREAM *qfile;
+ VSTREAM *cleanup;
+ int cleanup_flags;
+
+ /*
+ * Open the submitted file. If we cannot open it, and we're not having a
+ * file descriptor leak problem, delete the submitted file, so that we
+ * won't keep complaining about the same file again and again. XXX
+ * Perhaps we should save "bad" files elsewhere for further inspection.
+ * XXX How can we delete a file when open() fails with ENOENT?
+ */
+ qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0,
+ (struct stat *) 0, -1, -1, buf);
+ if (qfile == 0) {
+ if (errno != ENOENT)
+ msg_warn("open input file %s: %s", info->path, vstring_str(buf));
+ vstring_free(buf);
+ if (errno == EACCES)
+ msg_warn("if this file was created by Postfix < 1.1, then you may have to chmod a+r %s/%s",
+ var_queue_dir, info->path);
+ return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE);
+ }
+
+ /*
+ * Contact the cleanup service and read the queue ID that it has
+ * allocated. In case of trouble, request that the cleanup service
+ * bounces its copy of the message. because the original input file is
+ * not readable by the bounce service.
+ *
+ * If mail is re-injected with "postsuper -r", disable Milter applications.
+ * If they were run before the mail was queued then there is no need to
+ * run them again. Moreover, the queue file does not contain enough
+ * information to reproduce the exact same SMTP events and Sendmail
+ * macros that Milters received when the mail originally arrived in
+ * Postfix.
+ *
+ * The actual message copying code is in a separate routine, so that it is
+ * easier to implement the many possible error exits without forgetting
+ * to close files, or to release memory.
+ */
+ cleanup_flags =
+ input_transp_cleanup(CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_EXTERNAL,
+ pickup_input_transp_mask);
+ /* As documented in postsuper(1). */
+ if (MAIL_IS_REQUEUED(info))
+ cleanup_flags &= ~CLEANUP_FLAG_MILTER;
+ else
+ cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SENDMAIL);
+
+ cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
+ if (attr_scan(cleanup, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buf),
+ ATTR_TYPE_END) != 1
+ || attr_print(cleanup, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0) {
+ status = KEEP_MESSAGE_FILE;
+ } else {
+ info->id = mystrdup(vstring_str(buf));
+ status = pickup_copy(qfile, cleanup, info, buf);
+ }
+ vstream_fclose(qfile);
+ vstream_fclose(cleanup);
+ vstring_free(buf);
+ return (status);
+}
+
+/* pickup_init - init info structure */
+
+static void pickup_init(PICKUP_INFO *info)
+{
+ info->id = 0;
+ info->path = 0;
+ info->sender = 0;
+}
+
+/* pickup_free - wipe info structure */
+
+static void pickup_free(PICKUP_INFO *info)
+{
+#define SAFE_FREE(x) { if (x) myfree(x); }
+
+ SAFE_FREE(info->id);
+ SAFE_FREE(info->path);
+ SAFE_FREE(info->sender);
+}
+
+/* pickup_service - service client */
+
+static void pickup_service(char *unused_buf, ssize_t unused_len,
+ char *unused_service, char **argv)
+{
+ SCAN_DIR *scan;
+ char *queue_name;
+ PICKUP_INFO info;
+ const char *path;
+ char *id;
+ int file_count;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Skip over things that we don't want to open, such as files that are
+ * still being written, or garbage. Leave it up to the sysadmin to remove
+ * garbage. Keep scanning the queue directory until we stop removing
+ * files from it.
+ *
+ * When we find a file, stroke the watchdog so that it will not bark while
+ * some application is keeping us busy by injecting lots of mail into the
+ * maildrop directory.
+ */
+ queue_name = MAIL_QUEUE_MAILDROP; /* XXX should be a list */
+ do {
+ file_count = 0;
+ scan = scan_dir_open(queue_name);
+ while ((id = scan_dir_next(scan)) != 0) {
+ if (mail_open_ok(queue_name, id, &info.st, &path) == MAIL_OPEN_YES) {
+ pickup_init(&info);
+ info.path = mystrdup(path);
+ watchdog_pat();
+ if (pickup_file(&info) == REMOVE_MESSAGE_FILE) {
+ if (REMOVE(info.path))
+ msg_warn("remove %s: %m", info.path);
+ else
+ file_count++;
+ }
+ pickup_free(&info);
+ }
+ }
+ scan_dir_close(scan);
+ } while (file_count);
+}
+
+/* post_jail_init - drop privileges */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * In case master.cf was not updated for unprivileged service.
+ */
+ if (getuid() != var_owner_uid)
+ set_ugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * Initialize the receive transparency options: do we want unknown
+ * recipient checks, do we want address mapping.
+ */
+ pickup_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded server skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Use the multi-threaded skeleton, because no-one else should be
+ * monitoring our service socket while this process runs.
+ *
+ * XXX The default watchdog timeout for trigger servers is 1000s, while the
+ * cleanup server watchdog timeout is $daemon_timeout (i.e. several
+ * hours). We override the default 1000s timeout to avoid problems with
+ * slow mail submission. The real problem is of course that the
+ * single-threaded pickup server is not a good solution for mail
+ * submissions.
+ */
+ trigger_server_main(argc, argv, pickup_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_WATCHDOG(&var_daemon_timeout),
+ 0);
+}
diff --git a/src/pipe/.indent.pro b/src/pipe/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/pipe/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/pipe/.printfck b/src/pipe/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/pipe/.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/pipe/Makefile.in b/src/pipe/Makefile.in
new file mode 100644
index 0000000..42159ca
--- /dev/null
+++ b/src/pipe/Makefile.in
@@ -0,0 +1,107 @@
+SHELL = /bin/sh
+SRCS = pipe.c
+OBJS = pipe.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = pipe
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+pipe.o: ../../include/argv.h
+pipe.o: ../../include/attr.h
+pipe.o: ../../include/bounce.h
+pipe.o: ../../include/canon_addr.h
+pipe.o: ../../include/check_arg.h
+pipe.o: ../../include/defer.h
+pipe.o: ../../include/deliver_completed.h
+pipe.o: ../../include/deliver_request.h
+pipe.o: ../../include/delivered_hdr.h
+pipe.o: ../../include/dict.h
+pipe.o: ../../include/dsn.h
+pipe.o: ../../include/dsn_buf.h
+pipe.o: ../../include/dsn_util.h
+pipe.o: ../../include/flush_clnt.h
+pipe.o: ../../include/fold_addr.h
+pipe.o: ../../include/htable.h
+pipe.o: ../../include/iostuff.h
+pipe.o: ../../include/mac_parse.h
+pipe.o: ../../include/mail_addr.h
+pipe.o: ../../include/mail_conf.h
+pipe.o: ../../include/mail_copy.h
+pipe.o: ../../include/mail_params.h
+pipe.o: ../../include/mail_parm_split.h
+pipe.o: ../../include/mail_server.h
+pipe.o: ../../include/mail_version.h
+pipe.o: ../../include/msg.h
+pipe.o: ../../include/msg_stats.h
+pipe.o: ../../include/myflock.h
+pipe.o: ../../include/mymalloc.h
+pipe.o: ../../include/nvtable.h
+pipe.o: ../../include/off_cvt.h
+pipe.o: ../../include/pipe_command.h
+pipe.o: ../../include/quote_822_local.h
+pipe.o: ../../include/quote_flags.h
+pipe.o: ../../include/recipient_list.h
+pipe.o: ../../include/sent.h
+pipe.o: ../../include/set_eugid.h
+pipe.o: ../../include/split_addr.h
+pipe.o: ../../include/split_at.h
+pipe.o: ../../include/stringops.h
+pipe.o: ../../include/sys_defs.h
+pipe.o: ../../include/sys_exits.h
+pipe.o: ../../include/vbuf.h
+pipe.o: ../../include/vstream.h
+pipe.o: ../../include/vstring.h
+pipe.o: pipe.c
diff --git a/src/pipe/pipe.c b/src/pipe/pipe.c
new file mode 100644
index 0000000..8a99430
--- /dev/null
+++ b/src/pipe/pipe.c
@@ -0,0 +1,1397 @@
+/*++
+/* NAME
+/* pipe 8
+/* SUMMARY
+/* Postfix delivery to external command
+/* SYNOPSIS
+/* \fBpipe\fR [generic Postfix daemon options] command_attributes...
+/* DESCRIPTION
+/* The \fBpipe\fR(8) daemon processes requests from the Postfix queue
+/* manager to deliver messages to external commands.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* Message attributes such as sender address, recipient address and
+/* next-hop host name can be specified as command-line macros that are
+/* expanded before the external command is executed.
+/*
+/* The \fBpipe\fR(8) daemon updates queue files and marks recipients
+/* as finished, or it informs the queue manager that delivery should
+/* be tried again at a later time. Delivery status reports are sent
+/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as
+/* appropriate.
+/* SINGLE-RECIPIENT DELIVERY
+/* .ad
+/* .fi
+/* Some destinations cannot handle more than one recipient per
+/* delivery request. Examples are pagers or fax machines.
+/* In addition, multi-recipient delivery is undesirable when
+/* prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR
+/* message header.
+/*
+/* To prevent Postfix from sending multiple recipients per delivery
+/* request, specify
+/* .sp
+/* .nf
+/* \fItransport\fB_destination_recipient_limit = 1\fR
+/* .fi
+/*
+/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR
+/* is the name in the first column of the Postfix \fBmaster.cf\fR
+/* entry for the pipe-based delivery transport.
+/* COMMAND ATTRIBUTE SYNTAX
+/* .ad
+/* .fi
+/* The external command attributes are given in the \fBmaster.cf\fR
+/* file at the end of a service definition. The syntax is as follows:
+/* .IP "\fBchroot=\fIpathname\fR (optional)"
+/* Change the process root directory and working directory to
+/* the named directory. This happens before switching to the
+/* privileges specified with the \fBuser\fR attribute, and
+/* before executing the optional \fBdirectory=\fIpathname\fR
+/* directive. Delivery is deferred in case of failure.
+/* .sp
+/* This feature is available as of Postfix 2.3.
+/* .IP "\fBdirectory=\fIpathname\fR (optional)"
+/* Change to the named directory before executing the external command.
+/* The directory must be accessible for the user specified with the
+/* \fBuser\fR attribute (see below).
+/* The default working directory is \fB$queue_directory\fR.
+/* Delivery is deferred in case of failure.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP "\fBeol=\fIstring\fR (optional, default: \fB\en\fR)"
+/* The output record delimiter. Typically one would use either
+/* \fB\er\en\fR or \fB\en\fR. The usual C-style backslash escape
+/* sequences are recognized: \fB\ea \eb \ef \en \er \et \ev
+/* \e\fIddd\fR (up to three octal digits) and \fB\e\e\fR.
+/* .IP "\fBflags=BDFORXhqu.>\fR (optional)"
+/* Optional message processing flags. By default, a message is
+/* copied unchanged.
+/* .RS
+/* .IP \fBB\fR
+/* Append a blank line at the end of each message. This is required
+/* by some mail user agents that recognize "\fBFrom \fR" lines only
+/* when preceded by a blank line.
+/* .IP \fBD\fR
+/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header with the
+/* envelope recipient address. Note: for this to work, the
+/* \fItransport\fB_destination_recipient_limit\fR must be 1
+/* (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* The \fBD\fR flag also enforces loop detection (Postfix 2.5 and later):
+/* if a message already contains a \fBDelivered-To:\fR header
+/* with the same recipient address, then the message is
+/* returned as undeliverable. The address comparison is case
+/* insensitive.
+/* .sp
+/* This feature is available as of Postfix 2.0.
+/* .IP \fBF\fR
+/* Prepend a "\fBFrom \fIsender time_stamp\fR" envelope header to
+/* the message content.
+/* This is expected by, for example, \fBUUCP\fR software.
+/* .IP \fBO\fR
+/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message header
+/* with the recipient address as given to Postfix. Note: for this to
+/* work, the \fItransport\fB_destination_recipient_limit\fR must be 1
+/* (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* This feature is available as of Postfix 2.0.
+/* .IP \fBR\fR
+/* Prepend a \fBReturn-Path:\fR message header with the envelope sender
+/* address.
+/* .IP \fBX\fR
+/* Indicate that the external command performs final delivery.
+/* This flag affects the status reported in "success" DSN
+/* (delivery status notification) messages, and changes it
+/* from "relayed" into "delivered".
+/* .sp
+/* This feature is available as of Postfix 2.5.
+/* .IP \fBh\fR
+/* Fold the command-line \fB$original_recipient\fR and
+/* \fB$recipient\fR address domain part
+/* (text to the right of the right-most \fB@\fR character) to
+/* lower case; fold the entire command-line \fB$domain\fR and
+/* \fB$nexthop\fR host or domain information to lower case.
+/* This is recommended for delivery via \fBUUCP\fR.
+/* .IP \fBq\fR
+/* Quote white space and other special characters in the command-line
+/* \fB$sender\fR, \fB$original_recipient\fR and \fB$recipient\fR
+/* address localparts (text to the
+/* left of the right-most \fB@\fR character), according to an 8-bit
+/* transparent version of RFC 822.
+/* This is recommended for delivery via \fBUUCP\fR or \fBBSMTP\fR.
+/* .sp
+/* The result is compatible with the address parsing of command-line
+/* recipients by the Postfix \fBsendmail\fR(1) mail submission command.
+/* .sp
+/* The \fBq\fR flag affects only entire addresses, not the partial
+/* address information from the \fB$user\fR, \fB$extension\fR or
+/* \fB$mailbox\fR command-line macros.
+/* .IP \fBu\fR
+/* Fold the command-line \fB$original_recipient\fR and
+/* \fB$recipient\fR address localpart (text to
+/* the left of the right-most \fB@\fR character) to lower case.
+/* This is recommended for delivery via \fBUUCP\fR.
+/* .IP \fB.\fR
+/* Prepend "\fB.\fR" to lines starting with "\fB.\fR". This is needed
+/* by, for example, \fBBSMTP\fR software.
+/* .IP \fB>\fR
+/* Prepend "\fB>\fR" to lines starting with "\fBFrom \fR". This is expected
+/* by, for example, \fBUUCP\fR software.
+/* .RE
+/* .IP "\fBnull_sender\fR=\fIreplacement\fR (default: MAILER-DAEMON)"
+/* Replace the null sender address (typically used for delivery
+/* status notifications) with the specified text
+/* when expanding the \fB$sender\fR command-line macro, and
+/* when generating a From_ or Return-Path: message header.
+/*
+/* If the null sender replacement text is a non-empty string
+/* then it is affected by the \fBq\fR flag for address quoting
+/* in command-line arguments.
+/*
+/* The null sender replacement text may be empty; this form
+/* is recommended for content filters that feed mail back into
+/* Postfix. The empty sender address is not affected by the
+/* \fBq\fR flag for address quoting in command-line arguments.
+/* .sp
+/* Caution: a null sender address is easily mis-parsed by
+/* naive software. For example, when the \fBpipe\fR(8) daemon
+/* executes a command such as:
+/* .sp
+/* .nf
+/* \fIWrong\fR: command -f$sender -- $recipient
+/* .fi
+/* .IP
+/* the command will mis-parse the -f option value when the
+/* sender address is a null string. For correct parsing,
+/* specify \fB$sender\fR as an argument by itself:
+/* .sp
+/* .nf
+/* \fIRight\fR: command -f $sender -- $recipient
+/* .fi
+/* NOTE: DO NOT put quotes around the command, $sender, or $recipient.
+/* .IP
+/* This feature is available as of Postfix 2.3.
+/* .IP "\fBsize\fR=\fIsize_limit\fR (optional)"
+/* Don't deliver messages that exceed this size limit (in
+/* bytes); return them to the sender instead.
+/* .IP "\fBuser\fR=\fIusername\fR (required)"
+/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
+/* Execute the external command with the user ID and group ID of the
+/* specified \fIusername\fR. The software refuses to execute
+/* commands with root privileges, or with the privileges of the
+/* mail system owner. If \fIgroupname\fR is specified, the
+/* corresponding group ID is used instead of the group ID of
+/* \fIusername\fR.
+/* .IP "\fBargv\fR=\fIcommand\fR... (required)"
+/* The command to be executed. This must be specified as the
+/* last command attribute.
+/* The command is executed directly, i.e. without interpretation of
+/* shell meta characters by a shell command interpreter.
+/* .sp
+/* Specify "{" and "}" around command arguments that contain
+/* whitespace (Postfix 3.0 and later). Whitespace
+/* after the opening "{" and before the closing "}" is ignored.
+/* .sp
+/* In the command argument vector, the following macros are recognized
+/* and replaced with corresponding information from the Postfix queue
+/* manager delivery request.
+/* .sp
+/* In addition to the form ${\fIname\fR}, the forms $\fIname\fR and
+/* the deprecated form $(\fIname\fR) are also recognized.
+/* Specify \fB$$\fR where a single \fB$\fR is wanted.
+/* .RS
+/* .IP \fB${client_address}\fR
+/* This macro expands to the remote client network address.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${client_helo}\fR
+/* This macro expands to the remote client HELO command parameter.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${client_hostname}\fR
+/* This macro expands to the remote client hostname.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${client_port}\fR
+/* This macro expands to the remote client TCP port number.
+/* .sp
+/* This feature is available as of Postfix 2.5.
+/* .IP \fB${client_protocol}\fR
+/* This macro expands to the remote client protocol.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${domain}\fR
+/* This macro expands to the domain portion of the recipient
+/* address. For example, with an address \fIuser+foo@domain\fR
+/* the domain is \fIdomain\fR.
+/* .sp
+/* This information is modified by the \fBh\fR flag for case folding.
+/* .sp
+/* This feature is available as of Postfix 2.5.
+/* .IP \fB${extension}\fR
+/* This macro expands to the extension part of a recipient address.
+/* For example, with an address \fIuser+foo@domain\fR the extension is
+/* \fIfoo\fR.
+/* .sp
+/* A command-line argument that contains \fB${extension}\fR expands
+/* into as many command-line arguments as there are recipients.
+/* .sp
+/* This information is modified by the \fBu\fR flag for case folding.
+/* .IP \fB${mailbox}\fR
+/* This macro expands to the complete local part of a recipient address.
+/* For example, with an address \fIuser+foo@domain\fR the mailbox is
+/* \fIuser+foo\fR.
+/* .sp
+/* A command-line argument that contains \fB${mailbox}\fR
+/* expands to as many command-line arguments as there are recipients.
+/* .sp
+/* This information is modified by the \fBu\fR flag for case folding.
+/* .IP \fB${nexthop}\fR
+/* This macro expands to the next-hop hostname.
+/* .sp
+/* This information is modified by the \fBh\fR flag for case folding.
+/* .IP \fB${original_recipient}\fR
+/* This macro expands to the complete recipient address before any
+/* address rewriting or aliasing.
+/* .sp
+/* A command-line argument that contains
+/* \fB${original_recipient}\fR expands to as many
+/* command-line arguments as there are recipients.
+/* .sp
+/* This information is modified by the \fBhqu\fR flags for quoting
+/* and case folding.
+/* .sp
+/* This feature is available as of Postfix 2.5.
+/* .IP \fB${queue_id}\fR
+/* This macro expands to the queue id.
+/* .sp
+/* This feature is available as of Postfix 2.11.
+/* .IP \fB${recipient}\fR
+/* This macro expands to the complete recipient address.
+/* .sp
+/* A command-line argument that contains \fB${recipient}\fR
+/* expands to as many command-line arguments as there are recipients.
+/* .sp
+/* This information is modified by the \fBhqu\fR flags for quoting
+/* and case folding.
+/* .IP \fB${sasl_method}\fR
+/* This macro expands to the name of the SASL authentication
+/* mechanism in the AUTH command when the Postfix SMTP server
+/* received the message.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${sasl_sender}\fR
+/* This macro expands to the SASL sender name (i.e. the original
+/* submitter as per RFC 4954) in the MAIL FROM command when
+/* the Postfix SMTP server received the message.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${sasl_username}\fR
+/* This macro expands to the SASL user name in the AUTH command
+/* when the Postfix SMTP server received the message.
+/* .sp
+/* This feature is available as of Postfix 2.2.
+/* .IP \fB${sender}\fR
+/* This macro expands to the envelope sender address. By default,
+/* the null sender address expands to MAILER-DAEMON; this can
+/* be changed with the \fBnull_sender\fR attribute, as described
+/* above.
+/* .sp
+/* This information is modified by the \fBq\fR flag for quoting.
+/* .IP \fB${size}\fR
+/* This macro expands to Postfix's idea of the message size, which
+/* is an approximation of the size of the message as delivered.
+/* .IP \fB${user}\fR
+/* This macro expands to the username part of a recipient address.
+/* For example, with an address \fIuser+foo@domain\fR the username
+/* part is \fIuser\fR.
+/* .sp
+/* A command-line argument that contains \fB${user}\fR expands
+/* into as many command-line arguments as there are recipients.
+/* .sp
+/* This information is modified by the \fBu\fR flag for case folding.
+/* .RE
+/* STANDARDS
+/* RFC 3463 (Enhanced status codes)
+/* DIAGNOSTICS
+/* Command exit status codes are expected to
+/* follow the conventions defined in <\fBsysexits.h\fR>.
+/* Exit status 0 means normal successful completion.
+/*
+/* In the case of a non-zero exit status, a limited amount of
+/* command output is logged, and reported in a delivery status
+/* notification. When the output begins with a 4.X.X or 5.X.X
+/* enhanced status code, the status code takes precedence over
+/* the non-zero exit status (Postfix version 2.3 and later).
+/*
+/* After successful delivery (zero exit status) a limited
+/* amount of command output is logged, and reported in "success"
+/* delivery status notifications (Postfix 3.0 and later).
+/* This command output is not examined for the presence of an
+/* enhanced status code.
+/*
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are marked so that the queue manager
+/* can move them to the \fBcorrupt\fR queue for further inspection.
+/* SECURITY
+/* .fi
+/* .ad
+/* This program needs a dual personality 1) to access the private
+/* Postfix queue and IPC mechanisms, and 2) to execute external
+/* commands as the specified user. It is therefore security sensitive.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBpipe\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.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* In the text below, \fItransport\fR is the first field in a
+/* \fBmaster.cf\fR entry.
+/* .IP "\fBtransport_time_limit ($command_time_limit)\fR"
+/* A transport-specific override for the command_time_limit parameter
+/* value, where \fItransport\fR is the master.cf name of the message
+/* delivery transport.
+/* .PP
+/* Implemented in the qmgr(8) daemon:
+/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_recipient_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* 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 "\fBexport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a Postfix process will export
+/* to non-Postfix processes.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .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 "\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 "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* .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 3.0 and later:
+/* .IP "\fBpipe_delivery_status_filter ($default_delivery_status_filter)\fR"
+/* Optional filter for the \fBpipe\fR(8) delivery agent to change the
+/* delivery status code or explanatory text of successful or unsuccessful
+/* deliveries.
+/* .PP
+/* Available in Postfix version 3.3 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).
+/* .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.).
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <htable.h>
+#include <dict.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <mac_parse.h>
+#include <set_eugid.h>
+#include <split_at.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <bounce.h>
+#include <defer.h>
+#include <deliver_completed.h>
+#include <sent.h>
+#include <pipe_command.h>
+#include <mail_copy.h>
+#include <mail_addr.h>
+#include <canon_addr.h>
+#include <split_addr.h>
+#include <off_cvt.h>
+#include <quote_822_local.h>
+#include <flush_clnt.h>
+#include <dsn_util.h>
+#include <dsn_buf.h>
+#include <sys_exits.h>
+#include <delivered_hdr.h>
+#include <fold_addr.h>
+#include <mail_parm_split.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * The mini symbol table name and keys used for expanding macros in
+ * command-line arguments.
+ *
+ * XXX Update the parse_callback() routine when something gets added here,
+ * even when the macro is not recipient dependent.
+ */
+#define PIPE_DICT_TABLE "pipe_command" /* table name */
+#define PIPE_DICT_NEXTHOP "nexthop" /* key */
+#define PIPE_DICT_RCPT "recipient" /* key */
+#define PIPE_DICT_ORIG_RCPT "original_recipient" /* key */
+#define PIPE_DICT_SENDER "sender"/* key */
+#define PIPE_DICT_USER "user" /* key */
+#define PIPE_DICT_EXTENSION "extension" /* key */
+#define PIPE_DICT_MAILBOX "mailbox" /* key */
+#define PIPE_DICT_DOMAIN "domain"/* key */
+#define PIPE_DICT_SIZE "size" /* key */
+#define PIPE_DICT_CLIENT_ADDR "client_address" /* key */
+#define PIPE_DICT_CLIENT_NAME "client_hostname" /* key */
+#define PIPE_DICT_CLIENT_PORT "client_port" /* key */
+#define PIPE_DICT_CLIENT_PROTO "client_protocol" /* key */
+#define PIPE_DICT_CLIENT_HELO "client_helo" /* key */
+#define PIPE_DICT_SASL_METHOD "sasl_method" /* key */
+#define PIPE_DICT_SASL_USERNAME "sasl_username" /* key */
+#define PIPE_DICT_SASL_SENDER "sasl_sender" /* key */
+#define PIPE_DICT_QUEUE_ID "queue_id" /* key */
+
+ /*
+ * Flags used to pass back the type of special parameter found by
+ * parse_callback.
+ */
+#define PIPE_FLAG_RCPT (1<<0)
+#define PIPE_FLAG_USER (1<<1)
+#define PIPE_FLAG_EXTENSION (1<<2)
+#define PIPE_FLAG_MAILBOX (1<<3)
+#define PIPE_FLAG_DOMAIN (1<<4)
+#define PIPE_FLAG_ORIG_RCPT (1<<5)
+
+ /*
+ * Additional flags. These are colocated with mail_copy() flags. Allow some
+ * space for extension of the mail_copy() interface.
+ */
+#define PIPE_OPT_FOLD_BASE (16)
+#define PIPE_OPT_FOLD_USER (FOLD_ADDR_USER << PIPE_OPT_FOLD_BASE)
+#define PIPE_OPT_FOLD_HOST (FOLD_ADDR_HOST << PIPE_OPT_FOLD_BASE)
+#define PIPE_OPT_QUOTE_LOCAL (1 << (PIPE_OPT_FOLD_BASE + 2))
+#define PIPE_OPT_FINAL_DELIVERY (1 << (PIPE_OPT_FOLD_BASE + 3))
+
+#define PIPE_OPT_FOLD_ALL (FOLD_ADDR_ALL << PIPE_OPT_FOLD_BASE)
+#define PIPE_OPT_FOLD_FLAGS(f) \
+ (((f) & PIPE_OPT_FOLD_ALL) >> PIPE_OPT_FOLD_BASE)
+
+ /*
+ * Tunable parameters. Values are taken from the config file, after
+ * prepending the service name to _name, and so on.
+ */
+int var_command_maxtime; /* You can now leave this here. */
+
+ /*
+ * Other main.cf parameters.
+ */
+char *var_pipe_dsn_filter;
+
+ /*
+ * For convenience. Instead of passing around lists of parameters, bundle
+ * them up in convenient structures.
+ */
+
+ /*
+ * Structure for service-specific configuration parameters.
+ */
+typedef struct {
+ int time_limit; /* per-service time limit */
+} PIPE_PARAMS;
+
+ /*
+ * Structure for command-line parameters.
+ */
+typedef struct {
+ char **command; /* argument vector */
+ uid_t uid; /* command privileges */
+ gid_t gid; /* command privileges */
+ int flags; /* mail_copy() flags */
+ char *exec_dir; /* working directory */
+ char *chroot_dir; /* chroot directory */
+ VSTRING *eol; /* output record delimiter */
+ VSTRING *null_sender; /* null sender expansion */
+ off_t size_limit; /* max size in bytes we will accept */
+} PIPE_ATTR;
+
+ /*
+ * Structure for command-line parameter macro expansion.
+ */
+typedef struct {
+ const char *service; /* for warnings */
+ int expand_flag; /* callback result */
+} PIPE_STATE;
+
+ /*
+ * Silly little macros.
+ */
+#define STR vstring_str
+
+/* parse_callback - callback for mac_parse() */
+
+static int parse_callback(int type, VSTRING *buf, void *context)
+{
+ PIPE_STATE *state = (PIPE_STATE *) context;
+ struct cmd_flags {
+ const char *name;
+ int flags;
+ };
+ static struct cmd_flags cmd_flags[] = {
+ PIPE_DICT_NEXTHOP, 0,
+ PIPE_DICT_RCPT, PIPE_FLAG_RCPT,
+ PIPE_DICT_ORIG_RCPT, PIPE_FLAG_ORIG_RCPT,
+ PIPE_DICT_SENDER, 0,
+ PIPE_DICT_USER, PIPE_FLAG_USER,
+ PIPE_DICT_EXTENSION, PIPE_FLAG_EXTENSION,
+ PIPE_DICT_MAILBOX, PIPE_FLAG_MAILBOX,
+ PIPE_DICT_DOMAIN, PIPE_FLAG_DOMAIN,
+ PIPE_DICT_SIZE, 0,
+ PIPE_DICT_CLIENT_ADDR, 0,
+ PIPE_DICT_CLIENT_NAME, 0,
+ PIPE_DICT_CLIENT_PORT, 0,
+ PIPE_DICT_CLIENT_PROTO, 0,
+ PIPE_DICT_CLIENT_HELO, 0,
+ PIPE_DICT_SASL_METHOD, 0,
+ PIPE_DICT_SASL_USERNAME, 0,
+ PIPE_DICT_SASL_SENDER, 0,
+ PIPE_DICT_QUEUE_ID, 0,
+ 0, 0,
+ };
+ struct cmd_flags *p;
+
+ /*
+ * See if this command-line argument references a special macro.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ for (p = cmd_flags; /* see below */ ; p++) {
+ if (p->name == 0) {
+ msg_warn("file %s/%s: service %s: unknown macro name: \"%s\"",
+ var_config_dir, MASTER_CONF_FILE,
+ state->service, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ } else if (strcmp(vstring_str(buf), p->name) == 0) {
+ state->expand_flag |= p->flags;
+ return (0);
+ }
+ }
+ }
+ return (0);
+}
+
+/* morph_recipient - morph a recipient address */
+
+static void morph_recipient(VSTRING *buf, const char *address, int flags)
+{
+ VSTRING *temp = vstring_alloc(100);
+
+ /*
+ * Quote the recipient address as appropriate.
+ */
+ if (flags & PIPE_OPT_QUOTE_LOCAL)
+ quote_822_local(temp, address);
+ else
+ vstring_strcpy(temp, address);
+
+ /*
+ * Fold the recipient address as appropriate.
+ */
+ fold_addr(buf, STR(temp), PIPE_OPT_FOLD_FLAGS(flags));
+
+ vstring_free(temp);
+}
+
+/* expand_argv - expand macros in the argument vector */
+
+static ARGV *expand_argv(const char *service, char **argv,
+ RECIPIENT_LIST *rcpt_list, int flags)
+{
+ VSTRING *buf = vstring_alloc(100);
+ ARGV *result;
+ char **cpp;
+ PIPE_STATE state;
+ int i;
+ char *ext;
+ char *dom;
+
+ /*
+ * This appears to be simple operation (replace $name by its expansion).
+ * However, it becomes complex because a command-line argument that
+ * references $recipient must expand to as many command-line arguments as
+ * there are recipients (that's wat programs called by sendmail expect).
+ * So we parse each command-line argument, and depending on what we find,
+ * we either expand the argument just once, or we expand it once for each
+ * recipient. In either case we end up parsing the command-line argument
+ * twice. The amount of CPU time wasted will be negligible.
+ *
+ * Note: we can't use recursive macro expansion here, because recursion
+ * would screw up mail addresses that contain $ characters.
+ */
+#define NO 0
+#define EARLY_RETURN(x) { argv_free(result); vstring_free(buf); return (x); }
+
+ result = argv_alloc(1);
+ for (cpp = argv; *cpp; cpp++) {
+ state.service = service;
+ state.expand_flag = 0;
+ if (mac_parse(*cpp, parse_callback, (void *) &state) & MAC_PARSE_ERROR)
+ EARLY_RETURN(0);
+ if (state.expand_flag == 0) { /* no $recipient etc. */
+ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END);
+ } else { /* contains $recipient etc. */
+ for (i = 0; i < rcpt_list->len; i++) {
+
+ /*
+ * This argument contains $recipient.
+ */
+ if (state.expand_flag & PIPE_FLAG_RCPT) {
+ morph_recipient(buf, rcpt_list->info[i].address, flags);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf));
+ }
+
+ /*
+ * This argument contains $original_recipient.
+ */
+ if (state.expand_flag & PIPE_FLAG_ORIG_RCPT) {
+ morph_recipient(buf, rcpt_list->info[i].orig_addr, flags);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_ORIG_RCPT, STR(buf));
+ }
+
+ /*
+ * This argument contains $user. Extract the plain user name.
+ * Either anything to the left of the extension delimiter or,
+ * in absence of the latter, anything to the left of the
+ * rightmost @.
+ *
+ * Beware: if the user name is blank (e.g. +user@host), the
+ * argument is suppressed. This is necessary to allow for
+ * cyrus bulletin-board (global mailbox) delivery. XXX But,
+ * skipping empty user parts will also prevent other
+ * expansions of this specific command-line argument.
+ */
+ if (state.expand_flag & PIPE_FLAG_USER) {
+ morph_recipient(buf, rcpt_list->info[i].address,
+ flags & PIPE_OPT_FOLD_ALL);
+ if (split_at_right(STR(buf), '@') == 0)
+ msg_warn("no @ in recipient address: %s",
+ rcpt_list->info[i].address);
+ if (*var_rcpt_delim)
+ split_addr(STR(buf), var_rcpt_delim);
+ if (*STR(buf) == 0)
+ continue;
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf));
+ }
+
+ /*
+ * This argument contains $extension. Extract the recipient
+ * extension: anything between the leftmost extension
+ * delimiter and the rightmost @. The extension may be blank.
+ */
+ if (state.expand_flag & PIPE_FLAG_EXTENSION) {
+ morph_recipient(buf, rcpt_list->info[i].address,
+ flags & PIPE_OPT_FOLD_ALL);
+ if (split_at_right(STR(buf), '@') == 0)
+ msg_warn("no @ in recipient address: %s",
+ rcpt_list->info[i].address);
+ if (*var_rcpt_delim == 0
+ || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0)
+ ext = ""; /* insert null arg */
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext);
+ }
+
+ /*
+ * This argument contains $mailbox. Extract the mailbox name:
+ * anything to the left of the rightmost @.
+ */
+ if (state.expand_flag & PIPE_FLAG_MAILBOX) {
+ morph_recipient(buf, rcpt_list->info[i].address,
+ flags & PIPE_OPT_FOLD_ALL);
+ if (split_at_right(STR(buf), '@') == 0)
+ msg_warn("no @ in recipient address: %s",
+ rcpt_list->info[i].address);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf));
+ }
+
+ /*
+ * This argument contains $domain. Extract the domain name:
+ * anything to the right of the rightmost @.
+ */
+ if (state.expand_flag & PIPE_FLAG_DOMAIN) {
+ morph_recipient(buf, rcpt_list->info[i].address,
+ flags & PIPE_OPT_FOLD_ALL);
+ dom = split_at_right(STR(buf), '@');
+ if (dom == 0) {
+ msg_warn("no @ in recipient address: %s",
+ rcpt_list->info[i].address);
+ dom = ""; /* insert null arg */
+ }
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_DOMAIN, dom);
+ }
+
+ /*
+ * Done.
+ */
+ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END);
+ }
+ }
+ }
+ argv_terminate(result);
+ vstring_free(buf);
+ return (result);
+}
+
+/* get_service_params - get service-name dependent config information */
+
+static void get_service_params(PIPE_PARAMS *config, char *service)
+{
+ const char *myname = "get_service_params";
+
+ /*
+ * Figure out the command time limit for this transport.
+ */
+ config->time_limit =
+ get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
+
+ /*
+ * Give the poor tester a clue of what is going on.
+ */
+ if (msg_verbose)
+ msg_info("%s: time_limit %d", myname, config->time_limit);
+}
+
+/* get_service_attr - get command-line attributes */
+
+static void get_service_attr(PIPE_ATTR *attr, char **argv)
+{
+ const char *myname = "get_service_attr";
+ struct passwd *pwd;
+ struct group *grp;
+ char *user; /* user name */
+ char *group; /* group name */
+ char *size; /* max message size */
+ char *cp;
+
+ /*
+ * Initialize.
+ */
+ user = 0;
+ group = 0;
+ attr->command = 0;
+ attr->flags = 0;
+ attr->exec_dir = 0;
+ attr->chroot_dir = 0;
+ attr->eol = vstring_strcpy(vstring_alloc(1), "\n");
+ attr->null_sender = vstring_strcpy(vstring_alloc(1), MAIL_ADDR_MAIL_DAEMON);
+ attr->size_limit = 0;
+
+ /*
+ * Iterate over the command-line attribute list.
+ */
+ for ( /* void */ ; *argv != 0; argv++) {
+
+ /*
+ * flags=stuff
+ */
+ if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) {
+ for (cp = *argv + sizeof("flags=") - 1; *cp; cp++) {
+ switch (*cp) {
+ case 'B':
+ attr->flags |= MAIL_COPY_BLANK;
+ break;
+ case 'D':
+ attr->flags |= MAIL_COPY_DELIVERED;
+ break;
+ case 'F':
+ attr->flags |= MAIL_COPY_FROM;
+ break;
+ case 'O':
+ attr->flags |= MAIL_COPY_ORIG_RCPT;
+ break;
+ case 'R':
+ attr->flags |= MAIL_COPY_RETURN_PATH;
+ break;
+ case 'X':
+ attr->flags |= PIPE_OPT_FINAL_DELIVERY;
+ break;
+ case '.':
+ attr->flags |= MAIL_COPY_DOT;
+ break;
+ case '>':
+ attr->flags |= MAIL_COPY_QUOTE;
+ break;
+ case 'h':
+ attr->flags |= PIPE_OPT_FOLD_HOST;
+ break;
+ case 'q':
+ attr->flags |= PIPE_OPT_QUOTE_LOCAL;
+ break;
+ case 'u':
+ attr->flags |= PIPE_OPT_FOLD_USER;
+ break;
+ default:
+ msg_fatal("unknown flag: %c (ignored)", *cp);
+ break;
+ }
+ }
+ }
+
+ /*
+ * user=username[:groupname]
+ */
+ else if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
+ user = *argv + sizeof("user=") - 1;
+ if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
+ if (*group == 0)
+ group = 0;
+ if ((pwd = getpwnam(user)) == 0)
+ msg_fatal("%s: unknown username: %s", myname, user);
+ attr->uid = pwd->pw_uid;
+ if (group != 0) {
+ if ((grp = getgrnam(group)) == 0)
+ msg_fatal("%s: unknown group: %s", myname, group);
+ attr->gid = grp->gr_gid;
+ } else {
+ attr->gid = pwd->pw_gid;
+ }
+ }
+
+ /*
+ * directory=string
+ */
+ else if (strncasecmp("directory=", *argv, sizeof("directory=") - 1) == 0) {
+ attr->exec_dir = mystrdup(*argv + sizeof("directory=") - 1);
+ }
+
+ /*
+ * chroot=string
+ */
+ else if (strncasecmp("chroot=", *argv, sizeof("chroot=") - 1) == 0) {
+ attr->chroot_dir = mystrdup(*argv + sizeof("chroot=") - 1);
+ }
+
+ /*
+ * eol=string
+ */
+ else if (strncasecmp("eol=", *argv, sizeof("eol=") - 1) == 0) {
+ unescape(attr->eol, *argv + sizeof("eol=") - 1);
+ }
+
+ /*
+ * null_sender=string
+ */
+ else if (strncasecmp("null_sender=", *argv, sizeof("null_sender=") - 1) == 0) {
+ vstring_strcpy(attr->null_sender, *argv + sizeof("null_sender=") - 1);
+ }
+
+ /*
+ * size=max_message_size (in bytes)
+ */
+ else if (strncasecmp("size=", *argv, sizeof("size=") - 1) == 0) {
+ size = *argv + sizeof("size=") - 1;
+ if ((attr->size_limit = off_cvt_string(size)) < 0)
+ msg_fatal("%s: bad size= value: %s", myname, size);
+ }
+
+ /*
+ * argv=command...
+ */
+ else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
+ *argv += sizeof("argv=") - 1; /* XXX clobbers argv */
+ attr->command = argv;
+ break;
+ }
+
+ /*
+ * Bad.
+ */
+ else
+ msg_fatal("unknown attribute name: %s", *argv);
+ }
+
+ /*
+ * Sanity checks. Verify that every member has an acceptable value.
+ */
+ if (user == 0)
+ msg_fatal("missing user= command-line attribute");
+ if (attr->command == 0)
+ msg_fatal("missing argv= command-line attribute");
+ if (attr->uid == 0)
+ msg_fatal("user= command-line attribute specifies root privileges");
+ if (attr->uid == var_owner_uid)
+ msg_fatal("user= command-line attribute specifies mail system owner %s",
+ var_mail_owner);
+ if (attr->gid == 0)
+ msg_fatal("user= command-line attribute specifies privileged group id 0");
+ if (attr->gid == var_owner_gid)
+ msg_fatal("user= command-line attribute specifies mail system owner %s group id %ld",
+ var_mail_owner, (long) attr->gid);
+ if (attr->gid == var_sgid_gid)
+ msg_fatal("user= command-line attribute specifies mail system %s group id %ld",
+ var_sgid_group, (long) attr->gid);
+
+ /*
+ * Give the poor tester a clue of what is going on.
+ */
+ if (msg_verbose)
+ msg_info("%s: uid %ld, gid %ld, flags %d, size %ld",
+ myname, (long) attr->uid, (long) attr->gid,
+ attr->flags, (long) attr->size_limit);
+}
+
+/* eval_command_status - do something with command completion status */
+
+static int eval_command_status(int command_status, char *service,
+ DELIVER_REQUEST *request, PIPE_ATTR *attr,
+ DSN_BUF *why)
+{
+ RECIPIENT *rcpt;
+ int status;
+ int result = 0;
+ int n;
+ char *saved_text;
+
+ /*
+ * Depending on the result, bounce or defer the message, and mark the
+ * recipient as done where appropriate.
+ */
+ switch (command_status) {
+ case PIPE_STAT_OK:
+ /* Save the command output before dsb_update() clobbers it. */
+ vstring_truncate(why->reason, trimblanks(STR(why->reason),
+ VSTRING_LEN(why->reason)) - STR(why->reason));
+ if (VSTRING_LEN(why->reason) > 0) {
+ VSTRING_TERMINATE(why->reason);
+ saved_text =
+ vstring_export(vstring_sprintf(
+ vstring_alloc(VSTRING_LEN(why->reason)),
+ " (%.100s)", STR(why->reason)));
+ } else
+ saved_text = mystrdup(""); /* uses shared R/O storage */
+ dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ?
+ "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "delivered via %s service%s", service, saved_text);
+ myfree(saved_text);
+ (void) DSN_FROM_DSN_BUF(why);
+ for (n = 0; n < request->rcpt_list.len; n++) {
+ rcpt = request->rcpt_list.info + n;
+ status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats, rcpt,
+ service, &why->dsn);
+ if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
+ deliver_completed(request->fp, rcpt->offset);
+ result |= status;
+ }
+ break;
+ case PIPE_STAT_BOUNCE:
+ case PIPE_STAT_DEFER:
+ (void) DSN_FROM_DSN_BUF(why);
+ for (n = 0; n < request->rcpt_list.len; n++) {
+ rcpt = request->rcpt_list.info + n;
+ /* XXX Maybe encapsulate this with ndr_append(). */
+ status = (STR(why->status)[0] != '4' ?
+ bounce_append : defer_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id,
+ &request->msg_stats, rcpt,
+ service, &why->dsn);
+ if (status == 0)
+ deliver_completed(request->fp, rcpt->offset);
+ result |= status;
+ }
+ break;
+ case PIPE_STAT_CORRUPT:
+ /* XXX DSN should we send something? */
+ result |= DEL_STAT_DEFER;
+ break;
+ default:
+ msg_panic("eval_command_status: bad status %d", command_status);
+ /* NOTREACHED */
+ }
+
+ return (result);
+}
+
+/* deliver_message - deliver message with extreme prejudice */
+
+static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv)
+{
+ const char *myname = "deliver_message";
+ static PIPE_PARAMS conf;
+ static PIPE_ATTR attr;
+ RECIPIENT_LIST *rcpt_list = &request->rcpt_list;
+ DSN_BUF *why = dsb_create();
+ VSTRING *buf;
+ ARGV *expanded_argv = 0;
+ int deliver_status;
+ int command_status;
+ ARGV *export_env;
+ const char *sender;
+
+#define DELIVER_MSG_CLEANUP() { \
+ dsb_free(why); \
+ if (expanded_argv) argv_free(expanded_argv); \
+ }
+
+ if (msg_verbose)
+ msg_info("%s: from <%s>", myname, request->sender);
+
+ /*
+ * Sanity checks. The get_service_params() and get_service_attr()
+ * routines also do some sanity checks. Look up service attributes and
+ * config information only once. This is safe since the information comes
+ * from a trusted source, not from the delivery request.
+ */
+ if (request->nexthop[0] == 0)
+ msg_fatal("empty nexthop hostname");
+ if (rcpt_list->len <= 0)
+ msg_fatal("recipient count: %d", rcpt_list->len);
+ if (attr.command == 0) {
+ get_service_params(&conf, service);
+ get_service_attr(&attr, argv);
+ }
+
+ /*
+ * The D flag cannot be specified for multi-recipient deliveries.
+ */
+ if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) {
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
+ request, &attr, why);
+ msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1",
+ service);
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+
+ /*
+ * The O flag cannot be specified for multi-recipient deliveries.
+ */
+ if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) {
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
+ request, &attr, why);
+ msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1",
+ service);
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+
+ /*
+ * Check that this agent accepts messages this large.
+ */
+ if (attr.size_limit != 0 && request->data_size > attr.size_limit) {
+ if (msg_verbose)
+ msg_info("%s: too big: size_limit = %ld, request->data_size = %ld",
+ myname, (long) attr.size_limit, request->data_size);
+ dsb_simple(why, "5.2.3", "message too large");
+ deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service,
+ request, &attr, why);
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+
+ /*
+ * Don't deliver a trace-only request.
+ */
+ if (DEL_REQ_TRACE_ONLY(request->flags)) {
+ RECIPIENT *rcpt;
+ int status;
+ int n;
+
+ deliver_status = 0;
+ dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]);
+ (void) DSN_FROM_DSN_BUF(why);
+ for (n = 0; n < request->rcpt_list.len; n++) {
+ rcpt = request->rcpt_list.info + n;
+ status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats,
+ rcpt, service, &why->dsn);
+ if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
+ deliver_completed(request->fp, rcpt->offset);
+ deliver_status |= status;
+ }
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+
+ /*
+ * Report mail delivery loops. By definition, this requires
+ * single-recipient delivery. Don't silently lose recipients.
+ */
+ if (attr.flags & MAIL_COPY_DELIVERED) {
+ DELIVERED_HDR_INFO *info;
+ RECIPIENT *rcpt;
+ int loop_found;
+
+ if (request->rcpt_list.len > 1)
+ msg_panic("%s: delivered-to enabled with multi-recipient request",
+ myname);
+ info = delivered_hdr_init(request->fp, request->data_offset,
+ FOLD_ADDR_ALL);
+ rcpt = request->rcpt_list.info;
+ loop_found = delivered_hdr_find(info, rcpt->address);
+ delivered_hdr_free(info);
+ if (loop_found) {
+ dsb_simple(why, "5.4.6", "mail forwarding loop for %s",
+ rcpt->address);
+ deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service,
+ request, &attr, why);
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+ }
+
+ /*
+ * Deliver. Set the nexthop and sender variables, and expand the command
+ * argument vector. Recipients will be expanded on the fly. XXX Rewrite
+ * envelope and header addresses according to transport-specific
+ * rewriting rules.
+ */
+ if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0)
+ msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp));
+
+ /*
+ * A non-empty null sender replacement is subject to the 'q' flag.
+ */
+ buf = vstring_alloc(10);
+ sender = *request->sender ? request->sender : STR(attr.null_sender);
+ if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) {
+ quote_822_local(buf, sender);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf));
+ } else
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender);
+ if (attr.flags & PIPE_OPT_FOLD_HOST) {
+ casefold(buf, request->nexthop);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf));
+ } else
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop);
+ vstring_sprintf(buf, "%ld", (long) request->data_size);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf));
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR,
+ request->client_addr);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO,
+ request->client_helo);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME,
+ request->client_name);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT,
+ request->client_port);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO,
+ request->client_proto);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD,
+ request->sasl_method);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME,
+ request->sasl_username);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER,
+ request->sasl_sender);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID,
+ request->queue_id);
+ vstring_free(buf);
+
+ if ((expanded_argv = expand_argv(service, attr.command,
+ rcpt_list, attr.flags)) == 0) {
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
+ request, &attr, why);
+ DELIVER_MSG_CLEANUP();
+ return (deliver_status);
+ }
+ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
+
+ command_status = pipe_command(request->fp, why,
+ CA_PIPE_CMD_UID(attr.uid),
+ CA_PIPE_CMD_GID(attr.gid),
+ CA_PIPE_CMD_SENDER(sender),
+ CA_PIPE_CMD_COPY_FLAGS(attr.flags),
+ CA_PIPE_CMD_ARGV(expanded_argv->argv),
+ CA_PIPE_CMD_TIME_LIMIT(conf.time_limit),
+ CA_PIPE_CMD_EOL(STR(attr.eol)),
+ CA_PIPE_CMD_EXPORT(export_env->argv),
+ CA_PIPE_CMD_CWD(attr.exec_dir),
+ CA_PIPE_CMD_CHROOT(attr.chroot_dir),
+ CA_PIPE_CMD_ORIG_RCPT(rcpt_list->info[0].orig_addr),
+ CA_PIPE_CMD_DELIVERED(rcpt_list->info[0].address),
+ CA_PIPE_CMD_END);
+ argv_free(export_env);
+
+ deliver_status = eval_command_status(command_status, service, request,
+ &attr, why);
+
+ /*
+ * Clean up.
+ */
+ DELIVER_MSG_CLEANUP();
+
+ return (deliver_status);
+}
+
+/* pipe_service - perform service for client */
+
+static void pipe_service(VSTREAM *client_stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to delivery via external command. What we see below is a
+ * little protocol to (1) tell the queue manager that we are ready, (2)
+ * read a request from the queue manager, and (3) report the completion
+ * status of that request. All connection-management stuff is handled by
+ * the common code in single_server.c.
+ */
+ if ((request = deliver_request_read(client_stream)) != 0) {
+ status = deliver_message(request, service, argv);
+ deliver_request_done(client_stream, request, status);
+ }
+}
+
+/* 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);
+ }
+}
+
+/* drop_privileges - drop privileges most of the time */
+
+static void drop_privileges(char *unused_name, char **unused_argv)
+{
+ set_eugid(var_owner_uid, var_owner_gid);
+}
+
+/* pre_init - initialize */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+ flush_init();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_PIPE_DSN_FILTER, DEF_PIPE_DSN_FILTER, &var_pipe_dsn_filter, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, pipe_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ CA_MAIL_SERVER_POST_INIT(drop_privileges),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_PRIVILEGED,
+ CA_MAIL_SERVER_BOUNCE_INIT(VAR_PIPE_DSN_FILTER,
+ &var_pipe_dsn_filter),
+ 0);
+}
diff --git a/src/postalias/.indent.pro b/src/postalias/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postalias/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postalias/.printfck b/src/postalias/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postalias/.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/postalias/Makefile.in b/src/postalias/Makefile.in
new file mode 100644
index 0000000..5e5b72d
--- /dev/null
+++ b/src/postalias/Makefile.in
@@ -0,0 +1,121 @@
+SHELL = /bin/sh
+SRCS = postalias.c
+OBJS = postalias.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postalias
+INC_DIR = ../../include
+LIBS = ../../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 $? >$@
+
+update: ../../bin/$(PROG)
+
+tests: test1 test2 fail_test
+
+root_tests:
+
+test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) map.in
+ for key in abc ghi; \
+ do \
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \
+ done
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -f map.in
+ for key in ABC; \
+ do \
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \
+ done
+ rm -f map.in.db
+
+test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) map.in
+ for key in abc ghi; \
+ do \
+ echo $${key} | ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \
+ done
+ ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -f map.in
+ for key in ABC; \
+ do \
+ echo $${key} | ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \
+ done
+ rm -f map.in.db
+
+fail_test: $(PROG) aliases fail_test.in fail_test.ref
+ -(${SHLIB_ENV} sh fail_test.in 2>&1 || exit 0) | sed \
+ -e 's/No error:/Unknown error:/' \
+ -e 's/Success/Unknown error: 0/' > fail_test.tmp
+ diff fail_test.ref fail_test.tmp
+ rm -f fail_test.tmp
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postalias.o: ../../include/argv.h
+postalias.o: ../../include/check_arg.h
+postalias.o: ../../include/clean_env.h
+postalias.o: ../../include/dict.h
+postalias.o: ../../include/dict_proxy.h
+postalias.o: ../../include/mail_conf.h
+postalias.o: ../../include/mail_dict.h
+postalias.o: ../../include/mail_params.h
+postalias.o: ../../include/mail_parm_split.h
+postalias.o: ../../include/mail_task.h
+postalias.o: ../../include/mail_version.h
+postalias.o: ../../include/maillog_client.h
+postalias.o: ../../include/mkmap.h
+postalias.o: ../../include/msg.h
+postalias.o: ../../include/msg_vstream.h
+postalias.o: ../../include/myflock.h
+postalias.o: ../../include/mymalloc.h
+postalias.o: ../../include/readlline.h
+postalias.o: ../../include/resolve_clnt.h
+postalias.o: ../../include/set_eugid.h
+postalias.o: ../../include/split_at.h
+postalias.o: ../../include/stringops.h
+postalias.o: ../../include/sys_defs.h
+postalias.o: ../../include/tok822.h
+postalias.o: ../../include/vbuf.h
+postalias.o: ../../include/vstream.h
+postalias.o: ../../include/vstring.h
+postalias.o: ../../include/vstring_vstream.h
+postalias.o: ../../include/warn_stat.h
+postalias.o: postalias.c
diff --git a/src/postalias/aliases b/src/postalias/aliases
new file mode 100644
index 0000000..a2a278d
--- /dev/null
+++ b/src/postalias/aliases
@@ -0,0 +1 @@
+xx: yy
diff --git a/src/postalias/fail_test.in b/src/postalias/fail_test.in
new file mode 100644
index 0000000..4a68f6e
--- /dev/null
+++ b/src/postalias/fail_test.in
@@ -0,0 +1,7 @@
+${VALGRIND} ./postalias -q xx fail:aliases
+echo xx | ${VALGRIND} ./postalias -q - fail:aliases
+${VALGRIND} ./postalias -d xx fail:aliases
+echo xx | ${VALGRIND} ./postalias -d - fail:aliases
+${VALGRIND} ./postalias -s fail:aliases
+${VALGRIND} ./postalias -i fail:aliases < aliases
+${VALGRIND} ./postalias fail:aliases
diff --git a/src/postalias/fail_test.ref b/src/postalias/fail_test.ref
new file mode 100644
index 0000000..9cb6024
--- /dev/null
+++ b/src/postalias/fail_test.ref
@@ -0,0 +1,7 @@
+postalias: fatal: table fail:aliases: query error: Application error
+postalias: fatal: table fail:aliases: query error: Application error
+postalias: fatal: table fail:aliases: delete error: Application error
+postalias: fatal: table fail:aliases: delete error: Application error
+postalias: fatal: table fail:aliases: sequence error: Application error
+postalias: fatal: table fail:aliases: write error: Application error
+postalias: fatal: table fail:aliases: write error: Application error
diff --git a/src/postalias/map-abc1.ref b/src/postalias/map-abc1.ref
new file mode 100644
index 0000000..142195f
--- /dev/null
+++ b/src/postalias/map-abc1.ref
@@ -0,0 +1 @@
+DEF
diff --git a/src/postalias/map-abc2.ref b/src/postalias/map-abc2.ref
new file mode 100644
index 0000000..dfbdab6
--- /dev/null
+++ b/src/postalias/map-abc2.ref
@@ -0,0 +1 @@
+abc: DEF
diff --git a/src/postalias/map-ghi1.ref b/src/postalias/map-ghi1.ref
new file mode 100644
index 0000000..7beb1db
--- /dev/null
+++ b/src/postalias/map-ghi1.ref
@@ -0,0 +1 @@
+jkl
diff --git a/src/postalias/map-ghi2.ref b/src/postalias/map-ghi2.ref
new file mode 100644
index 0000000..e2ca310
--- /dev/null
+++ b/src/postalias/map-ghi2.ref
@@ -0,0 +1 @@
+ghi: jkl
diff --git a/src/postalias/map-uABC1.ref b/src/postalias/map-uABC1.ref
new file mode 100644
index 0000000..142195f
--- /dev/null
+++ b/src/postalias/map-uABC1.ref
@@ -0,0 +1 @@
+DEF
diff --git a/src/postalias/map-uABC2.ref b/src/postalias/map-uABC2.ref
new file mode 100644
index 0000000..929916b
--- /dev/null
+++ b/src/postalias/map-uABC2.ref
@@ -0,0 +1 @@
+ABC: DEF
diff --git a/src/postalias/map.in b/src/postalias/map.in
new file mode 100644
index 0000000..203fa0c
--- /dev/null
+++ b/src/postalias/map.in
@@ -0,0 +1,2 @@
+ABC: DEF
+ghi: jkl
diff --git a/src/postalias/postalias.c b/src/postalias/postalias.c
new file mode 100644
index 0000000..9538883
--- /dev/null
+++ b/src/postalias/postalias.c
@@ -0,0 +1,906 @@
+/*++
+/* NAME
+/* postalias 1
+/* SUMMARY
+/* Postfix alias database maintenance
+/* SYNOPSIS
+/* .fi
+/* \fBpostalias\fR [\fB-Nfinoprsuvw\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR]
+/* [\fIfile_type\fR:]\fIfile_name\fR ...
+/* DESCRIPTION
+/* The \fBpostalias\fR(1) command creates or queries one or more Postfix
+/* alias databases, or updates an existing one. The input and output
+/* file formats are expected to be compatible with Sendmail version 8,
+/* and are expected to be suitable for use as NIS alias maps.
+/*
+/* If the result files do not exist they will be created with the
+/* same group and other read permissions as their source file.
+/*
+/* While a database update is in progress, signal delivery is
+/* postponed, and an exclusive, advisory, lock is placed on the
+/* entire database, in order to avoid surprises in spectator
+/* processes.
+/*
+/* The format of Postfix alias input files is described in
+/* \fBaliases\fR(5).
+/*
+/* By default the lookup key is mapped to lowercase to make
+/* the lookups case insensitive; as of Postfix 2.3 this case
+/* folding happens only with tables whose lookup keys are
+/* fixed-case strings such as btree:, dbm: or hash:. With
+/* earlier versions, the lookup key is folded even with tables
+/* where a lookup field can match both upper and lower case
+/* text, such as regexp: and pcre:. This resulted in loss of
+/* information with $\fInumber\fR substitutions.
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR configuration file in the named directory
+/* instead of the default configuration directory.
+/* .IP "\fB-d \fIkey\fR"
+/* Search the specified maps for \fIkey\fR and remove one entry per map.
+/* The exit status is zero when the requested information was found.
+/*
+/* If a key value of \fB-\fR is specified, the program reads key
+/* values from the standard input stream. The exit status is zero
+/* when at least one of the requested keys was found.
+/* .IP \fB-f\fR
+/* Do not fold the lookup key to lower case while creating or querying
+/* a table.
+/*
+/* With Postfix version 2.3 and later, this option has no
+/* effect for regular expression tables. There, case folding
+/* is controlled by appending a flag to a pattern.
+/* .IP \fB-i\fR
+/* Incremental mode. Read entries from standard input and do not
+/* truncate an existing database. By default, \fBpostalias\fR(1) creates
+/* a new database from the entries in \fIfile_name\fR.
+/* .IP \fB-N\fR
+/* Include the terminating null character that terminates lookup keys
+/* and values. By default, \fBpostalias\fR(1) does whatever
+/* is the default for
+/* the host operating system.
+/* .IP \fB-n\fR
+/* Don't include the terminating null character that terminates lookup
+/* keys and values. By default, \fBpostalias\fR(1) does whatever
+/* is the default for
+/* the host operating system.
+/* .IP \fB-o\fR
+/* Do not release root privileges when processing a non-root
+/* input file. By default, \fBpostalias\fR(1) drops root privileges
+/* and runs as the source file owner instead.
+/* .IP \fB-p\fR
+/* Do not inherit the file access permissions from the input file
+/* when creating a new file. Instead, create a new file with default
+/* access permissions (mode 0644).
+/* .IP "\fB-q \fIkey\fR"
+/* Search the specified maps for \fIkey\fR and write the first value
+/* found to the standard output stream. The exit status is zero
+/* when the requested information was found.
+/*
+/* Note: this performs a single query with the key as specified,
+/* and does not make iterative queries with substrings of the
+/* key as described in the aliases(5) manual page.
+/*
+/* If a key value of \fB-\fR is specified, the program reads key
+/* values from the standard input stream and writes one line of
+/* \fIkey: value\fR output for each key that was found. The exit
+/* status is zero when at least one of the requested keys was found.
+/* .IP \fB-r\fR
+/* When updating a table, do not complain about attempts to update
+/* existing entries, and make those updates anyway.
+/* .IP \fB-s\fR
+/* Retrieve all database elements, and write one line of
+/* \fIkey: value\fR output for each element. The elements are
+/* printed in database order, which is not necessarily the same
+/* as the original input order.
+/* This feature is available in Postfix version 2.2 and later,
+/* and is not available for all database types.
+/* .IP \fB-u\fR
+/* Disable UTF-8 support. UTF-8 support is enabled by default
+/* when "smtputf8_enable = yes". It requires that keys and
+/* values are valid UTF-8 strings.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* .IP \fB-w\fR
+/* When updating a table, do not complain about attempts to update
+/* existing entries, and ignore those attempts.
+/* .PP
+/* Arguments:
+/* .IP \fIfile_type\fR
+/* The database type. To find out what types are supported, use
+/* the "\fBpostconf -m\fR" command.
+/*
+/* The \fBpostalias\fR(1) command can query any supported file type,
+/* but it can create only the following file types:
+/* .RS
+/* .IP \fBbtree\fR
+/* The output is a btree file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBcdb\fR
+/* The output is one file named \fIfile_name\fB.cdb\fR.
+/* This is available on systems with support for \fBcdb\fR databases.
+/* .IP \fBdbm\fR
+/* The output consists of two files, named \fIfile_name\fB.pag\fR and
+/* \fIfile_name\fB.dir\fR.
+/* This is available on systems with support for \fBdbm\fR databases.
+/* .IP \fBfail\fR
+/* A table that reliably fails all requests. The lookup table
+/* name is used for logging only. This table exists to simplify
+/* Postfix error tests.
+/* .IP \fBhash\fR
+/* The output is a hashed file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBlmdb\fR
+/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR.
+/* \fBlmdb\fR supports concurrent writes and reads from different
+/* processes, unlike other supported file-based tables.
+/* This is available on systems with support for \fBlmdb\fR databases.
+/* .IP \fBsdbm\fR
+/* The output consists of two files, named \fIfile_name\fB.pag\fR and
+/* \fIfile_name\fB.dir\fR.
+/* This is available on systems with support for \fBsdbm\fR databases.
+/* .PP
+/* When no \fIfile_type\fR is specified, the software uses the database
+/* type specified via the \fBdefault_database_type\fR configuration
+/* parameter.
+/* The default value for this parameter depends on the host environment.
+/* .RE
+/* .IP \fIfile_name\fR
+/* The name of the alias database source file when creating a database.
+/* DIAGNOSTICS
+/* Problems are logged to the standard error stream and to
+/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8). No output means that
+/* no problems were detected. Duplicate entries are skipped and are
+/* flagged with a warning.
+/*
+/* \fBpostalias\fR(1) terminates with zero exit status in case of success
+/* (including successful "\fBpostalias -q\fR" lookup) and terminates
+/* with non-zero exit status in case of failure.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* .IP \fBMAIL_VERBOSE\fR
+/* Enable verbose logging for debugging purposes.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBalias_database (see 'postconf -d' output)\fR"
+/* The alias databases for \fBlocal\fR(8) delivery that are updated with
+/* "\fBnewaliases\fR" or with "\fBsendmail -bi\fR".
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR"
+/* The per-table I/O buffer size for programs that create Berkeley DB
+/* hash or btree tables.
+/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR"
+/* The per-table I/O buffer size for programs that read Berkeley DB
+/* hash or btree tables.
+/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR"
+/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1)
+/* and \fBpostmap\fR(1) commands.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBsmtputf8_enable (yes)\fR"
+/* Enable preliminary SMTPUTF8 support for the protocols described
+/* in RFC 6531, RFC 6532, and RFC 6533.
+/* .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 2.11 and later:
+/* .IP "\fBlmdb_map_size (16777216)\fR"
+/* The initial OpenLDAP LMDB database size limit in bytes.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* SEE ALSO
+/* aliases(5), format of alias database input file.
+/* local(8), Postfix local delivery agent.
+/* postconf(1), supported database types
+/* postconf(5), configuration parameters
+/* postmap(1), create/update/query lookup tables
+/* newaliases(1), Sendmail compatibility interface.
+/* 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
+/* DATABASE_README, Postfix lookup table overview
+/* 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/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <vstring_vstream.h>
+#include <set_eugid.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+
+/* Global library. */
+
+#include <tok822.h>
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mkmap.h>
+#include <mail_task.h>
+#include <dict_proxy.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+#define POSTALIAS_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */
+#define POSTALIAS_FLAG_SAVE_PERM (1<<1) /* copy access permission
+ * from source */
+
+/* postalias - create or update alias database */
+
+static void postalias(char *map_type, char *path_name, int postalias_flags,
+ int open_flags, int dict_flags)
+{
+ VSTREAM *NOCLOBBER source_fp;
+ VSTRING *line_buffer;
+ MKMAP *mkmap;
+ int lineno;
+ int last_line;
+ VSTRING *key_buffer;
+ VSTRING *value_buffer;
+ TOK822 *tok_list;
+ TOK822 *key_list;
+ TOK822 *colon;
+ TOK822 *value_list;
+ struct stat st;
+ mode_t saved_mask;
+
+ /*
+ * Initialize.
+ */
+ line_buffer = vstring_alloc(100);
+ key_buffer = vstring_alloc(100);
+ value_buffer = vstring_alloc(100);
+ if ((open_flags & O_TRUNC) == 0) {
+ /* Incremental mode. */
+ source_fp = VSTREAM_IN;
+ vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END);
+ } else {
+ /* Create database. */
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ msg_fatal("can't create maps via the proxy service");
+ dict_flags |= DICT_FLAG_BULK_UPDATE;
+ if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", path_name);
+ }
+ if (fstat(vstream_fileno(source_fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path_name);
+
+ /*
+ * Turn off group/other read permissions as indicated in the source file.
+ */
+ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
+ saved_mask = umask(022 | (~st.st_mode & 077));
+
+ /*
+ * If running as root, run as the owner of the source file, so that the
+ * result shows proper ownership, and so that a bug in postalias does not
+ * allow privilege escalation.
+ */
+ if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0
+ && (st.st_uid != geteuid() || st.st_gid != getegid()))
+ set_eugid(st.st_uid, st.st_gid);
+
+ /*
+ * Open the database, create it when it does not exist, truncate it when
+ * it does exist, and lock out any spectators.
+ */
+ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags);
+
+ /*
+ * And restore the umask, in case it matters.
+ */
+ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
+ umask(saved_mask);
+
+ /*
+ * Trap "exceptions" so that we can restart a bulk-mode update after a
+ * recoverable error.
+ */
+ for (;;) {
+ if (dict_isjmp(mkmap->dict) != 0
+ && dict_setjmp(mkmap->dict) != 0
+ && vstream_fseek(source_fp, SEEK_SET, 0) < 0)
+ msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp));
+
+ /*
+ * Add records to the database.
+ */
+ last_line = 0;
+ while (readllines(line_buffer, source_fp, &last_line, &lineno)) {
+
+ /*
+ * First some UTF-8 checks sans casefolding.
+ */
+ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
+ && !allascii(STR(line_buffer))
+ && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
+ msg_warn("%s, line %d: non-UTF-8 input \"%s\""
+ " -- ignoring this line",
+ VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
+ continue;
+ }
+
+ /*
+ * Tokenize the input, so that we do the right thing when a
+ * quoted localpart contains special characters such as "@", ":"
+ * and so on.
+ */
+ if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0)
+ continue;
+
+ /*
+ * Enforce the key:value format. Disallow missing keys,
+ * multi-address keys, or missing values. In order to specify an
+ * empty string or value, enclose it in double quotes.
+ */
+ if ((colon = tok822_find_type(tok_list, ':')) == 0
+ || colon->prev == 0 || colon->next == 0
+ || tok822_rfind_type(colon, ',')) {
+ msg_warn("%s, line %d: need name:value pair",
+ VSTREAM_PATH(source_fp), lineno);
+ tok822_free_tree(tok_list);
+ continue;
+ }
+
+ /*
+ * Key must be local. XXX We should use the Postfix rewriting and
+ * resolving services to handle all address forms correctly.
+ * However, we can't count on the mail system being up when the
+ * alias database is being built, so we're guessing a bit.
+ */
+ if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) {
+ msg_warn("%s, line %d: name must be local",
+ VSTREAM_PATH(source_fp), lineno);
+ tok822_free_tree(tok_list);
+ continue;
+ }
+
+ /*
+ * Split the input into key and value parts, and convert from
+ * token representation back to string representation. Convert
+ * the key to internal (unquoted) form, because the resolver
+ * produces addresses in internal form. Convert the value to
+ * external (quoted) form, because it will have to be re-parsed
+ * upon lookup. Discard the token representation when done.
+ */
+ key_list = tok_list;
+ tok_list = 0;
+ value_list = tok822_cut_after(colon);
+ tok822_unlink(colon);
+ tok822_free(colon);
+
+ tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL);
+ tok822_free_tree(key_list);
+
+ tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL);
+ tok822_free_tree(value_list);
+
+ /*
+ * Store the value under a case-insensitive key.
+ */
+ mkmap_append(mkmap, STR(key_buffer), STR(value_buffer));
+ if (mkmap->dict->error)
+ msg_fatal("table %s:%s: write error: %m",
+ mkmap->dict->type, mkmap->dict->name);
+ }
+ break;
+ }
+
+ /*
+ * Update or append sendmail and NIS signatures.
+ */
+ if ((open_flags & O_TRUNC) == 0)
+ mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE;
+
+ /*
+ * Sendmail compatibility: add the @:@ signature to indicate that the
+ * database is complete. This might be needed by NIS clients running
+ * sendmail.
+ */
+ mkmap_append(mkmap, "@", "@");
+ if (mkmap->dict->error)
+ msg_fatal("table %s:%s: write error: %m",
+ mkmap->dict->type, mkmap->dict->name);
+
+ /*
+ * NIS compatibility: add time and master info. Unlike other information,
+ * this information MUST be written without a trailing null appended to
+ * key or value.
+ */
+ mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL;
+ mkmap->dict->flags |= DICT_FLAG_TRY0NULL;
+ vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0));
+#if (defined(HAS_NIS) || defined(HAS_NISPLUS))
+ mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX;
+ mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer));
+ mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname);
+#endif
+
+ /*
+ * Close the alias database, and release the lock.
+ */
+ mkmap_close(mkmap);
+
+ /*
+ * Cleanup. We're about to terminate, but it is a good sanity check.
+ */
+ vstring_free(value_buffer);
+ vstring_free(key_buffer);
+ vstring_free(line_buffer);
+ if (source_fp != VSTREAM_IN)
+ vstream_fclose(source_fp);
+}
+
+/* postalias_queries - apply multiple requests from stdin */
+
+static int postalias_queries(VSTREAM *in, char **maps, const int map_count,
+ const int dict_flags)
+{
+ int found = 0;
+ VSTRING *keybuf = vstring_alloc(100);
+ DICT **dicts;
+ const char *map_name;
+ const char *value;
+ int n;
+
+ /*
+ * Sanity check.
+ */
+ if (map_count <= 0)
+ msg_panic("postalias_queries: bad map count");
+
+ /*
+ * Prepare to open maps lazily.
+ */
+ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
+ for (n = 0; n < map_count; n++)
+ dicts[n] = 0;
+
+ /*
+ * Perform all queries. Open maps on the fly, to avoid opening
+ * unnecessary maps.
+ */
+ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) {
+ for (n = 0; n < map_count; n++) {
+ if (dicts[n] == 0)
+ dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ?
+ dict_open3(maps[n], map_name, O_RDONLY, dict_flags) :
+ dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags));
+ if ((value = dict_get(dicts[n], STR(keybuf))) != 0) {
+ if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ dicts[n]->type, dicts[n]->name, STR(keybuf));
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ dicts[n]->type, dicts[n]->name);
+ }
+ vstream_printf("%s: %s\n", STR(keybuf), value);
+ found = 1;
+ break;
+ }
+ if (dicts[n]->error)
+ msg_fatal("table %s:%s: query error: %m",
+ dicts[n]->type, dicts[n]->name);
+ }
+ }
+ if (found)
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Cleanup.
+ */
+ for (n = 0; n < map_count; n++)
+ if (dicts[n])
+ dict_close(dicts[n]);
+ myfree((void *) dicts);
+ vstring_free(keybuf);
+
+ return (found);
+}
+
+/* postalias_query - query a map and print the result to stdout */
+
+static int postalias_query(const char *map_type, const char *map_name,
+ const char *key, int dict_flags)
+{
+ DICT *dict;
+ const char *value;
+
+ dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
+ if ((value = dict_get(dict, key)) != 0) {
+ if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ map_type, map_name, key);
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ map_type, map_name);
+ }
+ vstream_printf("%s\n", value);
+ }
+ if (dict->error)
+ msg_fatal("table %s:%s: query error: %m", dict->type, dict->name);
+ vstream_fflush(VSTREAM_OUT);
+ dict_close(dict);
+ return (value != 0);
+}
+
+/* postalias_deletes - apply multiple requests from stdin */
+
+static int postalias_deletes(VSTREAM *in, char **maps, const int map_count,
+ int dict_flags)
+{
+ int found = 0;
+ VSTRING *keybuf = vstring_alloc(100);
+ DICT **dicts;
+ const char *map_name;
+ int n;
+ int open_flags;
+
+ /*
+ * Sanity check.
+ */
+ if (map_count <= 0)
+ msg_panic("postalias_deletes: bad map count");
+
+ /*
+ * Open maps ahead of time.
+ */
+ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
+ for (n = 0; n < map_count; n++) {
+ map_name = split_at(maps[n], ':');
+ if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0)
+ open_flags = O_RDWR | O_CREAT; /* XXX */
+ else
+ open_flags = O_RDWR;
+ dicts[n] = (map_name != 0 ?
+ dict_open3(maps[n], map_name, open_flags, dict_flags) :
+ dict_open3(var_db_type, maps[n], open_flags, dict_flags));
+ }
+
+ /*
+ * Perform all requests.
+ */
+ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) {
+ for (n = 0; n < map_count; n++) {
+ found |= (dict_del(dicts[n], STR(keybuf)) == 0);
+ if (dicts[n]->error)
+ msg_fatal("table %s:%s: delete error: %m",
+ dicts[n]->type, dicts[n]->name);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ for (n = 0; n < map_count; n++)
+ if (dicts[n])
+ dict_close(dicts[n]);
+ myfree((void *) dicts);
+ vstring_free(keybuf);
+
+ return (found);
+}
+
+/* postalias_delete - delete a key value pair from a map */
+
+static int postalias_delete(const char *map_type, const char *map_name,
+ const char *key, int dict_flags)
+{
+ DICT *dict;
+ int status;
+ int open_flags;
+
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ open_flags = O_RDWR | O_CREAT; /* XXX */
+ else
+ open_flags = O_RDWR;
+ dict = dict_open3(map_type, map_name, open_flags, dict_flags);
+ status = dict_del(dict, key);
+ if (dict->error)
+ msg_fatal("table %s:%s: delete error: %m", dict->type, dict->name);
+ dict_close(dict);
+ return (status == 0);
+}
+
+/* postalias_seq - print all map entries to stdout */
+
+static void postalias_seq(const char *map_type, const char *map_name,
+ int dict_flags)
+{
+ DICT *dict;
+ const char *key;
+ const char *value;
+ int func;
+
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ msg_fatal("can't sequence maps via the proxy service");
+ dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
+ for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) {
+ if (dict_seq(dict, func, &key, &value) != 0)
+ break;
+ if (*key == 0) {
+ msg_warn("table %s:%s: empty lookup key value is not allowed",
+ map_type, map_name);
+ } else if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ map_type, map_name, key);
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ map_type, map_name);
+ }
+ vstream_printf("%s: %s\n", key, value);
+ }
+ if (dict->error)
+ msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name);
+ vstream_fflush(VSTREAM_OUT);
+ dict_close(dict);
+}
+
+/* usage - explain */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-Nfinoprsuvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
+ myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ char *path_name;
+ int ch;
+ int fd;
+ char *slash;
+ struct stat st;
+ int postalias_flags = POSTALIAS_FLAG_AS_OWNER | POSTALIAS_FLAG_SAVE_PERM;
+ int open_flags = O_RDWR | O_CREAT | O_TRUNC;
+ int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ char *query = 0;
+ char *delkey = 0;
+ int sequence = 0;
+ int found;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process environment options as early as we can. We are not set-uid,
+ * and we are supposed to be running in a controlled environment.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Initialize. Set up logging. Read the global configuration file after
+ * parsing command-line arguments.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rsuvw")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ break;
+ case 'N':
+ dict_flags |= DICT_FLAG_TRY1NULL;
+ dict_flags &= ~DICT_FLAG_TRY0NULL;
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'd':
+ if (sequence || query || delkey)
+ msg_fatal("specify only one of -s -q or -d");
+ delkey = optarg;
+ break;
+ case 'f':
+ dict_flags &= ~DICT_FLAG_FOLD_FIX;
+ break;
+ case 'i':
+ open_flags &= ~O_TRUNC;
+ break;
+ case 'n':
+ dict_flags |= DICT_FLAG_TRY0NULL;
+ dict_flags &= ~DICT_FLAG_TRY1NULL;
+ break;
+ case 'o':
+ postalias_flags &= ~POSTALIAS_FLAG_AS_OWNER;
+ break;
+ case 'p':
+ postalias_flags &= ~POSTALIAS_FLAG_SAVE_PERM;
+ break;
+ case 'q':
+ if (sequence || query || delkey)
+ msg_fatal("specify only one of -s -q or -d");
+ query = optarg;
+ break;
+ case 'r':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE);
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ break;
+ case 's':
+ if (query || delkey)
+ msg_fatal("specify only one of -s or -q or -d");
+ sequence = 1;
+ break;
+ case 'u':
+ dict_flags &= ~DICT_FLAG_UTF8_REQUEST;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE);
+ dict_flags |= DICT_FLAG_DUP_IGNORE;
+ break;
+ }
+ }
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE);
+ mail_dict_init();
+
+ /*
+ * Use the map type specified by the user, or fall back to a default
+ * database type.
+ */
+ if (delkey) { /* remove entry */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ if (strcmp(delkey, "-") == 0)
+ exit(postalias_deletes(VSTREAM_IN, argv + optind, argc - optind,
+ dict_flags | DICT_FLAG_LOCK) == 0);
+ found = 0;
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ found |= postalias_delete(argv[optind], path_name, delkey,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ found |= postalias_delete(var_db_type, argv[optind], delkey,
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ optind++;
+ }
+ exit(found ? 0 : 1);
+ } else if (query) { /* query map(s) */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ if (strcmp(query, "-") == 0)
+ exit(postalias_queries(VSTREAM_IN, argv + optind, argc - optind,
+ dict_flags | DICT_FLAG_LOCK) == 0);
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ found = postalias_query(argv[optind], path_name, query,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ found = postalias_query(var_db_type, argv[optind], query,
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ if (found)
+ exit(0);
+ optind++;
+ }
+ exit(1);
+ } else if (sequence) {
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ postalias_seq(argv[optind], path_name,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ postalias_seq(var_db_type, argv[optind],
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ exit(0);
+ }
+ exit(1);
+ } else { /* create/update map(s) */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ postalias(argv[optind], path_name, postalias_flags,
+ open_flags, dict_flags);
+ } else {
+ postalias(var_db_type, argv[optind], postalias_flags,
+ open_flags, dict_flags);
+ }
+ optind++;
+ }
+ exit(0);
+ }
+}
diff --git a/src/postcat/.indent.pro b/src/postcat/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postcat/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postcat/.printfck b/src/postcat/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postcat/.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/postcat/Makefile.in b/src/postcat/Makefile.in
new file mode 100644
index 0000000..06c5eb8
--- /dev/null
+++ b/src/postcat/Makefile.in
@@ -0,0 +1,128 @@
+SHELL = /bin/sh
+SRCS = postcat.c
+OBJS = postcat.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postcat
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests: default_test ebh_test e_test b_test h_test eb_test eh_test bh_test
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+default_test: test-queue-file default_test.ref
+ ./postcat test-queue-file >postcat.tmp 2>&1
+ diff default_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+ebh_test: test-queue-file default_test.ref
+ ./postcat -ebh test-queue-file >postcat.tmp 2>&1
+ diff default_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+e_test: test-queue-file e_test.ref
+ ./postcat -e test-queue-file >postcat.tmp 2>&1
+ diff e_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+b_test: test-queue-file b_test.ref
+ ./postcat -b test-queue-file >postcat.tmp 2>&1
+ diff b_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+h_test: test-queue-file h_test.ref
+ ./postcat -h test-queue-file >postcat.tmp 2>&1
+ diff h_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+eb_test: test-queue-file eb_test.ref
+ ./postcat -eb test-queue-file >postcat.tmp 2>&1
+ diff eb_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+eh_test: test-queue-file eh_test.ref
+ ./postcat -eh test-queue-file >postcat.tmp 2>&1
+ diff eh_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+bh_test: test-queue-file bh_test.ref
+ ./postcat -bh test-queue-file >postcat.tmp 2>&1
+ diff bh_test.ref postcat.tmp
+ rm -f postcat.tmp
+
+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'
+postcat.o: ../../include/argv.h
+postcat.o: ../../include/attr.h
+postcat.o: ../../include/check_arg.h
+postcat.o: ../../include/clean_env.h
+postcat.o: ../../include/htable.h
+postcat.o: ../../include/iostuff.h
+postcat.o: ../../include/is_header.h
+postcat.o: ../../include/lex_822.h
+postcat.o: ../../include/mail_conf.h
+postcat.o: ../../include/mail_params.h
+postcat.o: ../../include/mail_parm_split.h
+postcat.o: ../../include/mail_proto.h
+postcat.o: ../../include/mail_queue.h
+postcat.o: ../../include/mail_version.h
+postcat.o: ../../include/msg.h
+postcat.o: ../../include/msg_vstream.h
+postcat.o: ../../include/mymalloc.h
+postcat.o: ../../include/nvtable.h
+postcat.o: ../../include/rec_type.h
+postcat.o: ../../include/record.h
+postcat.o: ../../include/stringops.h
+postcat.o: ../../include/sys_defs.h
+postcat.o: ../../include/vbuf.h
+postcat.o: ../../include/vstream.h
+postcat.o: ../../include/vstring.h
+postcat.o: ../../include/vstring_vstream.h
+postcat.o: ../../include/warn_stat.h
+postcat.o: postcat.c
diff --git a/src/postcat/b_test.ref b/src/postcat/b_test.ref
new file mode 100644
index 0000000..bacff0c
--- /dev/null
+++ b/src/postcat/b_test.ref
@@ -0,0 +1,2 @@
+
+text
diff --git a/src/postcat/bh_test.ref b/src/postcat/bh_test.ref
new file mode 100644
index 0000000..bfe9e2d
--- /dev/null
+++ b/src/postcat/bh_test.ref
@@ -0,0 +1,9 @@
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id DE040290405; Sun, 21 Jan 2007 13:33:08 -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!
+
+text
diff --git a/src/postcat/default_test.ref b/src/postcat/default_test.ref
new file mode 100644
index 0000000..eede38b
--- /dev/null
+++ b/src/postcat/default_test.ref
@@ -0,0 +1,21 @@
+*** ENVELOPE RECORDS test-queue-file ***
+message_size: 332 182 1 0 332
+message_arrival_time: Sun Jan 21 13:32:59 2007
+create_time: Sun Jan 21 13:33:08 2007
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+*** MESSAGE CONTENTS test-queue-file ***
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id DE040290405; Sun, 21 Jan 2007 13:33:08 -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!
+
+text
+*** HEADER EXTRACTED test-queue-file ***
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE FILE END test-queue-file ***
diff --git a/src/postcat/e_test.ref b/src/postcat/e_test.ref
new file mode 100644
index 0000000..308b0df
--- /dev/null
+++ b/src/postcat/e_test.ref
@@ -0,0 +1,12 @@
+*** ENVELOPE RECORDS test-queue-file ***
+message_size: 332 182 1 0 332
+message_arrival_time: Sun Jan 21 13:32:59 2007
+create_time: Sun Jan 21 13:33:08 2007
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+*** MESSAGE CONTENTS test-queue-file ***
+*** HEADER EXTRACTED test-queue-file ***
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE FILE END test-queue-file ***
diff --git a/src/postcat/eb_test.ref b/src/postcat/eb_test.ref
new file mode 100644
index 0000000..b61beb2
--- /dev/null
+++ b/src/postcat/eb_test.ref
@@ -0,0 +1,14 @@
+*** ENVELOPE RECORDS test-queue-file ***
+message_size: 332 182 1 0 332
+message_arrival_time: Sun Jan 21 13:32:59 2007
+create_time: Sun Jan 21 13:33:08 2007
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+*** MESSAGE CONTENTS test-queue-file ***
+
+text
+*** HEADER EXTRACTED test-queue-file ***
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE FILE END test-queue-file ***
diff --git a/src/postcat/eh_test.ref b/src/postcat/eh_test.ref
new file mode 100644
index 0000000..3142553
--- /dev/null
+++ b/src/postcat/eh_test.ref
@@ -0,0 +1,19 @@
+*** ENVELOPE RECORDS test-queue-file ***
+message_size: 332 182 1 0 332
+message_arrival_time: Sun Jan 21 13:32:59 2007
+create_time: Sun Jan 21 13:33:08 2007
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+*** MESSAGE CONTENTS test-queue-file ***
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id DE040290405; Sun, 21 Jan 2007 13:33:08 -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!
+*** HEADER EXTRACTED test-queue-file ***
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE FILE END test-queue-file ***
diff --git a/src/postcat/h_test.ref b/src/postcat/h_test.ref
new file mode 100644
index 0000000..e1a8025
--- /dev/null
+++ b/src/postcat/h_test.ref
@@ -0,0 +1,7 @@
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id DE040290405; Sun, 21 Jan 2007 13:33:08 -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!
diff --git a/src/postcat/postcat.c b/src/postcat/postcat.c
new file mode 100644
index 0000000..36f2740
--- /dev/null
+++ b/src/postcat/postcat.c
@@ -0,0 +1,598 @@
+/*++
+/* NAME
+/* postcat 1
+/* SUMMARY
+/* show Postfix queue file contents
+/* SYNOPSIS
+/* \fBpostcat\fR [\fB-bdehnoqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...]
+/* DESCRIPTION
+/* The \fBpostcat\fR(1) command prints the contents of the
+/* named \fIfiles\fR in human-readable form. The files are
+/* expected to be in Postfix queue file format. If no \fIfiles\fR
+/* are specified on the command line, the program reads from
+/* standard input.
+/*
+/* By default, \fBpostcat\fR(1) shows the envelope and message
+/* content, as if the options \fB-beh\fR were specified. To
+/* view message content only, specify \fB-bh\fR (Postfix 2.7
+/* and later).
+/*
+/* Options:
+/* .IP \fB-b\fR
+/* Show body content. The \fB-b\fR option starts producing
+/* output at the first non-header line, and stops when the end
+/* of the message is reached.
+/* .sp
+/* This feature is available in Postfix 2.7 and later.
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory.
+/* .IP \fB-d\fR
+/* Print the decimal type of each record.
+/* .IP \fB-e\fR
+/* Show message envelope content.
+/* .sp
+/* This feature is available in Postfix 2.7 and later.
+/* .IP \fB-h\fR
+/* Show message header content. The \fB-h\fR option produces
+/* output from the beginning of the message up to, but not
+/* including, the first non-header line.
+/* .sp
+/* This feature is available in Postfix 2.7 and later.
+/* .IP \fB-o\fR
+/* Print the queue file offset of each record.
+/* .IP \fB-q\fR
+/* Search the Postfix queue for the named \fIfiles\fR instead
+/* of taking the names literally.
+/*
+/* This feature is available in Postfix 2.0 and later.
+/* .IP \fB-r\fR
+/* Print records in file order, don't follow pointer records.
+/*
+/* This feature is available in Postfix 3.7 and later.
+/* .IP "\fB-s \fIoffset\fR"
+/* Skip to the specified queue file offset.
+/*
+/* This feature is available in Postfix 3.7 and later.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* FILES
+/* /var/spool/postfix, Postfix queue directory
+/* SEE ALSO
+/* postconf(5), Postfix configuration
+/* 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/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h> /* sscanf() */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <mail_queue.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <is_header.h>
+#include <lex_822.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+#define PC_FLAG_SEARCH_QUEUE (1<<0) /* search queue */
+#define PC_FLAG_PRINT_OFFSET (1<<1) /* print record offsets */
+#define PC_FLAG_PRINT_ENV (1<<2) /* print envelope records */
+#define PC_FLAG_PRINT_HEADER (1<<3) /* print header records */
+#define PC_FLAG_PRINT_BODY (1<<4) /* print body records */
+#define PC_FLAG_PRINT_RTYPE_DEC (1<<5) /* print decimal record type */
+#define PC_FLAG_PRINT_RTYPE_SYM (1<<6) /* print symbolic record type */
+#define PC_FLAG_RAW (1<<7) /* don't follow pointers */
+
+#define PC_MASK_PRINT_TEXT (PC_FLAG_PRINT_HEADER | PC_FLAG_PRINT_BODY)
+#define PC_MASK_PRINT_ALL (PC_FLAG_PRINT_ENV | PC_MASK_PRINT_TEXT)
+
+ /*
+ * State machine.
+ */
+#define PC_STATE_ENV 0 /* initial or extracted envelope */
+#define PC_STATE_HEADER 1 /* primary header */
+#define PC_STATE_BODY 2 /* other */
+
+off_t start_offset = 0;
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* postcat - visualize Postfix queue file contents */
+
+static void postcat(VSTREAM *fp, VSTRING *buffer, int flags)
+{
+ int prev_type = 0;
+ int rec_type;
+ struct timeval tv;
+ time_t time;
+ int ch;
+ off_t offset;
+ const char *error_text;
+ char *attr_name;
+ char *attr_value;
+ int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT);
+ int state; /* state machine, input type */
+ int do_print; /* state machine, output control */
+ long data_offset; /* state machine, read optimization */
+ long data_size; /* state machine, read optimization */
+
+#define TEXT_RECORD(rec_type) \
+ (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM)
+
+ /*
+ * Skip over or absorb some bytes.
+ */
+ if (start_offset > 0) {
+ if (fp == VSTREAM_IN) {
+ for (offset = 0; offset < start_offset; offset++)
+ if (VSTREAM_GETC(fp) == VSTREAM_EOF)
+ msg_fatal("%s: skip %ld bytes failed after %ld",
+ VSTREAM_PATH(fp), (long) start_offset,
+ (long) offset);
+ } else {
+ if (vstream_fseek(fp, start_offset, SEEK_SET) < 0)
+ msg_fatal("%s: seek to %ld: %m",
+ VSTREAM_PATH(fp), (long) start_offset);
+ }
+ }
+
+ /*
+ * See if this is a plausible file.
+ */
+ if (start_offset == 0 && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
+ if (!strchr(REC_TYPE_ENVELOPE, ch)) {
+ msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp));
+ return;
+ }
+ vstream_ungetc(fp, ch);
+ }
+
+ /*
+ * Other preliminaries.
+ */
+ if (start_offset == 0 && (flags & PC_FLAG_PRINT_ENV))
+ vstream_printf("*** ENVELOPE RECORDS %s ***\n",
+ VSTREAM_PATH(fp));
+ state = PC_STATE_ENV;
+ do_print = (flags & PC_FLAG_PRINT_ENV);
+ data_offset = data_size = -1;
+
+ /*
+ * Now look at the rest.
+ */
+ for (;;) {
+ if (flags & PC_FLAG_PRINT_OFFSET)
+ offset = vstream_ftell(fp);
+ rec_type = rec_get_raw(fp, buffer, 0, rec_flags);
+ if (rec_type == REC_TYPE_ERROR)
+ msg_fatal("record read error");
+ if (rec_type == REC_TYPE_EOF)
+ break;
+
+ /*
+ * First inspect records that have side effects on the (envelope,
+ * header, body) state machine or on the record reading order.
+ *
+ * XXX Comments marked "Optimization:" identify subtle code that will
+ * likely need to be revised when the queue file organization is
+ * changed.
+ */
+#define PRINT_MARKER(flags, fp, offset, type, text) do { \
+ if ((flags) & PC_FLAG_PRINT_OFFSET) \
+ vstream_printf("%9lu ", (unsigned long) (offset)); \
+ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \
+ vstream_printf("%3d ", (type)); \
+ vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \
+ vstream_fflush(VSTREAM_OUT); \
+} while (0)
+
+#define PRINT_RECORD(flags, offset, type, value) do { \
+ if ((flags) & PC_FLAG_PRINT_OFFSET) \
+ vstream_printf("%9lu ", (unsigned long) (offset)); \
+ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \
+ vstream_printf("%3d ", (type)); \
+ vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \
+ vstream_fflush(VSTREAM_OUT); \
+} while (0)
+
+ if (TEXT_RECORD(rec_type)) {
+ /* This is wrong when the message starts with whitespace. */
+ if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT))
+ && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type)
+ && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) {
+ /* Update the state machine. */
+ state = PC_STATE_BODY;
+ do_print = (flags & PC_FLAG_PRINT_BODY);
+ /* Optimization: terminate if nothing left to print. */
+ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0)
+ break;
+ /* Optimization: skip to extracted segment marker. */
+ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV)
+ && data_offset >= 0 && data_size >= 0
+ && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
+ msg_fatal("seek error: %m");
+ }
+ /* Optional output happens further down below. */
+ } else if (rec_type == REC_TYPE_MESG) {
+ /* Sanity check. */
+ if (state != PC_STATE_ENV)
+ msg_warn("%s: out-of-order message content marker",
+ VSTREAM_PATH(fp));
+ /* Optional output. */
+ if (flags & PC_FLAG_PRINT_ENV)
+ PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS");
+ /* Optimization: skip to extracted segment marker. */
+ if ((flags & PC_MASK_PRINT_TEXT) == 0
+ && data_offset >= 0 && data_size >= 0
+ && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
+ msg_fatal("seek error: %m");
+ /* Update the state machine, even when skipping. */
+ state = PC_STATE_HEADER;
+ do_print = (flags & PC_FLAG_PRINT_HEADER);
+ continue;
+ } else if (rec_type == REC_TYPE_XTRA) {
+ /* Sanity check. */
+ if (state != PC_STATE_HEADER && state != PC_STATE_BODY)
+ msg_warn("%s: out-of-order extracted segment marker",
+ VSTREAM_PATH(fp));
+ /* Optional output (terminate preceding header/body line). */
+ if (do_print && prev_type == REC_TYPE_CONT)
+ VSTREAM_PUTCHAR('\n');
+ if (flags & PC_FLAG_PRINT_ENV)
+ PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED");
+ /* Update the state machine. */
+ state = PC_STATE_ENV;
+ do_print = (flags & PC_FLAG_PRINT_ENV);
+ /* Optimization: terminate if nothing left to print. */
+ if (do_print == 0)
+ break;
+ continue;
+ } else if (rec_type == REC_TYPE_END) {
+ /* Sanity check. */
+ if (state != PC_STATE_ENV)
+ msg_warn("%s: out-of-order message end marker",
+ VSTREAM_PATH(fp));
+ /* Optional output. */
+ if (flags & PC_FLAG_PRINT_ENV)
+ PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END");
+ if (flags & PC_FLAG_RAW)
+ continue;
+ /* Terminate the state machine. */
+ break;
+ } else if (rec_type == REC_TYPE_PTR) {
+ /* Optional output. */
+ /* This record type is exposed only with '-v'. */
+ if (do_print)
+ PRINT_RECORD(flags, offset, rec_type, STR(buffer));
+ /* Skip to the pointer's target record. */
+ if ((flags & PC_FLAG_RAW) == 0
+ && rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR)
+ msg_fatal("bad pointer record, or input is not seekable");
+ continue;
+ } else if (rec_type == REC_TYPE_SIZE) {
+ /* Optional output (here before we update the state machine). */
+ if (do_print)
+ PRINT_RECORD(flags, offset, rec_type, STR(buffer));
+ /* Read the message size/offset for the state machine optimizer. */
+ if (data_size >= 0 || data_offset >= 0) {
+ msg_warn("file contains multiple size records");
+ } else {
+ if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2
+ || data_offset <= 0 || data_size <= 0)
+ msg_warn("invalid size record: %.100s", STR(buffer));
+ /* Optimization: skip to the message header. */
+ if ((flags & PC_FLAG_PRINT_ENV) == 0) {
+ if (vstream_fseek(fp, data_offset, SEEK_SET) < 0)
+ msg_fatal("seek error: %m");
+ /* Update the state machine. */
+ state = PC_STATE_HEADER;
+ do_print = (flags & PC_FLAG_PRINT_HEADER);
+ }
+ }
+ continue;
+ }
+
+ /*
+ * Don't inspect side-effect-free records that aren't printed.
+ */
+ if (do_print == 0)
+ continue;
+ if (flags & PC_FLAG_PRINT_OFFSET)
+ vstream_printf("%9lu ", (unsigned long) offset);
+ if (flags & PC_FLAG_PRINT_RTYPE_DEC)
+ vstream_printf("%3d ", rec_type);
+ switch (rec_type) {
+ case REC_TYPE_TIME:
+ REC_TYPE_TIME_SCAN(STR(buffer), tv);
+ time = tv.tv_sec;
+ vstream_printf("%s: %s", rec_type_name(rec_type),
+ asctime(localtime(&time)));
+ break;
+ case REC_TYPE_WARN:
+ REC_TYPE_WARN_SCAN(STR(buffer), time);
+ vstream_printf("%s: %s", rec_type_name(rec_type),
+ asctime(localtime(&time)));
+ break;
+ case REC_TYPE_CONT: /* REC_TYPE_FILT collision */
+ if (state == PC_STATE_ENV)
+ vstream_printf("%s: ", rec_type_name(rec_type));
+ else if (msg_verbose)
+ vstream_printf("unterminated_text: ");
+ vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
+ if (state == PC_STATE_ENV || msg_verbose
+ || (flags & PC_FLAG_PRINT_OFFSET) != 0) {
+ rec_type = 0;
+ VSTREAM_PUTCHAR('\n');
+ }
+ break;
+ case REC_TYPE_NORM:
+ if (msg_verbose)
+ vstream_printf("%s: ", rec_type_name(rec_type));
+ vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
+ VSTREAM_PUTCHAR('\n');
+ break;
+ case REC_TYPE_DTXT:
+ /* This record type is exposed only with '-v'. */
+ vstream_printf("%s: ", rec_type_name(rec_type));
+ vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
+ VSTREAM_PUTCHAR('\n');
+ break;
+ case REC_TYPE_ATTR:
+ error_text = split_nameval(STR(buffer), &attr_name, &attr_value);
+ if (error_text != 0) {
+ msg_warn("%s: malformed attribute: %s: %.100s",
+ VSTREAM_PATH(fp), error_text, STR(buffer));
+ break;
+ }
+ if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) {
+ time = atol(attr_value);
+ vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME,
+ asctime(localtime(&time)));
+ } else {
+ vstream_printf("%s: %s=%s\n", rec_type_name(rec_type),
+ attr_name, attr_value);
+ }
+ break;
+ default:
+ vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer));
+ break;
+ }
+ prev_type = rec_type;
+
+ /*
+ * In case the next record is broken.
+ */
+ vstream_fflush(VSTREAM_OUT);
+ }
+}
+
+/* usage - explain and terminate */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-b (body text)] [-c config_dir] [-d (decimal record type)] [-e (envelope records)] [-h (header text)] [-q (access queue)] [-v] [file(s)...]",
+ myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer;
+ VSTREAM *fp;
+ int ch;
+ int fd;
+ struct stat st;
+ int flags = 0;
+ static char *queue_names[] = {
+ MAIL_QUEUE_MAILDROP,
+ MAIL_QUEUE_INCOMING,
+ MAIL_QUEUE_ACTIVE,
+ MAIL_QUEUE_DEFERRED,
+ MAIL_QUEUE_HOLD,
+ MAIL_QUEUE_SAVED,
+ 0,
+ };
+ char **cpp;
+ int tries;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up logging.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "bc:dehoqrs:v")) > 0) {
+ switch (ch) {
+ case 'b':
+ flags |= PC_FLAG_PRINT_BODY;
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'd':
+ flags |= PC_FLAG_PRINT_RTYPE_DEC;
+ break;
+ case 'e':
+ flags |= PC_FLAG_PRINT_ENV;
+ break;
+ case 'h':
+ flags |= PC_FLAG_PRINT_HEADER;
+ break;
+ case 'o':
+ flags |= PC_FLAG_PRINT_OFFSET;
+ break;
+ case 'q':
+ flags |= PC_FLAG_SEARCH_QUEUE;
+ break;
+ case 'r':
+ flags |= PC_FLAG_RAW;
+ break;
+ case 's':
+ if (!alldig(optarg) || (start_offset = atol(optarg)) < 0)
+ msg_fatal("bad offset: %s", optarg);
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if ((flags & PC_MASK_PRINT_ALL) == 0)
+ flags |= PC_MASK_PRINT_ALL;
+
+ /*
+ * Further initialization...
+ */
+ mail_conf_read();
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+
+ /*
+ * Initialize.
+ */
+ buffer = vstring_alloc(10);
+
+ /*
+ * If no file names are given, copy stdin.
+ */
+ if (argc == optind) {
+ vstream_control(VSTREAM_IN,
+ CA_VSTREAM_CTL_PATH("stdin"),
+ CA_VSTREAM_CTL_END);
+ postcat(VSTREAM_IN, buffer, flags);
+ }
+
+ /*
+ * Copy the named queue files in the specified order.
+ */
+ else if (flags & PC_FLAG_SEARCH_QUEUE) {
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+ while (optind < argc) {
+ if (!mail_queue_id_ok(argv[optind]))
+ msg_fatal("bad mail queue ID: %s", argv[optind]);
+ for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++)
+ for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++)
+ fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0);
+ if (fp == 0)
+ msg_fatal("open queue file %s: %m", argv[optind]);
+ postcat(fp, buffer, flags);
+ if (vstream_fclose(fp))
+ msg_warn("close %s: %m", argv[optind]);
+ optind++;
+ }
+ }
+
+ /*
+ * Copy the named files in the specified order.
+ */
+ else {
+ while (optind < argc) {
+ if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", argv[optind]);
+ postcat(fp, buffer, flags);
+ if (vstream_fclose(fp))
+ msg_warn("close %s: %m", argv[optind]);
+ optind++;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buffer);
+ exit(0);
+}
diff --git a/src/postcat/test-queue-file b/src/postcat/test-queue-file
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/postcat/test-queue-file
Binary files differ
diff --git a/src/postconf/.indent.pro b/src/postconf/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postconf/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postconf/.printfck b/src/postconf/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postconf/.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/postconf/Makefile.in b/src/postconf/Makefile.in
new file mode 100644
index 0000000..60c797a
--- /dev/null
+++ b/src/postconf/Makefile.in
@@ -0,0 +1,1304 @@
+SHELL = /bin/sh
+SRCS = postconf.c postconf_builtin.c postconf_edit.c postconf_main.c \
+ postconf_master.c postconf_misc.c postconf_node.c postconf_other.c \
+ postconf_service.c postconf_unused.c postconf_user.c postconf_dbms.c \
+ postconf_lookup.c postconf_match.c postconf_print.c
+OBJS = postconf.o postconf_builtin.o postconf_edit.o postconf_main.o \
+ postconf_master.o postconf_misc.o postconf_node.o postconf_other.o \
+ postconf_service.o postconf_unused.o postconf_user.o postconf_dbms.o \
+ postconf_lookup.o postconf_match.o postconf_print.o
+HDRS = postconf.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) -DLEGACY_DBMS_SUPPORT
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+MAKES = bool_table.h bool_vars.h int_table.h int_vars.h str_table.h \
+ str_vars.h time_table.h time_vars.h raw_table.h raw_vars.h \
+ nint_table.h nint_vars.h nbool_table.h nbool_vars.h long_table.h \
+ long_vars.h str_fn_table.h str_fn_vars.h
+DB_MAKES= pcf_ldap_suffixes.h pcf_memcache_suffixes.h pcf_mysql_suffixes.h \
+ pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h
+TEST_TMP= main.cf master.cf test*.tmp
+DUMMIES = makes_dummy # for "make -j"
+PROG = postconf
+SAMPLES = ../../conf/main.cf.default
+INC_DIR = ../../include
+LIBS = ../../lib/libxsasl.a \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+HTABLE_FIX = NORANDOMIZE=1
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+../../conf/main.cf.default: $(PROG) Makefile
+ rm -f $@
+ (echo "# DO NOT EDIT THIS FILE. EDIT THE MAIN.CF FILE INSTEAD. THE"; \
+ echo "# TEXT HERE JUST SHOWS DEFAULT SETTINGS BUILT INTO POSTFIX."; \
+ echo "#"; $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -d -c ../../conf) | \
+ egrep -v '^(myhostname|mydomain|mynetworks|process_name|process_id) ' >$@
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \
+ test12 test13 test14 test15 test16 test17 test18 test19 test20 test21 \
+ test22 test23 test24 test25 test26 test27 test28 test29 test30 test4b \
+ test31 test32 test33 test34 test35 test36 test37 test39 test40 test41 \
+ test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \
+ test52 test53 test54 test55 test56 test57 test58 test59 test60 test61 \
+ test62 test63 test64 test65 test66 test67 test68 test69 test70
+
+root_tests:
+
+update: ../../bin/$(PROG) $(SAMPLES)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+$(MAKES): makes_dummy
+
+makes_dummy: $(INC_DIR)/mail_params.h ../global/mail_params.c extract.awk Makefile.in
+ $(AWK) -f extract.awk ../*/*.c | $(SHELL)
+ touch makes_dummy
+
+$(DB_MAKES): extract_cfg.sh Makefile.in
+
+pcf_ldap_suffixes.h: ../global/dict_ldap.c
+ sh extract_cfg.sh -d ../global/dict_ldap.c > $@
+
+pcf_memcache_suffixes.h: ../global/dict_memcache.c
+ sh extract_cfg.sh -d ../global/dict_memcache.c > $@
+
+pcf_mysql_suffixes.h: ../global/dict_mysql.c
+ sh extract_cfg.sh -d -s ../global/dict_mysql.c > $@
+
+pcf_pgsql_suffixes.h: ../global/dict_pgsql.c
+ sh extract_cfg.sh -d -s ../global/dict_pgsql.c > $@
+
+pcf_sqlite_suffixes.h: ../global/dict_sqlite.c
+ sh extract_cfg.sh -d -s ../global/dict_sqlite.c > $@
+
+# Define two parameters with smtpd_restriction_classes. One will be ignored.
+
+test1: $(PROG) test1.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo smtpd_restriction_classes = foo bar >> main.cf
+ echo foo = yes >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test1.tmp 2>&1
+ diff test1.ref test1.tmp
+ rm -f main.cf master.cf test1.tmp
+
+# Define two unused parameters. Expect two warnings.
+
+test2: $(PROG) test2.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo restriction_classes = foo bar >> main.cf
+ echo foo = yes >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test2.tmp 2>&1
+ diff test2.ref test2.tmp
+ rm -f main.cf master.cf test2.tmp
+
+# Define one parameter in main.cf, validate it with main.cf.
+
+test3: $(PROG) test3.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo = yes >> main.cf
+ echo 'bar = $$foo' >> main.cf
+ echo 'always_bcc = $$bar' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test3.tmp 2>&1
+ diff test3.ref test3.tmp
+ rm -f main.cf master.cf test3.tmp
+
+# Define one parameter in main.cf, validate it with master.cf.
+
+test4: $(PROG) test4.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo = yes >> main.cf
+ echo 'bar = $$foo' >> main.cf
+ echo smtpd unix - n n - 0 smtpd >> master.cf
+ echo ' -o always_bcc=$$bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test4.tmp 2>&1
+ diff test4.ref test4.tmp
+ rm -f main.cf master.cf test4.tmp
+
+# Define one parameter in master.cf, validate it with main.cf.
+
+test4b: $(PROG) test4b.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'always_bcc = $$foo' >> main.cf
+ echo 'biff = $$bar' >> main.cf
+ echo 'bar = aaa' >> main.cf
+ echo smtpd1 unix - n n - 0 smtpd >> master.cf
+ echo ' -o foo=xxx -o bar=yyy -o baz=zzz' >> master.cf
+ echo '#smtpd2 unix - n n - 0 smtpd' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test4b.tmp 2>&1
+ diff test4b.ref test4b.tmp
+ rm -f main.cf master.cf test4b.tmp
+
+# Define one user-defined parameter with name=value in master.cf,
+# validate it with known_parameter=$$name in master.cf.
+
+test5: $(PROG) test5.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo smtpd unix - n n - 0 smtpd >> master.cf
+ echo ' -o bar=yes -o always_bcc=$$bar -o' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test5.tmp 2>&1
+ diff test5.ref test5.tmp
+ rm -f main.cf master.cf test5.tmp
+
+# Basic functionality test: service parameters for delivery agents.
+
+test6: $(PROG) test6.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings unix - n n - 0 pipe >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test6.tmp
+ diff test6.ref test6.tmp
+ rm -f main.cf master.cf test6.tmp
+
+# Basic functionality test: service parameters for spawn programs.
+
+test7: $(PROG) test7.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test7.tmp
+ diff test7.ref test7.tmp
+ rm -f main.cf master.cf test7.tmp
+
+test8: $(PROG) test8.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings inet - n n - 0 spawn >> master.cf
+ echo whatevershebrings_time_limit=1 >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test8.tmp
+ diff test8.ref test8.tmp
+ rm -f main.cf master.cf test8.tmp
+
+test9: $(PROG) test9.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M '*'/inet >test9.tmp 2>&1
+ diff test9.ref test9.tmp
+ rm -f main.cf master.cf test9.tmp
+
+test10: $(PROG) test10.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M bar/inet foo/unix >test10.tmp 2>&1
+ diff test10.ref test10.tmp
+ rm -f main.cf master.cf test10.tmp
+
+test11: $(PROG) test11.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo bar unix - n n - 0 spawn >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M >test11.tmp 2>&1
+ diff test11.ref test11.tmp
+ rm -f main.cf master.cf test11.tmp
+
+# Duplicate service entry.
+
+test12: $(PROG) test12.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo bar=yes >> main.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o always_bcc=$$bar -o' >> master.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o always_bcc=$$bar -o' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M >test12.tmp 2>&1
+ diff test12.ref test12.tmp
+ rm -f main.cf master.cf test12.tmp
+
+# Define parameter with restriction_classes in master.cf, validate in main.cf.
+
+test13: $(PROG) test13.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo bar=yes >> main.cf
+ echo baz=xx >> main.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o smtpd_restriction_classes=bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test13.tmp 2>&1
+ diff test13.ref test13.tmp
+ rm -f main.cf master.cf test13.tmp
+
+# Define parameter with restriction_classes in main.cf, validate in master.cf.
+
+test14: $(PROG) test14.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo smtpd_restriction_classes=bar >> main.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o bar=yes -o baz=xx' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test14.tmp 2>&1
+ diff test14.ref test14.tmp
+ rm -f main.cf master.cf test14.tmp
+
+# Define two parameters, one is hidden by master.cf.
+
+test15: $(PROG) test15.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo bar=xx >> main.cf
+ echo baz=yy >> main.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o bar=yes -o always_bcc=$$bar$$baz' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test15.tmp 2>&1
+ diff test15.ref test15.tmp
+ rm -f main.cf master.cf test15.tmp
+
+# Test graceful degradation if master.cf is unavailable.
+
+test16: $(PROG) test16.ref
+ rm -f main.cf master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test16.tmp 2>&1
+ diff test16.ref test16.tmp
+ rm -f main.cf master.cf test16.tmp
+
+test17: $(PROG) test17.ref
+ rm -f main.cf master.cf
+ touch -t 197101010000 main.cf
+ -$(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc . >test17.tmp 2>&1; exit 0
+ diff test17.ref test17.tmp
+ rm -f main.cf master.cf test17.tmp
+
+# Test legacy $name in built-in defaults.
+
+test18: $(PROG) test18.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo virtual_maps=xxx >> main.cf
+ echo smtpd_client_connection_limit_exceptions=yyy >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test18.tmp 2>&1
+ diff test18.ref test18.tmp
+ rm -f main.cf master.cf test18.tmp
+
+# Test $name in "raw" parameters.
+
+test19: $(PROG) test19.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo forward_path='$$'aaaa >> main.cf
+ echo default_rbl_reply='$$'bbbb >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test19.tmp 2>&1
+ diff test19.ref test19.tmp
+ rm -f main.cf master.cf test19.tmp
+
+# Test master.cf line folding.
+
+test20: $(PROG) test20.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo inet - n n - 0 spawn >> master.cf
+ echo ' -o always_bcc=$$bar$$baz' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc . >test20.tmp 2>&1
+ diff test20.ref test20.tmp
+ rm -f main.cf master.cf test20.tmp
+
+# Test main.cf line folding.
+
+test21: $(PROG) test21.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo forward_path = xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx \
+ xxxxxxxxxxxxx xxxxxxxxxxxxxx >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nfc . >test21.tmp 2>&1
+ diff test21.ref test21.tmp
+ rm -f main.cf master.cf test21.tmp
+
+# Like test6, but using a delivery agent that has no _time_limit magic.
+
+test22: $(PROG) test22.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test22.tmp
+ diff test22.ref test22.tmp
+ rm -f main.cf master.cf test22.tmp
+
+# Test the -C flag for each category.
+
+test23: $(PROG) test23.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo always_bcc = yes >> main.cf
+ echo name = value >> main.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -nC builtin >test23.tmp 2>&1
+ diff test23.ref test23.tmp
+ rm -f main.cf master.cf test23.tmp
+
+test24: $(PROG) test24.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo always_bcc = yes >> main.cf
+ echo name = value >> main.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -nC user >test24.tmp 2>&1
+ diff test24.ref test24.tmp
+ rm -f main.cf master.cf test24.tmp
+
+test25: $(PROG) test25.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo always_bcc = yes >> main.cf
+ echo name = value >> main.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -C service 2>&1 | grep whatevershebrings >test25.tmp
+ diff test25.ref test25.tmp
+ rm -f main.cf master.cf test25.tmp
+
+# Test completeness of "-C all".
+
+test26: $(PROG) test26.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo always_bcc = yes >> main.cf
+ echo name = value >> main.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . -C all >test26.tmp 2>&1
+ diff test26.ref test26.tmp
+ rm -f main.cf master.cf test26.tmp
+
+test27: $(PROG) test27.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo always_bcc = yes >> main.cf
+ echo name = value >> main.cf
+ echo whatevershebrings unix - n n - 0 smtp >> master.cf
+ echo ' -o always_bcc=$$name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -C all 2>&1 | grep whatevershebrings >test27.tmp
+ diff test27.ref test27.tmp
+ rm -f main.cf master.cf test27.tmp
+
+# Test macro expansion, type:table parsing and scoping.
+
+test28: $(PROG) test28.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'xx = proxy:ldap:foo' >> main.cf
+ echo 'foo_domain = bar' >> main.cf
+ echo 'header_checks = ldap:hh' >> main.cf
+ echo 'hh_domain = whatever' >> main.cf
+ echo 'zz = $$yy' >> main.cf
+ echo 'yy = aap' >> main.cf
+ echo 'db = memcache' >> main.cf
+ echo whatevershebrings unix - n n - 0 other >> master.cf
+ echo ' -o body_checks=$$db:$$zz' >> master.cf
+ echo 'aap_domain = whatever' >> main.cf
+ echo 'aa_domain = whatever' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test28.tmp 2>&1
+ diff test28.ref test28.tmp
+ rm -f main.cf master.cf test28.tmp
+
+# Test the handling of known and unknown database-defined suffixes.
+
+test29: $(PROG) test29.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'ldapxx = proxy:ldap:ldapfoo' >> main.cf
+ echo 'ldapfoo_domain = bar' >> main.cf
+ echo 'ldapfoo_domainx = bar' >> main.cf
+ echo 'mysqlxx = proxy:mysql:mysqlfoo' >> main.cf
+ echo 'mysqlfoo_domain = bar' >> main.cf
+ echo 'mysqlfoo_domainx = bar' >> main.cf
+ echo 'pgsqlxx = proxy:pgsql:pgsqlfoo' >> main.cf
+ echo 'pgsqlfoo_domain = bar' >> main.cf
+ echo 'pgsqlfoo_domainx = bar' >> main.cf
+ echo 'sqlitexx = proxy:sqlite:sqlitefoo' >> main.cf
+ echo 'sqlitefoo_domain = bar' >> main.cf
+ echo 'sqlitefoo_domainx = bar' >> main.cf
+ echo 'memcachexx = proxy:memcache:memcachefoo' >> main.cf
+ echo 'memcachefoo_domain = bar' >> main.cf
+ echo 'memcachefoo_domainx = bar' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test29.tmp 2>&1
+ diff test29.ref test29.tmp
+ rm -f main.cf master.cf test29.tmp
+
+test30: $(PROG) test30.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo p1=xx >> main.cf
+ echo p2=xx >> main.cf
+ echo p3=xx >> main.cf
+ echo p4=xx >> main.cf
+ echo whatevershebrings unix - n n - 0 other >> master.cf
+ echo ' -o body_checks=$$p1' >> master.cf
+ echo ' -o bodyx_checks=$$p2' >> master.cf
+ echo ' -oheader_checks=$$p3' >> master.cf
+ echo ' -oheaderx_checks=$$p4' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test30.tmp 2>&1
+ diff test30.ref test30.tmp
+ rm -f main.cf master.cf test30.tmp
+
+# Does a non-default setting propagate to a non-default value?
+
+test31: $(PROG) test31.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'smtpd_helo_restrictions=whatever' >> main.cf
+ echo 'smtpd_sender_restrictions=$$smtpd_helo_restrictions' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc . >test31.tmp 2>&1
+ diff test31.ref test31.tmp
+ rm -f main.cf master.cf test31.tmp
+
+# Does a non-default setting propagate to a default value?
+
+test32: $(PROG) test32.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'relay_domains=whatever' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . fast_flush_domains >test32.tmp 2>&1
+ diff test32.ref test32.tmp
+ rm -f main.cf master.cf test32.tmp
+
+# Does a default setting propagate to a non-default value?
+
+test33: $(PROG) test33.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'mydestination=whatever' >> main.cf
+ echo 'always_bcc=$$relay_domains' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . always_bcc >test33.tmp 2>&1
+ diff test33.ref test33.tmp
+ rm -f main.cf master.cf test33.tmp
+
+test34: $(PROG) test34.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'mydestination=whatever' >> main.cf
+ echo 'process_name=xxx' >> main.cf
+ echo 'process_id=yyy' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . mydestination process_name >test34.tmp 2>&1
+ diff test34.ref test34.tmp
+ rm -f main.cf master.cf test34.tmp
+
+test35: $(PROG) test35.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings unix - n n - 0 other >> master.cf
+ echo ' -o body_checks=whatever' >> master.cf
+ echo ' -o process_name=aaa' >> master.cf
+ echo ' -o process_id=bbb' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . process_name >test35.tmp 2>&1
+ diff test35.ref test35.tmp
+ rm -f main.cf master.cf test35.tmp
+
+test36: $(PROG) test36.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'mydestination=$$virtual_mapx' >> main.cf
+ echo 'virtual_alias_maps=$$virtual_maps' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc . >test36.tmp 2>&1
+ diff test36.ref test36.tmp
+ rm -f main.cf master.cf test36.tmp
+
+test37: $(PROG) test37.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'xxx=yyy' >> main.cf
+ echo 'aaa=bbb' >> main.cf
+ echo whatever unix - n n - 0 other >> master.cf
+ echo ' -o mydestination=$$xxx' >> master.cf
+ echo ' -o always_bcc=$$aaa' >> master.cf
+ echo ' -o aaa=ccc' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfxc . >test37.tmp 2>&1
+ diff test37.ref test37.tmp
+ rm -f main.cf master.cf test37.tmp
+
+test39: $(PROG) test39.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc . '*'/unix >test39.tmp 2>&1
+ diff test39.ref test39.tmp
+ rm -f main.cf master.cf test39.tmp
+
+test40: $(PROG) test40.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -voaaa=bbb' >> master.cf
+ echo ' -vo ccc=$$aaa' >> master.cf
+ echo ' -v -oddd=$$ccc' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfxc . '*'/unix >test40.tmp 2>&1
+ diff test40.ref test40.tmp
+ rm -f main.cf master.cf test40.tmp
+
+test41: $(PROG) test41.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar unix - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test41.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test41.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=YYY bar/unix/aaa=BBB >>test41.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test41.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . >>test41.tmp 2>&1
+ diff test41.ref test41.tmp
+ rm -f main.cf master.cf test41.tmp
+
+test42: $(PROG) test42.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar unix - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test42.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test42.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . >>test42.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -PXc. bar/unix/xxx bar/unix/aaa >>test42.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test42.tmp 2>&1
+ diff test42.ref test42.tmp
+ rm -f main.cf master.cf test42.tmp
+
+test43: $(PROG) test43.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar unix - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Fc . bar/unix/chroot=y bar/unix/command='aa -stuffobb=cc dd' >test43.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test43.tmp 2>&1
+ diff test43.ref test43.tmp
+ rm -f main.cf master.cf test43.tmp
+
+test44: $(PROG) test44.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar unix - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc . bar/unix='xx inet - n n - 0 aa -stuffobb=cc dd' >test44.tmp 2>&1
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test44.tmp 2>&1
+ diff test44.ref test44.tmp
+ rm -f main.cf master.cf test44.tmp
+
+test45: $(PROG) test45.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar xxxx - n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test45.tmp 2>&1 || true
+ diff test45.ref test45.tmp
+ rm -f main.cf master.cf test45.tmp
+
+test46: $(PROG) test46.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet X n n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test46.tmp 2>&1 || true
+ diff test46.ref test46.tmp
+ rm -f main.cf master.cf test46.tmp
+
+test47: $(PROG) test47.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - X n - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test47.tmp 2>&1 || true
+ diff test47.ref test47.tmp
+ rm -f main.cf master.cf test47.tmp
+
+test48: $(PROG) test48.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n X - 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test48.tmp 2>&1 || true
+ diff test48.ref test48.tmp
+ rm -f main.cf master.cf test48.tmp
+
+test49: $(PROG) test49.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n X 0 other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test49.tmp 2>&1 || true
+ diff test49.ref test49.tmp
+ rm -f main.cf master.cf test49.tmp
+
+test50: $(PROG) test50.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n - X other >> master.cf
+ echo baz unix - n n - 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test50.tmp 2>&1 || true
+ diff test50.ref test50.tmp
+ rm -f main.cf master.cf test50.tmp
+
+test51: $(PROG) test51.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n -? 0 other >> master.cf
+ echo bar inet - n n X? 0 other >> master.cf
+ echo baz unix - n n 0? 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test51.tmp 2>&1 || true
+ diff test51.ref test51.tmp
+ rm -f main.cf master.cf test51.tmp
+
+test52: $(PROG) test52.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -MXc. bar/inet foo/unix xxx/yyy
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test52.tmp 2>&1 || true
+ diff test52.ref test52.tmp
+ rm -f main.cf master.cf test52.tmp
+
+test53: $(PROG) test53.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet xxx/yyy
+ diff test53.ref master.cf
+ rm -f main.cf master.cf test53.tmp
+
+test54: $(PROG) test54.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet foo/unix
+ diff test54.ref master.cf
+ rm -f main.cf master.cf test54.tmp
+
+test55: $(PROG) test55.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet baz/unix
+ diff test55.ref master.cf
+ rm -f main.cf master.cf test55.tmp
+
+test56: $(PROG) test56.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo " -o first" >> master.cf
+ echo " -o second" >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet xxx/yyy
+ diff test56.ref master.cf
+ rm -f main.cf master.cf test56.tmp
+
+# Many more tests in util/mac_expand.in.
+
+test57: $(PROG) test57.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'x = $${{1} == {2}?{error}:x-value}' >> main.cf
+ echo 'y = y-value' >> main.cf
+ echo 'bar = $${x?{$$y}:$$z}' >> main.cf
+ echo 'baz = $${x?{$$z}:$$y}' >> main.cf
+ echo 'foo = $$bar$$baz' >> main.cf
+ echo 't1 = Postfix 2.11 $${{$${x?bug:x}} == {bug}?in}compatible' >> main.cf
+ echo 't2 = $$t1' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc. >test57.tmp 2>&1
+ diff test57.ref test57.tmp
+ rm -f main.cf master.cf test57.tmp
+
+test58: $(PROG) test58.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'mydestination = foo bar pipemap:{ldap:xxx, memcache:yy}x randmap:{xx' >> main.cf
+ echo 'xxx_domain = foo' >> main.cf
+ echo 'xxx_bogus = foo' >> main.cf
+ echo 'yy_backup = bbb' >> main.cf
+ echo 'yy_bogus = bbb' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./postconf -nc. >test58.tmp 2>&1 || true
+ diff test58.ref test58.tmp
+ rm -f main.cf master.cf test58.tmp
+
+test59: $(PROG) test59.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo bar inet - n n 0 0 other >> master.cf
+ echo " -o name1=value1" >> master.cf
+ echo " -o { name2 = value2a value2b }" >> master.cf
+ echo " { arg1a arg1b }" >> master.cf
+ echo " { arg2a arg2b }x" >> master.cf
+ echo " { arg3a arg3b " >> master.cf
+ echo baz unix - n n 0 0 other >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test59.tmp 2>&1 || true
+ diff test59.ref test59.tmp
+ rm -f main.cf master.cf test59.tmp
+
+test60: $(PROG) test60.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o always_bcc=bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Fhc. >test60.tmp 2>&1 || true
+ diff test60.ref test60.tmp
+ rm -f main.cf master.cf test60.tmp
+
+test61: $(PROG) test61.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o always_bcc=bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Phc. >test61.tmp 2>&1 || true
+ diff test61.ref test61.tmp
+ rm -f main.cf master.cf test61.tmp
+
+test62: $(PROG) test62.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o always_bcc=bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -FHc. >test62.tmp 2>&1 || true
+ diff test62.ref test62.tmp
+ rm -f main.cf master.cf test62.tmp
+
+test63: $(PROG) test63.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o always_bcc=bar' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -PHc. >test63.tmp 2>&1 || true
+ diff test63.ref test63.tmp
+ rm -f main.cf master.cf test63.tmp
+
+# main.cf overrides built-in default.
+
+test64: $(PROG) test64.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'relayhost = relay-from-main.cf' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. relayhost >test64.tmp 2>&1
+ diff test64.ref test64.tmp
+ rm -f main.cf master.cf test64.tmp
+
+# '-o name=value' overrides main.cf.
+
+test65: $(PROG) test65.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo 'relayhost = relay-from-main.cf' >> main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -o relayhost=relay-from-cmd-line relayhost >test65.tmp 2>&1
+ diff test65.ref test65.tmp
+ rm -f main.cf master.cf test65.tmp
+
+# unknown parameters in database configuration file (absolute pathname).
+
+test66: $(PROG) test66.ref
+ rm -f main.cf master.cf
+ touch master.cf
+ echo alias_maps = ldap:`pwd`/test66.cf >> main.cf
+ echo " " mysql:`pwd`/test66.cf >> main.cf
+ echo " " pgsql:`pwd`/test66.cf >> main.cf
+ echo " " sqlite:`pwd`/test66.cf >> main.cf
+ echo " " memcache:`pwd`/test66.cf >> main.cf
+ echo junk = junk >> test66.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. 2>test66.tmp >/dev/null
+ sed "s;PWD;`pwd`;" test66.ref | diff - test66.tmp
+ rm -f main.cf master.cf test66.tmp test66.cf
+
+# expand process name and service name in master.cf.
+
+test67: $(PROG) test67.ref
+ rm -f main.cf master.cf
+ touch master.cf
+ echo 'smtp inet n - n - - smtpd' >>master.cf
+ echo ' -o test1_process_name=$$process_name' >> master.cf
+ echo ' -o test1_service_name=$$service_name' >> master.cf
+ echo 'smtp unix n - n - - smtp' >>master.cf
+ echo ' -o test2_process_name=$$process_name' >> master.cf
+ echo ' -o test2_service_name=$$service_name' >> master.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xMfc. >test67.tmp 2>&1
+ diff test67.ref test67.tmp
+ rm -f main.cf master.cf test67.tmp
+
+test68: $(PROG) test68.ref
+ rm -f main.cf master.cf
+ touch master.cf
+ echo foo = ldap:`pwd` >> main.cf
+ echo 'alias_maps = $$foo/test68.cf' >> main.cf
+ echo " " mysql:`pwd`/test68.cf >> main.cf
+ echo " " pgsql:`pwd`/test68.cf >> main.cf
+ echo " " sqlite:`pwd`/test68.cf >> main.cf
+ echo " " memcache:`pwd`/test68.cf >> main.cf
+ echo junk = junk >> test68.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. 2>test68.tmp >/dev/null
+ sed "s;PWD;`pwd`;" test68.ref | diff - test68.tmp
+ rm -f main.cf master.cf test68.tmp test68.cf
+
+# See also test28 for user-defined parameters defined in main.cf.
+
+test69: $(PROG) test69.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo whatevershebrings unix - n n - 0 other >> master.cf
+ echo " -o ldap=ldap:`pwd`" >> master.cf
+ echo ' -o body_checks=$$ldap/test69.cf' >> master.cf
+ echo junk = junk >> test69.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test69.tmp 2>&1
+ sed "s;PWD;`pwd`;" test69.ref | diff - test69.tmp
+ rm -f main.cf master.cf test69.tmp test69.cf
+
+# Nasty nesting. Prove that the ldap table is detected. If support for
+# legacy table syntax is dropped, replace ldap:xyz with ldap:`pwd`/xyz
+# and update the good/bad parameter name tests.
+
+test70: $(PROG) test70.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ echo "smtpd_client_restrictions = check_sender_access {" >>main.cf
+ echo " pipemap:{ldap:used} { search_order = foo, bar } }" >>main.cf
+ echo "used_server_host = 127.0.0.1" >>main.cf
+ echo "unused_server_host = 127.0.0.1" >>main.cf
+ touch -t 197101010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test70.tmp 2>&1
+ diff test70.ref test70.tmp
+ rm -f main.cf master.cf test70.tmp test70.cf
+
+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 $(MAKES) $(AUTOS) $(DUMMIES) \
+ $(TEST_TMP) $(DB_MAKES)
+ rm -rf printfck
+
+tidy: clean
+
+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'
+postconf.o: ../../include/argv.h
+postconf.o: ../../include/check_arg.h
+postconf.o: ../../include/compat_level.h
+postconf.o: ../../include/dict.h
+postconf.o: ../../include/htable.h
+postconf.o: ../../include/mail_conf.h
+postconf.o: ../../include/mail_dict.h
+postconf.o: ../../include/mail_params.h
+postconf.o: ../../include/mail_run.h
+postconf.o: ../../include/mail_version.h
+postconf.o: ../../include/msg.h
+postconf.o: ../../include/msg_vstream.h
+postconf.o: ../../include/myflock.h
+postconf.o: ../../include/mymalloc.h
+postconf.o: ../../include/name_code.h
+postconf.o: ../../include/name_mask.h
+postconf.o: ../../include/stringops.h
+postconf.o: ../../include/sys_defs.h
+postconf.o: ../../include/vbuf.h
+postconf.o: ../../include/vstream.h
+postconf.o: ../../include/vstring.h
+postconf.o: ../../include/warn_stat.h
+postconf.o: postconf.c
+postconf.o: postconf.h
+postconf_builtin.o: ../../include/argv.h
+postconf_builtin.o: ../../include/attr.h
+postconf_builtin.o: ../../include/check_arg.h
+postconf_builtin.o: ../../include/dict.h
+postconf_builtin.o: ../../include/get_hostname.h
+postconf_builtin.o: ../../include/htable.h
+postconf_builtin.o: ../../include/inet_proto.h
+postconf_builtin.o: ../../include/iostuff.h
+postconf_builtin.o: ../../include/mail_addr.h
+postconf_builtin.o: ../../include/mail_conf.h
+postconf_builtin.o: ../../include/mail_params.h
+postconf_builtin.o: ../../include/mail_proto.h
+postconf_builtin.o: ../../include/mail_version.h
+postconf_builtin.o: ../../include/msg.h
+postconf_builtin.o: ../../include/myflock.h
+postconf_builtin.o: ../../include/mymalloc.h
+postconf_builtin.o: ../../include/mynetworks.h
+postconf_builtin.o: ../../include/name_code.h
+postconf_builtin.o: ../../include/nvtable.h
+postconf_builtin.o: ../../include/server_acl.h
+postconf_builtin.o: ../../include/stringops.h
+postconf_builtin.o: ../../include/sys_defs.h
+postconf_builtin.o: ../../include/vbuf.h
+postconf_builtin.o: ../../include/vstream.h
+postconf_builtin.o: ../../include/vstring.h
+postconf_builtin.o: bool_table.h
+postconf_builtin.o: bool_vars.h
+postconf_builtin.o: install_table.h
+postconf_builtin.o: install_vars.h
+postconf_builtin.o: int_table.h
+postconf_builtin.o: int_vars.h
+postconf_builtin.o: long_table.h
+postconf_builtin.o: long_vars.h
+postconf_builtin.o: nbool_table.h
+postconf_builtin.o: nbool_vars.h
+postconf_builtin.o: nint_table.h
+postconf_builtin.o: nint_vars.h
+postconf_builtin.o: postconf.h
+postconf_builtin.o: postconf_builtin.c
+postconf_builtin.o: raw_table.h
+postconf_builtin.o: raw_vars.h
+postconf_builtin.o: str_fn_table.h
+postconf_builtin.o: str_fn_vars.h
+postconf_builtin.o: str_table.h
+postconf_builtin.o: str_vars.h
+postconf_builtin.o: time_table.h
+postconf_builtin.o: time_vars.h
+postconf_dbms.o: ../../include/argv.h
+postconf_dbms.o: ../../include/check_arg.h
+postconf_dbms.o: ../../include/dict.h
+postconf_dbms.o: ../../include/dict_ht.h
+postconf_dbms.o: ../../include/dict_ldap.h
+postconf_dbms.o: ../../include/dict_memcache.h
+postconf_dbms.o: ../../include/dict_mysql.h
+postconf_dbms.o: ../../include/dict_pgsql.h
+postconf_dbms.o: ../../include/dict_proxy.h
+postconf_dbms.o: ../../include/dict_sqlite.h
+postconf_dbms.o: ../../include/htable.h
+postconf_dbms.o: ../../include/mac_expand.h
+postconf_dbms.o: ../../include/mac_parse.h
+postconf_dbms.o: ../../include/mail_conf.h
+postconf_dbms.o: ../../include/mail_params.h
+postconf_dbms.o: ../../include/msg.h
+postconf_dbms.o: ../../include/myflock.h
+postconf_dbms.o: ../../include/mymalloc.h
+postconf_dbms.o: ../../include/name_code.h
+postconf_dbms.o: ../../include/split_at.h
+postconf_dbms.o: ../../include/stringops.h
+postconf_dbms.o: ../../include/sys_defs.h
+postconf_dbms.o: ../../include/vbuf.h
+postconf_dbms.o: ../../include/vstream.h
+postconf_dbms.o: ../../include/vstring.h
+postconf_dbms.o: pcf_ldap_suffixes.h
+postconf_dbms.o: pcf_memcache_suffixes.h
+postconf_dbms.o: pcf_mysql_suffixes.h
+postconf_dbms.o: pcf_pgsql_suffixes.h
+postconf_dbms.o: pcf_sqlite_suffixes.h
+postconf_dbms.o: postconf.h
+postconf_dbms.o: postconf_dbms.c
+postconf_edit.o: ../../include/argv.h
+postconf_edit.o: ../../include/check_arg.h
+postconf_edit.o: ../../include/dict.h
+postconf_edit.o: ../../include/edit_file.h
+postconf_edit.o: ../../include/htable.h
+postconf_edit.o: ../../include/mail_params.h
+postconf_edit.o: ../../include/msg.h
+postconf_edit.o: ../../include/myflock.h
+postconf_edit.o: ../../include/mymalloc.h
+postconf_edit.o: ../../include/name_code.h
+postconf_edit.o: ../../include/readlline.h
+postconf_edit.o: ../../include/split_at.h
+postconf_edit.o: ../../include/stringops.h
+postconf_edit.o: ../../include/sys_defs.h
+postconf_edit.o: ../../include/vbuf.h
+postconf_edit.o: ../../include/vstream.h
+postconf_edit.o: ../../include/vstring.h
+postconf_edit.o: ../../include/vstring_vstream.h
+postconf_edit.o: postconf.h
+postconf_edit.o: postconf_edit.c
+postconf_lookup.o: ../../include/argv.h
+postconf_lookup.o: ../../include/check_arg.h
+postconf_lookup.o: ../../include/dict.h
+postconf_lookup.o: ../../include/htable.h
+postconf_lookup.o: ../../include/mac_expand.h
+postconf_lookup.o: ../../include/mac_parse.h
+postconf_lookup.o: ../../include/mail_conf.h
+postconf_lookup.o: ../../include/msg.h
+postconf_lookup.o: ../../include/myflock.h
+postconf_lookup.o: ../../include/mymalloc.h
+postconf_lookup.o: ../../include/name_code.h
+postconf_lookup.o: ../../include/stringops.h
+postconf_lookup.o: ../../include/sys_defs.h
+postconf_lookup.o: ../../include/vbuf.h
+postconf_lookup.o: ../../include/vstream.h
+postconf_lookup.o: ../../include/vstring.h
+postconf_lookup.o: postconf.h
+postconf_lookup.o: postconf_lookup.c
+postconf_main.o: ../../include/argv.h
+postconf_main.o: ../../include/check_arg.h
+postconf_main.o: ../../include/dict.h
+postconf_main.o: ../../include/htable.h
+postconf_main.o: ../../include/mac_expand.h
+postconf_main.o: ../../include/mac_parse.h
+postconf_main.o: ../../include/mail_conf.h
+postconf_main.o: ../../include/mail_params.h
+postconf_main.o: ../../include/msg.h
+postconf_main.o: ../../include/myflock.h
+postconf_main.o: ../../include/mymalloc.h
+postconf_main.o: ../../include/name_code.h
+postconf_main.o: ../../include/readlline.h
+postconf_main.o: ../../include/stringops.h
+postconf_main.o: ../../include/sys_defs.h
+postconf_main.o: ../../include/vbuf.h
+postconf_main.o: ../../include/vstream.h
+postconf_main.o: ../../include/vstring.h
+postconf_main.o: postconf.h
+postconf_main.o: postconf_main.c
+postconf_master.o: ../../include/argv.h
+postconf_master.o: ../../include/check_arg.h
+postconf_master.o: ../../include/dict.h
+postconf_master.o: ../../include/htable.h
+postconf_master.o: ../../include/mail_params.h
+postconf_master.o: ../../include/master_proto.h
+postconf_master.o: ../../include/msg.h
+postconf_master.o: ../../include/myflock.h
+postconf_master.o: ../../include/mymalloc.h
+postconf_master.o: ../../include/name_code.h
+postconf_master.o: ../../include/readlline.h
+postconf_master.o: ../../include/split_at.h
+postconf_master.o: ../../include/stringops.h
+postconf_master.o: ../../include/sys_defs.h
+postconf_master.o: ../../include/vbuf.h
+postconf_master.o: ../../include/vstream.h
+postconf_master.o: ../../include/vstring.h
+postconf_master.o: postconf.h
+postconf_master.o: postconf_master.c
+postconf_match.o: ../../include/argv.h
+postconf_match.o: ../../include/check_arg.h
+postconf_match.o: ../../include/dict.h
+postconf_match.o: ../../include/htable.h
+postconf_match.o: ../../include/msg.h
+postconf_match.o: ../../include/myflock.h
+postconf_match.o: ../../include/mymalloc.h
+postconf_match.o: ../../include/name_code.h
+postconf_match.o: ../../include/split_at.h
+postconf_match.o: ../../include/sys_defs.h
+postconf_match.o: ../../include/vbuf.h
+postconf_match.o: ../../include/vstream.h
+postconf_match.o: ../../include/vstring.h
+postconf_match.o: postconf.h
+postconf_match.o: postconf_match.c
+postconf_misc.o: ../../include/argv.h
+postconf_misc.o: ../../include/check_arg.h
+postconf_misc.o: ../../include/dict.h
+postconf_misc.o: ../../include/htable.h
+postconf_misc.o: ../../include/mail_conf.h
+postconf_misc.o: ../../include/mail_params.h
+postconf_misc.o: ../../include/myflock.h
+postconf_misc.o: ../../include/mymalloc.h
+postconf_misc.o: ../../include/name_code.h
+postconf_misc.o: ../../include/safe.h
+postconf_misc.o: ../../include/sys_defs.h
+postconf_misc.o: ../../include/vbuf.h
+postconf_misc.o: ../../include/vstream.h
+postconf_misc.o: ../../include/vstring.h
+postconf_misc.o: postconf.h
+postconf_misc.o: postconf_misc.c
+postconf_node.o: ../../include/argv.h
+postconf_node.o: ../../include/check_arg.h
+postconf_node.o: ../../include/dict.h
+postconf_node.o: ../../include/htable.h
+postconf_node.o: ../../include/msg.h
+postconf_node.o: ../../include/myflock.h
+postconf_node.o: ../../include/mymalloc.h
+postconf_node.o: ../../include/name_code.h
+postconf_node.o: ../../include/sys_defs.h
+postconf_node.o: ../../include/vbuf.h
+postconf_node.o: ../../include/vstream.h
+postconf_node.o: ../../include/vstring.h
+postconf_node.o: postconf.h
+postconf_node.o: postconf_node.c
+postconf_other.o: ../../include/argv.h
+postconf_other.o: ../../include/check_arg.h
+postconf_other.o: ../../include/dict.h
+postconf_other.o: ../../include/dns.h
+postconf_other.o: ../../include/htable.h
+postconf_other.o: ../../include/mbox_conf.h
+postconf_other.o: ../../include/msg.h
+postconf_other.o: ../../include/myaddrinfo.h
+postconf_other.o: ../../include/myflock.h
+postconf_other.o: ../../include/name_code.h
+postconf_other.o: ../../include/name_mask.h
+postconf_other.o: ../../include/sock_addr.h
+postconf_other.o: ../../include/sys_defs.h
+postconf_other.o: ../../include/tls.h
+postconf_other.o: ../../include/vbuf.h
+postconf_other.o: ../../include/vstream.h
+postconf_other.o: ../../include/vstring.h
+postconf_other.o: ../../include/xsasl.h
+postconf_other.o: postconf.h
+postconf_other.o: postconf_other.c
+postconf_print.o: ../../include/argv.h
+postconf_print.o: ../../include/check_arg.h
+postconf_print.o: ../../include/dict.h
+postconf_print.o: ../../include/htable.h
+postconf_print.o: ../../include/msg.h
+postconf_print.o: ../../include/myflock.h
+postconf_print.o: ../../include/name_code.h
+postconf_print.o: ../../include/sys_defs.h
+postconf_print.o: ../../include/vbuf.h
+postconf_print.o: ../../include/vstream.h
+postconf_print.o: ../../include/vstring.h
+postconf_print.o: postconf.h
+postconf_print.o: postconf_print.c
+postconf_service.o: ../../include/argv.h
+postconf_service.o: ../../include/check_arg.h
+postconf_service.o: ../../include/dict.h
+postconf_service.o: ../../include/htable.h
+postconf_service.o: ../../include/mail_params.h
+postconf_service.o: ../../include/msg.h
+postconf_service.o: ../../include/myflock.h
+postconf_service.o: ../../include/mymalloc.h
+postconf_service.o: ../../include/name_code.h
+postconf_service.o: ../../include/stringops.h
+postconf_service.o: ../../include/sys_defs.h
+postconf_service.o: ../../include/vbuf.h
+postconf_service.o: ../../include/vstream.h
+postconf_service.o: ../../include/vstring.h
+postconf_service.o: postconf.h
+postconf_service.o: postconf_service.c
+postconf_unused.o: ../../include/argv.h
+postconf_unused.o: ../../include/check_arg.h
+postconf_unused.o: ../../include/dict.h
+postconf_unused.o: ../../include/htable.h
+postconf_unused.o: ../../include/mail_conf.h
+postconf_unused.o: ../../include/mail_params.h
+postconf_unused.o: ../../include/msg.h
+postconf_unused.o: ../../include/myflock.h
+postconf_unused.o: ../../include/name_code.h
+postconf_unused.o: ../../include/sys_defs.h
+postconf_unused.o: ../../include/vbuf.h
+postconf_unused.o: ../../include/vstream.h
+postconf_unused.o: ../../include/vstring.h
+postconf_unused.o: postconf.h
+postconf_unused.o: postconf_unused.c
+postconf_user.o: ../../include/argv.h
+postconf_user.o: ../../include/check_arg.h
+postconf_user.o: ../../include/dict.h
+postconf_user.o: ../../include/htable.h
+postconf_user.o: ../../include/mac_expand.h
+postconf_user.o: ../../include/mac_parse.h
+postconf_user.o: ../../include/mail_conf.h
+postconf_user.o: ../../include/mail_params.h
+postconf_user.o: ../../include/msg.h
+postconf_user.o: ../../include/myflock.h
+postconf_user.o: ../../include/mymalloc.h
+postconf_user.o: ../../include/name_code.h
+postconf_user.o: ../../include/stringops.h
+postconf_user.o: ../../include/sys_defs.h
+postconf_user.o: ../../include/vbuf.h
+postconf_user.o: ../../include/vstream.h
+postconf_user.o: ../../include/vstring.h
+postconf_user.o: postconf.h
+postconf_user.o: postconf_user.c
diff --git a/src/postconf/extract.awk b/src/postconf/extract.awk
new file mode 100644
index 0000000..809020d
--- /dev/null
+++ b/src/postconf/extract.awk
@@ -0,0 +1,201 @@
+# Extract initialization tables from actual source code.
+
+# XXX: Associated variable aliasing:
+#
+# Some parameters bind to different variables in different contexts,
+# And other parameters map to associated variables in a many-to-1
+# fashion. This is mostly the result of the SMTP+LMTP integration
+# and the overloading of parameters that have identical semantics,
+# for the corresponding context.
+#
+# The "++table[...]" below ignores the associated variable name
+# when doing duplicate elimination. Differences in the default value
+# or lower/upper bounds still result in "postconf -d" duplicates,
+# which are a sign of an error somewhere...
+#
+# XXX Work around ancient AWK implementations with a 10 file limit
+# and no working close() operator (e.g. Solaris). Some systems
+# have a more modern implementation that is XPG4-compatible, but it
+# is too much bother to find out where each system keeps these.
+
+{ owned_by_library = (FILENAME ~ /\/(global|tls)\//) }
+
+/^(static| )*(const +)?CONFIG_INT_TABLE .*\{/,/\};/ {
+ if ($1 ~ /VAR/) {
+ if (!owned_by_library)
+ int_vars["int " substr($3,2,length($3)-2) ";"] = 1
+ if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ int_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_STR_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ str_vars["char *" substr($3,2,length($3)-2) ";"] = 1
+ if (++stab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ str_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_STR_FN_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ str_fn_vars["char *" substr($3,2,length($3)-2) ";"] = 1
+ $2 = "pcf_" $2
+ if (++stab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ str_fn_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_RAW_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ raw_vars["char *" substr($3,2,length($3)-2) ";"] = 1
+ if (++rtab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ raw_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_BOOL_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ bool_vars["int " substr($3,2,length($3)-2) ";"] = 1
+ if (++btab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ bool_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_TIME_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ time_vars["int " substr($3,2,length($3)-2) ";"] = 1
+ if (++ttab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ time_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_NINT_TABLE .*\{/,/\};/ {
+ if ($1 ~ /VAR/) {
+ if (!owned_by_library)
+ nint_vars["int " substr($3,2,length($3)-2) ";"] = 1
+ if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ nint_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_NBOOL_TABLE .*\{/,/\};/ {
+ if ($1 ~ /^VAR/) {
+ if (!owned_by_library)
+ nbool_vars["int " substr($3,2,length($3)-2) ";"] = 1
+ if (++btab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ nbool_table[$0] = 1
+ }
+ }
+}
+/^(static| )*(const +)?CONFIG_LONG_TABLE .*\{/,/\};/ {
+ if ($1 ~ /VAR/) {
+ if (!owned_by_library)
+ long_vars["long " substr($3,2,length($3)-2) ";"] = 1
+ if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) {
+ long_table[$0] = 1
+ }
+ }
+}
+
+END {
+ # Print parameter declarations without busting old AWK's file limit.
+ print "cat >int_vars.h <<'EOF'"
+ for (key in int_vars)
+ print key
+ print "EOF"
+
+ print "cat >str_vars.h <<'EOF'"
+ for (key in str_vars)
+ print key
+ print "EOF"
+
+ print "cat >str_fn_vars.h <<'EOF'"
+ for (key in str_fn_vars)
+ print key
+ print "EOF"
+
+ print "cat >raw_vars.h <<'EOF'"
+ for (key in raw_vars)
+ print key
+ print "EOF"
+
+ print "cat >bool_vars.h <<'EOF'"
+ for (key in bool_vars)
+ print key
+ print "EOF"
+
+ print "cat >time_vars.h <<'EOF'"
+ for (key in time_vars)
+ print key
+ print "EOF"
+
+ print "cat >nint_vars.h <<'EOF'"
+ for (key in nint_vars)
+ print key
+ print "EOF"
+
+ print "cat >nbool_vars.h <<'EOF'"
+ for (key in nbool_vars)
+ print key
+ print "EOF"
+
+ print "cat >long_vars.h <<'EOF'"
+ for (key in long_vars)
+ print key
+ print "EOF"
+
+ # Print parameter initializations without busting old AWK's file limit.
+ print "sed 's/[ ][ ]*/ /g' >int_table.h <<'EOF'"
+ for (key in int_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >str_table.h <<'EOF'"
+ for (key in str_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >str_fn_table.h <<'EOF'"
+ for (key in str_fn_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >raw_table.h <<'EOF'"
+ for (key in raw_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >bool_table.h <<'EOF'"
+ for (key in bool_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >time_table.h <<'EOF'"
+ for (key in time_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >nint_table.h <<'EOF'"
+ for (key in nint_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >nbool_table.h <<'EOF'"
+ for (key in nbool_table)
+ print key
+ print "EOF"
+
+ print "sed 's/[ ][ ]*/ /g' >long_table.h <<'EOF'"
+ for (key in long_table)
+ print key
+ print "EOF"
+
+ # Flush output nicely.
+ exit(0);
+}
diff --git a/src/postconf/extract_cfg.sh b/src/postconf/extract_cfg.sh
new file mode 100644
index 0000000..5901e95
--- /dev/null
+++ b/src/postconf/extract_cfg.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# To view the formatted manual page of this file, type:
+# POSTFIXSOURCE/mantools/srctoman - extract_cfg.sh | nroff -man
+
+#++
+# NAME
+# extract_cfg 1
+# SUMMARY
+# extract database parameter names from cfg_get_xxx() calls
+# SYNOPSIS
+# \fBextract_cfg [-d|-s] [\fIfile...\fB]\fR
+# DESCRIPTION
+# The \fBextract_cfg\fR command extracts the parameter names
+# from cfg_get_{str,int,bool}() calls in dict_xxx.c files. The
+# output is one parameter name per line, formatted as a C string
+# followed by comma.
+#
+# Options:
+# .IP \fB-d\fR
+# Add the "domain" parameter to the output. This is used by
+# the LDAP, memcache, and *SQL* tables.
+# .IP \fB-s\fR
+# Add the legacy SQL query parameters: "select_field", "table",
+# "where_field", and "additional_conditions".
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# .ad
+# .fi
+# This command was introduced with Postfix 3.3.
+# AUTHOR(S)
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#--
+
+# In case not installed.
+m4 </dev/null || exit 1
+
+# Flags to add db_common parameter names.
+add_legacy_sql_query_params=
+add_domain_param=
+
+# Parse JCL.
+
+while :
+do
+ case "$1" in
+ -d) add_domain_param=1;;
+ -s) add_legacy_sql_query_params=1;;
+ -*) echo Bad option: $1 1>&2; exit 1;;
+ *) break;;
+ esac
+ shift
+done
+
+# We use m4 macros to extract arguments from cfg_get_xxx() calls that
+# may span multiple lines. We sandwich information of interest between
+# control-A characters. Multiple cfg_get_xxx() calls on the same line
+# should be OK, as long as the calls don't nest.
+
+(
+cat <<'EOF'
+define(`cfg_get_str',`$2
+')dnl
+define(`cfg_get_int',`$2
+')dnl
+define(`cfg_get_bool',`$2
+')dnl
+EOF
+# Convert selected C macro definitions into m4 macro definitions.
+sed 's/^#define[ ]*\([DICT_MC_NAME_A-Za-z0-9_]*\)[ ]*\("[^"]*"\)/define(`\1'"'"',`\2'"'"')/' "$@"
+) | m4 | awk -F '// { print $2 }' | (
+test -n "$add_domain_param" && {
+cat <<EOF
+"domain"
+EOF
+}
+test -n "$add_legacy_sql_query_params" && {
+cat <<EOF
+"table"
+"select_field"
+"where_field"
+"additional_conditions"
+EOF
+}
+cat -
+) | sort -u | sed 's/$/,/'
diff --git a/src/postconf/install_table.h b/src/postconf/install_table.h
new file mode 100644
index 0000000..0e0ea0a
--- /dev/null
+++ b/src/postconf/install_table.h
@@ -0,0 +1,2 @@
+ VAR_CONFIG_DIR, DEF_CONFIG_DIR, &var_config_dir, 1, 0,
+ VAR_DEBUG_COMMAND, "", &var_debug_command, 1, 0,
diff --git a/src/postconf/install_vars.h b/src/postconf/install_vars.h
new file mode 100644
index 0000000..746c81e
--- /dev/null
+++ b/src/postconf/install_vars.h
@@ -0,0 +1 @@
+char *var_debug_command;
diff --git a/src/postconf/postconf.c b/src/postconf/postconf.c
new file mode 100644
index 0000000..f598a5b
--- /dev/null
+++ b/src/postconf/postconf.c
@@ -0,0 +1,1113 @@
+/*++
+/* NAME
+/* postconf 1
+/* SUMMARY
+/* Postfix configuration utility
+/* SYNOPSIS
+/* .fi
+/* .ti -4
+/* \fBManaging main.cf:\fR
+/*
+/* \fBpostconf\fR [\fB-dfhHnopvx\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fB-C \fIclass,...\fR] [\fIparameter ...\fR]
+/*
+/* \fBpostconf\fR [\fB-epv\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIparameter\fB=\fIvalue ...\fR
+/*
+/* \fBpostconf\fR \fB-#\fR [\fB-pv\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIparameter ...\fR
+/*
+/* \fBpostconf\fR \fB-X\fR [\fB-pv\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIparameter ...\fR
+/*
+/* .ti -4
+/* \fBManaging master.cf service entries:\fR
+/*
+/* \fBpostconf\fR \fB-M\fR [\fB-fovx\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fIservice\fR[\fB/\fItype\fR]\fI ...\fR]
+/*
+/* \fBpostconf\fR \fB-M\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype\fB=\fIvalue ...\fR
+/*
+/* \fBpostconf\fR \fB-M#\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype ...\fR
+/*
+/* \fBpostconf\fR \fB-MX\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype ...\fR
+/*
+/* .ti -4
+/* \fBManaging master.cf service fields:\fR
+/*
+/* \fBpostconf\fR \fB-F\fR [\fB-fhHovx\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIfield\fR]]\fI ...\fR]
+/*
+/* \fBpostconf\fR \fB-F\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype\fB/\fIfield\fB=\fIvalue ...\fR
+/*
+/* .ti -4
+/* \fBManaging master.cf service parameters:\fR
+/*
+/* \fBpostconf\fR \fB-P\fR [\fB-fhHovx\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIparameter\fR]]\fI ...\fR]
+/*
+/* \fBpostconf\fR \fB-P\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype\fB/\fIparameter\fB=\fIvalue ...\fR
+/*
+/* \fBpostconf\fR \fB-PX\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/* \fIservice\fB/\fItype\fB/\fIparameter ...\fR
+/*
+/* .ti -4
+/* \fBManaging bounce message templates:\fR
+/*
+/* \fBpostconf\fR \fB-b\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fItemplate_file\fR]
+/*
+/* \fBpostconf\fR \fB-t\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fItemplate_file\fR]
+/*
+/* .ti -4
+/* \fBManaging TLS features:\fR
+/*
+/* \fBpostconf\fR \fB-T \fImode\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR]
+/*
+/* .ti -4
+/* \fBManaging other configuration:\fR
+/*
+/* \fBpostconf\fR \fB-a\fR|\fB-A\fR|\fB-l\fR|\fB-m\fR [\fB-v\fR]
+/* [\fB-c \fIconfig_dir\fR]
+/* DESCRIPTION
+/* By default, the \fBpostconf\fR(1) command displays the
+/* values of \fBmain.cf\fR configuration parameters, and warns
+/* about possible mis-typed parameter names (Postfix 2.9 and later).
+/* The command can also change \fBmain.cf\fR configuration
+/* parameter values, or display other configuration information
+/* about the Postfix mail system.
+/*
+/* Options:
+/* .IP \fB-a\fR
+/* List the available SASL plug-in types for the Postfix SMTP
+/* server. The plug-in type is selected with the \fBsmtpd_sasl_type\fR
+/* configuration parameter by specifying one of the names
+/* listed below.
+/* .RS
+/* .IP \fBcyrus\fR
+/* This server plug-in is available when Postfix is built with
+/* Cyrus SASL support.
+/* .IP \fBdovecot\fR
+/* This server plug-in uses the Dovecot authentication server,
+/* and is available when Postfix is built with any form of SASL
+/* support.
+/* .RE
+/* .IP
+/* This feature is available with Postfix 2.3 and later.
+/* .IP \fB-A\fR
+/* List the available SASL plug-in types for the Postfix SMTP
+/* client. The plug-in type is selected with the \fBsmtp_sasl_type\fR
+/* or \fBlmtp_sasl_type\fR configuration parameters by specifying
+/* one of the names listed below.
+/* .RS
+/* .IP \fBcyrus\fR
+/* This client plug-in is available when Postfix is built with
+/* Cyrus SASL support.
+/* .RE
+/* .IP
+/* This feature is available with Postfix 2.3 and later.
+/* .IP "\fB-b\fR [\fItemplate_file\fR]"
+/* Display the message text that appears at the beginning of
+/* delivery status notification (DSN) messages, expanding
+/* $\fBname\fR expressions with actual values as described in
+/* \fBbounce\fR(5).
+/*
+/* To override the \fBbounce_template_file\fR parameter setting,
+/* specify a template file name at the end of the "\fBpostconf
+/* -b\fR" command line. Specify an empty file name to display
+/* built-in templates (in shell language: "").
+/*
+/* This feature is available with Postfix 2.3 and later.
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory.
+/* .IP "\fB-C \fIclass,...\fR"
+/* When displaying \fBmain.cf\fR parameters, select only
+/* parameters from the specified class(es):
+/* .RS
+/* .IP \fBbuiltin\fR
+/* Parameters with built-in names.
+/* .IP \fBservice\fR
+/* Parameters with service-defined names (the first field of
+/* a \fBmaster.cf\fR entry plus a Postfix-defined suffix).
+/* .IP \fBuser\fR
+/* Parameters with user-defined names.
+/* .IP \fBall\fR
+/* All the above classes.
+/* .RE
+/* .IP
+/* The default is as if "\fB-C all\fR" is
+/* specified.
+/*
+/* This feature is available with Postfix 2.9 and later.
+/* .IP \fB-d\fR
+/* Print \fBmain.cf\fR default parameter settings instead of
+/* actual settings.
+/* Specify \fB-df\fR to fold long lines for human readability
+/* (Postfix 2.9 and later).
+/* .IP \fB-e\fR
+/* Edit the \fBmain.cf\fR configuration file, and update
+/* parameter settings with the "\fIname=value\fR" pairs on the
+/* \fBpostconf\fR(1) command line.
+/*
+/* With \fB-M\fR, edit the \fBmaster.cf\fR configuration file,
+/* and replace one or more service entries with new values as
+/* specified with "\fIservice/type=value\fR" on the \fBpostconf\fR(1)
+/* command line.
+/*
+/* With \fB-F\fR, edit the \fBmaster.cf\fR configuration file,
+/* and replace one or more service fields with new values as
+/* specified with "\fIservice/type/field=value\fR" on the
+/* \fBpostconf\fR(1) command line. Currently, the "command"
+/* field contains the command name and command arguments. This
+/* may change in the near future, so that the "command" field
+/* contains only the command name, and a new "arguments"
+/* pseudofield contains the command arguments.
+/*
+/* With \fB-P\fR, edit the \fBmaster.cf\fR configuration file,
+/* and add or update one or more service parameter settings
+/* (-o parameter=value settings) with new values as specified
+/* with "\fIservice/type/parameter=value\fR" on the \fBpostconf\fR(1)
+/* command line.
+/*
+/* In all cases the file is copied to a temporary file then
+/* renamed into place. Specify quotes to protect special
+/* characters and whitespace on the \fBpostconf\fR(1) command
+/* line.
+/*
+/* The \fB-e\fR option is no longer needed with Postfix version
+/* 2.8 and later, as it is assumed whenever a value is specified
+/* (empty or non-empty).
+/* .IP \fB-f\fR
+/* Fold long lines when printing \fBmain.cf\fR or \fBmaster.cf\fR
+/* configuration file entries, for human readability.
+/*
+/* This feature is available with Postfix 2.9 and later.
+/* .IP \fB-F\fR
+/* Show \fBmaster.cf\fR per-entry field settings (by default
+/* all services and all fields), formatted as
+/* "\fIservice/type/field=value\fR", one per line. Specify
+/* \fB-Ff\fR to fold long lines.
+/*
+/* Specify one or more "\fIservice/type/field\fR" instances
+/* on the \fBpostconf\fR(1) command line to limit the output
+/* to fields of interest. Trailing parameter name or service
+/* type fields that are omitted will be handled as "*" wildcard
+/* fields.
+/*
+/* This feature is available with Postfix 2.11 and later.
+/* .IP \fB-h\fR
+/* Show parameter or attribute values without the "\fIname\fR = "
+/* label that normally precedes the value.
+/* .IP \fB-H\fR
+/* Show parameter or attribute names without the " = \fIvalue\fR"
+/* that normally follows the name.
+/*
+/* This feature is available with Postfix 3.1 and later.
+/* .IP \fB-l\fR
+/* List the names of all supported mailbox locking methods.
+/* Postfix supports the following methods:
+/* .RS
+/* .IP \fBflock\fR
+/* A kernel-based advisory locking method for local files only.
+/* This locking method is available on systems with a BSD
+/* compatible library.
+/* .IP \fBfcntl\fR
+/* A kernel-based advisory locking method for local and remote
+/* files.
+/* .IP \fBdotlock\fR
+/* An application-level locking method. An application locks
+/* a file named \fIfilename\fR by creating a file named
+/* \fIfilename\fB.lock\fR. The application is expected to
+/* remove its own lock file, as well as stale lock files that
+/* were left behind after abnormal program termination.
+/* .RE
+/* .IP \fB-m\fR
+/* List the names of all supported lookup table types. In
+/* Postfix configuration files, lookup tables are specified
+/* as \fItype\fB:\fIname\fR, where \fItype\fR is one of the
+/* types listed below. The table \fIname\fR syntax depends on
+/* the lookup table type as described in the DATABASE_README
+/* document.
+/* .RS
+/* .IP \fBbtree\fR
+/* A sorted, balanced tree structure. Available on systems
+/* with support for Berkeley DB databases.
+/* .IP \fBcdb\fR
+/* A read-optimized structure with no support for incremental
+/* updates. Available on systems with support for CDB databases.
+/*
+/* This feature is available with Postfix 2.2 and later.
+/* .IP \fBcidr\fR
+/* A table that associates values with Classless Inter-Domain
+/* Routing (CIDR) patterns. This is described in \fBcidr_table\fR(5).
+/*
+/* This feature is available with Postfix 2.2 and later.
+/* .IP \fBdbm\fR
+/* An indexed file type based on hashing. Available on systems
+/* with support for DBM databases.
+/* .IP \fBenviron\fR
+/* The UNIX process environment array. The lookup key is the
+/* environment variable name; the table name is ignored. Originally
+/* implemented for testing, someone may find this useful someday.
+/* .IP \fBfail\fR
+/* A table that reliably fails all requests. The lookup table
+/* name is used for logging. This table exists to simplify
+/* Postfix error tests.
+/*
+/* This feature is available with Postfix 2.9 and later.
+/* .IP \fBhash\fR
+/* An indexed file type based on hashing. Available on systems
+/* with support for Berkeley DB databases.
+/* .IP "\fBinline\fR (read-only)"
+/* A non-shared, in-memory lookup table. Example: "\fBinline:{
+/* \fIkey\fB=\fIvalue\fB, { \fIkey\fB = \fItext with whitespace
+/* or comma\fB }}\fR". Key-value pairs are separated by
+/* whitespace or comma; with a key-value pair inside "\fB{}\fR",
+/* whitespace is ignored after the opening "\fB{\fR", around
+/* the "\fB=\fR" between key and value, and before the closing
+/* "\fB}\fR". Inline tables eliminate the need to create a
+/* database file for just a few fixed elements. See also the
+/* \fIstatic:\fR map type.
+/*
+/* This feature is available with Postfix 3.0 and later.
+/* .IP \fBinternal\fR
+/* A non-shared, in-memory hash table. Its content are lost
+/* when a process terminates.
+/* .IP "\fBlmdb\fR"
+/* OpenLDAP LMDB database (a memory-mapped, persistent file).
+/* Available on systems with support for LMDB databases. This
+/* is described in \fBlmdb_table\fR(5).
+/*
+/* This feature is available with Postfix 2.11 and later.
+/* .IP "\fBldap\fR (read-only)"
+/* LDAP database client. This is described in \fBldap_table\fR(5).
+/* .IP "\fBmemcache\fR"
+/* Memcache database client. This is described in
+/* \fBmemcache_table\fR(5).
+/*
+/* This feature is available with Postfix 2.9 and later.
+/* .IP "\fBmysql\fR (read-only)"
+/* MySQL database client. Available on systems with support
+/* for MySQL databases. This is described in \fBmysql_table\fR(5).
+/* .IP "\fBpcre\fR (read-only)"
+/* A lookup table based on Perl Compatible Regular Expressions.
+/* The file format is described in \fBpcre_table\fR(5).
+/* .IP "\fBpgsql\fR (read-only)"
+/* PostgreSQL database client. This is described in
+/* \fBpgsql_table\fR(5).
+/*
+/* This feature is available with Postfix 2.1 and later.
+/* .IP "\fBpipemap\fR (read-only)"
+/* A lookup table that constructs a pipeline of tables. Example:
+/* "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fB}\fR".
+/* Each "pipemap:" query is given to the first table. Each
+/* lookup result becomes the query for the next table in the
+/* pipeline, and the last table produces the final result.
+/* When any table lookup produces no result, the pipeline
+/* produces no result. The first and last characters of the
+/* "pipemap:" table name must be "\fB{\fR" and "\fB}\fR".
+/* Within these, individual maps are separated with comma or
+/* whitespace.
+/*
+/* This feature is available with Postfix 3.0 and later.
+/* .IP "\fBproxy\fR"
+/* Postfix \fBproxymap\fR(8) client for shared access to Postfix
+/* databases. The table name syntax is \fItype\fB:\fIname\fR.
+/*
+/* This feature is available with Postfix 2.0 and later.
+/* .IP "\fBrandmap\fR (read-only)"
+/* An in-memory table that performs random selection. Example:
+/* "\fBrandmap:{\fIresult_1, ..., result_n\fB}\fR". Each table query
+/* returns a random choice from the specified results. The first
+/* and last characters of the "randmap:" table name must be
+/* "\fB{\fR" and "\fB}\fR". Within these, individual results
+/* are separated with comma or whitespace. To give a specific
+/* result more weight, specify it multiple times.
+/*
+/* This feature is available with Postfix 3.0 and later.
+/* .IP "\fBregexp\fR (read-only)"
+/* A lookup table based on regular expressions. The file format
+/* is described in \fBregexp_table\fR(5).
+/* .IP \fBsdbm\fR
+/* An indexed file type based on hashing. Available on systems
+/* with support for SDBM databases.
+/*
+/* This feature is available with Postfix 2.2 and later.
+/* .IP "\fBsocketmap\fR (read-only)"
+/* Sendmail-style socketmap client. The table name is
+/* \fBinet\fR:\fIhost\fR:\fIport\fR:\fIname\fR for a TCP/IP
+/* server, or \fBunix\fR:\fIpathname\fR:\fIname\fR for a
+/* UNIX-domain server. This is described in \fBsocketmap_table\fR(5).
+/*
+/* This feature is available with Postfix 2.10 and later.
+/* .IP "\fBsqlite\fR (read-only)"
+/* SQLite database. This is described in \fBsqlite_table\fR(5).
+/*
+/* This feature is available with Postfix 2.8 and later.
+/* .IP "\fBstatic\fR (read-only)"
+/* A table that always returns its name as lookup result. For
+/* example, \fBstatic:foobar\fR always returns the string
+/* \fBfoobar\fR as lookup result. Specify "\fBstatic:{ \fItext
+/* with whitespace\fB }\fR" when the result contains whitespace;
+/* this form ignores whitespace after the opening "\fB{\fR"
+/* and before the closing
+/* "\fB}\fR". See also the \fIinline:\fR map.
+/*
+/* The form "\fBstatic:{\fItext\fB}\fR is available with Postfix
+/* 3.0 and later.
+/* .IP "\fBtcp\fR (read-only)"
+/* TCP/IP client. The protocol is described in \fBtcp_table\fR(5).
+/* .IP "\fBtexthash\fR (read-only)"
+/* Produces similar results as hash: files, except that you
+/* don't need to run the \fBpostmap\fR(1) command before you
+/* can use the file, and that it does not detect changes after
+/* the file is read.
+/*
+/* This feature is available with Postfix 2.8 and later.
+/* .IP "\fBunionmap\fR (read-only)"
+/* A table that sends each query to multiple lookup tables and
+/* that concatenates all found results, separated by comma.
+/* The table name syntax is the same as for \fBpipemap\fR.
+/*
+/* This feature is available with Postfix 3.0 and later.
+/* .IP "\fBunix\fR (read-only)"
+/* A limited view of the UNIX authentication database. The
+/* following tables are implemented:
+/* .RS
+/*. IP \fBunix:passwd.byname\fR
+/* The table is the UNIX password database. The key is a login
+/* name. The result is a password file entry in \fBpasswd\fR(5)
+/* format.
+/* .IP \fBunix:group.byname\fR
+/* The table is the UNIX group database. The key is a group
+/* name. The result is a group file entry in \fBgroup\fR(5)
+/* format.
+/* .RE
+/* .RE
+/* .IP
+/* Other table types may exist depending on how Postfix was
+/* built.
+/* .IP \fB-M\fR
+/* Show \fBmaster.cf\fR file contents instead of \fBmain.cf\fR
+/* file contents. Specify \fB-Mf\fR to fold long lines for
+/* human readability.
+/*
+/* Specify zero or more arguments, each with a \fIservice-name\fR
+/* or \fIservice-name/service-type\fR pair, where \fIservice-name\fR
+/* is the first field of a master.cf entry and \fIservice-type\fR
+/* is one of (\fBinet\fR, \fBunix\fR, \fBfifo\fR, or \fBpass\fR).
+/*
+/* If \fIservice-name\fR or \fIservice-name/service-type\fR
+/* is specified, only the matching master.cf entries will be
+/* output. For example, "\fBpostconf -Mf smtp\fR" will output
+/* all services named "smtp", and "\fBpostconf -Mf smtp/inet\fR"
+/* will output only the smtp service that listens on the
+/* network. Trailing service type fields that are omitted
+/* will be handled as "*" wildcard fields.
+/*
+/* This feature is available with Postfix 2.9 and later. The
+/* syntax was changed from "\fIname.type\fR" to "\fIname/type\fR",
+/* and "*" wildcard support was added with Postfix 2.11.
+/* .IP \fB-n\fR
+/* Show only configuration parameters that have explicit
+/* \fIname=value\fR settings in \fBmain.cf\fR. Specify \fB-nf\fR
+/* to fold long lines for human readability (Postfix 2.9 and
+/* later). To show settings that differ from built-in defaults
+/* only, use the following bash syntax:
+/* .nf
+/* LANG=C comm -23 <(postconf -n) <(postconf -d)
+/* .fi
+/* Replace "-23" with "-12" to show settings that duplicate
+/* built-in defaults.
+/* .IP "\fB-o \fIname=value\fR"
+/* Override \fBmain.cf\fR parameter settings. This lets you see
+/* the effect changing a parameter would have when it is used in
+/* other configuration parameters, e.g.:
+/* .nf
+/* postconf -x -o stress=yes
+/* .fi
+/*
+/* This feature is available with Postfix 2.10 and later.
+/* .IP \fB-p\fR
+/* Show \fBmain.cf\fR parameter settings. This is the default.
+/*
+/* This feature is available with Postfix 2.11 and later.
+/* .IP \fB-P\fR
+/* Show \fBmaster.cf\fR service parameter settings (by default
+/* all services and all parameters), formatted as
+/* "\fIservice/type/parameter=value\fR", one per line. Specify
+/* \fB-Pf\fR to fold long lines.
+/*
+/* Specify one or more "\fIservice/type/parameter\fR" instances
+/* on the \fBpostconf\fR(1) command line to limit the output
+/* to parameters of interest. Trailing parameter name or
+/* service type fields that are omitted will be handled as "*"
+/* wildcard fields.
+/*
+/* This feature is available with Postfix 2.11 and later.
+/* .IP "\fB-t\fR [\fItemplate_file\fR]"
+/* Display the templates for text that appears at the beginning
+/* of delivery status notification (DSN) messages, without
+/* expanding $\fBname\fR expressions.
+/*
+/* To override the \fBbounce_template_file\fR parameter setting,
+/* specify a template file name at the end of the "\fBpostconf
+/* -t\fR" command line. Specify an empty file name to display
+/* built-in templates (in shell language: "").
+/*
+/* This feature is available with Postfix 2.3 and later.
+/* .IP "\fB-T \fImode\fR"
+/* If Postfix is compiled without TLS support, the \fB-T\fR option
+/* produces no output. Otherwise, if an invalid \fImode\fR is specified,
+/* the \fB-T\fR option reports an error and exits with a non-zero status
+/* code. The valid modes are:
+/* .RS
+/* .IP \fBcompile-version\fR
+/* Output the OpenSSL version that Postfix was compiled with
+/* (i.e. the OpenSSL version in a header file). The output
+/* format is the same as with the command "\fBopenssl version\fR".
+/* .IP \fBrun-version\fR
+/* Output the OpenSSL version that Postfix is linked with at
+/* runtime (i.e. the OpenSSL version in a shared library).
+/* .IP \fBpublic-key-algorithms\fR
+/* Output the lower-case names of the supported public-key
+/* algorithms, one per-line.
+/* .RE
+/* .IP
+/* This feature is available with Postfix 3.1 and later.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple
+/* \fB-v\fR options make the software increasingly verbose.
+/* .IP \fB-x\fR
+/* Expand \fI$name\fR in \fBmain.cf\fR or \fBmaster.cf\fR
+/* parameter values. The expansion is recursive.
+/*
+/* This feature is available with Postfix 2.10 and later.
+/* .IP \fB-X\fR
+/* Edit the \fBmain.cf\fR configuration file, and remove the
+/* parameters named on the \fBpostconf\fR(1) command line.
+/* Specify a list of parameter names, not "\fIname=value\fR"
+/* pairs.
+/*
+/* With \fB-M\fR, edit the \fBmaster.cf\fR configuration file,
+/* and remove one or more service entries as specified with
+/* "\fIservice/type\fR" on the \fBpostconf\fR(1) command line.
+/*
+/* With \fB-P\fR, edit the \fBmaster.cf\fR configuration file,
+/* and remove one or more service parameter settings (-o
+/* parameter=value settings) as specified with
+/* "\fIservice/type/parameter\fR" on the \fBpostconf\fR(1)
+/* command line.
+/*
+/* In all cases the file is copied to a temporary file then
+/* renamed into place. Specify quotes to protect special
+/* characters on the \fBpostconf\fR(1) command line.
+/*
+/* There is no \fBpostconf\fR(1) command to perform the reverse
+/* operation.
+/*
+/* This feature is available with Postfix 2.10 and later.
+/* Support for -M and -P was added with Postfix 2.11.
+/* .IP \fB-#\fR
+/* Edit the \fBmain.cf\fR configuration file, and comment out
+/* the parameters named on the \fBpostconf\fR(1) command line,
+/* so that those parameters revert to their default values.
+/* Specify a list of parameter names, not "\fIname=value\fR"
+/* pairs.
+/*
+/* With \fB-M\fR, edit the \fBmaster.cf\fR configuration file,
+/* and comment out one or more service entries as specified
+/* with "\fIservice/type\fR" on the \fBpostconf\fR(1) command
+/* line.
+/*
+/* In all cases the file is copied to a temporary file then
+/* renamed into place. Specify quotes to protect special
+/* characters on the \fBpostconf\fR(1) command line.
+/*
+/* There is no \fBpostconf\fR(1) command to perform the reverse
+/* operation.
+/*
+/* This feature is available with Postfix 2.6 and later. Support
+/* for -M was added with Postfix 2.11.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially
+/* relevant to this program.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBbounce_template_file (empty)\fR"
+/* Pathname of a configuration file with bounce message templates.
+/* FILES
+/* /etc/postfix/main.cf, Postfix configuration parameters
+/* /etc/postfix/master.cf, Postfix master daemon configuration
+/* SEE ALSO
+/* bounce(5), bounce template file format
+/* master(5), master.cf configuration file syntax
+/* postconf(5), main.cf configuration file syntax
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+/* html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* DATABASE_README, Postfix lookup table overview
+/* 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/stat.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <dict.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <name_mask.h>
+#include <warn_stat.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_version.h>
+#include <mail_run.h>
+#include <mail_dict.h>
+#include <compat_level.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * Global storage. See postconf.h for description.
+ */
+PCF_PARAM_TABLE *pcf_param_table;
+PCF_MASTER_ENT *pcf_master_table;
+int pcf_cmd_mode = PCF_DEF_MODE;
+
+ /*
+ * Application fingerprinting.
+ */
+MAIL_VERSION_STAMP_DECLARE;
+
+ /*
+ * This program has so many command-line options that we have to implement a
+ * compatibility matrix to weed out the conflicting option combinations, and
+ * to alert the user about option combinations that have no effect.
+ */
+
+ /*
+ * Options that are mutually-exclusive. First entry must specify the major
+ * modes. Other entries specify conflicts between option modifiers.
+ */
+static const int pcf_incompat_options[] = {
+ /* Major modes. */
+ PCF_SHOW_SASL_SERV | PCF_SHOW_SASL_CLNT | PCF_EXP_DSN_TEMPL \
+ |PCF_SHOW_LOCKS | PCF_SHOW_MAPS | PCF_DUMP_DSN_TEMPL | PCF_MAIN_PARAM \
+ |PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM | PCF_SHOW_TLS,
+ /* Modifiers. */
+ PCF_SHOW_DEFS | PCF_EDIT_CONF | PCF_SHOW_NONDEF | PCF_COMMENT_OUT \
+ |PCF_EDIT_EXCL,
+ PCF_FOLD_LINE | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL,
+ PCF_SHOW_EVAL | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL,
+ PCF_MAIN_OVER | PCF_SHOW_DEFS | PCF_EDIT_CONF | PCF_COMMENT_OUT \
+ |PCF_EDIT_EXCL,
+ PCF_HIDE_NAME | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL \
+ |PCF_HIDE_VALUE,
+ 0,
+};
+
+ /*
+ * Options, and the only options that they are compatible with. There must
+ * be one entry for each major mode. Other entries specify compatibility
+ * between option modifiers.
+ */
+static const int pcf_compat_options[][2] = {
+ /* Major modes. */
+ {PCF_SHOW_SASL_SERV, 0},
+ {PCF_SHOW_SASL_CLNT, 0},
+ {PCF_EXP_DSN_TEMPL, 0},
+ {PCF_SHOW_LOCKS, 0},
+ {PCF_SHOW_MAPS, 0,},
+ {PCF_SHOW_TLS, 0,},
+ {PCF_DUMP_DSN_TEMPL, 0},
+ {PCF_MAIN_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \
+ |PCF_FOLD_LINE | PCF_HIDE_NAME | PCF_PARAM_CLASS \
+ |PCF_SHOW_EVAL | PCF_SHOW_DEFS | PCF_SHOW_NONDEF \
+ |PCF_MAIN_OVER | PCF_HIDE_VALUE)},
+ {PCF_MASTER_ENTRY, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \
+ |PCF_FOLD_LINE | PCF_MAIN_OVER | PCF_SHOW_EVAL)},
+ {PCF_MASTER_FLD, (PCF_EDIT_CONF | PCF_FOLD_LINE | PCF_HIDE_NAME \
+ |PCF_MAIN_OVER | PCF_SHOW_EVAL | PCF_HIDE_VALUE)},
+ {PCF_MASTER_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_FOLD_LINE \
+ |PCF_HIDE_NAME | PCF_MAIN_OVER | PCF_SHOW_EVAL \
+ |PCF_HIDE_VALUE)},
+ /* Modifiers. */
+ {PCF_PARAM_CLASS, (PCF_MAIN_PARAM | PCF_SHOW_DEFS | PCF_SHOW_NONDEF)},
+ 0,
+};
+
+ /*
+ * Compatibility to string conversion support.
+ */
+static const NAME_MASK pcf_compat_names[] = {
+ "-a", PCF_SHOW_SASL_SERV,
+ "-A", PCF_SHOW_SASL_CLNT,
+ "-b", PCF_EXP_DSN_TEMPL,
+ "-C", PCF_PARAM_CLASS,
+ "-d", PCF_SHOW_DEFS,
+ "-e", PCF_EDIT_CONF,
+ "-f", PCF_FOLD_LINE,
+ "-F", PCF_MASTER_FLD,
+ "-h", PCF_HIDE_NAME,
+ "-H", PCF_HIDE_VALUE,
+ "-l", PCF_SHOW_LOCKS,
+ "-m", PCF_SHOW_MAPS,
+ "-M", PCF_MASTER_ENTRY,
+ "-n", PCF_SHOW_NONDEF,
+ "-o", PCF_MAIN_OVER,
+ "-p", PCF_MAIN_PARAM,
+ "-P", PCF_MASTER_PARAM,
+ "-t", PCF_DUMP_DSN_TEMPL,
+ "-T", PCF_SHOW_TLS,
+ "-x", PCF_SHOW_EVAL,
+ "-X", PCF_EDIT_EXCL,
+ "-#", PCF_COMMENT_OUT,
+ 0,
+};
+
+/* usage - enumerate parameters without compatibility info */
+
+static void usage(const char *progname)
+{
+ msg_fatal("usage: %s"
+ " [-a (server SASL types)]"
+ " [-A (client SASL types)]"
+ " [-b (bounce templates)]"
+ " [-c config_dir]"
+ " [-c param_class]"
+ " [-d (parameter defaults)]"
+ " [-e (edit configuration)]"
+ " [-f (fold lines)]"
+ " [-F (master.cf fields)]"
+ " [-h (no names)]"
+ " [-H (no values)]"
+ " [-l (lock types)]"
+ " [-m (map types)]"
+ " [-M (master.cf)]"
+ " [-n (non-default parameters)]"
+ " [-o name=value (override parameter value)]"
+ " [-p (main.cf, default)]"
+ " [-P (master.cf parameters)]"
+ " [-t (bounce templates)]"
+ " [-T compile-version|run-version|public-key-algorithms]"
+ " [-v (verbose)]"
+ " [-x (expand parameter values)]"
+ " [-X (exclude)]"
+ " [-# (comment-out)]"
+ " [name...]", progname);
+}
+
+/* pcf_check_exclusive_options - complain about mutually-exclusive options */
+
+static void pcf_check_exclusive_options(int optval)
+{
+ const char *myname = "pcf_check_exclusive_options";
+ const int *op;
+ int oval;
+ unsigned mask;
+
+ for (op = pcf_incompat_options; (oval = *op) != 0; op++) {
+ oval &= optval;
+ for (mask = ~0U; (mask & oval) != 0; mask >>= 1) {
+ if ((mask & oval) != oval)
+ msg_fatal("specify one of %s",
+ str_name_mask(myname, pcf_compat_names, oval));
+ }
+ }
+}
+
+/* pcf_check_compat_options - complain about incompatible options */
+
+static void pcf_check_compat_options(int optval)
+{
+ const char *myname = "pcf_check_compat_options";
+ VSTRING *buf1 = vstring_alloc(10);
+ VSTRING *buf2 = vstring_alloc(10);
+ const int (*op)[2];
+ int excess;
+
+ for (op = pcf_compat_options; op[0][0] != 0; op++) {
+ if ((optval & *op[0]) != 0
+ && (excess = (optval & ~((*op)[0] | (*op)[1]))) != 0)
+ msg_fatal("with option %s, do not specify %s",
+ str_name_mask_opt(buf1, myname, pcf_compat_names,
+ (*op)[0], NAME_MASK_NUMBER),
+ str_name_mask_opt(buf2, myname, pcf_compat_names,
+ excess, NAME_MASK_NUMBER));
+ }
+ vstring_free(buf1);
+ vstring_free(buf2);
+}
+
+/* main */
+
+int main(int argc, char **argv)
+{
+ int ch;
+ int fd;
+ struct stat st;
+ ARGV *ext_argv = 0;
+ int param_class = PCF_PARAM_MASK_CLASS;
+ static const NAME_MASK param_class_table[] = {
+ "builtin", PCF_PARAM_FLAG_BUILTIN,
+ "service", PCF_PARAM_FLAG_SERVICE,
+ "user", PCF_PARAM_FLAG_USER,
+ "all", PCF_PARAM_MASK_CLASS,
+ 0,
+ };
+ ARGV *override_params = 0;
+ const char *pcf_tls_arg = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up logging.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHlmMno:pPtT:vxX#")) > 0) {
+ switch (ch) {
+ case 'a':
+ pcf_cmd_mode |= PCF_SHOW_SASL_SERV;
+ break;
+ case 'A':
+ pcf_cmd_mode |= PCF_SHOW_SASL_CLNT;
+ break;
+ case 'b':
+ pcf_cmd_mode |= PCF_EXP_DSN_TEMPL;
+ if (ext_argv)
+ msg_fatal("specify one of -b and -t");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "bounce", "-SVnexpand_templates", (char *) 0);
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'C':
+ param_class = name_mask_opt("-C option", param_class_table,
+ optarg, NAME_MASK_ANY_CASE | NAME_MASK_FATAL);
+ break;
+ case 'd':
+ pcf_cmd_mode |= PCF_SHOW_DEFS;
+ break;
+ case 'e':
+ pcf_cmd_mode |= PCF_EDIT_CONF;
+ break;
+ case 'f':
+ pcf_cmd_mode |= PCF_FOLD_LINE;
+ break;
+ case 'F':
+ pcf_cmd_mode |= PCF_MASTER_FLD;
+ break;
+ case '#':
+ pcf_cmd_mode |= PCF_COMMENT_OUT;
+ break;
+ case 'h':
+ pcf_cmd_mode |= PCF_HIDE_NAME;
+ break;
+ case 'H':
+ pcf_cmd_mode |= PCF_HIDE_VALUE;
+ break;
+ case 'l':
+ pcf_cmd_mode |= PCF_SHOW_LOCKS;
+ break;
+ case 'm':
+ pcf_cmd_mode |= PCF_SHOW_MAPS;
+ break;
+ case 'M':
+ pcf_cmd_mode |= PCF_MASTER_ENTRY;
+ break;
+ case 'n':
+ pcf_cmd_mode |= PCF_SHOW_NONDEF;
+ break;
+ case 'o':
+ pcf_cmd_mode |= PCF_MAIN_OVER;
+ if (override_params == 0)
+ override_params = argv_alloc(2);
+ argv_add(override_params, optarg, (char *) 0);
+ break;
+ case 'p':
+ pcf_cmd_mode |= PCF_MAIN_PARAM;
+ break;
+ case 'P':
+ pcf_cmd_mode |= PCF_MASTER_PARAM;
+ break;
+ case 't':
+ pcf_cmd_mode |= PCF_DUMP_DSN_TEMPL;
+ if (ext_argv)
+ msg_fatal("specify one of -b and -t");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "bounce", "-SVndump_templates", (char *) 0);
+ break;
+ case 'T':
+ if (pcf_cmd_mode & PCF_SHOW_TLS)
+ msg_fatal("At most one -T <mode> option may be specified");
+ pcf_cmd_mode |= PCF_SHOW_TLS;
+ pcf_tls_arg = optarg;
+ break;
+ case 'x':
+ pcf_cmd_mode |= PCF_SHOW_EVAL;
+ break;
+ case 'X':
+ /* This is irreversible, therefore require two-finger action. */
+ pcf_cmd_mode |= PCF_EDIT_EXCL;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ /*
+ * For consistency with mail_params_init().
+ */
+ compat_level_relop_register();
+
+ /*
+ * We don't enforce import_environment consistency in this program.
+ *
+ * We don't extract import_environment from main.cf, because the postconf
+ * command must be able to extract parameter settings from main.cf before
+ * all installation parameters such as mail_owner or setgid_group have a
+ * legitimate value.
+ *
+ * We would need the functionality of mail_params_init() including all the
+ * side effects of populating the CONFIG_DICT with default values so that
+ * $name expansion works correctly, but excluding all the parameter value
+ * sanity checks so that it would not abort at installation time.
+ */
+
+ /*
+ * Make all options explicit, before checking their compatibility.
+ */
+#define PCF_MAIN_OR_MASTER \
+ (PCF_MAIN_PARAM | PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)
+
+ if ((pcf_cmd_mode & pcf_incompat_options[0]) == 0)
+ pcf_cmd_mode |= PCF_MAIN_PARAM;
+ if ((pcf_cmd_mode & PCF_MAIN_OR_MASTER)
+ && argv[optind] && strchr(argv[optind], '='))
+ pcf_cmd_mode |= PCF_EDIT_CONF;
+
+ /*
+ * Sanity check.
+ */
+ pcf_check_exclusive_options(pcf_cmd_mode);
+ pcf_check_compat_options(pcf_cmd_mode);
+
+ if ((pcf_cmd_mode & PCF_EDIT_CONF) && argc == optind)
+ msg_fatal("-e requires name=value argument");
+
+ /*
+ * Display bounce template information and exit.
+ */
+ if (ext_argv) {
+ if (argv[optind]) {
+ if (argv[optind + 1])
+ msg_fatal("options -b and -t require at most one template file");
+ argv_add(ext_argv, "-o",
+ concatenate(VAR_BOUNCE_TMPL, "=",
+ argv[optind], (char *) 0),
+ (char *) 0);
+ }
+ /* Grr... */
+ argv_add(ext_argv, "-o",
+ concatenate(VAR_QUEUE_DIR, "=", ".", (char *) 0),
+ (char *) 0);
+ mail_conf_read();
+ mail_run_replace(var_daemon_dir, ext_argv->argv);
+ /* NOTREACHED */
+ }
+
+ /*
+ * If showing map types, show them and exit
+ */
+ if (pcf_cmd_mode & PCF_SHOW_MAPS) {
+ mail_conf_read();
+ mail_dict_init();
+ pcf_show_maps();
+ }
+
+ /*
+ * If showing locking methods, show them and exit
+ */
+ else if (pcf_cmd_mode & PCF_SHOW_LOCKS) {
+ pcf_show_locks();
+ }
+
+ /*
+ * If showing master.cf entries, show them and exit
+ */
+ else if ((pcf_cmd_mode & (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM))
+ && !(pcf_cmd_mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT))) {
+ pcf_read_master(PCF_FAIL_ON_OPEN_ERROR);
+ pcf_read_parameters();
+ if (override_params)
+ pcf_set_parameters(override_params->argv);
+ pcf_register_builtin_parameters(basename(argv[0]), getpid());
+ pcf_register_service_parameters();
+ pcf_register_user_parameters();
+ if (pcf_cmd_mode & PCF_MASTER_FLD)
+ pcf_show_master_fields(VSTREAM_OUT, pcf_cmd_mode, argc - optind,
+ argv + optind);
+ else if (pcf_cmd_mode & PCF_MASTER_PARAM)
+ pcf_show_master_params(VSTREAM_OUT, pcf_cmd_mode, argc - optind,
+ argv + optind);
+ else
+ pcf_show_master_entries(VSTREAM_OUT, pcf_cmd_mode, argc - optind,
+ argv + optind);
+ pcf_flag_unused_master_parameters();
+ }
+
+ /*
+ * If showing SASL plug-in types, show them and exit
+ */
+ else if (pcf_cmd_mode & PCF_SHOW_SASL_SERV) {
+ pcf_show_sasl(PCF_SHOW_SASL_SERV);
+ } else if (pcf_cmd_mode & PCF_SHOW_SASL_CLNT) {
+ pcf_show_sasl(PCF_SHOW_SASL_CLNT);
+ }
+
+ /*
+ * Show TLS info and exit.
+ */
+ else if (pcf_cmd_mode & PCF_SHOW_TLS) {
+ pcf_show_tls(pcf_tls_arg);
+ }
+
+ /*
+ * Edit main.cf or master.cf.
+ */
+ else if (pcf_cmd_mode & (PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL)) {
+ if (optind == argc)
+ msg_fatal("missing service argument");
+ if (pcf_cmd_mode & (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)) {
+ pcf_edit_master(pcf_cmd_mode, argc - optind, argv + optind);
+ } else {
+ pcf_edit_main(pcf_cmd_mode, argc - optind, argv + optind);
+ }
+ }
+
+ /*
+ * If showing non-default values, read main.cf.
+ */
+ else {
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) {
+ pcf_read_parameters();
+ if (override_params)
+ pcf_set_parameters(override_params->argv);
+ }
+ pcf_register_builtin_parameters(basename(argv[0]), getpid());
+
+ /*
+ * Add service-dependent parameters (service names from master.cf)
+ * and user-defined parameters ($name macros in parameter values in
+ * main.cf and master.cf, but only if those names have a name=value
+ * in main.cf or master.cf).
+ */
+ pcf_read_master(PCF_WARN_ON_OPEN_ERROR);
+ pcf_register_service_parameters();
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0)
+ pcf_register_user_parameters();
+
+ /*
+ * Show the requested values.
+ */
+ pcf_show_parameters(VSTREAM_OUT, pcf_cmd_mode, param_class,
+ argv + optind);
+
+ /*
+ * Flag unused parameters. This makes no sense with "postconf -d",
+ * because that ignores all the user-specified parameters and
+ * user-specified macro expansions in main.cf.
+ */
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) {
+ pcf_flag_unused_main_parameters();
+ pcf_flag_unused_master_parameters();
+ }
+ }
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+}
diff --git a/src/postconf/postconf.h b/src/postconf/postconf.h
new file mode 100644
index 0000000..7b23380
--- /dev/null
+++ b/src/postconf/postconf.h
@@ -0,0 +1,327 @@
+/*++
+/* NAME
+/* postconf 3h
+/* SUMMARY
+/* module interfaces
+/* SYNOPSIS
+/* #include <postconf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <argv.h>
+#include <dict.h>
+#include <name_code.h>
+
+ /*
+ * What we're supposed to be doing.
+ */
+#define PCF_SHOW_NONDEF (1<<0) /* show main.cf non-default settings */
+#define PCF_SHOW_DEFS (1<<1) /* show main.cf default setting */
+#define PCF_HIDE_NAME (1<<2) /* hide main.cf parameter name */
+#define PCF_SHOW_MAPS (1<<3) /* show map types */
+#define PCF_EDIT_CONF (1<<4) /* edit main.cf or master.cf */
+#define PCF_SHOW_LOCKS (1<<5) /* show mailbox lock methods */
+#define PCF_SHOW_EVAL (1<<6) /* expand main.cf right-hand sides */
+#define PCF_SHOW_SASL_SERV (1<<7) /* show server auth plugin types */
+#define PCF_SHOW_SASL_CLNT (1<<8) /* show client auth plugin types */
+#define PCF_COMMENT_OUT (1<<9) /* #-out selected main.cf entries */
+#define PCF_MASTER_ENTRY (1<<10) /* manage master.cf entries */
+#define PCF_FOLD_LINE (1<<11) /* fold long *.cf entries */
+#define PCF_EDIT_EXCL (1<<12) /* exclude main.cf entries */
+#define PCF_MASTER_FLD (1<<13) /* hierarchical pathname */
+#define PCF_MAIN_PARAM (1<<14) /* manage main.cf entries */
+#define PCF_EXP_DSN_TEMPL (1<<15) /* expand bounce templates */
+#define PCF_PARAM_CLASS (1<<16) /* select parameter class */
+#define PCF_MAIN_OVER (1<<17) /* override parameter values */
+#define PCF_DUMP_DSN_TEMPL (1<<18) /* show bounce templates */
+#define PCF_MASTER_PARAM (1<<19) /* manage master.cf -o name=value */
+#define PCF_HIDE_VALUE (1<<20) /* hide main.cf/master.cf =value */
+#define PCF_SHOW_TLS (1<<21) /* TLS support introspection */
+
+#define PCF_DEF_MODE 0
+
+ /*
+ * Structure for one "valid parameter" (built-in, service-defined or valid
+ * user-defined). See the postconf_builtin, postconf_service and
+ * postconf_user modules for narrative text.
+ */
+typedef struct {
+ int flags; /* see below */
+ void *param_data; /* mostly, the default value */
+ const char *(*convert_fn) (void *); /* value to string */
+} PCF_PARAM_NODE;
+
+ /* Values for flags. See the postconf_node module for narrative text. */
+#define PCF_PARAM_FLAG_RAW (1<<0) /* raw parameter value */
+#define PCF_PARAM_FLAG_BUILTIN (1<<1) /* built-in parameter name */
+#define PCF_PARAM_FLAG_SERVICE (1<<2) /* service-defined parameter name */
+#define PCF_PARAM_FLAG_USER (1<<3) /* user-defined parameter name */
+#define PCF_PARAM_FLAG_LEGACY (1<<4) /* legacy parameter name */
+#define PCF_PARAM_FLAG_READONLY (1<<5) /* legacy parameter name */
+#define PCF_PARAM_FLAG_DBMS (1<<6) /* dbms-defined parameter name */
+
+#define PCF_PARAM_MASK_CLASS \
+ (PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_SERVICE | PCF_PARAM_FLAG_USER)
+#define PCF_PARAM_CLASS_OVERRIDE(node, class) \
+ ((node)->flags = (((node)->flags & ~PCF_PARAM_MASK_CLASS) | (class)))
+
+#define PCF_RAW_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_RAW)
+#define PCF_BUILTIN_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_BUILTIN)
+#define PCF_SERVICE_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_SERVICE)
+#define PCF_USER_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_USER)
+#define PCF_LEGACY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_LEGACY)
+#define PCF_READONLY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_READONLY)
+#define PCF_DBMS_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_DBMS)
+
+ /* Values for param_data. See postconf_node module for narrative text. */
+#define PCF_PARAM_NO_DATA ((char *) 0)
+
+ /*
+ * Lookup table for global "valid parameter" information.
+ */
+#define PCF_PARAM_TABLE HTABLE
+#define PCF_PARAM_INFO HTABLE_INFO
+
+extern PCF_PARAM_TABLE *pcf_param_table;
+
+ /*
+ * postconf_node.c.
+ */
+#define PCF_PARAM_TABLE_CREATE(size) htable_create(size);
+#define PCF_PARAM_NODE_CAST(ptr) ((PCF_PARAM_NODE *) (ptr))
+
+#define PCF_PARAM_TABLE_LIST(table) htable_list(table)
+#define PCF_PARAM_INFO_NAME(ht) ((const char *) (ht)->key)
+#define PCF_PARAM_INFO_NODE(ht) PCF_PARAM_NODE_CAST((ht)->value)
+
+#define PCF_PARAM_TABLE_FIND(table, name) \
+ PCF_PARAM_NODE_CAST(htable_find((table), (name)))
+#define PCF_PARAM_TABLE_LOCATE(table, name) htable_locate((table), (name))
+#define PCF_PARAM_TABLE_ENTER(table, name, flags, data, func) \
+ htable_enter((table), (name), (char *) pcf_make_param_node((flags), \
+ (data), (func)))
+
+extern PCF_PARAM_NODE *pcf_make_param_node(int, void *, const char *(*) (void *));
+extern const char *pcf_convert_param_node(int, const char *, PCF_PARAM_NODE *);
+extern VSTRING *pcf_param_string_buf;
+
+ /*
+ * Structure of one master.cf entry.
+ */
+typedef struct {
+ char *name_space; /* service/type, parameter name space */
+ ARGV *argv; /* null, or master.cf fields */
+ DICT *all_params; /* null, or all name=value entries */
+ DICT *ro_params; /* read-only name=value entries */
+ HTABLE *valid_names; /* null, or "valid" parameter names */
+} PCF_MASTER_ENT;
+
+#define PCF_MASTER_MIN_FIELDS 8 /* mandatory field minimum */
+
+#define PCF_MASTER_NAME_SERVICE "service"
+#define PCF_MASTER_NAME_TYPE "type"
+#define PCF_MASTER_NAME_PRIVATE "private"
+#define PCF_MASTER_NAME_UNPRIV "unprivileged"
+#define PCF_MASTER_NAME_CHROOT "chroot"
+#define PCF_MASTER_NAME_WAKEUP "wakeup"
+#define PCF_MASTER_NAME_MAXPROC "process_limit"
+#define PCF_MASTER_NAME_CMD "command"
+
+#define PCF_MASTER_FLD_SERVICE 0 /* service name */
+#define PCF_MASTER_FLD_TYPE 1 /* service type */
+#define PCF_MASTER_FLD_PRIVATE 2 /* private service */
+#define PCF_MASTER_FLD_UNPRIV 3 /* unprivileged service */
+#define PCF_MASTER_FLD_CHROOT 4 /* chrooted service */
+#define PCF_MASTER_FLD_WAKEUP 5 /* wakeup timer */
+#define PCF_MASTER_FLD_MAXPROC 6 /* process limit */
+#define PCF_MASTER_FLD_CMD 7 /* command */
+
+#define PCF_MASTER_FLD_WILDC -1 /* wild-card */
+#define PCF_MASTER_FLD_NONE -2 /* not available */
+
+ /*
+ * Lookup table for master.cf entries. The table is terminated with an entry
+ * that has a null argv member.
+ */
+extern PCF_MASTER_ENT *pcf_master_table;
+
+ /*
+ * Line-wrapping support.
+ */
+#define PCF_LINE_LIMIT 80 /* try to fold longer lines */
+#define PCF_SEPARATORS " \t\r\n"
+#define PCF_INDENT_LEN 4 /* indent long text by 4 */
+#define PCF_INDENT_TEXT " "
+
+ /*
+ * XXX Global so that postconf_builtin.c call-backs can see it.
+ */
+extern int pcf_cmd_mode;
+
+ /*
+ * postconf_misc.c.
+ */
+extern void pcf_set_config_dir(void);
+
+ /*
+ * postconf_main.c
+ */
+extern void pcf_read_parameters(void);
+extern void pcf_set_parameters(char **);
+extern void pcf_show_parameters(VSTREAM *, int, int, char **);
+
+ /*
+ * postconf_edit.c
+ */
+extern void pcf_edit_main(int, int, char **);
+extern void pcf_edit_master(int, int, char **);
+
+ /*
+ * postconf_master.c.
+ */
+extern const char pcf_daemon_options_expecting_value[];
+extern void pcf_read_master(int);
+extern void pcf_show_master_entries(VSTREAM *, int, int, char **);
+extern const char *pcf_parse_master_entry(PCF_MASTER_ENT *, const char *);
+extern void pcf_print_master_entry(VSTREAM *, int, PCF_MASTER_ENT *);
+extern void pcf_free_master_entry(PCF_MASTER_ENT *);
+extern void pcf_show_master_fields(VSTREAM *, int, int, char **);
+extern void pcf_edit_master_field(PCF_MASTER_ENT *, int, const char *);
+extern void pcf_show_master_params(VSTREAM *, int, int, char **);
+extern void pcf_edit_master_param(PCF_MASTER_ENT *, int, const char *, const char *);
+
+#define PCF_WARN_ON_OPEN_ERROR 0
+#define PCF_FAIL_ON_OPEN_ERROR 1
+
+#define PCF_MASTER_BLANKS " \t\r\n" /* XXX */
+
+ /*
+ * Master.cf parameter namespace management. The idea is to manage master.cf
+ * "-o name=value" settings with other tools than text editors.
+ *
+ * The natural choice is to use "service-name.service-type.parameter-name", but
+ * unfortunately the '.' may appear in service and parameter names.
+ *
+ * For example, a spawn(8) listener can have a service name 127.0.0.1:10028.
+ * This service name becomes part of a service-dependent parameter name
+ * "127.0.0.1:10028_time_limit". All those '.' characters mean we can't use
+ * '.' as the parameter namespace delimiter.
+ *
+ * (We could require that such service names are specified as $foo:port with
+ * the value of "foo" defined in main.cf or at the top of master.cf.)
+ *
+ * But it is easier if we use '/' instead.
+ */
+#define PCF_NAMESP_SEP_CH '/'
+#define PCF_NAMESP_SEP_STR "/"
+
+#define PCF_LEGACY_SEP_CH '.'
+
+ /*
+ * postconf_match.c.
+ */
+#define PCF_MATCH_WILDC_STR "*"
+#define PCF_MATCH_ANY(p) ((p)[0] == PCF_MATCH_WILDC_STR[0] && (p)[1] == 0)
+#define PCF_MATCH_STRING(p, s) (PCF_MATCH_ANY(p) || strcmp((p), (s)) == 0)
+
+extern ARGV *pcf_parse_service_pattern(const char *, int, int);
+extern int pcf_parse_field_pattern(const char *);
+
+#define PCF_IS_MAGIC_SERVICE_PATTERN(pat) \
+ (PCF_MATCH_ANY((pat)->argv[0]) || PCF_MATCH_ANY((pat)->argv[1]))
+#define PCF_MATCH_SERVICE_PATTERN(pat, name, type) \
+ (PCF_MATCH_STRING((pat)->argv[0], (name)) \
+ && PCF_MATCH_STRING((pat)->argv[1], (type)))
+
+#define pcf_is_magic_field_pattern(pat) ((pat) == PCF_MASTER_FLD_WILDC)
+#define pcf_str_field_pattern(pat) ((const char *) (pcf_field_name_offset[pat].name))
+
+#define PCF_IS_MAGIC_PARAM_PATTERN(pat) PCF_MATCH_ANY(pat)
+#define PCF_MATCH_PARAM_PATTERN(pat, name) PCF_MATCH_STRING((pat), (name))
+
+/* The following is not part of the postconf_match API. */
+extern NAME_CODE pcf_field_name_offset[];
+
+ /*
+ * postconf_builtin.c.
+ */
+extern void pcf_register_builtin_parameters(const char *, pid_t);
+
+ /*
+ * postconf_service.c.
+ */
+extern void pcf_register_service_parameters(void);
+
+ /*
+ * Parameter context structure.
+ */
+typedef struct {
+ PCF_MASTER_ENT *local_scope;
+ int param_class;
+} PCF_PARAM_CTX;
+
+ /*
+ * postconf_user.c.
+ */
+extern void pcf_register_user_parameters(void);
+
+ /*
+ * postconf_dbms.c
+ */
+extern void pcf_register_dbms_parameters(const char *,
+ const char *(*) (const char *, int, PCF_MASTER_ENT *),
+ PCF_MASTER_ENT *);
+
+ /*
+ * postconf_lookup.c.
+ */
+extern const char *pcf_lookup_parameter_value(int, const char *,
+ PCF_MASTER_ENT *,
+ PCF_PARAM_NODE *);
+
+extern char *pcf_expand_parameter_value(VSTRING *, int, const char *,
+ PCF_MASTER_ENT *);
+
+ /*
+ * postconf_print.c.
+ */
+extern void PRINTFLIKE(3, 4) pcf_print_line(VSTREAM *, int, const char *,...);
+
+ /*
+ * postconf_unused.c.
+ */
+extern void pcf_flag_unused_main_parameters(void);
+extern void pcf_flag_unused_master_parameters(void);
+
+ /*
+ * postconf_other.c.
+ */
+extern void pcf_show_maps(void);
+extern void pcf_show_locks(void);
+extern void pcf_show_sasl(int);
+extern void pcf_show_tls(const char *);
+
+/* 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/postconf/postconf_builtin.c b/src/postconf/postconf_builtin.c
new file mode 100644
index 0000000..1fc337c
--- /dev/null
+++ b/src/postconf/postconf_builtin.c
@@ -0,0 +1,469 @@
+/*++
+/* NAME
+/* postconf_builtin 3
+/* SUMMARY
+/* built-in main.cf parameter support
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_register_builtin_parameters(procname, pid)
+/* const char *procname;
+/* pid_t pid;
+/* DESCRIPTION
+/* pcf_register_builtin_parameters() initializes the global
+/* main.cf parameter name space and adds all built-in parameter
+/* information.
+/*
+/* Arguments:
+/*.IP procname
+/* Provides the default value for the "process_name" parameter.
+/*.IP pid
+/* Provides the default value for the "process_id" parameter.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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>
+
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <get_hostname.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mynetworks.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <mail_addr.h>
+#include <inet_proto.h>
+#include <server_acl.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * Support for built-in parameters: declarations generated by scanning
+ * actual C source files.
+ */
+#include "time_vars.h"
+#include "bool_vars.h"
+#include "int_vars.h"
+#include "str_vars.h"
+#include "raw_vars.h"
+#include "nint_vars.h"
+#include "nbool_vars.h"
+#include "long_vars.h"
+
+ /*
+ * Support for built-in parameters: manually extracted.
+ */
+#include "install_vars.h"
+
+ /*
+ * Support for built-in parameters: lookup tables generated by scanning
+ * actual C source files.
+ */
+static const CONFIG_TIME_TABLE pcf_time_table[] = {
+#include "time_table.h"
+ 0,
+};
+
+static const CONFIG_BOOL_TABLE pcf_bool_table[] = {
+#include "bool_table.h"
+ 0,
+};
+
+static const CONFIG_INT_TABLE pcf_int_table[] = {
+#include "int_table.h"
+ 0,
+};
+
+static const CONFIG_STR_TABLE pcf_str_table[] = {
+#include "str_table.h"
+#include "install_table.h"
+ 0,
+};
+
+static const CONFIG_RAW_TABLE pcf_raw_table[] = {
+#include "raw_table.h"
+ 0,
+};
+
+static const CONFIG_NINT_TABLE pcf_nint_table[] = {
+#include "nint_table.h"
+ 0,
+};
+
+static const CONFIG_NBOOL_TABLE pcf_nbool_table[] = {
+#include "nbool_table.h"
+ 0,
+};
+
+static const CONFIG_LONG_TABLE pcf_long_table[] = {
+#include "long_table.h"
+ 0,
+};
+
+ /*
+ * Legacy parameters for backwards compatibility.
+ */
+static const CONFIG_STR_TABLE pcf_legacy_str_table[] = {
+ {"virtual_maps", ""},
+ {"fallback_relay", ""},
+ {"authorized_verp_clients", ""},
+ {"smtpd_client_connection_limit_exceptions", ""},
+ {"postscreen_dnsbl_ttl", ""},
+ {"postscreen_blacklist_action", ""},
+ {"postscreen_dnsbl_whitelist_threshold", ""},
+ {"postscreen_whitelist_interfaces", ""},
+ {"lmtp_per_record_deadline", ""},
+ {"smtp_per_record_deadline", ""},
+ {"smtpd_per_record_deadline", ""},
+ {"tlsproxy_client_level", ""},
+ {"tlsproxy_client_policy", ""},
+ 0,
+};
+
+ /*
+ * Parameters whose default values are normally initialized by calling a
+ * function. We direct the calls to our own versions of those functions
+ * because the run-time conditions are slightly different.
+ *
+ * Important: if the evaluation of a parameter default value has any side
+ * effects, then those side effects must happen only once.
+ */
+static const char *pcf_check_myhostname(void);
+static const char *pcf_check_mydomainname(void);
+static const char *pcf_mynetworks(void);
+
+#include "str_fn_vars.h"
+
+static const CONFIG_STR_FN_TABLE pcf_str_fn_table[] = {
+#include "str_fn_table.h"
+ 0,
+};
+
+ /*
+ * Parameters whose default values are normally initialized by ad-hoc code.
+ * The AWK script cannot identify these parameters or values, so we provide
+ * our own.
+ *
+ * Important: if the evaluation of a parameter default value has any side
+ * effects, then those side effects must happen only once.
+ */
+static CONFIG_STR_TABLE pcf_adhoc_procname = {VAR_PROCNAME};
+static CONFIG_STR_TABLE pcf_adhoc_servname = {VAR_SERVNAME};
+static CONFIG_INT_TABLE pcf_adhoc_pid = {VAR_PID};
+
+#define STR(x) vstring_str(x)
+
+/* pcf_check_myhostname - lookup hostname and validate */
+
+static const char *pcf_check_myhostname(void)
+{
+ static const char *name;
+ const char *dot;
+ const char *domain;
+
+ /*
+ * Use cached result.
+ */
+ if (name)
+ return (name);
+
+ /*
+ * If the local machine name is not in FQDN form, try to append the
+ * contents of $mydomain.
+ */
+ name = get_hostname();
+ if ((dot = strchr(name, '.')) == 0) {
+ if ((domain = mail_conf_lookup_eval(VAR_MYDOMAIN)) == 0)
+ domain = DEF_MYDOMAIN;
+ name = concatenate(name, ".", domain, (char *) 0);
+ }
+ return (name);
+}
+
+/* pcf_get_myhostname - look up and store my hostname */
+
+static void pcf_get_myhostname(void)
+{
+ const char *name;
+
+ if ((name = mail_conf_lookup_eval(VAR_MYHOSTNAME)) == 0)
+ name = pcf_check_myhostname();
+ var_myhostname = mystrdup(name);
+}
+
+/* pcf_check_mydomainname - lookup domain name and validate */
+
+static const char *pcf_check_mydomainname(void)
+{
+ static const char *domain;
+ char *dot;
+
+ /*
+ * Use cached result.
+ */
+ if (domain)
+ return (domain);
+
+ /*
+ * Use a default domain when the hostname is not a FQDN ("foo").
+ */
+ if (var_myhostname == 0)
+ pcf_get_myhostname();
+ if ((dot = strchr(var_myhostname, '.')) == 0)
+ return (domain = DEF_MYDOMAIN);
+ return (domain = mystrdup(dot + 1));
+}
+
+/* pcf_mynetworks - lookup network address list */
+
+static const char *pcf_mynetworks(void)
+{
+ static const char *networks;
+ VSTRING *exp_buf;
+ const char *junk;
+
+ /*
+ * Use cached result.
+ */
+ if (networks)
+ return (networks);
+
+ exp_buf = vstring_alloc(100);
+
+ if (var_inet_interfaces == 0) {
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS)
+ || (junk = mail_conf_lookup_eval(VAR_INET_INTERFACES)) == 0)
+ junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode,
+ DEF_INET_INTERFACES,
+ (PCF_MASTER_ENT *) 0);
+ var_inet_interfaces = mystrdup(junk);
+ }
+ if (var_mynetworks_style == 0) {
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS)
+ || (junk = mail_conf_lookup_eval(VAR_MYNETWORKS_STYLE)) == 0)
+ junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode,
+ DEF_MYNETWORKS_STYLE,
+ (PCF_MASTER_ENT *) 0);
+ var_mynetworks_style = mystrdup(junk);
+ }
+ if (var_inet_protocols == 0) {
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS)
+ || (junk = mail_conf_lookup_eval(VAR_INET_PROTOCOLS)) == 0)
+ junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode,
+ DEF_INET_PROTOCOLS,
+ (PCF_MASTER_ENT *) 0);
+ var_inet_protocols = mystrdup(junk);
+ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols);
+ }
+ vstring_free(exp_buf);
+ return (networks = mystrdup(mynetworks()));
+}
+
+/* pcf_conv_bool_parameter - get boolean parameter string value */
+
+static const char *pcf_conv_bool_parameter(void *ptr)
+{
+ CONFIG_BOOL_TABLE *cbt = (CONFIG_BOOL_TABLE *) ptr;
+
+ return (cbt->defval ? "yes" : "no");
+}
+
+/* pcf_conv_time_parameter - get relative time parameter string value */
+
+static const char *pcf_conv_time_parameter(void *ptr)
+{
+ CONFIG_TIME_TABLE *ctt = (CONFIG_TIME_TABLE *) ptr;
+
+ return (ctt->defval);
+}
+
+/* pcf_conv_int_parameter - get integer parameter string value */
+
+static const char *pcf_conv_int_parameter(void *ptr)
+{
+ CONFIG_INT_TABLE *cit = (CONFIG_INT_TABLE *) ptr;
+
+ return (STR(vstring_sprintf(pcf_param_string_buf, "%d", cit->defval)));
+}
+
+/* pcf_conv_str_parameter - get string parameter string value */
+
+static const char *pcf_conv_str_parameter(void *ptr)
+{
+ CONFIG_STR_TABLE *cst = (CONFIG_STR_TABLE *) ptr;
+
+ return (cst->defval);
+}
+
+/* pcf_conv_str_fn_parameter - get string-function parameter string value */
+
+static const char *pcf_conv_str_fn_parameter(void *ptr)
+{
+ CONFIG_STR_FN_TABLE *cft = (CONFIG_STR_FN_TABLE *) ptr;
+
+ return (cft->defval());
+}
+
+/* pcf_conv_raw_parameter - get raw string parameter string value */
+
+static const char *pcf_conv_raw_parameter(void *ptr)
+{
+ CONFIG_RAW_TABLE *rst = (CONFIG_RAW_TABLE *) ptr;
+
+ return (rst->defval);
+}
+
+/* pcf_conv_nint_parameter - get new integer parameter string value */
+
+static const char *pcf_conv_nint_parameter(void *ptr)
+{
+ CONFIG_NINT_TABLE *rst = (CONFIG_NINT_TABLE *) ptr;
+
+ return (rst->defval);
+}
+
+/* pcf_conv_nbool_parameter - get new boolean parameter string value */
+
+static const char *pcf_conv_nbool_parameter(void *ptr)
+{
+ CONFIG_NBOOL_TABLE *bst = (CONFIG_NBOOL_TABLE *) ptr;
+
+ return (bst->defval);
+}
+
+/* pcf_conv_long_parameter - get long parameter string value */
+
+static const char *pcf_conv_long_parameter(void *ptr)
+{
+ CONFIG_LONG_TABLE *clt = (CONFIG_LONG_TABLE *) ptr;
+
+ return (STR(vstring_sprintf(pcf_param_string_buf, "%ld", clt->defval)));
+}
+
+/* pcf_register_builtin_parameters - add built-ins to the global name space */
+
+void pcf_register_builtin_parameters(const char *procname, pid_t pid)
+{
+ const char *myname = "pcf_register_builtin_parameters";
+ const CONFIG_TIME_TABLE *ctt;
+ const CONFIG_BOOL_TABLE *cbt;
+ const CONFIG_INT_TABLE *cit;
+ const CONFIG_STR_TABLE *cst;
+ const CONFIG_STR_FN_TABLE *cft;
+ const CONFIG_RAW_TABLE *rst;
+ const CONFIG_NINT_TABLE *nst;
+ const CONFIG_NBOOL_TABLE *bst;
+ const CONFIG_LONG_TABLE *lst;
+
+ /*
+ * Sanity checks.
+ */
+ if (pcf_param_table != 0)
+ msg_panic("%s: global parameter table is already initialized", myname);
+
+ /*
+ * Initialize the global parameter table.
+ */
+ pcf_param_table = PCF_PARAM_TABLE_CREATE(1000);
+
+ /*
+ * Add the built-in parameters to the global name space. The class
+ * (built-in) is tentative; some parameters are actually service-defined,
+ * but they have their own default value.
+ */
+ for (ctt = pcf_time_table; ctt->name; ctt++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, ctt->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) ctt,
+ pcf_conv_time_parameter);
+ for (cbt = pcf_bool_table; cbt->name; cbt++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, cbt->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) cbt,
+ pcf_conv_bool_parameter);
+ for (cit = pcf_int_table; cit->name; cit++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, cit->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) cit,
+ pcf_conv_int_parameter);
+ for (cst = pcf_str_table; cst->name; cst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, cst->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) cst,
+ pcf_conv_str_parameter);
+ for (cft = pcf_str_fn_table; cft->name; cft++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, cft->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) cft,
+ pcf_conv_str_fn_parameter);
+ for (rst = pcf_raw_table; rst->name; rst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, rst->name,
+ PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_RAW,
+ (void *) rst, pcf_conv_raw_parameter);
+ for (nst = pcf_nint_table; nst->name; nst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, nst->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) nst,
+ pcf_conv_nint_parameter);
+ for (bst = pcf_nbool_table; bst->name; bst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, bst->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) bst,
+ pcf_conv_nbool_parameter);
+ for (lst = pcf_long_table; lst->name; lst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, lst->name,
+ PCF_PARAM_FLAG_BUILTIN, (void *) lst,
+ pcf_conv_long_parameter);
+
+ /*
+ * Register legacy parameters (used as a backwards-compatible migration
+ * aid).
+ */
+ for (cst = pcf_legacy_str_table; cst->name; cst++)
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, cst->name,
+ PCF_PARAM_FLAG_LEGACY, (void *) cst,
+ pcf_conv_str_parameter);
+
+ /*
+ * Register parameters whose default value is normally initialized by
+ * ad-hoc code.
+ */
+ pcf_adhoc_procname.defval = mystrdup(procname);
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_procname.name,
+ PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY,
+ (void *) &pcf_adhoc_procname, pcf_conv_str_parameter);
+ pcf_adhoc_servname.defval = mystrdup("");
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_servname.name,
+ PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY,
+ (void *) &pcf_adhoc_servname, pcf_conv_str_parameter);
+ pcf_adhoc_pid.defval = pid;
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_pid.name,
+ PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY,
+ (void *) &pcf_adhoc_pid, pcf_conv_int_parameter);
+}
diff --git a/src/postconf/postconf_dbms.c b/src/postconf/postconf_dbms.c
new file mode 100644
index 0000000..d21cad1
--- /dev/null
+++ b/src/postconf/postconf_dbms.c
@@ -0,0 +1,338 @@
+/*++
+/* NAME
+/* postconf_dbms 3
+/* SUMMARY
+/* legacy support for database-defined main.cf parameter names
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_register_dbms_parameters(param_value, flag_parameter,
+/* local_scope)
+/* const char *param_value;
+/* const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *);
+/* PCF_MASTER_ENT *local_scope;
+/* DESCRIPTION
+/* This module implements legacy support for database configuration
+/* where main.cf parameter names are generated by prepending
+/* the database name to a database-defined suffix.
+/*
+/* Arguments:
+/* .IP param_value
+/* A parameter value to be searched for "type:table" strings.
+/* When a database type is found that supports legacy-style
+/* configuration, the table name is combined with each of the
+/* database-defined suffixes to generate candidate parameter
+/* names for that database type; if the table name specifies
+/* a client configuration file, that file is scanned for unused
+/* parameter settings.
+/* .IP flag_parameter
+/* A function that takes as arguments a candidate parameter
+/* name, parameter flags, and a PCF_MASTER_ENT pointer. The
+/* function will flag the parameter as "used" if it has a
+/* "name=value" entry in the local or global namespace.
+/* .IP local_scope
+/* The local namespace.
+/* DIAGNOSTICS
+/* No explicit 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+#include <split_at.h>
+#include <mac_expand.h>
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <dict_ht.h>
+#include <dict_proxy.h>
+#include <dict_ldap.h>
+#include <dict_mysql.h>
+#include <dict_pgsql.h>
+#include <dict_sqlite.h>
+#include <dict_memcache.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+#ifdef LEGACY_DBMS_SUPPORT
+
+ /*
+ * The legacy database interface automagically instantiates a list of
+ * parameters by prepending the table name to database-specific suffixes.
+ */
+
+/* See ldap_table(5). */
+
+static const char *pcf_ldap_suffixes[] = {
+#include "pcf_ldap_suffixes.h"
+ 0,
+};
+
+/* See mysql_table(5). */
+
+static const char *pcf_mysql_suffixes[] = {
+#include "pcf_mysql_suffixes.h"
+ 0,
+};
+
+/* See pgsql_table(5). */
+
+static const char *pcf_pgsql_suffixes[] = {
+#include "pcf_pgsql_suffixes.h"
+ 0,
+};
+
+/* See sqlite_table(5). */
+
+static const char *pcf_sqlite_suffixes[] = {
+#include "pcf_sqlite_suffixes.h"
+ 0,
+};
+
+/* See memcache_table(5). */
+
+static const char *pcf_memcache_suffixes[] = {
+#include "pcf_memcache_suffixes.h"
+ 0,
+};
+
+ /*
+ * Bundle up the database types and their suffix lists.
+ */
+typedef struct {
+ const char *db_type;
+ const char **db_suffixes;
+} PCF_DBMS_INFO;
+
+static const PCF_DBMS_INFO pcf_dbms_info[] = {
+ DICT_TYPE_LDAP, pcf_ldap_suffixes,
+ DICT_TYPE_MYSQL, pcf_mysql_suffixes,
+ DICT_TYPE_PGSQL, pcf_pgsql_suffixes,
+ DICT_TYPE_SQLITE, pcf_sqlite_suffixes,
+ DICT_TYPE_MEMCACHE, pcf_memcache_suffixes,
+ 0,
+};
+
+/* pcf_check_dbms_client - look for unused names in client configuration */
+
+static void pcf_check_dbms_client(const PCF_DBMS_INFO *dp, const char *cf_file)
+{
+ DICT *dict;
+ VSTREAM *fp;
+ const char **cpp;
+ const char *name;
+ const char *value;
+ char *dict_spec;
+ int dir;
+
+ /*
+ * We read each database client configuration file into its own
+ * dictionary, and nag only the first time that a file is visited.
+ */
+ dict_spec = concatenate(dp->db_type, ":", cf_file, (char *) 0);
+ if ((dict = dict_handle(dict_spec)) == 0) {
+ struct stat st;
+
+ /*
+ * Populate the dictionary with settings in this database client
+ * configuration file. Don't die if a file can't be opened - some
+ * files may contain passwords and should not be world-readable.
+ * Note: dict_load_fp() nags about duplicate parameter settings.
+ */
+ dict = dict_ht_open(dict_spec, O_CREAT | O_RDWR, 0);
+ dict_register(dict_spec, dict);
+ if ((fp = vstream_fopen(cf_file, O_RDONLY, 0)) == 0) {
+ if (errno != EACCES)
+ msg_warn("open \"%s\" configuration \"%s\": %m",
+ dp->db_type, cf_file);
+ myfree(dict_spec);
+ return;
+ }
+ if (fstat(vstream_fileno(fp), &st) == 0 && !S_ISREG(st.st_mode)) {
+ msg_warn("open \"%s\" configuration \"%s\": not a regular file",
+ dp->db_type, cf_file);
+ myfree(dict_spec);
+ (void) vstream_fclose(fp);
+ return;
+ }
+ dict_load_fp(dict_spec, fp);
+ if (vstream_fclose(fp)) {
+ msg_warn("read \"%s\" configuration \"%s\": %m",
+ dp->db_type, cf_file);
+ myfree(dict_spec);
+ return;
+ }
+
+ /*
+ * Remove all known database client parameters from this dictionary,
+ * then report the remaining ones as "unused". We use ad-hoc logging
+ * code, because a database client parameter namespace is unlike the
+ * parameter namespaces in main.cf or master.cf.
+ */
+ for (cpp = dp->db_suffixes; *cpp; cpp++)
+ (void) dict_del(dict, *cpp);
+ for (dir = DICT_SEQ_FUN_FIRST;
+ dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS;
+ dir = DICT_SEQ_FUN_NEXT)
+ msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value);
+ }
+ myfree(dict_spec);
+}
+
+/* pcf_register_dbms_helper - parse one possible database type:name */
+
+static void pcf_register_dbms_helper(char *str_value,
+ const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
+ PCF_MASTER_ENT *local_scope)
+{
+ const PCF_DBMS_INFO *dp;
+ char *db_type;
+ char *prefix;
+ static VSTRING *candidate = 0;
+ const char **cpp;
+ char *err;
+
+ /*
+ * Naive parsing. We don't really know if this substring specifies a
+ * database or some other text.
+ */
+ while ((db_type = mystrtokq(&str_value, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (*db_type == CHARS_BRACE[0]) {
+ if ((err = extpar(&db_type, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) {
+ /* XXX Encapsulate this in pcf_warn() function. */
+ if (local_scope)
+ msg_warn("%s:%s: %s",
+ MASTER_CONF_FILE, local_scope->name_space, err);
+ else
+ msg_warn("%s: %s", MAIN_CONF_FILE, err);
+ myfree(err);
+ }
+ pcf_register_dbms_helper(db_type, flag_parameter, local_scope);
+ continue;
+ }
+
+ /*
+ * Skip over "proxy:" maptypes, to emulate the proxymap(8) server's
+ * behavior when opening a local database configuration file.
+ */
+ while ((prefix = split_at(db_type, ':')) != 0
+ && strcmp(db_type, DICT_TYPE_PROXY) == 0)
+ db_type = prefix;
+
+ if (prefix == 0)
+ continue;
+
+ /*
+ * Look for database:prefix where the prefix is an absolute pathname.
+ * Then, report unknown database client configuration parameters.
+ *
+ * XXX What about a pathname beginning with '.'? This supposedly is
+ * relative to the queue directory, which is the default directory
+ * for all Postfix daemon processes. This would also have to handle
+ * the case that the queue is not yet created.
+ */
+ if (*prefix == '/') {
+ for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
+ if (strcmp(db_type, dp->db_type) == 0) {
+ pcf_check_dbms_client(dp, prefix);
+ break;
+ }
+ }
+ continue;
+ }
+
+ /*
+ * Look for database:prefix where the prefix is not a pathname and
+ * the database is a known type. Synthesize candidate parameter names
+ * from the user-defined prefix and from the database-defined suffix
+ * list, and see if those parameters have a "name=value" entry in the
+ * local or global namespace.
+ */
+ if (*prefix != '.') {
+ if (*prefix == CHARS_BRACE[0]) {
+ if ((err = extpar(&prefix, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) {
+ /* XXX Encapsulate this in pcf_warn() function. */
+ if (local_scope)
+ msg_warn("%s:%s: %s",
+ MASTER_CONF_FILE, local_scope->name_space,
+ err);
+ else
+ msg_warn("%s: %s", MAIN_CONF_FILE, err);
+ myfree(err);
+ }
+ pcf_register_dbms_helper(prefix, flag_parameter, local_scope);
+ continue;
+ } else {
+ for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
+ if (strcmp(db_type, dp->db_type) == 0) {
+ for (cpp = dp->db_suffixes; *cpp; cpp++) {
+ vstring_sprintf(candidate ? candidate :
+ (candidate = vstring_alloc(30)),
+ "%s_%s", prefix, *cpp);
+ flag_parameter(STR(candidate),
+ PCF_PARAM_FLAG_DBMS | PCF_PARAM_FLAG_USER,
+ local_scope);
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+/* pcf_register_dbms_parameters - look for database_type:prefix_name */
+
+void pcf_register_dbms_parameters(const char *param_value,
+ const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
+ PCF_MASTER_ENT *local_scope)
+{
+ char *bufp;
+ static VSTRING *buffer = 0;
+
+ /*
+ * XXX This does not examine both sides of conditional macro expansion,
+ * and may expand the "wrong" conditional macros. This is the best we can
+ * do for legacy database configuration support.
+ */
+ if (buffer == 0)
+ buffer = vstring_alloc(100);
+ bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value,
+ local_scope);
+ pcf_register_dbms_helper(bufp, flag_parameter, local_scope);
+}
+
+#endif
diff --git a/src/postconf/postconf_edit.c b/src/postconf/postconf_edit.c
new file mode 100644
index 0000000..2f9a607
--- /dev/null
+++ b/src/postconf/postconf_edit.c
@@ -0,0 +1,625 @@
+/*++
+/* NAME
+/* postconf_edit 3
+/* SUMMARY
+/* edit main.cf or master.cf
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_edit_main(mode, argc, argv)
+/* int mode;
+/* int argc;
+/* char **argv;
+/*
+/* void pcf_edit_master(mode, argc, argv)
+/* int mode;
+/* int argc;
+/* char **argv;
+/* DESCRIPTION
+/* pcf_edit_main() edits the \fBmain.cf\fR configuration file.
+/* It replaces or adds parameter settings given as "\fIname=value\fR"
+/* pairs given on the command line, or removes parameter
+/* settings given as "\fIname\fR" on the command line. The
+/* file is copied to a temporary file then renamed into place.
+/*
+/* pcf_edit_master() edits the \fBmaster.cf\fR configuration
+/* file. The file is copied to a temporary file then renamed
+/* into place. Depending on the flags in \fBmode\fR:
+/* .IP PCF_MASTER_ENTRY
+/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds
+/* entire master.cf entries, specified on the command line as
+/* "\fIname/type = name type private unprivileged chroot wakeup
+/* process_limit command...\fR".
+/*
+/* With PCF_EDIT_EXCL or PCF_COMMENT_OUT, pcf_edit_master()
+/* removes or comments out entries specified on the command
+/* line as "\fIname/type\fR.
+/* .IP PCF_MASTER_FLD
+/* With PCF_EDIT_CONF, pcf_edit_master() replaces the value
+/* of specific service attributes, specified on the command
+/* line as "\fIname/type/attribute = value\fR".
+/* .IP PCF_MASTER_PARAM
+/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds the
+/* value of service parameters, specified on the command line
+/* as "\fIname/type/parameter = value\fR".
+/*
+/* With PCF_EDIT_EXCL, pcf_edit_master() removes service
+/* parameters specified on the command line as "\fIparametername\fR".
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* FILES
+/* /etc/postfix/main.cf, Postfix configuration parameters
+/* /etc/postfix/main.cf.tmp, temporary name
+/* /etc/postfix/master.cf, Postfix configuration parameters
+/* /etc/postfix/master.cf.tmp, temporary name
+/* 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>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <edit_file.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+#define STR(x) vstring_str(x)
+
+/* pcf_find_cf_info - pass-through non-content line, return content or null */
+
+static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst)
+{
+ char *cp;
+
+ for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++)
+ /* void */ ;
+ /* Pass-through comment, all-whitespace, or empty line. */
+ if (*cp == '#' || *cp == 0) {
+ vstream_fputs(STR(buf), dst);
+ return (0);
+ } else {
+ return (cp);
+ }
+}
+
+/* pcf_next_cf_line - return next content line, pass non-content */
+
+static char *pcf_next_cf_line(VSTRING *buf, VSTREAM *src, VSTREAM *dst, int *lineno)
+{
+ char *cp;
+
+ while (vstring_get(buf, src) != VSTREAM_EOF) {
+ if (lineno)
+ *lineno += 1;
+ if ((cp = pcf_find_cf_info(buf, dst)) != 0)
+ return (cp);
+ }
+ return (0);
+}
+
+/* pcf_gobble_cf_line - accumulate multi-line content, pass non-content */
+
+static void pcf_gobble_cf_line(VSTRING *full_entry_buf, VSTRING *line_buf,
+ VSTREAM *src, VSTREAM *dst, int *lineno)
+{
+ int ch;
+
+ vstring_strcpy(full_entry_buf, STR(line_buf));
+ for (;;) {
+ if ((ch = VSTREAM_GETC(src)) != VSTREAM_EOF)
+ vstream_ungetc(src, ch);
+ if ((ch != '#' && !ISSPACE(ch))
+ || vstring_get(line_buf, src) == VSTREAM_EOF)
+ break;
+ lineno += 1;
+ if (pcf_find_cf_info(line_buf, dst))
+ vstring_strcat(full_entry_buf, STR(line_buf));
+ }
+}
+
+/* pcf_edit_main - edit main.cf file */
+
+void pcf_edit_main(int mode, int argc, char **argv)
+{
+ char *path;
+ EDIT_FILE *ep;
+ VSTREAM *src;
+ VSTREAM *dst;
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *key = vstring_alloc(10);
+ char *cp;
+ char *pattern;
+ char *edit_value;
+ HTABLE *table;
+ struct cvalue {
+ char *value;
+ int found;
+ };
+ struct cvalue *cvalue;
+ HTABLE_INFO **ht_info;
+ HTABLE_INFO **ht;
+ int interesting;
+ const char *err;
+
+ /*
+ * Store command-line parameters for quick lookup.
+ */
+ table = htable_create(argc);
+ while ((cp = *argv++) != 0) {
+ if (strchr(cp, '\n') != 0)
+ msg_fatal("-e, -X, or -# accepts no multi-line input");
+ while (ISSPACE(*cp))
+ cp++;
+ if (*cp == '#')
+ msg_fatal("-e, -X, or -# accepts no comment input");
+ if (mode & PCF_EDIT_CONF) {
+ if ((err = split_nameval(cp, &pattern, &edit_value)) != 0)
+ msg_fatal("%s: \"%s\"", err, cp);
+ } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) {
+ if (*cp == 0)
+ msg_fatal("-X or -# requires non-blank parameter names");
+ if (strchr(cp, '=') != 0)
+ msg_fatal("-X or -# requires parameter names without value");
+ pattern = cp;
+ trimblanks(pattern, 0);
+ edit_value = 0;
+ } else {
+ msg_panic("pcf_edit_main: unknown mode %d", mode);
+ }
+ if ((cvalue = htable_find(table, pattern)) != 0) {
+ msg_warn("ignoring earlier request: '%s = %s'",
+ pattern, cvalue->value);
+ htable_delete(table, pattern, myfree);
+ }
+ cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue));
+ cvalue->value = edit_value;
+ cvalue->found = 0;
+ htable_enter(table, pattern, (void *) cvalue);
+ }
+
+ /*
+ * Open a temp file for the result. This uses a deterministic name so we
+ * don't leave behind thrash with random names.
+ */
+ pcf_set_config_dir();
+ path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0);
+ if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0)
+ msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX);
+ dst = ep->tmp_fp;
+
+ /*
+ * Open the original file for input.
+ */
+ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) {
+ /* OK to delete, since we control the temp file name exclusively. */
+ (void) unlink(ep->tmp_path);
+ msg_fatal("open %s for reading: %m", path);
+ }
+
+ /*
+ * Copy original file to temp file, while replacing parameters on the
+ * fly. Issue warnings for names found multiple times.
+ */
+#define STR(x) vstring_str(x)
+
+ interesting = 0;
+ while ((cp = pcf_next_cf_line(buf, src, dst, (int *) 0)) != 0) {
+ /* Copy, skip or replace continued text. */
+ if (cp > STR(buf)) {
+ if (interesting == 0)
+ vstream_fputs(STR(buf), dst);
+ else if (mode & PCF_COMMENT_OUT)
+ vstream_fprintf(dst, "#%s", STR(buf));
+ }
+ /* Copy or replace start of logical line. */
+ else {
+ vstring_strncpy(key, cp, strcspn(cp, CHARS_SPACE "="));
+ cvalue = (struct cvalue *) htable_find(table, STR(key));
+ if ((interesting = !!cvalue) != 0) {
+ if (cvalue->found++ == 1)
+ msg_warn("%s: multiple entries for \"%s\"", path, STR(key));
+ if (mode & PCF_EDIT_CONF)
+ vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value);
+ else if (mode & PCF_COMMENT_OUT)
+ vstream_fprintf(dst, "#%s", cp);
+ } else {
+ vstream_fputs(STR(buf), dst);
+ }
+ }
+ }
+
+ /*
+ * Generate new entries for parameters that were not found.
+ */
+ if (mode & PCF_EDIT_CONF) {
+ for (ht_info = ht = htable_list(table); *ht; ht++) {
+ cvalue = (struct cvalue *) ht[0]->value;
+ if (cvalue->found == 0)
+ vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value);
+ }
+ myfree((void *) ht_info);
+ }
+
+ /*
+ * When all is well, rename the temp file to the original one.
+ */
+ if (vstream_fclose(src))
+ msg_fatal("read %s: %m", path);
+ if (edit_file_close(ep) != 0)
+ msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX);
+
+ /*
+ * Cleanup.
+ */
+ myfree(path);
+ vstring_free(buf);
+ vstring_free(key);
+ htable_free(table, myfree);
+}
+
+ /*
+ * Data structure to hold a master.cf edit request.
+ */
+typedef struct {
+ int match_count; /* hit count */
+ const char *raw_text; /* unparsed command-line argument */
+ char *parsed_text; /* destructive parse */
+ ARGV *service_pattern; /* service name, type, ... */
+ int field_number; /* attribute field number */
+ const char *param_pattern; /* parameter name */
+ char *edit_value; /* value substring */
+} PCF_MASTER_EDIT_REQ;
+
+/* pcf_edit_master - edit master.cf file */
+
+void pcf_edit_master(int mode, int argc, char **argv)
+{
+ const char *myname = "pcf_edit_master";
+ char *path;
+ EDIT_FILE *ep;
+ VSTREAM *src;
+ VSTREAM *dst;
+ VSTRING *line_buf = vstring_alloc(100);
+ VSTRING *parse_buf = vstring_alloc(100);
+ int lineno;
+ PCF_MASTER_ENT *new_entry;
+ VSTRING *full_entry_buf = vstring_alloc(100);
+ char *cp;
+ char *pattern;
+ int service_name_type_matched;
+ const char *err;
+ PCF_MASTER_EDIT_REQ *edit_reqs;
+ PCF_MASTER_EDIT_REQ *req;
+ int num_reqs = argc;
+ const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#";
+ char *service_name;
+ char *service_type;
+
+ /*
+ * Sanity check.
+ */
+ if (num_reqs <= 0)
+ msg_panic("%s: empty argument list", myname);
+
+ /*
+ * Preprocessing: split pattern=value, then split the pattern components.
+ */
+ edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof(*edit_reqs) * num_reqs);
+ for (req = edit_reqs; *argv != 0; req++, argv++) {
+ req->match_count = 0;
+ req->raw_text = *argv;
+ cp = req->parsed_text = mystrdup(req->raw_text);
+ if (strchr(cp, '\n') != 0)
+ msg_fatal("%s accept no multi-line input", edit_opts);
+ while (ISSPACE(*cp))
+ cp++;
+ if (*cp == '#')
+ msg_fatal("%s accept no comment input", edit_opts);
+ /* Separate the pattern from the value. */
+ if (mode & PCF_EDIT_CONF) {
+ if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0)
+ msg_fatal("%s: \"%s\"", err, req->raw_text);
+#if 0
+ if ((mode & PCF_MASTER_PARAM)
+ && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
+ msg_fatal("whitespace in parameter value: \"%s\"",
+ req->raw_text);
+#endif
+ } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) {
+ if (strchr(cp, '=') != 0)
+ msg_fatal("-X or -# requires names without value");
+ pattern = cp;
+ trimblanks(pattern, 0);
+ req->edit_value = 0;
+ } else {
+ msg_panic("%s: unknown mode %d", myname, mode);
+ }
+
+#define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)
+
+ /*
+ * Split name/type or name/type/whatever pattern into components.
+ */
+ switch (mode & PCF_MASTER_MASK) {
+ case PCF_MASTER_ENTRY:
+ if ((req->service_pattern =
+ pcf_parse_service_pattern(pattern, 2, 2)) == 0)
+ msg_fatal("-Me, -MX or -M# requires service_name/type");
+ break;
+ case PCF_MASTER_FLD:
+ if ((req->service_pattern =
+ pcf_parse_service_pattern(pattern, 3, 3)) == 0)
+ msg_fatal("-Fe or -FX requires service_name/type/field_name");
+ req->field_number =
+ pcf_parse_field_pattern(req->service_pattern->argv[2]);
+ if (pcf_is_magic_field_pattern(req->field_number))
+ msg_fatal("-Fe does not accept wild-card field name");
+ if ((mode & PCF_EDIT_CONF)
+ && req->field_number < PCF_MASTER_FLD_CMD
+ && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
+ msg_fatal("-Fe does not accept whitespace in non-command field");
+ break;
+ case PCF_MASTER_PARAM:
+ if ((req->service_pattern =
+ pcf_parse_service_pattern(pattern, 3, 3)) == 0)
+ msg_fatal("-Pe or -PX requires service_name/type/parameter");
+ req->param_pattern = req->service_pattern->argv[2];
+ if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern))
+ msg_fatal("-Pe does not accept wild-card parameter name");
+ if ((mode & PCF_EDIT_CONF)
+ && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
+ msg_fatal("-Pe does not accept whitespace in parameter value");
+ break;
+ default:
+ msg_panic("%s: unknown edit mode %d", myname, mode);
+ }
+ }
+
+ /*
+ * Open a temp file for the result. This uses a deterministic name so we
+ * don't leave behind thrash with random names.
+ */
+ pcf_set_config_dir();
+ path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0);
+ if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0)
+ msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX);
+ dst = ep->tmp_fp;
+
+ /*
+ * Open the original file for input.
+ */
+ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) {
+ /* OK to delete, since we control the temp file name exclusively. */
+ (void) unlink(ep->tmp_path);
+ msg_fatal("open %s for reading: %m", path);
+ }
+
+ /*
+ * Copy original file to temp file, while replacing service entries on
+ * the fly.
+ */
+ service_name_type_matched = 0;
+ new_entry = 0;
+ lineno = 0;
+ while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0) {
+ vstring_strcpy(line_buf, STR(parse_buf));
+
+ /*
+ * Copy, skip or replace continued text.
+ */
+ if (cp > STR(parse_buf)) {
+ if (service_name_type_matched == 0)
+ vstream_fputs(STR(line_buf), dst);
+ else if (mode & PCF_COMMENT_OUT)
+ vstream_fprintf(dst, "#%s", STR(line_buf));
+ }
+
+ /*
+ * Copy or replace (start of) logical line.
+ */
+ else {
+ service_name_type_matched = 0;
+
+ /*
+ * Parse out the service name and type.
+ */
+ if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0
+ || (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0)
+ msg_fatal("file %s: line %d: specify service name and type "
+ "on the same line", path, lineno);
+ if (strchr(service_name, '='))
+ msg_fatal("file %s: line %d: service name syntax \"%s\" is "
+ "unsupported with %s", path, lineno, service_name,
+ edit_opts);
+ if (service_type[strcspn(service_type, "=/")] != 0)
+ msg_fatal("file %s: line %d: "
+ "service type syntax \"%s\" is unsupported with %s",
+ path, lineno, service_type, edit_opts);
+
+ /*
+ * Match each service pattern.
+ *
+ * Additional care is needed when a request adds or replaces an
+ * entire service definition, instead of a specific field or
+ * parameter. Given a command "postconf -M name1/type1='name2
+ * type2 ...'", where name1 and name2 may differ, and likewise
+ * for type1 and type2:
+ *
+ * - First, if an existing service definition a) matches the service
+ * pattern 'name1/type1', or b) matches the name and type in the
+ * new service definition 'name2 type2 ...', remove the service
+ * definition.
+ *
+ * - Then, after an a) or b) type match, add a new service
+ * definition for 'name2 type2 ...', but only after the first
+ * match.
+ *
+ * - Finally, if a request had no a) or b) type match for any
+ * master.cf service definition, add a new service definition for
+ * 'name2 type2 ...'.
+ */
+ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
+ PCF_MASTER_ENT *tentative_entry = 0;
+ int use_tentative_entry = 0;
+
+ /* Additional care for whole service definition requests. */
+ if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) {
+ tentative_entry = (PCF_MASTER_ENT *)
+ mymalloc(sizeof(*tentative_entry));
+ if ((err = pcf_parse_master_entry(tentative_entry,
+ req->edit_value)) != 0)
+ msg_fatal("%s: \"%s\"", err, req->raw_text);
+ }
+ if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern,
+ service_name,
+ service_type)) {
+ service_name_type_matched = 1; /* Sticky flag */
+ req->match_count += 1;
+
+ /*
+ * Generate replacement master.cf entries.
+ */
+ if ((mode & PCF_EDIT_CONF)
+ || ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) {
+ switch (mode & PCF_MASTER_MASK) {
+
+ /*
+ * Replace master.cf entry field or parameter
+ * value.
+ */
+ case PCF_MASTER_FLD:
+ case PCF_MASTER_PARAM:
+ if (new_entry == 0) {
+ /* Gobble up any continuation lines. */
+ pcf_gobble_cf_line(full_entry_buf, line_buf,
+ src, dst, &lineno);
+ new_entry = (PCF_MASTER_ENT *)
+ mymalloc(sizeof(*new_entry));
+ if ((err = pcf_parse_master_entry(new_entry,
+ STR(full_entry_buf))) != 0)
+ msg_fatal("file %s: line %d: %s",
+ path, lineno, err);
+ }
+ if (mode & PCF_MASTER_FLD) {
+ pcf_edit_master_field(new_entry,
+ req->field_number,
+ req->edit_value);
+ } else {
+ pcf_edit_master_param(new_entry, mode,
+ req->param_pattern,
+ req->edit_value);
+ }
+ break;
+
+ /*
+ * Replace entire master.cf entry.
+ */
+ case PCF_MASTER_ENTRY:
+ if (req->match_count == 1)
+ use_tentative_entry = 1;
+ break;
+ default:
+ msg_panic("%s: unknown edit mode %d", myname, mode);
+ }
+ }
+ } else if (tentative_entry != 0
+ && PCF_MATCH_SERVICE_PATTERN(tentative_entry->argv,
+ service_name,
+ service_type)) {
+ service_name_type_matched = 1; /* Sticky flag */
+ req->match_count += 1;
+ if (req->match_count == 1)
+ use_tentative_entry = 1;
+ }
+ if (tentative_entry != 0) {
+ if (use_tentative_entry) {
+ if (new_entry != 0)
+ pcf_free_master_entry(new_entry);
+ new_entry = tentative_entry;
+ } else {
+ pcf_free_master_entry(tentative_entry);
+ }
+ }
+ }
+
+ /*
+ * Pass through or replace the current input line.
+ */
+ if (new_entry) {
+ pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry);
+ pcf_free_master_entry(new_entry);
+ new_entry = 0;
+ } else if (service_name_type_matched == 0) {
+ vstream_fputs(STR(line_buf), dst);
+ } else if (mode & PCF_COMMENT_OUT) {
+ vstream_fprintf(dst, "#%s", STR(line_buf));
+ }
+ }
+ }
+
+ /*
+ * Postprocessing: when editing entire service entries, generate new
+ * entries for services not found. Otherwise (editing fields or
+ * parameters), "service not found" is a fatal error.
+ */
+ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
+ if (req->match_count == 0) {
+ if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) {
+ new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry));
+ if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0)
+ msg_fatal("%s: \"%s\"", err, req->raw_text);
+ pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry);
+ pcf_free_master_entry(new_entry);
+ } else if ((mode & PCF_MASTER_ENTRY) == 0) {
+ msg_warn("unmatched service_name/type: \"%s\"", req->raw_text);
+ }
+ }
+ }
+
+ /*
+ * When all is well, rename the temp file to the original one.
+ */
+ if (vstream_fclose(src))
+ msg_fatal("read %s: %m", path);
+ if (edit_file_close(ep) != 0)
+ msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX);
+
+ /*
+ * Cleanup.
+ */
+ myfree(path);
+ vstring_free(line_buf);
+ vstring_free(parse_buf);
+ vstring_free(full_entry_buf);
+ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
+ argv_free(req->service_pattern);
+ myfree(req->parsed_text);
+ }
+ myfree((void *) edit_reqs);
+}
diff --git a/src/postconf/postconf_lookup.c b/src/postconf/postconf_lookup.c
new file mode 100644
index 0000000..5185681
--- /dev/null
+++ b/src/postconf/postconf_lookup.c
@@ -0,0 +1,194 @@
+/*++
+/* NAME
+/* postconf_lookup 3
+/* SUMMARY
+/* parameter lookup routines
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* const char *pcf_lookup_parameter_value(mode, name, local_scope, node)
+/* int mode;
+/* const char *name;
+/* PCF_MASTER_ENT *local_scope;
+/* PCF_PARAM_NODE *node;
+/*
+/* char *pcf_expand_parameter_value(buf, mode, value, local_scope)
+/* VSTRING *buf;
+/* int mode;
+/* const char *value;
+/* PCF_MASTER_ENT *local_scope;
+/* DESCRIPTION
+/* These functions perform parameter value lookups. The order
+/* of decreasing precedence is:
+/* .IP \(bu
+/* Search name=value parameter settings in master.cf. These
+/* lookups are disabled with the PCF_SHOW_DEFS flag.
+/* .IP \(bu
+/* Search name=value parameter settings in main.cf. These
+/* lookups are disabled with the PCF_SHOW_DEFS flag.
+/* .IP \(bu
+/* Search built-in default parameter settings. These lookups
+/* are disabled with the PCF_SHOW_NONDEF flag.
+/* .PP
+/* pcf_lookup_parameter_value() looks up the value for the
+/* named parameter, and returns null if the name was not found.
+/*
+/* pcf_expand_parameter_value() expands $name in the specified
+/* parameter value. This function ignores the PCF_SHOW_NONDEF
+/* flag. The result value is a pointer to storage in a
+/* user-supplied buffer, or in a buffer that is overwritten
+/* with each call.
+/*
+/* Arguments:
+/* .IP buf
+/* Pointer to user-supplied buffer; must not be null.
+/* .IP mode
+/* Bit-wise OR of zero or one of the following (other flags
+/* are ignored):
+/* .RS
+/* .IP PCF_SHOW_DEFS
+/* Search built-in default parameter settings only.
+/* .IP PCF_SHOW_NONDEF
+/* Search local (master.cf) and global (main.cf) name=value
+/* parameter settings only.
+/* .RE
+/* .IP name
+/* The name of a parameter to be looked up.
+/* .IP value
+/* The parameter value where $name should be expanded.
+/* .IP local_scope
+/* Pointer to master.cf entry with local name=value settings,
+/* or a null pointer (i.e. no local parameter lookup).
+/* .IP node
+/* Global default value for the named parameter, or a null
+/* pointer (i.e. do the global default lookup anyway).
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+#include <dict.h>
+#include <stringops.h>
+#include <mac_expand.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+#define STR(x) vstring_str(x)
+
+/* pcf_lookup_parameter_value - look up specific parameter value */
+
+const char *pcf_lookup_parameter_value(int mode, const char *name,
+ PCF_MASTER_ENT *local_scope,
+ PCF_PARAM_NODE *node)
+{
+ const char *value = 0;
+
+#define LOOKUP(dict, name) ((dict) ? dict_get((dict), (name)) : 0)
+
+ /*
+ * Local name=value entries in master.cf take precedence over global
+ * name=value entries in main.cf. Built-in defaults have the lowest
+ * precedence.
+ */
+ if ((mode & PCF_SHOW_DEFS) != 0
+ || ((local_scope == 0
+ || ((value = LOOKUP(local_scope->ro_params, name)) == 0
+ && (value = LOOKUP(local_scope->all_params, name)) == 0))
+ && (value = dict_lookup(CONFIG_DICT, name)) == 0
+ && (mode & PCF_SHOW_NONDEF) == 0)) {
+ if (node != 0 || (node = PCF_PARAM_TABLE_FIND(pcf_param_table, name)) != 0)
+ value = pcf_convert_param_node(PCF_SHOW_DEFS, name, node);
+ }
+ return (value);
+}
+
+ /*
+ * Data structure to pass private state while recursively expanding $name in
+ * parameter values.
+ */
+typedef struct {
+ int mode;
+ PCF_MASTER_ENT *local_scope;
+} PCF_EVAL_CTX;
+
+/* pcf_lookup_parameter_value_wrapper - macro parser call-back routine */
+
+static const char *pcf_lookup_parameter_value_wrapper(const char *key,
+ int unused_type,
+ void *context)
+{
+ PCF_EVAL_CTX *cp = (PCF_EVAL_CTX *) context;
+
+ return (pcf_lookup_parameter_value(cp->mode, key, cp->local_scope,
+ (PCF_PARAM_NODE *) 0));
+}
+
+/* pcf_expand_parameter_value - expand $name in parameter value */
+
+char *pcf_expand_parameter_value(VSTRING *buf, int mode, const char *value,
+ PCF_MASTER_ENT *local_scope)
+{
+ const char *myname = "pcf_expand_parameter_value";
+ int status;
+ PCF_EVAL_CTX eval_ctx;
+
+ /*
+ * Sanity check.
+ */
+ if (buf == 0)
+ msg_panic("%s: null buffer pointer", myname);
+
+ /*
+ * Expand macros recursively.
+ *
+ * When expanding $name in "postconf -n" parameter values, don't limit the
+ * search to only non-default parameter values.
+ *
+ * When expanding $name in "postconf -d" parameter values, do limit the
+ * search to only default parameter values.
+ */
+#define DONT_FILTER (char *) 0
+
+ eval_ctx.mode = (mode & ~PCF_SHOW_NONDEF);
+ eval_ctx.local_scope = local_scope;
+ status = mac_expand(buf, value, MAC_EXP_FLAG_RECURSE, DONT_FILTER,
+ pcf_lookup_parameter_value_wrapper, (void *) &eval_ctx);
+ if (status & MAC_PARSE_ERROR)
+ msg_fatal("macro processing error");
+ if (msg_verbose > 1) {
+ if (strcmp(value, STR(buf)) != 0)
+ msg_info("%s: expand %s -> %s", myname, value, STR(buf));
+ else
+ msg_info("%s: const %s", myname, value);
+ }
+ return (STR(buf));
+}
diff --git a/src/postconf/postconf_main.c b/src/postconf/postconf_main.c
new file mode 100644
index 0000000..8e6e226
--- /dev/null
+++ b/src/postconf/postconf_main.c
@@ -0,0 +1,220 @@
+/*++
+/* NAME
+/* postconf_main 3
+/* SUMMARY
+/* basic support for main.cf
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_read_parameters()
+/*
+/* void pcf_show_parameters(fp, mode, param_class, names)
+/* VSTREAM *fp;
+/* int mode;
+/* int param_class;
+/* char **names;
+/* DESCRIPTION
+/* pcf_read_parameters() reads parameters from main.cf.
+/*
+/* pcf_set_parameters() takes an array of \fIname=value\fR
+/* pairs and overrides settings read with pcf_read_parameters().
+/*
+/* pcf_show_parameters() writes main.cf parameters to the
+/* specified output stream.
+/*
+/* Arguments:
+/* .IP fp
+/* Output stream.
+/* .IP mode
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP PCF_FOLD_LINE
+/* Fold long lines.
+/* .IP PCF_SHOW_DEFS
+/* Output default parameter values.
+/* .IP PCF_SHOW_NONDEF
+/* Output explicit settings only.
+/* .IP PCF_HIDE_NAME
+/* Output parameter values without the "name =" prefix.
+/* .IP PCF_SHOW_EVAL
+/* Expand $name in parameter values.
+/* .RE
+/* .IP param_class
+/* Bit-wise OR of one or more of the following:
+/* .RS
+/* .IP PCF_PARAM_FLAG_BUILTIN
+/* Show built-in parameters.
+/* .IP PCF_PARAM_FLAG_SERVICE
+/* Show service-defined parameters.
+/* .IP PCF_PARAM_FLAG_USER
+/* Show user-defined parameters.
+/* .RE
+/* .IP names
+/* List of zero or more parameter names. If the list is empty,
+/* output all parameters.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <readlline.h>
+#include <dict.h>
+#include <stringops.h>
+#include <htable.h>
+#include <mac_expand.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+#define STR(x) vstring_str(x)
+
+/* pcf_read_parameters - read parameter info from file */
+
+void pcf_read_parameters(void)
+{
+ char *path;
+
+ /*
+ * A direct rip-off of mail_conf_read(). XXX Avoid code duplication by
+ * better code decomposition.
+ */
+ pcf_set_config_dir();
+ path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0);
+ if (dict_load_file_xt(CONFIG_DICT, path) == 0)
+ msg_fatal("open %s: %m", path);
+ myfree(path);
+}
+
+/* pcf_set_parameters - add or override name=value pairs */
+
+void pcf_set_parameters(char **name_val_array)
+{
+ char *name, *value, *junk;
+ const char *err;
+ char **cpp;
+
+ for (cpp = name_val_array; *cpp; cpp++) {
+ junk = mystrdup(*cpp);
+ if ((err = split_nameval(junk, &name, &value)) != 0)
+ msg_fatal("invalid parameter override: %s: %s", *cpp, err);
+ mail_conf_update(name, value);
+ myfree(junk);
+ }
+}
+
+/* pcf_print_parameter - show specific parameter */
+
+static void pcf_print_parameter(VSTREAM *fp, int mode, const char *name,
+ PCF_PARAM_NODE *node)
+{
+ static VSTRING *exp_buf = 0;
+ const char *value;
+
+ if (exp_buf == 0)
+ exp_buf = vstring_alloc(100);
+
+ /*
+ * Use the default or actual value.
+ */
+ value = pcf_lookup_parameter_value(mode, name, (PCF_MASTER_ENT *) 0, node);
+
+ /*
+ * Optionally expand $name in the parameter value. Print the result with
+ * or without the name= prefix.
+ */
+ if (value != 0) {
+ if (mode & PCF_HIDE_VALUE) {
+ pcf_print_line(fp, mode, "%s\n", name);
+ } else {
+ if ((mode & PCF_SHOW_EVAL) != 0 && PCF_RAW_PARAMETER(node) == 0)
+ value = pcf_expand_parameter_value(exp_buf, mode, value,
+ (PCF_MASTER_ENT *) 0);
+ if ((mode & PCF_HIDE_NAME) == 0) {
+ pcf_print_line(fp, mode, "%s = %s\n", name, value);
+ } else {
+ pcf_print_line(fp, mode, "%s\n", value);
+ }
+ }
+ if (msg_verbose)
+ vstream_fflush(fp);
+ }
+}
+
+/* pcf_comp_names - qsort helper */
+
+static int pcf_comp_names(const void *a, const void *b)
+{
+ PCF_PARAM_INFO **ap = (PCF_PARAM_INFO **) a;
+ PCF_PARAM_INFO **bp = (PCF_PARAM_INFO **) b;
+
+ return (strcmp(PCF_PARAM_INFO_NAME(ap[0]),
+ PCF_PARAM_INFO_NAME(bp[0])));
+}
+
+/* pcf_show_parameters - show parameter info */
+
+void pcf_show_parameters(VSTREAM *fp, int mode, int param_class, char **names)
+{
+ PCF_PARAM_INFO **list;
+ PCF_PARAM_INFO **ht;
+ char **namep;
+ PCF_PARAM_NODE *node;
+
+ /*
+ * Show all parameters.
+ */
+ if (*names == 0) {
+ list = PCF_PARAM_TABLE_LIST(pcf_param_table);
+ qsort((void *) list, pcf_param_table->used, sizeof(*list),
+ pcf_comp_names);
+ for (ht = list; *ht; ht++)
+ if (param_class & PCF_PARAM_INFO_NODE(*ht)->flags)
+ pcf_print_parameter(fp, mode, PCF_PARAM_INFO_NAME(*ht),
+ PCF_PARAM_INFO_NODE(*ht));
+ myfree((void *) list);
+ return;
+ }
+
+ /*
+ * Show named parameters.
+ */
+ for (namep = names; *namep; namep++) {
+ if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, *namep)) == 0) {
+ msg_warn("%s: unknown parameter", *namep);
+ } else {
+ pcf_print_parameter(fp, mode, *namep, node);
+ }
+ }
+}
diff --git a/src/postconf/postconf_master.c b/src/postconf/postconf_master.c
new file mode 100644
index 0000000..5b876b2
--- /dev/null
+++ b/src/postconf/postconf_master.c
@@ -0,0 +1,1120 @@
+/*++
+/* NAME
+/* postconf_master 3
+/* SUMMARY
+/* support for master.cf
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* const char pcf_daemon_options_expecting_value[];
+/*
+/* void pcf_read_master(fail_on_open)
+/* int fail_on_open;
+/*
+/* void pcf_show_master_entries(fp, mode, service_filters)
+/* VSTREAM *fp;
+/* int mode;
+/* char **service_filters;
+/*
+/* void pcf_show_master_fields(fp, mode, n_filters, field_filters)
+/* VSTREAM *fp;
+/* int mode;
+/* int n_filters;
+/* char **field_filters;
+/*
+/* void pcf_edit_master_field(masterp, field, new_value)
+/* PCF_MASTER_ENT *masterp;
+/* int field;
+/* const char *new_value;
+/*
+/* void pcf_show_master_params(fp, mode, argc, **param_filters)
+/* VSTREAM *fp;
+/* int mode;
+/* int argc;
+/* char **param_filters;
+/*
+/* void pcf_edit_master_param(masterp, mode, param_name, param_value)
+/* PCF_MASTER_ENT *masterp;
+/* int mode;
+/* const char *param_name;
+/* const char *param_value;
+/* AUXILIARY FUNCTIONS
+/* const char *pcf_parse_master_entry(masterp, buf)
+/* PCF_MASTER_ENT *masterp;
+/* const char *buf;
+/*
+/* void pcf_print_master_entry(fp, mode, masterp)
+/* VSTREAM *fp;
+/* int mode;
+/* PCF_MASTER_ENT *masterp;
+/*
+/* void pcf_free_master_entry(masterp)
+/* PCF_MASTER_ENT *masterp;
+/* DESCRIPTION
+/* pcf_read_master() reads entries from master.cf into memory.
+/*
+/* pcf_show_master_entries() writes the entries in the master.cf
+/* file to the specified stream.
+/*
+/* pcf_show_master_fields() writes name/type/field=value records
+/* to the specified stream.
+/*
+/* pcf_edit_master_field() updates the value of a single-column
+/* or multi-column attribute.
+/*
+/* pcf_show_master_params() writes name/type/parameter=value
+/* records to the specified stream.
+/*
+/* pcf_edit_master_param() updates, removes or adds the named
+/* parameter in a master.cf entry (the remove request ignores
+/* the parameter value).
+/*
+/* pcf_daemon_options_expecting_value[] is an array of master.cf
+/* daemon command-line options that expect an option value.
+/*
+/* pcf_parse_master_entry() parses a (perhaps multi-line)
+/* string that contains a complete master.cf entry, and
+/* normalizes daemon command-line options to simplify further
+/* handling.
+/*
+/* pcf_print_master_entry() prints a parsed master.cf entry.
+/*
+/* pcf_free_master_entry() returns storage to the heap that
+/* was allocated by pcf_parse_master_entry().
+/*
+/* Arguments
+/* .IP fail_on_open
+/* Specify FAIL_ON_OPEN if open failure is a fatal error,
+/* WARN_ON_OPEN if a warning should be logged instead.
+/* .IP fp
+/* Output stream.
+/* .IP mode
+/* Bit-wise OR of flags. Flags other than the following are
+/* ignored.
+/* .RS
+/* .IP PCF_FOLD_LINE
+/* Wrap long output lines.
+/* .IP PCF_SHOW_EVAL
+/* Expand $name in parameter values.
+/* .IP PCF_EDIT_EXCL
+/* Request that pcf_edit_master_param() removes the parameter.
+/* .RE
+/* .IP n_filters
+/* The number of command-line filters.
+/* .IP field_filters
+/* A list of zero or more service field patterns (name/type/field).
+/* The output is formatted as "name/type/field = value". If
+/* no filters are specified, pcf_show_master_fields() outputs
+/* the fields of all master.cf entries in the specified order.
+/* .IP param_filters
+/* A list of zero or more service parameter patterns
+/* (name/type/parameter). The output is formatted as
+/* "name/type/parameter = value". If no filters are specified,
+/* pcf_show_master_params() outputs the parameters of all
+/* master.cf entries in sorted order.
+/* .IP service_filters
+/* A list of zero or more service patterns (name or name/type).
+/* If no filters are specified, pcf_show_master_entries()
+/* outputs all master.cf entries in the specified order.
+/* .IP field
+/* Index into parsed master.cf entry.
+/* .IP new_value
+/* Replacement value for the specified field. It is split in
+/* whitespace in case of a multi-field attribute.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <argv.h>
+#include <vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <dict_ht.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Master library. */
+
+#include <master_proto.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+const char pcf_daemon_options_expecting_value[] = "o";
+
+ /*
+ * Data structure to capture a command-line service field filter.
+ */
+typedef struct {
+ int match_count; /* hit count */
+ const char *raw_text; /* full pattern text */
+ ARGV *service_pattern; /* parsed service name, type, ... */
+ int field_pattern; /* parsed field pattern */
+ const char *param_pattern; /* parameter pattern */
+} PCF_MASTER_FLD_REQ;
+
+ /*
+ * Valid inputs.
+ */
+static const char *pcf_valid_master_types[] = {
+ MASTER_XPORT_NAME_UNIX,
+ MASTER_XPORT_NAME_FIFO,
+ MASTER_XPORT_NAME_INET,
+ MASTER_XPORT_NAME_PASS,
+ MASTER_XPORT_NAME_UXDG,
+ 0,
+};
+
+static const char pcf_valid_bool_types[] = "yn-";
+
+static VSTRING *pcf_exp_buf;
+
+#define STR(x) vstring_str(x)
+
+/* pcf_extract_field - extract text from {}, trim leading/trailing blanks */
+
+static void pcf_extract_field(ARGV *argv, int field, const char *parens)
+{
+ char *arg = argv->argv[field];
+ char *err;
+
+ if ((err = extpar(&arg, parens, EXTPAR_FLAG_STRIP)) != 0) {
+ msg_warn("%s: %s", MASTER_CONF_FILE, err);
+ myfree(err);
+ }
+ argv_replace_one(argv, field, arg);
+}
+
+/* pcf_normalize_nameval - normalize name = value from inside {} */
+
+static void pcf_normalize_nameval(ARGV *argv, int field)
+{
+ char *arg = argv->argv[field];
+ char *name;
+ char *value;
+ const char *err;
+ char *normalized;
+
+ if ((err = split_nameval(arg, &name, &value)) != 0) {
+ msg_warn("%s: %s: \"%s\"", MASTER_CONF_FILE, err, arg);
+ } else {
+ normalized = concatenate(name, "=", value, (char *) 0);
+ argv_replace_one(argv, field, normalized);
+ myfree(normalized);
+ }
+}
+
+/* pcf_normalize_daemon_args - bring daemon arguments into canonical form */
+
+static void pcf_normalize_daemon_args(ARGV *argv)
+{
+ int field;
+ char *arg;
+ char *cp;
+ char *junk;
+ int extract_field;
+
+ /*
+ * Normalize options to simplify later processing.
+ */
+ for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) {
+ arg = argv->argv[field];
+ if (arg[0] != '-' || strcmp(arg, "--") == 0)
+ break;
+ for (cp = arg + 1; *cp; cp++) {
+ if (strchr(pcf_daemon_options_expecting_value, *cp) != 0
+ && cp > arg + 1) {
+ /* Split "-stuffozz" into "-stuff" and "-ozz". */
+ junk = concatenate("-", cp, (char *) 0);
+ argv_insert_one(argv, field + 1, junk);
+ myfree(junk);
+ *cp = 0; /* XXX argv_replace_one() */
+ break;
+ }
+ }
+ if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0)
+ /* Option requires no value. */
+ continue;
+ if (arg[2] != 0) {
+ /* Split "-oname=value" into "-o" "name=value". */
+ argv_insert_one(argv, field + 1, arg + 2);
+ arg[2] = 0; /* XXX argv_replace_one() */
+ field += 1;
+ extract_field = (argv->argv[field][0] == CHARS_BRACE[0]);
+ } else if (argv->argv[field + 1] != 0) {
+ /* Already in "-o" "name=value" form. */
+ field += 1;
+ extract_field = (argv->argv[field][0] == CHARS_BRACE[0]);
+ } else
+ extract_field = 0;
+ /* Extract text inside {}, optionally convert to name=value. */
+ if (extract_field) {
+ pcf_extract_field(argv, field, CHARS_BRACE);
+ if (argv->argv[field - 1][1] == 'o')
+ pcf_normalize_nameval(argv, field);
+ }
+ }
+ /* Normalize non-option arguments. */
+ for ( /* void */ ; argv->argv[field] != 0; field++)
+ /* Extract text inside {}. */
+ if (argv->argv[field][0] == CHARS_BRACE[0])
+ pcf_extract_field(argv, field, CHARS_BRACE);
+}
+
+/* pcf_fix_fatal - fix multiline text before release */
+
+static NORETURN PRINTFLIKE(1, 2) pcf_fix_fatal(const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ va_list ap;
+
+ /*
+ * Replace newline with whitespace.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ translit(STR(buf), "\n", " ");
+ msg_fatal("%s", STR(buf));
+ /* NOTREACHED */
+}
+
+/* pcf_check_master_entry - sanity check master.cf entry */
+
+static void pcf_check_master_entry(ARGV *argv, const char *raw_text)
+{
+ const char **cpp;
+ char *cp;
+ int len;
+ int field;
+
+ cp = argv->argv[PCF_MASTER_FLD_TYPE];
+ for (cpp = pcf_valid_master_types; /* see below */ ; cpp++) {
+ if (*cpp == 0)
+ pcf_fix_fatal("invalid " PCF_MASTER_NAME_TYPE " field \"%s\" in \"%s\"",
+ cp, raw_text);
+ if (strcmp(*cpp, cp) == 0)
+ break;
+ }
+
+ for (field = PCF_MASTER_FLD_PRIVATE; field <= PCF_MASTER_FLD_CHROOT; field++) {
+ cp = argv->argv[field];
+ if (cp[1] != 0 || strchr(pcf_valid_bool_types, *cp) == 0)
+ pcf_fix_fatal("invalid %s field \"%s\" in \"%s\"",
+ pcf_str_field_pattern(field), cp, raw_text);
+ }
+
+ cp = argv->argv[PCF_MASTER_FLD_WAKEUP];
+ len = strlen(cp);
+ if (len > 0 && cp[len - 1] == '?')
+ len--;
+ if (!(cp[0] == '-' && len == 1) && strspn(cp, "0123456789") != len)
+ pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \"%s\" in \"%s\"",
+ cp, raw_text);
+
+ cp = argv->argv[PCF_MASTER_FLD_MAXPROC];
+ if (strcmp("-", cp) != 0 && cp[strspn(cp, "0123456789")] != 0)
+ pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \"%s\" in \"%s\"",
+ cp, raw_text);
+}
+
+/* pcf_free_master_entry - destroy parsed entry */
+
+void pcf_free_master_entry(PCF_MASTER_ENT *masterp)
+{
+ /* XX Fixme: allocation/deallocation asymmetry. */
+ myfree(masterp->name_space);
+ argv_free(masterp->argv);
+ if (masterp->valid_names)
+ htable_free(masterp->valid_names, myfree);
+ if (masterp->ro_params)
+ dict_close(masterp->ro_params);
+ if (masterp->all_params)
+ dict_close(masterp->all_params);
+ myfree((void *) masterp);
+}
+
+/* pcf_parse_master_entry - parse one master line */
+
+const char *pcf_parse_master_entry(PCF_MASTER_ENT *masterp, const char *buf)
+{
+ ARGV *argv;
+ char *ro_name_space;
+ char *process_name;
+
+ /*
+ * We can't use the master daemon's master_ent routines in their current
+ * form. They convert everything to internal form, and they skip disabled
+ * services.
+ *
+ * The postconf command needs to show default fields as "-", and needs to
+ * know about all service names so that it can generate service-dependent
+ * parameter names (transport-dependent etc.).
+ *
+ * XXX Do per-field sanity checks.
+ */
+ argv = argv_splitq(buf, PCF_MASTER_BLANKS, CHARS_BRACE);
+ if (argv->argc < PCF_MASTER_MIN_FIELDS) {
+ argv_free(argv); /* Coverity 201311 */
+ return ("bad field count");
+ }
+ pcf_check_master_entry(argv, buf);
+ pcf_normalize_daemon_args(argv);
+ masterp->name_space =
+ concatenate(argv->argv[0], PCF_NAMESP_SEP_STR, argv->argv[1], (char *) 0);
+ ro_name_space =
+ concatenate("ro", PCF_NAMESP_SEP_STR, masterp->name_space, (char *) 0);
+ masterp->argv = argv;
+ masterp->valid_names = 0;
+ masterp->ro_params = dict_ht_open(ro_name_space, O_CREAT | O_RDWR, 0);
+ process_name = basename(argv->argv[PCF_MASTER_FLD_CMD]);
+ dict_put(masterp->ro_params, VAR_PROCNAME, process_name);
+ dict_put(masterp->ro_params, VAR_SERVNAME,
+ strcmp(process_name, argv->argv[0]) != 0 ?
+ argv->argv[0] : process_name);
+ myfree(ro_name_space);
+ masterp->all_params = 0;
+ return (0);
+}
+
+/* pcf_read_master - read and digest the master.cf file */
+
+void pcf_read_master(int fail_on_open_error)
+{
+ const char *myname = "pcf_read_master";
+ char *path;
+ VSTRING *buf;
+ VSTREAM *fp;
+ const char *err;
+ int entry_count = 0;
+ int line_count;
+ int last_line = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (pcf_master_table != 0)
+ msg_panic("%s: master table is already initialized", myname);
+
+ /*
+ * Get the location of master.cf.
+ */
+ if (var_config_dir == 0)
+ pcf_set_config_dir();
+ path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0);
+
+ /*
+ * Initialize the in-memory master table.
+ */
+ pcf_master_table = (PCF_MASTER_ENT *) mymalloc(sizeof(*pcf_master_table));
+
+ /*
+ * Skip blank lines and comment lines. Degrade gracefully if master.cf is
+ * not available, and master.cf is not the primary target.
+ */
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) {
+ if (fail_on_open_error)
+ msg_fatal("open %s: %m", path);
+ msg_warn("open %s: %m", path);
+ } else {
+ buf = vstring_alloc(100);
+ while (readllines(buf, fp, &last_line, &line_count) != 0) {
+ pcf_master_table = (PCF_MASTER_ENT *) myrealloc((void *) pcf_master_table,
+ (entry_count + 2) * sizeof(*pcf_master_table));
+ if ((err = pcf_parse_master_entry(pcf_master_table + entry_count,
+ STR(buf))) != 0)
+ msg_fatal("file %s: line %d: %s", path, line_count, err);
+ entry_count += 1;
+ }
+ vstream_fclose(fp);
+ vstring_free(buf);
+ }
+
+ /*
+ * Null-terminate the master table and clean up.
+ */
+ pcf_master_table[entry_count].argv = 0;
+ myfree(path);
+}
+
+/* pcf_print_master_entry - print one master line */
+
+void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp)
+{
+ char **argv = masterp->argv->argv;
+ const char *arg;
+ const char *aval;
+ int arg_len;
+ int line_len;
+ int field;
+ int in_daemon_options;
+ int need_parens;
+ static int column_goal[] = {
+ 0, /* service */
+ 11, /* type */
+ 17, /* private */
+ 25, /* unpriv */
+ 33, /* chroot */
+ 41, /* wakeup */
+ 49, /* maxproc */
+ 57, /* command */
+ };
+
+#define ADD_TEXT(text, len) do { \
+ vstream_fputs(text, fp); line_len += len; } \
+ while (0)
+#define ADD_SPACE ADD_TEXT(" ", 1)
+
+ if (pcf_exp_buf == 0)
+ pcf_exp_buf = vstring_alloc(100);
+
+ /*
+ * Show the standard fields at their preferred column position. Use at
+ * least one-space column separation.
+ */
+ for (line_len = 0, field = 0; field < PCF_MASTER_MIN_FIELDS; field++) {
+ arg = argv[field];
+ if (line_len > 0) {
+ do {
+ ADD_SPACE;
+ } while (line_len < column_goal[field]);
+ }
+ ADD_TEXT(arg, strlen(arg));
+ }
+
+ /*
+ * Format the daemon command-line options and non-option arguments. Here,
+ * we have no data-dependent preference for column positions, but we do
+ * have argument grouping preferences.
+ */
+ in_daemon_options = 1;
+ for ( /* void */ ; (arg = argv[field]) != 0; field++) {
+ arg_len = strlen(arg);
+ aval = 0;
+ need_parens = 0;
+ if (in_daemon_options) {
+
+ /*
+ * Try to show the generic options (-v -D) on the first line, and
+ * non-options on a later line.
+ */
+ if (arg[0] != '-' || strcmp(arg, "--") == 0) {
+ in_daemon_options = 0;
+#if 0
+ if (mode & PCF_FOLD_LINE)
+ /* Force line wrap. */
+ line_len = PCF_LINE_LIMIT;
+#endif
+ }
+
+ /*
+ * Special processing for options that require a value.
+ */
+ else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0
+ && (aval = argv[field + 1]) != 0) {
+
+ /* Force line wrap before option with value. */
+ line_len = PCF_LINE_LIMIT;
+
+ /*
+ * Optionally, expand $name in parameter value.
+ */
+ if (strcmp(arg, "-o") == 0
+ && (mode & PCF_SHOW_EVAL) != 0)
+ aval = pcf_expand_parameter_value(pcf_exp_buf, mode,
+ aval, masterp);
+
+ /*
+ * Keep option and value on the same line.
+ */
+ arg_len += strlen(aval) + 3;
+ if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0)
+ arg_len += 2;
+ }
+ } else {
+ need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)];
+ }
+
+ /*
+ * Insert a line break when the next item won't fit.
+ */
+ if (line_len > PCF_INDENT_LEN) {
+ if ((mode & PCF_FOLD_LINE) == 0
+ || line_len + 1 + arg_len < PCF_LINE_LIMIT) {
+ ADD_SPACE;
+ } else {
+ vstream_fputs("\n" PCF_INDENT_TEXT, fp);
+ line_len = PCF_INDENT_LEN;
+ }
+ }
+ if (in_daemon_options == 0 && need_parens)
+ ADD_TEXT("{", 1);
+ ADD_TEXT(arg, strlen(arg));
+ if (in_daemon_options == 0 && need_parens)
+ ADD_TEXT("}", 1);
+ if (aval) {
+ ADD_TEXT(" ", 1);
+ if (need_parens)
+ ADD_TEXT("{", 1);
+ ADD_TEXT(aval, strlen(aval));
+ if (need_parens)
+ ADD_TEXT("}", 1);
+ field += 1;
+
+ /* Force line wrap after option with value. */
+ line_len = PCF_LINE_LIMIT;
+
+ }
+ }
+ vstream_fputs("\n", fp);
+
+ if (msg_verbose)
+ vstream_fflush(fp);
+}
+
+/* pcf_show_master_entries - show master.cf entries */
+
+void pcf_show_master_entries(VSTREAM *fp, int mode, int argc, char **argv)
+{
+ PCF_MASTER_ENT *masterp;
+ PCF_MASTER_FLD_REQ *field_reqs;
+ PCF_MASTER_FLD_REQ *req;
+
+ /*
+ * Parse the filter expressions.
+ */
+ if (argc > 0) {
+ field_reqs = (PCF_MASTER_FLD_REQ *)
+ mymalloc(sizeof(*field_reqs) * argc);
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ req->match_count = 0;
+ req->raw_text = *argv++;
+ req->service_pattern =
+ pcf_parse_service_pattern(req->raw_text, 1, 2);
+ if (req->service_pattern == 0)
+ msg_fatal("-M option requires service_name[/type]");
+ }
+ }
+
+ /*
+ * Iterate over the master table.
+ */
+ for (masterp = pcf_master_table; masterp->argv != 0; masterp++) {
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern,
+ masterp->argv->argv[0],
+ masterp->argv->argv[1])) {
+ req->match_count++;
+ pcf_print_master_entry(fp, mode, masterp);
+ }
+ }
+ } else {
+ pcf_print_master_entry(fp, mode, masterp);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (req->match_count == 0)
+ msg_warn("unmatched request: \"%s\"", req->raw_text);
+ argv_free(req->service_pattern);
+ }
+ myfree((void *) field_reqs);
+ }
+}
+
+/* pcf_print_master_field - scaffolding */
+
+static void pcf_print_master_field(VSTREAM *fp, int mode,
+ PCF_MASTER_ENT *masterp,
+ int field)
+{
+ char **argv = masterp->argv->argv;
+ const char *arg;
+ const char *aval;
+ int arg_len;
+ int line_len;
+ int in_daemon_options;
+ int need_parens;
+
+ if (pcf_exp_buf == 0)
+ pcf_exp_buf = vstring_alloc(100);
+
+ /*
+ * Show the field value, or the first value in the case of a multi-column
+ * field.
+ */
+#define ADD_CHAR(ch) ADD_TEXT((ch), 1)
+
+ line_len = 0;
+ if ((mode & PCF_HIDE_NAME) == 0) {
+ ADD_TEXT(argv[0], strlen(argv[0]));
+ ADD_CHAR(PCF_NAMESP_SEP_STR);
+ ADD_TEXT(argv[1], strlen(argv[1]));
+ ADD_CHAR(PCF_NAMESP_SEP_STR);
+ ADD_TEXT(pcf_str_field_pattern(field), strlen(pcf_str_field_pattern(field)));
+ }
+ if ((mode & (PCF_HIDE_NAME | PCF_HIDE_VALUE)) == 0) {
+ ADD_TEXT(" = ", 3);
+ }
+ if ((mode & PCF_HIDE_VALUE) == 0) {
+ if (line_len > 0 && line_len + strlen(argv[field]) > PCF_LINE_LIMIT) {
+ vstream_fputs("\n" PCF_INDENT_TEXT, fp);
+ line_len = PCF_INDENT_LEN;
+ }
+ ADD_TEXT(argv[field], strlen(argv[field]));
+ }
+
+ /*
+ * Format the daemon command-line options and non-option arguments. Here,
+ * we have no data-dependent preference for column positions, but we do
+ * have argument grouping preferences.
+ */
+ if (field == PCF_MASTER_FLD_CMD && (mode & PCF_HIDE_VALUE) == 0) {
+ in_daemon_options = 1;
+ for (field += 1; (arg = argv[field]) != 0; field++) {
+ arg_len = strlen(arg);
+ aval = 0;
+ need_parens = 0;
+ if (in_daemon_options) {
+
+ /*
+ * We make no special case for generic options (-v -D)
+ * options.
+ */
+ if (arg[0] != '-' || strcmp(arg, "--") == 0) {
+ in_daemon_options = 0;
+ } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0
+ && (aval = argv[field + 1]) != 0) {
+
+ /* Force line break before option with value. */
+ line_len = PCF_LINE_LIMIT;
+
+ /*
+ * Optionally, expand $name in parameter value.
+ */
+ if (strcmp(arg, "-o") == 0
+ && (mode & PCF_SHOW_EVAL) != 0)
+ aval = pcf_expand_parameter_value(pcf_exp_buf, mode,
+ aval, masterp);
+
+ /*
+ * Keep option and value on the same line.
+ */
+ arg_len += strlen(aval) + 1;
+ if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0)
+ arg_len += 2;
+ }
+ } else {
+ need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)];
+ }
+
+ /*
+ * Insert a line break when the next item won't fit.
+ */
+ if (line_len > PCF_INDENT_LEN) {
+ if ((mode & PCF_FOLD_LINE) == 0
+ || line_len + 1 + arg_len < PCF_LINE_LIMIT) {
+ ADD_SPACE;
+ } else {
+ vstream_fputs("\n" PCF_INDENT_TEXT, fp);
+ line_len = PCF_INDENT_LEN;
+ }
+ }
+ if (in_daemon_options == 0 && need_parens)
+ ADD_TEXT("{", 1);
+ ADD_TEXT(arg, strlen(arg));
+ if (in_daemon_options == 0 && need_parens)
+ ADD_TEXT("}", 1);
+ if (aval) {
+ ADD_SPACE;
+ if (need_parens)
+ ADD_TEXT("{", 1);
+ ADD_TEXT(aval, strlen(aval));
+ if (need_parens)
+ ADD_TEXT("}", 1);
+ field += 1;
+
+ /* Force line break after option with value. */
+ line_len = PCF_LINE_LIMIT;
+ }
+ }
+ }
+ vstream_fputs("\n", fp);
+
+ if (msg_verbose)
+ vstream_fflush(fp);
+}
+
+/* pcf_show_master_fields - show master.cf fields */
+
+void pcf_show_master_fields(VSTREAM *fp, int mode, int argc, char **argv)
+{
+ const char *myname = "pcf_show_master_fields";
+ PCF_MASTER_ENT *masterp;
+ PCF_MASTER_FLD_REQ *field_reqs;
+ PCF_MASTER_FLD_REQ *req;
+ int field;
+
+ /*
+ * Parse the filter expressions.
+ */
+ if (argc > 0) {
+ field_reqs = (PCF_MASTER_FLD_REQ *)
+ mymalloc(sizeof(*field_reqs) * argc);
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ req->match_count = 0;
+ req->raw_text = *argv++;
+ req->service_pattern =
+ pcf_parse_service_pattern(req->raw_text, 1, 3);
+ if (req->service_pattern == 0)
+ msg_fatal("-F option requires service_name[/type[/field]]");
+ field = req->field_pattern =
+ pcf_parse_field_pattern(req->service_pattern->argv[2]);
+ if (pcf_is_magic_field_pattern(field) == 0
+ && (field < 0 || field > PCF_MASTER_FLD_CMD))
+ msg_panic("%s: bad attribute field index: %d",
+ myname, field);
+ }
+ }
+
+ /*
+ * Iterate over the master table.
+ */
+ for (masterp = pcf_master_table; masterp->argv != 0; masterp++) {
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern,
+ masterp->argv->argv[0],
+ masterp->argv->argv[1])) {
+ req->match_count++;
+ field = req->field_pattern;
+ if (pcf_is_magic_field_pattern(field)) {
+ for (field = 0; field <= PCF_MASTER_FLD_CMD; field++)
+ pcf_print_master_field(fp, mode, masterp, field);
+ } else {
+ pcf_print_master_field(fp, mode, masterp, field);
+ }
+ }
+ }
+ } else {
+ for (field = 0; field <= PCF_MASTER_FLD_CMD; field++)
+ pcf_print_master_field(fp, mode, masterp, field);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (req->match_count == 0)
+ msg_warn("unmatched request: \"%s\"", req->raw_text);
+ argv_free(req->service_pattern);
+ }
+ myfree((void *) field_reqs);
+ }
+}
+
+/* pcf_edit_master_field - replace master.cf field value. */
+
+void pcf_edit_master_field(PCF_MASTER_ENT *masterp, int field,
+ const char *new_value)
+{
+
+ /*
+ * Replace multi-column attribute.
+ */
+ if (field == PCF_MASTER_FLD_CMD) {
+ argv_truncate(masterp->argv, PCF_MASTER_FLD_CMD);
+ argv_splitq_append(masterp->argv, new_value, PCF_MASTER_BLANKS, CHARS_BRACE);
+ pcf_normalize_daemon_args(masterp->argv);
+ }
+
+ /*
+ * Replace single-column attribute.
+ */
+ else {
+ argv_replace_one(masterp->argv, field, new_value);
+ }
+
+ /*
+ * Do per-field sanity checks.
+ */
+ pcf_check_master_entry(masterp->argv, new_value);
+}
+
+/* pcf_print_master_param - scaffolding */
+
+static void pcf_print_master_param(VSTREAM *fp, int mode,
+ PCF_MASTER_ENT *masterp,
+ const char *param_name,
+ const char *param_value)
+{
+ if (pcf_exp_buf == 0)
+ pcf_exp_buf = vstring_alloc(100);
+
+ if (mode & PCF_HIDE_VALUE) {
+ pcf_print_line(fp, mode, "%s%c%s\n",
+ masterp->name_space, PCF_NAMESP_SEP_CH,
+ param_name);
+ } else {
+ if ((mode & PCF_SHOW_EVAL) != 0)
+ param_value = pcf_expand_parameter_value(pcf_exp_buf, mode,
+ param_value, masterp);
+ if ((mode & PCF_HIDE_NAME) == 0) {
+ pcf_print_line(fp, mode, "%s%c%s = %s\n",
+ masterp->name_space, PCF_NAMESP_SEP_CH,
+ param_name, param_value);
+ } else {
+ pcf_print_line(fp, mode, "%s\n", param_value);
+ }
+ }
+ if (msg_verbose)
+ vstream_fflush(fp);
+}
+
+/* pcf_sort_argv_cb - sort argv call-back */
+
+static int pcf_sort_argv_cb(const void *a, const void *b)
+{
+ return (strcmp(*(char **) a, *(char **) b));
+}
+
+/* pcf_show_master_any_param - show any parameter in master.cf service entry */
+
+static void pcf_show_master_any_param(VSTREAM *fp, int mode,
+ PCF_MASTER_ENT *masterp)
+{
+ const char *myname = "pcf_show_master_any_param";
+ ARGV *argv = argv_alloc(10);
+ DICT *dict = masterp->all_params;
+ const char *param_name;
+ const char *param_value;
+ int param_count = 0;
+ int how;
+ char **cpp;
+
+ /*
+ * Print parameters in sorted order. The number of parameters per
+ * master.cf entry is small, so we optimize for code simplicity and don't
+ * worry about the cost of double lookup.
+ */
+
+ /* Look up the parameter names and ignore the values. */
+
+ for (how = DICT_SEQ_FUN_FIRST;
+ dict->sequence(dict, how, &param_name, &param_value) == 0;
+ how = DICT_SEQ_FUN_NEXT) {
+ argv_add(argv, param_name, ARGV_END);
+ param_count++;
+ }
+
+ /* Print the parameters in sorted order. */
+
+ qsort(argv->argv, param_count, sizeof(argv->argv[0]), pcf_sort_argv_cb);
+ for (cpp = argv->argv; (param_name = *cpp) != 0; cpp++) {
+ if ((param_value = dict_get(dict, param_name)) == 0)
+ msg_panic("%s: parameter name not found: %s", myname, param_name);
+ pcf_print_master_param(fp, mode, masterp, param_name, param_value);
+ }
+
+ /*
+ * Clean up.
+ */
+ argv_free(argv);
+}
+
+/* pcf_show_master_params - show master.cf params */
+
+void pcf_show_master_params(VSTREAM *fp, int mode, int argc, char **argv)
+{
+ PCF_MASTER_ENT *masterp;
+ PCF_MASTER_FLD_REQ *field_reqs;
+ PCF_MASTER_FLD_REQ *req;
+ DICT *dict;
+ const char *param_value;
+
+ /*
+ * Parse the filter expressions.
+ */
+ if (argc > 0) {
+ field_reqs = (PCF_MASTER_FLD_REQ *)
+ mymalloc(sizeof(*field_reqs) * argc);
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ req->match_count = 0;
+ req->raw_text = *argv++;
+ req->service_pattern =
+ pcf_parse_service_pattern(req->raw_text, 1, 3);
+ if (req->service_pattern == 0)
+ msg_fatal("-P option requires service_name[/type[/parameter]]");
+ req->param_pattern = req->service_pattern->argv[2];
+ }
+ }
+
+ /*
+ * Iterate over the master table.
+ */
+ for (masterp = pcf_master_table; masterp->argv != 0; masterp++) {
+ if ((dict = masterp->all_params) != 0) {
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern,
+ masterp->argv->argv[0],
+ masterp->argv->argv[1])) {
+ if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) {
+ pcf_show_master_any_param(fp, mode, masterp);
+ req->match_count += 1;
+ } else if ((param_value = dict_get(dict,
+ req->param_pattern)) != 0) {
+ pcf_print_master_param(fp, mode, masterp,
+ req->param_pattern,
+ param_value);
+ req->match_count += 1;
+ }
+ }
+ }
+ } else {
+ pcf_show_master_any_param(fp, mode, masterp);
+ }
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (argc > 0) {
+ for (req = field_reqs; req < field_reqs + argc; req++) {
+ if (req->match_count == 0)
+ msg_warn("unmatched request: \"%s\"", req->raw_text);
+ argv_free(req->service_pattern);
+ }
+ myfree((void *) field_reqs);
+ }
+}
+
+/* pcf_edit_master_param - update, add or remove -o parameter=value */
+
+void pcf_edit_master_param(PCF_MASTER_ENT *masterp, int mode,
+ const char *param_name,
+ const char *param_value)
+{
+ const char *myname = "pcf_edit_master_param";
+ ARGV *argv = masterp->argv;
+ const char *arg;
+ const char *aval;
+ int param_match = 0;
+ int name_len = strlen(param_name);
+ int field;
+
+ for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) {
+ arg = argv->argv[field];
+
+ /*
+ * Stop at the first non-option argument or end-of-list.
+ */
+ if (arg[0] != '-' || strcmp(arg, "--") == 0) {
+ break;
+ }
+
+ /*
+ * Zoom in on command-line options with a value.
+ */
+ else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0
+ && (aval = argv->argv[field + 1]) != 0) {
+
+ /*
+ * Zoom in on "-o parameter=value".
+ */
+ if (strcmp(arg, "-o") == 0) {
+ if (strncmp(aval, param_name, name_len) == 0
+ && aval[name_len] == '=') {
+ param_match = 1;
+ switch (mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL)) {
+
+ /*
+ * Update parameter=value.
+ */
+ case PCF_EDIT_CONF:
+ aval = concatenate(param_name, "=",
+ param_value, (char *) 0);
+ argv_replace_one(argv, field + 1, aval);
+ myfree((void *) aval);
+ if (masterp->all_params)
+ dict_put(masterp->all_params, param_name, param_value);
+ /* XXX Update parameter "used/defined" status. */
+ break;
+
+ /*
+ * Delete parameter=value.
+ */
+ case PCF_EDIT_EXCL:
+ argv_delete(argv, field, 2);
+ if (masterp->all_params)
+ dict_del(masterp->all_params, param_name);
+ /* XXX Update parameter "used/defined" status. */
+ field -= 2;
+ break;
+ default:
+ msg_panic("%s: unexpected mode: %d", myname, mode);
+ }
+ }
+ }
+
+ /*
+ * Skip over the command-line option value.
+ */
+ field += 1;
+ }
+ }
+
+ /*
+ * Add unmatched parameter.
+ */
+ if ((mode & PCF_EDIT_CONF) && param_match == 0) {
+ /* XXX Generalize: argv_insert(argv, where, list...) */
+ argv_insert_one(argv, field, "-o");
+ aval = concatenate(param_name, "=",
+ param_value, (char *) 0);
+ argv_insert_one(argv, field + 1, aval);
+ if (masterp->all_params)
+ dict_put(masterp->all_params, param_name, param_value);
+ /* XXX May affect parameter "used/defined" status. */
+ myfree((void *) aval);
+ param_match = 1;
+ }
+}
diff --git a/src/postconf/postconf_match.c b/src/postconf/postconf_match.c
new file mode 100644
index 0000000..53709a4
--- /dev/null
+++ b/src/postconf/postconf_match.c
@@ -0,0 +1,188 @@
+/*++
+/* NAME
+/* postconf_match 3
+/* SUMMARY
+/* pattern-matching support
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* int pcf_parse_field_pattern(field_expr)
+/* char *field_expr;
+/*
+/* const char *pcf_str_field_pattern(field_pattern)
+/* int field_pattern;
+/*
+/* int pcf_is_magic_field_pattern(field_pattern)
+/* int field_pattern;
+/*
+/* ARGV *pcf_parse_service_pattern(service_expr, min_expr, max_expr)
+/* const char *service_expr;
+/* int min_expr;
+/* int max_expr;
+/*
+/* int PCF_IS_MAGIC_SERVICE_PATTERN(service_pattern)
+/* const ARGV *service_pattern;
+/*
+/* int PCF_MATCH_SERVICE_PATTERN(service_pattern, service_name,
+/* service_type)
+/* const ARGV *service_pattern;
+/* const char *service_name;
+/* const char *service_type;
+/*
+/* const char *pcf_str_field_pattern(field_pattern)
+/* int field_pattern;
+/*
+/* int PCF_IS_MAGIC_PARAM_PATTERN(param_pattern)
+/* const char *param_pattern;
+/*
+/* int PCF_MATCH_PARAM_PATTERN(param_pattern, param_name)
+/* const char *param_pattern;
+/* const char *param_name;
+/* DESCRIPTION
+/* pcf_parse_service_pattern() takes an expression and splits
+/* it up on '/' into an array of sub-expressions, This function
+/* returns null if the input does fewer than "min" or more
+/* than "max" sub-expressions.
+/*
+/* PCF_IS_MAGIC_SERVICE_PATTERN() returns non-zero if any of
+/* the service name or service type sub-expressions contains
+/* a matching operator (as opposed to string literals that
+/* only match themselves). This is an unsafe macro that evaluates
+/* its arguments more than once.
+/*
+/* PCF_MATCH_SERVICE_PATTERN() matches a service name and type
+/* from master.cf against the parsed pattern. This is an unsafe
+/* macro that evaluates its arguments more than once.
+/*
+/* pcf_parse_field_pattern() converts a field sub-expression,
+/* and returns the conversion result.
+/*
+/* pcf_str_field_pattern() converts a result from
+/* pcf_parse_field_pattern() into string form.
+/*
+/* pcf_is_magic_field_pattern() returns non-zero if the field
+/* pattern sub-expression contained a matching operator (as
+/* opposed to a string literal that only matches itself).
+/*
+/* PCF_IS_MAGIC_PARAM_PATTERN() returns non-zero if the parameter
+/* sub-expression contains a matching operator (as opposed to
+/* a string literal that only matches itself). This is an
+/* unsafe macro that evaluates its arguments more than once.
+/*
+/* PCF_MATCH_PARAM_PATTERN() matches a parameter name from
+/* master.cf against the parsed pattern. This is an unsafe
+/* macro that evaluates its arguments more than once.
+/*
+/* Arguments
+/* .IP field_expr
+/* A field expression.
+/* .IP service_expr
+/* This argument is split on '/' into its constituent
+/* sub-expressions.
+/* .IP min_expr
+/* The minimum number of sub-expressions in service_expr.
+/* .IP max_expr
+/* The maximum number of sub-expressions in service_expr.
+/* .IP service_name
+/* Service name from master.cf.
+/* .IP service_type
+/* Service type from master.cf.
+/* .IP param_pattern
+/* A parameter name expression.
+/* DIAGNOSTICS
+/* Fatal errors: invalid syntax.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <split_at.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * Conversion table. Each PCF_MASTER_NAME_XXX name entry must be stored at
+ * table offset PCF_MASTER_FLD_XXX. So don't mess it up.
+ */
+NAME_CODE pcf_field_name_offset[] = {
+ PCF_MASTER_NAME_SERVICE, PCF_MASTER_FLD_SERVICE,
+ PCF_MASTER_NAME_TYPE, PCF_MASTER_FLD_TYPE,
+ PCF_MASTER_NAME_PRIVATE, PCF_MASTER_FLD_PRIVATE,
+ PCF_MASTER_NAME_UNPRIV, PCF_MASTER_FLD_UNPRIV,
+ PCF_MASTER_NAME_CHROOT, PCF_MASTER_FLD_CHROOT,
+ PCF_MASTER_NAME_WAKEUP, PCF_MASTER_FLD_WAKEUP,
+ PCF_MASTER_NAME_MAXPROC, PCF_MASTER_FLD_MAXPROC,
+ PCF_MASTER_NAME_CMD, PCF_MASTER_FLD_CMD,
+ "*", PCF_MASTER_FLD_WILDC,
+ 0, PCF_MASTER_FLD_NONE,
+};
+
+/* pcf_parse_field_pattern - parse service attribute pattern */
+
+int pcf_parse_field_pattern(const char *field_name)
+{
+ int field_pattern;
+
+ if ((field_pattern = name_code(pcf_field_name_offset,
+ NAME_CODE_FLAG_STRICT_CASE,
+ field_name)) == PCF_MASTER_FLD_NONE)
+ msg_fatal("invalid service attribute name: \"%s\"", field_name);
+ return (field_pattern);
+}
+
+/* pcf_parse_service_pattern - parse service pattern */
+
+ARGV *pcf_parse_service_pattern(const char *pattern, int min_expr, int max_expr)
+{
+ ARGV *argv;
+ char **cpp;
+
+ /*
+ * Work around argv_split() lameness.
+ */
+ if (*pattern == '/')
+ return (0);
+ argv = argv_split(pattern, PCF_NAMESP_SEP_STR);
+ if (argv->argc < min_expr || argv->argc > max_expr) {
+ argv_free(argv);
+ return (0);
+ }
+
+ /*
+ * Allow '*' only all by itself.
+ */
+ for (cpp = argv->argv; *cpp; cpp++) {
+ if (!PCF_MATCH_ANY(*cpp) && strchr(*cpp, PCF_MATCH_WILDC_STR[0]) != 0) {
+ argv_free(argv);
+ return (0);
+ }
+ }
+
+ /*
+ * Provide defaults for missing fields.
+ */
+ while (argv->argc < max_expr)
+ argv_add(argv, PCF_MATCH_WILDC_STR, ARGV_END);
+ return (argv);
+}
diff --git a/src/postconf/postconf_misc.c b/src/postconf/postconf_misc.c
new file mode 100644
index 0000000..0107651
--- /dev/null
+++ b/src/postconf/postconf_misc.c
@@ -0,0 +1,60 @@
+/*++
+/* NAME
+/* postconf_misc 3
+/* SUMMARY
+/* miscellaneous low-level code
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_set_config_dir()
+/* DESCRIPTION
+/* pcf_set_config_dir() forcibly overrides the var_config_dir
+/* parameter setting with the value from the environment or
+/* with the default pathname, and updates the mail parameter
+/* dictionary.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <mymalloc.h>
+#include <safe.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+/* pcf_set_config_dir - forcibly override var_config_dir */
+
+void pcf_set_config_dir(void)
+{
+ char *config_dir;
+
+ if (var_config_dir)
+ myfree(var_config_dir);
+ if ((config_dir = safe_getenv(CONF_ENV_PATH)) != 0) {
+ var_config_dir = mystrdup(config_dir);
+ set_mail_conf_str(VAR_CONFIG_DIR, var_config_dir);
+ } else {
+ var_config_dir = mystrdup(DEF_CONFIG_DIR);
+ }
+}
diff --git a/src/postconf/postconf_node.c b/src/postconf/postconf_node.c
new file mode 100644
index 0000000..dca3594
--- /dev/null
+++ b/src/postconf/postconf_node.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* postconf_node 3
+/* SUMMARY
+/* low-level parameter node support
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* PCF_PARAM_TABLE *PCF_PARAM_TABLE_CREATE(size)
+/* ssize_t size;
+/*
+/* PCF_PARAM_INFO **PCF_PARAM_TABLE_LIST(table)
+/* PCF_PARAM_TABLE *table;
+/*
+/* const char *PCF_PARAM_INFO_NAME(info)
+/* PCF_PARAM_INFO *info;
+/*
+/* PCF_PARAM_NODE *PCF_PARAM_INFO_NODE(info)
+/* PCF_PARAM_INFO *info;
+/*
+/* PCF_PARAM_NODE *PCF_PARAM_TABLE_FIND(table, name)
+/* PCF_PARAM_TABLE *table;
+/* const char *name;
+/*
+/* PCF_PARAM_INFO *PCF_PARAM_TABLE_LOCATE(table, name)
+/* PCF_PARAM_TABLE *table;
+/* const char *name;
+/*
+/* PCF_PARAM_INFO *PCF_PARAM_TABLE_ENTER(table, name, flags,
+/* param_data, convert_fn)
+/* PCF_PARAM_TABLE *table;
+/* const char *name;
+/* int flags;
+/* void *param_data;
+/* const char *(*convert_fn)(void *);
+/*
+/* PCF_PARAM_NODE *pcf_make_param_node(flags, param_data, convert_fn)
+/* int flags;
+/* void *param_data;
+/* const char *(*convert_fn) (void *);
+/*
+/* const char *pcf_convert_param_node(mode, name, node)
+/* int mode;
+/* const char *name;
+/* PCF_PARAM_NODE *node;
+/*
+/* VSTRING *pcf_param_string_buf;
+/*
+/* PCF_RAW_PARAMETER(node)
+/* const PCF_PARAM_NODE *node;
+/* DESCRIPTION
+/* This module maintains data structures (PCF_PARAM_NODE) with
+/* information about known-legitimate parameters. These data
+/* structures are stored in a hash table.
+/*
+/* The PCF_PARAM_MUMBLE() macros are wrappers around the
+/* htable(3) module. Their sole purpose is to encapsulate all
+/* the pointer casting from and to (PCF_PARAM_NODE *). Apart
+/* from that, the macros have no features worth discussing.
+/*
+/* pcf_make_param_node() creates a node for the global parameter
+/* table. This node provides a parameter default value, and a
+/* function that converts the default value to string.
+/*
+/* pcf_convert_param_node() produces a string representation
+/* for a global parameter default value.
+/*
+/* PCF_RAW_PARAMETER() returns non-zero if the specified
+/* parameter node represents a "raw parameter". The value of
+/* such parameters must not be scanned for macro names. Some
+/* "raw parameter" values contain "$" without macros, such as
+/* the smtpd_expansion_filter "safe character" set; and some
+/* contain $name from a private name space, such as forward_path.
+/* Some "raw parameter" values in postscreen(8) are safe to
+/* expand by one level. Support for that may be added later.
+/*
+/* pcf_param_string_buf is a buffer that is initialized on the
+/* fly and that parameter-to-string conversion functions may
+/* use for temporary result storage.
+/*
+/* Arguments:
+/* .IP size
+/* The initial size of the hash table.
+/* .IP table
+/* A hash table for storage of "valid parameter" information.
+/* .IP info
+/* A data structure with a name component and a PCF_PARAM_NODE
+/* component. Use PCF_PARAM_INFO_NAME() and PCF_PARAM_INFO_NODE()
+/* to access these components.
+/* .IP name
+/* The name of a "valid parameter".
+/* .IP flags
+/* PCF_PARAM_FLAG_RAW for a "raw parameter", PCF_PARAM_FLAG_NONE
+/* otherwise. See the PCF_RAW_PARAMETER() discussion above for
+/* discussion of "raw parameter" values.
+/* .IP param_data
+/* Information about the parameter value. Specify PCF_PARAM_NO_DATA
+/* if this is not applicable.
+/* .IP convert_fn
+/* The function that will be invoked to produce a string
+/* representation of the information in param_data. The function
+/* receives the param_data value as argument.
+/* .IP mode
+/* For now, the PCF_SHOW_DEFS flag is required.
+/* .IP name
+/* The name of the parameter whose value is requested. This
+/* is used for diagnostics.
+/* .IP node
+/* The (flags, param_data, convert_fn) information that needs
+/* to be converted to a string representation of the default
+/* value.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+VSTRING *pcf_param_string_buf;
+
+/* pcf_make_param_node - make node for global parameter table */
+
+PCF_PARAM_NODE *pcf_make_param_node(int flags, void *param_data,
+ const char *(*convert_fn) (void *))
+{
+ PCF_PARAM_NODE *node;
+
+ node = (PCF_PARAM_NODE *) mymalloc(sizeof(*node));
+ node->flags = flags;
+ node->param_data = param_data;
+ node->convert_fn = convert_fn;
+ return (node);
+}
+
+/* pcf_convert_param_node - get default parameter value */
+
+const char *pcf_convert_param_node(int mode, const char *name, PCF_PARAM_NODE *node)
+{
+ const char *myname = "pcf_convert_param_node";
+ const char *value;
+
+ /*
+ * One-off initialization.
+ */
+ if (pcf_param_string_buf == 0)
+ pcf_param_string_buf = vstring_alloc(100);
+
+ /*
+ * Sanity check. A null value indicates that a parameter does not have
+ * the requested value. At this time, the only requested value can be the
+ * default value, and a null pointer value makes no sense here.
+ */
+ if ((mode & PCF_SHOW_DEFS) == 0)
+ msg_panic("%s: request for non-default value of parameter %s",
+ myname, name);
+ if ((value = node->convert_fn(node->param_data)) == 0)
+ msg_panic("%s: parameter %s has null pointer default value",
+ myname, name);
+
+ /*
+ * Return the parameter default value.
+ */
+ return (value);
+}
diff --git a/src/postconf/postconf_other.c b/src/postconf/postconf_other.c
new file mode 100644
index 0000000..0c4c0c4
--- /dev/null
+++ b/src/postconf/postconf_other.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* postconf_other 3
+/* SUMMARY
+/* support for miscellaneous information categories
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_show_maps()
+/*
+/* void pcf_show_locks()
+/*
+/* void pcf_show_sasl(mode)
+/* int mode;
+/*
+/* void pcf_show_tls(what)
+/* const char *what;
+/* DESCRIPTION
+/* pcf_show_maps() lists the available map (lookup table)
+/* types.
+/*
+/* pcf_show_locks() lists the available mailbox lock types.
+/*
+/* pcf_show_sasl() shows the available SASL authentication
+/* plugin types.
+/*
+/* pcf_show_tls() reports the "compile-version" or "run-version"
+/* of the TLS library, or the supported public-key algorithms.
+/*
+/* Arguments:
+/* .IP mode
+/* Show server information if the PCF_SHOW_SASL_SERV flag is
+/* set, otherwise show client information.
+/* .IP what
+/* One of the literals "compile-version", "run-version" or
+/* "public-key-algorithms".
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <vstream.h>
+#include <argv.h>
+#include <dict.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mbox_conf.h>
+
+/* XSASL library. */
+
+#include <xsasl.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+/* pcf_show_maps - show available maps */
+
+void pcf_show_maps(void)
+{
+ ARGV *maps_argv;
+ int i;
+
+ maps_argv = dict_mapnames();
+ for (i = 0; i < maps_argv->argc; i++)
+ vstream_printf("%s\n", maps_argv->argv[i]);
+ argv_free(maps_argv);
+}
+
+/* pcf_show_locks - show available mailbox locking methods */
+
+void pcf_show_locks(void)
+{
+ ARGV *locks_argv;
+ int i;
+
+ locks_argv = mbox_lock_names();
+ for (i = 0; i < locks_argv->argc; i++)
+ vstream_printf("%s\n", locks_argv->argv[i]);
+ argv_free(locks_argv);
+}
+
+/* pcf_show_sasl - show SASL plug-in types */
+
+void pcf_show_sasl(int what)
+{
+ ARGV *sasl_argv;
+ int i;
+
+ sasl_argv = (what & PCF_SHOW_SASL_SERV) ? xsasl_server_types() :
+ xsasl_client_types();
+ for (i = 0; i < sasl_argv->argc; i++)
+ vstream_printf("%s\n", sasl_argv->argv[i]);
+ argv_free(sasl_argv);
+}
+
+/* pcf_show_tls - show TLS support */
+
+void pcf_show_tls(const char *what)
+{
+#ifdef USE_TLS
+ if (strcmp(what, "compile-version") == 0)
+ vstream_printf("%s\n", tls_compile_version());
+ else if (strcmp(what, "run-version") == 0)
+ vstream_printf("%s\n", tls_run_version());
+ else if (strcmp(what, "public-key-algorithms") == 0) {
+ const char **cpp;
+
+ for (cpp = tls_pkey_algorithms(); *cpp; cpp++)
+ vstream_printf("%s\n", *cpp);
+ } else {
+ msg_warn("unknown 'postconf -T' mode: %s", what);
+ exit(1);
+ }
+#endif /* USE_TLS */
+}
diff --git a/src/postconf/postconf_print.c b/src/postconf/postconf_print.c
new file mode 100644
index 0000000..32c4015
--- /dev/null
+++ b/src/postconf/postconf_print.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* postconf_print 3
+/* SUMMARY
+/* basic line printing support
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_print_line(fp, mode, const char *fmt, ...)
+/* VSTREAM *fp;
+/* int mode;
+/* const char *fmt;
+/* DESCRIPTION
+/* pcf_print_line() formats text, normalized whitespace, and
+/* optionally folds long lines.
+/*
+/* Arguments:
+/* .IP fp
+/* Output stream.
+/* .IP mode
+/* Bit-wise OR of zero or more of the following (other flags
+/* are ignored):
+/* .RS
+/* .IP PCF_FOLD_LINE
+/* Fold long lines.
+/* .RE
+/* .IP fmt
+/* Format string.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+
+/* pcf_print_line - show line possibly folded, and with normalized whitespace */
+
+void pcf_print_line(VSTREAM *fp, int mode, const char *fmt,...)
+{
+ va_list ap;
+ static VSTRING *buf = 0;
+ char *start;
+ char *next;
+ int line_len = 0;
+ int word_len;
+
+ /*
+ * One-off initialization.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(100);
+
+ /*
+ * Format the text.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Normalize the whitespace. We don't use the line_wrap() routine because
+ * 1) that function does not normalize whitespace between words and 2) we
+ * want to normalize whitespace even when not wrapping lines.
+ *
+ * XXX Some parameters preserve whitespace: for example, smtpd_banner and
+ * smtpd_reject_footer. If we have to preserve whitespace between words,
+ * then perhaps readlline() can be changed to canonicalize whitespace
+ * that follows a newline.
+ */
+ for (start = STR(buf); *(start += strspn(start, PCF_SEPARATORS)) != 0; start = next) {
+ word_len = strcspn(start, PCF_SEPARATORS);
+ if (*(next = start + word_len) != 0)
+ *next++ = 0;
+ if (word_len > 0 && line_len > 0) {
+ if ((mode & PCF_FOLD_LINE) == 0
+ || line_len + word_len < PCF_LINE_LIMIT) {
+ vstream_fputs(" ", fp);
+ line_len += 1;
+ } else {
+ vstream_fputs("\n" PCF_INDENT_TEXT, fp);
+ line_len = PCF_INDENT_LEN;
+ }
+ }
+ vstream_fputs(start, fp);
+ line_len += word_len;
+ }
+ vstream_fputs("\n", fp);
+}
diff --git a/src/postconf/postconf_service.c b/src/postconf/postconf_service.c
new file mode 100644
index 0000000..38a4cce
--- /dev/null
+++ b/src/postconf/postconf_service.c
@@ -0,0 +1,199 @@
+/*++
+/* NAME
+/* postconf_service 3
+/* SUMMARY
+/* service-defined main.cf parameter name support
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_register_service_parameters()
+/* DESCRIPTION
+/* Service-defined parameter names are created by appending
+/* postfix-defined suffixes to master.cf service names. All
+/* service-defined parameters have default values that are
+/* defined by a built-in parameter.
+/*
+/* pcf_register_service_parameters() adds the service-defined
+/* parameters to the global name space. This function must be
+/* called after the built-in parameters are added to the global
+/* name space, and after the master.cf file is read.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <mymalloc.h>
+#include <htable.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <argv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * Basename of programs in $daemon_directory. XXX These belong in a header
+ * file, or they should be made configurable.
+ */
+#ifndef MAIL_PROGRAM_LOCAL
+#define MAIL_PROGRAM_LOCAL "local"
+#define MAIL_PROGRAM_ERROR "error"
+#define MAIL_PROGRAM_VIRTUAL "virtual"
+#define MAIL_PROGRAM_SMTP "smtp"
+#define MAIL_PROGRAM_LMTP "lmtp"
+#define MAIL_PROGRAM_PIPE "pipe"
+#define MAIL_PROGRAM_SPAWN "spawn"
+#endif
+
+ /*
+ * Ad-hoc name-value string pair.
+ */
+typedef struct {
+ const char *name;
+ const char *value;
+} PCF_STRING_NV;
+
+#define STR(x) vstring_str(x)
+
+/* pcf_convert_service_parameter - get service parameter string value */
+
+static const char *pcf_convert_service_parameter(void *ptr)
+{
+ return (STR(vstring_sprintf(pcf_param_string_buf, "$%s", (char *) ptr)));
+}
+
+/* pcf_register_service_parameter - add service parameter name and default */
+
+static void pcf_register_service_parameter(const char *service,
+ const char *suffix,
+ const char *defparam)
+{
+ char *name = concatenate(service, suffix, (char *) 0);
+ PCF_PARAM_NODE *node;
+
+ /*
+ * Skip service parameter names that have built-in definitions. This
+ * happens with message delivery transports that have a non-default
+ * per-destination concurrency or recipient limit, such as local(8).
+ *
+ * Some parameters were tentatively flagged as built-in, but they are
+ * service parameters with their own default value. We don't change the
+ * default but we correct the parameter class.
+ */
+ if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, name)) != 0) {
+ PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_SERVICE);
+ } else {
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, name, PCF_PARAM_FLAG_SERVICE,
+ (void *) defparam, pcf_convert_service_parameter);
+ }
+ myfree(name);
+}
+
+/* pcf_register_service_parameters - add all service parameters with defaults */
+
+void pcf_register_service_parameters(void)
+{
+ const char *myname = "pcf_register_service_parameters";
+ static const PCF_STRING_NV pipe_params[] = {
+ /* suffix, default parameter name */
+ _MAXTIME, VAR_COMMAND_MAXTIME,
+#define service_params (pipe_params + 1)
+ _XPORT_RCPT_LIMIT, VAR_XPORT_RCPT_LIMIT,
+ _STACK_RCPT_LIMIT, VAR_STACK_RCPT_LIMIT,
+ _XPORT_REFILL_LIMIT, VAR_XPORT_REFILL_LIMIT,
+ _XPORT_REFILL_DELAY, VAR_XPORT_REFILL_DELAY,
+ _DELIVERY_SLOT_COST, VAR_DELIVERY_SLOT_COST,
+ _DELIVERY_SLOT_LOAN, VAR_DELIVERY_SLOT_LOAN,
+ _DELIVERY_SLOT_DISCOUNT, VAR_DELIVERY_SLOT_DISCOUNT,
+ _MIN_DELIVERY_SLOTS, VAR_MIN_DELIVERY_SLOTS,
+ _INIT_DEST_CON, VAR_INIT_DEST_CON,
+ _DEST_CON_LIMIT, VAR_DEST_CON_LIMIT,
+ _DEST_RCPT_LIMIT, VAR_DEST_RCPT_LIMIT,
+ _CONC_POS_FDBACK, VAR_CONC_POS_FDBACK,
+ _CONC_NEG_FDBACK, VAR_CONC_NEG_FDBACK,
+ _CONC_COHORT_LIM, VAR_CONC_COHORT_LIM,
+ _DEST_RATE_DELAY, VAR_DEST_RATE_DELAY,
+ _XPORT_RATE_DELAY, VAR_XPORT_RATE_DELAY,
+ 0,
+ };
+ static const PCF_STRING_NV spawn_params[] = {
+ /* suffix, default parameter name */
+ _MAXTIME, VAR_COMMAND_MAXTIME,
+ 0,
+ };
+ typedef struct {
+ const char *progname;
+ const PCF_STRING_NV *params;
+ } PCF_SERVICE_DEF;
+ static const PCF_SERVICE_DEF service_defs[] = {
+ MAIL_PROGRAM_LOCAL, service_params,
+ MAIL_PROGRAM_ERROR, service_params,
+ MAIL_PROGRAM_VIRTUAL, service_params,
+ MAIL_PROGRAM_SMTP, service_params,
+ MAIL_PROGRAM_LMTP, service_params,
+ MAIL_PROGRAM_PIPE, pipe_params,
+ MAIL_PROGRAM_SPAWN, spawn_params,
+ 0,
+ };
+ const PCF_STRING_NV *sp;
+ const char *progname;
+ const char *service;
+ PCF_MASTER_ENT *masterp;
+ ARGV *argv;
+ const PCF_SERVICE_DEF *sd;
+
+ /*
+ * Sanity checks.
+ */
+ if (pcf_param_table == 0)
+ msg_panic("%s: global parameter table is not initialized", myname);
+ if (pcf_master_table == 0)
+ msg_panic("%s: master table is not initialized", myname);
+
+ /*
+ * Extract service names from master.cf and generate service parameter
+ * information.
+ */
+ for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) {
+
+ /*
+ * Add service parameters for message delivery transports or spawn
+ * programs.
+ */
+ progname = argv->argv[7];
+ for (sd = service_defs; sd->progname; sd++) {
+ if (strcmp(sd->progname, progname) == 0) {
+ service = argv->argv[0];
+ for (sp = sd->params; sp->name; sp++)
+ pcf_register_service_parameter(service, sp->name, sp->value);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/postconf/postconf_unused.c b/src/postconf/postconf_unused.c
new file mode 100644
index 0000000..d4416f8
--- /dev/null
+++ b/src/postconf/postconf_unused.c
@@ -0,0 +1,129 @@
+/*++
+/* NAME
+/* postconf_unused 3
+/* SUMMARY
+/* report unused parameters
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_flag_unused_main_parameters()
+/*
+/* void pcf_flag_unused_master_parameters()
+/* DESCRIPTION
+/* These functions must be called after all parameter information
+/* is initialized: built-ins, service-defined and user-defined.
+/* In other words, don't call these functions with "postconf
+/* -d" which ignores user-defined main.cf settings.
+/*
+/* pcf_flag_unused_main_parameters() reports unused "name=value"
+/* entries in main.cf.
+/*
+/* pcf_flag_unused_master_parameters() reports unused "-o
+/* name=value" entries in master.cf.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <dict.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+/* pcf_flag_unused_parameters - warn about unused parameters */
+
+static void pcf_flag_unused_parameters(DICT *dict, const char *conf_name,
+ PCF_MASTER_ENT *local_scope)
+{
+ const char *myname = "pcf_flag_unused_parameters";
+ const char *param_name;
+ const char *param_value;
+ int how;
+
+ /*
+ * Sanity checks.
+ */
+ if (pcf_param_table == 0)
+ msg_panic("%s: global parameter table is not initialized", myname);
+
+ /*
+ * Iterate over all entries, and flag parameter names that aren't used
+ * anywhere. Show the warning message at the end of the output.
+ */
+ if (dict->sequence == 0)
+ msg_panic("%s: parameter dictionary %s has no iterator",
+ myname, conf_name);
+ for (how = DICT_SEQ_FUN_FIRST;
+ dict->sequence(dict, how, &param_name, &param_value) == 0;
+ how = DICT_SEQ_FUN_NEXT) {
+ if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, param_name) == 0
+ && (local_scope == 0
+ || PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, param_name) == 0)) {
+ vstream_fflush(VSTREAM_OUT);
+ msg_warn("%s/%s: unused parameter: %s=%s",
+ var_config_dir, conf_name, param_name, param_value);
+ }
+ }
+}
+
+/* pcf_flag_unused_main_parameters - warn about unused parameters */
+
+void pcf_flag_unused_main_parameters(void)
+{
+ const char *myname = "pcf_flag_unused_main_parameters";
+ DICT *dict;
+
+ /*
+ * Iterate over all main.cf entries, and flag parameter names that aren't
+ * used anywhere.
+ */
+ if ((dict = dict_handle(CONFIG_DICT)) == 0)
+ msg_panic("%s: parameter dictionary %s not found",
+ myname, CONFIG_DICT);
+ pcf_flag_unused_parameters(dict, MAIN_CONF_FILE, (PCF_MASTER_ENT *) 0);
+}
+
+/* pcf_flag_unused_master_parameters - warn about unused parameters */
+
+void pcf_flag_unused_master_parameters(void)
+{
+ const char *myname = "pcf_flag_unused_master_parameters";
+ PCF_MASTER_ENT *masterp;
+ DICT *dict;
+
+ /*
+ * Sanity checks.
+ */
+ if (pcf_master_table == 0)
+ msg_panic("%s: master table is not initialized", myname);
+
+ /*
+ * Iterate over all master.cf entries, and flag parameter names that
+ * aren't used anywhere.
+ */
+ for (masterp = pcf_master_table; masterp->argv != 0; masterp++)
+ if ((dict = masterp->all_params) != 0)
+ pcf_flag_unused_parameters(dict, MASTER_CONF_FILE, masterp);
+}
diff --git a/src/postconf/postconf_user.c b/src/postconf/postconf_user.c
new file mode 100644
index 0000000..5942ec0
--- /dev/null
+++ b/src/postconf/postconf_user.c
@@ -0,0 +1,422 @@
+/*++
+/* NAME
+/* postconf_user 3
+/* SUMMARY
+/* support for user-defined main.cf parameter names
+/* SYNOPSIS
+/* #include <postconf.h>
+/*
+/* void pcf_register_user_parameters()
+/* DESCRIPTION
+/* Postfix has multiple parameter name spaces: the global
+/* main.cf parameter name space, and the local parameter name
+/* space of each master.cf entry. Parameters in local name
+/* spaces take precedence over global parameters.
+/*
+/* There are three categories of known parameter names: built-in,
+/* service-defined (see postconf_service.c), and valid
+/* user-defined.
+/*
+/* There are two categories of valid user-defined parameter
+/* names:
+/*
+/* - Parameters whose user-defined-name appears in the value
+/* of smtpd_restriction_classes in main.cf or master.cf.
+/*
+/* - Parameters whose $user-defined-name appear in the value
+/* of "name=value" entries in main.cf or master.cf.
+/*
+/* - In both cases the parameters must have a
+/* "user-defined-name=value" entry in main.cf or master.cf.
+/*
+/* Other user-defined parameter names are flagged as "unused".
+/*
+/* pcf_register_user_parameters() scans the global and per-service
+/* name spaces for user-defined parameters and flags parameters
+/* as "valid" in the global name space (pcf_param_table) or
+/* in the per-service name space (valid_params).
+/*
+/* This function also invokes pcf_register_dbms_parameters() to
+/* to instantiate legacy per-dbms parameters, and to examine
+/* per-dbms configuration files. This is limited to the content
+/* of global and local, built-in and per-service, parameters.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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 <mymalloc.h>
+#include <htable.h>
+#include <mac_expand.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postconf.h>
+
+ /*
+ * Hash with all user-defined names in the global smtpd_restriction_classes
+ * value. This is used when validating "-o user-defined-name=value" entries
+ * in master.cf.
+ */
+static HTABLE *pcf_rest_class_table;
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+ /*
+ * Macros to make code with obscure constants more readable.
+ */
+#define NO_SCAN_RESULT ((VSTRING *) 0)
+#define NO_SCAN_FILTER ((char *) 0)
+
+/* SCAN_USER_PARAMETER_VALUE - examine macro names in parameter value */
+
+#define SCAN_USER_PARAMETER_VALUE(value, class, scope) do { \
+ PCF_PARAM_CTX _ctx; \
+ _ctx.local_scope = (scope); \
+ _ctx.param_class = (class); \
+ (void) mac_expand(NO_SCAN_RESULT, (value), MAC_EXP_FLAG_SCAN, \
+ NO_SCAN_FILTER, pcf_flag_user_parameter_wrapper, (void *) &_ctx); \
+} while (0)
+
+/* pcf_convert_user_parameter - get user-defined parameter string value */
+
+static const char *pcf_convert_user_parameter(void *unused_ptr)
+{
+ return (""); /* can't happen */
+}
+
+/* pcf_flag_user_parameter - flag user-defined name "valid" if it has name=value */
+
+static const char *pcf_flag_user_parameter(const char *mac_name,
+ int param_class,
+ PCF_MASTER_ENT *local_scope)
+{
+ const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE;
+ int user_supplied = 0;
+
+ /*
+ * If the name=value exists in the local (or global) name space, update
+ * the local (or global) "valid" parameter name table.
+ *
+ * Do not "validate" user-defined parameters whose name appears only as
+ * macro expansion; this is how Postfix historically implements backwards
+ * compatibility after a feature name change.
+ */
+ if (local_scope && dict_get(local_scope->all_params, mac_name)) {
+ user_supplied = 1;
+ /* $name in master.cf references name=value in master.cf. */
+ if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
+ PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
+ param_class, PCF_PARAM_NO_DATA,
+ pcf_convert_user_parameter);
+ if (msg_verbose)
+ msg_info("$%s in %s:%s validates %s=value in %s:%s",
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space,
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space);
+ }
+ } else if (mail_conf_lookup(mac_name) != 0) {
+ user_supplied = 1;
+ /* $name in main/master.cf references name=value in main.cf. */
+ if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) {
+ PCF_PARAM_TABLE_ENTER(pcf_param_table, mac_name, param_class,
+ PCF_PARAM_NO_DATA, pcf_convert_user_parameter);
+ if (msg_verbose) {
+ if (local_scope)
+ msg_info("$%s in %s:%s validates %s=value in %s",
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space,
+ mac_name, MAIN_CONF_FILE);
+ else
+ msg_info("$%s in %s validates %s=value in %s",
+ mac_name, MAIN_CONF_FILE,
+ mac_name, MAIN_CONF_FILE);
+ }
+ }
+ }
+ if (local_scope == 0) {
+ for (local_scope = pcf_master_table; local_scope->argv; local_scope++) {
+ if (local_scope->all_params != 0
+ && dict_get(local_scope->all_params, mac_name) != 0) {
+ user_supplied = 1;
+ /* $name in main.cf references name=value in master.cf. */
+ if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
+ PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
+ param_class, PCF_PARAM_NO_DATA,
+ pcf_convert_user_parameter);
+ if (msg_verbose)
+ msg_info("$%s in %s validates %s=value in %s:%s",
+ mac_name, MAIN_CONF_FILE,
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space);
+ }
+ }
+ }
+ }
+
+ /*
+ * Warn about a $name that has no user-supplied explicit value or
+ * Postfix-supplied default value. We don't enforce this for legacy DBMS
+ * parameters because they exist only for backwards compatibility, so we
+ * don't bother to figure out which parameters come without defaults.
+ */
+ if (user_supplied == 0 && (param_class & PCF_PARAM_FLAG_DBMS) == 0
+ && PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0)
+ msg_warn("%s/%s: undefined parameter: %s",
+ var_config_dir, source, mac_name);
+ return (0);
+}
+
+/* pcf_flag_user_parameter_wrapper - mac_expand call-back helper */
+
+static const char *pcf_flag_user_parameter_wrapper(const char *mac_name,
+ int unused_mode,
+ void *context)
+{
+ PCF_PARAM_CTX *ctx = (PCF_PARAM_CTX *) context;
+
+ return (pcf_flag_user_parameter(mac_name, ctx->param_class, ctx->local_scope));
+}
+
+/* pcf_lookup_eval - generalized mail_conf_lookup_eval */
+
+static const char *pcf_lookup_eval(const char *dict_name, const char *name)
+{
+ const char *value;
+
+#define RECURSIVE 1
+
+ if ((value = dict_lookup(dict_name, name)) != 0)
+ value = dict_eval(dict_name, value, RECURSIVE);
+ return (value);
+}
+
+/* pcf_scan_user_parameter_namespace - scan parameters in name space */
+
+static void pcf_scan_user_parameter_namespace(const char *dict_name,
+ PCF_MASTER_ENT *local_scope)
+{
+ const char *myname = "pcf_scan_user_parameter_namespace";
+ const char *class_list;
+ char *saved_class_list;
+ char *cp;
+ DICT *dict;
+ char *param_name;
+ int how;
+ const char *cparam_name;
+ const char *cparam_value;
+ PCF_PARAM_NODE *node;
+ const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE;
+
+ /*
+ * Flag parameter names in smtpd_restriction_classes as "valid", but only
+ * if they have a "name=value" entry. If we are in not in a local name
+ * space, update the global restriction class name table, so that we can
+ * query the global table from within a local master.cf name space.
+ */
+ if ((class_list = pcf_lookup_eval(dict_name, VAR_REST_CLASSES)) != 0) {
+ cp = saved_class_list = mystrdup(class_list);
+ while ((param_name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if (local_scope == 0
+ && htable_locate(pcf_rest_class_table, param_name) == 0)
+ htable_enter(pcf_rest_class_table, param_name, "");
+ pcf_flag_user_parameter(param_name, PCF_PARAM_FLAG_USER, local_scope);
+ }
+ myfree(saved_class_list);
+ }
+
+ /*
+ * For all "name=value" instances: a) if the name space is local and the
+ * name appears in the global restriction class table, flag the name as
+ * "valid" in the local name space; b) scan the value for macro
+ * expansions of unknown parameter names, and flag those parameter names
+ * as "valid" if they have a "name=value" entry.
+ *
+ * We delete name=value entries for read-only parameters, to maintain
+ * compatibility with Postfix programs that ignore such settings.
+ */
+ if ((dict = dict_handle(dict_name)) == 0)
+ msg_panic("%s: parameter dictionary %s not found",
+ myname, dict_name);
+ if (dict->sequence == 0)
+ msg_panic("%s: parameter dictionary %s has no iterator",
+ myname, dict_name);
+ for (how = DICT_SEQ_FUN_FIRST;
+ dict->sequence(dict, how, &cparam_name, &cparam_value) == 0;
+ how = DICT_SEQ_FUN_NEXT) {
+ if (local_scope != 0
+ && PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, cparam_name) == 0
+ && htable_locate(pcf_rest_class_table, cparam_name) != 0)
+ PCF_PARAM_TABLE_ENTER(local_scope->valid_names, cparam_name,
+ PCF_PARAM_FLAG_USER, PCF_PARAM_NO_DATA,
+ pcf_convert_user_parameter);
+ if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, cparam_name)) != 0) {
+ if (PCF_READONLY_PARAMETER(node)) {
+ msg_warn("%s/%s: read-only parameter assignment: %s=%s",
+ var_config_dir, source, cparam_name, cparam_value);
+ /* Can't use dict_del() with Postfix<2.10 htable_sequence(). */
+ if (dict_del(dict, cparam_name) != 0)
+ msg_panic("%s: can't delete %s/%s parameter entry for %s",
+ myname, var_config_dir, source, cparam_name);
+ continue;
+ }
+ /* Re-label legacy parameter as user-defined, so it's printed. */
+ if (PCF_LEGACY_PARAMETER(node))
+ PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_USER);
+ /* Skip "do not expand" parameters. */
+ if (PCF_RAW_PARAMETER(node))
+ continue;
+ }
+ SCAN_USER_PARAMETER_VALUE(cparam_value, PCF_PARAM_FLAG_USER, local_scope);
+#ifdef LEGACY_DBMS_SUPPORT
+
+ /*
+ * Scan global or local parameters that are built-in or per-service
+ * (when node == 0, the parameter doesn't exist in the global
+ * namespace and therefore it can't be built-in or per-service).
+ */
+ if (node != 0
+ && (PCF_BUILTIN_PARAMETER(node) || PCF_SERVICE_PARAMETER(node)))
+ pcf_register_dbms_parameters(cparam_value, pcf_flag_user_parameter,
+ local_scope);
+#endif
+ }
+}
+
+/* pcf_scan_default_parameter_values - scan parameters at implicit defaults */
+
+static void pcf_scan_default_parameter_values(HTABLE *valid_params,
+ const char *dict_name,
+ PCF_MASTER_ENT *local_scope)
+{
+ const char *myname = "pcf_scan_default_parameter_values";
+ PCF_PARAM_INFO **list;
+ PCF_PARAM_INFO **ht;
+ const char *param_value;
+
+ list = PCF_PARAM_TABLE_LIST(valid_params);
+ for (ht = list; *ht; ht++) {
+ /* Skip "do not expand" parameters. */
+ if (PCF_RAW_PARAMETER(PCF_PARAM_INFO_NODE(*ht)))
+ continue;
+ /* Skip parameters with a non-default value. */
+ if (dict_lookup(dict_name, PCF_PARAM_INFO_NAME(*ht)))
+ continue;
+ if ((param_value = pcf_convert_param_node(PCF_SHOW_DEFS, PCF_PARAM_INFO_NAME(*ht),
+ PCF_PARAM_INFO_NODE(*ht))) == 0)
+ msg_panic("%s: parameter %s has no default value",
+ myname, PCF_PARAM_INFO_NAME(*ht));
+ SCAN_USER_PARAMETER_VALUE(param_value, PCF_PARAM_FLAG_USER, local_scope);
+ /* No need to scan default values for legacy DBMS configuration. */
+ }
+ myfree((void *) list);
+}
+
+/* pcf_register_user_parameters - add parameters with user-defined names */
+
+void pcf_register_user_parameters(void)
+{
+ const char *myname = "pcf_register_user_parameters";
+ PCF_MASTER_ENT *masterp;
+ ARGV *argv;
+ char *arg;
+ char *aval;
+ int field;
+ char *saved_arg;
+ char *param_name;
+ char *param_value;
+ DICT *dict;
+
+ /*
+ * Sanity checks.
+ */
+ if (pcf_param_table == 0)
+ msg_panic("%s: global parameter table is not initialized", myname);
+ if (pcf_master_table == 0)
+ msg_panic("%s: master table is not initialized", myname);
+ if (pcf_rest_class_table != 0)
+ msg_panic("%s: restriction class table is already initialized", myname);
+
+ /*
+ * Initialize the table with global restriction class names.
+ */
+ pcf_rest_class_table = htable_create(1);
+
+ /*
+ * Initialize the per-service parameter name spaces.
+ */
+ for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) {
+ for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) {
+ arg = argv->argv[field];
+ if (arg[0] != '-' || strcmp(arg, "--") == 0)
+ break;
+ if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0
+ || (aval = argv->argv[field + 1]) == 0)
+ continue;
+ if (strcmp(arg, "-o") == 0) {
+ saved_arg = mystrdup(aval);
+ if (split_nameval(saved_arg, &param_name, &param_value) == 0)
+ dict_update(masterp->name_space, param_name, param_value);
+ myfree(saved_arg);
+ }
+ field += 1;
+ }
+ if ((dict = dict_handle(masterp->name_space)) != 0) {
+ masterp->all_params = dict;
+ masterp->valid_names = htable_create(1);
+ }
+ }
+
+ /*
+ * Scan the "-o parameter=value" instances in each master.cf name space.
+ */
+ for (masterp = pcf_master_table; masterp->argv != 0; masterp++)
+ if (masterp->all_params != 0)
+ pcf_scan_user_parameter_namespace(masterp->name_space, masterp);
+
+ /*
+ * Scan parameter values that are left at their defaults in the global
+ * name space. Some defaults contain the $name of an obsolete parameter
+ * for backwards compatibility purposes. We might warn that an explicit
+ * name=value is obsolete, but we must not warn that the parameter is
+ * unused.
+ */
+ pcf_scan_default_parameter_values(pcf_param_table, CONFIG_DICT,
+ (PCF_MASTER_ENT *) 0);
+
+ /*
+ * Scan the explicit name=value entries in the global name space.
+ */
+ pcf_scan_user_parameter_namespace(CONFIG_DICT, (PCF_MASTER_ENT *) 0);
+}
diff --git a/src/postconf/test1.ref b/src/postconf/test1.ref
new file mode 100644
index 0000000..2aa9903
--- /dev/null
+++ b/src/postconf/test1.ref
@@ -0,0 +1,4 @@
+./postconf: warning: ./main.cf: undefined parameter: bar
+config_directory = .
+foo = yes
+smtpd_restriction_classes = foo bar
diff --git a/src/postconf/test10.ref b/src/postconf/test10.ref
new file mode 100644
index 0000000..5bb6891
--- /dev/null
+++ b/src/postconf/test10.ref
@@ -0,0 +1,2 @@
+./postconf: warning: unmatched request: "bar/inet"
+./postconf: warning: unmatched request: "foo/unix"
diff --git a/src/postconf/test11.ref b/src/postconf/test11.ref
new file mode 100644
index 0000000..c4cc727
--- /dev/null
+++ b/src/postconf/test11.ref
@@ -0,0 +1,2 @@
+foo inet - n n - 0 spawn
+bar unix - n n - 0 spawn
diff --git a/src/postconf/test12.ref b/src/postconf/test12.ref
new file mode 100644
index 0000000..a9f5eb4
--- /dev/null
+++ b/src/postconf/test12.ref
@@ -0,0 +1,2 @@
+foo inet - n n - 0 spawn -o always_bcc=$bar -o
+foo inet - n n - 0 spawn -o always_bcc=$bar -o
diff --git a/src/postconf/test13.ref b/src/postconf/test13.ref
new file mode 100644
index 0000000..09787fa
--- /dev/null
+++ b/src/postconf/test13.ref
@@ -0,0 +1,3 @@
+bar = yes
+config_directory = .
+./postconf: warning: ./main.cf: unused parameter: baz=xx
diff --git a/src/postconf/test14.ref b/src/postconf/test14.ref
new file mode 100644
index 0000000..98884f3
--- /dev/null
+++ b/src/postconf/test14.ref
@@ -0,0 +1,3 @@
+config_directory = .
+smtpd_restriction_classes = bar
+./postconf: warning: ./master.cf: unused parameter: baz=xx
diff --git a/src/postconf/test15.ref b/src/postconf/test15.ref
new file mode 100644
index 0000000..2a15ca8
--- /dev/null
+++ b/src/postconf/test15.ref
@@ -0,0 +1,3 @@
+baz = yy
+config_directory = .
+./postconf: warning: ./main.cf: unused parameter: bar=xx
diff --git a/src/postconf/test16.ref b/src/postconf/test16.ref
new file mode 100644
index 0000000..a1c2e06
--- /dev/null
+++ b/src/postconf/test16.ref
@@ -0,0 +1,2 @@
+./postconf: warning: open ./master.cf: No such file or directory
+config_directory = .
diff --git a/src/postconf/test17.ref b/src/postconf/test17.ref
new file mode 100644
index 0000000..bcfb716
--- /dev/null
+++ b/src/postconf/test17.ref
@@ -0,0 +1 @@
+./postconf: fatal: open ./master.cf: No such file or directory
diff --git a/src/postconf/test18.ref b/src/postconf/test18.ref
new file mode 100644
index 0000000..09224a6
--- /dev/null
+++ b/src/postconf/test18.ref
@@ -0,0 +1,3 @@
+config_directory = .
+smtpd_client_connection_limit_exceptions = yyy
+virtual_maps = xxx
diff --git a/src/postconf/test19.ref b/src/postconf/test19.ref
new file mode 100644
index 0000000..5a286c6
--- /dev/null
+++ b/src/postconf/test19.ref
@@ -0,0 +1,3 @@
+config_directory = .
+default_rbl_reply = $bbbb
+forward_path = $aaaa
diff --git a/src/postconf/test2.ref b/src/postconf/test2.ref
new file mode 100644
index 0000000..49af249
--- /dev/null
+++ b/src/postconf/test2.ref
@@ -0,0 +1,3 @@
+config_directory = .
+./postconf: warning: ./main.cf: unused parameter: restriction_classes=foo bar
+./postconf: warning: ./main.cf: unused parameter: foo=yes
diff --git a/src/postconf/test20.ref b/src/postconf/test20.ref
new file mode 100644
index 0000000..0a430d2
--- /dev/null
+++ b/src/postconf/test20.ref
@@ -0,0 +1,4 @@
+./postconf: warning: ./master.cf: undefined parameter: bar
+./postconf: warning: ./master.cf: undefined parameter: baz
+foo inet - n n - 0 spawn
+ -o always_bcc=$bar$baz
diff --git a/src/postconf/test21.ref b/src/postconf/test21.ref
new file mode 100644
index 0000000..423197e
--- /dev/null
+++ b/src/postconf/test21.ref
@@ -0,0 +1,3 @@
+config_directory = .
+forward_path = xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxx
+ xxxxxxxxxxxxxx
diff --git a/src/postconf/test22.ref b/src/postconf/test22.ref
new file mode 100644
index 0000000..1e4629f
--- /dev/null
+++ b/src/postconf/test22.ref
@@ -0,0 +1,16 @@
+whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost
+whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount
+whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan
+whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit
+whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit
+whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback
+whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback
+whatevershebrings_destination_rate_delay = $default_destination_rate_delay
+whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit
+whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit
+whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency
+whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots
+whatevershebrings_recipient_limit = $default_recipient_limit
+whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay
+whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit
+whatevershebrings_transport_rate_delay = $default_transport_rate_delay
diff --git a/src/postconf/test23.ref b/src/postconf/test23.ref
new file mode 100644
index 0000000..d1f9a90
--- /dev/null
+++ b/src/postconf/test23.ref
@@ -0,0 +1,2 @@
+always_bcc = yes
+config_directory = .
diff --git a/src/postconf/test24.ref b/src/postconf/test24.ref
new file mode 100644
index 0000000..2292793
--- /dev/null
+++ b/src/postconf/test24.ref
@@ -0,0 +1 @@
+name = value
diff --git a/src/postconf/test25.ref b/src/postconf/test25.ref
new file mode 100644
index 0000000..1e4629f
--- /dev/null
+++ b/src/postconf/test25.ref
@@ -0,0 +1,16 @@
+whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost
+whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount
+whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan
+whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit
+whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit
+whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback
+whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback
+whatevershebrings_destination_rate_delay = $default_destination_rate_delay
+whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit
+whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit
+whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency
+whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots
+whatevershebrings_recipient_limit = $default_recipient_limit
+whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay
+whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit
+whatevershebrings_transport_rate_delay = $default_transport_rate_delay
diff --git a/src/postconf/test26.ref b/src/postconf/test26.ref
new file mode 100644
index 0000000..0b33025
--- /dev/null
+++ b/src/postconf/test26.ref
@@ -0,0 +1,3 @@
+always_bcc = yes
+config_directory = .
+name = value
diff --git a/src/postconf/test27.ref b/src/postconf/test27.ref
new file mode 100644
index 0000000..1e4629f
--- /dev/null
+++ b/src/postconf/test27.ref
@@ -0,0 +1,16 @@
+whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost
+whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount
+whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan
+whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit
+whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit
+whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback
+whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback
+whatevershebrings_destination_rate_delay = $default_destination_rate_delay
+whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit
+whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit
+whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency
+whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots
+whatevershebrings_recipient_limit = $default_recipient_limit
+whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay
+whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit
+whatevershebrings_transport_rate_delay = $default_transport_rate_delay
diff --git a/src/postconf/test28.ref b/src/postconf/test28.ref
new file mode 100644
index 0000000..4e93734
--- /dev/null
+++ b/src/postconf/test28.ref
@@ -0,0 +1,10 @@
+aap_domain = whatever
+config_directory = .
+db = memcache
+header_checks = ldap:hh
+hh_domain = whatever
+yy = aap
+zz = $yy
+./postconf: warning: ./main.cf: unused parameter: foo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: xx=proxy:ldap:foo
+./postconf: warning: ./main.cf: unused parameter: aa_domain=whatever
diff --git a/src/postconf/test29.ref b/src/postconf/test29.ref
new file mode 100644
index 0000000..646890a
--- /dev/null
+++ b/src/postconf/test29.ref
@@ -0,0 +1,16 @@
+config_directory = .
+./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: ldapxx=proxy:ldap:ldapfoo
+./postconf: warning: ./main.cf: unused parameter: sqlitexx=proxy:sqlite:sqlitefoo
+./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: memcachefoo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: ldapfoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: ldapfoo_domain=bar
+./postconf: warning: ./main.cf: unused parameter: memcachexx=proxy:memcache:memcachefoo
+./postconf: warning: ./main.cf: unused parameter: memcachefoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: mysqlxx=proxy:mysql:mysqlfoo
+./postconf: warning: ./main.cf: unused parameter: pgsqlxx=proxy:pgsql:pgsqlfoo
diff --git a/src/postconf/test3.ref b/src/postconf/test3.ref
new file mode 100644
index 0000000..bdeff73
--- /dev/null
+++ b/src/postconf/test3.ref
@@ -0,0 +1,4 @@
+always_bcc = $bar
+bar = $foo
+config_directory = .
+foo = yes
diff --git a/src/postconf/test30.ref b/src/postconf/test30.ref
new file mode 100644
index 0000000..706a429
--- /dev/null
+++ b/src/postconf/test30.ref
@@ -0,0 +1,7 @@
+config_directory = .
+p1 = xx
+p2 = xx
+p3 = xx
+p4 = xx
+./postconf: warning: ./master.cf: unused parameter: bodyx_checks=$p2
+./postconf: warning: ./master.cf: unused parameter: headerx_checks=$p4
diff --git a/src/postconf/test31.ref b/src/postconf/test31.ref
new file mode 100644
index 0000000..6fda09c
--- /dev/null
+++ b/src/postconf/test31.ref
@@ -0,0 +1,3 @@
+config_directory = .
+smtpd_helo_restrictions = whatever
+smtpd_sender_restrictions = whatever
diff --git a/src/postconf/test32.ref b/src/postconf/test32.ref
new file mode 100644
index 0000000..43e2b7b
--- /dev/null
+++ b/src/postconf/test32.ref
@@ -0,0 +1 @@
+fast_flush_domains = whatever
diff --git a/src/postconf/test33.ref b/src/postconf/test33.ref
new file mode 100644
index 0000000..7b3f52d
--- /dev/null
+++ b/src/postconf/test33.ref
@@ -0,0 +1 @@
+always_bcc = whatever
diff --git a/src/postconf/test34.ref b/src/postconf/test34.ref
new file mode 100644
index 0000000..2c9d6bd
--- /dev/null
+++ b/src/postconf/test34.ref
@@ -0,0 +1,4 @@
+./postconf: warning: ./main.cf: read-only parameter assignment: process_name=xxx
+./postconf: warning: ./main.cf: read-only parameter assignment: process_id=yyy
+mydestination = whatever
+process_name = postconf
diff --git a/src/postconf/test35.ref b/src/postconf/test35.ref
new file mode 100644
index 0000000..601648f
--- /dev/null
+++ b/src/postconf/test35.ref
@@ -0,0 +1,3 @@
+./postconf: warning: ./master.cf: read-only parameter assignment: process_name=aaa
+./postconf: warning: ./master.cf: read-only parameter assignment: process_id=bbb
+process_name = postconf
diff --git a/src/postconf/test36.ref b/src/postconf/test36.ref
new file mode 100644
index 0000000..bf09921
--- /dev/null
+++ b/src/postconf/test36.ref
@@ -0,0 +1,4 @@
+./postconf: warning: ./main.cf: undefined parameter: virtual_mapx
+config_directory = .
+mydestination =
+virtual_alias_maps =
diff --git a/src/postconf/test37.ref b/src/postconf/test37.ref
new file mode 100644
index 0000000..8ef6d6d
--- /dev/null
+++ b/src/postconf/test37.ref
@@ -0,0 +1,4 @@
+whatever unix - n n - 0 other
+ -o mydestination=yyy
+ -o always_bcc=ccc
+ -o aaa=ccc
diff --git a/src/postconf/test38.ref b/src/postconf/test38.ref
new file mode 100644
index 0000000..186ffc3
--- /dev/null
+++ b/src/postconf/test38.ref
@@ -0,0 +1 @@
+bar unix - n n - 0 other -o aaa=ccc
diff --git a/src/postconf/test39.ref b/src/postconf/test39.ref
new file mode 100644
index 0000000..35b078d
--- /dev/null
+++ b/src/postconf/test39.ref
@@ -0,0 +1,2 @@
+foo unix - n n - 0 other
+baz unix - n n - 0 other
diff --git a/src/postconf/test4.ref b/src/postconf/test4.ref
new file mode 100644
index 0000000..6ec1913
--- /dev/null
+++ b/src/postconf/test4.ref
@@ -0,0 +1,3 @@
+bar = $foo
+config_directory = .
+foo = yes
diff --git a/src/postconf/test40.ref b/src/postconf/test40.ref
new file mode 100644
index 0000000..fa3a5d7
--- /dev/null
+++ b/src/postconf/test40.ref
@@ -0,0 +1,7 @@
+foo unix - n n - 0 other -v
+ -o aaa=bbb
+ -v
+ -o ccc=bbb
+ -v
+ -o ddd=bbb
+./postconf: warning: ./master.cf: unused parameter: ddd=$ccc
diff --git a/src/postconf/test41.ref b/src/postconf/test41.ref
new file mode 100644
index 0000000..f8200d4
--- /dev/null
+++ b/src/postconf/test41.ref
@@ -0,0 +1,18 @@
+foo unix - n n - 0 other
+bar unix - n n - 0 other
+ -o xxx=yyy
+ -o aaa=bbb
+baz unix - n n - 0 other
+./postconf: warning: ./master.cf: unused parameter: aaa=bbb
+./postconf: warning: ./master.cf: unused parameter: xxx=yyy
+foo unix - n n - 0 other
+bar unix - n n - 0 other
+ -o xxx=YYY
+ -o aaa=BBB
+baz unix - n n - 0 other
+./postconf: warning: ./master.cf: unused parameter: aaa=BBB
+./postconf: warning: ./master.cf: unused parameter: xxx=YYY
+bar/unix/aaa = BBB
+bar/unix/xxx = YYY
+./postconf: warning: ./master.cf: unused parameter: aaa=BBB
+./postconf: warning: ./master.cf: unused parameter: xxx=YYY
diff --git a/src/postconf/test42.ref b/src/postconf/test42.ref
new file mode 100644
index 0000000..80676f3
--- /dev/null
+++ b/src/postconf/test42.ref
@@ -0,0 +1,14 @@
+foo unix - n n - 0 other
+bar unix - n n - 0 other
+ -o xxx=yyy
+ -o aaa=bbb
+baz unix - n n - 0 other
+./postconf: warning: ./master.cf: unused parameter: aaa=bbb
+./postconf: warning: ./master.cf: unused parameter: xxx=yyy
+bar/unix/aaa = bbb
+bar/unix/xxx = yyy
+./postconf: warning: ./master.cf: unused parameter: aaa=bbb
+./postconf: warning: ./master.cf: unused parameter: xxx=yyy
+foo unix - n n - 0 other
+bar unix - n n - 0 other
+baz unix - n n - 0 other
diff --git a/src/postconf/test43.ref b/src/postconf/test43.ref
new file mode 100644
index 0000000..2bfea0e
--- /dev/null
+++ b/src/postconf/test43.ref
@@ -0,0 +1,6 @@
+foo unix - n n - 0 other
+bar unix - n y - 0 aa -stuff
+ -o bb=cc
+ dd
+baz unix - n n - 0 other
+./postconf: warning: ./master.cf: unused parameter: bb=cc
diff --git a/src/postconf/test44.ref b/src/postconf/test44.ref
new file mode 100644
index 0000000..b8cf23d
--- /dev/null
+++ b/src/postconf/test44.ref
@@ -0,0 +1,6 @@
+foo unix - n n - 0 other
+xx inet - n n - 0 aa -stuff
+ -o bb=cc
+ dd
+baz unix - n n - 0 other
+./postconf: warning: ./master.cf: unused parameter: bb=cc
diff --git a/src/postconf/test45.ref b/src/postconf/test45.ref
new file mode 100644
index 0000000..8ae54a0
--- /dev/null
+++ b/src/postconf/test45.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid type field "xxxx" in "bar xxxx - n n - 0 other"
diff --git a/src/postconf/test46.ref b/src/postconf/test46.ref
new file mode 100644
index 0000000..aac7c1f
--- /dev/null
+++ b/src/postconf/test46.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid private field "X" in "bar inet X n n - 0 other"
diff --git a/src/postconf/test47.ref b/src/postconf/test47.ref
new file mode 100644
index 0000000..e8d46ed
--- /dev/null
+++ b/src/postconf/test47.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid unprivileged field "X" in "bar inet - X n - 0 other"
diff --git a/src/postconf/test48.ref b/src/postconf/test48.ref
new file mode 100644
index 0000000..9403117
--- /dev/null
+++ b/src/postconf/test48.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid chroot field "X" in "bar inet - n X - 0 other"
diff --git a/src/postconf/test49.ref b/src/postconf/test49.ref
new file mode 100644
index 0000000..f52b171
--- /dev/null
+++ b/src/postconf/test49.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid wakeup field "X" in "bar inet - n n X 0 other"
diff --git a/src/postconf/test4b.ref b/src/postconf/test4b.ref
new file mode 100644
index 0000000..5dda033
--- /dev/null
+++ b/src/postconf/test4b.ref
@@ -0,0 +1,5 @@
+always_bcc = $foo
+bar = aaa
+biff = $bar
+config_directory = .
+./postconf: warning: ./master.cf: unused parameter: baz=zzz
diff --git a/src/postconf/test5.ref b/src/postconf/test5.ref
new file mode 100644
index 0000000..41bee93
--- /dev/null
+++ b/src/postconf/test5.ref
@@ -0,0 +1 @@
+config_directory = .
diff --git a/src/postconf/test50.ref b/src/postconf/test50.ref
new file mode 100644
index 0000000..41cdc44
--- /dev/null
+++ b/src/postconf/test50.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid process_limit field "X" in "bar inet - n n - X other"
diff --git a/src/postconf/test51.ref b/src/postconf/test51.ref
new file mode 100644
index 0000000..a37d749
--- /dev/null
+++ b/src/postconf/test51.ref
@@ -0,0 +1 @@
+./postconf: fatal: invalid wakeup field "X?" in "bar inet - n n X? 0 other"
diff --git a/src/postconf/test52.ref b/src/postconf/test52.ref
new file mode 100644
index 0000000..8902db9
--- /dev/null
+++ b/src/postconf/test52.ref
@@ -0,0 +1 @@
+baz unix - n n 0 0 other
diff --git a/src/postconf/test53.ref b/src/postconf/test53.ref
new file mode 100644
index 0000000..c81cf65
--- /dev/null
+++ b/src/postconf/test53.ref
@@ -0,0 +1,3 @@
+foo unix - n n - 0 other
+#bar inet - n n 0 0 other
+baz unix - n n 0 0 other
diff --git a/src/postconf/test54.ref b/src/postconf/test54.ref
new file mode 100644
index 0000000..045a9f6
--- /dev/null
+++ b/src/postconf/test54.ref
@@ -0,0 +1,3 @@
+#foo unix - n n - 0 other
+#bar inet - n n 0 0 other
+baz unix - n n 0 0 other
diff --git a/src/postconf/test55.ref b/src/postconf/test55.ref
new file mode 100644
index 0000000..96c11b0
--- /dev/null
+++ b/src/postconf/test55.ref
@@ -0,0 +1,3 @@
+foo unix - n n - 0 other
+#bar inet - n n 0 0 other
+#baz unix - n n 0 0 other
diff --git a/src/postconf/test56.ref b/src/postconf/test56.ref
new file mode 100644
index 0000000..ac845f6
--- /dev/null
+++ b/src/postconf/test56.ref
@@ -0,0 +1,5 @@
+foo unix - n n - 0 other
+#bar inet - n n 0 0 other
+# -o first
+# -o second
+baz unix - n n 0 0 other
diff --git a/src/postconf/test57.ref b/src/postconf/test57.ref
new file mode 100644
index 0000000..362fd16
--- /dev/null
+++ b/src/postconf/test57.ref
@@ -0,0 +1,10 @@
+./postconf: warning: ./main.cf: undefined parameter: z
+./postconf: warning: ./main.cf: undefined parameter: z
+bar = y-value
+baz =
+config_directory = .
+t1 = Postfix 2.11 compatible
+x = x-value
+y = y-value
+./postconf: warning: ./main.cf: unused parameter: t2=$t1
+./postconf: warning: ./main.cf: unused parameter: foo=$bar$baz
diff --git a/src/postconf/test58.ref b/src/postconf/test58.ref
new file mode 100644
index 0000000..ac24a4d
--- /dev/null
+++ b/src/postconf/test58.ref
@@ -0,0 +1,8 @@
+./postconf: warning: main.cf: syntax error after '}' in "{ldap:xxx, memcache:yy}x"
+./postconf: warning: main.cf: missing '}' in "{xx"
+config_directory = .
+mydestination = foo bar pipemap:{ldap:xxx, memcache:yy}x randmap:{xx
+xxx_domain = foo
+yy_backup = bbb
+./postconf: warning: ./main.cf: unused parameter: xxx_bogus=foo
+./postconf: warning: ./main.cf: unused parameter: yy_bogus=bbb
diff --git a/src/postconf/test59.ref b/src/postconf/test59.ref
new file mode 100644
index 0000000..c5cb3f6
--- /dev/null
+++ b/src/postconf/test59.ref
@@ -0,0 +1,10 @@
+./postconf: warning: master.cf: syntax error after '}' in "{ arg2a arg2b }x"
+./postconf: warning: master.cf: missing '}' in "{ arg3a arg3b "
+foo unix - n n - 0 other
+bar inet - n n 0 0 other
+ -o name1=value1
+ -o {name2=value2a value2b}
+ arg1a arg1b {arg2a arg2b} {arg3a arg3b}
+baz unix - n n 0 0 other
+./postconf: warning: ./master.cf: unused parameter: name2=value2a value2b
+./postconf: warning: ./master.cf: unused parameter: name1=value1
diff --git a/src/postconf/test6.ref b/src/postconf/test6.ref
new file mode 100644
index 0000000..55e47f2
--- /dev/null
+++ b/src/postconf/test6.ref
@@ -0,0 +1,17 @@
+whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost
+whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount
+whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan
+whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit
+whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit
+whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback
+whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback
+whatevershebrings_destination_rate_delay = $default_destination_rate_delay
+whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit
+whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit
+whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency
+whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots
+whatevershebrings_recipient_limit = $default_recipient_limit
+whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay
+whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit
+whatevershebrings_time_limit = $command_time_limit
+whatevershebrings_transport_rate_delay = $default_transport_rate_delay
diff --git a/src/postconf/test60.ref b/src/postconf/test60.ref
new file mode 100644
index 0000000..5a6f3ca
--- /dev/null
+++ b/src/postconf/test60.ref
@@ -0,0 +1,8 @@
+foo
+unix
+-
+n
+n
+-
+0
+other -o always_bcc=bar
diff --git a/src/postconf/test61.ref b/src/postconf/test61.ref
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/src/postconf/test61.ref
@@ -0,0 +1 @@
+bar
diff --git a/src/postconf/test62.ref b/src/postconf/test62.ref
new file mode 100644
index 0000000..ffc4913
--- /dev/null
+++ b/src/postconf/test62.ref
@@ -0,0 +1,8 @@
+foo/unix/service
+foo/unix/type
+foo/unix/private
+foo/unix/unprivileged
+foo/unix/chroot
+foo/unix/wakeup
+foo/unix/process_limit
+foo/unix/command
diff --git a/src/postconf/test63.ref b/src/postconf/test63.ref
new file mode 100644
index 0000000..b609d62
--- /dev/null
+++ b/src/postconf/test63.ref
@@ -0,0 +1 @@
+foo/unix/always_bcc
diff --git a/src/postconf/test64.ref b/src/postconf/test64.ref
new file mode 100644
index 0000000..595620c
--- /dev/null
+++ b/src/postconf/test64.ref
@@ -0,0 +1 @@
+relayhost = relay-from-main.cf
diff --git a/src/postconf/test65.ref b/src/postconf/test65.ref
new file mode 100644
index 0000000..6bc7fd8
--- /dev/null
+++ b/src/postconf/test65.ref
@@ -0,0 +1 @@
+relayhost = relay-from-cmd-line
diff --git a/src/postconf/test66.ref b/src/postconf/test66.ref
new file mode 100644
index 0000000..bd35822
--- /dev/null
+++ b/src/postconf/test66.ref
@@ -0,0 +1,5 @@
+./postconf: warning: ldap:PWD/test66.cf: unused parameter: junk=junk
+./postconf: warning: mysql:PWD/test66.cf: unused parameter: junk=junk
+./postconf: warning: pgsql:PWD/test66.cf: unused parameter: junk=junk
+./postconf: warning: sqlite:PWD/test66.cf: unused parameter: junk=junk
+./postconf: warning: memcache:PWD/test66.cf: unused parameter: junk=junk
diff --git a/src/postconf/test67.ref b/src/postconf/test67.ref
new file mode 100644
index 0000000..2014e99
--- /dev/null
+++ b/src/postconf/test67.ref
@@ -0,0 +1,10 @@
+smtp inet n - n - - smtpd
+ -o test1_process_name=smtpd
+ -o test1_service_name=smtp
+smtp unix n - n - - smtp
+ -o test2_process_name=smtp
+ -o test2_service_name=smtp
+./postconf: warning: ./master.cf: unused parameter: test1_service_name=$service_name
+./postconf: warning: ./master.cf: unused parameter: test1_process_name=$process_name
+./postconf: warning: ./master.cf: unused parameter: test2_service_name=$service_name
+./postconf: warning: ./master.cf: unused parameter: test2_process_name=$process_name
diff --git a/src/postconf/test68.ref b/src/postconf/test68.ref
new file mode 100644
index 0000000..e2d7c7d
--- /dev/null
+++ b/src/postconf/test68.ref
@@ -0,0 +1,5 @@
+./postconf: warning: ldap:PWD/test68.cf: unused parameter: junk=junk
+./postconf: warning: mysql:PWD/test68.cf: unused parameter: junk=junk
+./postconf: warning: pgsql:PWD/test68.cf: unused parameter: junk=junk
+./postconf: warning: sqlite:PWD/test68.cf: unused parameter: junk=junk
+./postconf: warning: memcache:PWD/test68.cf: unused parameter: junk=junk
diff --git a/src/postconf/test69.ref b/src/postconf/test69.ref
new file mode 100644
index 0000000..465a91f
--- /dev/null
+++ b/src/postconf/test69.ref
@@ -0,0 +1,2 @@
+./postconf: warning: ldap:PWD/test69.cf: unused parameter: junk=junk
+config_directory = .
diff --git a/src/postconf/test7.ref b/src/postconf/test7.ref
new file mode 100644
index 0000000..1bd731b
--- /dev/null
+++ b/src/postconf/test7.ref
@@ -0,0 +1 @@
+whatevershebrings_time_limit = $command_time_limit
diff --git a/src/postconf/test70.ref b/src/postconf/test70.ref
new file mode 100644
index 0000000..d81a9d1
--- /dev/null
+++ b/src/postconf/test70.ref
@@ -0,0 +1,4 @@
+config_directory = .
+smtpd_client_restrictions = check_sender_access { pipemap:{ldap:used} { search_order = foo, bar } }
+used_server_host = 127.0.0.1
+./postconf: warning: ./main.cf: unused parameter: unused_server_host=127.0.0.1
diff --git a/src/postconf/test8.ref b/src/postconf/test8.ref
new file mode 100644
index 0000000..af47b44
--- /dev/null
+++ b/src/postconf/test8.ref
@@ -0,0 +1 @@
+whatevershebrings_time_limit = 1
diff --git a/src/postconf/test9.ref b/src/postconf/test9.ref
new file mode 100644
index 0000000..b62303f
--- /dev/null
+++ b/src/postconf/test9.ref
@@ -0,0 +1 @@
+foo inet - n n - 0 spawn
diff --git a/src/postdrop/.indent.pro b/src/postdrop/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postdrop/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postdrop/.printfck b/src/postdrop/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postdrop/.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/postdrop/Makefile.in b/src/postdrop/Makefile.in
new file mode 100644
index 0000000..cd60a94
--- /dev/null
+++ b/src/postdrop/Makefile.in
@@ -0,0 +1,96 @@
+SHELL = /bin/sh
+SRCS = postdrop.c
+OBJS = postdrop.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postdrop
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postdrop.o: ../../include/argv.h
+postdrop.o: ../../include/attr.h
+postdrop.o: ../../include/check_arg.h
+postdrop.o: ../../include/clean_env.h
+postdrop.o: ../../include/cleanup_user.h
+postdrop.o: ../../include/dict.h
+postdrop.o: ../../include/htable.h
+postdrop.o: ../../include/iostuff.h
+postdrop.o: ../../include/login_sender_match.h
+postdrop.o: ../../include/mail_conf.h
+postdrop.o: ../../include/mail_dict.h
+postdrop.o: ../../include/mail_params.h
+postdrop.o: ../../include/mail_parm_split.h
+postdrop.o: ../../include/mail_proto.h
+postdrop.o: ../../include/mail_queue.h
+postdrop.o: ../../include/mail_stream.h
+postdrop.o: ../../include/mail_task.h
+postdrop.o: ../../include/mail_version.h
+postdrop.o: ../../include/maillog_client.h
+postdrop.o: ../../include/msg.h
+postdrop.o: ../../include/msg_vstream.h
+postdrop.o: ../../include/myflock.h
+postdrop.o: ../../include/mymalloc.h
+postdrop.o: ../../include/mypwd.h
+postdrop.o: ../../include/nvtable.h
+postdrop.o: ../../include/rec_attr_map.h
+postdrop.o: ../../include/rec_type.h
+postdrop.o: ../../include/record.h
+postdrop.o: ../../include/stringops.h
+postdrop.o: ../../include/sys_defs.h
+postdrop.o: ../../include/user_acl.h
+postdrop.o: ../../include/vbuf.h
+postdrop.o: ../../include/vstream.h
+postdrop.o: ../../include/vstring.h
+postdrop.o: ../../include/warn_stat.h
+postdrop.o: postdrop.c
diff --git a/src/postdrop/postdrop.c b/src/postdrop/postdrop.c
new file mode 100644
index 0000000..e9335e9
--- /dev/null
+++ b/src/postdrop/postdrop.c
@@ -0,0 +1,628 @@
+/*++
+/* NAME
+/* postdrop 1
+/* SUMMARY
+/* Postfix mail posting utility
+/* SYNOPSIS
+/* \fBpostdrop\fR [\fB-rv\fR] [\fB-c \fIconfig_dir\fR]
+/* DESCRIPTION
+/* The \fBpostdrop\fR(1) command creates a file in the \fBmaildrop\fR
+/* directory and copies its standard input to the file.
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory. See also the
+/* MAIL_CONFIG environment setting below.
+/* .IP \fB-r\fR
+/* Use a Postfix-internal protocol for reading the message from
+/* standard input, and for reporting status information on standard
+/* output. This is currently the only supported method.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose. As of Postfix 2.3,
+/* this option is available for the super-user only.
+/* SECURITY
+/* .ad
+/* .fi
+/* The command is designed to run with set-group ID privileges, so
+/* that it can write to the \fBmaildrop\fR queue directory and so that
+/* it can connect to Postfix daemon processes.
+/* DIAGNOSTICS
+/* Fatal errors: malformed input, I/O error, out of memory. Problems
+/* are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8) and to
+/* the standard error stream.
+/* When the input is incomplete, or when the process receives a HUP,
+/* INT, QUIT or TERM signal, the queue file is deleted.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP MAIL_CONFIG
+/* Directory with the \fBmain.cf\fR file. In order to avoid exploitation
+/* of set-group ID privileges, a non-standard directory is allowed only
+/* if:
+/* .RS
+/* .IP \(bu
+/* The name is listed in the standard \fBmain.cf\fR file with the
+/* \fBalternate_config_directories\fR configuration parameter.
+/* .IP \(bu
+/* The command is invoked by the super-user.
+/* .RE
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBalternate_config_directories (empty)\fR"
+/* A list of non-default Postfix configuration directories that may
+/* be specified with "-c config_directory" on the command line (in the
+/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG
+/* environment parameter.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .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".
+/* .IP "\fBtrigger_timeout (10s)\fR"
+/* The time limit for sending a trigger to a Postfix daemon (for
+/* example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon).
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBauthorized_submit_users (static:anyone)\fR"
+/* List of users who are authorized to submit mail with the \fBsendmail\fR(1)
+/* command (and with the privileged \fBpostdrop\fR(1) helper command).
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBlocal_login_sender_maps (static:*)\fR"
+/* A list of lookup tables that are searched by the UNIX login name,
+/* and that return a list of allowed envelope sender patterns separated
+/* by space or comma.
+/* .IP "\fBempty_address_local_login_sender_maps_lookup_key (<>)\fR"
+/* The lookup key to be used in local_login_sender_maps tables, instead
+/* of the null sender address.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* FILES
+/* /var/spool/postfix/maildrop, maildrop queue
+/* SEE ALSO
+/* sendmail(1), compatibility interface
+/* postconf(5), configuration parameters
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h> /* remove() */
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <warn_stat.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+#include <argv.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <mypwd.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <mail_task.h>
+#include <clean_env.h>
+#include <mail_stream.h>
+#include <cleanup_user.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_dict.h>
+#include <user_acl.h>
+#include <rec_attr_map.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+#include <login_sender_match.h>
+
+/* Application-specific. */
+
+ /*
+ * WARNING WARNING WARNING
+ *
+ * This software is designed to run set-gid. In order to avoid exploitation of
+ * privilege, this software should not run any external commands, nor should
+ * it take any information from the user unless that information can be
+ * properly sanitized. To get an idea of how much information a process can
+ * inherit from a potentially hostile user, examine all the members of the
+ * process structure (typically, in /usr/include/sys/proc.h): the current
+ * directory, open files, timers, signals, environment, command line, umask,
+ * and so on.
+ */
+
+ /*
+ * Local mail submission access list.
+ */
+char *var_submit_acl;
+char *var_local_login_snd_maps;
+char *var_null_local_login_snd_maps_key;
+
+static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SUBMIT_ACL, DEF_SUBMIT_ACL, &var_submit_acl, 0, 0,
+ VAR_LOCAL_LOGIN_SND_MAPS, DEF_LOCAL_LOGIN_SND_MAPS, &var_local_login_snd_maps, 0, 0,
+ VAR_NULL_LOCAL_LOGIN_SND_MAPS_KEY, DEF_NULL_LOCAL_LOGIN_SND_MAPS_KEY, &var_null_local_login_snd_maps_key, 0, 0,
+ 0,
+};
+
+ /*
+ * Queue file name. Global, so that the cleanup routine can find it when
+ * called by the run-time error handler.
+ */
+static char *postdrop_path;
+
+/* postdrop_sig - catch signal and clean up */
+
+static void postdrop_sig(int sig)
+{
+
+ /*
+ * This is the fatal error handler. Don't try to do anything fancy.
+ *
+ * To avoid privilege escalation in a set-gid program, Postfix logging
+ * functions must not be called from a user-triggered signal handler,
+ * because Postfix logging functions may allocate memory on the fly (as
+ * does the syslog() library function), and the memory allocator is not
+ * reentrant.
+ *
+ * Assume atomic signal() updates, even when emulated with sigaction(). We
+ * use the in-kernel SIGINT handler address as an atomic variable to
+ * prevent nested postdrop_sig() calls. For this reason, main() must
+ * configure postdrop_sig() as SIGINT handler before other signal
+ * handlers are allowed to invoke postdrop_sig().
+ */
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ (void) signal(SIGQUIT, SIG_IGN);
+ (void) signal(SIGTERM, SIG_IGN);
+ (void) signal(SIGHUP, SIG_IGN);
+ if (postdrop_path) {
+ (void) remove(postdrop_path);
+ postdrop_path = 0;
+ }
+ /* Future proofing. If you need exit() here then you broke Postfix. */
+ if (sig)
+ _exit(sig);
+ }
+}
+
+/* postdrop_cleanup - callback for the runtime error handler */
+
+static void postdrop_cleanup(void)
+{
+ postdrop_sig(0);
+}
+
+/* check_login_sender_acl - check if a user is authorized to use this sender */
+
+static int check_login_sender_acl(uid_t uid, VSTRING *sender_buf,
+ VSTRING *reason)
+{
+ const char myname[] = "check_login_sender_acl";
+ struct mypasswd *user_info;
+ char *user_name;
+ VSTRING *user_name_buf = 0;
+ LOGIN_SENDER_MATCH *lsm;
+ int res;
+
+ /*
+ * Sanity checks.
+ */
+ if (vstring_memchr(sender_buf, '\0') != 0) {
+ vstring_sprintf(reason, "NUL in FROM record");
+ return (CLEANUP_STAT_BAD);
+ }
+
+ /*
+ * Optimization.
+ */
+#ifndef SNAPSHOT
+ if (strcmp(var_local_login_snd_maps, DEF_LOCAL_LOGIN_SND_MAPS) == 0)
+ return (CLEANUP_STAT_OK);
+#endif
+
+ /*
+ * Get the username.
+ */
+ if ((user_info = mypwuid(uid)) != 0) {
+ user_name = user_info->pw_name;
+ } else {
+ user_name_buf = vstring_alloc(10);
+ vstring_sprintf(user_name_buf, "uid:%ld", (long) uid);
+ user_name = vstring_str(user_name_buf);
+ }
+
+
+ /*
+ * Apply the a login-sender matcher. TODO: add DICT flags.
+ */
+ lsm = login_sender_create(VAR_LOCAL_LOGIN_SND_MAPS,
+ var_local_login_snd_maps,
+ var_rcpt_delim,
+ var_null_local_login_snd_maps_key, "*");
+ res = login_sender_match(lsm, user_name, vstring_str(sender_buf));
+ login_sender_free(lsm);
+ if (user_name_buf)
+ vstring_free(user_name_buf);
+ switch (res) {
+ case LSM_STAT_FOUND:
+ return (CLEANUP_STAT_OK);
+ case LSM_STAT_NOTFOUND:
+ vstring_sprintf(reason, "not authorized to use sender='%s'",
+ vstring_str(sender_buf));
+ return (CLEANUP_STAT_NOPERM);
+ case LSM_STAT_RETRY:
+ case LSM_STAT_CONFIG:
+ vstring_sprintf(reason, "%s table lookup error for '%s'",
+ VAR_LOCAL_LOGIN_SND_MAPS, var_local_login_snd_maps);
+ return (CLEANUP_STAT_WRITE);
+ default:
+ msg_panic("%s: bad login_sender_match() result: %d", myname, res);
+ }
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ struct stat st;
+ int fd;
+ int c;
+ VSTRING *buf;
+ int status = CLEANUP_STAT_OK;
+ VSTRING *reason = vstring_alloc(100);
+ MAIL_STREAM *dst;
+ int rec_type;
+ static char *segment_info[] = {
+ REC_TYPE_POST_ENVELOPE, REC_TYPE_POST_CONTENT, REC_TYPE_POST_EXTRACT, ""
+ };
+ char **expected;
+ uid_t uid = getuid();
+ ARGV *import_env;
+ const char *error_text;
+ char *attr_name;
+ char *attr_value;
+ const char *errstr;
+ char *junk;
+ struct timeval start;
+ int saved_errno;
+ int from_count = 0;
+ int rcpt_count = 0;
+ int validate_input = 1;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up logging. Censor the process name: it is provided by the user.
+ */
+ argv[0] = "postdrop";
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(mail_task("postdrop"), MAILLOG_CLIENT_FLAG_NONE);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL. This program is set-gid and must sanitize all command-line
+ * arguments. The configuration directory argument is validated by the
+ * mail configuration read routine. Don't do complex things until we have
+ * completed initializations.
+ */
+ while ((c = GETOPT(argc, argv, "c:rv")) > 0) {
+ switch (c) {
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'r': /* forward compatibility */
+ break;
+ case 'v':
+ if (geteuid() == 0)
+ msg_verbose++;
+ break;
+ default:
+ msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]);
+ }
+ }
+
+ /*
+ * Read the global configuration file and extract configuration
+ * information.
+ */
+ mail_conf_read();
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task("postdrop"), MAILLOG_CLIENT_FLAG_NONE);
+ get_mail_conf_str_table(str_table);
+
+ /*
+ * Stop run-away process accidents by limiting the queue file size. This
+ * is not a defense against DOS attack.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && get_file_limit() > var_message_limit)
+ set_file_limit((off_t) var_message_limit);
+
+ /*
+ * This program is installed with setgid privileges. Strip the process
+ * environment so that we don't have to trust the C library.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+ if (msg_verbose)
+ msg_info("chdir %s", var_queue_dir);
+
+ /*
+ * Set up signal handlers and a runtime error handler so that we can
+ * clean up incomplete output.
+ *
+ * postdrop_sig() uses the in-kernel SIGINT handler address as an atomic
+ * variable to prevent nested postdrop_sig() calls. For this reason, the
+ * SIGINT handler must be configured before other signal handlers are
+ * allowed to invoke postdrop_sig().
+ */
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGXFSZ, SIG_IGN);
+
+ signal(SIGINT, postdrop_sig);
+ signal(SIGQUIT, postdrop_sig);
+ if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
+ signal(SIGTERM, postdrop_sig);
+ if (signal(SIGHUP, SIG_IGN) == SIG_DFL)
+ signal(SIGHUP, postdrop_sig);
+ msg_cleanup(postdrop_cleanup);
+
+ /* End of initializations. */
+
+ /*
+ * Mail submission access control. Should this be in the user-land gate,
+ * or in the daemon process?
+ */
+ mail_dict_init();
+ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl,
+ uid)) != 0)
+ msg_fatal("User %s(%ld) is not allowed to submit mail",
+ errstr, (long) uid);
+
+ /*
+ * Don't trust the caller's time information.
+ */
+ GETTIMEOFDAY(&start);
+
+ /*
+ * Create queue file. mail_stream_file() never fails. Send the queue ID
+ * to the caller. Stash away a copy of the queue file name so we can
+ * clean up in case of a fatal error or an interrupt.
+ */
+ dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC,
+ var_pickup_service, 0444);
+ attr_print(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_POSTDROP),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, dst->id),
+ ATTR_TYPE_END);
+ vstream_fflush(VSTREAM_OUT);
+ postdrop_path = mystrdup(VSTREAM_PATH(dst->stream));
+
+ /*
+ * Copy stdin to file. The format is checked so that we can recognize
+ * incomplete input and cancel the operation. With the sanity checks
+ * applied here, the pickup daemon could skip format checks and pass a
+ * file descriptor to the cleanup daemon. These are by no means all
+ * sanity checks - the cleanup service and queue manager services will
+ * reject messages that lack required information.
+ *
+ * If something goes wrong, slurp up the input before responding to the
+ * client, otherwise the client will give up after detecting SIGPIPE.
+ *
+ * Allow attribute records if the attribute specifies the MIME body type
+ * (sendmail -B).
+ */
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END);
+ buf = vstring_alloc(100);
+ expected = segment_info;
+ /* Override time information from the untrusted caller. */
+ rec_fprintf(dst->stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(start));
+ for (;;) {
+ /* Don't allow PTR records. */
+ rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE);
+ if (rec_type == REC_TYPE_EOF) { /* request canceled */
+ mail_stream_cleanup(dst);
+ if (remove(postdrop_path))
+ msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path);
+ else if (msg_verbose)
+ msg_info("remove %s", postdrop_path);
+ myfree(postdrop_path);
+ postdrop_path = 0;
+ exit(0);
+ }
+ if (rec_type == REC_TYPE_ERROR)
+ msg_fatal("uid=%ld: malformed input", (long) uid);
+ if (strchr(*expected, rec_type) == 0)
+ msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type);
+ if (rec_type == **expected)
+ expected++;
+ /* Override time information from the untrusted caller. */
+ if (rec_type == REC_TYPE_TIME)
+ continue;
+ /* Check these at submission time instead of pickup time. */
+ if (rec_type == REC_TYPE_FROM) {
+ status |= check_login_sender_acl(uid, buf, reason);
+ from_count++;
+ }
+ if (rec_type == REC_TYPE_RCPT)
+ rcpt_count++;
+ /* Limit the attribute types that users may specify. */
+ if (rec_type == REC_TYPE_ATTR) {
+ if ((error_text = split_nameval(vstring_str(buf), &attr_name,
+ &attr_value)) != 0) {
+ msg_warn("uid=%ld: ignoring malformed record: %s: %.200s",
+ (long) uid, error_text, vstring_str(buf));
+ continue;
+ }
+#define STREQ(x,y) (strcmp(x,y) == 0)
+
+ if ((STREQ(attr_name, MAIL_ATTR_ENCODING)
+ && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT)
+ || STREQ(attr_value, MAIL_ATTR_ENC_8BIT)
+ || STREQ(attr_value, MAIL_ATTR_ENC_NONE)))
+ || STREQ(attr_name, MAIL_ATTR_DSN_ENVID)
+ || STREQ(attr_name, MAIL_ATTR_DSN_NOTIFY)
+ || rec_attr_map(attr_name)
+ || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT)
+ && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)
+ || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)))
+ || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */
+ rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s",
+ attr_name, attr_value);
+ } else {
+ msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s",
+ (long) uid, attr_name, attr_value);
+ }
+ continue;
+ }
+ if (status != CLEANUP_STAT_OK
+ || REC_PUT_BUF(dst->stream, rec_type, buf) < 0) {
+ /* rec_get() errors must not clobber errno. */
+ saved_errno = errno;
+ while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit,
+ REC_FLAG_NONE)) != REC_TYPE_END
+ && rec_type != REC_TYPE_EOF)
+ if (rec_type == REC_TYPE_ERROR)
+ msg_fatal("uid=%ld: malformed input", (long) uid);
+ validate_input = 0;
+ errno = saved_errno;
+ break;
+ }
+ if (rec_type == REC_TYPE_END)
+ break;
+ }
+ vstring_free(buf);
+
+ /*
+ * As of Postfix 2.7 the pickup daemon discards mail without recipients.
+ * Such mail may enter the maildrop queue when "postsuper -r" is invoked
+ * before the queue manager deletes an already delivered message. Looking
+ * at file ownership is not a good way to make decisions on what mail to
+ * discard. Instead, the pickup server now requires that new submissions
+ * always have at least one recipient record.
+ *
+ * The Postfix sendmail command already rejects mail without recipients.
+ * However, in the future postdrop may receive mail via other programs,
+ * so we add a redundant recipient check here for future proofing.
+ *
+ * The test for the sender address is just for consistency of error
+ * reporting (report at submission time instead of pickup time). Besides
+ * the segment terminator records, there aren't any other mandatory
+ * records in a Postfix submission queue file.
+ *
+ * TODO: return an informative reason for missing sender, too many senders,
+ * or missing recipient.
+ */
+ if (validate_input && (from_count == 0 || rcpt_count == 0))
+ status |= CLEANUP_STAT_BAD;
+ if (status != CLEANUP_STAT_OK) {
+ mail_stream_cleanup(dst);
+ }
+
+ /*
+ * Finish the file.
+ */
+ else if ((status = mail_stream_finish(dst, reason)) != 0) {
+ msg_warn("uid=%ld: %m", (long) uid);
+ postdrop_cleanup();
+ }
+
+ /*
+ * Disable deletion on fatal error before reporting success, so the file
+ * will not be deleted after we have taken responsibility for delivery.
+ */
+ if (postdrop_path) {
+ junk = postdrop_path;
+ postdrop_path = 0;
+ myfree(junk);
+ }
+
+ /*
+ * Send the completion status to the caller and terminate.
+ */
+ attr_print(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, status != CLEANUP_STAT_OK
+ && VSTRING_LEN(reason) > 0 ?
+ vstring_str(reason) : ""),
+ ATTR_TYPE_END);
+ vstream_fflush(VSTREAM_OUT);
+ exit(status);
+}
diff --git a/src/postfix/.indent.pro b/src/postfix/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postfix/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postfix/.printfck b/src/postfix/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postfix/.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/postfix/Makefile.in b/src/postfix/Makefile.in
new file mode 100644
index 0000000..dc3bf43
--- /dev/null
+++ b/src/postfix/Makefile.in
@@ -0,0 +1,83 @@
+SHELL = /bin/sh
+SRCS = postfix.c
+OBJS = postfix.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+FILES = Makefile $(SRCS) $(HDRS)
+INC_DIR = ../../include
+TESTPROG=
+PROG = postfix
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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`
+
+shar:
+ @shar $(FILES)
+
+lint:
+ lint $(SRCS)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+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'
+postfix.o: ../../include/argv.h
+postfix.o: ../../include/check_arg.h
+postfix.o: ../../include/clean_env.h
+postfix.o: ../../include/compat_level.h
+postfix.o: ../../include/mail_conf.h
+postfix.o: ../../include/mail_params.h
+postfix.o: ../../include/mail_parm_split.h
+postfix.o: ../../include/mail_version.h
+postfix.o: ../../include/maillog_client.h
+postfix.o: ../../include/msg.h
+postfix.o: ../../include/msg_vstream.h
+postfix.o: ../../include/safe.h
+postfix.o: ../../include/stringops.h
+postfix.o: ../../include/sys_defs.h
+postfix.o: ../../include/vbuf.h
+postfix.o: ../../include/vstream.h
+postfix.o: ../../include/vstring.h
+postfix.o: ../../include/warn_stat.h
+postfix.o: postfix.c
diff --git a/src/postfix/postfix.c b/src/postfix/postfix.c
new file mode 100644
index 0000000..fed152d
--- /dev/null
+++ b/src/postfix/postfix.c
@@ -0,0 +1,660 @@
+/*++
+/* NAME
+/* postfix 1
+/* SUMMARY
+/* Postfix control program
+/* SYNOPSIS
+/* .fi
+/* \fBpostfix\fR [\fB-Dv\fR] [\fB-c \fIconfig_dir\fR] \fIcommand\fR
+/* DESCRIPTION
+/* This command is reserved for the superuser. To submit mail,
+/* use the Postfix \fBsendmail\fR(1) command.
+/*
+/* The \fBpostfix\fR(1) command controls the operation of the Postfix
+/* mail system: start or stop the \fBmaster\fR(8) daemon, do a health
+/* check, and other maintenance.
+/*
+/* By default, the \fBpostfix\fR(1) command sets up a standardized
+/* environment and runs the \fBpostfix-script\fR shell script
+/* to do the actual work.
+/*
+/* However, when support for multiple Postfix instances is
+/* configured, \fBpostfix\fR(1) executes the command specified
+/* with the \fBmulti_instance_wrapper\fR configuration parameter.
+/* This command will execute the \fIcommand\fR for each
+/* applicable Postfix instance.
+/*
+/* The following commands are implemented:
+/* .IP \fBcheck\fR
+/* Warn about bad directory/file ownership or permissions,
+/* and create missing directories.
+/* .IP \fBstart\fR
+/* Start the Postfix mail system. This also runs the configuration
+/* check described above.
+/* .IP \fBstart-fg\fR
+/* Like \fBstart\fR, but keep the \fBmaster\fR(8) daemon running
+/* in the foreground, and enable \fBmaster\fR(8) "init" mode
+/* when running as PID 1.
+/* This command requires that multi-instance support is
+/* disabled (i.e. the multi_instance_directories parameter
+/* value must be empty).
+/*
+/* When running Postfix inside a container, see MAILLOG_README
+/* for logging to stdout. Postfix logs to syslog by default,
+/* which requires a) running a syslogd process inside the
+/* container, or b) mounting the container host's /dev/log
+/* socket inside the container (example: "docker run -v
+/* /dev/log:/dev/log ..."), and c) a distinct Postfix "syslog_name"
+/* prefix that identifies logging from the Postfix instance.
+/* .IP \fBstop\fR
+/* Stop the Postfix mail system in an orderly fashion. If
+/* possible, running processes are allowed to terminate at
+/* their earliest convenience.
+/* .sp
+/* Note: in order to refresh the Postfix mail system after a
+/* configuration change, do not use the \fBstart\fR and \fBstop\fR
+/* commands in succession. Use the \fBreload\fR command instead.
+/* .IP \fBabort\fR
+/* Stop the Postfix mail system abruptly. Running processes are
+/* signaled to stop immediately.
+/* .IP \fBflush\fR
+/* Force delivery: attempt to deliver every message in the deferred
+/* mail queue. Normally, attempts to deliver delayed mail happen at
+/* regular intervals, the interval doubling after each failed attempt.
+/* .sp
+/* Warning: flushing undeliverable mail frequently will result in
+/* poor delivery performance of all other mail.
+/* .IP \fBreload\fR
+/* Re-read configuration files. Running processes terminate at their
+/* earliest convenience.
+/* .IP \fBstatus\fR
+/* Indicate if the Postfix mail system is currently running.
+/* .IP "\fBset-permissions\fR [\fIname\fR=\fIvalue ...\fR]"
+/* Set the ownership and permissions of Postfix related files and
+/* directories, as specified in the \fBpostfix-files\fR file.
+/* .sp
+/* Specify \fIname\fR=\fIvalue\fR to override and update specific
+/* main.cf configuration parameters. Use this, for example, to
+/* change the \fBmail_owner\fR or \fBsetgid_group\fR setting for an
+/* already installed Postfix system.
+/* .sp
+/* This feature is available in Postfix 2.1 and later. With
+/* Postfix 2.0 and earlier, use "\fB$config_directory/post-install
+/* set-permissions\fR".
+/* .IP "\fBlogrotate\fR"
+/* Rotate the logfile specified with $maillog_file, by appending
+/* a time-stamp suffix that is formatted according to
+/* $maillog_file_rotate_suffix, and by compressing the file
+/* with the command specified with $maillog_file_compressor.
+/* This will not rotate /dev/* files.
+/* .sp
+/* This feature is available in Postfix 3.4 and later.
+/* .IP "\fBtls\fR \fIsubcommand\fR"
+/* Enable opportunistic TLS in the Postfix SMTP client or
+/* server, and manage Postfix SMTP server TLS private keys and
+/* certificates. See postfix-tls(1) for documentation.
+/* .sp
+/* This feature is available in Postfix 3.1 and later.
+/* .IP "\fBupgrade-configuration\fR [\fIname\fR=\fIvalue ...\fR]"
+/* Update the \fBmain.cf\fR and \fBmaster.cf\fR files with information
+/* that Postfix needs in order to run: add or update services, and add
+/* or update configuration parameter settings.
+/* .sp
+/* Specify \fIname\fR=\fIvalue\fR to override and update specific
+/* main.cf configuration parameters.
+/* .sp
+/* This feature is available in Postfix 2.1 and later. With
+/* Postfix 2.0 and earlier, use "\fB$config_directory/post-install
+/* upgrade-configuration\fR".
+/* .PP
+/* The following options are implemented:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in
+/* the named directory instead of the default configuration directory.
+/* Use this to distinguish between multiple Postfix instances on the
+/* same host.
+/*
+/* With Postfix 2.6 and later, this option forces the postfix(1)
+/* command to operate on the specified Postfix instance only.
+/* This behavior is inherited by postfix(1) commands that run
+/* as a descendant of the current process.
+/* .IP "\fB-D\fR (with \fBpostfix start\fR only)"
+/* Run each Postfix daemon under control of a debugger as specified
+/* via the \fBdebugger_command\fR configuration parameter.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* The \fBpostfix\fR(1) command exports the following environment
+/* variables before executing the \fBpostfix-script\fR file:
+/* .IP \fBMAIL_CONFIG\fR
+/* This is set when the -c command-line option is present.
+/*
+/* With Postfix 2.6 and later, this environment variable forces
+/* the postfix(1) command to operate on the specified Postfix
+/* instance only. This behavior is inherited by postfix(1)
+/* commands that run as a descendant of the current process.
+/* .IP \fBMAIL_VERBOSE\fR
+/* This is set when the -v command-line option is present.
+/* .IP \fBMAIL_DEBUG\fR
+/* This is set when the -D command-line option is present.
+/* .PP
+/* When the internal logging service is enabled (by setting a
+/* non-empty maillog_file parameter value) the postfix(1)
+/* command exports settings that are used by child processes
+/* before they have processed main.cf or command-line settings.
+/* .IP \fBPOSTLOG_SERVICE
+/* The name of the public postlog service endpoint.
+/* .IP \fBPOSTLOG_HOSTNAME
+/* The hostname to prepend to internal logging.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR configuration parameters are
+/* exported as environment variables with the same names:
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix support programs and daemon programs.
+/* .IP "\fBhtml_directory (see 'postconf -d' output)\fR"
+/* The location of Postfix HTML files that describe how to build,
+/* configure or operate a specific Postfix subsystem or feature.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .IP "\fBmailq_path (see 'postconf -d' output)\fR"
+/* Sendmail compatibility feature that specifies where the Postfix
+/* \fBmailq\fR(1) command is installed.
+/* .IP "\fBmanpage_directory (see 'postconf -d' output)\fR"
+/* Where the Postfix manual pages are installed.
+/* .IP "\fBnewaliases_path (see 'postconf -d' output)\fR"
+/* Sendmail compatibility feature that specifies the location of the
+/* \fBnewaliases\fR(1) command.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBreadme_directory (see 'postconf -d' output)\fR"
+/* The location of Postfix README files that describe how to build,
+/* configure or operate a specific Postfix subsystem or feature.
+/* .IP "\fBsendmail_path (see 'postconf -d' output)\fR"
+/* A Sendmail compatibility feature that specifies the location of
+/* the Postfix \fBsendmail\fR(1) command.
+/* .IP "\fBsetgid_group (postdrop)\fR"
+/* The group ownership of set-gid Postfix commands and of group-writable
+/* Postfix directories.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix-writable data files (for example:
+/* caches, pseudo-random numbers).
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBcompatibility_level (0)\fR"
+/* A safety net that causes Postfix to run with backwards-compatible
+/* default settings after an upgrade to a newer Postfix version.
+/* .IP "\fBmeta_directory (see 'postconf -d' output)\fR"
+/* The location of non-executable files that are shared among
+/* multiple Postfix instances, such as postfix-files, dynamicmaps.cf,
+/* and the multi-instance template files main.cf.proto and master.cf.proto.
+/* .IP "\fBshlib_directory (see 'postconf -d' output)\fR"
+/* The location of Postfix dynamically-linked libraries
+/* (libpostfix-*.so), and the default location of Postfix database
+/* plugins (postfix-*.so) that have a relative pathname in the
+/* dynamicmaps.cf file.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBopenssl_path (openssl)\fR"
+/* The location of the OpenSSL command line program \fBopenssl\fR(1).
+/* .PP
+/* Other configuration parameters:
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .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.6 and later:
+/* .IP "\fBmulti_instance_directories (empty)\fR"
+/* An optional list of non-default Postfix configuration directories;
+/* these directories belong to additional Postfix instances that share
+/* the Postfix executable files and documentation with the default
+/* Postfix instance, and that are started, stopped, etc., together
+/* with the default Postfix instance.
+/* .IP "\fBmulti_instance_wrapper (empty)\fR"
+/* The pathname of a multi-instance manager command that the
+/* \fBpostfix\fR(1) command invokes when the multi_instance_directories
+/* parameter value is non-empty.
+/* .IP "\fBmulti_instance_group (empty)\fR"
+/* The optional instance group name of this Postfix instance.
+/* .IP "\fBmulti_instance_name (empty)\fR"
+/* The optional instance name of this Postfix instance.
+/* .IP "\fBmulti_instance_enable (no)\fR"
+/* Allow this Postfix instance to be started, stopped, etc., by a
+/* multi-instance manager.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBmaillog_file (empty)\fR"
+/* The name of an optional logfile that is written by the Postfix
+/* \fBpostlogd\fR(8) service.
+/* .IP "\fBmaillog_file_compressor (gzip)\fR"
+/* The program to run after rotating $maillog_file with "postfix
+/* logrotate".
+/* .IP "\fBmaillog_file_prefixes (/var, /dev/stdout)\fR"
+/* A list of allowed prefixes for a maillog_file value.
+/* .IP "\fBmaillog_file_rotate_suffix (%Y%m%d-%H%M%S)\fR"
+/* The format of the suffix to append to $maillog_file while rotating
+/* the file with "postfix logrotate".
+/* .IP "\fBpostlog_service_name (postlog)\fR"
+/* The name of the \fBpostlogd\fR(8) service entry in master.cf.
+/* FILES
+/* .ad
+/* .fi
+/* Prior to Postfix version 2.6, all of the following files
+/* were in \fB$config_directory\fR. Some files are now in
+/* \fB$daemon_directory\fR or \fB$meta_directory\fR so that they
+/* can be shared among multiple instances that run the same Postfix
+/* version.
+/*
+/* Use the command "\fBpostconf config_directory\fR" or
+/* "\fBpostconf daemon_directory\fR" to expand the names
+/* into their actual values.
+/* .na
+/* .nf
+/*
+/* $config_directory/main.cf, Postfix configuration parameters
+/* $config_directory/master.cf, Postfix daemon processes
+/* $daemon_directory/postfix-script, administrative commands
+/* $daemon_directory/post-install, post-installation configuration
+/* $meta_directory/dynamicmaps.cf, plug-in database clients
+/* $meta_directory/postfix-files, file/directory permissions
+/* SEE ALSO
+/* Commands:
+/* postalias(1), create/update/query alias database
+/* postcat(1), examine Postfix queue file
+/* postconf(1), Postfix configuration utility
+/* postdrop(1), Postfix mail posting utility
+/* postfix(1), Postfix control program
+/* postfix-tls(1), Postfix TLS management
+/* postkick(1), trigger Postfix daemon
+/* postlock(1), Postfix-compatible locking
+/* postlog(1), Postfix-compatible logging
+/* postmap(1), Postfix lookup table manager
+/* postmulti(1), Postfix multi-instance manager
+/* postqueue(1), Postfix mail queue control
+/* postsuper(1), Postfix housekeeping
+/* mailq(1), Sendmail compatibility interface
+/* newaliases(1), Sendmail compatibility interface
+/* sendmail(1), Sendmail compatibility interface
+/*
+/* Postfix configuration:
+/* bounce(5), Postfix bounce message templates
+/* master(5), Postfix master.cf file syntax
+/* postconf(5), Postfix main.cf file syntax
+/* postfix-wrapper(5), Postfix multi-instance API
+/*
+/* Table-driven mechanisms:
+/* access(5), Postfix SMTP access control table
+/* aliases(5), Postfix alias database
+/* canonical(5), Postfix input address rewriting
+/* generic(5), Postfix output address rewriting
+/* header_checks(5), body_checks(5), Postfix content inspection
+/* relocated(5), Users that have moved
+/* transport(5), Postfix routing table
+/* virtual(5), Postfix virtual aliasing
+/*
+/* Table lookup mechanisms:
+/* cidr_table(5), Associate CIDR pattern with value
+/* ldap_table(5), Postfix LDAP client
+/* lmdb_table(5), Postfix LMDB database driver
+/* memcache_table(5), Postfix memcache client
+/* mysql_table(5), Postfix MYSQL client
+/* nisplus_table(5), Postfix NIS+ client
+/* pcre_table(5), Associate PCRE pattern with value
+/* pgsql_table(5), Postfix PostgreSQL client
+/* regexp_table(5), Associate POSIX regexp pattern with value
+/* socketmap_table(5), Postfix socketmap client
+/* sqlite_table(5), Postfix SQLite database driver
+/* tcp_table(5), Postfix client-server table lookup
+/*
+/* Daemon processes:
+/* anvil(8), Postfix connection/rate limiting
+/* bounce(8), defer(8), trace(8), Delivery status reports
+/* cleanup(8), canonicalize and enqueue message
+/* discard(8), Postfix discard delivery agent
+/* dnsblog(8), DNS allow/denylist logger
+/* error(8), Postfix error delivery agent
+/* flush(8), Postfix fast ETRN service
+/* local(8), Postfix local delivery agent
+/* master(8), Postfix master daemon
+/* oqmgr(8), old Postfix queue manager
+/* pickup(8), Postfix local mail pickup
+/* pipe(8), deliver mail to non-Postfix command
+/* postlogd(8), Postfix internal logging service
+/* postscreen(8), Postfix zombie blocker
+/* proxymap(8), Postfix lookup table proxy server
+/* qmgr(8), Postfix queue manager
+/* qmqpd(8), Postfix QMQP server
+/* scache(8), Postfix connection cache manager
+/* showq(8), list Postfix mail queue
+/* smtp(8), lmtp(8), Postfix SMTP+LMTP client
+/* smtpd(8), Postfix SMTP server
+/* spawn(8), run non-Postfix server
+/* tlsmgr(8), Postfix TLS cache and randomness manager
+/* tlsproxy(8), Postfix TLS proxy server
+/* trivial-rewrite(8), Postfix address rewriting
+/* verify(8), Postfix address verification
+/* virtual(8), Postfix virtual delivery agent
+/*
+/* Other:
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* OVERVIEW, overview of Postfix commands and processes
+/* BASIC_CONFIGURATION_README, Postfix basic configuration
+/* ADDRESS_REWRITING_README, Postfix address rewriting
+/* SMTPD_ACCESS_README, SMTP relay/access control
+/* CONTENT_INSPECTION_README, Postfix content inspection
+/* QSHAPE_README, Postfix queue analysis
+/* 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
+/*
+/* TLS support by:
+/* Lutz Jaenicke
+/* Brandenburg University of Technology
+/* Cottbus, Germany
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* SASL support originally by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* LMTP support originally by:
+/* Philip A. Prindeville
+/* Mirapoint, Inc.
+/* USA.
+/*
+/* Amos Gouaux
+/* University of Texas at Dallas
+/* P.O. Box 830688, MC34
+/* Richardson, TX 75083, USA
+/*
+/* IPv6 support originally by:
+/* Mark Huizer, Eindhoven University, The Netherlands
+/* Jun-ichiro 'itojun' Hagino, KAME project, Japan
+/* The Linux PLD project
+/* Dean Strik, Eindhoven University, The Netherlands
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <vstream.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <clean_env.h>
+#include <argv.h>
+#include <safe.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+#include <compat_level.h>
+
+/* Additional installation parameters. */
+
+static char *var_sendmail_path;
+static char *var_mailq_path;
+static char *var_newalias_path;
+static char *var_manpage_dir;
+static char *var_sample_dir;
+static char *var_readme_dir;
+static char *var_html_dir;
+
+/* check_setenv - setenv() with extreme prejudice */
+
+static void check_setenv(char *name, char *value)
+{
+#define CLOBBER 1
+ if (setenv(name, value, CLOBBER) < 0)
+ msg_fatal("setenv: %m");
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - run administrative script from controlled environment */
+
+int main(int argc, char **argv)
+{
+ char *script;
+ struct stat st;
+ char *slash;
+ int fd;
+ int ch;
+ ARGV *import_env;
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SENDMAIL_PATH, DEF_SENDMAIL_PATH, &var_sendmail_path, 1, 0,
+ VAR_MAILQ_PATH, DEF_MAILQ_PATH, &var_mailq_path, 1, 0,
+ VAR_NEWALIAS_PATH, DEF_NEWALIAS_PATH, &var_newalias_path, 1, 0,
+ VAR_MANPAGE_DIR, DEF_MANPAGE_DIR, &var_manpage_dir, 1, 0,
+ VAR_SAMPLE_DIR, DEF_SAMPLE_DIR, &var_sample_dir, 1, 0,
+ VAR_README_DIR, DEF_README_DIR, &var_readme_dir, 1, 0,
+ VAR_HTML_DIR, DEF_HTML_DIR, &var_html_dir, 1, 0,
+ 0,
+ };
+ int force_single_instance;
+ ARGV *my_argv;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up diagnostics. XXX What if stdin is the system console during
+ * boot time? It seems a bad idea to log startup errors to the console.
+ * This is UNIX, a system that can run without hand holding.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ if (isatty(STDERR_FILENO))
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * The mail system must be run by the superuser so it can revoke
+ * privileges for selected operations. That's right - it takes privileges
+ * to toss privileges.
+ */
+ if (getuid() != 0) {
+ msg_error("to submit mail, use the Postfix sendmail command");
+ msg_fatal("the postfix command is reserved for the superuser");
+ }
+ if (unsafe() != 0)
+ msg_fatal("the postfix command must not run as a set-uid process");
+
+ /*
+ * Parse switches.
+ */
+ while ((ch = GETOPT(argc, argv, "c:Dv")) > 0) {
+ switch (ch) {
+ default:
+ msg_fatal("usage: %s [-c config_dir] [-Dv] command", argv[0]);
+ case 'c':
+ if (*optarg != '/')
+ msg_fatal("-c requires absolute pathname");
+ check_setenv(CONF_ENV_PATH, optarg);
+ break;
+ case 'D':
+ check_setenv(CONF_ENV_DEBUG, "");
+ break;
+ case 'v':
+ msg_verbose++;
+ check_setenv(CONF_ENV_VERB, "");
+ break;
+ }
+ }
+ force_single_instance = (getenv(CONF_ENV_PATH) != 0);
+
+ /*
+ * Copy a bunch of configuration parameters into the environment for easy
+ * access by the maintenance shell script.
+ */
+ mail_conf_read();
+ get_mail_conf_str_table(str_table);
+
+ /*
+ * Environment import filter, to enforce consistent behavior whether this
+ * command is started by hand, or at system boot time. This is necessary
+ * because some shell scripts use environment settings to override
+ * main.cf settings.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ /*
+ * This is after calling clean_env(), to ensure that POSTLOG_XXX exports
+ * will work, even if import_environment would remove them.
+ */
+ maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ /*
+ * Alert the sysadmin that the backwards-compatible settings are still in
+ * effect.
+ */
+ if (compat_level < compat_level_from_string(LAST_COMPAT_LEVEL, msg_panic)) {
+ msg_info("Postfix is using backwards-compatible default settings");
+ msg_info("See http://www.postfix.org/COMPATIBILITY_README.html "
+ "for details");
+ msg_info("To disable backwards compatibility use \"postconf "
+ VAR_COMPAT_LEVEL "=%s\" and \"postfix reload\"",
+ LAST_COMPAT_LEVEL);
+ }
+ check_setenv("PATH", ROOT_PATH); /* sys_defs.h */
+ check_setenv(CONF_ENV_PATH, var_config_dir);/* mail_conf.h */
+
+ check_setenv(VAR_COMMAND_DIR, var_command_dir); /* main.cf */
+ check_setenv(VAR_DAEMON_DIR, var_daemon_dir); /* main.cf */
+ check_setenv(VAR_DATA_DIR, var_data_dir); /* main.cf */
+ check_setenv(VAR_META_DIR, var_meta_dir); /* main.cf */
+ check_setenv(VAR_QUEUE_DIR, var_queue_dir); /* main.cf */
+ check_setenv(VAR_CONFIG_DIR, var_config_dir); /* main.cf */
+ check_setenv(VAR_SHLIB_DIR, var_shlib_dir); /* main.cf */
+
+ /*
+ * Do we want to keep adding things here as shell scripts evolve?
+ */
+ check_setenv(VAR_MAIL_OWNER, var_mail_owner); /* main.cf */
+ check_setenv(VAR_SGID_GROUP, var_sgid_group); /* main.cf */
+ check_setenv(VAR_SENDMAIL_PATH, var_sendmail_path); /* main.cf */
+ check_setenv(VAR_MAILQ_PATH, var_mailq_path); /* main.cf */
+ check_setenv(VAR_NEWALIAS_PATH, var_newalias_path); /* main.cf */
+ check_setenv(VAR_MANPAGE_DIR, var_manpage_dir); /* main.cf */
+ check_setenv(VAR_SAMPLE_DIR, var_sample_dir); /* main.cf */
+ check_setenv(VAR_README_DIR, var_readme_dir); /* main.cf */
+ check_setenv(VAR_HTML_DIR, var_html_dir); /* main.cf */
+
+ /*
+ * Make sure these directories exist. Run the maintenance scripts with as
+ * current directory the mail database.
+ */
+ if (chdir(var_command_dir))
+ msg_fatal("chdir(%s): %m", var_command_dir);
+ if (chdir(var_daemon_dir))
+ msg_fatal("chdir(%s): %m", var_daemon_dir);
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir(%s): %m", var_queue_dir);
+
+ /*
+ * Run the management script.
+ */
+ if (force_single_instance
+ || argv_split(var_multi_conf_dirs, CHARS_COMMA_SP)->argc == 0) {
+ script = concatenate(var_daemon_dir, "/postfix-script", (char *) 0);
+ if (optind < 1)
+ msg_panic("bad optind value");
+ argv[optind - 1] = script;
+ execvp(script, argv + optind - 1);
+ msg_fatal("%s: %m", script);
+ }
+
+ /*
+ * Hand off control to a multi-instance manager.
+ */
+ else {
+ if (*var_multi_wrapper == 0)
+ msg_fatal("multi-instance support is requested, but %s is empty",
+ VAR_MULTI_WRAPPER);
+ my_argv = argv_split(var_multi_wrapper, CHARS_SPACE);
+ do {
+ argv_add(my_argv, argv[optind], (char *) 0);
+ } while (argv[optind++] != 0);
+ execvp(my_argv->argv[0], my_argv->argv);
+ msg_fatal("%s: %m", my_argv->argv[0]);
+ }
+}
diff --git a/src/postkick/.indent.pro b/src/postkick/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postkick/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postkick/.printfck b/src/postkick/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postkick/.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/postkick/Makefile.in b/src/postkick/Makefile.in
new file mode 100644
index 0000000..a0c7271
--- /dev/null
+++ b/src/postkick/Makefile.in
@@ -0,0 +1,83 @@
+SHELL = /bin/sh
+SRCS = postkick.c
+OBJS = postkick.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postkick
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postkick.o: ../../include/argv.h
+postkick.o: ../../include/attr.h
+postkick.o: ../../include/check_arg.h
+postkick.o: ../../include/clean_env.h
+postkick.o: ../../include/events.h
+postkick.o: ../../include/htable.h
+postkick.o: ../../include/iostuff.h
+postkick.o: ../../include/mail_conf.h
+postkick.o: ../../include/mail_params.h
+postkick.o: ../../include/mail_parm_split.h
+postkick.o: ../../include/mail_proto.h
+postkick.o: ../../include/mail_version.h
+postkick.o: ../../include/msg.h
+postkick.o: ../../include/msg_vstream.h
+postkick.o: ../../include/mymalloc.h
+postkick.o: ../../include/nvtable.h
+postkick.o: ../../include/safe.h
+postkick.o: ../../include/sys_defs.h
+postkick.o: ../../include/vbuf.h
+postkick.o: ../../include/vstream.h
+postkick.o: ../../include/vstring.h
+postkick.o: ../../include/warn_stat.h
+postkick.o: postkick.c
diff --git a/src/postkick/postkick.c b/src/postkick/postkick.c
new file mode 100644
index 0000000..6bf9245
--- /dev/null
+++ b/src/postkick/postkick.c
@@ -0,0 +1,221 @@
+/*++
+/* NAME
+/* postkick 1
+/* SUMMARY
+/* kick a Postfix service
+/* SYNOPSIS
+/* .fi
+/* \fBpostkick\fR [\fB-c \fIconfig_dir\fR] [\fB-v\fR]
+/* \fIclass service request\fR
+/* DESCRIPTION
+/* The \fBpostkick\fR(1) command sends \fIrequest\fR to the
+/* specified \fIservice\fR over a local transport channel.
+/* This command makes Postfix private IPC accessible
+/* for use in, for example, shell scripts.
+/*
+/* Options:
+/* .IP "\fB-c\fR \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR configuration file in the named directory
+/* instead of the default configuration directory.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* .PP
+/* Arguments:
+/* .IP \fIclass\fR
+/* Name of a class of local transport channel endpoints,
+/* either \fBpublic\fR (accessible by any local user) or
+/* \fBprivate\fR (administrative access only).
+/* .IP \fIservice\fR
+/* The name of a local transport endpoint within the named class.
+/* .IP \fIrequest\fR
+/* A string. The list of valid requests is service-specific.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to the standard error
+/* stream.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* .IP \fBMAIL_VERBOSE\fR
+/* Enable verbose logging for debugging purposes.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBapplication_event_drain_time (100s)\fR"
+/* How long the \fBpostkick\fR(1) command waits for a request to enter the
+/* Postfix daemon process input buffer before giving up.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* FILES
+/* /var/spool/postfix/private, private class endpoints
+/* /var/spool/postfix/public, public class endpoints
+/* SEE ALSO
+/* qmgr(8), queue manager trigger protocol
+/* pickup(8), local pickup daemon
+/* postconf(5), configuration parameters
+/* 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/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <safe.h>
+#include <events.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <mail_parm_split.h>
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-c config_dir] [-v] class service request", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ char *class;
+ char *service;
+ char *request;
+ int fd;
+ struct stat st;
+ char *slash;
+ int c;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (safe_getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Initialize. Set up logging. Read the global configuration file after
+ * parsing command-line arguments.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((c = GETOPT(argc, argv, "c:v")) > 0) {
+ switch (c) {
+ default:
+ usage(argv[0]);
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ if (argc != optind + 3)
+ usage(argv[0]);
+ class = argv[optind];
+ service = argv[optind + 1];
+ request = argv[optind + 2];
+
+ /*
+ * Finish initializations.
+ */
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ /*
+ * Kick the service.
+ */
+ if (mail_trigger(class, service, request, strlen(request)) < 0) {
+ msg_warn("Cannot contact class %s service %s - perhaps the mail system is down",
+ class, service);
+ exit(1);
+ }
+
+ /*
+ * Problem: With triggers over full duplex (i.e. non-FIFO) channels, we
+ * must avoid closing the channel before the server has received the
+ * request. Otherwise some hostile kernel may throw away the request.
+ *
+ * Solution: The trigger routine registers a read event handler that runs
+ * when the server closes the channel. The event_drain() routine waits
+ * for the event handler to run, but gives up when it takes too long.
+ */
+ else {
+ event_drain(var_event_drain);
+ exit(0);
+ }
+}
diff --git a/src/postlock/.indent.pro b/src/postlock/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postlock/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postlock/.printfck b/src/postlock/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postlock/.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/postlock/Makefile.in b/src/postlock/Makefile.in
new file mode 100644
index 0000000..fcb01d2
--- /dev/null
+++ b/src/postlock/Makefile.in
@@ -0,0 +1,86 @@
+SHELL = /bin/sh
+SRCS = postlock.c
+OBJS = postlock.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postlock
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postlock.o: ../../include/argv.h
+postlock.o: ../../include/check_arg.h
+postlock.o: ../../include/clean_env.h
+postlock.o: ../../include/deliver_flock.h
+postlock.o: ../../include/dot_lockfile.h
+postlock.o: ../../include/dsn.h
+postlock.o: ../../include/dsn_buf.h
+postlock.o: ../../include/dsn_util.h
+postlock.o: ../../include/iostuff.h
+postlock.o: ../../include/mail_conf.h
+postlock.o: ../../include/mail_params.h
+postlock.o: ../../include/mail_parm_split.h
+postlock.o: ../../include/mail_version.h
+postlock.o: ../../include/mbox_conf.h
+postlock.o: ../../include/mbox_open.h
+postlock.o: ../../include/msg.h
+postlock.o: ../../include/msg_vstream.h
+postlock.o: ../../include/myflock.h
+postlock.o: ../../include/safe_open.h
+postlock.o: ../../include/sys_defs.h
+postlock.o: ../../include/sys_exits.h
+postlock.o: ../../include/vbuf.h
+postlock.o: ../../include/vstream.h
+postlock.o: ../../include/vstring.h
+postlock.o: ../../include/warn_stat.h
+postlock.o: postlock.c
diff --git a/src/postlock/postlock.c b/src/postlock/postlock.c
new file mode 100644
index 0000000..a05d11e
--- /dev/null
+++ b/src/postlock/postlock.c
@@ -0,0 +1,282 @@
+/*++
+/* NAME
+/* postlock 1
+/* SUMMARY
+/* lock mail folder and execute command
+/* SYNOPSIS
+/* .fi
+/* \fBpostlock\fR [\fB-c \fIconfig_dir\fR] [\fB-l \fIlock_style\fR]
+/* [\fB-v\fR] \fIfile command...\fR
+/* DESCRIPTION
+/* The \fBpostlock\fR(1) command locks \fIfile\fR for exclusive
+/* access, and executes \fIcommand\fR. The locking method is
+/* compatible with the Postfix UNIX-style local delivery agent.
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR configuration file in the named directory
+/* instead of the default configuration directory.
+/* .IP "\fB-l \fIlock_style\fR"
+/* Override the locking method specified via the
+/* \fBmailbox_delivery_lock\fR configuration parameter (see below).
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* .PP
+/* Arguments:
+/* .IP \fIfile\fR
+/* A mailbox file. The user should have read/write permission.
+/* .IP \fIcommand...\fR
+/* The command to execute while \fIfile\fR is locked for exclusive
+/* access. The command is executed directly, i.e. without
+/* interpretation by a shell command interpreter.
+/* DIAGNOSTICS
+/* The result status is 75 (EX_TEMPFAIL) when \fBpostlock\fR(1)
+/* could not perform the requested operation. Otherwise, the
+/* exit status is the exit status from the command.
+/* BUGS
+/* With remote file systems, the ability to acquire a lock does not
+/* necessarily eliminate access conflicts. Avoid file access by
+/* processes running on different machines.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* .IP \fBMAIL_VERBOSE\fR
+/* Enable verbose logging for debugging purposes.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* LOCKING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdeliver_lock_attempts (20)\fR"
+/* The maximal number of attempts to acquire an exclusive lock on a
+/* mailbox file or \fBbounce\fR(8) logfile.
+/* .IP "\fBdeliver_lock_delay (1s)\fR"
+/* The time between attempts to acquire an exclusive lock on a mailbox
+/* file or \fBbounce\fR(8) logfile.
+/* .IP "\fBstale_lock_time (500s)\fR"
+/* The time after which a stale exclusive mailbox lockfile is removed.
+/* .IP "\fBmailbox_delivery_lock (see 'postconf -d' output)\fR"
+/* How to lock a UNIX-style \fBlocal\fR(8) mailbox before attempting delivery.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBfork_attempts (5)\fR"
+/* The maximal number of attempts to fork() a child process.
+/* .IP "\fBfork_delay (1s)\fR"
+/* The delay between attempts to fork() a child process.
+/* 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 "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* 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/stat.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <iostuff.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <dot_lockfile.h>
+#include <deliver_flock.h>
+#include <mail_conf.h>
+#include <sys_exits.h>
+#include <mbox_conf.h>
+#include <mbox_open.h>
+#include <dsn_util.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+/* usage - explain */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-c config_dir] [-l lock_style] [-v] folder command...", myname);
+}
+
+/* fatal_exit - all failures are deemed recoverable */
+
+static void fatal_exit(void)
+{
+ exit(EX_TEMPFAIL);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - go for it */
+
+int main(int argc, char **argv)
+{
+ DSN_BUF *why;
+ char *folder;
+ char **command;
+ int ch;
+ int fd;
+ struct stat st;
+ int count;
+ WAIT_STATUS_T status;
+ pid_t pid;
+ int lock_mask;
+ char *lock_style = 0;
+ MBOX *mp;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process environment options as early as we can. We are not set-uid,
+ * and we are supposed to be running in a controlled environment.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Set up logging and error handling. Intercept fatal exits so we can
+ * return a distinguished exit status.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_cleanup(fatal_exit);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "c:l:v")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'l':
+ lock_style = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ if (optind + 2 > argc)
+ usage(argv[0]);
+ folder = argv[optind];
+ command = argv + optind + 1;
+
+ /*
+ * Read the config file. The command line lock style can override the
+ * configured lock style.
+ */
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ lock_mask = mbox_lock_mask(lock_style ? lock_style :
+ get_mail_conf_str(VAR_MAILBOX_LOCK, DEF_MAILBOX_LOCK, 1, 0));
+
+ /*
+ * Lock the folder for exclusive access. Lose the lock upon exit. The
+ * command is not supposed to disappear into the background.
+ */
+ why = dsb_create();
+ if ((mp = mbox_open(folder, O_APPEND | O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR, (struct stat *) 0,
+ -1, -1, lock_mask, "5.2.0", why)) == 0)
+ msg_fatal("open file %s: %s", folder, vstring_str(why->reason));
+ dsb_free(why);
+
+ /*
+ * Run the command. Remove the lock after completion.
+ */
+ for (count = 1; (pid = fork()) == -1; count++) {
+ msg_warn("fork %s: %m", command[0]);
+ if (count >= var_fork_tries) {
+ mbox_release(mp);
+ exit(EX_TEMPFAIL);
+ }
+ sleep(var_fork_delay);
+ }
+ switch (pid) {
+ case 0:
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ execvp(command[0], command);
+ msg_fatal("execvp %s: %m", command[0]);
+ default:
+ if (waitpid(pid, &status, 0) < 0)
+ msg_fatal("waitpid: %m");
+ vstream_fclose(mp->fp);
+ mbox_release(mp);
+ exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
+ }
+}
diff --git a/src/postlog/.indent.pro b/src/postlog/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postlog/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postlog/.printfck b/src/postlog/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postlog/.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/postlog/Makefile.in b/src/postlog/Makefile.in
new file mode 100644
index 0000000..297eae2
--- /dev/null
+++ b/src/postlog/Makefile.in
@@ -0,0 +1,84 @@
+SHELL = /bin/sh
+SRCS = postlog.c
+OBJS = postlog.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+FILES = Makefile $(SRCS) $(HDRS)
+INC_DIR = ../../include
+TESTPROG=
+PROG = postlog
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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`
+
+shar:
+ @shar $(FILES)
+
+lint:
+ lint $(SRCS)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+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'
+postlog.o: ../../include/argv.h
+postlog.o: ../../include/check_arg.h
+postlog.o: ../../include/clean_env.h
+postlog.o: ../../include/mail_conf.h
+postlog.o: ../../include/mail_params.h
+postlog.o: ../../include/mail_parm_split.h
+postlog.o: ../../include/mail_task.h
+postlog.o: ../../include/mail_version.h
+postlog.o: ../../include/maillog_client.h
+postlog.o: ../../include/msg.h
+postlog.o: ../../include/msg_output.h
+postlog.o: ../../include/msg_vstream.h
+postlog.o: ../../include/stringops.h
+postlog.o: ../../include/sys_defs.h
+postlog.o: ../../include/vbuf.h
+postlog.o: ../../include/vstream.h
+postlog.o: ../../include/vstring.h
+postlog.o: ../../include/vstring_vstream.h
+postlog.o: ../../include/warn_stat.h
+postlog.o: postlog.c
diff --git a/src/postlog/postlog.c b/src/postlog/postlog.c
new file mode 100644
index 0000000..eddc432
--- /dev/null
+++ b/src/postlog/postlog.c
@@ -0,0 +1,372 @@
+/*++
+/* NAME
+/* postlog 1
+/* SUMMARY
+/* Postfix-compatible logging utility
+/* SYNOPSIS
+/* .fi
+/* .ad
+/* \fBpostlog\fR [\fB-iv\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fB-p \fIpriority\fR] [\fB-t \fItag\fR] [\fItext...\fR]
+/* DESCRIPTION
+/* The \fBpostlog\fR(1) command implements a Postfix-compatible logging
+/* interface for use in, for example, shell scripts.
+/*
+/* By default, \fBpostlog\fR(1) logs the \fItext\fR given on the command
+/* line as one record. If no \fItext\fR is specified on the command
+/* line, \fBpostlog\fR(1) reads from standard input and logs each input
+/* line as one record.
+/*
+/* By default, logging is sent to \fBsyslogd\fR(8) or
+/* \fBpostlogd\fR(8); when the
+/* standard error stream is connected to a terminal, logging
+/* is sent there as well.
+/*
+/* The following options are implemented:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR configuration file in the named directory
+/* instead of the default configuration directory.
+/* .IP "\fB-i\fR (obsolete)"
+/* Include the process ID in the logging tag. This flag is ignored as
+/* of Postfix 3.4, where the PID is always included.
+/* .IP "\fB-p \fIpriority\fR (default: \fBinfo\fR)"
+/* Specifies the logging severity: \fBinfo\fR, \fBwarn\fR,
+/* \fBerror\fR, \fBfatal\fR, or \fBpanic\fR. With Postfix 3.1
+/* and later, the program will pause for 1 second after reporting
+/* a \fBfatal\fR or \fBpanic\fR condition, just like other
+/* Postfix programs.
+/* .IP "\fB-t \fItag\fR"
+/* Specifies the logging tag, that is, the identifying name that
+/* appears at the beginning of each logging record. A default tag
+/* is used when none is specified.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBpostlog\fR(1) command is designed to run with
+/* set-groupid privileges, so that it can connect to the
+/* \fBpostlogd\fR(8) daemon process (Postfix 3.7 and later;
+/* earlier implementations of this command must not have
+/* set-groupid or set-userid permissions).
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP MAIL_CONFIG
+/* Directory with the \fBmain.cf\fR file.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .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 3.4 and later:
+/* .IP "\fBmaillog_file (empty)\fR"
+/* The name of an optional logfile that is written by the Postfix
+/* \fBpostlogd\fR(8) service.
+/* .IP "\fBpostlog_service_name (postlog)\fR"
+/* The name of the \fBpostlogd\fR(8) service entry in master.cf.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* The \fBpostlog\fR(1) command was introduced with Postfix
+/* version 3.4.
+/* 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/stat.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.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 <vstring_vstream.h>
+#include <msg_output.h>
+#include <msg_vstream.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h> /* XXX right place for LOG_FACILITY? */
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <mail_task.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+ /*
+ * WARNING WARNING WARNING
+ *
+ * This software is designed to run set-gid. In order to avoid exploitation of
+ * privilege, this software should not run any external commands, nor should
+ * it take any information from the user, unless that information can be
+ * properly sanitized. To get an idea of how much information a process can
+ * inherit from a potentially hostile user, examine all the members of the
+ * process structure (typically, in /usr/include/sys/proc.h): the current
+ * directory, open files, timers, signals, environment, command line, umask,
+ * and so on.
+ */
+
+ /*
+ * Access lists.
+ */
+#if 0
+char *var_postlog_acl;
+
+X static const CONFIG_STR_TABLE str_table[] = {
+ X VAR_POSTLOG_ACL, DEF_POSTLOG_ACL, &var_postlog_acl, 0, 0,
+ X 0,
+X};
+
+#endif
+
+ /*
+ * Support for the severity level mapping.
+ */
+struct level_table {
+ char *name;
+ int level;
+};
+
+static struct level_table level_table[] = {
+ "info", MSG_INFO,
+ "warn", MSG_WARN,
+ "warning", MSG_WARN,
+ "error", MSG_ERROR,
+ "err", MSG_ERROR,
+ "fatal", MSG_FATAL,
+ "crit", MSG_FATAL,
+ "panic", MSG_PANIC,
+ 0,
+};
+
+#define POSTLOG_CMD "postlog"
+
+/* level_map - lookup facility or severity value */
+
+static int level_map(char *name)
+{
+ struct level_table *t;
+
+ for (t = level_table; t->name; t++)
+ if (strcasecmp(t->name, name) == 0)
+ return (t->level);
+ msg_fatal("bad severity: \"%s\"", name);
+}
+
+/* log_argv - log the command line */
+
+static void log_argv(int level, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ while (*argv) {
+ vstring_strcat(buf, *argv++);
+ if (*argv)
+ vstring_strcat(buf, " ");
+ }
+ msg_printf(level, "%s", vstring_str(buf));
+ vstring_free(buf);
+}
+
+/* log_stream - log lines from a stream */
+
+static void log_stream(int level, VSTREAM *fp)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
+ msg_printf(level, "%s", vstring_str(buf));
+ vstring_free(buf);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - logger */
+
+int main(int argc, char **argv)
+{
+ struct stat st;
+ int fd;
+ int ch;
+ const char *tag;
+ char *unsanitized_tag;
+ int level = MSG_INFO;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up diagnostics. Censor the process name: it is provided by the
+ * user.
+ */
+ argv[0] = POSTLOG_CMD;
+ tag = mail_task(argv[0]);
+ if (isatty(STDERR_FILENO))
+ msg_vstream_init(tag, VSTREAM_ERR);
+ maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse switches. This program is set-gid and must sanitize all
+ * command-line parameters. The configuration directory argument is
+ * validated by the mail configuration read routine. Don't do complex
+ * things until we have completed initializations.
+ */
+ unsanitized_tag = 0;
+ while ((ch = GETOPT(argc, argv, "c:ip:t:v")) > 0) {
+ switch (ch) {
+ default:
+ msg_fatal("usage: %s [-c config_dir] [-i] [-p priority] [-t tag] [-v] [text]", argv[0]);
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'i':
+ break;
+ case 'p':
+ level = level_map(optarg);
+ break;
+ case 't':
+ unsanitized_tag = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+
+ /*
+ * Process the main.cf file. This may change the syslog_name setting and
+ * may require that mail_task() be re-evaluated.
+ */
+ mail_conf_read();
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task(POSTLOG_CMD), MAILLOG_CLIENT_FLAG_NONE);
+#if 0
+ mail_dict_init(); /* proxy, sql, ldap */
+ get_mail_conf_str_table(str_table);
+#endif
+
+ /*
+ * This program is designed to be set-gid, which makes it a potential
+ * target for attack. Strip and optionally override the process
+ * environment so that we don't have to trust the C library.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ /*
+ * Sanitize the user-specified tag. The result depends on the value of
+ * var_smtputf8_enable, therefore this code is after the mail_conf_read()
+ * call.
+ */
+ if (unsanitized_tag != 0)
+ tag = printable(unsanitized_tag, '?');
+
+ /*
+ * Re-initialize the logging, this time with the tag specified in main.cf
+ * or on the command line.
+ */
+ if (isatty(STDERR_FILENO))
+ msg_vstream_init(tag, VSTREAM_ERR);
+ maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+#if 0
+ uid_t uid = getuid();
+
+ if (uid != 0 && uid != var_owner_uid
+ && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl,
+ uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to invoke 'postlog'",
+ errstr, (long) uid);
+#endif
+
+ /*
+ * Log the command line or log lines from standard input.
+ */
+ if (argc > optind) {
+ log_argv(level, argv + optind);
+ } else {
+ log_stream(level, VSTREAM_IN);
+ }
+
+ /*
+ * Consistency with msg(3) functions.
+ */
+ if (level >= MSG_FATAL)
+ sleep(1);
+ exit(0);
+}
diff --git a/src/postlogd/Makefile.in b/src/postlogd/Makefile.in
new file mode 100644
index 0000000..21fad76
--- /dev/null
+++ b/src/postlogd/Makefile.in
@@ -0,0 +1,79 @@
+SHELL = /bin/sh
+SRCS = postlogd.c
+OBJS = postlogd.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postlogd
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postlogd.o: ../../include/check_arg.h
+postlogd.o: ../../include/htable.h
+postlogd.o: ../../include/logwriter.h
+postlogd.o: ../../include/mail_conf.h
+postlogd.o: ../../include/mail_params.h
+postlogd.o: ../../include/mail_server.h
+postlogd.o: ../../include/mail_task.h
+postlogd.o: ../../include/mail_version.h
+postlogd.o: ../../include/maillog_client.h
+postlogd.o: ../../include/msg.h
+postlogd.o: ../../include/msg_logger.h
+postlogd.o: ../../include/stringops.h
+postlogd.o: ../../include/sys_defs.h
+postlogd.o: ../../include/vbuf.h
+postlogd.o: ../../include/vstream.h
+postlogd.o: ../../include/vstring.h
+postlogd.o: postlogd.c
diff --git a/src/postlogd/postlogd.c b/src/postlogd/postlogd.c
new file mode 100644
index 0000000..902cbe5
--- /dev/null
+++ b/src/postlogd/postlogd.c
@@ -0,0 +1,267 @@
+/*++
+/* NAME
+/* postlogd 8
+/* SUMMARY
+/* Postfix internal log server
+/* SYNOPSIS
+/* \fBpostlogd\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* This program logs events on behalf of Postfix programs
+/* when the maillog configuration parameter specifies a non-empty
+/* value.
+/* BUGS
+/* Non-daemon Postfix programs don't know that they should log
+/* to the internal logging service before they have processed
+/* command-line options and main.cf parameters. These programs
+/* still log earlier events to the syslog service.
+/*
+/* If Postfix is down, the non-daemon programs \fBpostfix\fR(1),
+/* \fBpostsuper\fR(1), \fBpostmulti\fR(1), and \fBpostlog\fR(1),
+/* will log directly to \fB$maillog_file\fR. These programs
+/* expect to run with root privileges, for example during
+/* Postfix start-up, reload, or shutdown.
+/*
+/* Other non-daemon Postfix programs will never write directly to
+/* \fB$maillog_file\fR (also, logging to stdout would interfere
+/* with the operation of some of these programs). These programs
+/* can log to \fBpostlogd\fR(8) if they are run by the super-user,
+/* or if their executable file has set-gid permission. Do not
+/* set this permission on programs other than \fBpostdrop\fR(1),
+/* \fBpostqueue\fR(1) and (Postfix >= 3.7) \fBpostlog\fR(1).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as
+/* \fBpostlogd\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.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBmaillog_file (empty)\fR"
+/* The name of an optional logfile that is written by the Postfix
+/* \fBpostlogd\fR(8) service.
+/* .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 "\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".
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* .IP "\fBpostlogd_watchdog_timeout (10s)\fR"
+/* How much time a \fBpostlogd\fR(8) process may take to process a request
+/* before it is terminated by a built-in watchdog timer.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* syslogd(8), system logging
+/* README_FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* MAILLOG_README, Postfix logging to file or stdout
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 3.4.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <logwriter.h>
+#include <msg.h>
+#include <msg_logger.h>
+#include <stringops.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_task.h>
+#include <mail_version.h>
+#include <maillog_client.h>
+
+ /*
+ * Server skeleton.
+ */
+#include <mail_server.h>
+
+ /*
+ * Tunable parameters.
+ */
+int var_postlogd_watchdog;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * Logfile stream.
+ */
+static VSTREAM *postlogd_stream = 0;
+
+/* postlogd_fallback - log messages from postlogd(8) itself */
+
+static void postlogd_fallback(const char *buf)
+{
+ (void) logwriter_write(postlogd_stream, buf, strlen(buf));
+}
+
+/* postlogd_service - perform service for client */
+
+static void postlogd_service(char *buf, ssize_t len, char *unused_service,
+ char **unused_argv)
+{
+
+ if (postlogd_stream) {
+ (void) logwriter_write(postlogd_stream, buf, len);
+ }
+
+ /*
+ * After a configuration change that removes the maillog_file pathname,
+ * this service may still receive messages (after "postfix reload" or
+ * after process refresh) from programs that use the old maillog_file
+ * setting. Redirect those messages to the current logging mechanism.
+ */
+ else {
+ char *bp = buf;
+ char *progname_pid;
+
+ /*
+ * Avoid surprises: strip off the date, time, host, and program[pid]:
+ * prefix that were prepended by msg_logger(3). Then, hope that the
+ * current logging driver suppresses its own PID, when it sees that
+ * there is a PID embedded in the 'program name'.
+ */
+ (void) mystrtok(&bp, CHARS_SPACE); /* month */
+ (void) mystrtok(&bp, CHARS_SPACE); /* day */
+ (void) mystrtok(&bp, CHARS_SPACE); /* time */
+ (void) mystrtok(&bp, CHARS_SPACE); /* host */
+ progname_pid = mystrtok(&bp, ":" CHARS_SPACE); /* name[pid] sans ':' */
+ bp += strspn(bp, CHARS_SPACE);
+ if (progname_pid)
+ maillog_client_init(progname_pid, MAILLOG_CLIENT_FLAG_NONE);
+ msg_info("%.*s", (int) (len - (bp - buf)), bp);
+
+ /*
+ * Restore the program name, in case postlogd(8) needs to log
+ * something about itself. We have to call maillog_client_init() in
+ * any case, because neither msg_syslog_init() nor openlog() make a
+ * copy of the name argument. We can't leave that pointing into the
+ * middle of the above message buffer.
+ */
+ maillog_client_init(mail_task((char *) 0), MAILLOG_CLIENT_FLAG_NONE);
+ }
+}
+
+/* pre_jail_init - pre-jail handling */
+
+static void pre_jail_init(char *unused_service_name, char **argv)
+{
+
+ /*
+ * During process initialization, the postlogd daemon will log events to
+ * the postlog socket, so that they can be logged to file later. Once the
+ * postlogd daemon is handling requests, it will stop logging to the
+ * postlog socket and will instead write to the logfile, to avoid
+ * infinite recursion.
+ */
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * After a configuration change that removes the maillog_file pathname,
+ * this service may still receive messages from processes that still use
+ * the old configuration. Those messages will have to be redirected to
+ * the current logging subsystem.
+ */
+ if (*var_maillog_file != 0) {
+
+ /*
+ * Instantiate the logwriter or bust.
+ */
+ postlogd_stream = logwriter_open_or_die(var_maillog_file);
+
+ /*
+ * Inform the msg_logger client to stop using the postlog socket, and
+ * to call our logwriter.
+ */
+ msg_logger_control(CA_MSG_LOGGER_CTL_FALLBACK_ONLY,
+ CA_MSG_LOGGER_CTL_FALLBACK_FN(postlogd_fallback),
+ CA_MSG_LOGGER_CTL_END);
+ }
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Prevent automatic process suicide after a limited number of client
+ * requests. It is OK to terminate after a limited amount of idle time.
+ */
+ var_use_limit = 0;
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_POSTLOGD_WATCHDOG, DEF_POSTLOGD_WATCHDOG, &var_postlogd_watchdog, 10, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * This is a datagram service, not a stream service, so that postlogd can
+ * restart immediately after "postfix reload" without requiring clients
+ * to resend messages. Those messages remain queued in the kernel until a
+ * new postlogd process retrieves them. It would be unreasonable to
+ * require that clients retransmit logs, especially in the case of a
+ * fatal or panic error.
+ */
+ dgram_server_main(argc, argv, postlogd_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_WATCHDOG(&var_postlogd_watchdog),
+ 0);
+}
diff --git a/src/postmap/.indent.pro b/src/postmap/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postmap/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postmap/.printfck b/src/postmap/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postmap/.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/postmap/Makefile.in b/src/postmap/Makefile.in
new file mode 100644
index 0000000..dc5369d
--- /dev/null
+++ b/src/postmap/Makefile.in
@@ -0,0 +1,162 @@
+SHELL = /bin/sh
+SRCS = postmap.c
+OBJS = postmap.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postmap
+INC_DIR = ../../include
+LIBS = ../../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 $? >$@
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+tests: test1 test2 fail_test quote_test file_test lmdb_abb_test \
+ lmdb_bulk_test lmdb_incr_test
+
+root_tests:
+
+test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in
+ for key in abc ghi; \
+ do \
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \
+ done
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in
+ for key in ABC; \
+ do \
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \
+ done
+ rm -f map.in.db
+
+test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in
+ for key in abc ghi; \
+ do \
+ echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \
+ done
+ $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in
+ for key in ABC; \
+ do \
+ echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \
+ done
+ rm -f map.in.db
+
+fail_test: $(PROG) aliases fail_test.in fail_test.ref
+ -($(SHLIB_ENV) sh fail_test.in || exit 0) >fail_test.tmp 2>&1
+ diff fail_test.ref fail_test.tmp
+ rm -f fail_test.tmp
+
+quote_test: $(PROG) aliases quote_test.in quote_test.ref
+ rm -f quote_test_map.*
+ $(SHLIB_ENV) sh quote_test.in >quote_test.tmp 2>&1
+ diff quote_test.ref quote_test.tmp
+ rm -f quote_test.tmp quote_test_map.*
+
+file_test: $(PROG) file_test.in file_test.ref
+ rm -f file_test_map.* postmap-file-1 postmap-file-2
+ $(SHLIB_ENV) sh file_test.in >file_test.tmp 2>&1
+ diff file_test.ref file_test.tmp
+ rm -f file_test.tmp file_test_map.* postmap-file-1 postmap-file-2
+
+lmdb_abb_test: $(PROG) lmdb_abb lmdb_abb.ref
+ rm -f lmdb_abb.lmdb
+ ($(SHLIB_ENV) $(VALGRIND) ./postmap lmdb:lmdb_abb; \
+ $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_abb | sort) >lmdb_abb.tmp 2>&1
+ diff lmdb_abb.ref lmdb_abb.tmp
+ rm -f lmdb_abb.tmp lmdb_abb.lmdb
+
+lmdb_bulk_test: $(PROG)
+ rm -f lmdb_retry.lmdb main.cf
+ tr A-Z a-z < /usr/share/dict/words| \
+ sed -e 's/.*/& &/' -e 10000q| LANG=C sort -u >lmdb_retry
+ echo lmdb_map_size=10240 >main.cf
+ ($(SHLIB_ENV) $(VALGRIND) ./postmap -c . lmdb:lmdb_retry; \
+ $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_retry | \
+ LANG=C sort > lmdb_retry.tmp)
+ cmp lmdb_retry lmdb_retry.tmp
+ rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf
+
+lmdb_incr_test: $(PROG)
+ rm -f lmdb_retry.lmdb main.cf
+ tr A-Z a-z < /usr/share/dict/words| \
+ sed -e 's/.*/& &/' -e 1000q| LANG=C sort -u >lmdb_retry
+ echo lmdb_map_size=10240 >main.cf
+ ($(SHLIB_ENV) $(VALGRIND) ./postmap -ic . lmdb:lmdb_retry <lmdb_retry; \
+ $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_retry | \
+ LANG=C sort > lmdb_retry.tmp)
+ cmp lmdb_retry lmdb_retry.tmp
+ rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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) *.tmp junk *.db
+ rm -rf printfck
+
+tidy: clean
+
+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'
+postmap.o: ../../include/argv.h
+postmap.o: ../../include/check_arg.h
+postmap.o: ../../include/clean_env.h
+postmap.o: ../../include/dict.h
+postmap.o: ../../include/dict_proxy.h
+postmap.o: ../../include/header_opts.h
+postmap.o: ../../include/mail_conf.h
+postmap.o: ../../include/mail_dict.h
+postmap.o: ../../include/mail_params.h
+postmap.o: ../../include/mail_parm_split.h
+postmap.o: ../../include/mail_task.h
+postmap.o: ../../include/mail_version.h
+postmap.o: ../../include/maillog_client.h
+postmap.o: ../../include/mime_state.h
+postmap.o: ../../include/mkmap.h
+postmap.o: ../../include/msg.h
+postmap.o: ../../include/msg_vstream.h
+postmap.o: ../../include/myflock.h
+postmap.o: ../../include/mymalloc.h
+postmap.o: ../../include/readlline.h
+postmap.o: ../../include/rec_type.h
+postmap.o: ../../include/set_eugid.h
+postmap.o: ../../include/split_at.h
+postmap.o: ../../include/stringops.h
+postmap.o: ../../include/sys_defs.h
+postmap.o: ../../include/vbuf.h
+postmap.o: ../../include/vstream.h
+postmap.o: ../../include/vstring.h
+postmap.o: ../../include/vstring_vstream.h
+postmap.o: ../../include/warn_stat.h
+postmap.o: postmap.c
diff --git a/src/postmap/aliases b/src/postmap/aliases
new file mode 100644
index 0000000..cce2b6b
--- /dev/null
+++ b/src/postmap/aliases
@@ -0,0 +1 @@
+xx yy
diff --git a/src/postmap/fail_test.in b/src/postmap/fail_test.in
new file mode 100644
index 0000000..ce01127
--- /dev/null
+++ b/src/postmap/fail_test.in
@@ -0,0 +1,8 @@
+${VALGRIND} ./postmap -q xx fail:aliases
+echo xx | ${VALGRIND} ./postmap -q - fail:aliases
+echo xx | ${VALGRIND} ./postmap -bq - fail:aliases
+${VALGRIND} ./postmap -d xx fail:aliases
+echo xx | ${VALGRIND} ./postmap -d - fail:aliases
+${VALGRIND} ./postmap -s fail:aliases
+${VALGRIND} ./postmap -i fail:aliases < aliases
+${VALGRIND} ./postmap fail:aliases
diff --git a/src/postmap/fail_test.ref b/src/postmap/fail_test.ref
new file mode 100644
index 0000000..54c6f33
--- /dev/null
+++ b/src/postmap/fail_test.ref
@@ -0,0 +1,8 @@
+postmap: fatal: table fail:aliases: query error: Application error
+postmap: fatal: table fail:aliases: query error: Application error
+postmap: fatal: table fail:aliases: query error: Application error
+postmap: fatal: table fail:aliases: delete error: Application error
+postmap: fatal: table fail:aliases: delete error: Application error
+postmap: fatal: table fail:aliases: sequence error: Application error
+postmap: fatal: table fail:aliases: write error: Application error
+postmap: fatal: table fail:aliases: write error: Application error
diff --git a/src/postmap/file_test.in b/src/postmap/file_test.in
new file mode 100644
index 0000000..8c8a1a9
--- /dev/null
+++ b/src/postmap/file_test.in
@@ -0,0 +1,17 @@
+echo -n this-is-file1 > postmap-file-1
+echo -n this-is-file2 > postmap-file-2
+echo 'file-1 postmap-file-1' | ${VALGRIND} ./postmap -iF file_test_map || exit 1
+echo 'file-2 postmap-file-2' | ${VALGRIND} ./postmap -iF file_test_map || exit 1
+echo 'file-3 postmap-file-3' | ${VALGRIND} ./postmap -iF file_test_map || exit 1
+echo 'entry-4 postmap-entry-4' | ${VALGRIND} ./postmap -i file_test_map || exit 1
+${VALGRIND} ./postmap -s file_test_map | LC_ALL=C sort
+${VALGRIND} ./postmap -q file-1 file_test_map
+${VALGRIND} ./postmap -q file-2 file_test_map
+${VALGRIND} ./postmap -q file-3 file_test_map
+${VALGRIND} ./postmap -q entry-4 file_test_map
+${VALGRIND} ./postmap -Fs file_test_map | LC_ALL=C sort
+${VALGRIND} ./postmap -Fq file-1 file_test_map
+${VALGRIND} ./postmap -Fq file-2 file_test_map
+${VALGRIND} ./postmap -Fq file-3 file_test_map
+${VALGRIND} ./postmap -Fq entry-4 file_test_map
+exit 0
diff --git a/src/postmap/file_test.ref b/src/postmap/file_test.ref
new file mode 100644
index 0000000..bc4815d
--- /dev/null
+++ b/src/postmap/file_test.ref
@@ -0,0 +1,14 @@
+postmap: warning: stdin, line 1: open postmap-file-3: No such file or directory: skipping this entry
+entry-4 postmap-entry-4
+file-1 dGhpcy1pcy1maWxlMQ==
+file-2 dGhpcy1pcy1maWxlMg==
+dGhpcy1pcy1maWxlMQ==
+dGhpcy1pcy1maWxlMg==
+postmap-entry-4
+postmap: warning: table hash:file_test_map.db: key entry-4: malformed BASE64 value: postmap-entry-4
+file-1 this-is-file1
+file-2 this-is-file2
+this-is-file1
+this-is-file2
+postmap: warning: table hash:file_test_map.db: key entry-4: malformed BASE64 value: postmap-entry-4
+postmap: fatal: table hash:file_test_map.db: query error
diff --git a/src/postmap/lmdb_abb b/src/postmap/lmdb_abb
new file mode 100644
index 0000000..7ea8d03
--- /dev/null
+++ b/src/postmap/lmdb_abb
@@ -0,0 +1,3 @@
+a 1
+b 2
+b 3
diff --git a/src/postmap/lmdb_abb.ref b/src/postmap/lmdb_abb.ref
new file mode 100644
index 0000000..ea66c71
--- /dev/null
+++ b/src/postmap/lmdb_abb.ref
@@ -0,0 +1,3 @@
+postmap: warning: lmdb:lmdb_abb: duplicate entry: "b"
+a 1
+b 2
diff --git a/src/postmap/map-abc1.ref b/src/postmap/map-abc1.ref
new file mode 100644
index 0000000..142195f
--- /dev/null
+++ b/src/postmap/map-abc1.ref
@@ -0,0 +1 @@
+DEF
diff --git a/src/postmap/map-abc2.ref b/src/postmap/map-abc2.ref
new file mode 100644
index 0000000..2ee5fab
--- /dev/null
+++ b/src/postmap/map-abc2.ref
@@ -0,0 +1 @@
+abc DEF
diff --git a/src/postmap/map-ghi1.ref b/src/postmap/map-ghi1.ref
new file mode 100644
index 0000000..7beb1db
--- /dev/null
+++ b/src/postmap/map-ghi1.ref
@@ -0,0 +1 @@
+jkl
diff --git a/src/postmap/map-ghi2.ref b/src/postmap/map-ghi2.ref
new file mode 100644
index 0000000..5926a18
--- /dev/null
+++ b/src/postmap/map-ghi2.ref
@@ -0,0 +1 @@
+ghi jkl
diff --git a/src/postmap/map-uABC1.ref b/src/postmap/map-uABC1.ref
new file mode 100644
index 0000000..142195f
--- /dev/null
+++ b/src/postmap/map-uABC1.ref
@@ -0,0 +1 @@
+DEF
diff --git a/src/postmap/map-uABC2.ref b/src/postmap/map-uABC2.ref
new file mode 100644
index 0000000..cc3ea4e
--- /dev/null
+++ b/src/postmap/map-uABC2.ref
@@ -0,0 +1 @@
+ABC DEF
diff --git a/src/postmap/map.in b/src/postmap/map.in
new file mode 100644
index 0000000..f180554
--- /dev/null
+++ b/src/postmap/map.in
@@ -0,0 +1,2 @@
+ABC DEF
+ghi jkl
diff --git a/src/postmap/postmap.c b/src/postmap/postmap.c
new file mode 100644
index 0000000..66421d0
--- /dev/null
+++ b/src/postmap/postmap.c
@@ -0,0 +1,1155 @@
+/*++
+/* NAME
+/* postmap 1
+/* SUMMARY
+/* Postfix lookup table management
+/* SYNOPSIS
+/* .fi
+/* \fBpostmap\fR [\fB-bfFhimnNoprsuUvw\fR] [\fB-c \fIconfig_dir\fR]
+/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR]
+/* [\fIfile_type\fR:]\fIfile_name\fR ...
+/* DESCRIPTION
+/* The \fBpostmap\fR(1) command creates or queries one or more Postfix
+/* lookup tables, or updates an existing one.
+/*
+/* If the result files do not exist they will be created with the
+/* same group and other read permissions as their source file.
+/*
+/* While the table update is in progress, signal delivery is
+/* postponed, and an exclusive, advisory, lock is placed on the
+/* entire table, in order to avoid surprises in spectator
+/* processes.
+/* INPUT FILE FORMAT
+/* .ad
+/* .fi
+/* The format of a lookup table input file is as follows:
+/* .IP \(bu
+/* A table entry has the form
+/* .sp
+/* .nf
+/* \fIkey\fR whitespace \fIvalue\fR
+/* .fi
+/* .IP \(bu
+/* Empty lines and whitespace-only lines are ignored, as
+/* are lines whose first non-whitespace character is a `#'.
+/* .IP \(bu
+/* A logical line starts with non-whitespace text. A line that
+/* starts with whitespace continues a logical line.
+/* .PP
+/* The \fIkey\fR and \fIvalue\fR are processed as is, except that
+/* surrounding white space is stripped off. Whitespace in lookup
+/* keys is supported in Postfix 3.2 and later, by surrounding the
+/* key with double quote characters `"'. Within the double quotes,
+/* double quote `"' and backslash `\\' characters can be included
+/* by quoting them with a preceding backslash.
+/*
+/* When the \fB-F\fR option is given, the \fIvalue\fR must
+/* specify one or more filenames separated by comma and/or
+/* whitespace; \fBpostmap\fR(1) will concatenate the file
+/* content (with a newline character inserted between files)
+/* and will store the base64-encoded result instead of the
+/* \fIvalue\fR.
+/*
+/* When the \fIkey\fR specifies email address information, the
+/* localpart should be enclosed with double quotes if required
+/* by RFC 5322. For example, an address localpart that contains
+/* ";", or a localpart that starts or ends with ".".
+/*
+/* By default the lookup key is mapped to lowercase to make
+/* the lookups case insensitive; as of Postfix 2.3 this case
+/* folding happens only with tables whose lookup keys are
+/* fixed-case strings such as btree:, dbm: or hash:. With
+/* earlier versions, the lookup key is folded even with tables
+/* where a lookup field can match both upper and lower case
+/* text, such as regexp: and pcre:. This resulted in loss of
+/* information with $\fInumber\fR substitutions.
+/* COMMAND-LINE ARGUMENTS
+/* .ad
+/* .fi
+/* .IP \fB-b\fR
+/* Enable message body query mode. When reading lookup keys
+/* from standard input with "\fB-q -\fR", process the input
+/* as if it is an email message in RFC 5322 format. Each line
+/* of body content becomes one lookup key.
+/* .sp
+/* By default, the \fB-b\fR option starts generating lookup
+/* keys at the first non-header line, and stops when the end
+/* of the message is reached.
+/* To simulate \fBbody_checks\fR(5) processing, enable MIME
+/* parsing with \fB-m\fR. With this, the \fB-b\fR option
+/* generates no body-style lookup keys for attachment MIME
+/* headers and for attached message/* headers.
+/* .sp
+/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option
+/* disables UTF-8 syntax checks on query keys and lookup
+/* results. Specify the \fB-U\fR option to force UTF-8
+/* syntax checks anyway.
+/* .sp
+/* This feature is available in Postfix version 2.6 and later.
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR configuration file in the named directory
+/* instead of the default configuration directory.
+/* .IP "\fB-d \fIkey\fR"
+/* Search the specified maps for \fIkey\fR and remove one entry per map.
+/* The exit status is zero when the requested information was found.
+/*
+/* If a key value of \fB-\fR is specified, the program reads key
+/* values from the standard input stream. The exit status is zero
+/* when at least one of the requested keys was found.
+/* .IP \fB-f\fR
+/* Do not fold the lookup key to lower case while creating or querying
+/* a table.
+/*
+/* With Postfix version 2.3 and later, this option has no
+/* effect for regular expression tables. There, case folding
+/* is controlled by appending a flag to a pattern.
+/* .IP \fB-F\fR
+/* When querying a map, or listing a map, base64-decode each
+/* value. When creating a map from source file, process each
+/* value as a list of filenames, concatenate the content of
+/* those files, and store the base64-encoded result instead
+/* of the value (see INPUT FILE FORMAT for details).
+/* .sp
+/* This feature is available in Postfix version 3.4 and later.
+/* .IP \fB-h\fR
+/* Enable message header query mode. When reading lookup keys
+/* from standard input with "\fB-q -\fR", process the input
+/* as if it is an email message in RFC 5322 format. Each
+/* logical header line becomes one lookup key. A multi-line
+/* header becomes one lookup key with one or more embedded
+/* newline characters.
+/* .sp
+/* By default, the \fB-h\fR option generates lookup keys until
+/* the first non-header line is reached.
+/* To simulate \fBheader_checks\fR(5) processing, enable MIME
+/* parsing with \fB-m\fR. With this, the \fB-h\fR option also
+/* generates header-style lookup keys for attachment MIME
+/* headers and for attached message/* headers.
+/* .sp
+/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option
+/* option disables UTF-8 syntax checks on query keys and
+/* lookup results. Specify the \fB-U\fR option to force UTF-8
+/* syntax checks anyway.
+/* .sp
+/* This feature is available in Postfix version 2.6 and later.
+/* .IP \fB-i\fR
+/* Incremental mode. Read entries from standard input and do not
+/* truncate an existing database. By default, \fBpostmap\fR(1) creates
+/* a new database from the entries in \fBfile_name\fR.
+/* .IP \fB-m\fR
+/* Enable MIME parsing with "\fB-b\fR" and "\fB-h\fR".
+/* .sp
+/* This feature is available in Postfix version 2.6 and later.
+/* .IP \fB-N\fR
+/* Include the terminating null character that terminates lookup keys
+/* and values. By default, \fBpostmap\fR(1) does whatever is
+/* the default for
+/* the host operating system.
+/* .IP \fB-n\fR
+/* Don't include the terminating null character that terminates lookup
+/* keys and values. By default, \fBpostmap\fR(1) does whatever
+/* is the default for
+/* the host operating system.
+/* .IP \fB-o\fR
+/* Do not release root privileges when processing a non-root
+/* input file. By default, \fBpostmap\fR(1) drops root privileges
+/* and runs as the source file owner instead.
+/* .IP \fB-p\fR
+/* Do not inherit the file access permissions from the input file
+/* when creating a new file. Instead, create a new file with default
+/* access permissions (mode 0644).
+/* .IP "\fB-q \fIkey\fR"
+/* Search the specified maps for \fIkey\fR and write the first value
+/* found to the standard output stream. The exit status is zero
+/* when the requested information was found.
+/*
+/* Note: this performs a single query with the key as specified,
+/* and does not make iterative queries with substrings of the
+/* key as described for access(5), canonical(5), transport(5),
+/* virtual(5) and other Postfix table-driven features.
+/*
+/* If a key value of \fB-\fR is specified, the program reads key
+/* values from the standard input stream and writes one line of
+/* \fIkey value\fR output for each key that was found. The exit
+/* status is zero when at least one of the requested keys was found.
+/* .IP \fB-r\fR
+/* When updating a table, do not complain about attempts to update
+/* existing entries, and make those updates anyway.
+/* .IP \fB-s\fR
+/* Retrieve all database elements, and write one line of
+/* \fIkey value\fR output for each element. The elements are
+/* printed in database order, which is not necessarily the same
+/* as the original input order.
+/* .sp
+/* This feature is available in Postfix version 2.2 and later,
+/* and is not available for all database types.
+/* .IP \fB-u\fR
+/* Disable UTF-8 support. UTF-8 support is enabled by default
+/* when "smtputf8_enable = yes". It requires that keys and
+/* values are valid UTF-8 strings.
+/* .IP \fB-U\fR
+/* With "smtputf8_enable = yes", force UTF-8 syntax checks
+/* with the \fB-b\fR and \fB-h\fR options.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* .IP \fB-w\fR
+/* When updating a table, do not complain about attempts to update
+/* existing entries, and ignore those attempts.
+/* .PP
+/* Arguments:
+/* .IP \fIfile_type\fR
+/* The database type. To find out what types are supported, use
+/* the "\fBpostconf -m\fR" command.
+/*
+/* The \fBpostmap\fR(1) command can query any supported file type,
+/* but it can create only the following file types:
+/* .RS
+/* .IP \fBbtree\fR
+/* The output file is a btree file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBcdb\fR
+/* The output consists of one file, named \fIfile_name\fB.cdb\fR.
+/* This is available on systems with support for \fBcdb\fR databases.
+/* .IP \fBdbm\fR
+/* The output consists of two files, named \fIfile_name\fB.pag\fR and
+/* \fIfile_name\fB.dir\fR.
+/* This is available on systems with support for \fBdbm\fR databases.
+/* .IP \fBfail\fR
+/* A table that reliably fails all requests. The lookup table
+/* name is used for logging only. This table exists to simplify
+/* Postfix error tests.
+/* .IP \fBhash\fR
+/* The output file is a hashed file, named \fIfile_name\fB.db\fR.
+/* This is available on systems with support for \fBdb\fR databases.
+/* .IP \fBlmdb\fR
+/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR.
+/* \fBlmdb\fR supports concurrent writes and reads from different
+/* processes, unlike other supported file-based tables.
+/* This is available on systems with support for \fBlmdb\fR databases.
+/* .IP \fBsdbm\fR
+/* The output consists of two files, named \fIfile_name\fB.pag\fR and
+/* \fIfile_name\fB.dir\fR.
+/* This is available on systems with support for \fBsdbm\fR databases.
+/* .PP
+/* When no \fIfile_type\fR is specified, the software uses the database
+/* type specified via the \fBdefault_database_type\fR configuration
+/* parameter.
+/* .RE
+/* .IP \fIfile_name\fR
+/* The name of the lookup table source file when rebuilding a database.
+/* DIAGNOSTICS
+/* Problems are logged to the standard error stream and to
+/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* No output means that no problems were detected. Duplicate entries are
+/* skipped and are flagged with a warning.
+/*
+/* \fBpostmap\fR(1) terminates with zero exit status in case of success
+/* (including successful "\fBpostmap -q\fR" lookup) and terminates
+/* with non-zero exit status in case of failure.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* .IP \fBMAIL_VERBOSE\fR
+/* Enable verbose logging for debugging purposes.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR"
+/* The per-table I/O buffer size for programs that create Berkeley DB
+/* hash or btree tables.
+/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR"
+/* The per-table I/O buffer size for programs that read Berkeley DB
+/* hash or btree tables.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR"
+/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1)
+/* and \fBpostmap\fR(1) commands.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBsmtputf8_enable (yes)\fR"
+/* Enable preliminary SMTPUTF8 support for the protocols described
+/* in RFC 6531, RFC 6532, and RFC 6533.
+/* .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 2.11 and later:
+/* .IP "\fBlmdb_map_size (16777216)\fR"
+/* The initial OpenLDAP LMDB database size limit in bytes.
+/* SEE ALSO
+/* postalias(1), create/update/query alias database
+/* postconf(1), supported database types
+/* postconf(5), configuration parameters
+/* 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
+/* DATABASE_README, Postfix lookup table overview
+/* 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/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <vstring_vstream.h>
+#include <set_eugid.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_dict.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mkmap.h>
+#include <mail_task.h>
+#include <dict_proxy.h>
+#include <mime_state.h>
+#include <rec_type.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+#define POSTMAP_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */
+#define POSTMAP_FLAG_SAVE_PERM (1<<1) /* copy access permission from source */
+#define POSTMAP_FLAG_HEADER_KEY (1<<2) /* apply to header text */
+#define POSTMAP_FLAG_BODY_KEY (1<<3) /* apply to body text */
+#define POSTMAP_FLAG_MIME_KEY (1<<4) /* enable MIME parsing */
+
+#define POSTMAP_FLAG_HB_KEY (POSTMAP_FLAG_HEADER_KEY | POSTMAP_FLAG_BODY_KEY)
+#define POSTMAP_FLAG_FULL_KEY (POSTMAP_FLAG_BODY_KEY | POSTMAP_FLAG_MIME_KEY)
+#define POSTMAP_FLAG_ANY_KEY (POSTMAP_FLAG_HB_KEY | POSTMAP_FLAG_MIME_KEY)
+
+ /*
+ * MIME Engine call-back state for generating lookup keys from an email
+ * message read from standard input.
+ */
+typedef struct {
+ DICT **dicts; /* map handles */
+ char **maps; /* map names */
+ int map_count; /* yes, indeed */
+ int dict_flags; /* query flags */
+ int header_done; /* past primary header */
+ int found; /* result */
+} POSTMAP_KEY_STATE;
+
+/* postmap - create or update mapping database */
+
+static void postmap(char *map_type, char *path_name, int postmap_flags,
+ int open_flags, int dict_flags)
+{
+ VSTREAM *NOCLOBBER source_fp;
+ VSTRING *line_buffer;
+ MKMAP *mkmap;
+ int lineno;
+ int last_line;
+ char *key;
+ char *value;
+ struct stat st;
+ mode_t saved_mask;
+
+ /*
+ * Initialize.
+ */
+ line_buffer = vstring_alloc(100);
+ if ((open_flags & O_TRUNC) == 0) {
+ /* Incremental mode. */
+ source_fp = VSTREAM_IN;
+ vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END);
+ } else {
+ /* Create database. */
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ msg_fatal("can't create maps via the proxy service");
+ dict_flags |= DICT_FLAG_BULK_UPDATE;
+ if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", path_name);
+ }
+ if (fstat(vstream_fileno(source_fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path_name);
+
+ /*
+ * Turn off group/other read permissions as indicated in the source file.
+ */
+ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
+ saved_mask = umask(022 | (~st.st_mode & 077));
+
+ /*
+ * If running as root, run as the owner of the source file, so that the
+ * result shows proper ownership, and so that a bug in postmap does not
+ * allow privilege escalation.
+ */
+ if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0
+ && (st.st_uid != geteuid() || st.st_gid != getegid()))
+ set_eugid(st.st_uid, st.st_gid);
+
+ /*
+ * Open the database, optionally create it when it does not exist,
+ * optionally truncate it when it does exist, and lock out any
+ * spectators.
+ */
+ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags);
+
+ /*
+ * And restore the umask, in case it matters.
+ */
+ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
+ umask(saved_mask);
+
+ /*
+ * Trap "exceptions" so that we can restart a bulk-mode update after a
+ * recoverable error.
+ */
+ for (;;) {
+ if (dict_isjmp(mkmap->dict) != 0
+ && dict_setjmp(mkmap->dict) != 0
+ && vstream_fseek(source_fp, SEEK_SET, 0) < 0)
+ msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp));
+
+ /*
+ * Add records to the database. XXX This duplicates the parser in
+ * dict_thash.c.
+ */
+ last_line = 0;
+ while (readllines(line_buffer, source_fp, &last_line, &lineno)) {
+ int in_quotes = 0;
+
+ /*
+ * First some UTF-8 checks sans casefolding.
+ */
+ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
+ && !allascii(STR(line_buffer))
+ && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
+ msg_warn("%s, line %d: non-UTF-8 input \"%s\""
+ " -- ignoring this line",
+ VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
+ continue;
+ }
+
+ /*
+ * Terminate the key on the first unquoted whitespace character,
+ * then trim leading and trailing whitespace from the value.
+ */
+ for (value = STR(line_buffer); *value; value++) {
+ if (*value == '\\') {
+ if (*++value == 0)
+ break;
+ } else if (ISSPACE(*value)) {
+ if (!in_quotes)
+ break;
+ } else if (*value == '"') {
+ in_quotes = !in_quotes;
+ }
+ }
+ if (in_quotes) {
+ msg_warn("%s, line %d: unbalanced '\"' in '%s'"
+ " -- ignoring this line",
+ VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
+ continue;
+ }
+ if (*value)
+ *value++ = 0;
+ while (ISSPACE(*value))
+ value++;
+ trimblanks(value, 0)[0] = 0;
+
+ /*
+ * Leave the key in quoted form, because 1) postmap cannot assume
+ * that a string without @ contains an email address localpart,
+ * and 2) an address localpart may require quoting even when the
+ * quoted form contains no backslash or ".
+ */
+ key = STR(line_buffer);
+
+ /*
+ * Enforce the "key whitespace value" format. Disallow missing
+ * keys or missing values.
+ */
+ if (*key == 0 || *value == 0) {
+ msg_warn("%s, line %d: expected format: key whitespace value",
+ VSTREAM_PATH(source_fp), lineno);
+ continue;
+ }
+ if (key[strlen(key) - 1] == ':')
+ msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?",
+ VSTREAM_PATH(source_fp), lineno);
+
+ /*
+ * Optionally treat the vale as a filename, and replace the value
+ * with the BASE64-encoded content of the named file.
+ */
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(mkmap->dict, value)) == 0) {
+ err = dict_file_get_error(mkmap->dict);
+ msg_warn("%s, line %d: %s: skipping this entry",
+ VSTREAM_PATH(source_fp), lineno, err);
+ myfree(err);
+ continue;
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Store the value under a (possibly case-insensitive) key, as
+ * specified with open_flags.
+ */
+ mkmap_append(mkmap, key, value);
+ if (mkmap->dict->error)
+ msg_fatal("table %s:%s: write error: %m",
+ mkmap->dict->type, mkmap->dict->name);
+ }
+ break;
+ }
+
+ /*
+ * Close the mapping database, and release the lock.
+ */
+ mkmap_close(mkmap);
+
+ /*
+ * Cleanup. We're about to terminate, but it is a good sanity check.
+ */
+ vstring_free(line_buffer);
+ if (source_fp != VSTREAM_IN)
+ vstream_fclose(source_fp);
+}
+
+/* postmap_body - MIME engine body call-back routine */
+
+static void postmap_body(void *ptr, int unused_rec_type,
+ const char *keybuf,
+ ssize_t unused_len,
+ off_t unused_offset)
+{
+ POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr;
+ DICT **dicts = state->dicts;
+ char **maps = state->maps;
+ int map_count = state->map_count;
+ int dict_flags = state->dict_flags;
+ const char *map_name;
+ const char *value;
+ int n;
+
+ for (n = 0; n < map_count; n++) {
+ if (dicts[n] == 0)
+ dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ?
+ dict_open3(maps[n], map_name, O_RDONLY, dict_flags) :
+ dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags));
+ if ((value = dict_get(dicts[n], keybuf)) != 0) {
+ if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ dicts[n]->type, dicts[n]->name, keybuf);
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ dicts[n]->type, dicts[n]->name);
+ }
+ vstream_printf("%s %s\n", keybuf, value);
+ state->found = 1;
+ break;
+ }
+ if (dicts[n]->error)
+ msg_fatal("table %s:%s: query error: %m",
+ dicts[n]->type, dicts[n]->name);
+ }
+}
+
+/* postmap_header - MIME engine header call-back routine */
+
+static void postmap_header(void *ptr, int unused_header_class,
+ const HEADER_OPTS *unused_header_info,
+ VSTRING *header_buf,
+ off_t offset)
+{
+
+ /*
+ * Don't re-invent an already working wheel.
+ */
+ postmap_body(ptr, 0, STR(header_buf), LEN(header_buf), offset);
+}
+
+/* postmap_head_end - MIME engine end-of-header call-back routine */
+
+static void postmap_head_end(void *ptr)
+{
+ POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr;
+
+ /*
+ * Don't process the message body when we only examine primary headers.
+ */
+ state->header_done = 1;
+}
+
+/* postmap_queries - apply multiple requests from stdin */
+
+static int postmap_queries(VSTREAM *in, char **maps, const int map_count,
+ const int postmap_flags,
+ const int dict_flags)
+{
+ int found = 0;
+ VSTRING *keybuf = vstring_alloc(100);
+ DICT **dicts;
+ const char *map_name;
+ const char *value;
+ int n;
+
+ /*
+ * Sanity check.
+ */
+ if (map_count <= 0)
+ msg_panic("postmap_queries: bad map count");
+
+ /*
+ * Prepare to open maps lazily.
+ */
+ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
+ for (n = 0; n < map_count; n++)
+ dicts[n] = 0;
+
+ /*
+ * Perform all queries. Open maps on the fly, to avoid opening unnecessary
+ * maps.
+ */
+ if ((postmap_flags & POSTMAP_FLAG_HB_KEY) == 0) {
+ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) {
+ for (n = 0; n < map_count; n++) {
+ if (dicts[n] == 0)
+ dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ?
+ dict_open3(maps[n], map_name, O_RDONLY, dict_flags) :
+ dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags));
+ value = ((dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ?
+ dict_file_lookup : dicts[n]->lookup)
+ (dicts[n], STR(keybuf));
+ if (value != 0) {
+ if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ dicts[n]->type, dicts[n]->name, STR(keybuf));
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ dicts[n]->type, dicts[n]->name);
+ }
+ vstream_printf("%s %s\n", STR(keybuf), value);
+ found = 1;
+ break;
+ }
+ switch (dicts[n]->error) {
+ case 0:
+ break;
+ case DICT_ERR_CONFIG:
+ msg_fatal("table %s:%s: query error",
+ dicts[n]->type, dicts[n]->name);
+ default:
+ msg_fatal("table %s:%s: query error: %m",
+ dicts[n]->type, dicts[n]->name);
+ }
+ }
+ }
+ } else {
+ POSTMAP_KEY_STATE key_state;
+ MIME_STATE *mime_state;
+ int mime_errs = 0;
+
+ /*
+ * Bundle up the request and instantiate a MIME parsing engine.
+ */
+ key_state.dicts = dicts;
+ key_state.maps = maps;
+ key_state.map_count = map_count;
+ key_state.dict_flags = dict_flags;
+ key_state.header_done = 0;
+ key_state.found = 0;
+ mime_state =
+ mime_state_alloc((postmap_flags & POSTMAP_FLAG_MIME_KEY) ?
+ 0 : MIME_OPT_DISABLE_MIME,
+ (postmap_flags & POSTMAP_FLAG_HEADER_KEY) ?
+ postmap_header : (MIME_STATE_HEAD_OUT) 0,
+ (postmap_flags & POSTMAP_FLAG_FULL_KEY) ?
+ (MIME_STATE_ANY_END) 0 : postmap_head_end,
+ (postmap_flags & POSTMAP_FLAG_BODY_KEY) ?
+ postmap_body : (MIME_STATE_BODY_OUT) 0,
+ (MIME_STATE_ANY_END) 0,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) &key_state);
+
+ /*
+ * Process the input message.
+ */
+ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF
+ && key_state.header_done == 0 && mime_errs == 0)
+ mime_errs = mime_state_update(mime_state, REC_TYPE_NORM,
+ STR(keybuf), LEN(keybuf));
+
+ /*
+ * Flush the MIME engine output buffer and tidy up loose ends.
+ */
+ if (mime_errs == 0)
+ mime_errs = mime_state_update(mime_state, REC_TYPE_END, "", 0);
+ if (mime_errs)
+ msg_fatal("message format error: %s",
+ mime_state_detail(mime_errs)->text);
+ mime_state_free(mime_state);
+ found = key_state.found;
+ }
+
+ if (found)
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Cleanup.
+ */
+ for (n = 0; n < map_count; n++)
+ if (dicts[n])
+ dict_close(dicts[n]);
+ myfree((void *) dicts);
+ vstring_free(keybuf);
+
+ return (found);
+}
+
+/* postmap_query - query a map and print the result to stdout */
+
+static int postmap_query(const char *map_type, const char *map_name,
+ const char *key, int dict_flags)
+{
+ DICT *dict;
+ const char *value;
+
+ dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
+ value = ((dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ?
+ dict_file_lookup : dict->lookup) (dict, key);
+ if (value != 0) {
+ if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ map_type, map_name, key);
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ map_type, map_name);
+ }
+ vstream_printf("%s\n", value);
+ }
+ switch (dict->error) {
+ case 0:
+ break;
+ case DICT_ERR_CONFIG:
+ msg_fatal("table %s:%s: query error",
+ dict->type, dict->name);
+ default:
+ msg_fatal("table %s:%s: query error: %m",
+ dict->type, dict->name);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ dict_close(dict);
+ return (value != 0);
+}
+
+/* postmap_deletes - apply multiple requests from stdin */
+
+static int postmap_deletes(VSTREAM *in, char **maps, const int map_count,
+ int dict_flags)
+{
+ int found = 0;
+ VSTRING *keybuf = vstring_alloc(100);
+ DICT **dicts;
+ const char *map_name;
+ int n;
+ int open_flags;
+
+ /*
+ * Sanity check.
+ */
+ if (map_count <= 0)
+ msg_panic("postmap_deletes: bad map count");
+
+ /*
+ * Open maps ahead of time.
+ */
+ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
+ for (n = 0; n < map_count; n++) {
+ map_name = split_at(maps[n], ':');
+ if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0)
+ open_flags = O_RDWR | O_CREAT; /* XXX */
+ else
+ open_flags = O_RDWR;
+ dicts[n] = (map_name != 0 ?
+ dict_open3(maps[n], map_name, open_flags, dict_flags) :
+ dict_open3(var_db_type, maps[n], open_flags, dict_flags));
+ }
+
+ /*
+ * Perform all requests.
+ */
+ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) {
+ for (n = 0; n < map_count; n++) {
+ found |= (dict_del(dicts[n], STR(keybuf)) == 0);
+ if (dicts[n]->error)
+ msg_fatal("table %s:%s: delete error: %m",
+ dicts[n]->type, dicts[n]->name);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ for (n = 0; n < map_count; n++)
+ if (dicts[n])
+ dict_close(dicts[n]);
+ myfree((void *) dicts);
+ vstring_free(keybuf);
+
+ return (found);
+}
+
+/* postmap_delete - delete a (key, value) pair from a map */
+
+static int postmap_delete(const char *map_type, const char *map_name,
+ const char *key, int dict_flags)
+{
+ DICT *dict;
+ int status;
+ int open_flags;
+
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ open_flags = O_RDWR | O_CREAT; /* XXX */
+ else
+ open_flags = O_RDWR;
+ dict = dict_open3(map_type, map_name, open_flags, dict_flags);
+ status = dict_del(dict, key);
+ if (dict->error)
+ msg_fatal("table %s:%s: delete error: %m", dict->type, dict->name);
+ dict_close(dict);
+ return (status == 0);
+}
+
+/* postmap_seq - print all map entries to stdout */
+
+static void postmap_seq(const char *map_type, const char *map_name,
+ int dict_flags)
+{
+ DICT *dict;
+ const char *key;
+ const char *value;
+ int func;
+
+ if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
+ msg_fatal("can't sequence maps via the proxy service");
+ dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
+ for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) {
+ if (dict_seq(dict, func, &key, &value) != 0)
+ break;
+ if (*key == 0) {
+ msg_warn("table %s:%s: empty lookup key value is not allowed",
+ map_type, map_name);
+ } else if (*value == 0) {
+ msg_warn("table %s:%s: key %s: empty string result is not allowed",
+ map_type, map_name, key);
+ msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
+ map_type, map_name);
+ }
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *unb64;
+ char *err;
+
+ if ((unb64 = dict_file_from_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("table %s:%s: key %s: %s",
+ dict->type, dict->name, key, err);
+ myfree(err);
+ /* dict->error = DICT_ERR_CONFIG; */
+ continue;
+ }
+ value = STR(unb64);
+ }
+ vstream_printf("%s %s\n", key, value);
+ }
+ if (dict->error)
+ msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name);
+ vstream_fflush(VSTREAM_OUT);
+ dict_close(dict);
+}
+
+/* usage - explain */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s [-bfFhimnNoprsuUvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
+ myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ char *path_name;
+ int ch;
+ int fd;
+ char *slash;
+ struct stat st;
+ int postmap_flags = POSTMAP_FLAG_AS_OWNER | POSTMAP_FLAG_SAVE_PERM;
+ int open_flags = O_RDWR | O_CREAT | O_TRUNC;
+ int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ char *query = 0;
+ char *delkey = 0;
+ int sequence = 0;
+ int found;
+ int force_utf8 = 0;
+ ARGV *import_env;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process environment options as early as we can. We are not set-uid,
+ * and we are supposed to be running in a controlled environment.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Initialize. Set up logging. Read the global configuration file after
+ * parsing command-line arguments.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "bc:d:fFhimnNopq:rsuUvw")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ break;
+ case 'N':
+ dict_flags |= DICT_FLAG_TRY1NULL;
+ dict_flags &= ~DICT_FLAG_TRY0NULL;
+ break;
+ case 'b':
+ postmap_flags |= POSTMAP_FLAG_BODY_KEY;
+ break;
+ case 'c':
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("out of memory");
+ break;
+ case 'd':
+ if (sequence || query || delkey)
+ msg_fatal("specify only one of -s -q or -d");
+ delkey = optarg;
+ break;
+ case 'f':
+ dict_flags &= ~DICT_FLAG_FOLD_FIX;
+ break;
+ case 'F':
+ dict_flags |= DICT_FLAG_SRC_RHS_IS_FILE;
+ break;
+ case 'h':
+ postmap_flags |= POSTMAP_FLAG_HEADER_KEY;
+ break;
+ case 'i':
+ open_flags &= ~O_TRUNC;
+ break;
+ case 'm':
+ postmap_flags |= POSTMAP_FLAG_MIME_KEY;
+ break;
+ case 'n':
+ dict_flags |= DICT_FLAG_TRY0NULL;
+ dict_flags &= ~DICT_FLAG_TRY1NULL;
+ break;
+ case 'o':
+ postmap_flags &= ~POSTMAP_FLAG_AS_OWNER;
+ break;
+ case 'p':
+ postmap_flags &= ~POSTMAP_FLAG_SAVE_PERM;
+ break;
+ case 'q':
+ if (sequence || query || delkey)
+ msg_fatal("specify only one of -s -q or -d");
+ query = optarg;
+ break;
+ case 'r':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE);
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ break;
+ case 's':
+ if (query || delkey)
+ msg_fatal("specify only one of -s or -q or -d");
+ sequence = 1;
+ break;
+ case 'u':
+ dict_flags &= ~DICT_FLAG_UTF8_REQUEST;
+ break;
+ case 'U':
+ force_utf8 = 1;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE);
+ dict_flags |= DICT_FLAG_DUP_IGNORE;
+ break;
+ }
+ }
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE);
+ mail_dict_init();
+ if ((query == 0 || strcmp(query, "-") != 0)
+ && (postmap_flags & POSTMAP_FLAG_ANY_KEY))
+ msg_fatal("specify -b -h or -m only with \"-q -\"");
+ if ((postmap_flags & POSTMAP_FLAG_ANY_KEY) != 0
+ && (postmap_flags & POSTMAP_FLAG_ANY_KEY)
+ == (postmap_flags & POSTMAP_FLAG_MIME_KEY))
+ msg_warn("ignoring -m option without -b or -h");
+ if ((postmap_flags & (POSTMAP_FLAG_ANY_KEY & ~POSTMAP_FLAG_MIME_KEY))
+ && force_utf8 == 0)
+ dict_flags &= ~DICT_FLAG_UTF8_MASK;
+
+ /*
+ * Use the map type specified by the user, or fall back to a default
+ * database type.
+ */
+ if (delkey) { /* remove entry */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ if (strcmp(delkey, "-") == 0)
+ exit(postmap_deletes(VSTREAM_IN, argv + optind, argc - optind,
+ dict_flags | DICT_FLAG_LOCK) == 0);
+ found = 0;
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ found |= postmap_delete(argv[optind], path_name, delkey,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ found |= postmap_delete(var_db_type, argv[optind], delkey,
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ optind++;
+ }
+ exit(found ? 0 : 1);
+ } else if (query) { /* query map(s) */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ if (strcmp(query, "-") == 0)
+ exit(postmap_queries(VSTREAM_IN, argv + optind, argc - optind,
+ postmap_flags, dict_flags | DICT_FLAG_LOCK) == 0);
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ found = postmap_query(argv[optind], path_name, query,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ found = postmap_query(var_db_type, argv[optind], query,
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ if (found)
+ exit(0);
+ optind++;
+ }
+ exit(1);
+ } else if (sequence) {
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ postmap_seq(argv[optind], path_name,
+ dict_flags | DICT_FLAG_LOCK);
+ } else {
+ postmap_seq(var_db_type, argv[optind],
+ dict_flags | DICT_FLAG_LOCK);
+ }
+ exit(0);
+ }
+ exit(1);
+ } else { /* create/update map(s) */
+ if (optind + 1 > argc)
+ usage(argv[0]);
+ while (optind < argc) {
+ if ((path_name = split_at(argv[optind], ':')) != 0) {
+ postmap(argv[optind], path_name, postmap_flags,
+ open_flags, dict_flags);
+ } else {
+ postmap(var_db_type, argv[optind], postmap_flags,
+ open_flags, dict_flags);
+ }
+ optind++;
+ }
+ exit(0);
+ }
+}
diff --git a/src/postmap/quote_test.in b/src/postmap/quote_test.in
new file mode 100644
index 0000000..fb507fc
--- /dev/null
+++ b/src/postmap/quote_test.in
@@ -0,0 +1,8 @@
+echo '"aa bb" cc' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo '"dd ee ff' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo 'gg\ hh ii' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo '"gg\"hh" ii' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo '"jj@kk" ll' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo 'mm@nn@oo pp' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+echo '@oo pp' | ${VALGRIND} ./postmap -i quote_test_map || exit 1
+${VALGRIND} ./postmap -s quote_test_map | LC_ALL=C sort
diff --git a/src/postmap/quote_test.ref b/src/postmap/quote_test.ref
new file mode 100644
index 0000000..1643244
--- /dev/null
+++ b/src/postmap/quote_test.ref
@@ -0,0 +1,7 @@
+postmap: warning: stdin, line 1: unbalanced '"' in '"dd ee ff' -- ignoring this line
+"aa bb" cc
+"gg\"hh" ii
+"jj@kk" ll
+@oo pp
+gg\ hh ii
+mm@nn@oo pp
diff --git a/src/postmulti/.indent.pro b/src/postmulti/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postmulti/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postmulti/Makefile.in b/src/postmulti/Makefile.in
new file mode 100644
index 0000000..f2e3680
--- /dev/null
+++ b/src/postmulti/Makefile.in
@@ -0,0 +1,87 @@
+SHELL = /bin/sh
+SRCS = postmulti.c
+OBJS = postmulti.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+FILES = Makefile $(SRCS) $(HDRS)
+INC_DIR = ../../include
+TESTPROG=
+PROG = postmulti
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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`
+
+shar:
+ @shar $(FILES)
+
+lint:
+ lint $(SRCS)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+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'
+postmulti.o: ../../include/argv.h
+postmulti.o: ../../include/check_arg.h
+postmulti.o: ../../include/clean_env.h
+postmulti.o: ../../include/htable.h
+postmulti.o: ../../include/mail_conf.h
+postmulti.o: ../../include/mail_params.h
+postmulti.o: ../../include/mail_parm_split.h
+postmulti.o: ../../include/mail_version.h
+postmulti.o: ../../include/maillog_client.h
+postmulti.o: ../../include/msg.h
+postmulti.o: ../../include/msg_vstream.h
+postmulti.o: ../../include/mymalloc.h
+postmulti.o: ../../include/name_code.h
+postmulti.o: ../../include/ring.h
+postmulti.o: ../../include/safe.h
+postmulti.o: ../../include/stringops.h
+postmulti.o: ../../include/sys_defs.h
+postmulti.o: ../../include/vbuf.h
+postmulti.o: ../../include/vstream.h
+postmulti.o: ../../include/vstring.h
+postmulti.o: ../../include/vstring_vstream.h
+postmulti.o: ../../include/warn_stat.h
+postmulti.o: postmulti.c
diff --git a/src/postmulti/postmulti.c b/src/postmulti/postmulti.c
new file mode 100644
index 0000000..5adcd27
--- /dev/null
+++ b/src/postmulti/postmulti.c
@@ -0,0 +1,1843 @@
+/*++
+/* NAME
+/* postmulti 1
+/* SUMMARY
+/* Postfix multi-instance manager
+/* SYNOPSIS
+/* .fi
+/* .ti -4
+/* \fBEnabling multi-instance management:\fR
+/*
+/* \fBpostmulti\fR \fB-e init\fR [\fB-v\fR]
+/*
+/* .ti -4
+/* \fBIterator mode:\fR
+/*
+/* \fBpostmulti\fR \fB-l\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR]
+/* [\fB-i \fIname\fR]
+/*
+/* \fBpostmulti\fR \fB-p\fR [\fB-av\fR] [\fB-g \fIgroup\fR]
+/* [\fB-i \fIname\fR] \fIpostfix-command...\fR
+/*
+/* \fBpostmulti\fR \fB-x\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR]
+/* [\fB-i \fIname\fR] \fIunix-command...\fR
+/*
+/* .ti -4
+/* \fBLife-cycle management:\fR
+/*
+/* \fBpostmulti\fR \fB-e create\fR [\fB-av\fR]
+/* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR]
+/* [\fB-I \fIname\fR] [\fIparam=value\fR ...]
+/*
+/* \fBpostmulti\fR \fB-e import\fR [\fB-av\fR]
+/* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR]
+/* [\fB-I \fIname\fR] [\fBconfig_directory=\fI/path\fR]
+/*
+/* \fBpostmulti\fR \fB-e destroy\fR [\fB-v\fR] \fB-i \fIname\fR
+/*
+/* \fBpostmulti\fR \fB-e deport\fR [\fB-v\fR] \fB-i \fIname\fR
+/*
+/* \fBpostmulti\fR \fB-e enable\fR [\fB-v\fR] \fB-i \fIname\fR
+/*
+/* \fBpostmulti\fR \fB-e disable\fR [\fB-v\fR] \fB-i \fIname\fR
+/*
+/* \fBpostmulti\fR \fB-e assign\fR [\fB-v\fR] \fB-i \fIname\fR
+/* [\fB-I \fIname\fR] [-G \fIgroup\fR]
+/* DESCRIPTION
+/* The \fBpostmulti\fR(1) command allows a Postfix administrator
+/* to manage multiple Postfix instances on a single host.
+/*
+/* \fBpostmulti\fR(1) implements two fundamental modes of
+/* operation. In \fBiterator\fR mode, it executes the same
+/* command for multiple Postfix instances. In \fBlife-cycle
+/* management\fR mode, it adds or deletes one instance, or
+/* changes the multi-instance status of one instance.
+/*
+/* Each mode of operation has its own command syntax. For this
+/* reason, each mode is documented in separate sections below.
+/* BACKGROUND
+/* .ad
+/* .fi
+/* A multi-instance configuration consists of one primary
+/* Postfix instance, and one or more secondary instances whose
+/* configuration directory pathnames are recorded in the primary
+/* instance's main.cf file. Postfix instances share program
+/* files and documentation, but have their own configuration,
+/* queue and data directories.
+/*
+/* Currently, only the default Postfix instance can be used
+/* as primary instance in a multi-instance configuration. The
+/* \fBpostmulti\fR(1) command does not currently support a \fB-c\fR
+/* option to select an alternative primary instance, and exits
+/* with a fatal error if the \fBMAIL_CONFIG\fR environment
+/* variable is set to a non-default configuration directory.
+/*
+/* See the MULTI_INSTANCE_README tutorial for a more detailed
+/* discussion of multi-instance management with \fBpostmulti\fR(1).
+/* ITERATOR MODE
+/* .ad
+/* .fi
+/* In iterator mode, \fBpostmulti\fR performs the same operation
+/* on all Postfix instances in turn.
+/*
+/* If multi-instance support is not enabled, the requested
+/* command is performed just for the primary instance.
+/* .PP
+/* Iterator mode implements the following command options:
+/* .SH "Instance selection"
+/* .IP \fB-a\fR
+/* Perform the operation on all instances. This is the default.
+/* .IP "\fB-g \fIgroup\fR"
+/* Perform the operation only for members of the named \fIgroup\fR.
+/* .IP "\fB-i \fIname\fR"
+/* Perform the operation only for the instance with the specified
+/* \fIname\fR. You can specify either the instance name
+/* or the absolute pathname of the instance's configuration
+/* directory. Specify "-" to select the primary Postfix instance.
+/* .IP \fB-R\fR
+/* Reverse the iteration order. This may be appropriate when
+/* updating a multi-instance system, where "sink" instances
+/* are started before "source" instances.
+/* .sp
+/* This option cannot be used with \fB-p\fR.
+/* .SH "List mode"
+/* .IP \fB-l\fR
+/* List Postfix instances with their instance name, instance
+/* group name, enable/disable status and configuration directory.
+/* .SH "Postfix-wrapper mode"
+/* .IP "\fB-p \fIpostfix-command\fR"
+/* Invoke \fBpostfix(1)\fR to execute \fIpostfix-command\fR.
+/* This option implements the \fBpostfix-wrapper\fR(5) interface.
+/* .RS
+/* .IP \(bu
+/* With "start"-like commands, "postfix check" is executed for
+/* instances that are not enabled. The full list of commands
+/* is specified with the postmulti_start_commands parameter.
+/* .IP \(bu
+/* With "stop"-like commands, the iteration order is reversed,
+/* and disabled instances are skipped. The full list of commands
+/* is specified with the postmulti_stop_commands parameter.
+/* .IP \(bu
+/* With "reload" and other commands that require a started
+/* instance, disabled instances are skipped. The full list of
+/* commands is specified with the postmulti_control_commands
+/* parameter.
+/* .IP \(bu
+/* With "status" and other commands that don't require a started
+/* instance, the command is executed for all instances.
+/* .RE
+/* .IP
+/* The \fB-p\fR option can also be used interactively to
+/* start/stop/etc. a named instance or instance group. For
+/* example, to start just the instances in the group "msa",
+/* invoke \fBpostmulti\fR(1) as follows:
+/* .RS
+/* .IP
+/* # postmulti -g msa -p start
+/* .RE
+/* .SH "Command mode"
+/* .IP "\fB-x \fIunix-command\fR"
+/* Execute the specified \fIunix-command\fR for all Postfix instances.
+/* The command runs with appropriate environment settings for
+/* MAIL_CONFIG, command_directory, daemon_directory,
+/* config_directory, queue_directory, data_directory,
+/* multi_instance_name, multi_instance_group and
+/* multi_instance_enable.
+/* .SH "Other options"
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple
+/* \fB-v\fR options make the software increasingly verbose.
+/* LIFE-CYCLE MANAGEMENT MODE
+/* .ad
+/* .fi
+/* With the \fB-e\fR option \fBpostmulti\fR(1) can be used to
+/* add or delete a Postfix instance, and to manage the
+/* multi-instance status of an existing instance.
+/* .PP
+/* The following options are implemented:
+/* .SH "Existing instance selection"
+/* .IP \fB-a\fR
+/* When creating or importing an instance, place the new
+/* instance at the front of the secondary instance list.
+/* .IP "\fB-g \fIgroup\fR"
+/* When creating or importing an instance, place the new
+/* instance before the first secondary instance that is a
+/* member of the specified group.
+/* .IP "\fB-i \fIname\fR"
+/* When creating or importing an instance, place the new
+/* instance before the matching secondary instance.
+/* .sp
+/* With other life-cycle operations, apply the operation to
+/* the named existing instance. Specify "-" to select the
+/* primary Postfix instance.
+/* .SH "New or existing instance name assignment"
+/* .IP "\fB-I \fIname\fR"
+/* Assign the specified instance \fIname\fR to an existing
+/* instance, newly-created instance, or imported instance.
+/* Instance
+/* names other than "-" (which makes the instance "nameless")
+/* must start with "postfix-". This restriction reduces the
+/* likelihood of name collisions with system files.
+/* .IP "\fB-G \fIgroup\fR"
+/* Assign the specified \fIgroup\fR name to an existing instance
+/* or to a newly created or imported instance.
+/* .SH "Instance creation/deletion/status change"
+/* .IP "\fB-e \fIaction\fR"
+/* "Edit" managed instances. The following actions are supported:
+/* .RS
+/* .IP \fBinit\fR
+/* This command is required before \fBpostmulti\fR(1) can be
+/* used to manage Postfix instances. The "postmulti -e init"
+/* command updates the primary instance's main.cf file by
+/* setting:
+/* .RS
+/* .IP
+/* .nf
+/* multi_instance_wrapper =
+/* ${command_directory}/postmulti -p --
+/* multi_instance_enable = yes
+/* .fi
+/* .RE
+/* .IP
+/* You can set these by other means if you prefer.
+/* .IP \fBcreate\fR
+/* Create a new Postfix instance and add it to the
+/* multi_instance_directories parameter of the primary instance.
+/* The "\fB-I \fIname\fR" option is recommended to give the
+/* instance a short name that is used to construct default
+/* values for the private directories of the new instance. The
+/* "\fB-G \fIgroup\fR" option may be specified to assign the
+/* instance to a group, otherwise, the new instance is not a
+/* member of any group.
+/* .sp
+/* The new instance main.cf is the stock main.cf with the
+/* parameters that specify the locations of shared files cloned
+/* from the primary instance. For "nameless" instances, you
+/* should manually adjust "syslog_name" to yield a unique
+/* "logtag" starting with "postfix-" that will uniquely identify
+/* the instance in the mail logs. It is simpler to assign the
+/* instance a short name with the "\fB-I \fIname\fR" option.
+/* .sp
+/* Optional "name=value" arguments specify the instance
+/* config_directory, queue_directory and data_directory.
+/* For example:
+/* .RS
+/* .IP
+/* .nf
+/* # postmulti -I postfix-mumble \e
+/* -G mygroup -e create \e
+/* config_directory=/my/config/dir \e
+/* queue_directory=/my/queue/dir \e
+/* data_directory=/my/data/dir
+/* .fi
+/* .RE
+/* .IP
+/* If any of these pathnames is not supplied, the program
+/* attempts to generate the missing pathname(s) by taking the
+/* corresponding primary instance pathname, and replacing the
+/* last pathname component by the value of the \fB-I\fR option.
+/* .sp
+/* If the instance configuration directory already exists, and
+/* contains both a main.cf and master.cf file, \fBcreate\fR
+/* will "import" the instance as-is. For existing instances,
+/* \fBcreate\fR and \fBimport\fR are identical.
+/* .IP \fBimport\fR
+/* Import an existing instance into the list of instances
+/* managed by the \fBpostmulti\fR(1) multi-instance manager.
+/* This adds the instance to the multi_instance_directories
+/* list of the primary instance. If the "\fB-I \fIname\fR"
+/* option is provided it specifies the new name for the instance
+/* and is used to define a default location for the instance
+/* configuration directory (as with \fBcreate\fR above). The
+/* "\fB-G \fIgroup\fR" option may be used to assign the instance
+/* to a group. Add a "\fBconfig_directory=\fI/path\fR" argument
+/* to override a default pathname based on "\fB-I \fIname\fR".
+/* .IP \fBdestroy\fR
+/* Destroy a secondary Postfix instance. To be a candidate for
+/* destruction an instance must be disabled, stopped and its
+/* queue must not contain any messages. Attempts to destroy
+/* the primary Postfix instance trigger a fatal error, without
+/* destroying the instance.
+/* .sp
+/* The instance is removed from the primary instance main.cf
+/* file's alternate_config_directories parameter and its data,
+/* queue and configuration directories are cleaned of files
+/* and directories created by the Postfix system. The main.cf
+/* and master.cf files are removed from the configuration
+/* directory even if they have been modified since initial
+/* creation. Finally, the instance is "deported" from the list
+/* of managed instances.
+/* .sp
+/* If other files are present in instance private directories,
+/* the directories may not be fully removed, a warning is
+/* logged to alert the administrator. It is expected that an
+/* instance built using "fresh" directories via the \fBcreate\fR
+/* action will be fully removed by the \fBdestroy\fR action
+/* (if first disabled). If the instance configuration and queue
+/* directories are populated with additional files (access and
+/* rewriting tables, chroot jail content, etc.) the instance
+/* directories will not be fully removed.
+/* .sp
+/* The \fBdestroy\fR action triggers potentially dangerous
+/* file removal operations. Make sure the instance's data,
+/* queue and configuration directories are set correctly and
+/* do not contain any valuable files.
+/* .IP \fBdeport\fR
+/* Deport a secondary instance from the list of managed
+/* instances. This deletes the instance configuration directory
+/* from the primary instance's multi_instance_directories list,
+/* but does not remove any files or directories.
+/* .IP \fBassign\fR
+/* Assign a new instance name or a new group name to the
+/* selected instance. Use "\fB-G -\fR" to specify "no group"
+/* and "\fB-I -\fR" to specify "no name". If you choose to
+/* make an instance "nameless", set a suitable syslog_name in
+/* the corresponding main.cf file.
+/* .IP \fBenable\fR
+/* Mark the selected instance as enabled. This just sets the
+/* multi_instance_enable parameter to "yes" in the instance's
+/* main.cf file.
+/* .IP \fBdisable\fR
+/* Mark the selected instance as disabled. This means that
+/* the instance will not be started etc. with "postfix start",
+/* "postmulti -p start" and so on. The instance can still be
+/* started etc. with "postfix -c config-directory start".
+/* .SH "Other options"
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple
+/* \fB-v\fR options make the software increasingly verbose.
+/* .RE
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* The \fBpostmulti\fR(1) command exports the following environment
+/* variables before executing the requested \fIcommand\fR for a given
+/* instance:
+/* .IP \fBMAIL_VERBOSE\fR
+/* This is set when the -v command-line option is present.
+/* .IP \fBMAIL_CONFIG\fR
+/* The location of the configuration directory of the instance.
+/* CONFIGURATION PARAMETERS
+/* .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_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix support programs and daemon programs.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBmulti_instance_directories (empty)\fR"
+/* An optional list of non-default Postfix configuration directories;
+/* these directories belong to additional Postfix instances that share
+/* the Postfix executable files and documentation with the default
+/* Postfix instance, and that are started, stopped, etc., together
+/* with the default Postfix instance.
+/* .IP "\fBmulti_instance_group (empty)\fR"
+/* The optional instance group name of this Postfix instance.
+/* .IP "\fBmulti_instance_name (empty)\fR"
+/* The optional instance name of this Postfix instance.
+/* .IP "\fBmulti_instance_enable (no)\fR"
+/* Allow this Postfix instance to be started, stopped, etc., by a
+/* multi-instance manager.
+/* .IP "\fBpostmulti_start_commands (start)\fR"
+/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats
+/* as "start" commands.
+/* .IP "\fBpostmulti_stop_commands (see 'postconf -d' output)\fR"
+/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats
+/* as "stop" commands.
+/* .IP "\fBpostmulti_control_commands (reload flush)\fR"
+/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager
+/* treats as "control" commands, that operate on running instances.
+/* .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 3.0 and later:
+/* .IP "\fBmeta_directory (see 'postconf -d' output)\fR"
+/* The location of non-executable files that are shared among
+/* multiple Postfix instances, such as postfix-files, dynamicmaps.cf,
+/* and the multi-instance template files main.cf.proto and master.cf.proto.
+/* .IP "\fBshlib_directory (see 'postconf -d' output)\fR"
+/* The location of Postfix dynamically-linked libraries
+/* (libpostfix-*.so), and the default location of Postfix database
+/* plugins (postfix-*.so) that have a relative pathname in the
+/* dynamicmaps.cf file.
+/* FILES
+/* $meta_directory/main.cf.proto, stock configuration file
+/* $meta_directory/master.cf.proto, stock configuration file
+/* $daemon_directory/postmulti-script, life-cycle helper program
+/* SEE ALSO
+/* postfix(1), Postfix control program
+/* postfix-wrapper(5), Postfix multi-instance API
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+/* html_directory\fR" to locate this information.
+/* .nf
+/* .na
+/* MULTI_INSTANCE_README, Postfix multi-instance management
+/* HISTORY
+/* .ad
+/* .fi
+/* The \fBpostmulti\fR(1) command was introduced with Postfix
+/* version 2.6.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* 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/stat.h>
+#include <sys/wait.h>
+#include <vstream.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <clean_env.h>
+#include <argv.h>
+#include <safe.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <name_code.h>
+#include <ring.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_version.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+ /*
+ * Configuration parameters, specific to postmulti(1).
+ */
+char *var_multi_start_cmds;
+char *var_multi_stop_cmds;
+char *var_multi_cntrl_cmds;
+
+ /*
+ * Shared directory pathnames.
+ */
+typedef struct {
+ const char *param_name;
+ char **param_value;
+} SHARED_PATH;
+
+static SHARED_PATH shared_dir_table[] = {
+ VAR_COMMAND_DIR, &var_command_dir,
+ VAR_DAEMON_DIR, &var_daemon_dir,
+ VAR_META_DIR, &var_meta_dir,
+ VAR_SHLIB_DIR, &var_shlib_dir,
+ 0,
+};
+
+ /*
+ * Actions.
+ */
+#define ITER_CMD_POSTFIX (1<<0) /* postfix(1) iterator mode */
+#define ITER_CMD_LIST (1<<1) /* listing iterator mode */
+#define ITER_CMD_GENERIC (1<<2) /* generic command iterator mode */
+
+#define ITER_CMD_MASK_ALL \
+ (ITER_CMD_POSTFIX | ITER_CMD_LIST | ITER_CMD_GENERIC)
+
+#define EDIT_CMD_CREATE (1<<4) /* create new instance */
+#define EDIT_CMD_IMPORT (1<<5) /* import existing instance */
+#define EDIT_CMD_DESTROY (1<<6) /* destroy instance */
+#define EDIT_CMD_DEPORT (1<<7) /* export instance */
+#define EDIT_CMD_ENABLE (1<<8) /* enable start/stop */
+#define EDIT_CMD_DISABLE (1<<9) /* disable start/stop */
+#define EDIT_CMD_ASSIGN (1<<10) /* assign name/group */
+#define EDIT_CMD_INIT (1<<11) /* hook into main.cf */
+
+#define EDIT_CMD_MASK_ADD (EDIT_CMD_CREATE | EDIT_CMD_IMPORT)
+#define EDIT_CMD_MASK_DEL (EDIT_CMD_DESTROY | EDIT_CMD_DEPORT)
+#define EDIT_CMD_MASK_ASSIGN (EDIT_CMD_MASK_ADD | EDIT_CMD_ASSIGN)
+#define EDIT_CMD_MASK_ENB (EDIT_CMD_ENABLE | EDIT_CMD_DISABLE)
+#define EDIT_CMD_MASK_ALL \
+ (EDIT_CMD_MASK_ASSIGN | EDIT_CMD_MASK_DEL | EDIT_CMD_MASK_ENB | \
+ EDIT_CMD_INIT)
+
+ /*
+ * Edit command to number mapping, and vice versa.
+ */
+static NAME_CODE edit_command_table[] = {
+ "create", EDIT_CMD_CREATE,
+ "import", EDIT_CMD_IMPORT,
+ "destroy", EDIT_CMD_DESTROY,
+ "deport", EDIT_CMD_DEPORT,
+ "enable", EDIT_CMD_ENABLE,
+ "disable", EDIT_CMD_DISABLE,
+ "assign", EDIT_CMD_ASSIGN,
+ "init", EDIT_CMD_INIT,
+ 0, -1,
+};
+
+#define EDIT_CMD_CODE(str) \
+ name_code(edit_command_table, NAME_CODE_FLAG_STRICT_CASE, (str))
+#define EDIT_CMD_STR(code) str_name_code(edit_command_table, (code))
+
+ /*
+ * Mandatory prefix for non-empty instance names.
+ */
+#ifndef NAME_PREFIX
+#define NAME_PREFIX "postfix-"
+#endif
+#define HAS_NAME_PREFIX(name) \
+ (strncmp((name), NAME_PREFIX, sizeof(NAME_PREFIX)-1) == 0)
+#define NEED_NAME_PREFIX(name) \
+ ((name) != 0 && strcmp((name), "-") != 0 && !HAS_NAME_PREFIX(name))
+#define NAME_SUFFIX(name) ((name) + sizeof(NAME_PREFIX) - 1)
+
+ /*
+ * In-core instance structure. Only private information is kept here.
+ */
+typedef struct instance {
+ RING ring; /* linkage. */
+ char *config_dir; /* private */
+ char *queue_dir; /* private */
+ char *data_dir; /* private */
+ char *name; /* null or name */
+ char *gname; /* null or group */
+ int enabled; /* start/stop enable */
+ int primary; /* special */
+} INSTANCE;
+
+ /*
+ * Managed instance list (edit mode and iterator mode).
+ */
+static RING instance_hd[1]; /* instance list head */
+
+#define RING_TO_INSTANCE(ring_ptr) RING_TO_APPL(ring_ptr, INSTANCE, ring)
+#define RING_PTR_OF(x) (&((x)->ring))
+
+#define FOREACH_INSTANCE(entry) \
+ for ((entry) = instance_hd; \
+ ((entry) = ring_succ(entry)) != instance_hd;)
+
+#define FOREACH_SECONDARY_INSTANCE(entry) \
+ for ((entry) = ring_succ(instance_hd); \
+ ((entry) = ring_succ(entry)) != instance_hd;)
+
+#define NEXT_ITERATOR_INSTANCE(flags, entry) \
+ (((flags) & ITER_FLAG_REVERSE) ? ring_pred(entry) : ring_succ(entry))
+
+#define FOREACH_ITERATOR_INSTANCE(flags, entry) \
+ for ((entry) = instance_hd; \
+ ((entry) = NEXT_ITERATOR_INSTANCE(flags, (entry))) != instance_hd;)
+
+ /*
+ * Instance selection. One can either select all instances, select by
+ * instance name, or select by instance group.
+ */
+typedef struct {
+ int type; /* see below */
+ char *name; /* undefined or name */
+} INST_SELECTION;
+
+#define INST_SEL_NONE 0 /* default: no selection */
+#define INST_SEL_ALL 1 /* select all instances */
+#define INST_SEL_NAME 2 /* select instance name */
+#define INST_SEL_GROUP 3 /* select instance group */
+
+ /*
+ * Instance name assignment. Each instance may be assigned an instance name
+ * (this must be globally unique within a multi-instance cluster) or an
+ * instance group name (this is intended to be shared). Externally, empty
+ * names may be represented as "-". Internally, we use "" only, to simplify
+ * the code.
+ */
+typedef struct {
+ char *name; /* null or assigned instance name */
+ char *gname; /* null or assigned group name */
+} NAME_ASSIGNMENT;
+
+ /*
+ * Iterator controls for non-edit commands. One can reverse the iteration
+ * order, or give special treatment to disabled instances.
+ */
+#define ITER_FLAG_DEFAULT 0 /* default setting */
+#define ITER_FLAG_REVERSE (1<<0) /* reverse iteration order */
+#define ITER_FLAG_CHECK_DISABLED (1<<1) /* check disabled instances */
+#define ITER_FLAG_SKIP_DISABLED (1<<2) /* skip disabled instances */
+
+ /*
+ * Environment export controls for edit commands. postmulti(1) exports only
+ * things that need to be updated.
+ */
+#define EXP_FLAG_MULTI_DIRS (1<<0) /* export multi_instance_directories */
+#define EXP_FLAG_MULTI_NAME (1<<1) /* export multi_instance_name */
+#define EXP_FLAG_MULTI_GROUP (1<<2) /* export multi_instance_group */
+
+ /*
+ * To detect conflicts, each instance name and each shared or private
+ * pathname is registered in one place, with its owner. Everyone must
+ * register their claims when they join, and will be rejected in case of
+ * conflict.
+ *
+ * Each claim value involves a parameter value (either a directory name or an
+ * instance name). Each claim owner is the config_directory pathname plus
+ * the parameter name.
+ *
+ * XXX: No multi.cf lock file, so this is not race-free.
+ */
+static HTABLE *claim_table;
+
+#define IS_CLAIMED_BY(name) \
+ (claim_table ? htable_find(claim_table, (name)) : 0)
+
+ /*
+ * Forward references.
+ */
+static int iterate_command(int, int, char **, INST_SELECTION *);
+static int match_instance_selection(INSTANCE *, INST_SELECTION *);
+
+ /*
+ * Convenience.
+ */
+#define INSTANCE_NAME(i) ((i)->name ? (i)->name : (i)->config_dir)
+#define STR(buf) vstring_str(buf)
+
+/* register_claim - register claim or bust */
+
+static void register_claim(const char *instance_path, const char *param_name,
+ const char *param_value)
+{
+ const char *myname = "register_claim";
+ char *requestor;
+ const char *owner;
+
+ /*
+ * Sanity checks.
+ */
+ if (instance_path == 0 || *instance_path == 0)
+ msg_panic("%s: no or empty instance pathname", myname);
+ if (param_name == 0 || *param_name == 0)
+ msg_panic("%s: no or empty parameter name", myname);
+ if (param_value == 0)
+ msg_panic("%s: no parameter value", myname);
+
+ /*
+ * Make a claim or report a conflict.
+ */
+ if (claim_table == 0)
+ claim_table = htable_create(100);
+ requestor = concatenate(instance_path, ", ", param_name, (char *) 0);
+ if ((owner = htable_find(claim_table, param_value)) == 0) {
+ (void) htable_enter(claim_table, param_value, requestor);
+ } else if (strcmp(owner, requestor) == 0) {
+ myfree(requestor);
+ } else {
+ msg_fatal("instance %s, %s=%s conflicts with instance %s=%s",
+ instance_path, param_name, param_value, owner, param_value);
+ }
+}
+
+/* claim_instance_attributes - claim multiple private instance attributes */
+
+static void claim_instance_attributes(INSTANCE *ip)
+{
+
+ /*
+ * Detect instance name or pathname conflicts between this instance and
+ * other instances. XXX: No multi.cf lock file, so this is not race-free.
+ */
+ if (ip->name)
+ register_claim(ip->config_dir, VAR_MULTI_NAME, ip->name);
+ register_claim(ip->config_dir, VAR_CONFIG_DIR, ip->config_dir);
+ register_claim(ip->config_dir, VAR_QUEUE_DIR, ip->queue_dir);
+ register_claim(ip->config_dir, VAR_DATA_DIR, ip->data_dir);
+}
+
+/* alloc_instance - allocate a single instance object */
+
+static INSTANCE *alloc_instance(const char *config_dir)
+{
+ INSTANCE *ip = (INSTANCE *) mymalloc(sizeof(INSTANCE));
+
+ ring_init(RING_PTR_OF(ip));
+ ip->config_dir = config_dir ? mystrdup(config_dir) : 0;
+ ip->queue_dir = 0;
+ ip->data_dir = 0;
+ ip->name = 0;
+ ip->gname = 0;
+ ip->enabled = 0;
+ ip->primary = 0;
+
+ return (ip);
+}
+
+#if 0
+
+/* free_instance - free a single instance object */
+
+static void free_instance(INSTANCE *ip)
+{
+
+ /*
+ * If we continue after secondary main.cf file read error, we must be
+ * prepared for the case that some parameters may be missing.
+ */
+ if (ip->name)
+ myfree(ip->name);
+ if (ip->gname)
+ myfree(ip->gname);
+ if (ip->config_dir)
+ myfree(ip->config_dir);
+ if (ip->queue_dir)
+ myfree(ip->queue_dir);
+ if (ip->data_dir)
+ myfree(ip->data_dir);
+ myfree((void *) ip);
+}
+
+#endif
+
+/* insert_instance - insert instance before selected location, claim names */
+
+static void insert_instance(INSTANCE *ip, INST_SELECTION *selection)
+{
+ RING *old;
+
+#define append_instance(ip) insert_instance((ip), (INST_SELECTION *) 0)
+
+ /*
+ * Insert instance before the selected site.
+ */
+ claim_instance_attributes(ip);
+ if (ring_succ(instance_hd) == 0)
+ ring_init(instance_hd);
+ if (selection && selection->type != INST_SEL_NONE) {
+ FOREACH_SECONDARY_INSTANCE(old) {
+ if (match_instance_selection(RING_TO_INSTANCE(old), selection)) {
+ ring_prepend(old, RING_PTR_OF(ip));
+ return;
+ }
+ }
+ if (selection->type != INST_SEL_ALL)
+ msg_fatal("No matching secondary instances");
+ }
+ ring_prepend(instance_hd, RING_PTR_OF(ip));
+}
+
+/* create_primary_instance - synthetic entry for primary instance */
+
+static INSTANCE *create_primary_instance(void)
+{
+ INSTANCE *ip = alloc_instance(var_config_dir);
+
+ /*
+ * There is no need to load primary instance parameter settings from
+ * file. We already have the main.cf parameters of interest in memory.
+ */
+#define SAVE_INSTANCE_NAME(val) (*(val) ? mystrdup(val) : 0)
+
+ ip->name = SAVE_INSTANCE_NAME(var_multi_name);
+ ip->gname = SAVE_INSTANCE_NAME(var_multi_group);
+ ip->enabled = var_multi_enable;
+ ip->queue_dir = mystrdup(var_queue_dir);
+ ip->data_dir = mystrdup(var_data_dir);
+ ip->primary = 1;
+ return (ip);
+}
+
+/* load_instance - read instance parameters from config_dir/main.cf */
+
+static INSTANCE *load_instance(INSTANCE *ip)
+{
+ VSTREAM *pipe;
+ VSTRING *buf;
+ char *name;
+ char *value;
+ ARGV *cmd;
+ int count = 0;
+ static NAME_CODE bool_code[] = {
+ CONFIG_BOOL_YES, 1,
+ CONFIG_BOOL_NO, 0,
+ 0, -1,
+ };
+
+ /*
+ * Expand parameter values in the context of the target main.cf file.
+ */
+#define REQUEST_PARAM_COUNT 5 /* # of requested parameters */
+
+ cmd = argv_alloc(REQUEST_PARAM_COUNT + 3);
+ name = concatenate(var_command_dir, "/", "postconf", (char *) 0);
+ argv_add(cmd, name, "-xc", ip->config_dir,
+ VAR_QUEUE_DIR, VAR_DATA_DIR,
+ VAR_MULTI_NAME, VAR_MULTI_GROUP, VAR_MULTI_ENABLE,
+ (char *) 0);
+ myfree(name);
+ pipe = vstream_popen(O_RDONLY, CA_VSTREAM_POPEN_ARGV(cmd->argv),
+ CA_VSTREAM_POPEN_END);
+ argv_free(cmd);
+ if (pipe == 0)
+ msg_fatal("Cannot parse %s/main.cf file: %m", ip->config_dir);
+
+ /*
+ * Read parameter settings from postconf. See also comments below on
+ * whether we should continue or skip groups after error instead of
+ * bailing out immediately.
+ */
+ buf = vstring_alloc(100);
+ while (vstring_get_nonl(buf, pipe) != VSTREAM_EOF) {
+ if (split_nameval(STR(buf), &name, &value))
+ msg_fatal("Invalid %s/main.cf parameter: %s",
+ ip->config_dir, STR(buf));
+ if (strcmp(name, VAR_QUEUE_DIR) == 0 && ++count)
+ ip->queue_dir = mystrdup(value);
+ else if (strcmp(name, VAR_DATA_DIR) == 0 && ++count)
+ ip->data_dir = mystrdup(value);
+ else if (strcmp(name, VAR_MULTI_NAME) == 0 && ++count)
+ ip->name = SAVE_INSTANCE_NAME(value);
+ else if (strcmp(name, VAR_MULTI_GROUP) == 0 && ++count)
+ ip->gname = SAVE_INSTANCE_NAME(value);
+ else if (strcmp(name, VAR_MULTI_ENABLE) == 0 && ++count) {
+ /* mail_conf_bool(3) is case insensitive! */
+ ip->enabled = name_code(bool_code, NAME_CODE_FLAG_NONE, value);
+ if (ip->enabled < 0)
+ msg_fatal("Unexpected %s/main.cf entry: %s = %s",
+ ip->config_dir, VAR_MULTI_ENABLE, value);
+ }
+ }
+ vstring_free(buf);
+
+ /*
+ * XXX We should not bail out while reading a bad secondary main.cf file.
+ * When we manage dozens or more instances, the likelihood increases that
+ * some file will be damaged or missing after a system crash. That is not
+ * a good reason to prevent undamaged Postfix instances from starting.
+ */
+ if (count != REQUEST_PARAM_COUNT)
+ msg_fatal("Failed to obtain all required %s/main.cf parameters",
+ ip->config_dir);
+
+ if (vstream_pclose(pipe))
+ msg_fatal("Cannot parse %s/main.cf file", ip->config_dir);
+ return (ip);
+}
+
+/* load_all_instances - compute list of Postfix instances */
+
+static void load_all_instances(void)
+{
+ INSTANCE *primary_instance;
+ char **cpp;
+ ARGV *secondary_names;
+
+ /*
+ * Avoid unexpected behavior when $multi_instance_directories contains
+ * only comma characters. Count the actual number of elements, before we
+ * decide that the list is empty.
+ */
+ secondary_names = argv_split(var_multi_conf_dirs, CHARS_COMMA_SP);
+
+ /*
+ * First, the primary instance. This is synthesized out of thin air.
+ */
+ primary_instance = create_primary_instance();
+ if (secondary_names->argc == 0)
+ primary_instance->enabled = 1; /* Single-instance mode */
+ append_instance(primary_instance);
+
+ /*
+ * Next, instances defined in $multi_instance_directories. Note:
+ * load_instance() has side effects on the global config dictionary, but
+ * this does not affect the values that have already been extracted into
+ * C variables.
+ */
+ for (cpp = secondary_names->argv; *cpp != 0; cpp++)
+ append_instance(load_instance(alloc_instance(*cpp)));
+
+ argv_free(secondary_names);
+}
+
+/* match_instance_selection - match all/name/group constraints */
+
+static int match_instance_selection(INSTANCE *ip, INST_SELECTION *selection)
+{
+ char *iname;
+ char *name;
+
+ /*
+ * When selecting (rather than assigning names) an instance, we match by
+ * the instance name, config_directory path, or the instance name suffix
+ * (name without mandatory prefix). Selecting "-" selects the primary
+ * instance.
+ */
+ switch (selection->type) {
+ case INST_SEL_NONE:
+ return (0);
+ case INST_SEL_ALL:
+ return (1);
+ case INST_SEL_GROUP:
+ return (ip->gname != 0 && strcmp(selection->name, ip->gname) == 0);
+ case INST_SEL_NAME:
+ name = selection->name;
+ if (*name == '/' || ip->name == 0)
+ iname = ip->config_dir;
+ else if (!HAS_NAME_PREFIX(name) && HAS_NAME_PREFIX(ip->name))
+ iname = NAME_SUFFIX(ip->name);
+ else
+ iname = ip->name;
+ return (strcmp(name, iname) == 0
+ || (ip->primary && strcmp(name, "-") == 0));
+ default:
+ msg_panic("match_instance_selection: unknown selection type: %d",
+ selection->type);
+ }
+}
+
+/* check_setenv - setenv() with extreme prejudice */
+
+static void check_setenv(const char *name, const char *value)
+{
+#define CLOBBER 1
+ if (setenv(name, value, CLOBBER) < 0)
+ msg_fatal("setenv: %m");
+}
+
+/* prepend_command_path - prepend command_directory to PATH */
+
+static void prepend_command_path(void)
+{
+ char *cmd_path;
+
+ /*
+ * Carefully prepend "$command_directory:" to PATH. We can free the
+ * buffer after check_setenv(), since the value is copied there.
+ */
+ cmd_path = safe_getenv("PATH");
+ cmd_path = concatenate(var_command_dir, ":", (cmd_path && *cmd_path) ?
+ cmd_path : ROOT_PATH, (char *) 0);
+ check_setenv("PATH", cmd_path);
+ myfree(cmd_path);
+}
+
+/* check_shared_dir_status - check and claim shared directories */
+
+static void check_shared_dir_status(void)
+{
+ struct stat st;
+ const SHARED_PATH *sp;
+
+ /*
+ * XXX Avoid false conflicts with meta_directory. This usually overlaps
+ * with other directories, typically config_directory, shlib_directory or
+ * daemon_directory.
+ */
+ for (sp = shared_dir_table; sp->param_name; ++sp) {
+ if (sp->param_value[0][0] != '/') /* "no" or other special */
+ continue;
+ if (stat(sp->param_value[0], &st) < 0)
+ msg_fatal("%s = '%s': directory not found: %m",
+ sp->param_name, sp->param_value[0]);
+ if (!S_ISDIR(st.st_mode))
+ msg_fatal("%s = '%s' is not a directory",
+ sp->param_name, sp->param_value[0]);
+ if (strcmp(sp->param_name, VAR_META_DIR) == 0)
+ continue;
+ register_claim(var_config_dir, sp->param_name, sp->param_value[0]);
+ }
+}
+
+/* check_safe_name - allow instance or group name with only "safe" characters */
+
+static int check_safe_name(const char *s)
+{
+#define SAFE_PUNCT "!@%-_=+:./"
+ if (*s == 0)
+ return (0);
+ for (; *s; ++s) {
+ if (!ISALNUM(*s) && !strchr(SAFE_PUNCT, *s))
+ return (0);
+ }
+ return (1);
+}
+
+/* check_name_assignments - Check validity of assigned instance or group name */
+
+static void check_name_assignments(NAME_ASSIGNMENT *assignment)
+{
+
+ /*
+ * Syntax check the assigned instance name. This name is also used to
+ * generate directory pathnames, so we must not allow "/" characters.
+ *
+ * The value "" will clear the name and is always valid. The command-line
+ * parser has already converted "-" into "", to simplify implementation.
+ */
+ if (assignment->name && *assignment->name) {
+ if (!check_safe_name(assignment->name))
+ msg_fatal("Unsafe characters in new instance name: '%s'",
+ assignment->name);
+ if (strchr(assignment->name, '/'))
+ msg_fatal("Illegal '/' character in new instance name: '%s'",
+ assignment->name);
+ if (NEED_NAME_PREFIX(assignment->name))
+ msg_fatal("New instance name must start with '%s'",
+ NAME_PREFIX);
+ }
+
+ /*
+ * Syntax check the assigned group name.
+ */
+ if (assignment->gname && *assignment->gname) {
+ if (!check_safe_name(assignment->gname))
+ msg_fatal("Unsafe characters in '-G %s'", assignment->gname);
+ }
+}
+
+/* do_name_assignments - assign instance/group names */
+
+static int do_name_assignments(INSTANCE *target, NAME_ASSIGNMENT *assignment)
+{
+ int export_flags = 0;
+
+ /*
+ * The command-line parser has already converted "-" into "", to simplify
+ * implementation.
+ */
+ if (assignment->name
+ && strcmp(assignment->name, target->name ? target->name : "")) {
+ register_claim(target->config_dir, VAR_MULTI_NAME, assignment->name);
+ if (target->name)
+ myfree(target->name);
+ target->name = SAVE_INSTANCE_NAME(assignment->name);
+ export_flags |= EXP_FLAG_MULTI_NAME;
+ }
+ if (assignment->gname
+ && strcmp(assignment->gname, target->gname ? target->gname : "")) {
+ if (target->gname)
+ myfree(target->gname);
+ target->gname = SAVE_INSTANCE_NAME(assignment->gname);
+ export_flags |= EXP_FLAG_MULTI_GROUP;
+ }
+ return (export_flags);
+}
+
+/* make_private_path - generate secondary pathname using primary as template */
+
+static char *make_private_path(const char *param_name,
+ const char *primary_value,
+ NAME_ASSIGNMENT *assignment)
+{
+ char *path;
+ char *base;
+ char *end;
+
+ /*
+ * The command-line parser has already converted "-" into "", to simplify
+ * implementation.
+ */
+ if (assignment->name == 0 || *assignment->name == 0)
+ msg_fatal("Missing %s parameter value", param_name);
+
+ if (*primary_value != '/')
+ msg_fatal("Invalid default %s parameter value: '%s': "
+ "specify an absolute pathname",
+ param_name, primary_value);
+
+ base = mystrdup(primary_value);
+ if ((end = strrchr(base, '/')) != 0) {
+ /* Drop trailing slashes */
+ if (end[1] == '\0') {
+ while (--end > base && *end == '/')
+ *end = '\0';
+ end = strrchr(base, '/');
+ }
+ /* Drop last path component */
+ while (end > base && *end == '/')
+ *end-- = '\0';
+ }
+ path = concatenate(base[1] ? base : "", "/",
+ assignment->name, (char *) 0);
+ myfree(base);
+ return (path);
+}
+
+/* assign_new_parameter - assign new instance private name=value */
+
+static void assign_new_parameter(INSTANCE *new, int edit_cmd,
+ const char *arg)
+{
+ char *saved_arg;
+ char *name;
+ char *value;
+ char *end;
+ char **target = 0;
+
+ /*
+ * With "import", only config_directory is specified on the command line
+ * (either explicitly as config_directory=/path/name, or implicitly as
+ * instance name). The other private directory pathnames are taken from
+ * the existing instance's main.cf file.
+ *
+ * With "create", all private pathname parameters are specified on the
+ * command line, or generated from an instance name.
+ */
+ saved_arg = mystrdup(arg);
+ if (split_nameval(saved_arg, &name, &value))
+ msg_fatal("Malformed parameter setting '%s'", arg);
+
+ if (strcmp(VAR_CONFIG_DIR, name) == 0) {
+ target = &new->config_dir;
+ } else if (edit_cmd != EDIT_CMD_IMPORT) {
+ if (strcmp(VAR_QUEUE_DIR, name) == 0) {
+ target = &new->queue_dir;
+ } else if (strcmp(VAR_DATA_DIR, name) == 0) {
+ target = &new->data_dir;
+ }
+ }
+ if (target == 0)
+ msg_fatal("Parameter '%s' not valid with action %s",
+ name, EDIT_CMD_STR(edit_cmd));
+
+ /*
+ * Extract and assign the parameter value. We do a limited number of
+ * checks here. Conflicts between instances are checked by the caller.
+ * More checks may be implemented in the helper script if inspired.
+ */
+ if (*value != '/')
+ msg_fatal("Parameter setting '%s' is not an absolute path", name);
+
+ /* Tolerate+trim trailing "/" from readline completion */
+ for (end = value + strlen(value) - 1; end > value && *end == '/'; --end)
+ *end = 0;
+
+ /* No checks here for "/." or other shoot-foot silliness. */
+ if (end == value)
+ msg_fatal("Parameter setting '%s' is the root directory", name);
+
+ if (*target)
+ myfree(*target);
+ *target = mystrdup(value);
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_arg);
+}
+
+/* assign_new_parameters - initialize new instance private parameters */
+
+static void assign_new_parameters(INSTANCE *new, int edit_cmd,
+ char **argv, NAME_ASSIGNMENT *assignment)
+{
+ const char *owner;
+
+ /*
+ * Sanity check the explicit parameter settings. More stringent checks
+ * may take place in the helper script.
+ */
+ while (*argv)
+ assign_new_parameter(new, edit_cmd, *argv++);
+
+ /*
+ * Initialize any missing private directory pathnames, using the primary
+ * configuration directory parameter values as a template, and using the
+ * assigned instance name to fill in the blanks.
+ *
+ * When importing an existing instance, load private directory pathnames
+ * from its main.cf file.
+ */
+ if (new->config_dir == 0)
+ new->config_dir =
+ make_private_path(VAR_CONFIG_DIR, var_config_dir, assignment);
+ /* Needed for better-quality error message. */
+ if ((owner = IS_CLAIMED_BY(new->config_dir)) != 0)
+ msg_fatal("new %s=%s is already in use by instance %s=%s",
+ VAR_CONFIG_DIR, new->config_dir, owner, new->config_dir);
+ if (edit_cmd != EDIT_CMD_IMPORT) {
+ if (new->queue_dir == 0)
+ new->queue_dir =
+ make_private_path(VAR_QUEUE_DIR, var_queue_dir, assignment);
+ if (new->data_dir == 0)
+ new->data_dir =
+ make_private_path(VAR_DATA_DIR, var_data_dir, assignment);
+ } else {
+ load_instance(new);
+ }
+}
+
+/* export_helper_environment - update environment settings for helper command */
+
+static void export_helper_environment(INSTANCE *target, int export_flags)
+{
+ ARGV *import_env;
+ VSTRING *multi_dirs;
+ const SHARED_PATH *sp;
+ RING *entry;
+
+ /*
+ * Environment import filter, to enforce consistent behavior whether this
+ * command is started by hand, or at system boot time. This is necessary
+ * because some shell scripts use environment settings to override
+ * main.cf settings.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ /*
+ * Prepend $command_directory: to PATH. This supposedly ensures that
+ * naive programs will execute commands from the right Postfix version.
+ */
+ prepend_command_path();
+
+ /*
+ * The following ensures that Postfix's own programs will target the
+ * primary instance.
+ */
+ check_setenv(CONF_ENV_PATH, var_config_dir);
+
+ /*
+ * Export the parameter settings that are shared between instances.
+ */
+ for (sp = shared_dir_table; sp->param_name; ++sp)
+ check_setenv(sp->param_name, sp->param_value[0]);
+
+ /*
+ * Export the target instance's private directory locations.
+ */
+ check_setenv(VAR_CONFIG_DIR, target->config_dir);
+ check_setenv(VAR_QUEUE_DIR, target->queue_dir);
+ check_setenv(VAR_DATA_DIR, target->data_dir);
+
+ /*
+ * With operations that add or delete a secondary instance, we export the
+ * modified multi_instance_directories parameter value for the primary
+ * Postfix instance.
+ */
+ if (export_flags & EXP_FLAG_MULTI_DIRS) {
+ multi_dirs = vstring_alloc(100);
+ FOREACH_SECONDARY_INSTANCE(entry) {
+ if (VSTRING_LEN(multi_dirs) > 0)
+ VSTRING_ADDCH(multi_dirs, ' ');
+ vstring_strcat(multi_dirs, RING_TO_INSTANCE(entry)->config_dir);
+ }
+ check_setenv(VAR_MULTI_CONF_DIRS, STR(multi_dirs));
+ vstring_free(multi_dirs);
+ }
+
+ /*
+ * Export updates for the instance name and group. Empty value (or no
+ * export) means don't update, "-" means clear.
+ */
+ if (export_flags & EXP_FLAG_MULTI_NAME)
+ check_setenv(VAR_MULTI_NAME, target->name && *target->name ?
+ target->name : "-");
+
+ if (export_flags & EXP_FLAG_MULTI_GROUP)
+ check_setenv(VAR_MULTI_GROUP, target->gname && *target->gname ?
+ target->gname : "-");
+
+ /*
+ * If we would implement enable/disable commands by exporting the updated
+ * parameter value, then we could skip commands that have no effect, just
+ * like we can skip "assign" commands that make no change.
+ */
+}
+
+/* install_new_instance - install and return newly created instance */
+
+static INSTANCE *install_new_instance(int edit_cmd, char **argv,
+ INST_SELECTION *selection,
+ NAME_ASSIGNMENT *assignment,
+ int *export_flags)
+{
+ INSTANCE *new;
+
+ new = alloc_instance((char *) 0);
+ check_name_assignments(assignment);
+ assign_new_parameters(new, edit_cmd, argv, assignment);
+ *export_flags |=
+ (do_name_assignments(new, assignment) | EXP_FLAG_MULTI_DIRS);
+ insert_instance(new, selection);
+ return (new);
+}
+
+/* update_instance - update existing instance, return export flags */
+
+static int update_instance(INSTANCE *target, NAME_ASSIGNMENT *assignment)
+{
+ int export_flags;
+
+ check_name_assignments(assignment);
+ export_flags = do_name_assignments(target, assignment);
+ return (export_flags);
+}
+
+/* select_existing_instance - return instance selected for management */
+
+static INSTANCE *select_existing_instance(INST_SELECTION *selection,
+ int unlink_flag,
+ int *export_flags)
+{
+ INSTANCE *selected = 0;
+ RING *entry;
+ INSTANCE *ip;
+
+#define DONT_UNLINK 0
+#define DO_UNLINK 1
+
+ if (selection->type != INST_SEL_NAME)
+ msg_fatal("Select an instance via '-i name'");
+
+ /* Find the selected instance and its predecessor */
+ FOREACH_INSTANCE(entry) {
+ if (match_instance_selection(ip = RING_TO_INSTANCE(entry), selection)) {
+ selected = ip;
+ break;
+ }
+ }
+
+ if (selected == 0)
+ msg_fatal("No instance named %s", selection->name);
+
+ if (unlink_flag) {
+ /* Splice the target instance out of the list */
+ if (ring_pred(entry) == instance_hd)
+ msg_fatal("Cannot remove the primary instance");
+ if (selected->enabled)
+ msg_fatal("Cannot remove enabled instances");
+ ring_detach(entry);
+ if (export_flags == 0)
+ msg_panic("select_existing_instance: no export flags");
+ *export_flags |= EXP_FLAG_MULTI_DIRS;
+ }
+ return (selected);
+}
+
+/* manage - create/destroy/... manage instances */
+
+static NORETURN manage(int edit_cmd, int argc, char **argv,
+ INST_SELECTION *selection,
+ NAME_ASSIGNMENT *assignment)
+{
+ char *cmd;
+ INSTANCE *target;
+ int export_flags;
+
+ /*
+ * Edit mode is not subject to iterator controls.
+ */
+#define NO_EXPORT_FLAGS ((int *) 0)
+ export_flags = 0;
+
+ switch (edit_cmd) {
+ case EDIT_CMD_INIT:
+ target = create_primary_instance();
+ break;
+
+ case EDIT_CMD_CREATE:
+ case EDIT_CMD_IMPORT:
+ load_all_instances();
+ target = install_new_instance(edit_cmd, argv, selection,
+ assignment, &export_flags);
+ break;
+
+ case EDIT_CMD_ASSIGN:
+ load_all_instances();
+ target =
+ select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS);
+ export_flags |= update_instance(target, assignment);
+ if (export_flags == 0)
+ exit(0);
+ break;
+
+ case EDIT_CMD_DESTROY:
+ case EDIT_CMD_DEPORT:
+ load_all_instances();
+ target = select_existing_instance(selection, DO_UNLINK, &export_flags);
+ break;
+
+ default:
+ load_all_instances();
+ target =
+ select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS);
+ break;
+ }
+
+ /*
+ * Set up the helper script's process environment, and execute the helper
+ * script.
+ */
+#define HELPER "postmulti-script"
+
+ export_helper_environment(target, export_flags);
+ cmd = concatenate(var_daemon_dir, "/" HELPER, (char *) 0);
+ execl(cmd, cmd, "-e", EDIT_CMD_STR(edit_cmd), (char *) 0);
+ msg_fatal("%s: %m", cmd);
+}
+
+/* run_user_command - execute external command with requested MAIL_CONFIG env */
+
+static int run_user_command(INSTANCE *ip, int iter_cmd, int iter_flags,
+ char **argv)
+{
+ WAIT_STATUS_T status;
+ int pid;
+ int wpid;
+
+ /*
+ * Set up a process environment. The postfix(1) command needs MAIL_CONFIG
+ * (or the equivalent command-line option); it overrides everything else.
+ *
+ * postmulti(1) typically runs various Postfix utilities (postsuper, ...) in
+ * the context of one or more instances. It can also run various scripts
+ * on the users PATH. So we can't clobber the user's PATH, but do want to
+ * make sure that the utilities in $command_directory are always found in
+ * the right place (or at all).
+ */
+ switch (pid = fork()) {
+ case -1:
+ msg_warn("fork %s: %m", argv[0]);
+ return -1;
+ case 0:
+ check_setenv(CONF_ENV_PATH, ip->config_dir);
+ if (iter_cmd != ITER_CMD_POSTFIX) {
+ check_setenv(VAR_DAEMON_DIR, var_daemon_dir);
+ check_setenv(VAR_COMMAND_DIR, var_command_dir);
+ check_setenv(VAR_CONFIG_DIR, ip->config_dir);
+ check_setenv(VAR_QUEUE_DIR, ip->queue_dir);
+ check_setenv(VAR_DATA_DIR, ip->data_dir);
+ check_setenv(VAR_MULTI_NAME, ip->name ? ip->name : "");
+ check_setenv(VAR_MULTI_GROUP, ip->gname ? ip->gname : "");
+ check_setenv(VAR_MULTI_ENABLE, ip->enabled ?
+ CONFIG_BOOL_YES : CONFIG_BOOL_NO);
+ prepend_command_path();
+ }
+
+ /*
+ * Replace: postfix -- start ... With: postfix -- check ...
+ */
+ if (iter_cmd == ITER_CMD_POSTFIX
+ && (iter_flags & ITER_FLAG_CHECK_DISABLED) && !ip->enabled)
+ argv[2] = "check";
+
+ execvp(argv[0], argv);
+ msg_fatal("execvp %s: %m", argv[0]);
+ default:
+ do {
+ wpid = waitpid(pid, &status, 0);
+ } while (wpid == -1 && errno == EINTR);
+ return (wpid == -1 ? -1 :
+ WIFEXITED(status) ? WEXITSTATUS(status) : 1);
+ }
+}
+
+/* word_in_list - look up command in start, stop, or control list */
+
+static int word_in_list(char *cmdlist, const char *cmd)
+{
+ char *saved;
+ char *cp;
+ char *elem;
+
+ cp = saved = mystrdup(cmdlist);
+ while ((elem = mystrtok(&cp, CHARS_COMMA_SP)) != 0 && strcmp(elem, cmd) != 0)
+ /* void */ ;
+ myfree(saved);
+ return (elem != 0);
+}
+
+/* iterate_postfix_command - execute postfix(1) command */
+
+static int iterate_postfix_command(int iter_cmd, int argc, char **argv,
+ INST_SELECTION *selection)
+{
+ int exit_status;
+ char *cmd;
+ ARGV *my_argv;
+ int iter_flags;
+
+ /*
+ * Override the iterator controls.
+ */
+ if (word_in_list(var_multi_start_cmds, argv[0])) {
+ iter_flags = ITER_FLAG_CHECK_DISABLED;
+ } else if (word_in_list(var_multi_stop_cmds, argv[0])) {
+ iter_flags = ITER_FLAG_SKIP_DISABLED | ITER_FLAG_REVERSE;
+ } else if (word_in_list(var_multi_cntrl_cmds, argv[0])) {
+ iter_flags = ITER_FLAG_SKIP_DISABLED;
+ } else {
+ iter_flags = 0;
+ }
+
+ /*
+ * Override the command line in a straightforward manner: prepend
+ * "postfix --" to the command arguments. Other overrides (environment,
+ * start -> check) are implemented below the iterator.
+ */
+#define POSTFIX_CMD "postfix"
+
+ my_argv = argv_alloc(argc + 2);
+ cmd = concatenate(var_command_dir, "/" POSTFIX_CMD, (char *) 0);
+ argv_add(my_argv, cmd, "--", (char *) 0);
+ myfree(cmd);
+ while (*argv)
+ argv_add(my_argv, *argv++, (char *) 0);
+
+ /*
+ * Execute the command for all applicable Postfix instances.
+ */
+ exit_status =
+ iterate_command(iter_cmd, iter_flags, my_argv->argv, selection);
+
+ argv_free(my_argv);
+ return (exit_status);
+}
+
+/* list_instances - list all selected instances */
+
+static void list_instances(int iter_flags, INST_SELECTION *selection)
+{
+ RING *entry;
+ INSTANCE *ip;
+
+ /*
+ * Iterate over the selected instances.
+ */
+ FOREACH_ITERATOR_INSTANCE(iter_flags, entry) {
+ ip = RING_TO_INSTANCE(entry);
+ if (match_instance_selection(ip, selection))
+ vstream_printf("%-15s %-15s %-9s %s\n",
+ ip->name ? ip->name : "-",
+ ip->gname ? ip->gname : "-",
+ ip->enabled ? "y" : "n",
+ ip->config_dir);
+ }
+ if (vstream_fflush(VSTREAM_OUT))
+ msg_fatal("error writing output: %m");
+}
+
+/* iterate_command - execute command for selected instances */
+
+static int iterate_command(int iter_cmd, int iter_flags, char **argv,
+ INST_SELECTION *selection)
+{
+ int exit_status = 0;
+ int matched = 0;
+ RING *entry;
+ INSTANCE *ip;
+
+ /*
+ * Iterate over the selected instances.
+ */
+ FOREACH_ITERATOR_INSTANCE(iter_flags, entry) {
+ ip = RING_TO_INSTANCE(entry);
+ if ((iter_flags & ITER_FLAG_SKIP_DISABLED) && !ip->enabled)
+ continue;
+ if (!match_instance_selection(ip, selection))
+ continue;
+ matched = 1;
+
+ /* Run the requested command */
+ if (run_user_command(ip, iter_cmd, iter_flags, argv) != 0)
+ exit_status = 1;
+ }
+ if (matched == 0)
+ msg_fatal("No matching instances");
+
+ return (exit_status);
+}
+
+/* iterate - Iterate over all or selected instances */
+
+static NORETURN iterate(int iter_cmd, int iter_flags, int argc, char **argv,
+ INST_SELECTION *selection)
+{
+ int exit_status;
+
+ /*
+ * In iterator mode, no selection means wild-card selection.
+ */
+ if (selection->type == INST_SEL_NONE)
+ selection->type = INST_SEL_ALL;
+
+ /*
+ * Load the in-memory instance table from main.cf files.
+ */
+ load_all_instances();
+
+ /*
+ * Iterate over the selected instances.
+ */
+ switch (iter_cmd) {
+ case ITER_CMD_POSTFIX:
+ exit_status = iterate_postfix_command(iter_cmd, argc, argv, selection);
+ break;
+ case ITER_CMD_LIST:
+ list_instances(iter_flags, selection);
+ exit_status = 0;
+ break;
+ case ITER_CMD_GENERIC:
+ exit_status = iterate_command(iter_cmd, iter_flags, argv, selection);
+ break;
+ default:
+ msg_panic("iterate: unknown mode: %d", iter_cmd);
+ }
+ exit(exit_status);
+}
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("Usage:"
+ "%s -l [-v] [-a] [-g group] [-i instance] | "
+ "%s -p [-v] [-a] [-g group] [-i instance] command... | "
+ "%s -x [-v] [-a] [-i name] [-g group] command... | "
+ "%s -e action [-v] [-a] [-i name] [-g group] [-I name] "
+ "[-G group] [param=value ...]",
+ progname, progname, progname, progname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - iterate commands over multiple instance or manage instances */
+
+int main(int argc, char **argv)
+{
+ int fd;
+ struct stat st;
+ char *slash;
+ char *config_dir;
+ int ch;
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_MULTI_START_CMDS, DEF_MULTI_START_CMDS, &var_multi_start_cmds, 0, 0,
+ VAR_MULTI_STOP_CMDS, DEF_MULTI_STOP_CMDS, &var_multi_stop_cmds, 0, 0,
+ VAR_MULTI_CNTRL_CMDS, DEF_MULTI_CNTRL_CMDS, &var_multi_cntrl_cmds, 0, 0,
+ 0,
+ };
+ int instance_select_count = 0;
+ int command_mode_count = 0;
+ INST_SELECTION selection;
+ NAME_ASSIGNMENT assignment;
+ int iter_flags = ITER_FLAG_DEFAULT;
+ int cmd_mode = 0;
+ int code;
+
+ selection.type = INST_SEL_NONE;
+ assignment.name = assignment.gname = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Set up diagnostics. XXX What if stdin is the system console during
+ * boot time? It seems a bad idea to log startup errors to the console.
+ * This is UNIX, a system that can run without hand holding.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ if (isatty(STDERR_FILENO))
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Process main.cf parameters. This is done before the GETOPT() loop to
+ * improve logging. This assumes that no command-line option can affect
+ * parameter processing.
+ */
+ mail_conf_read();
+ get_mail_conf_str_table(str_table);
+ maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+
+ if ((config_dir = getenv(CONF_ENV_PATH)) != 0
+ && strcmp(config_dir, DEF_CONFIG_DIR) != 0)
+ msg_fatal("Non-default configuration directory: %s=%s",
+ CONF_ENV_PATH, config_dir);
+
+ /*
+ * Parse switches. Move the above mail_conf_read() block after this loop,
+ * if any command-line option can affect parameter processing.
+ */
+ while ((ch = GETOPT(argc, argv, "ae:g:i:G:I:lpRvx")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ /* NOTREACHED */
+ case 'a':
+ if (selection.type != INST_SEL_ALL)
+ instance_select_count++;
+ selection.type = INST_SEL_ALL;
+ break;
+ case 'e':
+ if ((code = EDIT_CMD_CODE(optarg)) < 0)
+ msg_fatal("Invalid '-e' edit action '%s'. Specify '%s', "
+ "'%s', '%s', '%s', '%s', '%s', '%s' or '%s'",
+ optarg,
+ EDIT_CMD_STR(EDIT_CMD_CREATE),
+ EDIT_CMD_STR(EDIT_CMD_DESTROY),
+ EDIT_CMD_STR(EDIT_CMD_IMPORT),
+ EDIT_CMD_STR(EDIT_CMD_DEPORT),
+ EDIT_CMD_STR(EDIT_CMD_ENABLE),
+ EDIT_CMD_STR(EDIT_CMD_DISABLE),
+ EDIT_CMD_STR(EDIT_CMD_ASSIGN),
+ EDIT_CMD_STR(EDIT_CMD_INIT));
+ if (cmd_mode != code)
+ command_mode_count++;
+ cmd_mode = code;
+ break;
+ case 'g':
+ instance_select_count++;
+ selection.type = INST_SEL_GROUP;
+ selection.name = optarg;
+ break;
+ case 'i':
+ instance_select_count++;
+ selection.type = INST_SEL_NAME;
+ selection.name = optarg;
+ break;
+ case 'G':
+ if (assignment.gname != 0)
+ msg_fatal("Specify at most one '-G' option");
+ assignment.gname = strcmp(optarg, "-") == 0 ? "" : optarg;
+ break;
+ case 'I':
+ if (assignment.name != 0)
+ msg_fatal("Specify at most one '-I' option");
+ assignment.name = strcmp(optarg, "-") == 0 ? "" : optarg;
+ break;
+ case 'l':
+ if (cmd_mode != ITER_CMD_LIST)
+ command_mode_count++;
+ cmd_mode = ITER_CMD_LIST;
+ break;
+ case 'p':
+ if (cmd_mode != ITER_CMD_POSTFIX)
+ command_mode_count++;
+ cmd_mode = ITER_CMD_POSTFIX;
+ break;
+ case 'R':
+ iter_flags ^= ITER_FLAG_REVERSE;
+ break;
+ case 'v':
+ msg_verbose++;
+ check_setenv(CONF_ENV_VERB, "");
+ break;
+ case 'x':
+ if (cmd_mode != ITER_CMD_GENERIC)
+ command_mode_count++;
+ cmd_mode = ITER_CMD_GENERIC;
+ break;
+ }
+ }
+
+ /*
+ * Report missing arguments, or wrong arguments in the wrong context.
+ */
+ if (instance_select_count > 1)
+ msg_fatal("Specity no more than one of '-a', '-g', '-i'");
+
+ if (command_mode_count != 1)
+ msg_fatal("Specify exactly one of '-e', '-l', '-p', '-x'");
+
+ if (cmd_mode == ITER_CMD_LIST && argc > optind)
+ msg_fatal("Command not allowed with '-l'");
+
+ if (cmd_mode == ITER_CMD_POSTFIX || cmd_mode == ITER_CMD_GENERIC)
+ if (argc == optind)
+ msg_fatal("Command required with '-p' or '-x' option");
+
+ if (cmd_mode == ITER_CMD_POSTFIX || (cmd_mode & EDIT_CMD_MASK_ALL))
+ if (iter_flags != ITER_FLAG_DEFAULT)
+ msg_fatal("The '-p' and '-e' options preclude the use of '-R'");
+
+ if ((cmd_mode & EDIT_CMD_MASK_ASSIGN) == 0
+ && (assignment.name || assignment.gname)) {
+ if ((cmd_mode & EDIT_CMD_MASK_ALL) == 0)
+ msg_fatal("Cannot assign instance name or group without '-e %s'",
+ EDIT_CMD_STR(EDIT_CMD_ASSIGN));
+ else
+ msg_fatal("Cannot assign instance name or group with '-e %s'",
+ EDIT_CMD_STR(cmd_mode));
+ }
+ if (cmd_mode & EDIT_CMD_MASK_ALL) {
+ if (cmd_mode == EDIT_CMD_ASSIGN
+ && (assignment.name == 0 && assignment.gname == 0))
+ msg_fatal("Specify new instance name or group with '-e %s'",
+ EDIT_CMD_STR(cmd_mode));
+
+ if ((cmd_mode & ~EDIT_CMD_MASK_ADD) != 0 && argc > optind)
+ msg_fatal("Parameter overrides not valid with '-e %s'",
+ EDIT_CMD_STR(cmd_mode));
+ }
+
+ /*
+ * Sanity checks.
+ */
+ check_shared_dir_status();
+
+ /*
+ * Iterate over selected instances, or manipulate one instance.
+ */
+ if (cmd_mode & ITER_CMD_MASK_ALL)
+ iterate(cmd_mode, iter_flags, argc - optind, argv + optind, &selection);
+ else
+ manage(cmd_mode, argc - optind, argv + optind, &selection, &assignment);
+}
diff --git a/src/postqueue/.indent.pro b/src/postqueue/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postqueue/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postqueue/Makefile.in b/src/postqueue/Makefile.in
new file mode 100644
index 0000000..f01c3ae
--- /dev/null
+++ b/src/postqueue/Makefile.in
@@ -0,0 +1,133 @@
+SHELL = /bin/sh
+SRCS = postqueue.c showq_compat.c showq_json.c
+OBJS = postqueue.o showq_compat.o showq_json.o
+HDRS = postqueue.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postqueue
+INC_DIR = ../../include
+LIBS = ../../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 postqueue.h
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postqueue.o: ../../include/argv.h
+postqueue.o: ../../include/attr.h
+postqueue.o: ../../include/check_arg.h
+postqueue.o: ../../include/clean_env.h
+postqueue.o: ../../include/connect.h
+postqueue.o: ../../include/events.h
+postqueue.o: ../../include/flush_clnt.h
+postqueue.o: ../../include/htable.h
+postqueue.o: ../../include/iostuff.h
+postqueue.o: ../../include/mail_conf.h
+postqueue.o: ../../include/mail_dict.h
+postqueue.o: ../../include/mail_flush.h
+postqueue.o: ../../include/mail_params.h
+postqueue.o: ../../include/mail_parm_split.h
+postqueue.o: ../../include/mail_proto.h
+postqueue.o: ../../include/mail_queue.h
+postqueue.o: ../../include/mail_run.h
+postqueue.o: ../../include/mail_task.h
+postqueue.o: ../../include/mail_version.h
+postqueue.o: ../../include/maillog_client.h
+postqueue.o: ../../include/msg.h
+postqueue.o: ../../include/msg_vstream.h
+postqueue.o: ../../include/mymalloc.h
+postqueue.o: ../../include/nvtable.h
+postqueue.o: ../../include/safe.h
+postqueue.o: ../../include/smtp_stream.h
+postqueue.o: ../../include/stringops.h
+postqueue.o: ../../include/sys_defs.h
+postqueue.o: ../../include/user_acl.h
+postqueue.o: ../../include/valid_hostname.h
+postqueue.o: ../../include/valid_mailhost_addr.h
+postqueue.o: ../../include/vbuf.h
+postqueue.o: ../../include/vstream.h
+postqueue.o: ../../include/vstring.h
+postqueue.o: ../../include/warn_stat.h
+postqueue.o: postqueue.c
+postqueue.o: postqueue.h
+showq_compat.o: ../../include/attr.h
+showq_compat.o: ../../include/check_arg.h
+showq_compat.o: ../../include/htable.h
+showq_compat.o: ../../include/iostuff.h
+showq_compat.o: ../../include/mail_date.h
+showq_compat.o: ../../include/mail_params.h
+showq_compat.o: ../../include/mail_proto.h
+showq_compat.o: ../../include/mail_queue.h
+showq_compat.o: ../../include/msg.h
+showq_compat.o: ../../include/mymalloc.h
+showq_compat.o: ../../include/nvtable.h
+showq_compat.o: ../../include/stringops.h
+showq_compat.o: ../../include/sys_defs.h
+showq_compat.o: ../../include/vbuf.h
+showq_compat.o: ../../include/vstream.h
+showq_compat.o: ../../include/vstring.h
+showq_compat.o: postqueue.h
+showq_compat.o: showq_compat.c
+showq_json.o: ../../include/attr.h
+showq_json.o: ../../include/check_arg.h
+showq_json.o: ../../include/htable.h
+showq_json.o: ../../include/iostuff.h
+showq_json.o: ../../include/mail_date.h
+showq_json.o: ../../include/mail_params.h
+showq_json.o: ../../include/mail_proto.h
+showq_json.o: ../../include/mail_queue.h
+showq_json.o: ../../include/msg.h
+showq_json.o: ../../include/mymalloc.h
+showq_json.o: ../../include/nvtable.h
+showq_json.o: ../../include/stringops.h
+showq_json.o: ../../include/sys_defs.h
+showq_json.o: ../../include/vbuf.h
+showq_json.o: ../../include/vstream.h
+showq_json.o: ../../include/vstring.h
+showq_json.o: postqueue.h
+showq_json.o: showq_json.c
diff --git a/src/postqueue/postqueue.c b/src/postqueue/postqueue.c
new file mode 100644
index 0000000..09e5bca
--- /dev/null
+++ b/src/postqueue/postqueue.c
@@ -0,0 +1,727 @@
+/*++
+/* NAME
+/* postqueue 1
+/* SUMMARY
+/* Postfix queue control
+/* SYNOPSIS
+/* .ti -4
+/* \fBTo flush the mail queue\fR:
+/*
+/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-f\fR
+/*
+/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-i \fIqueue_id\fR
+/*
+/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-s \fIsite\fR
+/*
+/* .ti -4
+/* \fBTo list the mail queue\fR:
+/*
+/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-j\fR
+/*
+/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-p\fR
+/* DESCRIPTION
+/* The \fBpostqueue\fR(1) command implements the Postfix user interface
+/* for queue management. It implements operations that are
+/* traditionally available via the \fBsendmail\fR(1) command.
+/* See the \fBpostsuper\fR(1) command for queue operations
+/* that require super-user privileges such as deleting a message
+/* from the queue or changing the status of a message.
+/*
+/* The following options are recognized:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory. See also the
+/* MAIL_CONFIG environment setting below.
+/* .IP \fB-f\fR
+/* Flush the queue: attempt to deliver all queued mail.
+/*
+/* This option implements the traditional "\fBsendmail -q\fR" command,
+/* by contacting the Postfix \fBqmgr\fR(8) daemon.
+/*
+/* Warning: flushing undeliverable mail frequently will result in
+/* poor delivery performance of all other mail.
+/* .IP "\fB-i \fIqueue_id\fR"
+/* Schedule immediate delivery of deferred mail with the
+/* specified queue ID.
+/*
+/* This option implements the traditional \fBsendmail -qI\fR
+/* command, by contacting the \fBflush\fR(8) server.
+/*
+/* This feature is available with Postfix version 2.4 and later.
+/* .IP "\fB-j\fR"
+/* Produce a queue listing in JSON format, based on output
+/* from the showq(8) daemon. The result is a stream of zero
+/* or more JSON objects, one per queue file. Each object is
+/* followed by a newline character to support simple streaming
+/* parsers. See "\fBJSON OBJECT FORMAT\fR" below for details.
+/*
+/* This feature is available in Postfix 3.1 and later.
+/* .IP \fB-p\fR
+/* Produce a traditional sendmail-style queue listing.
+/* This option implements the traditional \fBmailq\fR command,
+/* by contacting the Postfix \fBshowq\fR(8) daemon.
+/*
+/* Each queue entry shows the queue file ID, message
+/* size, arrival time, sender, and the recipients that still need to
+/* be delivered. If mail could not be delivered upon the last attempt,
+/* the reason for failure is shown. The queue ID string
+/* is followed by an optional status character:
+/* .RS
+/* .IP \fB*\fR
+/* The message is in the \fBactive\fR queue, i.e. the message is
+/* selected for delivery.
+/* .IP \fB!\fR
+/* The message is in the \fBhold\fR queue, i.e. no further delivery
+/* attempt will be made until the mail is taken off hold.
+/* .IP \fB#\fR
+/* The message is forced to expire. See the \fBpostsuper\fR(1)
+/* options \fB-e\fR or \fB-f\fR.
+/* .sp
+/* This feature is available in Postfix 3.5 and later.
+/* .RE
+/* .IP "\fB-s \fIsite\fR"
+/* Schedule immediate delivery of all mail that is queued for the named
+/* \fIsite\fR. A numerical site must be specified as a valid RFC 5321
+/* address literal enclosed in [], just like in email addresses.
+/* The site must be eligible for the "fast flush" service.
+/* See \fBflush\fR(8) for more information about the "fast flush"
+/* service.
+/*
+/* This option implements the traditional "\fBsendmail -qR\fIsite\fR"
+/* command, by contacting the Postfix \fBflush\fR(8) daemon.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose. As of Postfix 2.3,
+/* this option is available for the super-user only.
+/* JSON OBJECT FORMAT
+/* .ad
+/* .fi
+/* Each JSON object represents one queue file; it is emitted
+/* as a single text line followed by a newline character.
+/*
+/* Object members have string values unless indicated otherwise.
+/* Programs should ignore object members that are not listed
+/* here; the list of members is expected to grow over time.
+/* .IP \fBqueue_name\fR
+/* The name of the queue where the message was found. Note
+/* that the contents of the mail queue may change while it is
+/* being listed; some messages may appear more than once, and
+/* some messages may be missed.
+/* .IP \fBqueue_id\fR
+/* The queue file name. The queue_id may be reused within a
+/* Postfix instance unless "enable_long_queue_ids = true" and
+/* time is monotonic. Even then, the queue_id is not expected
+/* to be unique between different Postfix instances. Management
+/* tools that require a unique name should combine the queue_id
+/* with the myhostname setting of the Postfix instance.
+/* .IP \fBarrival_time\fR
+/* The number of seconds since the start of the UNIX epoch.
+/* .IP \fBmessage_size\fR
+/* The number of bytes in the message header and body. This
+/* number does not include message envelope information. It
+/* is approximately equal to the number of bytes that would
+/* be transmitted via SMTP including the <CR><LF> line endings.
+/* .IP \fBforced_expire\fR
+/* The message is forced to expire (\fBtrue\fR or \fBfalse\fR).
+/* See the \fBpostsuper\fR(1) options \fB-e\fR or \fB-f\fR.
+/* .sp
+/* This feature is available in Postfix 3.5 and later.
+/* .IP \fBsender\fR
+/* The envelope sender address.
+/* .IP \fBrecipients\fR
+/* An array containing zero or more objects with members:
+/* .RS
+/* .IP \fBaddress\fR
+/* One recipient address.
+/* .IP \fBdelay_reason\fR
+/* If present, the reason for delayed delivery. Delayed
+/* recipients may have no delay reason, for example, while
+/* delivery is in progress, or after the system was stopped
+/* before it could record the reason.
+/* .RE
+/* SECURITY
+/* .ad
+/* .fi
+/* This program is designed to run with set-group ID privileges, so
+/* that it can connect to Postfix daemon processes.
+/* STANDARDS
+/* RFC 7159 (JSON notation)
+/* DIAGNOSTICS
+/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8),
+/* and to the standard error stream.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP MAIL_CONFIG
+/* Directory with the \fBmain.cf\fR file. In order to avoid exploitation
+/* of set-group ID privileges, a non-standard directory is allowed only
+/* if:
+/* .RS
+/* .IP \(bu
+/* The name is listed in the standard \fBmain.cf\fR file with the
+/* \fBalternate_config_directories\fR configuration parameter.
+/* .IP \(bu
+/* The command is invoked by the super-user.
+/* .RE
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBalternate_config_directories (empty)\fR"
+/* A list of non-default Postfix configuration directories that may
+/* be specified with "-c config_directory" on the command line (in the
+/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG
+/* environment parameter.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .IP "\fBfast_flush_domains ($relay_domains)\fR"
+/* Optional list of destinations that are eligible for per-destination
+/* logfiles with mail that is queued to those destinations.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .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".
+/* .IP "\fBtrigger_timeout (10s)\fR"
+/* The time limit for sending a trigger to a Postfix daemon (for
+/* example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon).
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBauthorized_flush_users (static:anyone)\fR"
+/* List of users who are authorized to flush the queue.
+/* .IP "\fBauthorized_mailq_users (static:anyone)\fR"
+/* List of users who are authorized to view the queue.
+/* FILES
+/* /var/spool/postfix, mail queue
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* showq(8), list mail queue
+/* flush(8), fast flush service
+/* sendmail(1), Sendmail-compatible user interface
+/* postsuper(1), privileged queue operations
+/* 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
+/* ETRN_README, Postfix ETRN howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The postqueue command was introduced with Postfix version 1.1.
+/* 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/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sysexits.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <clean_env.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <argv.h>
+#include <safe.h>
+#include <connect.h>
+#include <valid_hostname.h>
+#include <warn_stat.h>
+#include <events.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <mail_task.h>
+#include <mail_run.h>
+#include <mail_flush.h>
+#include <mail_queue.h>
+#include <flush_clnt.h>
+#include <smtp_stream.h>
+#include <user_acl.h>
+#include <valid_mailhost_addr.h>
+#include <mail_dict.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#include <postqueue.h>
+
+ /*
+ * WARNING WARNING WARNING
+ *
+ * This software is designed to run set-gid. In order to avoid exploitation of
+ * privilege, this software should not run any external commands, nor should
+ * it take any information from the user, unless that information can be
+ * properly sanitized. To get an idea of how much information a process can
+ * inherit from a potentially hostile user, examine all the members of the
+ * process structure (typically, in /usr/include/sys/proc.h): the current
+ * directory, open files, timers, signals, environment, command line, umask,
+ * and so on.
+ */
+
+ /*
+ * Modes of operation.
+ *
+ * XXX To support flush by recipient domain, or for destinations that have no
+ * mapping to logfile, the server has to defend against resource exhaustion
+ * attacks. A malicious user could fork off a postqueue client that starts
+ * an expensive requests and then kills the client immediately; this way she
+ * could create a high Postfix load on the system without ever exceeding her
+ * own per-user process limit. To prevent this, either the server needs to
+ * establish frequent proof of client liveliness with challenge/response, or
+ * the client needs to restrict expensive requests to privileged users only.
+ *
+ * We don't have this problem with queue listings. The showq server detects an
+ * EPIPE error after reporting a few queue entries.
+ */
+#define PQ_MODE_DEFAULT 0 /* noop */
+#define PQ_MODE_MAILQ_LIST 1 /* list mail queue */
+#define PQ_MODE_FLUSH_QUEUE 2 /* flush queue */
+#define PQ_MODE_FLUSH_SITE 3 /* flush site */
+#define PQ_MODE_FLUSH_FILE 4 /* flush message */
+#define PQ_MODE_JSON_LIST 5 /* JSON-format queue listing */
+
+ /*
+ * Silly little macros (SLMs).
+ */
+#define STR vstring_str
+
+ /*
+ * Queue manipulation access lists.
+ */
+char *var_flush_acl;
+char *var_showq_acl;
+
+static const CONFIG_STR_TABLE str_table[] = {
+ VAR_FLUSH_ACL, DEF_FLUSH_ACL, &var_flush_acl, 0, 0,
+ VAR_SHOWQ_ACL, DEF_SHOWQ_ACL, &var_showq_acl, 0, 0,
+ 0,
+};
+
+/* showq_client - run the appropriate showq protocol client */
+
+static void showq_client(int mode, VSTREAM *showq)
+{
+ if (attr_scan(showq, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ),
+ ATTR_TYPE_END) != 0)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+ switch (mode) {
+ case PQ_MODE_MAILQ_LIST:
+ showq_compat(showq);
+ break;
+ case PQ_MODE_JSON_LIST:
+ showq_json(showq);
+ break;
+ default:
+ msg_panic("show_queue: unknown mode %d", mode);
+ }
+}
+
+/* show_queue - show queue status */
+
+static void show_queue(int mode)
+{
+ const char *errstr;
+ VSTREAM *showq;
+ int n;
+ uid_t uid = getuid();
+
+ if (uid != 0 && uid != var_owner_uid
+ && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl,
+ uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to view the mail queue",
+ errstr, (long) uid);
+
+ /*
+ * Connect to the show queue service.
+ */
+ if ((showq = mail_connect(MAIL_CLASS_PUBLIC, var_showq_service, BLOCKING)) != 0) {
+ showq_client(mode, showq);
+ if (vstream_fclose(showq))
+ msg_warn("close: %m");
+ }
+
+ /*
+ * Don't assume that the mail system is down when the user has
+ * insufficient permission to access the showq socket.
+ */
+ else if (errno == EACCES || errno == EPERM) {
+ msg_fatal_status(EX_SOFTWARE,
+ "Connect to the %s %s service: %m",
+ var_mail_name, var_showq_service);
+ }
+
+ /*
+ * When the mail system is down, the superuser can still access the queue
+ * directly. Just run the showq program in stand-alone mode.
+ */
+ else if (geteuid() == 0) {
+ char *showq_path;
+ ARGV *argv;
+ int stat;
+
+ msg_warn("Mail system is down -- accessing queue directly"
+ " (Connect to the %s %s service: %m)",
+ var_mail_name, var_showq_service);
+ showq_path = concatenate(var_daemon_dir, "/", var_showq_service,
+ (char *) 0);
+ argv = argv_alloc(6);
+ argv_add(argv, showq_path, "-u", "-S", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(argv, "-v", (char *) 0);
+ argv_terminate(argv);
+ if ((showq = vstream_popen(O_RDONLY,
+ CA_VSTREAM_POPEN_ARGV(argv->argv),
+ CA_VSTREAM_POPEN_END)) == 0) {
+ stat = -1;
+ } else {
+ showq_client(mode, showq);
+ stat = vstream_pclose(showq);
+ }
+ argv_free(argv);
+ myfree(showq_path);
+ if (stat != 0)
+ msg_fatal_status(stat < 0 ? EX_OSERR : EX_SOFTWARE,
+ "Error running %s", showq_path);
+ }
+
+ /*
+ * When the mail system is down, unprivileged users are stuck, because by
+ * design the mail system contains no set_uid programs. The only way for
+ * an unprivileged user to cross protection boundaries is to talk to the
+ * showq daemon.
+ */
+ else {
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Queue report unavailable - mail system is down"
+ " (Connect to the %s %s service: %m)",
+ var_mail_name, var_showq_service);
+ }
+}
+
+/* flush_queue - force delivery */
+
+static void flush_queue(void)
+{
+ const char *errstr;
+ uid_t uid = getuid();
+
+ if (uid != 0 && uid != var_owner_uid
+ && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl,
+ uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to flush the mail queue",
+ errstr, (long) uid);
+
+ /*
+ * Trigger the flush queue service.
+ */
+ if (mail_flush_deferred() < 0)
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Cannot flush mail queue - mail system is down");
+ if (mail_flush_maildrop() < 0)
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Cannot flush mail queue - mail system is down");
+ event_drain(2);
+}
+
+/* flush_site - flush mail for site */
+
+static void flush_site(const char *site)
+{
+ int status;
+ const char *errstr;
+ uid_t uid = getuid();
+
+ if (uid != 0 && uid != var_owner_uid
+ && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl,
+ uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to flush the mail queue",
+ errstr, (long) uid);
+
+ flush_init();
+
+ switch (status = flush_send_site(site)) {
+ case FLUSH_STAT_OK:
+ exit(0);
+ case FLUSH_STAT_BAD:
+ msg_fatal_status(EX_USAGE, "Invalid request: \"%s\"", site);
+ case FLUSH_STAT_FAIL:
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Cannot flush mail queue - mail system is down");
+ case FLUSH_STAT_DENY:
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Flush service is not configured for destination \"%s\"",
+ site);
+ default:
+ msg_fatal_status(EX_SOFTWARE,
+ "Unknown flush server reply status %d", status);
+ }
+}
+
+/* flush_file - flush mail with specific queue ID */
+
+static void flush_file(const char *queue_id)
+{
+ int status;
+ const char *errstr;
+ uid_t uid = getuid();
+
+ if (uid != 0 && uid != var_owner_uid
+ && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl,
+ uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to flush the mail queue",
+ errstr, (long) uid);
+
+ switch (status = flush_send_file(queue_id)) {
+ case FLUSH_STAT_OK:
+ exit(0);
+ case FLUSH_STAT_BAD:
+ msg_fatal_status(EX_USAGE, "Invalid request: \"%s\"", queue_id);
+ case FLUSH_STAT_FAIL:
+ msg_fatal_status(EX_UNAVAILABLE,
+ "Cannot flush mail queue - mail system is down");
+ default:
+ msg_fatal_status(EX_SOFTWARE,
+ "Unexpected flush server reply status %d", status);
+ }
+}
+
+/* unavailable - sanitize exit status from library run-time errors */
+
+static void unavailable(void)
+{
+ exit(EX_UNAVAILABLE);
+}
+
+/* usage - scream and die */
+
+static NORETURN usage(void)
+{
+ msg_fatal_status(EX_USAGE, "usage: postqueue -f | postqueue -i queueid | postqueue -j | postqueue -p | postqueue -s site");
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ struct stat st;
+ int c;
+ int fd;
+ int mode = PQ_MODE_DEFAULT;
+ char *site_to_flush = 0;
+ char *id_to_flush = 0;
+ ARGV *import_env;
+ int bad_site;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal_status(EX_UNAVAILABLE, "open /dev/null: %m");
+
+ /*
+ * Initialize. Set up logging. Read the global configuration file after
+ * parsing command-line arguments. Censor the process name: it is
+ * provided by the user.
+ */
+ argv[0] = "postqueue";
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_cleanup(unavailable);
+ maillog_client_init(mail_task("postqueue"), MAILLOG_CLIENT_FLAG_NONE);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Parse JCL. This program is set-gid and must sanitize all command-line
+ * parameters. The configuration directory argument is validated by the
+ * mail configuration read routine. Don't do complex things until we have
+ * completed initializations.
+ */
+ while ((c = GETOPT(argc, argv, "c:fi:jps:v")) > 0) {
+ switch (c) {
+ case 'c': /* non-default configuration */
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal_status(EX_UNAVAILABLE, "out of memory");
+ break;
+ case 'f': /* flush queue */
+ if (mode != PQ_MODE_DEFAULT)
+ usage();
+ mode = PQ_MODE_FLUSH_QUEUE;
+ break;
+ case 'i': /* flush queue file */
+ if (mode != PQ_MODE_DEFAULT)
+ usage();
+ mode = PQ_MODE_FLUSH_FILE;
+ id_to_flush = optarg;
+ break;
+ case 'j':
+ if (mode != PQ_MODE_DEFAULT)
+ usage();
+ mode = PQ_MODE_JSON_LIST;
+ break;
+ case 'p': /* traditional mailq */
+ if (mode != PQ_MODE_DEFAULT)
+ usage();
+ mode = PQ_MODE_MAILQ_LIST;
+ break;
+ case 's': /* flush site */
+ if (mode != PQ_MODE_DEFAULT)
+ usage();
+ mode = PQ_MODE_FLUSH_SITE;
+ site_to_flush = optarg;
+ break;
+ case 'v':
+ if (geteuid() == 0)
+ msg_verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (argc > optind)
+ usage();
+
+ /*
+ * Further initialization...
+ */
+ mail_conf_read();
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task("postqueue"), MAILLOG_CLIENT_FLAG_NONE);
+ mail_dict_init(); /* proxy, sql, ldap */
+ get_mail_conf_str_table(str_table);
+
+ /*
+ * This program is designed to be set-gid, which makes it a potential
+ * target for attack. Strip and optionally override the process
+ * environment so that we don't have to trust the C library.
+ */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ clean_env(import_env->argv);
+ argv_free(import_env);
+
+ if (chdir(var_queue_dir))
+ msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir);
+
+ signal(SIGPIPE, SIG_IGN);
+
+ /* End of initializations. */
+
+ /*
+ * Further input validation.
+ */
+ if (site_to_flush != 0) {
+ bad_site = 0;
+ if (*site_to_flush == '[') {
+ bad_site = !valid_mailhost_literal(site_to_flush, DONT_GRIPE);
+ } else {
+ bad_site = !valid_hostname(site_to_flush, DONT_GRIPE);
+ }
+ if (bad_site)
+ msg_fatal_status(EX_USAGE,
+ "Cannot flush mail queue - invalid destination: \"%.100s%s\"",
+ site_to_flush, strlen(site_to_flush) > 100 ? "..." : "");
+ }
+ if (id_to_flush != 0) {
+ if (!mail_queue_id_ok(id_to_flush))
+ msg_fatal_status(EX_USAGE,
+ "Cannot flush queue ID - invalid name: \"%.100s%s\"",
+ id_to_flush, strlen(id_to_flush) > 100 ? "..." : "");
+ }
+
+ /*
+ * Start processing.
+ */
+ switch (mode) {
+ default:
+ msg_panic("unknown operation mode: %d", mode);
+ /* NOTREACHED */
+ case PQ_MODE_MAILQ_LIST:
+ case PQ_MODE_JSON_LIST:
+ show_queue(mode);
+ exit(0);
+ break;
+ case PQ_MODE_FLUSH_SITE:
+ flush_site(site_to_flush);
+ exit(0);
+ break;
+ case PQ_MODE_FLUSH_FILE:
+ flush_file(id_to_flush);
+ exit(0);
+ break;
+ case PQ_MODE_FLUSH_QUEUE:
+ flush_queue();
+ exit(0);
+ break;
+ case PQ_MODE_DEFAULT:
+ usage();
+ /* NOTREACHED */
+ }
+}
diff --git a/src/postqueue/postqueue.h b/src/postqueue/postqueue.h
new file mode 100644
index 0000000..b1b7d27
--- /dev/null
+++ b/src/postqueue/postqueue.h
@@ -0,0 +1,35 @@
+/*++
+/* NAME
+/* postqueue 5h
+/* SUMMARY
+/* postqueue internal interfaces
+/* SYNOPSIS
+/* #include <postqueue.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * showq_compat.c
+ */
+extern void showq_compat(VSTREAM *);
+
+ /*
+ * showq_json.c
+ */
+extern void showq_json(VSTREAM *);
+
+/* 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/postqueue/showq_compat.c b/src/postqueue/showq_compat.c
new file mode 100644
index 0000000..f5ca059
--- /dev/null
+++ b/src/postqueue/showq_compat.c
@@ -0,0 +1,222 @@
+/*++
+/* NAME
+/* showq_compat 8
+/* SUMMARY
+/* Sendmail mailq compatibility adapter
+/* SYNOPSIS
+/* void showq_compat(
+/* VSTREAM *showq)
+/* DESCRIPTION
+/* This function converts a record stream from the showq(8)
+/* daemon to of an approximation of Sendmail mailq command
+/* output.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, malformed showq(8) daemon output.
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+#include <sysexits.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <mail_date.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postqueue.h>
+
+ /*
+ * The enable_long_queue_ids parameter determines the output format.
+ *
+ * The historical output format for short queue IDs (inode number and time in
+ * microseconds modulo 1) is not suitable for large inode numbers, but we
+ * won't change it to avoid breaking compatibility with programs that parse
+ * this output.
+ */
+#define S_STRING_FORMAT "%-11s %7s %-20s %s\n"
+#define S_SENDER_FORMAT "%-11s %7ld %20.20s %s\n"
+#define S_HEADINGS "-Queue ID-", "--Size--", \
+ "----Arrival Time----", "-Sender/Recipient-------"
+
+#define L_STRING_FORMAT "%-17s %8s %-19s %s\n"
+#define L_SENDER_FORMAT "%-17s %8ld %19.19s %s\n"
+#define L_HEADINGS "----Queue ID-----", "--Size--", \
+ "---Arrival Time----", "--Sender/Recipient------"
+
+#define STR(x) vstring_str(x)
+
+/* showq_message - report status for one message */
+
+static unsigned long showq_message(VSTREAM *showq_stream)
+{
+ static VSTRING *queue_name = 0;
+ static VSTRING *queue_id = 0;
+ static VSTRING *id_status = 0;
+ static VSTRING *addr = 0;
+ static VSTRING *why = 0;
+ long arrival_time;
+ long message_size;
+ char *saved_reason = mystrdup("");
+ const char *show_reason;
+ int padding;
+ int showq_status;
+ time_t time_t_arrival_time;
+ int forced_expire;
+
+ /*
+ * One-time initialization.
+ */
+ if (queue_name == 0) {
+ queue_name = vstring_alloc(100);
+ queue_id = vstring_alloc(100);
+ id_status = vstring_alloc(100);
+ addr = vstring_alloc(100);
+ why = vstring_alloc(100);
+ }
+
+ /*
+ * Read the message properties and sender address.
+ */
+ if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
+ | ATTR_FLAG_PRINTABLE,
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
+ RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
+ RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
+ ATTR_TYPE_END) != 6)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+
+ /*
+ * Decorate queue file names in specific states, then print the result
+ * left-aligned, followed by other status info and the sender address
+ * which is already in externalized RFC 5321 form.
+ */
+ vstring_strcpy(id_status, STR(queue_id));
+ if (strcmp(STR(queue_name), MAIL_QUEUE_ACTIVE) == 0)
+ vstring_strcat(id_status, "*");
+ else if (strcmp(STR(queue_name), MAIL_QUEUE_HOLD) == 0)
+ vstring_strcat(id_status, "!");
+ if (forced_expire)
+ vstring_strcat(id_status, "#");
+ time_t_arrival_time = arrival_time;
+ vstream_printf(var_long_queue_ids ?
+ L_SENDER_FORMAT : S_SENDER_FORMAT, STR(id_status),
+ message_size, asctime(localtime(&time_t_arrival_time)),
+ STR(addr));
+
+ /*
+ * Read zero or more (recipient, reason) pair(s) until attr_scan_more()
+ * consumes a terminator. If the showq daemon messes up, don't try to
+ * resynchronize.
+ */
+ while ((showq_status = attr_scan_more(showq_stream)) > 0) {
+ if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
+ | ATTR_FLAG_PRINTABLE,
+ RECV_ATTR_STR(MAIL_ATTR_RECIP, addr),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, why),
+ ATTR_TYPE_END) != 2)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+
+ /*
+ * Don't output a "(reason)" line when no recipient has a reason, or
+ * when the previous recipient has the same (non)reason as the
+ * current recipient. Do output a "(reason unavailable)" when the
+ * previous recipient has a reason, and the current recipient has
+ * none.
+ */
+ if (strcmp(saved_reason, STR(why)) != 0) {
+ myfree(saved_reason);
+ saved_reason = mystrdup(STR(why));
+ show_reason = *saved_reason ? saved_reason : "reason unavailable";
+ if ((padding = 76 - (int) strlen(show_reason)) < 0)
+ padding = 0;
+ vstream_printf("%*s(%s)\n", padding, "", show_reason);
+ }
+ vstream_printf(var_long_queue_ids ?
+ L_STRING_FORMAT : S_STRING_FORMAT,
+ "", "", "", STR(addr));
+ }
+ if (showq_status < 0)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+ myfree(saved_reason);
+ return (message_size);
+}
+
+/* showq_compat - legacy mailq-style output adapter */
+
+void showq_compat(VSTREAM *showq_stream)
+{
+ unsigned long file_count = 0;
+ unsigned long queue_size = 0;
+ int showq_status;
+
+ /*
+ * Process zero or more queue file objects until attr_scan_more()
+ * consumes a terminator.
+ */
+ while ((showq_status = attr_scan_more(showq_stream)) > 0) {
+ if (file_count > 0) {
+ vstream_printf("\n");
+ } else if (var_long_queue_ids) {
+ vstream_printf(L_STRING_FORMAT, L_HEADINGS);
+ } else {
+ vstream_printf(S_STRING_FORMAT, S_HEADINGS);
+ }
+ queue_size += showq_message(showq_stream);
+ file_count++;
+ if (vstream_fflush(VSTREAM_OUT)) {
+ if (errno != EPIPE)
+ msg_fatal_status(EX_IOERR, "output write error: %m");
+ return;
+ }
+ }
+ if (showq_status < 0)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+
+ /*
+ * Print the queue summary.
+ */
+ if (file_count == 0)
+ vstream_printf("Mail queue is empty\n");
+ else {
+ vstream_printf("\n-- %lu Kbytes in %lu Request%s.\n",
+ queue_size / 1024, file_count,
+ file_count == 1 ? "" : "s");
+ }
+ if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE)
+ msg_fatal_status(EX_IOERR, "output write error: %m");
+}
diff --git a/src/postqueue/showq_json.c b/src/postqueue/showq_json.c
new file mode 100644
index 0000000..fc205c7
--- /dev/null
+++ b/src/postqueue/showq_json.c
@@ -0,0 +1,221 @@
+/*++
+/* NAME
+/* showq_json 8
+/* SUMMARY
+/* JSON queue status formatter
+/* SYNOPSIS
+/* void showq_json(
+/* VSTREAM *showq)
+/* DESCRIPTION
+/* This function converts showq(8) daemon output to JSON format.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, malformed showq(8) daemon output.
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sysexits.h>
+#include <ctype.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <mail_date.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postqueue.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* json_quote - quote JSON string */
+
+static char *json_quote(VSTRING *result, const char *text)
+{
+ unsigned char *cp;
+ int ch;
+
+ /*
+ * We use short escape sequences for common control characters. Note that
+ * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences
+ * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC
+ * 4627 complaint.
+ */
+ VSTRING_RESET(result);
+ for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) {
+ if (UNEXPECTED(ISCNTRL(ch))) {
+ switch (ch) {
+ case '\b':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'b');
+ break;
+ case '\f':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'f');
+ break;
+ case '\n':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'n');
+ break;
+ case '\r':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'r');
+ break;
+ case '\t':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 't');
+ break;
+ default:
+ vstring_sprintf(result, "\\u%04X", ch);
+ break;
+ }
+ } else {
+ switch (ch) {
+ case '\\':
+ case '"':
+ VSTRING_ADDCH(result, '\\');
+ /* FALLTHROUGH */
+ default:
+ VSTRING_ADDCH(result, ch);
+ break;
+ }
+ }
+ }
+ VSTRING_TERMINATE(result);
+
+ /*
+ * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with
+ * SMTPUTF8 disabled).
+ */
+ printable(STR(result), '?');
+ return (STR(result));
+}
+
+/* json_message - report status for one message */
+
+static void format_json(VSTREAM *showq_stream)
+{
+ static VSTRING *queue_name = 0;
+ static VSTRING *queue_id = 0;
+ static VSTRING *addr = 0;
+ static VSTRING *why = 0;
+ static VSTRING *quote_buf = 0;
+ long arrival_time;
+ long message_size;
+ int showq_status;
+ int rcpt_count = 0;
+ int forced_expire;
+
+ /*
+ * One-time initialization.
+ */
+ if (queue_name == 0) {
+ queue_name = vstring_alloc(100);
+ queue_id = vstring_alloc(100);
+ addr = vstring_alloc(100);
+ why = vstring_alloc(100);
+ quote_buf = vstring_alloc(100);
+ }
+
+ /*
+ * Read the message properties and sender address.
+ */
+ if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
+ | ATTR_FLAG_PRINTABLE,
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
+ RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
+ RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
+ ATTR_TYPE_END) != 6)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+ vstream_printf("{");
+ vstream_printf("\"queue_name\": \"%s\", ",
+ json_quote(quote_buf, STR(queue_name)));
+ vstream_printf("\"queue_id\": \"%s\", ",
+ json_quote(quote_buf, STR(queue_id)));
+ vstream_printf("\"arrival_time\": %ld, ", arrival_time);
+ vstream_printf("\"message_size\": %ld, ", message_size);
+ vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
+ vstream_printf("\"sender\": \"%s\", ",
+ json_quote(quote_buf, STR(addr)));
+
+ /*
+ * Read zero or more (recipient, reason) pair(s) until attr_scan_more()
+ * consumes a terminator. If the showq daemon messes up, don't try to
+ * resynchronize.
+ */
+ vstream_printf("\"recipients\": [");
+ for (rcpt_count = 0; (showq_status = attr_scan_more(showq_stream)) > 0; rcpt_count++) {
+ if (rcpt_count > 0)
+ vstream_printf(", ");
+ vstream_printf("{");
+ if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
+ | ATTR_FLAG_PRINTABLE,
+ RECV_ATTR_STR(MAIL_ATTR_RECIP, addr),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, why),
+ ATTR_TYPE_END) != 2)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+ vstream_printf("\"address\": \"%s\"",
+ json_quote(quote_buf, STR(addr)));
+ if (LEN(why) > 0)
+ vstream_printf(", \"delay_reason\": \"%s\"",
+ json_quote(quote_buf, STR(why)));
+ vstream_printf("}");
+ }
+ vstream_printf("]");
+ if (showq_status < 0)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+ vstream_printf("}\n");
+ if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE)
+ msg_fatal_status(EX_IOERR, "output write error: %m");
+}
+
+/* showq_json - streaming JSON-format output adapter */
+
+void showq_json(VSTREAM *showq_stream)
+{
+ int showq_status;
+
+ /*
+ * Emit zero or more queue file objects until attr_scan_more() consumes a
+ * terminator.
+ */
+ while ((showq_status = attr_scan_more(showq_stream)) > 0
+ && vstream_ferror(VSTREAM_OUT) == 0) {
+ format_json(showq_stream);
+ }
+ if (showq_status < 0)
+ msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
+}
diff --git a/src/postscreen/.indent.pro b/src/postscreen/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postscreen/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postscreen/Makefile.in b/src/postscreen/Makefile.in
new file mode 100644
index 0000000..8ed8692
--- /dev/null
+++ b/src/postscreen/Makefile.in
@@ -0,0 +1,428 @@
+SHELL = /bin/sh
+SRCS = postscreen.c postscreen_dict.c postscreen_dnsbl.c \
+ postscreen_early.c postscreen_smtpd.c postscreen_misc.c \
+ postscreen_state.c postscreen_tests.c postscreen_send.c \
+ postscreen_starttls.c postscreen_expand.c postscreen_endpt.c \
+ postscreen_haproxy.c
+OBJS = postscreen.o postscreen_dict.o postscreen_dnsbl.o \
+ postscreen_early.o postscreen_smtpd.o postscreen_misc.o \
+ postscreen_state.o postscreen_tests.o postscreen_send.o \
+ postscreen_starttls.o postscreen_expand.o postscreen_endpt.o \
+ postscreen_haproxy.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postscreen
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../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)
+
+tests: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postscreen.o: ../../include/addr_match_list.h
+postscreen.o: ../../include/argv.h
+postscreen.o: ../../include/attr.h
+postscreen.o: ../../include/check_arg.h
+postscreen.o: ../../include/data_redirect.h
+postscreen.o: ../../include/dict.h
+postscreen.o: ../../include/dict_cache.h
+postscreen.o: ../../include/events.h
+postscreen.o: ../../include/htable.h
+postscreen.o: ../../include/inet_proto.h
+postscreen.o: ../../include/iostuff.h
+postscreen.o: ../../include/mail_conf.h
+postscreen.o: ../../include/mail_params.h
+postscreen.o: ../../include/mail_proto.h
+postscreen.o: ../../include/mail_server.h
+postscreen.o: ../../include/mail_version.h
+postscreen.o: ../../include/maps.h
+postscreen.o: ../../include/match_list.h
+postscreen.o: ../../include/msg.h
+postscreen.o: ../../include/myaddrinfo.h
+postscreen.o: ../../include/myflock.h
+postscreen.o: ../../include/mymalloc.h
+postscreen.o: ../../include/name_code.h
+postscreen.o: ../../include/nvtable.h
+postscreen.o: ../../include/server_acl.h
+postscreen.o: ../../include/set_eugid.h
+postscreen.o: ../../include/string_list.h
+postscreen.o: ../../include/sys_defs.h
+postscreen.o: ../../include/vbuf.h
+postscreen.o: ../../include/vstream.h
+postscreen.o: ../../include/vstring.h
+postscreen.o: postscreen.c
+postscreen.o: postscreen.h
+postscreen_dict.o: ../../include/addr_match_list.h
+postscreen_dict.o: ../../include/argv.h
+postscreen_dict.o: ../../include/check_arg.h
+postscreen_dict.o: ../../include/dict.h
+postscreen_dict.o: ../../include/dict_cache.h
+postscreen_dict.o: ../../include/events.h
+postscreen_dict.o: ../../include/htable.h
+postscreen_dict.o: ../../include/maps.h
+postscreen_dict.o: ../../include/match_list.h
+postscreen_dict.o: ../../include/msg.h
+postscreen_dict.o: ../../include/myaddrinfo.h
+postscreen_dict.o: ../../include/myflock.h
+postscreen_dict.o: ../../include/server_acl.h
+postscreen_dict.o: ../../include/string_list.h
+postscreen_dict.o: ../../include/sys_defs.h
+postscreen_dict.o: ../../include/vbuf.h
+postscreen_dict.o: ../../include/vstream.h
+postscreen_dict.o: ../../include/vstring.h
+postscreen_dict.o: postscreen.h
+postscreen_dict.o: postscreen_dict.c
+postscreen_dnsbl.o: ../../include/addr_match_list.h
+postscreen_dnsbl.o: ../../include/argv.h
+postscreen_dnsbl.o: ../../include/attr.h
+postscreen_dnsbl.o: ../../include/check_arg.h
+postscreen_dnsbl.o: ../../include/connect.h
+postscreen_dnsbl.o: ../../include/dict.h
+postscreen_dnsbl.o: ../../include/dict_cache.h
+postscreen_dnsbl.o: ../../include/events.h
+postscreen_dnsbl.o: ../../include/htable.h
+postscreen_dnsbl.o: ../../include/iostuff.h
+postscreen_dnsbl.o: ../../include/ip_match.h
+postscreen_dnsbl.o: ../../include/mail_params.h
+postscreen_dnsbl.o: ../../include/mail_proto.h
+postscreen_dnsbl.o: ../../include/maps.h
+postscreen_dnsbl.o: ../../include/match_list.h
+postscreen_dnsbl.o: ../../include/msg.h
+postscreen_dnsbl.o: ../../include/myaddrinfo.h
+postscreen_dnsbl.o: ../../include/myflock.h
+postscreen_dnsbl.o: ../../include/mymalloc.h
+postscreen_dnsbl.o: ../../include/nvtable.h
+postscreen_dnsbl.o: ../../include/server_acl.h
+postscreen_dnsbl.o: ../../include/split_at.h
+postscreen_dnsbl.o: ../../include/string_list.h
+postscreen_dnsbl.o: ../../include/stringops.h
+postscreen_dnsbl.o: ../../include/sys_defs.h
+postscreen_dnsbl.o: ../../include/valid_hostname.h
+postscreen_dnsbl.o: ../../include/vbuf.h
+postscreen_dnsbl.o: ../../include/vstream.h
+postscreen_dnsbl.o: ../../include/vstring.h
+postscreen_dnsbl.o: postscreen.h
+postscreen_dnsbl.o: postscreen_dnsbl.c
+postscreen_early.o: ../../include/addr_match_list.h
+postscreen_early.o: ../../include/argv.h
+postscreen_early.o: ../../include/check_arg.h
+postscreen_early.o: ../../include/dict.h
+postscreen_early.o: ../../include/dict_cache.h
+postscreen_early.o: ../../include/events.h
+postscreen_early.o: ../../include/htable.h
+postscreen_early.o: ../../include/mail_params.h
+postscreen_early.o: ../../include/maps.h
+postscreen_early.o: ../../include/match_list.h
+postscreen_early.o: ../../include/msg.h
+postscreen_early.o: ../../include/myaddrinfo.h
+postscreen_early.o: ../../include/myflock.h
+postscreen_early.o: ../../include/mymalloc.h
+postscreen_early.o: ../../include/server_acl.h
+postscreen_early.o: ../../include/string_list.h
+postscreen_early.o: ../../include/stringops.h
+postscreen_early.o: ../../include/sys_defs.h
+postscreen_early.o: ../../include/vbuf.h
+postscreen_early.o: ../../include/vstream.h
+postscreen_early.o: ../../include/vstring.h
+postscreen_early.o: postscreen.h
+postscreen_early.o: postscreen_early.c
+postscreen_endpt.o: ../../include/addr_match_list.h
+postscreen_endpt.o: ../../include/argv.h
+postscreen_endpt.o: ../../include/check_arg.h
+postscreen_endpt.o: ../../include/dict.h
+postscreen_endpt.o: ../../include/dict_cache.h
+postscreen_endpt.o: ../../include/events.h
+postscreen_endpt.o: ../../include/haproxy_srvr.h
+postscreen_endpt.o: ../../include/htable.h
+postscreen_endpt.o: ../../include/inet_proto.h
+postscreen_endpt.o: ../../include/mail_params.h
+postscreen_endpt.o: ../../include/maps.h
+postscreen_endpt.o: ../../include/match_list.h
+postscreen_endpt.o: ../../include/msg.h
+postscreen_endpt.o: ../../include/myaddrinfo.h
+postscreen_endpt.o: ../../include/myflock.h
+postscreen_endpt.o: ../../include/server_acl.h
+postscreen_endpt.o: ../../include/string_list.h
+postscreen_endpt.o: ../../include/sys_defs.h
+postscreen_endpt.o: ../../include/vbuf.h
+postscreen_endpt.o: ../../include/vstream.h
+postscreen_endpt.o: ../../include/vstring.h
+postscreen_endpt.o: postscreen.h
+postscreen_endpt.o: postscreen_endpt.c
+postscreen_endpt.o: postscreen_haproxy.h
+postscreen_expand.o: ../../include/addr_match_list.h
+postscreen_expand.o: ../../include/argv.h
+postscreen_expand.o: ../../include/attr.h
+postscreen_expand.o: ../../include/check_arg.h
+postscreen_expand.o: ../../include/dict.h
+postscreen_expand.o: ../../include/dict_cache.h
+postscreen_expand.o: ../../include/events.h
+postscreen_expand.o: ../../include/htable.h
+postscreen_expand.o: ../../include/iostuff.h
+postscreen_expand.o: ../../include/mail_params.h
+postscreen_expand.o: ../../include/mail_proto.h
+postscreen_expand.o: ../../include/maps.h
+postscreen_expand.o: ../../include/match_list.h
+postscreen_expand.o: ../../include/msg.h
+postscreen_expand.o: ../../include/myaddrinfo.h
+postscreen_expand.o: ../../include/myflock.h
+postscreen_expand.o: ../../include/mymalloc.h
+postscreen_expand.o: ../../include/nvtable.h
+postscreen_expand.o: ../../include/server_acl.h
+postscreen_expand.o: ../../include/string_list.h
+postscreen_expand.o: ../../include/stringops.h
+postscreen_expand.o: ../../include/sys_defs.h
+postscreen_expand.o: ../../include/vbuf.h
+postscreen_expand.o: ../../include/vstream.h
+postscreen_expand.o: ../../include/vstring.h
+postscreen_expand.o: postscreen.h
+postscreen_expand.o: postscreen_expand.c
+postscreen_haproxy.o: ../../include/addr_match_list.h
+postscreen_haproxy.o: ../../include/argv.h
+postscreen_haproxy.o: ../../include/check_arg.h
+postscreen_haproxy.o: ../../include/dict.h
+postscreen_haproxy.o: ../../include/dict_cache.h
+postscreen_haproxy.o: ../../include/events.h
+postscreen_haproxy.o: ../../include/haproxy_srvr.h
+postscreen_haproxy.o: ../../include/htable.h
+postscreen_haproxy.o: ../../include/mail_params.h
+postscreen_haproxy.o: ../../include/maps.h
+postscreen_haproxy.o: ../../include/match_list.h
+postscreen_haproxy.o: ../../include/msg.h
+postscreen_haproxy.o: ../../include/myaddrinfo.h
+postscreen_haproxy.o: ../../include/myflock.h
+postscreen_haproxy.o: ../../include/mymalloc.h
+postscreen_haproxy.o: ../../include/server_acl.h
+postscreen_haproxy.o: ../../include/string_list.h
+postscreen_haproxy.o: ../../include/stringops.h
+postscreen_haproxy.o: ../../include/sys_defs.h
+postscreen_haproxy.o: ../../include/vbuf.h
+postscreen_haproxy.o: ../../include/vstream.h
+postscreen_haproxy.o: ../../include/vstring.h
+postscreen_haproxy.o: postscreen.h
+postscreen_haproxy.o: postscreen_haproxy.c
+postscreen_haproxy.o: postscreen_haproxy.h
+postscreen_misc.o: ../../include/addr_match_list.h
+postscreen_misc.o: ../../include/argv.h
+postscreen_misc.o: ../../include/check_arg.h
+postscreen_misc.o: ../../include/dict.h
+postscreen_misc.o: ../../include/dict_cache.h
+postscreen_misc.o: ../../include/events.h
+postscreen_misc.o: ../../include/format_tv.h
+postscreen_misc.o: ../../include/htable.h
+postscreen_misc.o: ../../include/iostuff.h
+postscreen_misc.o: ../../include/mail_params.h
+postscreen_misc.o: ../../include/maps.h
+postscreen_misc.o: ../../include/match_list.h
+postscreen_misc.o: ../../include/msg.h
+postscreen_misc.o: ../../include/myaddrinfo.h
+postscreen_misc.o: ../../include/myflock.h
+postscreen_misc.o: ../../include/server_acl.h
+postscreen_misc.o: ../../include/string_list.h
+postscreen_misc.o: ../../include/sys_defs.h
+postscreen_misc.o: ../../include/vbuf.h
+postscreen_misc.o: ../../include/vstream.h
+postscreen_misc.o: ../../include/vstring.h
+postscreen_misc.o: postscreen.h
+postscreen_misc.o: postscreen_misc.c
+postscreen_send.o: ../../include/addr_match_list.h
+postscreen_send.o: ../../include/argv.h
+postscreen_send.o: ../../include/attr.h
+postscreen_send.o: ../../include/check_arg.h
+postscreen_send.o: ../../include/connect.h
+postscreen_send.o: ../../include/dict.h
+postscreen_send.o: ../../include/dict_cache.h
+postscreen_send.o: ../../include/events.h
+postscreen_send.o: ../../include/htable.h
+postscreen_send.o: ../../include/iostuff.h
+postscreen_send.o: ../../include/mac_expand.h
+postscreen_send.o: ../../include/mac_parse.h
+postscreen_send.o: ../../include/mail_params.h
+postscreen_send.o: ../../include/mail_proto.h
+postscreen_send.o: ../../include/maps.h
+postscreen_send.o: ../../include/match_list.h
+postscreen_send.o: ../../include/msg.h
+postscreen_send.o: ../../include/myaddrinfo.h
+postscreen_send.o: ../../include/myflock.h
+postscreen_send.o: ../../include/mymalloc.h
+postscreen_send.o: ../../include/nvtable.h
+postscreen_send.o: ../../include/server_acl.h
+postscreen_send.o: ../../include/smtp_reply_footer.h
+postscreen_send.o: ../../include/string_list.h
+postscreen_send.o: ../../include/sys_defs.h
+postscreen_send.o: ../../include/vbuf.h
+postscreen_send.o: ../../include/vstream.h
+postscreen_send.o: ../../include/vstring.h
+postscreen_send.o: postscreen.h
+postscreen_send.o: postscreen_send.c
+postscreen_smtpd.o: ../../include/addr_match_list.h
+postscreen_smtpd.o: ../../include/argv.h
+postscreen_smtpd.o: ../../include/attr.h
+postscreen_smtpd.o: ../../include/check_arg.h
+postscreen_smtpd.o: ../../include/dict.h
+postscreen_smtpd.o: ../../include/dict_cache.h
+postscreen_smtpd.o: ../../include/dns.h
+postscreen_smtpd.o: ../../include/ehlo_mask.h
+postscreen_smtpd.o: ../../include/events.h
+postscreen_smtpd.o: ../../include/htable.h
+postscreen_smtpd.o: ../../include/info_log_addr_form.h
+postscreen_smtpd.o: ../../include/iostuff.h
+postscreen_smtpd.o: ../../include/is_header.h
+postscreen_smtpd.o: ../../include/lex_822.h
+postscreen_smtpd.o: ../../include/mail_params.h
+postscreen_smtpd.o: ../../include/mail_proto.h
+postscreen_smtpd.o: ../../include/maps.h
+postscreen_smtpd.o: ../../include/match_list.h
+postscreen_smtpd.o: ../../include/msg.h
+postscreen_smtpd.o: ../../include/myaddrinfo.h
+postscreen_smtpd.o: ../../include/myflock.h
+postscreen_smtpd.o: ../../include/mymalloc.h
+postscreen_smtpd.o: ../../include/name_code.h
+postscreen_smtpd.o: ../../include/name_mask.h
+postscreen_smtpd.o: ../../include/nvtable.h
+postscreen_smtpd.o: ../../include/server_acl.h
+postscreen_smtpd.o: ../../include/sock_addr.h
+postscreen_smtpd.o: ../../include/string_list.h
+postscreen_smtpd.o: ../../include/stringops.h
+postscreen_smtpd.o: ../../include/sys_defs.h
+postscreen_smtpd.o: ../../include/tls.h
+postscreen_smtpd.o: ../../include/vbuf.h
+postscreen_smtpd.o: ../../include/vstream.h
+postscreen_smtpd.o: ../../include/vstring.h
+postscreen_smtpd.o: postscreen.h
+postscreen_smtpd.o: postscreen_smtpd.c
+postscreen_starttls.o: ../../include/addr_match_list.h
+postscreen_starttls.o: ../../include/argv.h
+postscreen_starttls.o: ../../include/attr.h
+postscreen_starttls.o: ../../include/check_arg.h
+postscreen_starttls.o: ../../include/connect.h
+postscreen_starttls.o: ../../include/dict.h
+postscreen_starttls.o: ../../include/dict_cache.h
+postscreen_starttls.o: ../../include/dns.h
+postscreen_starttls.o: ../../include/events.h
+postscreen_starttls.o: ../../include/htable.h
+postscreen_starttls.o: ../../include/iostuff.h
+postscreen_starttls.o: ../../include/mail_params.h
+postscreen_starttls.o: ../../include/mail_proto.h
+postscreen_starttls.o: ../../include/maps.h
+postscreen_starttls.o: ../../include/match_list.h
+postscreen_starttls.o: ../../include/msg.h
+postscreen_starttls.o: ../../include/myaddrinfo.h
+postscreen_starttls.o: ../../include/myflock.h
+postscreen_starttls.o: ../../include/mymalloc.h
+postscreen_starttls.o: ../../include/name_code.h
+postscreen_starttls.o: ../../include/name_mask.h
+postscreen_starttls.o: ../../include/nvtable.h
+postscreen_starttls.o: ../../include/server_acl.h
+postscreen_starttls.o: ../../include/sock_addr.h
+postscreen_starttls.o: ../../include/string_list.h
+postscreen_starttls.o: ../../include/stringops.h
+postscreen_starttls.o: ../../include/sys_defs.h
+postscreen_starttls.o: ../../include/tls.h
+postscreen_starttls.o: ../../include/tls_proxy.h
+postscreen_starttls.o: ../../include/vbuf.h
+postscreen_starttls.o: ../../include/vstream.h
+postscreen_starttls.o: ../../include/vstring.h
+postscreen_starttls.o: postscreen.h
+postscreen_starttls.o: postscreen_starttls.c
+postscreen_state.o: ../../include/addr_match_list.h
+postscreen_state.o: ../../include/argv.h
+postscreen_state.o: ../../include/attr.h
+postscreen_state.o: ../../include/check_arg.h
+postscreen_state.o: ../../include/dict.h
+postscreen_state.o: ../../include/dict_cache.h
+postscreen_state.o: ../../include/events.h
+postscreen_state.o: ../../include/htable.h
+postscreen_state.o: ../../include/iostuff.h
+postscreen_state.o: ../../include/mail_conf.h
+postscreen_state.o: ../../include/mail_proto.h
+postscreen_state.o: ../../include/mail_server.h
+postscreen_state.o: ../../include/maps.h
+postscreen_state.o: ../../include/match_list.h
+postscreen_state.o: ../../include/msg.h
+postscreen_state.o: ../../include/myaddrinfo.h
+postscreen_state.o: ../../include/myflock.h
+postscreen_state.o: ../../include/mymalloc.h
+postscreen_state.o: ../../include/name_mask.h
+postscreen_state.o: ../../include/nvtable.h
+postscreen_state.o: ../../include/server_acl.h
+postscreen_state.o: ../../include/string_list.h
+postscreen_state.o: ../../include/sys_defs.h
+postscreen_state.o: ../../include/vbuf.h
+postscreen_state.o: ../../include/vstream.h
+postscreen_state.o: ../../include/vstring.h
+postscreen_state.o: postscreen.h
+postscreen_state.o: postscreen_state.c
+postscreen_tests.o: ../../include/addr_match_list.h
+postscreen_tests.o: ../../include/argv.h
+postscreen_tests.o: ../../include/check_arg.h
+postscreen_tests.o: ../../include/dict.h
+postscreen_tests.o: ../../include/dict_cache.h
+postscreen_tests.o: ../../include/events.h
+postscreen_tests.o: ../../include/htable.h
+postscreen_tests.o: ../../include/mail_params.h
+postscreen_tests.o: ../../include/maps.h
+postscreen_tests.o: ../../include/match_list.h
+postscreen_tests.o: ../../include/msg.h
+postscreen_tests.o: ../../include/myaddrinfo.h
+postscreen_tests.o: ../../include/myflock.h
+postscreen_tests.o: ../../include/name_code.h
+postscreen_tests.o: ../../include/sane_strtol.h
+postscreen_tests.o: ../../include/server_acl.h
+postscreen_tests.o: ../../include/string_list.h
+postscreen_tests.o: ../../include/sys_defs.h
+postscreen_tests.o: ../../include/vbuf.h
+postscreen_tests.o: ../../include/vstream.h
+postscreen_tests.o: ../../include/vstring.h
+postscreen_tests.o: postscreen.h
+postscreen_tests.o: postscreen_tests.c
diff --git a/src/postscreen/postscreen.c b/src/postscreen/postscreen.c
new file mode 100644
index 0000000..67716cb
--- /dev/null
+++ b/src/postscreen/postscreen.c
@@ -0,0 +1,1252 @@
+/*++
+/* NAME
+/* postscreen 8
+/* SUMMARY
+/* Postfix zombie blocker
+/* SYNOPSIS
+/* \fBpostscreen\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix \fBpostscreen\fR(8) server provides additional
+/* protection against mail server overload. One \fBpostscreen\fR(8)
+/* process handles multiple inbound SMTP connections, and decides
+/* which clients may talk to a Postfix SMTP server process.
+/* By keeping spambots away, \fBpostscreen\fR(8) leaves more
+/* SMTP server processes available for legitimate clients, and
+/* delays the onset of server overload conditions.
+/*
+/* This program should not be used on SMTP ports that receive
+/* mail from end-user clients (MUAs). In a typical deployment,
+/* \fBpostscreen\fR(8) handles the MX service on TCP port 25, and
+/* \fBsmtpd\fR(8) receives mail from MUAs on the \fBsubmission\fR
+/* service (TCP port 587) which requires client authentication.
+/* Alternatively, a site could set up a dedicated, non-postscreen,
+/* "port 25" server that provides \fBsubmission\fR service and
+/* client authentication, but no MX service.
+/*
+/* \fBpostscreen\fR(8) maintains a temporary allowlist for
+/* clients that have passed a number of tests. When an SMTP
+/* client IP address is allowlisted, \fBpostscreen\fR(8) hands
+/* off the connection immediately to a Postfix SMTP server
+/* process. This minimizes the overhead for legitimate mail.
+/*
+/* By default, \fBpostscreen\fR(8) logs statistics and hands
+/* off each connection to a Postfix SMTP server process, while
+/* excluding clients in mynetworks from all tests (primarily,
+/* to avoid problems with non-standard SMTP implementations
+/* in network appliances). This default mode blocks no clients,
+/* and is useful for non-destructive testing.
+/*
+/* In a typical production setting, \fBpostscreen\fR(8) is
+/* configured to reject mail from clients that fail one or
+/* more tests. \fBpostscreen\fR(8) logs rejected mail with the
+/* client address, helo, sender and recipient information.
+/*
+/* \fBpostscreen\fR(8) is not an SMTP proxy; this is intentional.
+/* The purpose is to keep spambots away from Postfix SMTP
+/* server processes, while minimizing overhead for legitimate
+/* traffic.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBpostscreen\fR(8) server is moderately security-sensitive.
+/* It talks to untrusted clients on the network. The process
+/* can be run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 1123 (Host requirements)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1869 (SMTP service extensions)
+/* RFC 1870 (Message Size Declaration)
+/* RFC 1985 (ETRN command)
+/* RFC 2034 (SMTP Enhanced Status Codes)
+/* RFC 2821 (SMTP protocol)
+/* Not: RFC 2920 (SMTP Pipelining)
+/* RFC 3030 (CHUNKING without BINARYMIME)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN Extension)
+/* RFC 3463 (Enhanced Status Codes)
+/* RFC 5321 (SMTP protocol, including multi-line 220 banners)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The \fBpostscreen\fR(8) built-in SMTP protocol engine
+/* currently does not announce support for AUTH, XCLIENT or
+/* XFORWARD.
+/* If you need to make these services available
+/* on port 25, then do not enable the optional "after 220
+/* server greeting" tests.
+/*
+/* The optional "after 220 server greeting" tests may result in
+/* unexpected delivery delays from senders that retry email delivery
+/* from a different IP address. Reason: after passing these tests a
+/* new client must disconnect, and reconnect from the same IP
+/* address before it can deliver mail. See POSTSCREEN_README, section
+/* "Tests after the 220 SMTP server greeting", for a discussion.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to main.cf are not picked up automatically, as
+/* \fBpostscreen\fR(8) processes may run for several hours.
+/* Use the command "postfix reload" after a configuration
+/* change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/*
+/* NOTE: Some \fBpostscreen\fR(8) parameters implement
+/* stress-dependent behavior. This is supported only when the
+/* default parameter value is stress-dependent (that is, it
+/* looks like ${stress?{X}:{Y}}, or it is the $\fIname\fR
+/* of an smtpd parameter with a stress-dependent default).
+/* Other parameters always evaluate as if the \fBstress\fR
+/* parameter value is the empty string.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_command_filter ($smtpd_command_filter)\fR"
+/* A mechanism to transform commands from remote SMTP clients.
+/* .IP "\fBpostscreen_discard_ehlo_keyword_address_maps ($smtpd_discard_ehlo_keyword_address_maps)\fR"
+/* Lookup tables, indexed by the remote SMTP client address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO response
+/* to a remote SMTP client.
+/* .IP "\fBpostscreen_discard_ehlo_keywords ($smtpd_discard_ehlo_keywords)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO
+/* response to a remote SMTP client.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBdns_ncache_ttl_fix_enable (no)\fR"
+/* Enable a workaround for future libc incompatibility.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBpostscreen_reject_footer_maps ($smtpd_reject_footer_maps)\fR"
+/* Optional lookup table for information that is appended after a 4XX
+/* or 5XX \fBpostscreen\fR(8) server response.
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBrespectful_logging (see 'postconf -d' output)\fR"
+/* Avoid logging that implies white is better than black.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_expansion_filter (see 'postconf -d' output)\fR"
+/* List of characters that are permitted in postscreen_reject_footer
+/* attribute expansions.
+/* .IP "\fBpostscreen_reject_footer ($smtpd_reject_footer)\fR"
+/* Optional information that is appended after a 4XX or 5XX
+/* \fBpostscreen\fR(8) server
+/* response.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* BEFORE-POSTSCREEN PROXY AGENT
+/* .ad
+/* .fi
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBpostscreen_upstream_proxy_protocol (empty)\fR"
+/* The name of the proxy protocol used by an optional before-postscreen
+/* proxy agent.
+/* .IP "\fBpostscreen_upstream_proxy_timeout (5s)\fR"
+/* The time limit for the proxy protocol specified with the
+/* postscreen_upstream_proxy_protocol parameter.
+/* PERMANENT ALLOW/DENYLIST TEST
+/* .ad
+/* .fi
+/* This test is executed immediately after a remote SMTP client
+/* connects. If a client is permanently allowlisted, the client
+/* will be handed off immediately to a Postfix SMTP server
+/* process.
+/* .IP "\fBpostscreen_access_list (permit_mynetworks)\fR"
+/* Permanent allow/denylist for remote SMTP client IP addresses.
+/* .IP "\fBpostscreen_blacklist_action (ignore)\fR"
+/* Renamed to postscreen_denylist_action in Postfix 3.6.
+/* MAIL EXCHANGER POLICY TESTS
+/* .ad
+/* .fi
+/* When \fBpostscreen\fR(8) is configured to monitor all primary
+/* and backup MX addresses, it can refuse to allowlist clients
+/* that connect to a backup MX address only. For small sites,
+/* this requires configuring primary and backup MX addresses
+/* on the same MTA. Larger sites would have to share the
+/* \fBpostscreen\fR(8) cache between primary and backup MTAs,
+/* which would introduce a common point of failure.
+/* .IP "\fBpostscreen_whitelist_interfaces (static:all)\fR"
+/* Renamed to postscreen_allowlist_interfaces in Postfix 3.6.
+/* BEFORE 220 GREETING TESTS
+/* .ad
+/* .fi
+/* These tests are executed before the remote SMTP client
+/* receives the "220 servername" greeting. If no tests remain
+/* after the successful completion of this phase, the client
+/* will be handed off immediately to a Postfix SMTP server
+/* process.
+/* .IP "\fBdnsblog_service_name (dnsblog)\fR"
+/* The name of the \fBdnsblog\fR(8) service entry in master.cf.
+/* .IP "\fBpostscreen_dnsbl_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client's combined
+/* DNSBL score is equal to or greater than a threshold (as defined
+/* with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+/* parameters).
+/* .IP "\fBpostscreen_dnsbl_reply_map (empty)\fR"
+/* A mapping from actual DNSBL domain name which includes a secret
+/* password, to the DNSBL domain name that postscreen will reply with
+/* when it rejects mail.
+/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
+/* Optional list of DNS allow/denylist domains, filters and weight
+/* factors.
+/* .IP "\fBpostscreen_dnsbl_threshold (1)\fR"
+/* The inclusive lower bound for blocking a remote SMTP client, based on
+/* its combined DNSBL score as defined with the postscreen_dnsbl_sites
+/* parameter.
+/* .IP "\fBpostscreen_greet_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client speaks
+/* before its turn within the time specified with the postscreen_greet_wait
+/* parameter.
+/* .IP "\fBpostscreen_greet_banner ($smtpd_banner)\fR"
+/* The \fItext\fR in the optional "220-\fItext\fR..." server
+/* response that
+/* \fBpostscreen\fR(8) sends ahead of the real Postfix SMTP server's "220
+/* text..." response, in an attempt to confuse bad SMTP clients so
+/* that they speak before their turn (pre-greet).
+/* .IP "\fBpostscreen_greet_wait (normal: 6s, overload: 2s)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will wait for an SMTP
+/* client to send a command before its turn, and for DNS blocklist
+/* lookup results to arrive (default: up to 2 seconds under stress,
+/* up to 6 seconds otherwise).
+/* .IP "\fBsmtpd_service_name (smtpd)\fR"
+/* The internal service that \fBpostscreen\fR(8) hands off allowed
+/* connections to.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBpostscreen_dnsbl_whitelist_threshold (0)\fR"
+/* Renamed to postscreen_dnsbl_allowlist_threshold in Postfix 3.6.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBpostscreen_dnsbl_timeout (10s)\fR"
+/* The time limit for DNSBL or DNSWL lookups.
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBpostscreen_denylist_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client is
+/* permanently denylisted with the postscreen_access_list parameter.
+/* .IP "\fBpostscreen_allowlist_interfaces (static:all)\fR"
+/* A list of local \fBpostscreen\fR(8) server IP addresses where a
+/* non-allowlisted remote SMTP client can obtain \fBpostscreen\fR(8)'s temporary
+/* allowlist status.
+/* .IP "\fBpostscreen_dnsbl_allowlist_threshold (0)\fR"
+/* Allow a remote SMTP client to skip "before" and "after 220
+/* greeting" protocol tests, based on its combined DNSBL score as
+/* defined with the postscreen_dnsbl_sites parameter.
+/* AFTER 220 GREETING TESTS
+/* .ad
+/* .fi
+/* These tests are executed after the remote SMTP client
+/* receives the "220 servername" greeting. If a client passes
+/* all tests during this phase, it will receive a 4XX response
+/* to all RCPT TO commands. After the client reconnects, it
+/* will be allowed to talk directly to a Postfix SMTP server
+/* process.
+/* .IP "\fBpostscreen_bare_newline_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends
+/* a bare newline character, that is, a newline not preceded by carriage
+/* return.
+/* .IP "\fBpostscreen_bare_newline_enable (no)\fR"
+/* Enable "bare newline" SMTP protocol tests in the \fBpostscreen\fR(8)
+/* server.
+/* .IP "\fBpostscreen_disable_vrfy_command ($disable_vrfy_command)\fR"
+/* Disable the SMTP VRFY command in the \fBpostscreen\fR(8) daemon.
+/* .IP "\fBpostscreen_forbidden_commands ($smtpd_forbidden_commands)\fR"
+/* List of commands that the \fBpostscreen\fR(8) server considers in
+/* violation of the SMTP protocol.
+/* .IP "\fBpostscreen_helo_required ($smtpd_helo_required)\fR"
+/* Require that a remote SMTP client sends HELO or EHLO before
+/* commencing a MAIL transaction.
+/* .IP "\fBpostscreen_non_smtp_command_action (drop)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends
+/* non-SMTP commands as specified with the postscreen_forbidden_commands
+/* parameter.
+/* .IP "\fBpostscreen_non_smtp_command_enable (no)\fR"
+/* Enable "non-SMTP command" tests in the \fBpostscreen\fR(8) server.
+/* .IP "\fBpostscreen_pipelining_action (enforce)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client
+/* sends
+/* multiple commands instead of sending one command and waiting for
+/* the server to respond.
+/* .IP "\fBpostscreen_pipelining_enable (no)\fR"
+/* Enable "pipelining" SMTP protocol tests in the \fBpostscreen\fR(8)
+/* server.
+/* CACHE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_cache_cleanup_interval (12h)\fR"
+/* The amount of time between \fBpostscreen\fR(8) cache cleanup runs.
+/* .IP "\fBpostscreen_cache_map (btree:$data_directory/postscreen_cache)\fR"
+/* Persistent storage for the \fBpostscreen\fR(8) server decisions.
+/* .IP "\fBpostscreen_cache_retention_time (7d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will cache an expired
+/* temporary allowlist entry before it is removed.
+/* .IP "\fBpostscreen_bare_newline_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "bare newline" SMTP protocol test.
+/* .IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR"
+/* The maximum amount of time that \fBpostscreen\fR(8) will use the
+/* result from a successful DNS-based reputation test before a
+/* client IP address is required to pass that test again.
+/* .IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR"
+/* The minimum amount of time that \fBpostscreen\fR(8) will use the
+/* result from a successful DNS-based reputation test before a
+/* client IP address is required to pass that test again.
+/* .IP "\fBpostscreen_greet_ttl (1d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful PREGREET test.
+/* .IP "\fBpostscreen_non_smtp_command_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "non_smtp_command" SMTP protocol test.
+/* .IP "\fBpostscreen_pipelining_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "pipelining" SMTP protocol test.
+/* RESOURCE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBpostscreen_client_connection_count_limit ($smtpd_client_connection_count_limit)\fR"
+/* How many simultaneous connections any remote SMTP client is
+/* allowed to have
+/* with the \fBpostscreen\fR(8) daemon.
+/* .IP "\fBpostscreen_command_count_limit (20)\fR"
+/* The limit on the total number of commands per SMTP session for
+/* \fBpostscreen\fR(8)'s built-in SMTP protocol engine.
+/* .IP "\fBpostscreen_command_time_limit (normal: 300s, overload: 10s)\fR"
+/* The time limit to read an entire command line with \fBpostscreen\fR(8)'s
+/* built-in SMTP protocol engine.
+/* .IP "\fBpostscreen_post_queue_limit ($default_process_limit)\fR"
+/* The number of clients that can be waiting for service from a
+/* real Postfix SMTP server process.
+/* .IP "\fBpostscreen_pre_queue_limit ($default_process_limit)\fR"
+/* The number of non-allowlisted clients that can be waiting for
+/* a decision whether they will receive service from a real Postfix
+/* SMTP server
+/* process.
+/* .IP "\fBpostscreen_watchdog_timeout (10s)\fR"
+/* How much time a \fBpostscreen\fR(8) process may take to respond to
+/* a remote SMTP client command or to perform a cache operation before it
+/* is terminated by a built-in watchdog timer.
+/* STARTTLS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_tls_security_level ($smtpd_tls_security_level)\fR"
+/* The SMTP TLS security level for the \fBpostscreen\fR(8) server; when
+/* a non-empty value is specified, this overrides the obsolete parameters
+/* postscreen_use_tls and postscreen_enforce_tls.
+/* .IP "\fBtlsproxy_service_name (tlsproxy)\fR"
+/* The name of the \fBtlsproxy\fR(8) service entry in master.cf.
+/* OBSOLETE STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* These parameters are supported for compatibility with
+/* \fBsmtpd\fR(8) legacy parameters.
+/* .IP "\fBpostscreen_use_tls ($smtpd_use_tls)\fR"
+/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+/* but do not require that clients use TLS encryption.
+/* .IP "\fBpostscreen_enforce_tls ($smtpd_enforce_tls)\fR"
+/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+/* require that clients use TLS encryption.
+/* 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 "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .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 "\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 "\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 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.).
+/* SEE ALSO
+/* smtpd(8), Postfix SMTP server
+/* tlsproxy(8), Postfix TLS proxy server
+/* dnsblog(8), DNS allow/denylist logger
+/* 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.
+/* .nf
+/* .na
+/* POSTSCREEN_README, Postfix Postscreen Howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 2.8.
+/*
+/* Many ideas in \fBpostscreen\fR(8) were explored in earlier
+/* work by Michael Tokarev, in OpenBSD spamd, and in MailChannels
+/* Traffic Control.
+/* 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/stat.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <myaddrinfo.h>
+#include <dict_cache.h>
+#include <set_eugid.h>
+#include <vstream.h>
+#include <name_code.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <data_redirect.h>
+#include <string_list.h>
+
+/* Master server protocols. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Configuration parameters.
+ */
+char *var_smtpd_service;
+char *var_smtpd_banner;
+bool var_disable_vrfy_cmd;
+bool var_helo_required;
+
+char *var_smtpd_cmd_filter;
+char *var_psc_cmd_filter;
+
+char *var_smtpd_forbid_cmds;
+char *var_psc_forbid_cmds;
+
+char *var_smtpd_ehlo_dis_words;
+char *var_smtpd_ehlo_dis_maps;
+char *var_psc_ehlo_dis_words;
+char *var_psc_ehlo_dis_maps;
+
+char *var_smtpd_tls_level;
+bool var_smtpd_use_tls;
+bool var_smtpd_enforce_tls;
+char *var_psc_tls_level;
+bool var_psc_use_tls;
+bool var_psc_enforce_tls;
+
+bool var_psc_disable_vrfy;
+bool var_psc_helo_required;
+
+char *var_psc_cache_map;
+int var_psc_cache_scan;
+int var_psc_cache_ret;
+int var_psc_post_queue_limit;
+int var_psc_pre_queue_limit;
+int var_psc_watchdog;
+
+char *var_psc_acl;
+char *var_psc_dnlist_action;
+
+char *var_psc_greet_ttl;
+int var_psc_greet_wait;
+
+char *var_psc_pregr_banner;
+char *var_psc_pregr_action;
+int var_psc_pregr_ttl;
+
+char *var_psc_dnsbl_sites;
+char *var_psc_dnsbl_reply;
+int var_psc_dnsbl_thresh;
+int var_psc_dnsbl_althresh;
+char *var_psc_dnsbl_action;
+int var_psc_dnsbl_min_ttl;
+int var_psc_dnsbl_max_ttl;
+int var_psc_dnsbl_tmout;
+
+bool var_psc_pipel_enable;
+char *var_psc_pipel_action;
+int var_psc_pipel_ttl;
+
+bool var_psc_nsmtp_enable;
+char *var_psc_nsmtp_action;
+int var_psc_nsmtp_ttl;
+
+bool var_psc_barlf_enable;
+char *var_psc_barlf_action;
+int var_psc_barlf_ttl;
+
+int var_psc_cmd_count;
+int var_psc_cmd_time;
+
+char *var_dnsblog_service;
+char *var_tlsproxy_service;
+
+char *var_smtpd_rej_footer;
+char *var_psc_rej_footer;
+char *var_psc_rej_ftr_maps;
+
+int var_smtpd_cconn_limit;
+int var_psc_cconn_limit;
+
+char *var_smtpd_exp_filter;
+char *var_psc_exp_filter;
+
+char *var_psc_allist_if;
+char *var_psc_uproxy_proto;
+int var_psc_uproxy_tmout;
+
+ /*
+ * Global variables.
+ */
+int psc_check_queue_length; /* connections being checked */
+int psc_post_queue_length; /* being sent to real SMTPD */
+DICT_CACHE *psc_cache_map; /* cache table handle */
+VSTRING *psc_temp; /* scratchpad */
+char *psc_smtpd_service_name; /* path to real SMTPD */
+int psc_pregr_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_dnsbl_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_pipel_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_nsmtp_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_barlf_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_min_ttl; /* Update with new tests! */
+STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */
+int psc_stress_greet_wait; /* stressed greet wait */
+int psc_normal_greet_wait; /* stressed greet wait */
+int psc_stress_cmd_time_limit; /* stressed command limit */
+int psc_normal_cmd_time_limit; /* normal command time limit */
+int psc_stress; /* stress level */
+int psc_lowat_check_queue_length; /* stress low-water mark */
+int psc_hiwat_check_queue_length; /* stress high-water mark */
+DICT *psc_dnsbl_reply; /* DNSBL name mapper */
+HTABLE *psc_client_concurrency; /* per-client concurrency */
+
+ /*
+ * Local variables and functions.
+ */
+static ARGV *psc_acl; /* permanent allow/denylist */
+static int psc_dnlist_action; /* PSC_ACT_DROP/ENFORCE/etc */
+static ADDR_MATCH_LIST *psc_allist_if; /* allowlist interfaces */
+
+static void psc_endpt_lookup_done(int, VSTREAM *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+
+/* psc_dump - dump some statistics before exit */
+
+static void psc_dump(char *unused_service, char **unused_argv)
+{
+
+ /*
+ * Dump preliminary cache cleanup statistics when the process commits
+ * suicide while a cache cleanup run is in progress. We can't currently
+ * distinguish between "postfix reload" (we should restart) or "maximal
+ * idle time reached" (we could finish the cache cleanup first).
+ */
+ if (psc_cache_map) {
+ dict_cache_close(psc_cache_map);
+ psc_cache_map = 0;
+ }
+}
+
+/* psc_drain - delayed exit after "postfix reload" */
+
+static void psc_drain(char *unused_service, char **unused_argv)
+{
+ int count;
+
+ /*
+ * After "postfix reload", complete work-in-progress in the background,
+ * instead of dropping already-accepted connections on the floor.
+ *
+ * Unfortunately we must close all writable tables, so we can't store or
+ * look up reputation information. The reason is that we don't have any
+ * multi-writer safety guarantees. We also can't use the single-writer
+ * proxywrite service, because its latency guarantees are too weak.
+ *
+ * All error retry counts shall be limited. Instead of blocking here, we
+ * could retry failed fork() operations in the event call-back routines,
+ * but we don't need perfection. The host system is severely overloaded
+ * and service levels are already way down.
+ *
+ * XXX Some Berkeley DB versions break with close-after-fork. Every new
+ * version is an improvement over its predecessor.
+ *
+ * XXX Don't assume that it is OK to share the same LMDB lockfile descriptor
+ * between different processes.
+ */
+ if (psc_cache_map != 0 /* XXX && psc_cache_map
+ requires locking */ ) {
+ dict_cache_close(psc_cache_map);
+ psc_cache_map = 0;
+ }
+ for (count = 0; /* see below */ ; count++) {
+ if (count >= 5) {
+ msg_fatal("fork: %m");
+ } else if (event_server_drain() != 0) {
+ msg_warn("fork: %m");
+ sleep(1);
+ continue;
+ } else {
+ return;
+ }
+ }
+}
+
+/* psc_service - handle new client connection */
+
+static void psc_service(VSTREAM *smtp_client_stream,
+ char *unused_service,
+ char **unused_argv)
+{
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This program handles all incoming connections, so it must not block.
+ * We use event-driven code for all operations that introduce latency.
+ *
+ * Note: instead of using VSTREAM-level timeouts, we enforce limits on the
+ * total amount of time to receive a complete SMTP command line.
+ */
+ non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING);
+
+ /*
+ * Look up the remote SMTP client address and port.
+ */
+ psc_endpt_lookup(smtp_client_stream, psc_endpt_lookup_done);
+}
+
+/* psc_warn_compat_respectful_logging - compatibility warning */
+
+static void psc_warn_compat_respectful_logging(PSC_STATE *state)
+{
+ msg_info("using backwards-compatible default setting "
+ VAR_RESPECTFUL_LOGGING "=no for client [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ warn_compat_respectful_logging = 0;
+}
+
+/* psc_endpt_lookup_done - endpoint lookup completed */
+
+static void psc_endpt_lookup_done(int endpt_status,
+ VSTREAM *smtp_client_stream,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ const char *myname = "psc_endpt_lookup_done";
+ PSC_STATE *state;
+ const char *stamp_str;
+ int saved_flags;
+
+ /*
+ * Best effort - if this non-blocking write(2) fails, so be it.
+ */
+ if (endpt_status < 0) {
+ (void) write(vstream_fileno(smtp_client_stream),
+ "421 4.3.2 No system resources\r\n",
+ sizeof("421 4.3.2 No system resources\r\n") - 1);
+ event_server_disconnect(smtp_client_stream);
+ return;
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d connect from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ smtp_client_addr->buf, smtp_client_port->buf);
+
+ msg_info("CONNECT from [%s]:%s to [%s]:%s",
+ smtp_client_addr->buf, smtp_client_port->buf,
+ smtp_server_addr->buf, smtp_server_port->buf);
+
+ /*
+ * Bundle up all the loose session pieces. This zeroes all flags and time
+ * stamps.
+ */
+ state = psc_new_session_state(smtp_client_stream, smtp_client_addr->buf,
+ smtp_client_port->buf,
+ smtp_server_addr->buf,
+ smtp_server_port->buf);
+
+ /*
+ * Reply with 421 when the client has too many open connections.
+ */
+ if (var_psc_cconn_limit > 0
+ && state->client_info->concurrency > var_psc_cconn_limit) {
+ msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.7.0 Error: too many connections\r\n");
+ return;
+ }
+
+ /*
+ * Reply with 421 when we can't forward more connections.
+ */
+ if (var_psc_post_queue_limit > 0
+ && psc_post_queue_length >= var_psc_post_queue_limit) {
+ msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.3.2 All server ports are busy\r\n");
+ return;
+ }
+
+ /*
+ * The permanent allow/denylist has highest precedence.
+ */
+ if (psc_acl != 0) {
+ switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) {
+
+ /*
+ * Permanently denylisted.
+ */
+ case PSC_ACL_ACT_DENYLIST:
+ msg_info("%sLISTED [%s]:%s",
+ var_respectful_logging ? "DENY" : "BLACK",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL);
+ switch (psc_dnlist_action) {
+ case PSC_ACT_DROP:
+ PSC_DROP_SESSION_STATE(state,
+ "521 5.3.2 Service currently unavailable\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.3.2 Service currently unavailable\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL);
+
+ /*
+ * Not: PSC_PASS_SESSION_STATE. Repeat this test the next
+ * time.
+ */
+ break;
+ default:
+ msg_panic("%s: unknown denylist action value %d",
+ myname, psc_dnlist_action);
+ }
+ break;
+
+ /*
+ * Permanently allowlisted.
+ */
+ case PSC_ACL_ACT_ALLOWLIST:
+ msg_info("%sLISTED [%s]:%s",
+ var_respectful_logging ? "ALLOW" : "WHITE",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ psc_conclude(state);
+ return;
+
+ /*
+ * Other: dunno (don't know) or error.
+ */
+ default:
+ break;
+ }
+ }
+
+ /*
+ * The temporary allowlist (i.e. the postscreen cache) has the lowest
+ * precedence. This cache contains information about the results of prior
+ * tests. Allowlist the client when all enabled test results are still
+ * valid.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->client_info->concurrency == 1
+ && psc_cache_map != 0
+ && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
+ saved_flags = state->flags;
+ psc_parse_tests(state, stamp_str, event_time());
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: cached + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) {
+ msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
+ psc_conclude(state);
+ return;
+ }
+ } else if (state->client_info->concurrency > 1) {
+ saved_flags = state->flags;
+ psc_todo_tests(state, event_time());
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: new + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ } else {
+ saved_flags = state->flags;
+ psc_new_tests(state);
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: new + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ }
+
+ /*
+ * Don't allowlist clients that connect to backup MX addresses. Fail
+ * "closed" on error.
+ */
+ if (addr_match_list_match(psc_allist_if, smtp_server_addr->buf) == 0) {
+ state->flags |= (PSC_STATE_FLAG_ALLIST_FAIL | PSC_STATE_FLAG_NOFORWARD);
+ msg_info("%sLIST VETO [%s]:%s", var_respectful_logging ?
+ "ALLOW" : "WHITE", PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ }
+
+ /*
+ * Reply with 421 when we can't analyze more connections. That also means
+ * no deep protocol tests when the noforward flag is raised.
+ */
+ if (var_psc_pre_queue_limit > 0
+ && psc_check_queue_length - psc_post_queue_length
+ >= var_psc_pre_queue_limit) {
+ msg_info("reject: connect from [%s]:%s: all screening ports busy",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.3.2 All screening ports are busy\r\n");
+ return;
+ }
+
+ /*
+ * If the client has no up-to-date results for some tests, do those tests
+ * first. Otherwise, skip the tests and hand off the connection.
+ */
+ if (state->flags & PSC_STATE_MASK_EARLY_TODO)
+ psc_early_tests(state);
+ else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD))
+ psc_smtpd_tests(state);
+ else
+ psc_conclude(state);
+}
+
+/* psc_cache_validator - validate one cache entry */
+
+static int psc_cache_validator(const char *client_addr,
+ const char *stamp_str,
+ void *unused_context)
+{
+ PSC_STATE dummy_state;
+ PSC_CLIENT_INFO dummy_client_info;
+
+ /*
+ * This function is called by the cache cleanup pseudo thread.
+ *
+ * When an entry is removed from the cache, the client will be reported as
+ * "NEW" in the next session where it passes all tests again. To avoid
+ * silly logging we remove the cache entry only after all tests have
+ * expired longer ago than the cache retention time.
+ */
+ dummy_state.client_info = &dummy_client_info;
+ psc_parse_tests(&dummy_state, stamp_str, event_time() - var_psc_cache_ret);
+ return ((dummy_state.flags & PSC_STATE_MASK_ANY_TODO) == 0);
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ VSTRING *redirect;
+
+ /*
+ * Open read-only maps before dropping privilege, for consistency with
+ * other Postfix daemons.
+ */
+ psc_acl_pre_jail_init(var_mynetworks, VAR_PSC_ACL);
+ if (*var_psc_acl)
+ psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL);
+ /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */
+ if (*var_psc_forbid_cmds)
+ psc_forbid_cmds = string_list_init(VAR_PSC_FORBID_CMDS,
+ MATCH_FLAG_RETURN,
+ var_psc_forbid_cmds);
+ if (*var_psc_dnsbl_reply)
+ psc_dnsbl_reply = dict_open(var_psc_dnsbl_reply, O_RDONLY,
+ DICT_FLAG_DUP_WARN);
+
+ /*
+ * Never, ever, get killed by a master signal, as that would corrupt the
+ * database when we're in the middle of an update.
+ */
+ if (setsid() < 0)
+ msg_warn("setsid: %m");
+
+ /*
+ * Security: don't create root-owned files that contain untrusted data.
+ * And don't create Postfix-owned files in root-owned directories,
+ * either. We want a correct relationship between (file or directory)
+ * ownership and (file or directory) content. To open files before going
+ * to jail, temporarily drop root privileges.
+ */
+ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
+ redirect = vstring_alloc(100);
+
+ /*
+ * Keep state in persistent external map. As a safety measure we sync the
+ * database on each update. This hurts on LINUX file systems that sync
+ * all dirty disk blocks whenever any application invokes fsync().
+ *
+ * Start the cache maintenance pseudo thread after dropping privileges.
+ */
+#define PSC_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_OPEN_LOCK)
+
+ if (*var_psc_cache_map)
+ psc_cache_map =
+ dict_cache_open(data_redirect_map(redirect, var_psc_cache_map),
+ O_CREAT | O_RDWR, PSC_DICT_OPEN_FLAGS);
+
+ /*
+ * Clean up and restore privilege.
+ */
+ vstring_free(redirect);
+ RESTORE_SAVED_EUGID();
+
+ /*
+ * Initialize the dummy SMTP engine.
+ */
+ psc_smtpd_pre_jail_init();
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ static time_t last_event_time;
+ time_t new_event_time;
+ const char *name;
+
+ /*
+ * If some table has changed then stop accepting new connections. Don't
+ * check the tables more than once a second.
+ */
+ new_event_time = event_time();
+ if (new_event_time >= last_event_time + 1
+ && (name = dict_changed_name()) != 0) {
+ msg_info("table %s has changed - finishing in the background", name);
+ event_server_drain();
+ } else {
+ last_event_time = new_event_time;
+ }
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+ const NAME_CODE actions[] = {
+ PSC_NAME_ACT_DROP, PSC_ACT_DROP,
+ PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE,
+ PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE,
+ PSC_NAME_ACT_CONT, PSC_ACT_IGNORE, /* compatibility */
+ 0, -1,
+ };
+ int cache_flags;
+ const char *tmp;
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail.
+ * Prevent automatic process suicide after a limited number of client
+ * requests. It is OK to terminate after a limited amount of idle time.
+ */
+ var_use_limit = 0;
+
+ /*
+ * Workaround for parameters whose values may contain "$", and that have
+ * a default of "$parametername". Not sure if it would be a good idea to
+ * always to this in the mail_conf_raw(3) module.
+ */
+ if (*var_psc_rej_footer == '$'
+ && mail_conf_lookup(var_psc_rej_footer + 1)) {
+ tmp = mail_conf_eval_once(var_psc_rej_footer);
+ myfree(var_psc_rej_footer);
+ var_psc_rej_footer = mystrdup(tmp);
+ }
+ if (*var_psc_exp_filter == '$'
+ && mail_conf_lookup(var_psc_exp_filter + 1)) {
+ tmp = mail_conf_eval_once(var_psc_exp_filter);
+ myfree(var_psc_exp_filter);
+ var_psc_exp_filter = mystrdup(tmp);
+ }
+
+ /*
+ * Other one-time initialization.
+ */
+ psc_temp = vstring_alloc(10);
+ vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service);
+ psc_smtpd_service_name = mystrdup(STR(psc_temp));
+ psc_dnsbl_init();
+ psc_early_init();
+ psc_smtpd_init();
+
+ if ((psc_dnlist_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_dnlist_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_DNLIST_ACTION,
+ var_psc_dnlist_action);
+ if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_dnsbl_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION,
+ var_psc_dnsbl_action);
+ if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_pregr_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION,
+ var_psc_pregr_action);
+ if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_pipel_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION,
+ var_psc_pipel_action);
+ if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_nsmtp_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION,
+ var_psc_nsmtp_action);
+ if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_barlf_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION,
+ var_psc_barlf_action);
+ /* Fail "closed" on error. */
+ psc_allist_if = addr_match_list_init(VAR_PSC_ALLIST_IF, MATCH_FLAG_RETURN,
+ var_psc_allist_if);
+
+ /*
+ * Start the cache maintenance pseudo thread last. Early cleanup makes
+ * verbose logging more informative (we get positive confirmation that
+ * the cleanup thread runs).
+ */
+ cache_flags = DICT_CACHE_FLAG_STATISTICS;
+ if (msg_verbose > 1)
+ cache_flags |= DICT_CACHE_FLAG_VERBOSE;
+ if (psc_cache_map != 0 && var_psc_cache_scan > 0)
+ dict_cache_control(psc_cache_map,
+ CA_DICT_CACHE_CTL_FLAGS(cache_flags),
+ CA_DICT_CACHE_CTL_INTERVAL(var_psc_cache_scan),
+ CA_DICT_CACHE_CTL_VALIDATOR(psc_cache_validator),
+ CA_DICT_CACHE_CTL_CONTEXT((void *) 0),
+ CA_DICT_CACHE_CTL_END);
+
+ /*
+ * Pre-compute the minimal and maximal TTL.
+ */
+ psc_min_ttl =
+ PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_min_ttl),
+ PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
+ var_psc_barlf_ttl));
+
+ /*
+ * Pre-compute the stress and normal command time limits.
+ */
+ mail_conf_update(VAR_STRESS, "yes");
+ psc_stress_cmd_time_limit =
+ get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
+ psc_stress_greet_wait =
+ get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);
+
+ mail_conf_update(VAR_STRESS, "");
+ psc_normal_cmd_time_limit =
+ get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
+ psc_normal_greet_wait =
+ get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);
+
+ psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit;
+ psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit;
+ if (msg_verbose)
+ msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d",
+ psc_stress_cmd_time_limit, psc_normal_cmd_time_limit,
+ psc_lowat_check_queue_length, psc_hiwat_check_queue_length);
+
+ if (psc_lowat_check_queue_length == 0)
+ msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit,
+ psc_lowat_check_queue_length);
+ if (psc_hiwat_check_queue_length == 0)
+ msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit,
+ psc_hiwat_check_queue_length);
+
+ /*
+ * Per-client concurrency.
+ */
+ psc_client_concurrency = htable_create(var_psc_pre_queue_limit);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * List smtpd(8) parameters before any postscreen(8) parameters that have
+ * defaults dependencies on them.
+ */
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SMTPD_SERVICE, DEF_SMTPD_SERVICE, &var_smtpd_service, 1, 0,
+ VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
+ VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
+ VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0,
+ VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0,
+ VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0,
+ VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0,
+ VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
+ VAR_PSC_PREGR_BANNER, DEF_PSC_PREGR_BANNER, &var_psc_pregr_banner, 0, 0,
+ VAR_PSC_PREGR_ACTION, DEF_PSC_PREGR_ACTION, &var_psc_pregr_action, 1, 0,
+ VAR_PSC_DNSBL_SITES, DEF_PSC_DNSBL_SITES, &var_psc_dnsbl_sites, 0, 0,
+ VAR_PSC_DNSBL_ACTION, DEF_PSC_DNSBL_ACTION, &var_psc_dnsbl_action, 1, 0,
+ VAR_PSC_PIPEL_ACTION, DEF_PSC_PIPEL_ACTION, &var_psc_pipel_action, 1, 0,
+ VAR_PSC_NSMTP_ACTION, DEF_PSC_NSMTP_ACTION, &var_psc_nsmtp_action, 1, 0,
+ VAR_PSC_BARLF_ACTION, DEF_PSC_BARLF_ACTION, &var_psc_barlf_action, 1, 0,
+ VAR_PSC_ACL, DEF_PSC_ACL, &var_psc_acl, 0, 0,
+ VAR_PSC_DNLIST_ACTION, DEF_PSC_DNLIST_ACTION, &var_psc_dnlist_action, 1, 0,
+ VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0,
+ VAR_PSC_EHLO_DIS_WORDS, DEF_PSC_EHLO_DIS_WORDS, &var_psc_ehlo_dis_words, 0, 0,
+ VAR_PSC_EHLO_DIS_MAPS, DEF_PSC_EHLO_DIS_MAPS, &var_psc_ehlo_dis_maps, 0, 0,
+ VAR_PSC_DNSBL_REPLY, DEF_PSC_DNSBL_REPLY, &var_psc_dnsbl_reply, 0, 0,
+ VAR_PSC_TLS_LEVEL, DEF_PSC_TLS_LEVEL, &var_psc_tls_level, 0, 0,
+ VAR_PSC_CMD_FILTER, DEF_PSC_CMD_FILTER, &var_psc_cmd_filter, 0, 0,
+ VAR_DNSBLOG_SERVICE, DEF_DNSBLOG_SERVICE, &var_dnsblog_service, 1, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ VAR_PSC_ALLIST_IF, DEF_PSC_ALLIST_IF, &var_psc_allist_if, 0, 0,
+ VAR_PSC_UPROXY_PROTO, DEF_PSC_UPROXY_PROTO, &var_psc_uproxy_proto, 0, 0,
+ VAR_PSC_REJ_FTR_MAPS, DEF_PSC_REJ_FTR_MAPS, &var_psc_rej_ftr_maps, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_PSC_DNSBL_THRESH, DEF_PSC_DNSBL_THRESH, &var_psc_dnsbl_thresh, 1, 0,
+ VAR_PSC_CMD_COUNT, DEF_PSC_CMD_COUNT, &var_psc_cmd_count, 1, 0,
+ VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
+ 0,
+ };
+ static const CONFIG_NINT_TABLE nint_table[] = {
+ VAR_PSC_POST_QLIMIT, DEF_PSC_POST_QLIMIT, &var_psc_post_queue_limit, 5, 0,
+ VAR_PSC_PRE_QLIMIT, DEF_PSC_PRE_QLIMIT, &var_psc_pre_queue_limit, 10, 0,
+ VAR_PSC_CCONN_LIMIT, DEF_PSC_CCONN_LIMIT, &var_psc_cconn_limit, 0, 0,
+ VAR_PSC_DNSBL_ALTHRESH, DEF_PSC_DNSBL_ALTHRESH, &var_psc_dnsbl_althresh, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, &var_psc_cmd_time, 1, 0,
+ VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, &var_psc_greet_wait, 1, 0,
+ VAR_PSC_PREGR_TTL, DEF_PSC_PREGR_TTL, &var_psc_pregr_ttl, 1, 0,
+ VAR_PSC_DNSBL_MIN_TTL, DEF_PSC_DNSBL_MIN_TTL, &var_psc_dnsbl_min_ttl, 1, 0,
+ VAR_PSC_DNSBL_MAX_TTL, DEF_PSC_DNSBL_MAX_TTL, &var_psc_dnsbl_max_ttl, 1, 0,
+ VAR_PSC_PIPEL_TTL, DEF_PSC_PIPEL_TTL, &var_psc_pipel_ttl, 1, 0,
+ VAR_PSC_NSMTP_TTL, DEF_PSC_NSMTP_TTL, &var_psc_nsmtp_ttl, 1, 0,
+ VAR_PSC_BARLF_TTL, DEF_PSC_BARLF_TTL, &var_psc_barlf_ttl, 1, 0,
+ VAR_PSC_CACHE_RET, DEF_PSC_CACHE_RET, &var_psc_cache_ret, 1, 0,
+ VAR_PSC_CACHE_SCAN, DEF_PSC_CACHE_SCAN, &var_psc_cache_scan, 0, 0,
+ VAR_PSC_WATCHDOG, DEF_PSC_WATCHDOG, &var_psc_watchdog, 10, 0,
+ VAR_PSC_UPROXY_TMOUT, DEF_PSC_UPROXY_TMOUT, &var_psc_uproxy_tmout, 1, 0,
+ VAR_PSC_DNSBL_TMOUT, DEF_PSC_DNSBL_TMOUT, &var_psc_dnsbl_tmout, 1, 0,
+
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
+ VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd,
+ VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls,
+ VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
+ VAR_PSC_PIPEL_ENABLE, DEF_PSC_PIPEL_ENABLE, &var_psc_pipel_enable,
+ VAR_PSC_NSMTP_ENABLE, DEF_PSC_NSMTP_ENABLE, &var_psc_nsmtp_enable,
+ VAR_PSC_BARLF_ENABLE, DEF_PSC_BARLF_ENABLE, &var_psc_barlf_enable,
+ 0,
+ };
+ static const CONFIG_RAW_TABLE raw_table[] = {
+ VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0,
+ VAR_PSC_REJ_FOOTER, DEF_PSC_REJ_FOOTER, &var_psc_rej_footer, 0, 0,
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0,
+ VAR_PSC_EXP_FILTER, DEF_PSC_EXP_FILTER, &var_psc_exp_filter, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_PSC_HELO_REQUIRED, DEF_PSC_HELO_REQUIRED, &var_psc_helo_required,
+ VAR_PSC_DISABLE_VRFY, DEF_PSC_DISABLE_VRFY, &var_psc_disable_vrfy,
+ VAR_PSC_USE_TLS, DEF_PSC_USE_TLS, &var_psc_use_tls,
+ VAR_PSC_ENFORCE_TLS, DEF_PSC_ENFORCE_TLS, &var_psc_enforce_tls,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ event_server_main(argc, argv, psc_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_NINT_TABLE(nint_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_RAW_TABLE(raw_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_SLOW_EXIT(psc_drain),
+ CA_MAIL_SERVER_EXIT(psc_dump),
+ CA_MAIL_SERVER_WATCHDOG(&var_psc_watchdog),
+ 0);
+}
diff --git a/src/postscreen/postscreen.h b/src/postscreen/postscreen.h
new file mode 100644
index 0000000..69a5e17
--- /dev/null
+++ b/src/postscreen/postscreen.h
@@ -0,0 +1,599 @@
+/*++
+/* NAME
+/* postscreen 3h
+/* SUMMARY
+/* postscreen internal interfaces
+/* SYNOPSIS
+/* #include <postscreen.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+
+ /*
+ * Utility library.
+ */
+#include <dict_cache.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <events.h>
+#include <htable.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <addr_match_list.h>
+#include <string_list.h>
+#include <maps.h>
+#include <server_acl.h>
+
+ /*
+ * Preliminary stuff, to be fixed.
+ */
+#define PSC_READ_BUF_SIZE 1024
+
+ /*
+ * Numeric indices and symbolic names for tests whose time stamps and status
+ * flags can be accessed by numeric index.
+ */
+#define PSC_TINDX_PREGR 0 /* pregreet */
+#define PSC_TINDX_DNSBL 1 /* dnsbl */
+#define PSC_TINDX_PIPEL 2 /* pipelining */
+#define PSC_TINDX_NSMTP 3 /* non-smtp command */
+#define PSC_TINDX_BARLF 4 /* bare newline */
+#define PSC_TINDX_COUNT 5 /* number of tests */
+
+#define PSC_TNAME_PREGR "pregreet"
+#define PSC_TNAME_DNSBL "dnsbl"
+#define PSC_TNAME_PIPEL "pipelining"
+#define PSC_TNAME_NSMTP "non-smtp command"
+#define PSC_TNAME_BARLF "bare newline"
+
+#define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname)
+
+ /*
+ * Per-client shared state.
+ */
+typedef struct {
+ int concurrency; /* per-client */
+ int pass_new_count; /* per-client */
+ time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */
+} PSC_CLIENT_INFO;
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ int flags; /* see below */
+ /* Socket state. */
+ VSTREAM *smtp_client_stream; /* remote SMTP client */
+ int smtp_server_fd; /* real SMTP server */
+ char *smtp_client_addr; /* client address */
+ char *smtp_client_port; /* client port */
+ char *smtp_server_addr; /* server address */
+ char *smtp_server_port; /* server port */
+ const char *final_reply; /* cause for hanging up */
+ VSTRING *send_buf; /* pending output */
+ /* Test context. */
+ struct timeval start_time; /* start of current test */
+ const char *test_name; /* name of current test */
+ PSC_CLIENT_INFO *client_info; /* shared client state */
+ VSTRING *dnsbl_reply; /* dnsbl reject text */
+ int dnsbl_score; /* saved DNSBL score */
+ int dnsbl_ttl; /* saved DNSBL TTL */
+ const char *dnsbl_name; /* DNSBL name with largest weight */
+ int dnsbl_index; /* dnsbl request index */
+ const char *rcpt_reply; /* how to reject recipients */
+ int command_count; /* error + junk command count */
+ const char *protocol; /* SMTP or ESMTP */
+ char *helo_name; /* SMTP helo/ehlo */
+ char *sender; /* MAIL FROM */
+ VSTRING *cmd_buffer; /* command read buffer */
+ int read_state; /* command read state machine */
+ /* smtpd(8) compatibility */
+ int ehlo_discard_mask; /* EHLO filter */
+ VSTRING *expand_buf; /* macro expansion */
+ const char *where; /* SMTP protocol state */
+} PSC_STATE;
+
+ /*
+ * Special expiration time values.
+ */
+#define PSC_TIME_STAMP_NEW (0) /* test was never passed */
+#define PSC_TIME_STAMP_DISABLED (1) /* never passed but disabled */
+#define PSC_TIME_STAMP_INVALID (-1) /* must not be cached */
+
+ /*
+ * Status flags.
+ */
+#define PSC_STATE_FLAG_NOFORWARD (1<<0) /* don't forward this session */
+#define PSC_STATE_FLAG_USING_TLS (1<<1) /* using the TLS proxy */
+#define PSC_STATE_FLAG_UNUSED2 (1<<2) /* use me! */
+#define PSC_STATE_FLAG_NEW (1<<3) /* some test was never passed */
+#define PSC_STATE_FLAG_DNLIST_FAIL (1<<4) /* denylisted */
+#define PSC_STATE_FLAG_HANGUP (1<<5) /* NOT a test failure */
+#define PSC_STATE_FLAG_SMTPD_X21 (1<<6) /* hang up after command */
+#define PSC_STATE_FLAG_ALLIST_FAIL (1<<7) /* do not allowlist */
+#define PSC_STATE_FLAG_TEST_BASE (8) /* start of indexable flags */
+
+ /*
+ * Tests whose flags and expiration time can be accessed by numerical index.
+ *
+ * Important: every MUMBLE_TODO flag must have a MUMBLE_PASS flag, such that
+ * MUMBLE_PASS == PSC_STATE_FLAGS_TODO_TO_PASS(MUMBLE_TODO).
+ *
+ * MUMBLE_TODO flags must not be cleared once raised. The _TODO_TO_PASS and
+ * _TODO_TO_DONE macros depend on this to decide that a group of tests is
+ * passed or completed.
+ *
+ * MUMBLE_DONE flags are used for "early" tests that have final results.
+ *
+ * MUMBLE_SKIP flags are used for "deep" tests where the client messed up.
+ * These flags look like MUMBLE_DONE but they are different. Deep tests can
+ * tentatively pass, but can still fail later in a session. The "ignore"
+ * action introduces an additional complication. MUMBLE_PASS indicates
+ * either that a deep test passed tentatively, or that the test failed but
+ * the result was ignored. MUMBLE_FAIL, on the other hand, is always final.
+ * We use MUMBLE_SKIP to indicate that a decision was either "fail" or
+ * forced "pass".
+ *
+ * The difference between DONE and SKIP is in the beholder's eye. These flags
+ * share the same bit.
+ */
+#define PSC_STATE_FLAGS_TODO_TO_PASS(todo_flags) ((todo_flags) >> 1)
+#define PSC_STATE_FLAGS_TODO_TO_DONE(todo_flags) ((todo_flags) << 1)
+
+#define PSC_STATE_FLAG_SHIFT_FAIL (0) /* failed test */
+#define PSC_STATE_FLAG_SHIFT_PASS (1) /* passed test */
+#define PSC_STATE_FLAG_SHIFT_TODO (2) /* expired test */
+#define PSC_STATE_FLAG_SHIFT_DONE (3) /* decision is final */
+#define PSC_STATE_FLAG_SHIFT_SKIP (3) /* action is already logged */
+#define PSC_STATE_FLAG_SHIFT_STRIDE (4) /* nr of flags per test */
+
+#define PSC_STATE_FLAG_SHIFT_BYFNAME(fname) (PSC_STATE_FLAG_SHIFT_ ## fname)
+
+ /*
+ * Indexable per-test flags. These are used for DNS allowlisting multiple
+ * tests, without needing per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTINDX_FNAME(tindx, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * (tindx) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_BYTINDX_FAIL(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), FAIL)
+#define PSC_STATE_FLAG_BYTINDX_PASS(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), PASS)
+#define PSC_STATE_FLAG_BYTINDX_TODO(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), TODO)
+#define PSC_STATE_FLAG_BYTINDX_DONE(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), DONE)
+#define PSC_STATE_FLAG_BYTINDX_SKIP(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), SKIP)
+
+ /*
+ * Flags with distinct names. These are used in the per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTNAME_FNAME(tname, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * PSC_TINDX_BYTNAME(tname) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_PREGR_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, FAIL)
+#define PSC_STATE_FLAG_PREGR_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, PASS)
+#define PSC_STATE_FLAG_PREGR_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, TODO)
+#define PSC_STATE_FLAG_PREGR_DONE PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, DONE)
+
+#define PSC_STATE_FLAG_DNSBL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, FAIL)
+#define PSC_STATE_FLAG_DNSBL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, PASS)
+#define PSC_STATE_FLAG_DNSBL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, TODO)
+#define PSC_STATE_FLAG_DNSBL_DONE PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, DONE)
+
+#define PSC_STATE_FLAG_PIPEL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, FAIL)
+#define PSC_STATE_FLAG_PIPEL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, PASS)
+#define PSC_STATE_FLAG_PIPEL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, TODO)
+#define PSC_STATE_FLAG_PIPEL_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, SKIP)
+
+#define PSC_STATE_FLAG_NSMTP_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, FAIL)
+#define PSC_STATE_FLAG_NSMTP_PASS PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, PASS)
+#define PSC_STATE_FLAG_NSMTP_TODO PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, TODO)
+#define PSC_STATE_FLAG_NSMTP_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, SKIP)
+
+#define PSC_STATE_FLAG_BARLF_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, FAIL)
+#define PSC_STATE_FLAG_BARLF_PASS PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, PASS)
+#define PSC_STATE_FLAG_BARLF_TODO PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, TODO)
+#define PSC_STATE_FLAG_BARLF_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, SKIP)
+
+ /*
+ * Aggregates for individual tests.
+ */
+#define PSC_STATE_MASK_PREGR_TODO_FAIL \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_FAIL)
+#define PSC_STATE_MASK_DNSBL_TODO_FAIL \
+ (PSC_STATE_FLAG_DNSBL_TODO | PSC_STATE_FLAG_DNSBL_FAIL)
+#define PSC_STATE_MASK_PIPEL_TODO_FAIL \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_FAIL)
+#define PSC_STATE_MASK_NSMTP_TODO_FAIL \
+ (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_FAIL)
+#define PSC_STATE_MASK_BARLF_TODO_FAIL \
+ (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_FAIL)
+
+#define PSC_STATE_MASK_PREGR_TODO_DONE \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_DONE)
+#define PSC_STATE_MASK_PIPEL_TODO_SKIP \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_SKIP)
+#define PSC_STATE_MASK_NSMTP_TODO_SKIP \
+ (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_SKIP)
+#define PSC_STATE_MASK_BARLF_TODO_SKIP \
+ (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_SKIP)
+
+#define PSC_STATE_MASK_PREGR_FAIL_DONE \
+ (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_PREGR_DONE)
+
+#define PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_PIPEL_TODO_FAIL | PSC_STATE_FLAG_PIPEL_PASS)
+#define PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_NSMTP_TODO_FAIL | PSC_STATE_FLAG_NSMTP_PASS)
+#define PSC_STATE_MASK_BARLF_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_BARLF_TODO_FAIL | PSC_STATE_FLAG_BARLF_PASS)
+
+ /*
+ * Separate aggregates for early tests and deep tests.
+ */
+#define PSC_STATE_MASK_EARLY_DONE \
+ (PSC_STATE_FLAG_PREGR_DONE | PSC_STATE_FLAG_DNSBL_DONE)
+#define PSC_STATE_MASK_EARLY_TODO \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_DNSBL_TODO)
+#define PSC_STATE_MASK_EARLY_PASS \
+ (PSC_STATE_FLAG_PREGR_PASS | PSC_STATE_FLAG_DNSBL_PASS)
+#define PSC_STATE_MASK_EARLY_FAIL \
+ (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_DNSBL_FAIL)
+
+#define PSC_STATE_MASK_SMTPD_TODO \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_NSMTP_TODO | \
+ PSC_STATE_FLAG_BARLF_TODO)
+#define PSC_STATE_MASK_SMTPD_PASS \
+ (PSC_STATE_FLAG_PIPEL_PASS | PSC_STATE_FLAG_NSMTP_PASS | \
+ PSC_STATE_FLAG_BARLF_PASS)
+#define PSC_STATE_MASK_SMTPD_FAIL \
+ (PSC_STATE_FLAG_PIPEL_FAIL | PSC_STATE_FLAG_NSMTP_FAIL | \
+ PSC_STATE_FLAG_BARLF_FAIL)
+
+ /*
+ * Super-aggregates for all tests combined.
+ */
+#define PSC_STATE_MASK_ANY_FAIL \
+ (PSC_STATE_FLAG_DNLIST_FAIL | \
+ PSC_STATE_MASK_EARLY_FAIL | PSC_STATE_MASK_SMTPD_FAIL | \
+ PSC_STATE_FLAG_ALLIST_FAIL)
+
+#define PSC_STATE_MASK_ANY_PASS \
+ (PSC_STATE_MASK_EARLY_PASS | PSC_STATE_MASK_SMTPD_PASS)
+
+#define PSC_STATE_MASK_ANY_TODO \
+ (PSC_STATE_MASK_EARLY_TODO | PSC_STATE_MASK_SMTPD_TODO)
+
+#define PSC_STATE_MASK_ANY_TODO_FAIL \
+ (PSC_STATE_MASK_ANY_TODO | PSC_STATE_MASK_ANY_FAIL)
+
+#define PSC_STATE_MASK_ANY_UPDATE \
+ (PSC_STATE_MASK_ANY_PASS)
+
+ /*
+ * Meta-commands for state->where that reflect the initial command processor
+ * state and commands that aren't implemented.
+ */
+#define PSC_SMTPD_CMD_CONNECT "CONNECT"
+#define PSC_SMTPD_CMD_UNIMPL "UNIMPLEMENTED"
+
+ /*
+ * See log_adhoc.c for discussion.
+ */
+typedef struct {
+ int dt_sec; /* make sure it's signed */
+ int dt_usec; /* make sure it's signed */
+} DELTA_TIME;
+
+#define PSC_CALC_DELTA(x, y, z) \
+ do { \
+ (x).dt_sec = (y).tv_sec - (z).tv_sec; \
+ (x).dt_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).dt_usec < 0) { \
+ (x).dt_usec += 1000000; \
+ (x).dt_sec -= 1; \
+ } \
+ while ((x).dt_usec >= 1000000) { \
+ (x).dt_usec -= 1000000; \
+ (x).dt_sec += 1; \
+ } \
+ if ((x).dt_sec < 0) \
+ (x).dt_sec = (x).dt_usec = 0; \
+ } while (0)
+
+#define SIG_DIGS 2
+
+ /*
+ * Event management.
+ */
+
+/* PSC_READ_EVENT_REQUEST - prepare for transition to next state */
+
+#define PSC_READ_EVENT_REQUEST(fd, action, context, timeout) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: read-request fd=%d", myname, (fd)); \
+ event_enable_read((fd), (action), (context)); \
+ event_request_timer((action), (context), (timeout)); \
+ } while (0)
+
+#define PSC_READ_EVENT_REQUEST2(fd, read_act, time_act, context, timeout) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: read-request fd=%d", myname, (fd)); \
+ event_enable_read((fd), (read_act), (context)); \
+ event_request_timer((time_act), (context), (timeout)); \
+ } while (0)
+
+/* PSC_CLEAR_EVENT_REQUEST - complete state transition */
+
+#define PSC_CLEAR_EVENT_REQUEST(fd, time_act, context) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: clear-request fd=%d", myname, (fd)); \
+ event_disable_readwrite(fd); \
+ event_cancel_timer((time_act), (context)); \
+ } while (0)
+
+ /*
+ * Failure enforcement policies.
+ */
+#define PSC_NAME_ACT_DROP "drop"
+#define PSC_NAME_ACT_ENFORCE "enforce"
+#define PSC_NAME_ACT_IGNORE "ignore"
+#define PSC_NAME_ACT_CONT "continue"
+
+#define PSC_ACT_DROP 1
+#define PSC_ACT_ENFORCE 2
+#define PSC_ACT_IGNORE 3
+
+ /*
+ * Global variables.
+ */
+extern int psc_check_queue_length; /* connections being checked */
+extern int psc_post_queue_length; /* being sent to real SMTPD */
+extern DICT_CACHE *psc_cache_map; /* cache table handle */
+extern VSTRING *psc_temp; /* scratchpad */
+extern char *psc_smtpd_service_name; /* path to real SMTPD */
+extern int psc_pregr_action; /* PSC_ACT_DROP etc. */
+extern int psc_dnsbl_action; /* PSC_ACT_DROP etc. */
+extern int psc_pipel_action; /* PSC_ACT_DROP etc. */
+extern int psc_nsmtp_action; /* PSC_ACT_DROP etc. */
+extern int psc_barlf_action; /* PSC_ACT_DROP etc. */
+extern int psc_min_ttl; /* Update with new tests! */
+extern STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */
+extern int psc_stress_greet_wait; /* stressed greet wait */
+extern int psc_normal_greet_wait; /* stressed greet wait */
+extern int psc_stress_cmd_time_limit; /* stressed command limit */
+extern int psc_normal_cmd_time_limit; /* normal command time limit */
+extern int psc_stress; /* stress level */
+extern int psc_lowat_check_queue_length;/* stress low-water mark */
+extern int psc_hiwat_check_queue_length;/* stress high-water mark */
+extern DICT *psc_dnsbl_reply; /* DNSBL name mapper */
+extern HTABLE *psc_client_concurrency; /* per-client concurrency */
+
+#define PSC_EFF_GREET_WAIT \
+ (psc_stress ? psc_stress_greet_wait : psc_normal_greet_wait)
+#define PSC_EFF_CMD_TIME_LIMIT \
+ (psc_stress ? psc_stress_cmd_time_limit : psc_normal_cmd_time_limit)
+
+ /*
+ * String plumbing macros.
+ */
+#define PSC_STRING_UPDATE(str, text) do { \
+ if (str) myfree(str); \
+ (str) = ((text) ? mystrdup(text) : 0); \
+ } while (0)
+
+#define PSC_STRING_RESET(str) do { \
+ if (str) { \
+ myfree(str); \
+ (str) = 0; \
+ } \
+ } while (0)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * postscreen_state.c
+ */
+#define PSC_CLIENT_ADDR_PORT(state) \
+ (state)->smtp_client_addr, (state)->smtp_client_port
+
+#define PSC_PASS_SESSION_STATE(state, what, bits) do { \
+ if (msg_verbose) \
+ msg_info("PASS %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_FAIL_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("FAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_SKIP_SESSION_STATE(state, what, bits) do { \
+ if (msg_verbose) \
+ msg_info("SKIP %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_DROP_SESSION_STATE(state, reply) do { \
+ if (msg_verbose) \
+ msg_info("DROP [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \
+ (state)->final_reply = (reply); \
+ psc_conclude(state); \
+ } while (0)
+#define PSC_ENFORCE_SESSION_STATE(state, reply) do { \
+ if (msg_verbose) \
+ msg_info("ENFORCE [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->rcpt_reply = (reply); \
+ (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \
+ } while (0)
+#define PSC_UNPASS_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("UNPASS [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags &= ~(bits); \
+ } while (0)
+#define PSC_UNFAIL_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("UNFAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags &= ~(bits); \
+ } while (0)
+#define PSC_ADD_SERVER_STATE(state, fd) do { \
+ (state)->smtp_server_fd = (fd); \
+ psc_post_queue_length++; \
+ } while (0)
+#define PSC_DEL_SERVER_STATE(state) do { \
+ close((state)->smtp_server_fd); \
+ (state)->smtp_server_fd = (-1); \
+ psc_post_queue_length--; \
+ } while (0)
+#define PSC_DEL_CLIENT_STATE(state) do { \
+ event_server_disconnect((state)->smtp_client_stream); \
+ (state)->smtp_client_stream = 0; \
+ psc_check_queue_length--; \
+ } while (0)
+extern PSC_STATE *psc_new_session_state(VSTREAM *, const char *, const char *, const char *, const char *);
+extern void psc_free_session_state(PSC_STATE *);
+extern const char *psc_print_state_flags(int, const char *);
+
+ /*
+ * postscreen_dict.c
+ */
+extern int psc_addr_match_list_match(ADDR_MATCH_LIST *, const char *);
+extern const char *psc_cache_lookup(DICT_CACHE *, const char *);
+extern void psc_cache_update(DICT_CACHE *, const char *, const char *);
+const char *psc_dict_get(DICT *, const char *);
+const char *psc_maps_find(MAPS *, const char *, int);
+
+ /*
+ * postscreen_dnsbl.c
+ */
+extern void psc_dnsbl_init(void);
+extern int psc_dnsbl_retrieve(const char *, const char **, int, int *);
+extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *);
+
+ /*
+ * postscreen_tests.c
+ */
+#define PSC_INIT_TESTS(dst) do { \
+ time_t *_it_stamp_p; \
+ (dst)->flags = 0; \
+ for (_it_stamp_p = (dst)->client_info->expire_time; \
+ _it_stamp_p < (dst)->client_info->expire_time + PSC_TINDX_COUNT; \
+ _it_stamp_p++) \
+ *_it_stamp_p = PSC_TIME_STAMP_INVALID; \
+ } while (0)
+#define PSC_INIT_TEST_FLAGS_ONLY(dst) do { \
+ (dst)->flags = 0; \
+ } while (0)
+#define PSC_BEGIN_TESTS(state, name) do { \
+ (state)->test_name = (name); \
+ GETTIMEOFDAY(&(state)->start_time); \
+ } while (0)
+extern void psc_new_tests(PSC_STATE *);
+extern void psc_parse_tests(PSC_STATE *, const char *, time_t);
+extern void psc_todo_tests(PSC_STATE *, time_t);
+extern char *psc_print_tests(VSTRING *, PSC_STATE *);
+extern char *psc_print_grey_key(VSTRING *, const char *, const char *,
+ const char *, const char *);
+extern const char *psc_test_name(int);
+
+#define PSC_MIN(x, y) ((x) < (y) ? (x) : (y))
+#define PSC_MAX(x, y) ((x) > (y) ? (x) : (y))
+
+ /*
+ * postscreen_early.c
+ */
+extern void psc_early_tests(PSC_STATE *);
+extern void psc_early_init(void);
+
+ /*
+ * postscreen_smtpd.c
+ */
+extern void psc_smtpd_tests(PSC_STATE *);
+extern void psc_smtpd_init(void);
+extern void psc_smtpd_pre_jail_init(void);
+
+#define PSC_SMTPD_X21(state, reply) do { \
+ (state)->flags |= PSC_STATE_FLAG_SMTPD_X21; \
+ (state)->final_reply = (reply); \
+ psc_smtpd_tests(state); \
+ } while (0)
+
+ /*
+ * postscreen_misc.c
+ */
+extern char *psc_format_delta_time(VSTRING *, struct timeval, DELTA_TIME *);
+extern void psc_conclude(PSC_STATE *);
+extern void psc_hangup_event(PSC_STATE *);
+
+ /*
+ * postscreen_send.c
+ */
+#define PSC_SEND_REPLY psc_send_reply /* legacy macro */
+extern void pcs_send_pre_jail_init(void);
+extern int psc_send_reply(PSC_STATE *, const char *);
+extern void psc_send_socket(PSC_STATE *);
+
+ /*
+ * postscreen_starttls.c
+ */
+extern void psc_starttls_open(PSC_STATE *, EVENT_NOTIFY_FN);
+
+ /*
+ * postscreen_expand.c
+ */
+extern VSTRING *psc_expand_filter;
+extern void psc_expand_init(void);
+extern const char *psc_expand_lookup(const char *, int, void *);
+
+ /*
+ * postscreen_endpt.c
+ */
+typedef void (*PSC_ENDPT_LOOKUP_FN) (int, VSTREAM *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+extern void psc_endpt_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+extern void psc_endpt_local_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+
+ /*
+ * postscreen_access emulation.
+ */
+#define PSC_ACL_ACT_ALLOWLIST SERVER_ACL_ACT_PERMIT
+#define PSC_ACL_ACT_DUNNO SERVER_ACL_ACT_DUNNO
+#define PSC_ACL_ACT_DENYLIST SERVER_ACL_ACT_REJECT
+#define PSC_ACL_ACT_ERROR SERVER_ACL_ACT_ERROR
+
+#define psc_acl_pre_jail_init server_acl_pre_jail_init
+#define psc_acl_parse server_acl_parse
+#define psc_acl_eval(s,a,p) server_acl_eval((s)->smtp_client_addr, (a), (p))
+
+/* 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/postscreen/postscreen_dict.c b/src/postscreen/postscreen_dict.c
new file mode 100644
index 0000000..131ff81
--- /dev/null
+++ b/src/postscreen/postscreen_dict.c
@@ -0,0 +1,182 @@
+/*++
+/* NAME
+/* postscreen_dict 3
+/* SUMMARY
+/* postscreen table access wrappers
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* int psc_addr_match_list_match(match_list, client_addr)
+/* ADDR_MATCH_LIST *match_list;
+/* const char *client_addr;
+/*
+/* const char *psc_cache_lookup(DICT_CACHE *cache, const char *key)
+/* DICT_CACHE *cache;
+/* const char *key;
+/*
+/* void psc_cache_update(cache, key, value)
+/* DICT_CACHE *cache;
+/* const char *key;
+/* const char *value;
+/*
+/* void psc_dict_get(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* void psc_maps_find(maps, key, flags)
+/* MAPS *maps;
+/* const char *key;
+/* int flags;
+/* DESCRIPTION
+/* This module implements wrappers around time-critical table
+/* access functions. The functions log a warning when table
+/* access takes a non-trivial amount of time.
+/*
+/* psc_addr_match_list_match() is a wrapper around
+/* addr_match_list_match().
+/*
+/* psc_cache_lookup() and psc_cache_update() are wrappers around
+/* the corresponding dict_cache() methods.
+/*
+/* psc_dict_get() and psc_maps_find() are wrappers around
+/* dict_get() and maps_find(), respectively.
+/* 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 <dict.h>
+
+/* Global library. */
+
+#include <maps.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Monitor time-critical operations.
+ *
+ * XXX Averaging support was added during a stable release candidate, so it
+ * provides only the absolute minimum necessary. A complete implementation
+ * should maintain separate statistics for each table, and it should not
+ * complain when the access latency is less than the time between accesses.
+ */
+#define PSC_GET_TIME_BEFORE_LOOKUP { \
+ struct timeval _before, _after; \
+ DELTA_TIME _delta; \
+ double _new_delta_ms; \
+ GETTIMEOFDAY(&_before);
+
+#define PSC_DELTA_MS(d) ((d).dt_sec * 1000.0 + (d).dt_usec / 1000.0)
+
+#define PSC_AVERAGE(new, old) (0.1 * (new) + 0.9 * (old))
+
+#ifndef PSC_THRESHOLD_MS
+#define PSC_THRESHOLD_MS 100 /* nag if latency > 100ms */
+#endif
+
+#ifndef PSC_WARN_LOCKOUT_S
+#define PSC_WARN_LOCKOUT_S 60 /* don't nag for 60s */
+#endif
+
+ /*
+ * Shared warning lock, so that we don't spam the logfile when the system
+ * becomes slow.
+ */
+static time_t psc_last_warn = 0;
+
+#define PSC_CHECK_TIME_AFTER_LOOKUP(table, action, average) \
+ GETTIMEOFDAY(&_after); \
+ PSC_CALC_DELTA(_delta, _after, _before); \
+ _new_delta_ms = PSC_DELTA_MS(_delta); \
+ if ((average = PSC_AVERAGE(_new_delta_ms, average)) > PSC_THRESHOLD_MS \
+ && psc_last_warn < _after.tv_sec - PSC_WARN_LOCKOUT_S) { \
+ msg_warn("%s: %s %s average delay is %.0f ms", \
+ myname, (table), (action), average); \
+ psc_last_warn = _after.tv_sec; \
+ } \
+}
+
+/* psc_addr_match_list_match - time-critical address list lookup */
+
+int psc_addr_match_list_match(ADDR_MATCH_LIST *addr_list,
+ const char *addr_str)
+{
+ const char *myname = "psc_addr_match_list_match";
+ int result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = addr_match_list_match(addr_list, addr_str);
+ PSC_CHECK_TIME_AFTER_LOOKUP("address list", "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_cache_lookup - time-critical cache lookup */
+
+const char *psc_cache_lookup(DICT_CACHE *cache, const char *key)
+{
+ const char *myname = "psc_cache_lookup";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = dict_cache_lookup(cache, key);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_cache_update - time-critical cache update */
+
+void psc_cache_update(DICT_CACHE *cache, const char *key, const char *value)
+{
+ const char *myname = "psc_cache_update";
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ dict_cache_update(cache, key, value);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "update", latency_ms);
+}
+
+/* psc_dict_get - time-critical table lookup */
+
+const char *psc_dict_get(DICT *dict, const char *key)
+{
+ const char *myname = "psc_dict_get";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = dict_get(dict, key);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict->name, "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_maps_find - time-critical table lookup */
+
+const char *psc_maps_find(MAPS *maps, const char *key, int flags)
+{
+ const char *myname = "psc_maps_find";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = maps_find(maps, key, flags);
+ PSC_CHECK_TIME_AFTER_LOOKUP(maps->title, "lookup", latency_ms);
+ return (result);
+}
diff --git a/src/postscreen/postscreen_dnsbl.c b/src/postscreen/postscreen_dnsbl.c
new file mode 100644
index 0000000..7d9a5e9
--- /dev/null
+++ b/src/postscreen/postscreen_dnsbl.c
@@ -0,0 +1,624 @@
+/*++
+/* NAME
+/* postscreen_dnsbl 3
+/* SUMMARY
+/* postscreen DNSBL support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_dnsbl_init(void)
+/*
+/* int psc_dnsbl_request(client_addr, callback, context)
+/* char *client_addr;
+/* void (*callback)(int, char *);
+/* char *context;
+/*
+/* int psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
+/* dnsbl_ttl)
+/* char *client_addr;
+/* const char **dnsbl_name;
+/* int dnsbl_index;
+/* int *dnsbl_ttl;
+/* DESCRIPTION
+/* This module implements preliminary support for DNSBL lookups.
+/* Multiple requests for the same information are handled with
+/* reference counts.
+/*
+/* psc_dnsbl_init() initializes this module, and must be called
+/* once before any of the other functions in this module.
+/*
+/* psc_dnsbl_request() requests a blocklist score for the
+/* specified client IP address and increments the reference
+/* count. The request completes in the background. The client
+/* IP address must be in inet_ntop(3) output format. The
+/* callback argument specifies a function that is called when
+/* the requested result is available. The context is passed
+/* on to the callback function. The callback should ignore its
+/* first argument (it exists for compatibility with Postfix
+/* generic event infrastructure).
+/* The result value is the index for the psc_dnsbl_retrieve()
+/* call.
+/*
+/* psc_dnsbl_retrieve() retrieves the result score and reply
+/* TTL requested with psc_dnsbl_request(), and decrements the
+/* reference count. The reply TTL value is clamped to
+/* postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl. It
+/* is an error to retrieve a score without requesting it first.
+/* 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 <netinet/in.h> /* inet_pton() */
+#include <arpa/inet.h> /* inet_pton() */
+#include <stdio.h> /* sscanf */
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <htable.h>
+#include <events.h>
+#include <vstream.h>
+#include <connect.h>
+#include <split_at.h>
+#include <valid_hostname.h>
+#include <ip_match.h>
+#include <myaddrinfo.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Talking to the DNSBLOG service.
+ */
+static char *psc_dnsbl_service;
+
+ /*
+ * Per-DNSBL filters and weights.
+ *
+ * The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains.
+ * We provide multiple access methods, one for quick iteration when sending
+ * queries to all DNSBL servers, and one for quick location when receiving a
+ * reply from one DNSBL server.
+ *
+ * Each DNSBL domain can be specified more than once, each time with a
+ * different (filter, weight) pair. We group (filter, weight) pairs in a
+ * linked list under their DNSBL domain name. The list head has a reference
+ * to a "safe name" for the DNSBL, in case the name includes a password.
+ */
+static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */
+static HTABLE_INFO **dnsbl_site_list; /* flattened cache */
+
+typedef struct {
+ const char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */
+ struct PSC_DNSBL_SITE *first; /* list of (filter, weight) tuples */
+} PSC_DNSBL_HEAD;
+
+typedef struct PSC_DNSBL_SITE {
+ char *filter; /* printable filter (default: null) */
+ char *byte_codes; /* encoded filter (default: null) */
+ int weight; /* reply weight (default: 1) */
+ struct PSC_DNSBL_SITE *next; /* linked list */
+} PSC_DNSBL_SITE;
+
+ /*
+ * Per-client DNSBL scores.
+ *
+ * Some SMTP clients make parallel connections. This can trigger parallel
+ * blocklist score requests when the pre-handshake delays of the connections
+ * overlap.
+ *
+ * We combine requests for the same score under the client IP address in a
+ * single reference-counted entry. The reference count goes up with each
+ * request for a score, and it goes down with each score retrieval. Each
+ * score has one or more requestors that need to be notified when the result
+ * is ready, so that postscreen can terminate a pre-handshake delay when all
+ * pre-handshake tests are completed.
+ */
+static HTABLE *dnsbl_score_cache; /* indexed by client address */
+
+typedef struct {
+ void (*callback) (int, void *); /* generic call-back routine */
+ void *context; /* generic call-back argument */
+} PSC_CALL_BACK_ENTRY;
+
+typedef struct {
+ const char *dnsbl_name; /* DNSBL with largest contribution */
+ int dnsbl_weight; /* weight of largest contribution */
+ int total; /* combined allow+denylist score */
+ int fail_ttl; /* combined reply TTL */
+ int pass_ttl; /* combined reply TTL */
+ int refcount; /* score reference count */
+ int pending_lookups; /* nr of DNS requests in flight */
+ int request_id; /* duplicate suppression */
+ /* Call-back table support. */
+ int index; /* next table index */
+ int limit; /* last valid index */
+ PSC_CALL_BACK_ENTRY table[1]; /* actually a bunch */
+} PSC_DNSBL_SCORE;
+
+#define PSC_CALL_BACK_INIT(sp) do { \
+ (sp)->limit = 0; \
+ (sp)->index = 0; \
+ } while (0)
+
+#define PSC_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1)
+
+#define PSC_CALL_BACK_CANCEL(sp, idx) do { \
+ PSC_CALL_BACK_ENTRY *_cb_; \
+ if ((idx) < 0 || (idx) >= (sp)->index) \
+ msg_panic("%s: index %d must be >= 0 and < %d", \
+ myname, (idx), (sp)->index); \
+ _cb_ = (sp)->table + (idx); \
+ event_cancel_timer(_cb_->callback, _cb_->context); \
+ _cb_->callback = 0; \
+ _cb_->context = 0; \
+ } while (0)
+
+#define PSC_CALL_BACK_EXTEND(hp, sp) do { \
+ if ((sp)->index >= (sp)->limit) { \
+ int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5); \
+ (hp)->value = myrealloc((void *) (sp), sizeof(*(sp)) + \
+ _count_ * sizeof((sp)->table)); \
+ (sp) = (PSC_DNSBL_SCORE *) (hp)->value; \
+ (sp)->limit = _count_; \
+ } \
+ } while (0)
+
+#define PSC_CALL_BACK_ENTER(sp, fn, ctx) do { \
+ PSC_CALL_BACK_ENTRY *_cb_ = (sp)->table + (sp)->index++; \
+ _cb_->callback = (fn); \
+ _cb_->context = (ctx); \
+ } while (0)
+
+#define PSC_CALL_BACK_NOTIFY(sp, ev) do { \
+ PSC_CALL_BACK_ENTRY *_cb_; \
+ for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
+ if (_cb_->callback != 0) \
+ _cb_->callback((ev), _cb_->context); \
+ } while (0)
+
+#define PSC_NULL_EVENT (0)
+
+ /*
+ * Per-request state.
+ *
+ * This implementation stores the client IP address and DNSBL domain in the
+ * DNSBLOG query/reply stream. This simplifies code, and allows the DNSBLOG
+ * server to produce more informative logging.
+ */
+static VSTRING *reply_client; /* client address in DNSBLOG reply */
+static VSTRING *reply_dnsbl; /* domain in DNSBLOG reply */
+static VSTRING *reply_addr; /* address list in DNSBLOG reply */
+
+/* psc_dnsbl_add_site - add DNSBL site information */
+
+static void psc_dnsbl_add_site(const char *site)
+{
+ const char *myname = "psc_dnsbl_add_site";
+ char *saved_site = mystrdup(site);
+ VSTRING *byte_codes = 0;
+ PSC_DNSBL_HEAD *head;
+ PSC_DNSBL_SITE *new_site;
+ char junk;
+ const char *weight_text;
+ char *pattern_text;
+ int weight;
+ HTABLE_INFO *ht;
+ char *parse_err;
+ const char *safe_dnsbl;
+
+ /*
+ * Parse the required DNSBL domain name, the optional reply filter and
+ * the optional reply weight factor.
+ */
+#define DO_GRIPE 1
+
+ /* Negative weight means allowlist. */
+ if ((weight_text = split_at(saved_site, '*')) != 0) {
+ if (sscanf(weight_text, "%d%c", &weight, &junk) != 1)
+ msg_fatal("bad DNSBL weight factor \"%s\" in \"%s\"",
+ weight_text, site);
+ } else {
+ weight = 1;
+ }
+ /* Reply filter. */
+ if ((pattern_text = split_at(saved_site, '=')) != 0) {
+ byte_codes = vstring_alloc(100);
+ if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0)
+ msg_fatal("bad DNSBL filter syntax: %s", parse_err);
+ }
+ if (valid_hostname(saved_site, DO_GRIPE) == 0)
+ msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"",
+ saved_site, site);
+
+ if (msg_verbose > 1)
+ msg_info("%s: \"%s\" -> domain=\"%s\" pattern=\"%s\" weight=%d",
+ myname, site, saved_site, pattern_text ? pattern_text :
+ "null", weight);
+
+ /*
+ * Look up or create the (filter, weight) list head for this DNSBL domain
+ * name.
+ */
+ if ((head = (PSC_DNSBL_HEAD *)
+ htable_find(dnsbl_site_cache, saved_site)) == 0) {
+ head = (PSC_DNSBL_HEAD *) mymalloc(sizeof(*head));
+ ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head);
+ /* Translate the DNSBL name into a safe name if available. */
+ if (psc_dnsbl_reply == 0
+ || (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0)
+ safe_dnsbl = ht->key;
+ head->safe_dnsbl = mystrdup(safe_dnsbl);
+ if (psc_dnsbl_reply && psc_dnsbl_reply->error)
+ msg_fatal("%s:%s lookup error", psc_dnsbl_reply->type,
+ psc_dnsbl_reply->name);
+ head->first = 0;
+ }
+
+ /*
+ * Append the new (filter, weight) node to the list for this DNSBL domain
+ * name.
+ */
+ new_site = (PSC_DNSBL_SITE *) mymalloc(sizeof(*new_site));
+ new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0);
+ new_site->byte_codes = (byte_codes ? ip_match_save(byte_codes) : 0);
+ new_site->weight = weight;
+ new_site->next = head->first;
+ head->first = new_site;
+
+ myfree(saved_site);
+ if (byte_codes)
+ vstring_free(byte_codes);
+}
+
+/* psc_dnsbl_match - match DNSBL reply filter */
+
+static int psc_dnsbl_match(const char *filter, ARGV *reply)
+{
+ char addr_buf[MAI_HOSTADDR_STRSIZE];
+ char **cpp;
+
+ /*
+ * Run the replies through the pattern-matching engine.
+ */
+ for (cpp = reply->argv; *cpp != 0; cpp++) {
+ if (inet_pton(AF_INET, *cpp, addr_buf) != 1)
+ msg_warn("address conversion error for %s -- ignoring this reply",
+ *cpp);
+ if (ip_match_execute(filter, addr_buf))
+ return (1);
+ }
+ return (0);
+}
+
+/* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
+
+int psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
+ int dnsbl_index, int *dnsbl_ttl)
+{
+ const char *myname = "psc_dnsbl_retrieve";
+ PSC_DNSBL_SCORE *score;
+ int result_score;
+ int result_ttl;
+
+ /*
+ * Sanity check.
+ */
+ if ((score = (PSC_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, client_addr)) == 0)
+ msg_panic("%s: no blocklist score for %s", myname, client_addr);
+
+ /*
+ * Disable callbacks.
+ */
+ PSC_CALL_BACK_CANCEL(score, dnsbl_index);
+
+ /*
+ * Reads are destructive.
+ */
+ result_score = score->total;
+ *dnsbl_name = score->dnsbl_name;
+ result_ttl = (result_score > 0) ? score->fail_ttl : score->pass_ttl;
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (result_ttl < var_psc_dnsbl_min_ttl)
+ result_ttl = var_psc_dnsbl_min_ttl;
+ if (result_ttl > var_psc_dnsbl_max_ttl)
+ result_ttl = var_psc_dnsbl_max_ttl;
+ *dnsbl_ttl = result_ttl;
+ if (msg_verbose)
+ msg_info("%s: addr=%s score=%d ttl=%d",
+ myname, client_addr, result_score, result_ttl);
+ score->refcount -= 1;
+ if (score->refcount < 1) {
+ if (msg_verbose > 1)
+ msg_info("%s: delete blocklist score for %s", myname, client_addr);
+ htable_delete(dnsbl_score_cache, client_addr, myfree);
+ }
+ return (result_score);
+}
+
+/* psc_dnsbl_receive - receive DNSBL reply, update blocklist score */
+
+static void psc_dnsbl_receive(int event, void *context)
+{
+ const char *myname = "psc_dnsbl_receive";
+ VSTREAM *stream = (VSTREAM *) context;
+ PSC_DNSBL_SCORE *score;
+ PSC_DNSBL_HEAD *head;
+ PSC_DNSBL_SITE *site;
+ ARGV *reply_argv;
+ int request_id;
+ int dnsbl_ttl;
+
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);
+
+ /*
+ * Receive the DNSBL lookup result.
+ *
+ * This is preliminary code to explore the field. Later, DNSBL lookup will
+ * be handled by an UDP-based DNS client that is built directly into some
+ * Postfix daemon.
+ *
+ * Don't bother looking up the blocklist score when the client IP address is
+ * not listed at the DNSBL.
+ *
+ * Don't panic when the blocklist score no longer exists. It may be deleted
+ * when the client triggers a "drop" action after pregreet, when the
+ * client does not pregreet and the DNSBL reply arrives late, or when the
+ * client triggers a "drop" action after hanging up.
+ */
+ if (event == EVENT_READ
+ && attr_scan(stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl),
+ RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
+ RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
+ RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
+ ATTR_TYPE_END) == 5
+ && (score = (PSC_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, STR(reply_client))) != 0
+ && score->request_id == request_id) {
+
+ /*
+ * Run this response past all applicable DNSBL filters and update the
+ * blocklist score for this client IP address.
+ *
+ * Don't panic when the DNSBL domain name is not found. The DNSBLOG
+ * server may be messed up.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"",
+ myname, STR(reply_client), score->total,
+ STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
+ head = (PSC_DNSBL_HEAD *)
+ htable_find(dnsbl_site_cache, STR(reply_dnsbl));
+ if (head == 0) {
+ /* Bogus domain. Do nothing. */
+ } else if (*STR(reply_addr) != 0) {
+ /* DNS reputation record(s) found. */
+ reply_argv = 0;
+ for (site = head->first; site != 0; site = site->next) {
+ if (site->byte_codes == 0
+ || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
+ (reply_argv = argv_split(STR(reply_addr), " ")))) {
+ if (score->dnsbl_name == 0
+ || score->dnsbl_weight < site->weight) {
+ score->dnsbl_name = head->safe_dnsbl;
+ score->dnsbl_weight = site->weight;
+ }
+ score->total += site->weight;
+ if (msg_verbose > 1)
+ msg_info("%s: filter=\"%s\" weight=%d score=%d",
+ myname, site->filter ? site->filter : "null",
+ site->weight, score->total);
+ }
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (site->weight > 0) {
+ if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+ score->fail_ttl = dnsbl_ttl;
+ } else {
+ if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+ score->pass_ttl = dnsbl_ttl;
+ }
+ }
+ if (reply_argv != 0)
+ argv_free(reply_argv);
+ } else {
+ /* No DNS reputation record found. */
+ for (site = head->first; site != 0; site = site->next) {
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (site->weight > 0) {
+ if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+ score->pass_ttl = dnsbl_ttl;
+ } else {
+ if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+ score->fail_ttl = dnsbl_ttl;
+ }
+ }
+ }
+
+ /*
+ * Notify the requestor(s) that the result is ready to be picked up.
+ * If this call isn't made, clients have to sit out the entire
+ * pre-handshake delay.
+ */
+ score->pending_lookups -= 1;
+ if (score->pending_lookups == 0)
+ PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
+ } else if (event == EVENT_TIME) {
+ msg_warn("dnsblog reply timeout %ds for %s",
+ var_psc_dnsbl_tmout, (char *) vstream_context(stream));
+ }
+ /* Here, score may be a null pointer. */
+ vstream_fclose(stream);
+}
+
+/* psc_dnsbl_request - send dnsbl query, increment reference count */
+
+int psc_dnsbl_request(const char *client_addr,
+ void (*callback) (int, void *),
+ void *context)
+{
+ const char *myname = "psc_dnsbl_request";
+ int fd;
+ VSTREAM *stream;
+ HTABLE_INFO **ht;
+ PSC_DNSBL_SCORE *score;
+ HTABLE_INFO *hash_node;
+ static int request_count;
+
+ /*
+ * Some spambots make several connections at nearly the same time,
+ * causing their pregreet delays to overlap. Such connections can share
+ * the efforts of DNSBL lookup.
+ *
+ * We store a reference-counted DNSBL score under its client IP address. We
+ * increment the reference count with each score request, and decrement
+ * the reference count with each score retrieval.
+ *
+ * Do not notify the requestor NOW when the DNS replies are already in.
+ * Reason: we must not make a backwards call while we are still in the
+ * middle of executing the corresponding forward call. Instead we create
+ * a zero-delay timer request and call the notification function from
+ * there.
+ *
+ * psc_dnsbl_request() could instead return a result value to indicate that
+ * the DNSBL score is already available, but that would complicate the
+ * caller with two different notification code paths: one asynchronous
+ * code path via the callback invocation, and one synchronous code path
+ * via the psc_dnsbl_request() result value. That would be a source of
+ * future bugs.
+ */
+ if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) {
+ score = (PSC_DNSBL_SCORE *) hash_node->value;
+ score->refcount += 1;
+ PSC_CALL_BACK_EXTEND(hash_node, score);
+ PSC_CALL_BACK_ENTER(score, callback, context);
+ if (msg_verbose > 1)
+ msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d",
+ myname, client_addr, score->refcount,
+ score->pending_lookups);
+ if (score->pending_lookups == 0)
+ event_request_timer(callback, context, EVENT_NULL_DELAY);
+ return (PSC_CALL_BACK_INDEX_OF_LAST(score));
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: create blocklist score for %s", myname, client_addr);
+ score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score));
+ score->request_id = request_count++;
+ score->dnsbl_name = 0;
+ score->dnsbl_weight = 0;
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ score->pass_ttl = -1;
+ score->fail_ttl = -1;
+ score->total = 0;
+ score->refcount = 1;
+ score->pending_lookups = 0;
+ PSC_CALL_BACK_INIT(score);
+ PSC_CALL_BACK_ENTER(score, callback, context);
+ (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score);
+
+ /*
+ * Send a query to all DNSBL servers. Later, DNSBL lookup will be done
+ * with an UDP-based DNS client that is built directly into Postfix code.
+ * We therefore do not optimize the maximum out of this temporary
+ * implementation.
+ */
+ for (ht = dnsbl_site_list; *ht; ht++) {
+ if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) {
+ msg_warn("%s: connect to %s service: %m",
+ myname, psc_dnsbl_service);
+ continue;
+ }
+ stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_CONTEXT(ht[0]->key),
+ CA_VSTREAM_CTL_END);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr),
+ SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id),
+ ATTR_TYPE_END);
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("%s: error sending to %s service: %m",
+ myname, psc_dnsbl_service);
+ vstream_fclose(stream);
+ continue;
+ }
+ PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive,
+ (void *) stream, var_psc_dnsbl_tmout);
+ score->pending_lookups += 1;
+ }
+ return (PSC_CALL_BACK_INDEX_OF_LAST(score));
+}
+
+/* psc_dnsbl_init - initialize */
+
+void psc_dnsbl_init(void)
+{
+ const char *myname = "psc_dnsbl_init";
+ ARGV *dnsbl_site = argv_split(var_psc_dnsbl_sites, CHARS_COMMA_SP);
+ char **cpp;
+
+ /*
+ * Sanity check.
+ */
+ if (dnsbl_site_cache != 0)
+ msg_panic("%s: called more than once", myname);
+
+ /*
+ * pre-compute the DNSBLOG socket name.
+ */
+ psc_dnsbl_service = concatenate(MAIL_CLASS_PRIVATE, "/",
+ var_dnsblog_service, (char *) 0);
+
+ /*
+ * Prepare for quick iteration when sending out queries to all DNSBL
+ * servers, and for quick lookup when a reply arrives from a specific
+ * DNSBL server.
+ */
+ dnsbl_site_cache = htable_create(13);
+ for (cpp = dnsbl_site->argv; *cpp; cpp++)
+ psc_dnsbl_add_site(*cpp);
+ argv_free(dnsbl_site);
+ dnsbl_site_list = htable_list(dnsbl_site_cache);
+
+ /*
+ * The per-client blocklist score.
+ */
+ dnsbl_score_cache = htable_create(13);
+
+ /*
+ * Space for ad-hoc DNSBLOG server request/reply parameters.
+ */
+ reply_client = vstring_alloc(100);
+ reply_dnsbl = vstring_alloc(100);
+ reply_addr = vstring_alloc(100);
+}
diff --git a/src/postscreen/postscreen_early.c b/src/postscreen/postscreen_early.c
new file mode 100644
index 0000000..c9d8faf
--- /dev/null
+++ b/src/postscreen/postscreen_early.c
@@ -0,0 +1,377 @@
+/*++
+/* NAME
+/* postscreen_early 3
+/* SUMMARY
+/* postscreen pre-handshake tests
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_early_init(void)
+/*
+/* void psc_early_tests(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* psc_early_tests() performs protocol tests before the SMTP
+/* handshake: the pregreet test and the DNSBL test. Control
+/* is passed to the psc_smtpd_tests() routine as appropriate.
+/*
+/* psc_early_init() performs one-time 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 <sys/socket.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+static char *psc_teaser_greeting;
+static VSTRING *psc_escape_buf;
+
+/* psc_allowlist_non_dnsbl - allowlist pending non-dnsbl tests */
+
+static void psc_allowlist_non_dnsbl(PSC_STATE *state)
+{
+ time_t now;
+ int tindx;
+
+ /*
+ * If no tests failed (we can't undo those), and if the allowlist
+ * threshold is met, flag non-dnsbl tests that are pending or disabled as
+ * successfully completed, and set their expiration times equal to the
+ * DNSBL expiration time, except for tests that would expire later.
+ *
+ * Why flag disabled tests as passed? When a disabled test is turned on,
+ * postscreen should not apply that test to clients that are already
+ * allowlisted based on their combined DNSBL score.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->dnsbl_score < var_psc_dnsbl_thresh
+ && var_psc_dnsbl_althresh < 0
+ && state->dnsbl_score <= var_psc_dnsbl_althresh) {
+ now = event_time();
+ for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) {
+ if (tindx == PSC_TINDX_DNSBL)
+ continue;
+ if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx))
+ && !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) {
+ if (msg_verbose)
+ msg_info("skip %s test for [%s]:%s",
+ psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state));
+ /* Wrong for deep protocol tests, but we disable those. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx);
+ /* This also disables pending deep protocol tests. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
+ }
+ /* Update expiration even if the test was completed or disabled. */
+ if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl)
+ state->client_info->expire_time[tindx] = now + state->dnsbl_ttl;
+ }
+ }
+}
+
+/* psc_early_event - handle pre-greet, EOF, and DNSBL results. */
+
+static void psc_early_event(int event, void *context)
+{
+ const char *myname = "psc_early_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t *expire_time = state->client_info->expire_time;
+ char read_buf[PSC_READ_BUF_SIZE];
+ int read_count;
+ DELTA_TIME elapsed;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, context);
+
+ /*
+ * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a
+ * memory leak.
+ *
+ * XXX We can avoid "forgetting" to do this by keeping a pointer to the
+ * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
+ * shave off a hash table lookup when retrieving the DNSBL result.
+ *
+ * A direct pointer increases the odds of dangling pointers. Hash-table
+ * lookup is safer, and that is why it's done that way.
+ */
+ switch (event) {
+
+ /*
+ * We either reached the end of the early tests time limit, or all
+ * early tests completed before the pregreet timer would go off.
+ */
+ case EVENT_TIME:
+
+ /*
+ * Check if the SMTP client spoke before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
+ expire_time[PSC_TINDX_PREGR] = event_time() + var_psc_pregr_ttl;
+ PSC_PASS_SESSION_STATE(state, "pregreet test",
+ PSC_STATE_FLAG_PREGR_PASS);
+ }
+ if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL)
+ && psc_pregr_action == PSC_ACT_IGNORE) {
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */
+ }
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ * Note: this score will be partial when some DNS lookup did not
+ * complete before the pregreet timer expired.
+ *
+ * If the client is DNS blocklisted, drop the connection, send the
+ * client to a dummy protocol engine, or continue to the next test.
+ */
+#define PSC_DNSBL_FORMAT \
+ "%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
+#define NO_DNSBL_SCORE INT_MAX
+
+ if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
+ if (state->dnsbl_score == NO_DNSBL_SCORE) {
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+ }
+ if (state->dnsbl_score < var_psc_dnsbl_thresh) {
+ expire_time[PSC_TINDX_DNSBL] = event_time() + state->dnsbl_ttl;
+ PSC_PASS_SESSION_STATE(state, "dnsbl test",
+ PSC_STATE_FLAG_DNSBL_PASS);
+ } else {
+ msg_info("DNSBL rank %d for [%s]:%s",
+ state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ switch (psc_dnsbl_action) {
+ case PSC_ACT_DROP:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "521",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
+ return;
+ case PSC_ACT_ENFORCE:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "550",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */
+ break;
+ default:
+ msg_panic("%s: unknown dnsbl action value %d",
+ myname, psc_dnsbl_action);
+
+ }
+ }
+ }
+
+ /*
+ * Pass the connection to a real SMTP server, or enter the dummy
+ * engine for deep tests.
+ */
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
+ || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
+ != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
+ psc_smtpd_tests(state);
+ else
+ psc_conclude(state);
+ return;
+
+ /*
+ * EOF, or the client spoke before its turn. We simply drop the
+ * connection, or we continue waiting and allow DNS replies to
+ * trickle in.
+ */
+ default:
+ if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
+ read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ /* XXX Wait for DNS replies to come in. */
+ psc_hangup_event(state);
+ return;
+ }
+ read_buf[read_count] = 0;
+ escape(psc_escape_buf, read_buf, read_count);
+ msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count,
+ psc_format_delta_time(psc_temp, state->start_time, &elapsed),
+ PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ switch (psc_pregr_action) {
+ case PSC_ACT_DROP:
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ /* We must handle this case after the timer expires. */
+ break;
+ default:
+ msg_panic("%s: unknown pregreet action value %d",
+ myname, psc_pregr_action);
+ }
+
+ /*
+ * Terminate the greet delay if we're just waiting for the pregreet
+ * test to complete. It is safe to call psc_early_event directly,
+ * since we are already in that function.
+ *
+ * XXX After this code passes all tests, swap around the two blocks in
+ * this switch statement and fall through from EVENT_READ into
+ * EVENT_TIME, instead of calling psc_early_event recursively.
+ */
+ state->flags |= PSC_STATE_FLAG_PREGR_DONE;
+ if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT
+ || ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)))
+ psc_early_event(EVENT_TIME, context);
+ else
+ event_request_timer(psc_early_event, context,
+ PSC_EFF_GREET_WAIT - elapsed.dt_sec);
+ return;
+ }
+}
+
+/* psc_early_dnsbl_event - cancel pregreet timer if waiting for DNS only */
+
+static void psc_early_dnsbl_event(int unused_event, void *context)
+{
+ const char *myname = "psc_early_dnsbl_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose)
+ msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state));
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ */
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
+ state->dnsbl_index, &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+
+ /*
+ * Terminate the greet delay if we're just waiting for DNSBL lookup to
+ * complete. Don't call psc_early_event directly, that would result in a
+ * dangling pointer.
+ */
+ state->flags |= PSC_STATE_FLAG_DNSBL_DONE;
+ if ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))
+ event_request_timer(psc_early_event, context, EVENT_NULL_DELAY);
+}
+
+/* psc_early_tests - start the early (before protocol) tests */
+
+void psc_early_tests(PSC_STATE *state)
+{
+ const char *myname = "psc_early_tests";
+
+ /*
+ * Report errors and progress in the context of this test.
+ */
+ PSC_BEGIN_TESTS(state, "tests before SMTP handshake");
+
+ /*
+ * Run a PREGREET test. Send half the greeting banner, by way of teaser,
+ * then wait briefly to see if the client speaks before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && psc_teaser_greeting != 0
+ && PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) {
+ psc_hangup_event(state);
+ return;
+ }
+
+ /*
+ * Run a DNS blocklist query.
+ */
+ if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0)
+ state->dnsbl_index =
+ psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event,
+ (void *) state);
+ else
+ state->dnsbl_index = -1;
+ state->dnsbl_score = NO_DNSBL_SCORE;
+
+ /*
+ * Wait for the client to respond or for DNS lookup to complete.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0)
+ PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+ else
+ event_request_timer(psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+}
+
+/* psc_early_init - initialize early tests */
+
+void psc_early_init(void)
+{
+ if (*var_psc_pregr_banner) {
+ vstring_sprintf(psc_temp, "220-%s\r\n", var_psc_pregr_banner);
+ psc_teaser_greeting = mystrdup(STR(psc_temp));
+ psc_escape_buf = vstring_alloc(100);
+ }
+}
diff --git a/src/postscreen/postscreen_endpt.c b/src/postscreen/postscreen_endpt.c
new file mode 100644
index 0000000..36949e3
--- /dev/null
+++ b/src/postscreen/postscreen_endpt.c
@@ -0,0 +1,232 @@
+/*++
+/* NAME
+/* postscreen_endpt 3
+/* SUMMARY
+/* look up connection endpoint information
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_endpt_lookup(smtp_client_stream, lookup_done)
+/* VSTREAM *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* AUXILIARY METHODS
+/* void psc_endpt_local_lookup(smtp_client_stream, lookup_done)
+/* VSTREAM *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* DESCRIPTION
+/* psc_endpt_lookup() looks up remote and local connection
+/* endpoint information, either through local system calls,
+/* or through an adapter for an up-stream proxy protocol.
+/*
+/* The following summarizes what the postscreen(8) server
+/* expects from a proxy protocol adapter routine.
+/* .IP \(bu
+/* Accept the same arguments as psc_endpt_lookup().
+/* .IP \(bu
+/* Call psc_endpt_local_lookup() to look up connection info
+/* when the upstream proxy indicates that the connection is
+/* not proxied (e.g., health check probe).
+/* .IP \(bu
+/* Validate protocol, address and port syntax. Permit only
+/* protocols that are configured with the main.cf:inet_protocols
+/* setting.
+/* .IP \(bu
+/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when
+/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols.
+/* .IP \(bu
+/* Log a clear warning message that explains why a request
+/* fails.
+/* .IP \(bu
+/* Never talk to the remote SMTP client.
+/* .PP
+/* Arguments:
+/* .IP client_stream
+/* A brand-new stream that is connected to the remote client.
+/* This argument MUST be passed to psc_endpt_local_lookup()
+/* if the up-stream proxy indicates that a connection is not
+/* proxied.
+/* .IP lookup
+/* Call-back routine that reports the result status, address
+/* and port information. The result status is -1 in case of
+/* error, 0 in case of success. This MUST NOT be called directly
+/* if the up-stream proxy indicates that a connection is not
+/* proxied; instead this MUST be called indirectly by
+/* psc_endpt_local_lookup().
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <vstream.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+#include <postscreen_haproxy.h>
+
+static const INET_PROTO_INFO *proto_info;
+
+/* psc_sockaddr_to_hostaddr - transform endpoint address and port to string */
+
+static int psc_sockaddr_to_hostaddr(struct sockaddr *addr_storage,
+ SOCKADDR_SIZE addr_storage_len,
+ MAI_HOSTADDR_STR *addr_buf,
+ MAI_SERVPORT_STR *port_buf,
+ int socktype)
+{
+ int aierr;
+
+ if ((aierr = sockaddr_to_hostaddr(addr_storage, addr_storage_len,
+ addr_buf, port_buf, socktype)) == 0
+ && strncasecmp("::ffff:", addr_buf->buf, 7) == 0
+ && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
+ memmove(addr_buf->buf, addr_buf->buf + 7,
+ sizeof(addr_buf->buf) - 7);
+ return (aierr);
+}
+
+/* psc_endpt_local_lookup - look up local system connection information */
+
+void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
+ PSC_ENDPT_LOOKUP_FN lookup_done)
+{
+ struct sockaddr_storage addr_storage;
+ SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
+ int status;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int aierr;
+
+ /*
+ * Look up the remote SMTP client address and port.
+ */
+ if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
+ &addr_storage, &addr_storage_len) < 0) {
+ msg_warn("getpeername: %m -- dropping this connection");
+ status = -1;
+ }
+
+ /*
+ * Convert the remote SMTP client address and port to printable form for
+ * logging and access control.
+ */
+ else if ((aierr = psc_sockaddr_to_hostaddr(
+ (struct sockaddr *) &addr_storage,
+ addr_storage_len, &smtp_client_addr,
+ &smtp_client_port, SOCK_STREAM)) != 0) {
+ msg_warn("cannot convert client address/port to string: %s"
+ " -- dropping this connection",
+ MAI_STRERROR(aierr));
+ status = -1;
+ }
+
+ /*
+ * Look up the local SMTP server address and port.
+ */
+ else if (getsockname(vstream_fileno(smtp_client_stream),
+ (struct sockaddr *) &addr_storage,
+ &addr_storage_len) < 0) {
+ msg_warn("getsockname: %m -- dropping this connection");
+ status = -1;
+ }
+
+ /*
+ * Convert the local SMTP server address and port to printable form for
+ * logging.
+ */
+ else if ((aierr = psc_sockaddr_to_hostaddr(
+ (struct sockaddr *) &addr_storage,
+ addr_storage_len, &smtp_server_addr,
+ &smtp_server_port, SOCK_STREAM)) != 0) {
+ msg_warn("cannot convert server address/port to string: %s"
+ " -- dropping this connection",
+ MAI_STRERROR(aierr));
+ status = -1;
+ } else {
+ status = 0;
+ }
+ lookup_done(status, smtp_client_stream,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+}
+
+ /*
+ * Lookup table for available proxy protocols.
+ */
+typedef struct {
+ const char *name;
+ void (*endpt_lookup) (VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+} PSC_ENDPT_LOOKUP_INFO;
+
+static const PSC_ENDPT_LOOKUP_INFO psc_endpt_lookup_info[] = {
+ NOPROXY_PROTO_NAME, psc_endpt_local_lookup,
+ HAPROXY_PROTO_NAME, psc_endpt_haproxy_lookup,
+ 0,
+};
+
+/* psc_endpt_lookup - look up connection endpoint information */
+
+void psc_endpt_lookup(VSTREAM *smtp_client_stream,
+ PSC_ENDPT_LOOKUP_FN notify)
+{
+ const PSC_ENDPT_LOOKUP_INFO *pp;
+
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+
+ for (pp = psc_endpt_lookup_info; /* see below */ ; pp++) {
+ if (pp->name == 0)
+ msg_fatal("unsupported %s value: %s",
+ VAR_PSC_UPROXY_PROTO, var_psc_uproxy_proto);
+ if (strcmp(var_psc_uproxy_proto, pp->name) == 0) {
+ pp->endpt_lookup(smtp_client_stream, notify);
+ return;
+ }
+ }
+}
diff --git a/src/postscreen/postscreen_expand.c b/src/postscreen/postscreen_expand.c
new file mode 100644
index 0000000..ecda543
--- /dev/null
+++ b/src/postscreen/postscreen_expand.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* postscreen_expand 3
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_expand_init()
+/*
+/* VSTRING *psc_expand_filter;
+/*
+/* const char *psc_expand_lookup(name, unused_mode, context)
+/* const char *name;
+/* int unused_mode;
+/* char *context;
+/* DESCRIPTION
+/* This module expands session-related macros.
+/*
+/* psc_expand_init() performs one-time initialization
+/* of the psc_expand_filter buffer.
+/*
+/* The psc_expand_filter buffer contains the characters
+/* that are allowed in macro expansion, as specified with the
+/* psc_expand_filter configuration parameter.
+/*
+/* psc_expand_lookup() returns the value of the named
+/* macro or a null pointer.
+/*
+/* Arguments:
+/* .IP name
+/* Macro name.
+/* .IP context
+/* Call-back context (a PSC_STATE pointer).
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors. postscreen_expand() returns the
+/* binary OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF
+/* (undefined macro name).
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Pre-parsed expansion filter.
+ */
+VSTRING *psc_expand_filter;
+
+/* psc_expand_init - initialize once during process lifetime */
+
+void psc_expand_init(void)
+{
+
+ /*
+ * Expand the expansion filter :-)
+ */
+ psc_expand_filter = vstring_alloc(10);
+ unescape(psc_expand_filter, var_psc_exp_filter);
+}
+
+/* psc_expand_lookup - generic SMTP attribute $name expansion */
+
+const char *psc_expand_lookup(const char *name, int unused_mode,
+ void *context)
+{
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t now;
+ struct tm *lt;
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("psc_expand_lookup: ${%s}", name);
+
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
+#define CONST_LEN(x) (sizeof(x) - 1)
+
+ /*
+ * Don't query main.cf parameters, as the result of expansion could
+ * reveal system-internal information in server replies.
+ *
+ * XXX: This said, multiple servers may be behind a single client-visible
+ * name or IP address, and each may generate its own logs. Therefore, it
+ * may be useful to expose the replying MTA id (myhostname) in the
+ * contact footer, to identify the right logs. So while we don't expose
+ * the raw configuration dictionary, we do expose "$myhostname" as
+ * expanded in var_myhostname.
+ *
+ * Return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_SERVER_NAME)) {
+ return (var_myhostname);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) {
+ return (state->smtp_client_addr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) {
+ return (state->smtp_client_port);
+ } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
+ if (time(&now) == (time_t) -1)
+ msg_fatal("time lookup failed: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(state->expand_buf);
+ do {
+ VSTRING_SPACE(state->expand_buf, 100);
+ } while (strftime(STR(state->expand_buf),
+ vstring_avail(state->expand_buf),
+ "%b %d %H:%M:%S", lt) == 0);
+ return (STR(state->expand_buf));
+ } else {
+ msg_warn("unknown macro name \"%s\" in expansion request", name);
+ return (0);
+ }
+}
diff --git a/src/postscreen/postscreen_haproxy.c b/src/postscreen/postscreen_haproxy.c
new file mode 100644
index 0000000..45c6b9a
--- /dev/null
+++ b/src/postscreen/postscreen_haproxy.c
@@ -0,0 +1,137 @@
+/*++
+/* NAME
+/* postscreen_haproxy 3
+/* SUMMARY
+/* haproxy protocol adapter
+/* SYNOPSIS
+/* #include <postscreen_haproxy.h>
+/*
+/* void psc_endpt_haproxy_lookup(smtp_client_stream, lookup_done)
+/* VSTRING *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* DESCRIPTION
+/* psc_endpt_haproxy_lookup() looks up connection endpoint
+/* information via the haproxy protocol, or looks up local
+/* information if the haproxy handshake indicates that a
+/* connection is not proxied. Arguments and results conform
+/* to the postscreen_endpt(3) API.
+/* 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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <myaddrinfo.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <haproxy_srvr.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+#include <postscreen_haproxy.h>
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ VSTREAM *stream;
+ PSC_ENDPT_LOOKUP_FN notify;
+} PSC_HAPROXY_STATE;
+
+/* psc_endpt_haproxy_event - read or time event */
+
+static void psc_endpt_haproxy_event(int event, void *context)
+{
+ const char *myname = "psc_endpt_haproxy_event";
+ PSC_HAPROXY_STATE *state = (PSC_HAPROXY_STATE *) context;
+ int status = 0;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int non_proxy = 0;
+
+ switch (event) {
+ case EVENT_TIME:
+ msg_warn("haproxy read: time limit exceeded");
+ status = -1;
+ break;
+ case EVENT_READ:
+ status = haproxy_srvr_receive(vstream_fileno(state->stream), &non_proxy,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+ }
+
+ /*
+ * Terminate this pseudo thread, and notify the caller.
+ */
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->stream),
+ psc_endpt_haproxy_event, context);
+ if (status == 0 && non_proxy)
+ psc_endpt_local_lookup(state->stream, state->notify);
+ else
+ state->notify(status, state->stream,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+ /* Note: the stream may be closed at this point. */
+ myfree((void *) state);
+}
+
+/* psc_endpt_haproxy_lookup - event-driven haproxy client */
+
+void psc_endpt_haproxy_lookup(VSTREAM *stream,
+ PSC_ENDPT_LOOKUP_FN notify)
+{
+ const char *myname = "psc_endpt_haproxy_lookup";
+ PSC_HAPROXY_STATE *state;
+
+ /*
+ * Prepare the per-session state. XXX To improve overload behavior,
+ * maintain a pool of these so that we can reduce memory allocator
+ * activity.
+ */
+ state = (PSC_HAPROXY_STATE *) mymalloc(sizeof(*state));
+ state->stream = stream;
+ state->notify = notify;
+
+ /*
+ * Read the haproxy line.
+ */
+ PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_endpt_haproxy_event,
+ (void *) state, var_psc_uproxy_tmout);
+}
diff --git a/src/postscreen/postscreen_haproxy.h b/src/postscreen/postscreen_haproxy.h
new file mode 100644
index 0000000..7557fb3
--- /dev/null
+++ b/src/postscreen/postscreen_haproxy.h
@@ -0,0 +1,30 @@
+/*++
+/* NAME
+/* postscreen_haproxy 3h
+/* SUMMARY
+/* postscreen haproxy protocol support
+/* SYNOPSIS
+/* #include <postscreen_haproxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * haproxy protocol interface.
+ */
+extern void psc_endpt_haproxy_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+
+/* 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/postscreen/postscreen_misc.c b/src/postscreen/postscreen_misc.c
new file mode 100644
index 0000000..f07c9ac
--- /dev/null
+++ b/src/postscreen/postscreen_misc.c
@@ -0,0 +1,164 @@
+/*++
+/* NAME
+/* postscreen_misc 3
+/* SUMMARY
+/* postscreen misc routines
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* char *psc_format_delta_time(buf, tv, delta)
+/* VSTRING *buf;
+/* struct timeval tv;
+/* DELTA_TIME *delta;
+/*
+/* void psc_conclude(state)
+/* PSC_STATE *state;
+/*
+/* void psc_hangup_event(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* psc_format_delta_time() computes the time difference between
+/* tv (past) and the present, formats the time difference with
+/* sub-second resolution in a human-readable way, and returns
+/* the integer time difference in seconds through the delta
+/* argument.
+/*
+/* psc_conclude() logs when a client passes all necessary tests,
+/* updates the postscreen cache for any testes that were passed,
+/* and either forwards the connection to a real SMTP server or
+/* replies with the text in state->error_reply and hangs up the
+/* connection (by default, state->error_reply is set to a
+/* default 421 reply).
+/*
+/* psc_hangup_event() cleans up after a client connection breaks
+/* unexpectedly. If logs the test where the break happened,
+/* and how much time as spent in that test before the connection
+/* broke.
+/* 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 <vstring.h>
+#include <iostuff.h>
+#include <format_tv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+/* psc_format_delta_time - pretty-formatted delta time */
+
+char *psc_format_delta_time(VSTRING *buf, struct timeval tv,
+ DELTA_TIME *delta)
+{
+ DELTA_TIME pdelay;
+ struct timeval now;
+
+ GETTIMEOFDAY(&now);
+ PSC_CALC_DELTA(pdelay, now, tv);
+ VSTRING_RESET(buf);
+ format_tv(buf, pdelay.dt_sec, pdelay.dt_usec, SIG_DIGS, var_delay_max_res);
+ *delta = pdelay;
+ return (STR(buf));
+}
+
+/* psc_conclude - bring this session to a conclusion */
+
+void psc_conclude(PSC_STATE *state)
+{
+ const char *myname = "psc_conclude";
+
+ if (msg_verbose)
+ msg_info("flags for %s: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+
+ /*
+ * Handle clients that passed at least one test other than permanent
+ * allowlisting, and that didn't fail any test including permanent
+ * denylisting. There may still be unfinished tests; those tests will
+ * need to be completed when the client returns in a later session.
+ */
+ if (state->flags & PSC_STATE_MASK_ANY_FAIL)
+ state->flags &= ~PSC_STATE_MASK_ANY_PASS;
+
+ /*
+ * Log our final blessing when all unfinished tests were completed.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0
+ && (state->flags & PSC_STATE_MASK_ANY_PASS) ==
+ PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO))
+ msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0
+ || state->client_info->pass_new_count++ > 0 ?
+ "OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state));
+
+ /*
+ * Update the postscreen cache. This still supports a scenario where a
+ * client gets allowlisted in the course of multiple sessions, as long as
+ * that client does not "fail" any test. Don't try to optimize away cache
+ * updates; we want cached information to be up-to-date even if a test
+ * result is renewed during overlapping SMTP sessions, and even if
+ * 'postfix reload' happens in the middle of that.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0
+ && psc_cache_map != 0) {
+ psc_print_tests(psc_temp, state);
+ psc_cache_update(psc_cache_map, state->smtp_client_addr, STR(psc_temp));
+ }
+
+ /*
+ * Either hand off the socket to a real SMTP engine, or say bye-bye.
+ */
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) == 0) {
+ psc_send_socket(state);
+ } else {
+ if ((state->flags & PSC_STATE_FLAG_HANGUP) == 0)
+ (void) PSC_SEND_REPLY(state, state->final_reply);
+ msg_info("DISCONNECT [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
+ psc_free_session_state(state);
+ }
+}
+
+/* psc_hangup_event - handle unexpected disconnect */
+
+void psc_hangup_event(PSC_STATE *state)
+{
+ DELTA_TIME elapsed;
+
+ /*
+ * Sessions can break at any time, even after the client passes all tests
+ * (some MTAs including Postfix don't send QUIT when connection reuse is
+ * enabled). This must not be treated as a protocol test failure.
+ *
+ * Log the current test phase, and the elapsed time after the start of that
+ * phase.
+ */
+ state->flags |= PSC_STATE_FLAG_HANGUP;
+ msg_info("HANGUP after %s from [%s]:%s in %s",
+ psc_format_delta_time(psc_temp, state->start_time, &elapsed),
+ PSC_CLIENT_ADDR_PORT(state), state->test_name);
+ state->flags |= PSC_STATE_FLAG_NOFORWARD;
+ psc_conclude(state);
+}
diff --git a/src/postscreen/postscreen_send.c b/src/postscreen/postscreen_send.c
new file mode 100644
index 0000000..53714b1
--- /dev/null
+++ b/src/postscreen/postscreen_send.c
@@ -0,0 +1,293 @@
+/*++
+/* NAME
+/* postscreen_send 3
+/* SUMMARY
+/* postscreen low-level output
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void pcs_send_pre_jail_init(void)
+/*
+/* int psc_send_reply(state, text)
+/* PSC_STATE *state;
+/* const char *text;
+/*
+/* int PSC_SEND_REPLY(state, text)
+/* PSC_STATE *state;
+/* const char *text;
+/*
+/* void psc_send_socket(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* pcs_send_pre_jail_init() performs one-time initialization.
+/*
+/* psc_send_reply() sends the specified text to the specified
+/* remote SMTP client. In case of an immediate error, it logs
+/* a warning (except EPIPE) with the client address and port,
+/* and returns a non-zero result (all errors including EPIPE).
+/*
+/* psc_send_reply() does a best effort to send the reply, but
+/* it won't block when the output is throttled by a hostile
+/* peer.
+/*
+/* PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
+/* It will eventually be replaced by its expansion.
+/*
+/* psc_send_socket() sends the specified socket to the real
+/* Postfix SMTP server. The socket is delivered in the background.
+/* This function must be called after all other session-related
+/* work is finished including postscreen cache updates.
+/*
+/* In case of an immediate error, psc_send_socket() sends a 421
+/* reply to the remote SMTP client and closes the connection.
+/* If the 220- greeting was sent, sending 421 would be invalid;
+/* instead, the client is redirected to the dummy SMTP engine
+/* which sends the 421 reply at the first legitimate opportunity.
+/* 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 <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <connect.h>
+#include <attr.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <smtp_reply_footer.h>
+#include <mail_proto.h>
+#include <maps.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+static MAPS *psc_rej_ftr_maps;
+
+ /*
+ * This program screens all inbound SMTP connections, so it better not waste
+ * time.
+ */
+#define PSC_SEND_SOCK_CONNECT_TIMEOUT 1
+#define PSC_SEND_SOCK_NOTIFY_TIMEOUT 100
+
+/* pcs_send_pre_jail_init - initialize */
+
+void pcs_send_pre_jail_init(void)
+{
+ static int init_count = 0;
+
+ if (init_count++ != 0)
+ msg_panic("pcs_send_pre_jail_init: multiple calls");
+
+ /*
+ * SMTP server reject footer.
+ */
+ if (*var_psc_rej_ftr_maps)
+ psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
+ var_psc_rej_ftr_maps,
+ DICT_FLAG_LOCK);
+}
+
+/* psc_get_footer - find that footer */
+
+static const char *psc_get_footer(const char *text, ssize_t text_len)
+{
+ static VSTRING *footer_buf = 0;
+
+ if (footer_buf == 0)
+ footer_buf = vstring_alloc(100);
+ /* Strip the \r\n for consistency with smtpd. */
+ vstring_strncpy(footer_buf, text, text_len);
+ return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
+}
+
+/* psc_send_reply - send reply to remote SMTP client */
+
+int psc_send_reply(PSC_STATE *state, const char *text)
+{
+ ssize_t start;
+ int ret;
+ const char *footer;
+ ssize_t text_len = strlen(text) - 2;
+
+ if (msg_verbose)
+ msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
+ state->smtp_client_port, (int) text_len, text);
+
+ /*
+ * Append the new text to earlier text that could not be sent because the
+ * output was throttled.
+ */
+ start = VSTRING_LEN(state->send_buf);
+ vstring_strcat(state->send_buf, text);
+
+ /*
+ * For soft_bounce support, we also fix the REJECT logging before the
+ * dummy SMTP engine calls the psc_send_reply() output routine. We do
+ * some double work, but it is for debugging only.
+ */
+ if (var_soft_bounce) {
+ if (text[0] == '5')
+ STR(state->send_buf)[start + 0] = '4';
+ if (text[4] == '5')
+ STR(state->send_buf)[start + 4] = '4';
+ }
+
+ /*
+ * Append the optional reply footer.
+ */
+ if ((*text == '4' || *text == '5')
+ && ((psc_rej_ftr_maps != 0
+ && (footer = psc_get_footer(text, text_len)) != 0)
+ || *(footer = var_psc_rej_footer) != 0))
+ smtp_reply_footer(state->send_buf, start, footer,
+ STR(psc_expand_filter), psc_expand_lookup,
+ (void *) state);
+
+ /*
+ * Do a best effort sending text, but don't block when the output is
+ * throttled by a hostile peer.
+ */
+ ret = write(vstream_fileno(state->smtp_client_stream),
+ STR(state->send_buf), LEN(state->send_buf));
+ if (ret > 0)
+ vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
+ if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
+ msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
+ state->smtp_client_port);
+ return (ret < 0 && errno != EAGAIN);
+}
+
+/* psc_send_socket_close_event - file descriptor has arrived or timeout */
+
+static void psc_send_socket_close_event(int event, void *context)
+{
+ const char *myname = "psc_send_socket_close_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, state->smtp_server_fd, state->smtp_client_addr,
+ state->smtp_client_port);
+
+ /*
+ * The real SMTP server has closed the local IPC channel, or we have
+ * reached the limit of our patience. In the latter case it is still
+ * possible that the real SMTP server will receive the socket so we
+ * should not interfere.
+ */
+ PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
+ context);
+ if (event == EVENT_TIME)
+ msg_warn("timeout sending connection to service %s",
+ psc_smtpd_service_name);
+ psc_free_session_state(state);
+}
+
+/* psc_send_socket - send socket to real SMTP server process */
+
+void psc_send_socket(PSC_STATE *state)
+{
+ const char *myname = "psc_send_socket";
+ int server_fd;
+ int pass_err;
+ VSTREAM *fp;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port);
+
+ /*
+ * Connect to the real SMTP service over a local IPC channel, send the
+ * file descriptor, and close the file descriptor to save resources.
+ * Experience has shown that some systems will discard information when
+ * we close a channel immediately after writing. Thus, we waste resources
+ * waiting for the remote side to close the local IPC channel first. The
+ * good side of waiting is that we learn when the real SMTP server is
+ * falling behind.
+ *
+ * This is where we would forward the connection to an SMTP server that
+ * provides an appropriate level of service for this client class. For
+ * example, a server that is more forgiving, or one that is more
+ * suspicious. Alternatively, we could send attributes along with the
+ * socket with client reputation information, making everything even more
+ * Postfix-specific.
+ */
+ if ((server_fd =
+ LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
+ PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
+ msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
+ if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
+ PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
+ } else {
+ PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
+ psc_free_session_state(state);
+ }
+ return;
+ }
+ /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
+ fp = vstream_fdopen(server_fd, O_RDWR);
+ pass_err =
+ (LOCAL_SEND_FD(server_fd,
+ vstream_fileno(state->smtp_client_stream)) < 0
+ || (attr_print(fp, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
+ ATTR_TYPE_END) || vstream_fflush(fp)));
+ /* XXX Note: no read between attr_print() and vstream_fdclose(). */
+ (void) vstream_fdclose(fp);
+ if (pass_err != 0) {
+ msg_warn("cannot pass connection to service %s: %m",
+ psc_smtpd_service_name);
+ (void) close(server_fd);
+ if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
+ PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
+ } else {
+ PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
+ psc_free_session_state(state);
+ }
+ return;
+ } else {
+
+ /*
+ * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
+ * where smtp-source sometimes sees the connection being closed after
+ * it has already received the real SMTP server's 220 greeting!
+ */
+#if 0
+ PSC_DEL_CLIENT_STATE(state);
+#endif
+ PSC_ADD_SERVER_STATE(state, server_fd);
+ PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
+ (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
+ return;
+ }
+}
diff --git a/src/postscreen/postscreen_smtpd.c b/src/postscreen/postscreen_smtpd.c
new file mode 100644
index 0000000..edd5d71
--- /dev/null
+++ b/src/postscreen/postscreen_smtpd.c
@@ -0,0 +1,1339 @@
+/*++
+/* NAME
+/* postscreen_smtpd 3
+/* SUMMARY
+/* postscreen built-in SMTP server engine
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_smtpd_pre_jail_init(void)
+/*
+/* void psc_smtpd_init(void)
+/*
+/* void psc_smtpd_tests(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_SMTPD_X21(state, final_reply)
+/* PSC_STATE *state;
+/* const char *final_reply;
+/* DESCRIPTION
+/* psc_smtpd_pre_jail_init() performs one-time per-process
+/* initialization during the "before chroot" execution phase.
+/*
+/* psc_smtpd_init() performs one-time per-process initialization.
+/*
+/* psc_smtpd_tests() starts up an SMTP server engine for deep
+/* protocol tests and for collecting helo/sender/recipient
+/* information.
+/*
+/* PSC_SMTPD_X21() redirects the SMTP client to an SMTP server
+/* engine, which sends the specified final reply at the first
+/* legitimate opportunity without doing any protocol tests.
+/*
+/* Unlike the Postfix SMTP server, this engine does not announce
+/* PIPELINING support. This exposes spambots that pipeline
+/* their commands anyway. Like the Postfix SMTP server, this
+/* engine will accept input with bare newline characters. To
+/* pass the "pipelining" and "bare newline" test, the client
+/* has to properly speak SMTP all the way to the RCPT TO
+/* command. These tests fail if the client violates the protocol
+/* at any stage.
+/*
+/* No support is announced for AUTH, XCLIENT or XFORWARD.
+/* Clients that need this should be allowlisted or should talk
+/* directly to the submission service.
+/*
+/* The engine rejects RCPT TO and VRFY commands with the
+/* state->rcpt_reply response which depends on program history,
+/* rejects ETRN with a generic response, and closes the
+/* connection after QUIT.
+/*
+/* Since this engine defers or rejects all non-junk commands,
+/* there is no point maintaining separate counters for "error"
+/* commands and "junk" commands. Instead, the engine maintains
+/* a per-session command counter, and terminates the session
+/* with a 421 reply when the command count exceeds the limit.
+/*
+/* We limit the command count, as well as the total time to
+/* receive a command. This limits the time per client more
+/* effectively than would be possible with read() timeouts.
+/*
+/* There is no concern about getting blocked on output. The
+/* psc_send() routine uses non-blocking output, and discards
+/* output that the client is not willing to receive.
+/* PROTOCOL INSPECTION VERSUS CONTENT INSPECTION
+/* The goal of postscreen is to keep spambots away from Postfix.
+/* To recognize spambots, postscreen measures properties of
+/* the client IP address and of the client SMTP protocol
+/* implementation. These client properties don't change with
+/* each delivery attempt. Therefore it is possible to make a
+/* long-term decision after a single measurement. For example,
+/* allow a good client to skip the DNSBL test for 24 hours,
+/* or to skip the pipelining test for one week.
+/*
+/* If postscreen were to measure properties of message content
+/* (MIME compliance, etc.) then it would measure properties
+/* that may change with each delivery attempt. Here, it would
+/* be wrong to make a long-term decision after a single
+/* measurement. Instead, postscreen would need to develop a
+/* ranking based on the content of multiple messages from the
+/* same client.
+/*
+/* Many spambots avoid spamming the same site repeatedly.
+/* Thus, postscreen must make decisions after a single
+/* measurement. Message content is not a good indicator for
+/* making long-term decisions after single measurements, and
+/* that is why postscreen does not inspect message content.
+/* REJECTING RCPT TO VERSUS SENDING LIVE SOCKETS TO SMTPD(8)
+/* When post-handshake protocol tests are enabled, postscreen
+/* rejects the RCPT TO command from a good client, and forces
+/* it to deliver mail in a later session. This is why
+/* post-handshake protocol tests have a longer expiration time
+/* than pre-handshake tests.
+/*
+/* Instead, postscreen could send the network socket to smtpd(8)
+/* and ship the session history (including TLS and other SMTP
+/* or non-SMTP attributes) as auxiliary data. The Postfix SMTP
+/* server would then use new code to replay the session history,
+/* and would use existing code to validate the client, helo,
+/* sender and recipient address.
+/*
+/* Such an approach would increase the implementation and
+/* maintenance effort, because:
+/*
+/* 1) New replay code would be needed in smtpd(8), such that
+/* the HELO, EHLO, and MAIL command handlers can delay their
+/* error responses until the RCPT TO reply.
+/*
+/* 2) postscreen(8) would have to implement more of smtpd(8)'s
+/* syntax checks, to avoid confusing delayed "syntax error"
+/* and other error responses syntax error responses while
+/* replaying history.
+/*
+/* 3) New code would be needed in postscreen(8) and smtpd(8)
+/* to send and receive the session history (including TLS and
+/* other SMTP or non-SMTP attributes) as auxiliary data while
+/* sending the network socket from postscreen(8) to smtpd(8).
+/* REJECTING RCPT TO VERSUS PROXYING LIVE SESSIONS TO SMTPD(8)
+/* An alternative would be to proxy the session history to a
+/* real Postfix SMTP process, presumably passing TLS and other
+/* attributes via an extended XCLIENT implementation. That
+/* would require all the work described in 2) above, plus
+/* duplication of all the features of the smtpd(8) TLS engine,
+/* plus additional XCLIENT support for a lot more attributes.
+/* 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 <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <is_header.h>
+#include <string_list.h>
+#include <maps.h>
+#include <ehlo_mask.h>
+#include <lex_822.h>
+#include <info_log_addr_form.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Plan for future body processing. See smtp-sink.c. For now, we have no
+ * per-session push-back except for the single-character push-back that
+ * VSTREAM guarantees after we read one character.
+ */
+#define PSC_SMTPD_HAVE_PUSH_BACK(state) (0)
+#define PSC_SMTPD_PUSH_BACK_CHAR(state, ch) \
+ vstream_ungetc((state)->smtp_client_stream, (ch))
+#define PSC_SMTPD_NEXT_CHAR(state) \
+ VSTREAM_GETC((state)->smtp_client_stream)
+
+#define PSC_SMTPD_BUFFER_EMPTY(state) \
+ (!PSC_SMTPD_HAVE_PUSH_BACK(state) \
+ && vstream_peek((state)->smtp_client_stream) <= 0)
+
+#define PSC_SMTPD_PEEK_DATA(state) \
+ vstream_peek_data((state)->smtp_client_stream)
+#define PSC_SMTPD_PEEK_LEN(state) \
+ vstream_peek((state)->smtp_client_stream)
+
+ /*
+ * Dynamic reply strings. To minimize overhead we format these once.
+ */
+static char *psc_smtpd_greeting; /* smtp banner */
+static char *psc_smtpd_helo_reply; /* helo reply */
+static char *psc_smtpd_ehlo_reply_plain;/* multi-line ehlo reply, non-TLS */
+static char *psc_smtpd_ehlo_reply_tls; /* multi-line ehlo reply, with TLS */
+static char *psc_smtpd_timeout_reply; /* timeout reply */
+static char *psc_smtpd_421_reply; /* generic final_reply value */
+
+ /*
+ * Forward declaration, needed by PSC_CLEAR_EVENT_REQUEST.
+ */
+static void psc_smtpd_time_event(int, void *);
+static void psc_smtpd_read_event(int, void *);
+
+ /*
+ * Encapsulation. The STARTTLS, EHLO and AUTH command handlers temporarily
+ * suspend SMTP command events, send an asynchronous proxy request, and
+ * resume SMTP command events after receiving the asynchronous proxy
+ * response (the EHLO handler must asynchronously talk to the auth server
+ * before it can announce the SASL mechanism list; the list can depend on
+ * the client IP address and on the presence on TLS encryption).
+ */
+#define PSC_RESUME_SMTP_CMD_EVENTS(state) do { \
+ PSC_READ_EVENT_REQUEST2(vstream_fileno((state)->smtp_client_stream), \
+ psc_smtpd_read_event, psc_smtpd_time_event, \
+ (void *) (state), PSC_EFF_CMD_TIME_LIMIT); \
+ if (!PSC_SMTPD_BUFFER_EMPTY(state)) \
+ psc_smtpd_read_event(EVENT_READ, (void *) state); \
+ } while (0)
+
+#define PSC_SUSPEND_SMTP_CMD_EVENTS(state) \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ psc_smtpd_time_event, (void *) (state));
+
+ /*
+ * Make control characters and other non-text visible.
+ */
+#define PSC_SMTPD_ESCAPE_TEXT(dest, src, src_len, max_len) do { \
+ ssize_t _s_len = (src_len); \
+ ssize_t _m_len = (max_len); \
+ (void) escape((dest), (src), _s_len < _m_len ? _s_len : _m_len); \
+ } while (0)
+
+ /*
+ * Command parser support.
+ */
+#define PSC_SMTPD_NEXT_TOKEN(ptr) mystrtok(&(ptr), " ")
+
+ /*
+ * EHLO keyword filter
+ */
+static MAPS *psc_ehlo_discard_maps;
+static int psc_ehlo_discard_mask;
+
+ /*
+ * Command editing filter.
+ */
+static DICT *psc_cmd_filter;
+
+ /*
+ * Encapsulation. We must not forget turn off input/timer events when we
+ * terminate the SMTP protocol engine.
+ *
+ * It would be safer to turn off input/timer events after each event, and to
+ * turn on input/timer events again when we want more input. But experience
+ * with the Postfix smtp-source and smtp-sink tools shows that this would
+ * noticeably increase the run-time cost.
+ */
+#define PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, event, reply) do { \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ (event), (void *) (state)); \
+ PSC_DROP_SESSION_STATE((state), (reply)); \
+ } while (0)
+
+#define PSC_CLEAR_EVENT_HANGUP(state, event) do { \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ (event), (void *) (state)); \
+ psc_hangup_event(state); \
+ } while (0)
+
+/* psc_helo_cmd - record HELO and respond */
+
+static int psc_helo_cmd(PSC_STATE *state, char *args)
+{
+ char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
+
+ /*
+ * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them.
+ */
+ if (helo_name == 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: HELO hostname\r\n"));
+
+ PSC_STRING_UPDATE(state->helo_name, helo_name);
+ PSC_STRING_RESET(state->sender);
+ /* Don't downgrade state->protocol, in case some test depends on this. */
+ return (PSC_SEND_REPLY(state, psc_smtpd_helo_reply));
+}
+
+/* psc_smtpd_format_ehlo_reply - format EHLO response */
+
+static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask
+ /* , const char *sasl_mechanism_list */ )
+{
+ const char *myname = "psc_smtpd_format_ehlo_reply";
+ int saved_len = 0;
+
+ if (msg_verbose)
+ msg_info("%s: discard_mask %s", myname, str_ehlo_mask(discard_mask));
+
+#define PSC_EHLO_APPEND(save, buf, fmt) do { \
+ (save) = LEN(buf); \
+ vstring_sprintf_append((buf), (fmt)); \
+ } while (0)
+
+#define PSC_EHLO_APPEND1(save, buf, fmt, arg1) do { \
+ (save) = LEN(buf); \
+ vstring_sprintf_append((buf), (fmt), (arg1)); \
+ } while (0)
+
+ vstring_sprintf(psc_temp, "250-%s\r\n", var_myhostname);
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit))
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "250-SIZE %lu\r\n",
+ (unsigned long) var_message_limit);
+ else
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-SIZE\r\n");
+ }
+ if ((discard_mask & EHLO_MASK_VRFY) == 0 && var_disable_vrfy_cmd == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-VRFY\r\n");
+ if ((discard_mask & EHLO_MASK_ETRN) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-ETRN\r\n");
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0 && var_psc_use_tls)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-STARTTLS\r\n");
+#ifdef TODO_SASL_AUTH
+ if ((discard_mask & EHLO_MASK_AUTH) == 0 && sasl_mechanism_list
+ && (!var_psc_tls_auth_only || (discard_mask & EHLO_MASK_STARTTLS))) {
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH %s", sasl_mechanism_list);
+ if (var_broken_auth_clients)
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH=%s", sasl_mechanism_list);
+ }
+#endif
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-ENHANCEDSTATUSCODES\r\n");
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-8BITMIME\r\n");
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-DSN\r\n");
+ /* Fix 20140708: announce SMTPUTF8. */
+ if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n");
+ if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n");
+ STR(psc_temp)[saved_len + 3] = ' ';
+}
+
+/* psc_ehlo_cmd - record EHLO and respond */
+
+static int psc_ehlo_cmd(PSC_STATE *state, char *args)
+{
+ char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
+ const char *ehlo_words;
+ int discard_mask;
+ char *reply;
+
+ /*
+ * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them.
+ */
+ if (helo_name == 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n"));
+
+ PSC_STRING_UPDATE(state->helo_name, helo_name);
+ PSC_STRING_RESET(state->sender);
+ state->protocol = MAIL_PROTO_ESMTP;
+
+ /*
+ * smtpd(8) compatibility: dynamic reply filtering.
+ */
+ if (psc_ehlo_discard_maps != 0
+ && (ehlo_words = psc_maps_find(psc_ehlo_discard_maps,
+ state->smtp_client_addr, 0)) != 0
+ && (discard_mask = ehlo_mask(ehlo_words)) != psc_ehlo_discard_mask) {
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("[%s]%s: discarding EHLO keywords: %s",
+ PSC_CLIENT_ADDR_PORT(state), str_ehlo_mask(discard_mask));
+ if (state->flags & PSC_STATE_FLAG_USING_TLS)
+ discard_mask |= EHLO_MASK_STARTTLS;
+ psc_smtpd_format_ehlo_reply(psc_temp, discard_mask);
+ reply = STR(psc_temp);
+ state->ehlo_discard_mask = discard_mask;
+ } else if (psc_ehlo_discard_maps && psc_ehlo_discard_maps->error) {
+ msg_fatal("%s lookup error for %s",
+ psc_ehlo_discard_maps->title, state->smtp_client_addr);
+ } else if (state->flags & PSC_STATE_FLAG_USING_TLS) {
+ reply = psc_smtpd_ehlo_reply_tls;
+ state->ehlo_discard_mask = psc_ehlo_discard_mask | EHLO_MASK_STARTTLS;
+ } else {
+ reply = psc_smtpd_ehlo_reply_plain;
+ state->ehlo_discard_mask = psc_ehlo_discard_mask;
+ }
+ return (PSC_SEND_REPLY(state, reply));
+}
+
+/* psc_starttls_resume - resume the SMTP protocol after tlsproxy activation */
+
+static void psc_starttls_resume(int unused_event, void *context)
+{
+ const char *myname = "psc_starttls_resume";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ /*
+ * Reset SMTP server state if STARTTLS was successful.
+ */
+ if (state->flags & PSC_STATE_FLAG_USING_TLS) {
+ /* Purge the push-back buffer, when implemented. */
+ PSC_STRING_RESET(state->helo_name);
+ PSC_STRING_RESET(state->sender);
+#ifdef TODO_SASL_AUTH
+ /* Reset SASL AUTH state. Dovecot responses may change. */
+#endif
+ }
+
+ /*
+ * Resume read/timeout events. If we still have unread input, resume the
+ * command processor immediately.
+ */
+ PSC_RESUME_SMTP_CMD_EVENTS(state);
+}
+
+/* psc_starttls_cmd - activate the tlsproxy server */
+
+static int psc_starttls_cmd(PSC_STATE *state, char *args)
+{
+ const char *myname = "psc_starttls_cmd";
+
+ /*
+ * smtpd(8) incompatibility: we can't send a 4XX reply that TLS is
+ * unavailable when tlsproxy(8) detects the problem too late.
+ */
+ if (PSC_SMTPD_NEXT_TOKEN(args) != 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n"));
+ if (state->flags & PSC_STATE_FLAG_USING_TLS)
+ return (PSC_SEND_REPLY(state,
+ "554 5.5.1 Error: TLS already active\r\n"));
+ if (var_psc_use_tls == 0 || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS))
+ return (PSC_SEND_REPLY(state,
+ "502 5.5.1 Error: command not implemented\r\n"));
+
+ /*
+ * Suspend the SMTP protocol until psc_starttls_resume() is called.
+ */
+ PSC_SUSPEND_SMTP_CMD_EVENTS(state);
+ psc_starttls_open(state, psc_starttls_resume);
+ return (0);
+}
+
+/* psc_extract_addr - extract MAIL/RCPT address, unquoted form */
+
+static char *psc_extract_addr(VSTRING *result, const char *string)
+{
+ const unsigned char *cp = (const unsigned char *) string;
+ char *addr;
+ char *colon;
+ int stop_at;
+ int inquote = 0;
+
+ /*
+ * smtpd(8) incompatibility: we allow more invalid address forms, and we
+ * don't validate recipients. We are not going to deliver them so we
+ * won't have to worry about deliverability. This may have to change when
+ * we pass the socket to a real SMTP server and replay message envelope
+ * commands.
+ */
+
+ /* Skip SP characters. */
+ while (*cp && *cp == ' ')
+ cp++;
+
+ /* Choose the terminator for <addr> or bare addr. */
+ if (*cp == '<') {
+ cp++;
+ stop_at = '>';
+ } else {
+ stop_at = ' ';
+ }
+
+ /* Skip to terminator or end. */
+ VSTRING_RESET(result);
+ for ( /* void */ ; *cp; cp++) {
+ if (!inquote && *cp == stop_at)
+ break;
+ if (*cp == '"') {
+ inquote = !inquote;
+ } else {
+ if (*cp == '\\' && *++cp == 0)
+ break;
+ VSTRING_ADDCH(result, *cp);
+ }
+ }
+ VSTRING_TERMINATE(result);
+
+ /*
+ * smtpd(8) compatibility: truncate deprecated route address form. This
+ * is primarily to simplify logfile analysis.
+ */
+ addr = STR(result);
+ if (*addr == '@' && (colon = strchr(addr, ':')) != 0)
+ addr = colon + 1;
+ return (addr);
+}
+
+/* psc_mail_cmd - record MAIL and respond */
+
+static int psc_mail_cmd(PSC_STATE *state, char *args)
+{
+ char *colon;
+ char *addr;
+
+ /*
+ * smtpd(8) incompatibility: we never reject the sender, and we ignore
+ * additional arguments.
+ */
+ if (var_psc_helo_required && state->helo_name == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: send HELO/EHLO first\r\n"));
+ if (state->sender != 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: nested MAIL command\r\n"));
+ if (args == 0 || (colon = strchr(args, ':')) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: MAIL FROM:<address>\r\n"));
+ if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.1.7 Bad sender address syntax\r\n"));
+ PSC_STRING_UPDATE(state->sender, addr);
+ return (PSC_SEND_REPLY(state, "250 2.1.0 Ok\r\n"));
+}
+
+/* psc_soften_reply - copy and soft-bounce a reply */
+
+static char *psc_soften_reply(const char *reply)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ vstring_strcpy(buf, reply);
+ if (reply[0] == '5')
+ STR(buf)[0] = '4';
+ if (reply[4] == '5')
+ STR(buf)[4] = '4';
+ return (STR(buf));
+}
+
+/* psc_rcpt_cmd record RCPT and respond */
+
+static int psc_rcpt_cmd(PSC_STATE *state, char *args)
+{
+ char *colon;
+ char *addr;
+
+ /*
+ * smtpd(8) incompatibility: we reject all recipients, and ignore
+ * additional arguments.
+ */
+ if (state->sender == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: need MAIL command\r\n"));
+ if (args == 0 || (colon = strchr(args, ':')) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: RCPT TO:<address>\r\n"));
+ if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.1.3 Bad recipient address syntax\r\n"));
+ msg_info("NOQUEUE: reject: RCPT from [%s]:%s: %.*s; "
+ "from=<%s>, to=<%s>, proto=%s, helo=<%s>",
+ PSC_CLIENT_ADDR_PORT(state),
+ (int) strlen(state->rcpt_reply) - 2,
+ var_soft_bounce == 0 ? state->rcpt_reply :
+ psc_soften_reply(state->rcpt_reply),
+ info_log_addr_form_sender(state->sender),
+ info_log_addr_form_recipient(addr), state->protocol,
+ state->helo_name ? state->helo_name : "");
+ return (PSC_SEND_REPLY(state, state->rcpt_reply));
+}
+
+/* psc_data_cmd - respond to DATA and disconnect */
+
+static int psc_data_cmd(PSC_STATE *state, char *args)
+{
+ const char myname[] = "psc_data_cmd";
+
+ /*
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of waiting for the next command. Justification: postscreen(8) should
+ * never see DATA from a legitimate client, because 1) the server rejects
+ * every recipient, and 2) the server does not announce PIPELINING.
+ */
+ msg_info("DATA without valid RCPT from [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (PSC_SMTPD_NEXT_TOKEN(args) != 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: DATA\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "503 5.5.1 Error: need RCPT command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_bdat_cmd - respond to BDAT and disconnect */
+
+static int psc_bdat_cmd(PSC_STATE *state, char *args)
+{
+ const char *myname = "psc_bdat_cmd";
+
+ /*
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of reading the entire BDAT chunk and staying in sync with the client.
+ * Justification: postscreen(8) should never see BDAT from a legitimate
+ * client, because 1) the server rejects every recipient, and 2) the
+ * server does not announce PIPELINING.
+ */
+ msg_info("BDAT without valid RCPT from [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "502 5.5.1 Error: command not implemented\r\n");
+ else if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: BDAT count [LAST]\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: need MAIL command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_rset_cmd - reset, send 250 OK */
+
+static int psc_rset_cmd(PSC_STATE *state, char *unused_args)
+{
+ PSC_STRING_RESET(state->sender);
+ return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n"));
+}
+
+/* psc_noop_cmd - respond to something */
+
+static int psc_noop_cmd(PSC_STATE *state, char *unused_args)
+{
+ return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n"));
+}
+
+/* psc_vrfy_cmd - respond to VRFY */
+
+static int psc_vrfy_cmd(PSC_STATE *state, char *args)
+{
+
+ /*
+ * smtpd(8) incompatibility: we reject all requests, and ignore
+ * additional arguments.
+ */
+ if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: VRFY address\r\n"));
+ if (var_psc_disable_vrfy)
+ return (PSC_SEND_REPLY(state,
+ "502 5.5.1 VRFY command is disabled\r\n"));
+ return (PSC_SEND_REPLY(state, state->rcpt_reply));
+}
+
+/* psc_etrn_cmd - reset, send 250 OK */
+
+static int psc_etrn_cmd(PSC_STATE *state, char *args)
+{
+
+ /*
+ * smtpd(8) incompatibility: we reject all requests, and ignore
+ * additional arguments.
+ */
+ if (var_psc_helo_required && state->helo_name == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: send HELO/EHLO first\r\n"));
+ if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ return (PSC_SEND_REPLY(state,
+ "500 Syntax: ETRN domain\r\n"));
+ return (PSC_SEND_REPLY(state, "458 Unable to queue messages\r\n"));
+}
+
+/* psc_quit_cmd - respond to QUIT and disconnect */
+
+static int psc_quit_cmd(PSC_STATE *state, char *unused_args)
+{
+ const char *myname = "psc_quit_cmd";
+
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ "221 2.0.0 Bye\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_smtpd_time_event - handle per-session time limit */
+
+static void psc_smtpd_time_event(int event, void *context)
+{
+ const char *myname = "psc_smtpd_time_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ msg_info("COMMAND TIME LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), state->where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_timeout_reply);
+}
+
+ /*
+ * The table of all SMTP commands that we know.
+ */
+typedef struct {
+ const char *name;
+ int (*action) (PSC_STATE *, char *);
+ int flags; /* see below */
+} PSC_SMTPD_COMMAND;
+
+#define PSC_SMTPD_CMD_FLAG_NONE (0) /* no flags (i.e. disabled) */
+#define PSC_SMTPD_CMD_FLAG_ENABLE (1<<0) /* command is enabled */
+#define PSC_SMTPD_CMD_FLAG_DESTROY (1<<1) /* dangling pointer alert */
+#define PSC_SMTPD_CMD_FLAG_PRE_TLS (1<<2) /* allowed with mandatory TLS */
+#define PSC_SMTPD_CMD_FLAG_SUSPEND (1<<3) /* suspend command engine */
+#define PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD (1<<4) /* command has payload */
+
+static const PSC_SMTPD_COMMAND command_table[] = {
+ "HELO", psc_helo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "EHLO", psc_ehlo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "STARTTLS", psc_starttls_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS | PSC_SMTPD_CMD_FLAG_SUSPEND,
+ "XCLIENT", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "XFORWARD", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "AUTH", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "MAIL", psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "RCPT", psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY,
+ /* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */
+ "BDAT", psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD,
+ "RSET", psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "NOOP", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "VRFY", psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "ETRN", psc_etrn_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "QUIT", psc_quit_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ 0,
+};
+
+/* psc_smtpd_read_event - pseudo responder */
+
+static void psc_smtpd_read_event(int event, void *context)
+{
+ const char *myname = "psc_smtpd_read_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t *expire_time = state->client_info->expire_time;
+ int ch;
+ struct cmd_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ const char *saved_where;
+
+#define PSC_SMTPD_CMD_ST_ANY 0
+#define PSC_SMTPD_CMD_ST_CR 1
+#define PSC_SMTPD_CMD_ST_CR_LF 2
+
+ static const struct cmd_trans cmd_trans[] = {
+ PSC_SMTPD_CMD_ST_ANY, '\r', PSC_SMTPD_CMD_ST_CR,
+ PSC_SMTPD_CMD_ST_CR, '\n', PSC_SMTPD_CMD_ST_CR_LF,
+ 0, 0, 0,
+ };
+ const struct cmd_trans *transp;
+ char *cmd_buffer_ptr;
+ char *command;
+ const PSC_SMTPD_COMMAND *cmdp;
+ int write_stat;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ /*
+ * Basic liveness requirements.
+ *
+ * Drain all input in the VSTREAM buffer, otherwise this socket will not
+ * receive further read event notification until the client disconnects!
+ *
+ * To suspend this loop temporarily before the buffer is drained, use the
+ * PSC_SUSPEND_SMTP_CMD_EVENTS() and PSC_RESUME_SMTP_CMD_EVENTS() macros,
+ * and set the PSC_SMTPD_CMD_FLAG_SUSPEND flag in the command table.
+ *
+ * Don't try to read input before it has arrived, otherwise we would starve
+ * the pseudo threads of other sessions. Get out of here as soon as the
+ * VSTREAM read buffer dries up. Do not look for more input in kernel
+ * buffers. That input wasn't likely there when psc_smtpd_read_event()
+ * was called. Also, yielding the pseudo thread will improve fairness for
+ * other pseudo threads.
+ */
+
+ /*
+ * Note: on entry into this function the VSTREAM buffer may or may not be
+ * empty, so we test the "no more input" condition at the bottom of the
+ * loops.
+ */
+ for (;;) {
+
+ /*
+ * Read one command line, possibly one fragment at a time.
+ */
+ for (;;) {
+
+ if ((ch = PSC_SMTPD_NEXT_CHAR(state)) == VSTREAM_EOF) {
+ PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
+ return;
+ }
+
+ /*
+ * Sanity check. We don't want to store infinitely long commands.
+ */
+ if (state->read_state == PSC_SMTPD_CMD_ST_ANY
+ && VSTRING_LEN(state->cmd_buffer) >= var_line_limit) {
+ msg_info("COMMAND LENGTH LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), state->where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_421_reply);
+ return;
+ }
+ VSTRING_ADDCH(state->cmd_buffer, ch);
+
+ /*
+ * Try to match the current character desired by the state
+ * machine. If that fails, try to restart the machine with a
+ * match for its first state. Like smtpd(8), we understand lines
+ * ending in <CR><LF> and bare <LF>. Unlike smtpd(8), we may
+ * treat lines ending in bare <LF> as an offense.
+ */
+ for (transp = cmd_trans; transp->state != state->read_state; transp++)
+ if (transp->want == 0)
+ msg_panic("%s: command_read: unknown state: %d",
+ myname, state->read_state);
+ if (ch == transp->want)
+ state->read_state = transp->next_state;
+ else if (ch == cmd_trans[0].want)
+ state->read_state = cmd_trans[0].next_state;
+ else
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+ if (state->read_state == PSC_SMTPD_CMD_ST_CR_LF) {
+ vstring_truncate(state->cmd_buffer,
+ VSTRING_LEN(state->cmd_buffer) - 2);
+ break;
+ }
+
+ /*
+ * Bare newline test.
+ */
+ if (ch == '\n') {
+ if ((state->flags & PSC_STATE_MASK_BARLF_TODO_SKIP)
+ == PSC_STATE_FLAG_BARLF_TODO) {
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, STR(state->cmd_buffer),
+ VSTRING_LEN(state->cmd_buffer) - 1, 100);
+ msg_info("BARE NEWLINE from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_PASS);
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this session. */
+ PSC_SKIP_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_SKIP);
+ switch (psc_barlf_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_BARLF_FAIL);
+ /* Temporarily allowlist until something expires. */
+ PSC_PASS_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_PASS);
+ expire_time[PSC_TINDX_BARLF] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown bare_newline action value %d",
+ myname, psc_barlf_action);
+ }
+ }
+ vstring_truncate(state->cmd_buffer,
+ VSTRING_LEN(state->cmd_buffer) - 1);
+ break;
+ }
+
+ /*
+ * Yield this pseudo thread when the VSTREAM buffer is empty in
+ * the middle of a command.
+ *
+ * XXX Do not reset the read timeout. The entire command must be
+ * received within the time limit.
+ */
+ if (PSC_SMTPD_BUFFER_EMPTY(state))
+ return;
+ }
+
+ /*
+ * Avoid complaints from Postfix maps about malformed content.
+ */
+#define PSC_BAD_UTF8(str, len) \
+ (var_smtputf8_enable && !valid_utf8_string((str), (len)))
+
+ /*
+ * Terminate the command buffer, and apply the last-resort command
+ * editing workaround.
+ */
+ VSTRING_TERMINATE(state->cmd_buffer);
+ if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer),
+ LEN(state->cmd_buffer))) {
+ const char *cp;
+
+ for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++)
+ /* void */ ;
+ if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0) {
+ msg_info("[%s]:%s: replacing command \"%.100s\" with \"%.100s\"",
+ state->smtp_client_addr, state->smtp_client_port,
+ STR(state->cmd_buffer), cp);
+ vstring_strcpy(state->cmd_buffer, cp);
+ } else if (psc_cmd_filter->error != 0) {
+ msg_fatal("%s:%s lookup error for \"%.100s\"",
+ psc_cmd_filter->type, psc_cmd_filter->name,
+ STR(state->cmd_buffer));
+ }
+ }
+
+ /*
+ * Reset the command buffer write pointer and state machine in
+ * preparation for the next command. For this to work as expected,
+ * VSTRING_RESET() must be non-destructive. We just can't ask for the
+ * VSTRING_LEN() and vstring_end() results.
+ */
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+ VSTRING_RESET(state->cmd_buffer);
+
+ /*
+ * Process the command line.
+ *
+ * Caution: some command handlers terminate the session and destroy the
+ * session state structure. When this happens we must leave the SMTP
+ * engine to avoid a dangling pointer problem.
+ */
+ cmd_buffer_ptr = STR(state->cmd_buffer);
+ if (msg_verbose)
+ msg_info("< [%s]:%s: %s", state->smtp_client_addr,
+ state->smtp_client_port, cmd_buffer_ptr);
+
+ /* Parse the command name. */
+ if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0)
+ command = "";
+
+ /*
+ * The non-SMTP, PIPELINING and command COUNT tests depend on the
+ * client command handler.
+ *
+ * Caution: cmdp->name and cmdp->action may be null on loop exit.
+ */
+ saved_where = state->where;
+ state->where = PSC_SMTPD_CMD_UNIMPL;
+ for (cmdp = command_table; cmdp->name != 0; cmdp++) {
+ if (strcasecmp(command, cmdp->name) == 0) {
+ state->where = cmdp->name;
+ break;
+ }
+ }
+
+ if ((state->flags & PSC_STATE_FLAG_SMTPD_X21)
+ && cmdp->action != psc_quit_cmd) {
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ state->final_reply);
+ return;
+ }
+ /* Non-SMTP command test. */
+ if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP)
+ == PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0
+ && (is_header(command)
+ || PSC_BAD_UTF8(command, strlen(command))
+ /* Ignore forbid_cmds lookup errors. Non-critical feature. */
+ || (*var_psc_forbid_cmds
+ && string_list_match(psc_forbid_cmds, command)))) {
+ printable(command, '?');
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr,
+ strlen(cmd_buffer_ptr), 100);
+ msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s",
+ PSC_CLIENT_ADDR_PORT(state), saved_where,
+ command, STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS);
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this SMTP session. */
+ PSC_SKIP_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_SKIP);
+ switch (psc_nsmtp_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.7.0 Error: I can break rules, too. Goodbye.\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_NSMTP_FAIL);
+ /* Temporarily allowlist until something else expires. */
+ PSC_PASS_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_PASS);
+ expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown non_smtp_command action value %d",
+ myname, psc_nsmtp_action);
+ }
+ }
+ /* Command PIPELINING test. */
+ if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0
+ && (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP)
+ == PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) {
+ printable(command, '?');
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state),
+ PSC_SMTPD_PEEK_LEN(state), 100);
+ msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s",
+ PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS);
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this SMTP session. */
+ PSC_SKIP_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_SKIP);
+ switch (psc_pipel_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_PIPEL_FAIL);
+ /* Temporarily allowlist until something else expires. */
+ PSC_PASS_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_PASS);
+ expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown pipelining action value %d",
+ myname, psc_pipel_action);
+ }
+ }
+
+ /*
+ * The following tests don't pass until the client gets all the way
+ * to the RCPT TO command. However, the client can still fail these
+ * tests with some later command.
+ */
+ if (cmdp->action == psc_rcpt_cmd) {
+ if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_BARLF_TODO) {
+ PSC_PASS_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_BARLF] = event_time() + var_psc_barlf_ttl;
+ }
+ if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_NSMTP_TODO) {
+ PSC_PASS_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_NSMTP] = event_time() + var_psc_nsmtp_ttl;
+ }
+ if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_PIPEL_TODO) {
+ PSC_PASS_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_PIPEL] = event_time() + var_psc_pipel_ttl;
+ }
+ }
+ /* Command COUNT limit test. */
+ if (++state->command_count > var_psc_cmd_count
+ && cmdp->action != psc_quit_cmd) {
+ msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), saved_where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_421_reply);
+ return;
+ }
+ /* Finally, execute the command. */
+ if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0) {
+ write_stat = PSC_SEND_REPLY(state,
+ "502 5.5.2 Error: command not recognized\r\n");
+ } else if (var_psc_enforce_tls
+ && (state->flags & PSC_STATE_FLAG_USING_TLS) == 0
+ && (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0) {
+ write_stat = PSC_SEND_REPLY(state,
+ "530 5.7.0 Must issue a STARTTLS command first\r\n");
+ } else {
+ write_stat = cmdp->action(state, cmd_buffer_ptr);
+ if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY)
+ return;
+ }
+
+ /*
+ * Terminate the session after a write error.
+ */
+ if (write_stat < 0) {
+ PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
+ return;
+ }
+
+ /*
+ * We're suspended, waiting for some external event to happen.
+ * Hopefully, someone will call us back to process the remainder of
+ * the pending input, otherwise we could hang.
+ */
+ if (cmdp->flags & PSC_SMTPD_CMD_FLAG_SUSPEND)
+ return;
+
+ /*
+ * Reset the command read timeout before reading the next command.
+ */
+ event_request_timer(psc_smtpd_time_event, (void *) state,
+ PSC_EFF_CMD_TIME_LIMIT);
+
+ /*
+ * Yield this pseudo thread when the VSTREAM buffer is empty.
+ */
+ if (PSC_SMTPD_BUFFER_EMPTY(state))
+ return;
+ }
+}
+
+/* psc_smtpd_tests - per-session deep protocol test initialization */
+
+void psc_smtpd_tests(PSC_STATE *state)
+{
+ static char *myname = "psc_smtpd_tests";
+
+ /*
+ * Report errors and progress in the context of this test.
+ */
+ PSC_BEGIN_TESTS(state, "tests after SMTP handshake");
+
+ /*
+ * Initialize per-session state that is used only by the dummy engine:
+ * the command read buffer and the command read state machine.
+ */
+ state->cmd_buffer = vstring_alloc(100);
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+
+ /*
+ * Disable all after-220 tests when we need to reply with 421 and hang up
+ * after reading the next SMTP client command.
+ *
+ * Opportunistically make postscreen more useful, by turning on all
+ * after-220 tests when a bad client failed a before-220 test.
+ *
+ * Otherwise, only apply the explicitly-configured after-220 tests.
+ */
+ if (state->flags & PSC_STATE_FLAG_SMTPD_X21) {
+ state->flags &= ~PSC_STATE_MASK_SMTPD_TODO;
+ } else if (state->flags & PSC_STATE_MASK_ANY_FAIL) {
+ state->flags |= PSC_STATE_MASK_SMTPD_TODO;
+ }
+
+ /*
+ * Send no SMTP banner to pregreeting clients. This eliminates a lot of
+ * "NON-SMTP COMMAND" events, and improves sender/recipient logging.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) == 0
+ && PSC_SEND_REPLY(state, psc_smtpd_greeting) != 0) {
+ psc_hangup_event(state);
+ return;
+ }
+
+ /*
+ * Wait for the client to respond.
+ */
+ PSC_READ_EVENT_REQUEST2(vstream_fileno(state->smtp_client_stream),
+ psc_smtpd_read_event, psc_smtpd_time_event,
+ (void *) state, PSC_EFF_CMD_TIME_LIMIT);
+}
+
+/* psc_smtpd_init - per-process deep protocol test initialization */
+
+void psc_smtpd_init(void)
+{
+
+ /*
+ * Initialize the server banner.
+ */
+ vstring_sprintf(psc_temp, "220 %s\r\n", var_smtpd_banner);
+ psc_smtpd_greeting = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the HELO reply.
+ */
+ vstring_sprintf(psc_temp, "250 %s\r\n", var_myhostname);
+ psc_smtpd_helo_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * STARTTLS support. Note the complete absence of #ifdef USE_TLS
+ * throughout the postscreen(8) source code. If Postfix is built without
+ * TLS support, then the TLS proxy will simply report that TLS is not
+ * available, and conventional error handling will take care of the
+ * issue.
+ *
+ * Legacy code copied from smtpd(8). The pre-fabricated EHLO reply depends
+ * on this.
+ */
+ if (*var_psc_tls_level) {
+ switch (tls_level_lookup(var_psc_tls_level)) {
+ default:
+ msg_fatal("Invalid TLS level \"%s\"", var_psc_tls_level);
+ /* NOTREACHED */
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
+ VAR_PSC_TLS_LEVEL, var_psc_tls_level);
+ /* FALLTHROUGH */
+ case TLS_LEV_ENCRYPT:
+ var_psc_enforce_tls = var_psc_use_tls = 1;
+ break;
+ case TLS_LEV_MAY:
+ var_psc_enforce_tls = 0;
+ var_psc_use_tls = 1;
+ break;
+ case TLS_LEV_NONE:
+ var_psc_enforce_tls = var_psc_use_tls = 0;
+ break;
+ }
+ }
+ var_psc_use_tls = var_psc_use_tls || var_psc_enforce_tls;
+#ifdef TODO_SASL_AUTH
+ var_psc_tls_auth_only = var_psc_tls_auth_only || var_psc_enforce_tls;
+#endif
+
+ /*
+ * Initialize the EHLO reply. Once for plaintext sessions, and once for
+ * TLS sessions.
+ */
+ psc_smtpd_format_ehlo_reply(psc_temp, psc_ehlo_discard_mask);
+ psc_smtpd_ehlo_reply_plain = mystrdup(STR(psc_temp));
+
+ psc_smtpd_format_ehlo_reply(psc_temp,
+ psc_ehlo_discard_mask | EHLO_MASK_STARTTLS);
+ psc_smtpd_ehlo_reply_tls = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the 421 timeout reply.
+ */
+ vstring_sprintf(psc_temp, "421 4.4.2 %s Error: timeout exceeded\r\n",
+ var_myhostname);
+ psc_smtpd_timeout_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the generic 421 reply.
+ */
+ vstring_sprintf(psc_temp, "421 %s Service unavailable - try again later\r\n",
+ var_myhostname);
+ psc_smtpd_421_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the reply footer.
+ */
+ if (*var_psc_rej_footer || *var_psc_rej_ftr_maps)
+ psc_expand_init();
+}
+
+/* psc_smtpd_pre_jail_init - per-process deep protocol test initialization */
+
+void psc_smtpd_pre_jail_init(void)
+{
+
+ /*
+ * Determine what server ESMTP features to suppress, typically to avoid
+ * inter-operability problems. We do the default filter here, and
+ * determine client-dependent filtering on the fly.
+ *
+ * XXX Bugger. This means we have to restart when the table changes!
+ */
+ if (*var_psc_ehlo_dis_maps)
+ psc_ehlo_discard_maps = maps_create(VAR_PSC_EHLO_DIS_MAPS,
+ var_psc_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+ psc_ehlo_discard_mask = ehlo_mask(var_psc_ehlo_dis_words);
+
+ /*
+ * Last-resort command editing support.
+ */
+ if (*var_psc_cmd_filter)
+ psc_cmd_filter = dict_open(var_psc_cmd_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * SMTP server reply footer.
+ */
+ if (*var_psc_rej_ftr_maps)
+ pcs_send_pre_jail_init();
+}
diff --git a/src/postscreen/postscreen_starttls.c b/src/postscreen/postscreen_starttls.c
new file mode 100644
index 0000000..4036a3d
--- /dev/null
+++ b/src/postscreen/postscreen_starttls.c
@@ -0,0 +1,317 @@
+/*++
+/* NAME
+/* postscreen_starttls 3
+/* SUMMARY
+/* postscreen TLS proxy support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* int psc_starttls_open(state, resume_event)
+/* PSC_STATE *state;
+/* void (*resume_event)(int unused_event, char *context);
+/* DESCRIPTION
+/* This module inserts the tlsproxy(8) proxy between the
+/* postscreen(8) server and the remote SMTP client. The entire
+/* process happens in the background, including notification
+/* of completion to the remote SMTP client and to the calling
+/* application.
+/*
+/* Before calling psc_starttls_open() the caller must turn off
+/* all pending timer and I/O event requests on the SMTP client
+/* stream.
+/*
+/* psc_starttls_open() starts the first transaction in the
+/* tlsproxy(8) hand-off protocol, and sets up event handlers
+/* for the successive protocol stages.
+/*
+/* Upon completion, the event handlers call resume_event()
+/* which must reset the SMTP helo/sender/etc. state when the
+/* PSC_STATE_FLAG_USING_TLS is set, and set up timer and read
+/* event requests to receive the next SMTP command.
+/* 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 <connect.h>
+#include <stringops.h> /* concatenate() */
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* TLS library. */
+
+#include <tls_proxy.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * For now, this code is built into the postscreen(8) daemon. In the future
+ * it may be abstracted into a reusable library module for use by other
+ * event-driven programs (perhaps smtp-source and smtp-sink).
+ */
+
+ /*
+ * Transient state for the postscreen(8)-to-tlsproxy(8) hand-off protocol.
+ */
+typedef struct {
+ VSTREAM *tlsproxy_stream; /* hand-off negotiation */
+ EVENT_NOTIFY_FN resume_event; /* call-back handler */
+ PSC_STATE *smtp_state; /* SMTP session state */
+} PSC_STARTTLS;
+
+#define TLSPROXY_INIT_TIMEOUT 10
+
+static char *psc_tlsp_service = 0;
+
+/* Resume the dummy SMTP engine after an event handling error */
+
+#define PSC_STARTTLS_EVENT_ERR_RESUME_RETURN() do { \
+ event_disable_readwrite(vstream_fileno(tlsproxy_stream)); \
+ PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); \
+ } while (0);
+
+/* Resume the dummy SMTP engine, possibly after swapping streams */
+
+#define PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state) do { \
+ vstream_fclose(tlsproxy_stream); \
+ starttls_state->resume_event(event, (void *) smtp_state); \
+ myfree((void *) starttls_state); \
+ return; \
+ } while (0)
+
+/* psc_starttls_finish - complete negotiation with TLS proxy */
+
+static void psc_starttls_finish(int event, void *context)
+{
+ const char *myname = "psc_starttls_finish";
+ PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context;
+ PSC_STATE *smtp_state = starttls_state->smtp_state;
+ VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream;
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: send client handle on proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ /*
+ * We leave read-event notification enabled on the postscreen to TLS
+ * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here,
+ * and one when resuming the dummy SMTP engine.
+ */
+ if (event != EVENT_TIME)
+ event_cancel_timer(psc_starttls_finish, (void *) starttls_state);
+
+ /*
+ * Receive the "TLS is available" indication.
+ *
+ * This may seem out of order, but we must have a read transaction between
+ * sending the request attributes and sending the SMTP client file
+ * descriptor. We can't assume UNIX-domain socket semantics here.
+ */
+ if (event != EVENT_READ
+ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1 || status == 0) {
+
+ /*
+ * The TLS proxy reports that the TLS engine is not available (due to
+ * configuration error, or other causes).
+ */
+ msg_warn("%s receiving status from %s service",
+ event == EVENT_TIME ? "timeout" : "problem", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Send the remote SMTP client file descriptor.
+ */
+ else if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream)) < 0) {
+
+ /*
+ * Some error: drop the TLS proxy stream.
+ */
+ msg_warn("problem sending file handle to %s service", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * After we send the plaintext 220 greeting, the client-side TLS engine
+ * is supposed to talk first, then the server-side TLS engine. However,
+ * postscreen(8) will not participate in that conversation.
+ */
+ else {
+ PSC_SEND_REPLY(smtp_state, "220 2.0.0 Ready to start TLS\r\n");
+
+ /*
+ * Swap the SMTP client stream and the TLS proxy stream, and close
+ * the direct connection to the SMTP client. The TLS proxy will talk
+ * directly to the SMTP client, and once the TLS handshake is
+ * completed, the TLS proxy will talk plaintext to postscreen(8).
+ *
+ * Swap the file descriptors from under the VSTREAM so that we don't
+ * have to worry about loss of user-configurable VSTREAM attributes.
+ */
+ vstream_fpurge(smtp_state->smtp_client_stream, VSTREAM_PURGE_BOTH);
+ vstream_control(smtp_state->smtp_client_stream,
+ CA_VSTREAM_CTL_SWAP_FD(tlsproxy_stream),
+ CA_VSTREAM_CTL_END);
+ smtp_state->flags |= PSC_STATE_FLAG_USING_TLS;
+ PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state);
+ }
+}
+
+/* psc_starttls_first - start negotiation with TLS proxy */
+
+static void psc_starttls_first(int event, void *context)
+{
+ const char *myname = "psc_starttls_first";
+ PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context;
+ PSC_STATE *smtp_state = starttls_state->smtp_state;
+ VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream;
+ static VSTRING *remote_endpt = 0;
+
+ if (msg_verbose)
+ msg_info("%s: receive server protocol on proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ /*
+ * We leave read-event notification enabled on the postscreen to TLS
+ * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here,
+ * and one when resuming the dummy SMTP engine.
+ */
+ if (event != EVENT_TIME)
+ event_cancel_timer(psc_starttls_first, (void *) starttls_state);
+
+ /*
+ * Receive and verify the server protocol.
+ */
+ if (event != EVENT_READ
+ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s receiving %s attribute from %s service: %m",
+ event == EVENT_TIME ? "timeout" : "problem",
+ MAIL_ATTR_PROTO, psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Send the data attributes now, and send the client file descriptor in a
+ * later transaction. We report all errors asynchronously, to avoid
+ * having to maintain multiple error delivery paths.
+ *
+ * XXX The formatted endpoint should be a state member. Then, we can
+ * simplify all the format strings throughout the program.
+ */
+ if (remote_endpt == 0)
+ remote_endpt = vstring_alloc(20);
+ vstring_sprintf(remote_endpt, "[%s]:%s", smtp_state->smtp_client_addr,
+ smtp_state->smtp_client_port);
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, TLS_PROXY_FLAG_ROLE_SERVER),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID, MAIL_SERVICE_SMTPD), /* XXX */
+ ATTR_TYPE_END);
+ if (vstream_fflush(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Set up a read event for the next phase of the TLS proxy handshake.
+ */
+ PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_finish,
+ (void *) starttls_state, TLSPROXY_INIT_TIMEOUT);
+}
+
+/* psc_starttls_open - open negotiations with TLS proxy */
+
+void psc_starttls_open(PSC_STATE *smtp_state, EVENT_NOTIFY_FN resume_event)
+{
+ const char *myname = "psc_starttls_open";
+ PSC_STARTTLS *starttls_state;
+ VSTREAM *tlsproxy_stream;
+ int fd;
+
+ if (psc_tlsp_service == 0) {
+ psc_tlsp_service = concatenate(MAIL_CLASS_PRIVATE "/",
+ var_tlsproxy_service, (char *) 0);
+ }
+
+ /*
+ * Connect to the tlsproxy(8) daemon. We report all errors
+ * asynchronously, to avoid having to maintain multiple delivery paths.
+ */
+ if ((fd = LOCAL_CONNECT(psc_tlsp_service, NON_BLOCKING, 1)) < 0) {
+ msg_warn("connect to %s service: %m", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ event_request_timer(resume_event, (void *) smtp_state, 0);
+ return;
+ }
+ if (msg_verbose)
+ msg_info("%s: connecting to proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, fd, vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(tlsproxy_stream,
+ VSTREAM_CTL_PATH, psc_tlsp_service,
+ VSTREAM_CTL_END);
+
+ /*
+ * Set up a read event for the next phase of the TLS proxy handshake.
+ */
+ starttls_state = (PSC_STARTTLS *) mymalloc(sizeof(*starttls_state));
+ starttls_state->tlsproxy_stream = tlsproxy_stream;
+ starttls_state->resume_event = resume_event;
+ starttls_state->smtp_state = smtp_state;
+ PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_first,
+ (void *) starttls_state, TLSPROXY_INIT_TIMEOUT);
+}
diff --git a/src/postscreen/postscreen_state.c b/src/postscreen/postscreen_state.c
new file mode 100644
index 0000000..2b5db3c
--- /dev/null
+++ b/src/postscreen/postscreen_state.c
@@ -0,0 +1,317 @@
+/*++
+/* NAME
+/* postscreen_state 3
+/* SUMMARY
+/* postscreen session state and queue length management
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* PSC_STATE *psc_new_session_state(stream, client_addr, client_port,
+/* server_addr, server_port)
+/* VSTREAM *stream;
+/* const char *client_addr;
+/* const char *client_port;
+/* const char *server_addr;
+/* const char *server_port;
+/*
+/* void psc_free_session_state(state)
+/* PSC_STATE *state;
+/*
+/* char *psc_print_state_flags(flags, context)
+/* int flags;
+/* const char *context;
+/*
+/* void PSC_ADD_SERVER_STATE(state, server_fd)
+/* PSC_STATE *state;
+/* int server_fd;
+/*
+/* void PSC_DEL_SERVER_STATE(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_DEL_CLIENT_STATE(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_DROP_SESSION_STATE(state, final_reply)
+/* PSC_STATE *state;
+/* const char *final_reply;
+/*
+/* void PSC_ENFORCE_SESSION_STATE(state, rcpt_reply)
+/* PSC_STATE *state;
+/* const char *rcpt_reply;
+/*
+/* void PSC_PASS_SESSION_STATE(state, testname, pass_flag)
+/* PSC_STATE *state;
+/* const char *testname;
+/* int pass_flag;
+/*
+/* void PSC_FAIL_SESSION_STATE(state, fail_flag)
+/* PSC_STATE *state;
+/* int fail_flag;
+/*
+/* void PSC_UNFAIL_SESSION_STATE(state, fail_flag)
+/* PSC_STATE *state;
+/* int fail_flag;
+/* DESCRIPTION
+/* This module maintains per-client session state, and two
+/* global file descriptor counters:
+/* .IP psc_check_queue_length
+/* The total number of remote SMTP client sockets.
+/* .IP psc_post_queue_length
+/* The total number of server file descriptors that are currently
+/* in use for client file descriptor passing. This number
+/* equals the number of client file descriptors in transit.
+/* .PP
+/* psc_new_session_state() creates a new session state object
+/* for the specified client stream, and increments the
+/* psc_check_queue_length counter. The flags and per-test time
+/* stamps are initialized with PSC_INIT_TESTS(), or for concurrent
+/* sessions, with PSC_INIT_TEST_FLAGS_ONLY(). The addr and
+/* port arguments are null-terminated strings with the remote
+/* SMTP client endpoint. The _reply members are set to
+/* polite "try again" SMTP replies. The protocol member is set
+/* to "SMTP".
+/*
+/* The psc_stress variable is set to non-zero when
+/* psc_check_queue_length passes over a high-water mark.
+/*
+/* psc_free_session_state() destroys the specified session state
+/* object, closes the applicable I/O channels, and decrements
+/* the applicable file descriptor counters: psc_check_queue_length
+/* and psc_post_queue_length.
+/*
+/* The psc_stress variable is reset to zero when psc_check_queue_length
+/* passes under a low-water mark.
+/*
+/* psc_print_state_flags() converts per-session flags into
+/* human-readable form. The context is for error reporting.
+/* The result is overwritten upon each call.
+/*
+/* PSC_ADD_SERVER_STATE() updates the specified session state
+/* object with the specified server file descriptor, and
+/* increments the global psc_post_queue_length file descriptor
+/* counter.
+/*
+/* PSC_DEL_SERVER_STATE() closes the specified session state
+/* object's server file descriptor, and decrements the global
+/* psc_post_queue_length file descriptor counter.
+/*
+/* PSC_DEL_CLIENT_STATE() updates the specified session state
+/* object, closes the client stream, and decrements the global
+/* psc_check_queue_length file descriptor counter.
+/*
+/* PSC_DROP_SESSION_STATE() updates the specified session state
+/* object and closes the client stream after sending the
+/* specified SMTP reply.
+/*
+/* PSC_ENFORCE_SESSION_STATE() updates the specified session
+/* state object. It arranges that the built-in SMTP engine
+/* logs sender/recipient information and rejects all RCPT TO
+/* commands with the specified SMTP reply.
+/*
+/* PSC_PASS_SESSION_STATE() sets the specified "pass" flag.
+/* The testname is used for debug logging.
+/*
+/* PSC_FAIL_SESSION_STATE() sets the specified "fail" flag.
+/*
+/* PSC_UNFAIL_SESSION_STATE() unsets the specified "fail" flag.
+/* 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 <name_mask.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+
+/* Master server protocols. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+/* psc_new_session_state - fill in connection state for event processing */
+
+PSC_STATE *psc_new_session_state(VSTREAM *stream,
+ const char *client_addr,
+ const char *client_port,
+ const char *server_addr,
+ const char *server_port)
+{
+ PSC_STATE *state;
+
+ state = (PSC_STATE *) mymalloc(sizeof(*state));
+ if ((state->smtp_client_stream = stream) != 0)
+ psc_check_queue_length++;
+ state->smtp_server_fd = (-1);
+ state->smtp_client_addr = mystrdup(client_addr);
+ state->smtp_client_port = mystrdup(client_port);
+ state->smtp_server_addr = mystrdup(server_addr);
+ state->smtp_server_port = mystrdup(server_port);
+ state->send_buf = vstring_alloc(100);
+ state->test_name = "TEST NAME HERE";
+ state->dnsbl_reply = 0;
+ state->final_reply = "421 4.3.2 Service currently unavailable\r\n";
+ state->rcpt_reply = "450 4.3.2 Service currently unavailable\r\n";
+ state->command_count = 0;
+ state->protocol = MAIL_PROTO_SMTP;
+ state->helo_name = 0;
+ state->sender = 0;
+ state->cmd_buffer = 0;
+ state->read_state = 0;
+ state->ehlo_discard_mask = 0; /* XXX Should be ~0 */
+ state->expand_buf = 0;
+ state->where = PSC_SMTPD_CMD_CONNECT;
+
+ /*
+ * Update the stress level.
+ */
+ if (psc_stress == 0
+ && psc_check_queue_length >= psc_hiwat_check_queue_length) {
+ psc_stress = 1;
+ msg_info("entering STRESS mode with %d connections",
+ psc_check_queue_length);
+ }
+
+ /*
+ * Update the per-client session count.
+ */
+ if ((state->client_info = (PSC_CLIENT_INFO *)
+ htable_find(psc_client_concurrency, client_addr)) == 0) {
+ state->client_info = (PSC_CLIENT_INFO *)
+ mymalloc(sizeof(state->client_info[0]));
+ (void) htable_enter(psc_client_concurrency, client_addr,
+ (void *) state->client_info);
+ PSC_INIT_TESTS(state);
+ state->client_info->concurrency = 1;
+ state->client_info->pass_new_count = 0;
+ } else {
+ PSC_INIT_TEST_FLAGS_ONLY(state);
+ state->client_info->concurrency += 1;
+ }
+
+ return (state);
+}
+
+/* psc_free_session_state - destroy connection state including connections */
+
+void psc_free_session_state(PSC_STATE *state)
+{
+ const char *myname = "psc_free_session_state";
+ HTABLE_INFO *ht;
+
+ /*
+ * Update the per-client session count.
+ */
+ if ((ht = htable_locate(psc_client_concurrency,
+ state->smtp_client_addr)) == 0)
+ msg_panic("%s: unknown client address: %s",
+ myname, state->smtp_client_addr);
+ if (--(state->client_info->concurrency) == 0)
+ htable_delete(psc_client_concurrency, state->smtp_client_addr, myfree);
+
+ if (state->smtp_client_stream != 0) {
+ PSC_DEL_CLIENT_STATE(state);
+ }
+ if (state->smtp_server_fd >= 0) {
+ PSC_DEL_SERVER_STATE(state);
+ }
+ if (state->send_buf != 0)
+ state->send_buf = vstring_free(state->send_buf);
+ myfree(state->smtp_client_addr);
+ myfree(state->smtp_client_port);
+ myfree(state->smtp_server_addr);
+ myfree(state->smtp_server_port);
+ if (state->dnsbl_reply)
+ vstring_free(state->dnsbl_reply);
+ if (state->helo_name)
+ myfree(state->helo_name);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->cmd_buffer)
+ vstring_free(state->cmd_buffer);
+ if (state->expand_buf)
+ vstring_free(state->expand_buf);
+ myfree((void *) state);
+
+ if (psc_check_queue_length < 0 || psc_post_queue_length < 0)
+ msg_panic("bad queue length: check_queue=%d, post_queue=%d",
+ psc_check_queue_length, psc_post_queue_length);
+
+ /*
+ * Update the stress level.
+ */
+ if (psc_stress != 0
+ && psc_check_queue_length <= psc_lowat_check_queue_length) {
+ psc_stress = 0;
+ msg_info("leaving STRESS mode with %d connections",
+ psc_check_queue_length);
+ }
+}
+
+/* psc_print_state_flags - format state flags */
+
+const char *psc_print_state_flags(int flags, const char *context)
+{
+ static const NAME_MASK flags_mask[] = {
+ "NOFORWARD", PSC_STATE_FLAG_NOFORWARD,
+ "USING_TLS", PSC_STATE_FLAG_USING_TLS,
+ "NEW", PSC_STATE_FLAG_NEW,
+ "DNLIST_FAIL", PSC_STATE_FLAG_DNLIST_FAIL,
+ "HANGUP", PSC_STATE_FLAG_HANGUP,
+ /* unused */
+ "ALLIST_FAIL", PSC_STATE_FLAG_ALLIST_FAIL,
+
+ "PREGR_FAIL", PSC_STATE_FLAG_PREGR_FAIL,
+ "PREGR_PASS", PSC_STATE_FLAG_PREGR_PASS,
+ "PREGR_TODO", PSC_STATE_FLAG_PREGR_TODO,
+ "PREGR_DONE", PSC_STATE_FLAG_PREGR_DONE,
+
+ "DNSBL_FAIL", PSC_STATE_FLAG_DNSBL_FAIL,
+ "DNSBL_PASS", PSC_STATE_FLAG_DNSBL_PASS,
+ "DNSBL_TODO", PSC_STATE_FLAG_DNSBL_TODO,
+ "DNSBL_DONE", PSC_STATE_FLAG_DNSBL_DONE,
+
+ "PIPEL_FAIL", PSC_STATE_FLAG_PIPEL_FAIL,
+ "PIPEL_PASS", PSC_STATE_FLAG_PIPEL_PASS,
+ "PIPEL_TODO", PSC_STATE_FLAG_PIPEL_TODO,
+ "PIPEL_SKIP", PSC_STATE_FLAG_PIPEL_SKIP,
+
+ "NSMTP_FAIL", PSC_STATE_FLAG_NSMTP_FAIL,
+ "NSMTP_PASS", PSC_STATE_FLAG_NSMTP_PASS,
+ "NSMTP_TODO", PSC_STATE_FLAG_NSMTP_TODO,
+ "NSMTP_SKIP", PSC_STATE_FLAG_NSMTP_SKIP,
+
+ "BARLF_FAIL", PSC_STATE_FLAG_BARLF_FAIL,
+ "BARLF_PASS", PSC_STATE_FLAG_BARLF_PASS,
+ "BARLF_TODO", PSC_STATE_FLAG_BARLF_TODO,
+ "BARLF_SKIP", PSC_STATE_FLAG_BARLF_SKIP,
+ 0,
+ };
+
+ return (str_name_mask_opt((VSTRING *) 0, context, flags_mask, flags,
+ NAME_MASK_PIPE | NAME_MASK_NUMBER));
+}
diff --git a/src/postscreen/postscreen_tests.c b/src/postscreen/postscreen_tests.c
new file mode 100644
index 0000000..5e18622
--- /dev/null
+++ b/src/postscreen/postscreen_tests.c
@@ -0,0 +1,341 @@
+/*++
+/* NAME
+/* postscreen_tests 3
+/* SUMMARY
+/* postscreen tests timestamp/flag bulk support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void PSC_INIT_TESTS(state)
+/* PSC_STATE *state;
+/*
+/* void psc_new_tests(state)
+/* PSC_STATE *state;
+/*
+/* void psc_parse_tests(state, stamp_text, time_value)
+/* PSC_STATE *state;
+/* const char *stamp_text;
+/* time_t time_value;
+/*
+/* void psc_todo_tests(state, time_value)
+/* PSC_STATE *state;
+/* const char *stamp_text;
+/* time_t time_value;
+/*
+/* char *psc_print_tests(buffer, state)
+/* VSTRING *buffer;
+/* PSC_STATE *state;
+/*
+/* char *psc_print_grey_key(buffer, client, helo, sender, rcpt)
+/* VSTRING *buffer;
+/* const char *client;
+/* const char *helo;
+/* const char *sender;
+/* const char *rcpt;
+/*
+/* const char *psc_test_name(tindx)
+/* int tindx;
+/* DESCRIPTION
+/* The functions in this module overwrite the per-test expiration
+/* time stamps and all flags bits. Some functions are implemented
+/* as unsafe macros, meaning they evaluate one or more arguments
+/* multiple times.
+/*
+/* PSC_INIT_TESTS() is an unsafe macro that sets the per-test
+/* expiration time stamps to PSC_TIME_STAMP_INVALID, and that
+/* zeroes all the flags bits. These values are not meant to
+/* be stored into the postscreen(8) cache.
+/*
+/* PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits. It
+/* should be used when the time stamps are already initialized.
+/*
+/* psc_new_tests() sets all test expiration time stamps to
+/* PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
+/*
+/* psc_parse_tests() parses a cache file record and invokes
+/* psc_todo_tests().
+/*
+/* psc_todo_tests() overwrites all per-session flag bits, and
+/* populates the flags based on test expiration time stamp
+/* information. Tests are considered "expired" when they
+/* would be expired at the specified time value. Only enabled
+/* tests are flagged as "expired"; the object is flagged as
+/* "new" if some enabled tests have "new" time stamps.
+/*
+/* psc_print_tests() creates a cache file record for the
+/* specified flags and per-test expiration time stamps.
+/* This may modify the time stamps for disabled tests.
+/*
+/* psc_print_grey_key() prints a greylist lookup key.
+/*
+/* psc_test_name() returns the name for the specified text
+/* index.
+/* 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 <stdio.h> /* sscanf */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <name_code.h>
+#include <sane_strtol.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Kludge to detect if some test is enabled.
+ */
+#define PSC_PREGR_TEST_ENABLE() (*var_psc_pregr_banner != 0)
+#define PSC_DNSBL_TEST_ENABLE() (*var_psc_dnsbl_sites != 0)
+
+ /*
+ * Format of a persistent cache entry (which is almost but not quite the
+ * same as the in-memory representation).
+ *
+ * Each cache entry has one time stamp for each test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It
+ * is reserved for in-memory objects that are still being initialized.
+ *
+ * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed.
+ * Postscreen will log the client with "pass new" when it passes the final
+ * test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never
+ * passed, and that the test was disabled when the cache entry was written.
+ *
+ * - Otherwise, the test was passed, and the time stamp indicates when that
+ * test result expires.
+ *
+ * A cache entry is expired when the time stamps of all passed tests are
+ * expired.
+ */
+
+/* psc_new_tests - initialize new test results from scratch */
+
+void psc_new_tests(PSC_STATE *state)
+{
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
+ * recognize cache entries that haven't passed all enabled tests. When we
+ * write a cache entry to the database, any new-but-disabled tests will
+ * get a PSC_TIME_STAMP_DISABLED time stamp.
+ */
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW;
+
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
+}
+
+/* psc_parse_tests - parse test results from cache */
+
+void psc_parse_tests(PSC_STATE *state,
+ const char *stamp_str,
+ time_t time_value)
+{
+ const char *start = stamp_str;
+ char *cp;
+ time_t *time_stamps = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * Parse the cache entry, and allow for older postscreen versions that
+ * implemented fewer tests. We pretend that the newer tests were disabled
+ * at the time that the cache entry was written.
+ */
+ for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+ *sp = sane_strtoul(start, &cp, 10);
+ if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
+ *sp = PSC_TIME_STAMP_DISABLED;
+ if (msg_verbose)
+ msg_info("%s -> %lu", start, (unsigned long) *sp);
+ if (*cp == ';')
+ start = cp + 1;
+ else
+ start = cp;
+ }
+
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, time_value);
+}
+
+/* psc_todo_tests - determine what tests to perform */
+
+void psc_todo_tests(PSC_STATE *state, time_t time_value)
+{
+ time_t *expire_time = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * Reset all per-session flags.
+ */
+ state->flags = 0;
+
+ /*
+ * Flag the tests as "new" when the cache entry has fields for all
+ * enabled tests, but the remote SMTP client has not yet passed all those
+ * tests.
+ */
+ for (sp = expire_time; sp < expire_time + PSC_TINDX_COUNT; sp++) {
+ if (*sp == PSC_TIME_STAMP_NEW)
+ state->flags |= PSC_STATE_FLAG_NEW;
+ }
+
+ /*
+ * Don't flag disabled tests as "todo", because there would be no way to
+ * make those bits go away.
+ */
+ if (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+
+ /*
+ * If any test has expired, proactively refresh tests that will expire
+ * soon. This can increase the occurrence of client-visible delays, but
+ * avoids questions about why a client can pass some test and then fail
+ * within seconds. The proactive refresh time is really a surrogate for
+ * the user's curiosity level, and therefore hard to choose optimally.
+ */
+#ifdef VAR_PSC_REFRESH_TIME
+ if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0
+ && var_psc_refresh_time > 0) {
+ time_t refresh_time = time_value + var_psc_refresh_time;
+
+ if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+ }
+#endif
+
+ /*
+ * Gratuitously make postscreen logging more useful by turning on all
+ * enabled pre-handshake tests when any pre-handshake test is turned on.
+ *
+ * XXX Don't enable PREGREET gratuitously before the test expires. With a
+ * short TTL for DNSBL allowlisting, turning on PREGREET would force a
+ * full postscreen_greet_wait too frequently.
+ */
+#if 0
+ if (state->flags & PSC_STATE_MASK_EARLY_TODO) {
+ if (PSC_PREGR_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ }
+#endif
+}
+
+/* psc_print_tests - print postscreen cache record */
+
+char *psc_print_tests(VSTRING *buf, PSC_STATE *state)
+{
+ const char *myname = "psc_print_tests";
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * Sanity check.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
+ msg_panic("%s: attempt to save a no-update record", myname);
+
+ /*
+ * Give disabled tests a dummy time stamp so that we don't log a client
+ * with "pass new" when some disabled test becomes enabled at some later
+ * time.
+ */
+ if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED;
+ if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED;
+
+ vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
+ (unsigned long) expire_time[PSC_TINDX_PREGR],
+ (unsigned long) expire_time[PSC_TINDX_DNSBL],
+ (unsigned long) expire_time[PSC_TINDX_PIPEL],
+ (unsigned long) expire_time[PSC_TINDX_NSMTP],
+ (unsigned long) expire_time[PSC_TINDX_BARLF]);
+ return (STR(buf));
+}
+
+/* psc_print_grey_key - print postscreen cache record */
+
+char *psc_print_grey_key(VSTRING *buf, const char *client,
+ const char *helo, const char *sender,
+ const char *rcpt)
+{
+ return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
+ client, helo, sender, rcpt)));
+}
+
+/* psc_test_name - map test index to symbolic name */
+
+const char *psc_test_name(int tindx)
+{
+ const char *myname = "psc_test_name";
+ const NAME_CODE test_name_map[] = {
+ PSC_TNAME_PREGR, PSC_TINDX_PREGR,
+ PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
+ PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
+ PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
+ PSC_TNAME_BARLF, PSC_TINDX_BARLF,
+ 0, -1,
+ };
+ const char *result;
+
+ if ((result = str_name_code(test_name_map, tindx)) == 0)
+ msg_panic("%s: bad index %d", myname, tindx);
+ return (result);
+}
diff --git a/src/postsuper/.indent.pro b/src/postsuper/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postsuper/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postsuper/.printfck b/src/postsuper/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/postsuper/.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/postsuper/Makefile.in b/src/postsuper/Makefile.in
new file mode 100644
index 0000000..30b540a
--- /dev/null
+++ b/src/postsuper/Makefile.in
@@ -0,0 +1,90 @@
+SHELL = /bin/sh
+SRCS = postsuper.c
+OBJS = postsuper.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postsuper
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+postsuper.o: ../../include/argv.h
+postsuper.o: ../../include/check_arg.h
+postsuper.o: ../../include/clean_env.h
+postsuper.o: ../../include/file_id.h
+postsuper.o: ../../include/mail_conf.h
+postsuper.o: ../../include/mail_open_ok.h
+postsuper.o: ../../include/mail_params.h
+postsuper.o: ../../include/mail_parm_split.h
+postsuper.o: ../../include/mail_queue.h
+postsuper.o: ../../include/mail_task.h
+postsuper.o: ../../include/mail_version.h
+postsuper.o: ../../include/maillog_client.h
+postsuper.o: ../../include/msg.h
+postsuper.o: ../../include/msg_vstream.h
+postsuper.o: ../../include/mymalloc.h
+postsuper.o: ../../include/myrand.h
+postsuper.o: ../../include/name_mask.h
+postsuper.o: ../../include/safe.h
+postsuper.o: ../../include/safe_open.h
+postsuper.o: ../../include/safe_ultostr.h
+postsuper.o: ../../include/sane_fsops.h
+postsuper.o: ../../include/scan_dir.h
+postsuper.o: ../../include/set_ugid.h
+postsuper.o: ../../include/sys_defs.h
+postsuper.o: ../../include/vbuf.h
+postsuper.o: ../../include/vstream.h
+postsuper.o: ../../include/vstring.h
+postsuper.o: ../../include/vstring_vstream.h
+postsuper.o: ../../include/warn_stat.h
+postsuper.o: postsuper.c
diff --git a/src/postsuper/postsuper.c b/src/postsuper/postsuper.c
new file mode 100644
index 0000000..d3f2d5b
--- /dev/null
+++ b/src/postsuper/postsuper.c
@@ -0,0 +1,1604 @@
+/*++
+/* NAME
+/* postsuper 1
+/* SUMMARY
+/* Postfix superintendent
+/* SYNOPSIS
+/* .fi
+/* \fBpostsuper\fR [\fB-psSv\fR]
+/* [\fB-c \fIconfig_dir\fR] [\fB-d \fIqueue_id\fR]
+/* [\fB-e \fIqueue_id\fR] [\fB-f \fIqueue_id\fR]
+/* [\fB-h \fIqueue_id\fR] [\fB-H \fIqueue_id\fR]
+/* [\fB-r \fIqueue_id\fR] [\fIdirectory ...\fR]
+/* DESCRIPTION
+/* The \fBpostsuper\fR(1) command does maintenance jobs on the Postfix
+/* queue. Use of the command is restricted to the superuser.
+/* See the \fBpostqueue\fR(1) command for unprivileged queue operations
+/* such as listing or flushing the mail queue.
+/*
+/* By default, \fBpostsuper\fR(1) performs the operations
+/* requested with the
+/* \fB-s\fR and \fB-p\fR command-line options on all Postfix queue
+/* directories - this includes the \fBincoming\fR, \fBactive\fR,
+/* \fBdeferred\fR, and \fBhold\fR directories with message
+/* files and the \fBbounce\fR,
+/* \fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files.
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory. See also the
+/* MAIL_CONFIG environment setting below.
+/* .IP "\fB-d \fIqueue_id\fR"
+/* Delete one message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR).
+/*
+/* To delete multiple files, specify the \fB-d\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input. For example, to delete all mail
+/* with exactly one recipient \fBuser@example.com\fR:
+/* .sp
+/* .nf
+/* postqueue -j | jq -r '
+/* # See JSON OBJECT FORMAT section in the postqueue(1) manpage
+/* select(.recipients[0].address == "user@example.com")
+/* | select(.recipients[1].address == null)
+/* | .queue_id
+/* ' | postsuper -d -
+/* .fi
+/* .sp
+/* (note the "jq -r" option), or the historical form:
+/* .sp
+/* .nf
+/* mailq | tail -n +2 | grep -v '^ *(' | awk 'BEGIN { RS = "" }
+/* # $7=sender, $8=recipient1, $9=recipient2
+/* { if ($8 == "user@example.com" && $9 == "")
+/* print $1 }
+/* ' | tr -d '*!' | postsuper -d -
+/* .fi
+/* .sp
+/* Specify "\fB-d ALL\fR" to remove all messages; for example, specify
+/* "\fB-d ALL deferred\fR" to delete all mail in the \fBdeferred\fR queue.
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* Warning: Postfix queue IDs are reused (always with Postfix
+/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no).
+/* There is a very small possibility that postsuper deletes the
+/* wrong message file when it is executed while the Postfix mail
+/* system is delivering mail.
+/* .sp
+/* The scenario is as follows:
+/* .RS
+/* .IP 1)
+/* The Postfix queue manager deletes the message that \fBpostsuper\fR(1)
+/* is asked to delete, because Postfix is finished with the
+/* message (it is delivered, or it is returned to the sender).
+/* .IP 2)
+/* New mail arrives, and the new message is given the same queue ID
+/* as the message that \fBpostsuper\fR(1) is supposed to delete.
+/* The probability for reusing a deleted queue ID is about 1 in 2**15
+/* (the number of different microsecond values that the system clock
+/* can distinguish within a second).
+/* .IP 3)
+/* \fBpostsuper\fR(1) deletes the new message, instead of the old
+/* message that it should have deleted.
+/* .RE
+/* .IP "\fB-e \fIqueue_id\fR"
+/* .IP "\fB-f \fIqueue_id\fR"
+/* Request forced expiration for one message with the named
+/* queue ID in the named mail queue(s) (default: \fBhold\fR,
+/* \fBincoming\fR, \fBactive\fR and \fBdeferred\fR).
+/* .RS
+/* .IP \(bu
+/* The message will be returned to the sender when the queue
+/* manager attempts to deliver that message (note that Postfix
+/* will never deliver messages in the \fBhold\fR queue).
+/* .IP \(bu
+/* The \fB-e\fR and \fB-f\fR options both request forced
+/* expiration. The difference is that \fB-f\fR will also release
+/* a message if it is in the \fBhold\fR queue. With \fB-e\fR, such
+/* a message would not be returned to the sender until it is
+/* released with \fB-f\fR or \fB-H\fR.
+/* .IP \(bu
+/* When a deferred message is force-expired, the return message
+/* will state the reason for the delay. Otherwise, the reason
+/* will be "message is administratively expired".
+/* .RE
+/* .IP
+/* To expire multiple files, specify the \fB-e\fR or \fB-f\fR
+/* option multiple times, or specify a \fIqueue_id\fR of \fB-\fR
+/* to read queue IDs from standard input (see the \fB-d\fR option
+/* above for an example, but be sure to replace \fB-d\fR in
+/* the example).
+/* .sp
+/* Specify "\fB-e ALL\fR" or "\fB-f ALL\fR" to expire all
+/* messages; for example, specify "\fB-e ALL deferred\fR" to
+/* expire all mail in the \fBdeferred\fR queue. As a safety
+/* measure, the word \fBALL\fR must be specified in upper case.
+/* .sp
+/* These features are available in Postfix 3.5 and later.
+/* .IP "\fB-h \fIqueue_id\fR"
+/* Put mail "on hold" so that no attempt is made to deliver it.
+/* Move one message with the named queue ID from the named
+/* mail queue(s) (default: \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR) to the \fBhold\fR queue.
+/*
+/* To hold multiple files, specify the \fB-h\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Specify "\fB-h ALL\fR" to hold all messages; for example, specify
+/* "\fB-h ALL deferred\fR" to hold all mail in the \fBdeferred\fR queue.
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* Note: while mail is "on hold" it will not expire when its
+/* time in the queue exceeds the \fBmaximal_queue_lifetime\fR
+/* or \fBbounce_queue_lifetime\fR setting. It becomes subject to
+/* expiration after it is released from "hold".
+/* .sp
+/* This feature is available in Postfix 2.0 and later.
+/* .IP "\fB-H \fIqueue_id\fR"
+/* Release mail that was put "on hold".
+/* Move one message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR) to the \fBdeferred\fR queue.
+/*
+/* To release multiple files, specify the \fB-H\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Note: specify "\fBpostsuper -r\fR" to release mail that was kept on
+/* hold for a significant fraction of \fB$maximal_queue_lifetime\fR
+/* or \fB$bounce_queue_lifetime\fR, or longer.
+/* .sp
+/* Specify "\fB-H ALL\fR" to release all mail that is "on hold".
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* This feature is available in Postfix 2.0 and later.
+/* .IP \fB-p\fR
+/* Purge old temporary files that are left over after system or
+/* software crashes.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .IP "\fB-r \fIqueue_id\fR"
+/* Requeue the message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR).
+/*
+/* To requeue multiple files, specify the \fB-r\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Specify "\fB-r ALL\fR" to requeue all messages. As a safety
+/* measure, the word \fBALL\fR must be specified in upper case.
+/* .sp
+/* A requeued message is moved to the \fBmaildrop\fR queue,
+/* from where it is copied by the \fBpickup\fR(8) and
+/* \fBcleanup\fR(8) daemons to a new queue file. In many
+/* respects its handling differs from that of a new local
+/* submission.
+/* .RS
+/* .IP \(bu
+/* The message is not subjected to the smtpd_milters or
+/* non_smtpd_milters settings. When mail has passed through
+/* an external content filter, this would produce incorrect
+/* results with Milter applications that depend on original
+/* SMTP connection state information.
+/* .IP \(bu
+/* The message is subjected again to mail address rewriting
+/* and substitution. This is useful when rewriting rules or
+/* virtual mappings have changed.
+/* .sp
+/* The address rewriting context (local or remote) is the same
+/* as when the message was received.
+/* .IP \(bu
+/* The message is subjected to the same content_filter settings
+/* (if any) as used for new local mail submissions. This is
+/* useful when content_filter settings have changed.
+/* .RE
+/* .IP
+/* Warning: Postfix queue IDs are reused (always with Postfix
+/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no).
+/* There is a very small possibility that \fBpostsuper\fR(1) requeues
+/* the wrong message file when it is executed while the Postfix mail
+/* system is running, but no harm should be done.
+/* .sp
+/* This feature is available in Postfix 1.1 and later.
+/* .IP \fB-s\fR
+/* Structure check and structure repair. This should be done once
+/* before Postfix startup.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .RS
+/* .IP \(bu
+/* Rename files whose name does not match the message file inode
+/* number. This operation is necessary after restoring a mail
+/* queue from a different machine or from backup, when queue
+/* files were created with Postfix <= 2.8 or with
+/* "enable_long_queue_ids = no".
+/* .IP \(bu
+/* Move queue files that are in the wrong place in the file system
+/* hierarchy and remove subdirectories that are no longer needed.
+/* File position rearrangements are necessary after a change in the
+/* \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR
+/* configuration parameters.
+/* .IP \(bu
+/* Rename queue files created with "enable_long_queue_ids =
+/* yes" to short names, for migration to Postfix <= 2.8. The
+/* procedure is as follows:
+/* .sp
+/* .nf
+/* .na
+/* # postfix stop
+/* # postconf enable_long_queue_ids=no
+/* # postsuper
+/* .ad
+/* .fi
+/* .sp
+/* Run \fBpostsuper\fR(1) repeatedly until it stops reporting
+/* file name changes.
+/* .RE
+/* .IP \fB-S\fR
+/* A redundant version of \fB-s\fR that requires that long
+/* file names also match the message file inode number. This
+/* option exists for testing purposes, and is available with
+/* Postfix 2.9 and later.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream and to
+/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/*
+/* \fBpostsuper\fR(1) reports the number of messages deleted
+/* with \fB-d\fR, the number of messages expired with \fB-e\fR,
+/* the number of messages expired or released with \fB-f\fR,
+/* the number of messages held or released with \fB-h\fR or
+/* \fB-H\fR, the number of messages requeued with \fB-r\fR,
+/* and the number of messages whose queue file name was fixed
+/* with \fB-s\fR. The report is written to the standard error
+/* stream and to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP MAIL_CONFIG
+/* Directory with the \fBmain.cf\fR file.
+/* BUGS
+/* Mail that is not sanitized by Postfix (i.e. mail in the \fBmaildrop\fR
+/* queue) cannot be placed "on hold".
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBhash_queue_depth (1)\fR"
+/* The number of subdirectory levels for queue directories listed with
+/* the hash_queue_names parameter.
+/* .IP "\fBhash_queue_names (deferred, defer)\fR"
+/* The names of queue directories that are split across multiple
+/* subdirectory levels.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .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.9 and later:
+/* .IP "\fBenable_long_queue_ids (no)\fR"
+/* Enable long, non-repeating, queue IDs (queue file names).
+/* SEE ALSO
+/* sendmail(1), Sendmail-compatible user interface
+/* postqueue(1), unprivileged queue operations
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h> /* remove() */
+#include <utime.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <scan_dir.h>
+#include <vstring.h>
+#include <safe.h>
+#include <set_ugid.h>
+#include <argv.h>
+#include <vstring_vstream.h>
+#include <sane_fsops.h>
+#include <myrand.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+#include <safe_open.h>
+#include <name_mask.h>
+
+/* Global library. */
+
+#include <mail_task.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#define MAIL_QUEUE_INTERNAL
+#include <mail_queue.h>
+#include <mail_open_ok.h>
+#include <file_id.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#define MAX_TEMP_AGE (7 * 60 * 60 * 24) /* temp file maximal age */
+#define STR vstring_str /* silly little macro */
+
+#define ACTION_STRUCT (1<<0) /* fix file organization */
+#define ACTION_PURGE (1<<1) /* purge old temp files */
+#define ACTION_DELETE_ONE (1<<2) /* delete named queue file(s) */
+#define ACTION_DELETE_ALL (1<<3) /* delete all queue file(s) */
+#define ACTION_REQUEUE_ONE (1<<4) /* requeue named queue file(s) */
+#define ACTION_REQUEUE_ALL (1<<5) /* requeue all queue file(s) */
+#define ACTION_HOLD_ONE (1<<6) /* put named queue file(s) on hold */
+#define ACTION_HOLD_ALL (1<<7) /* put all messages on hold */
+#define ACTION_RELEASE_ONE (1<<8) /* release named queue file(s) */
+#define ACTION_RELEASE_ALL (1<<9) /* release all "on hold" mail */
+#define ACTION_STRUCT_RED (1<<10) /* fix long queue ID inode fields */
+#define ACTION_EXPIRE_ONE (1<<11) /* expire named queue file(s) */
+#define ACTION_EXPIRE_ALL (1<<12) /* expire all queue file(s) */
+#define ACTION_EXP_REL_ONE (1<<13) /* expire+release named queue file(s) */
+#define ACTION_EXP_REL_ALL (1<<14) /* expire+release all queue file(s) */
+
+#define ACTION_DEFAULT (ACTION_STRUCT | ACTION_PURGE)
+
+ /*
+ * Actions that operate on individually named queue files. These must never
+ * be done after fixing queue file names to match their inode number because
+ * the target file may have been replaced. Actions that move files are safe
+ * only when queue file names match their inode number, otherwise mail can
+ * be lost due to filename collisions.
+ */
+#define ACTIONS_BY_QUEUE_ID (ACTION_DELETE_ONE | ACTION_REQUEUE_ONE \
+ | ACTION_HOLD_ONE | ACTION_RELEASE_ONE \
+ | ACTION_EXPIRE_ONE | ACTION_EXP_REL_ONE)
+
+ /*
+ * Mass actions. Actions that move files are safe only when queue file names
+ * match their inode number, otherwise mail can be lost due to filename
+ * collisions.
+ */
+#define ACTIONS_BY_WILDCARD (ACTION_DELETE_ALL | ACTION_REQUEUE_ALL \
+ | ACTION_HOLD_ALL | ACTION_RELEASE_ALL \
+ | ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL)
+
+#define ACTIONS_FOR_REPAIR (ACTION_PURGE | ACTION_STRUCT \
+ | ACTION_STRUCT_RED)
+
+ /*
+ * Information about queue directories and what we expect to do there. If a
+ * file has unexpected owner permissions and is older than some threshold,
+ * the file is discarded. We don't step into maildrop subdirectories - if
+ * maildrop is writable, we might end up in the wrong place, deleting the
+ * wrong information.
+ */
+struct queue_info {
+ char *name; /* directory name */
+ int perms; /* expected permissions */
+ int flags; /* see below */
+};
+
+#define RECURSE (1<<0) /* step into subdirectories */
+#define DONT_RECURSE 0 /* don't step into directories */
+
+static struct queue_info queue_info[] = {
+ MAIL_QUEUE_MAILDROP, MAIL_QUEUE_STAT_READY, DONT_RECURSE,
+ MAIL_QUEUE_INCOMING, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_ACTIVE, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_DEFERRED, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_HOLD, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_TRACE, 0600, RECURSE,
+ MAIL_QUEUE_DEFER, 0600, RECURSE,
+ MAIL_QUEUE_BOUNCE, 0600, RECURSE,
+ MAIL_QUEUE_FLUSH, 0600, RECURSE,
+ 0,
+};
+
+ /*
+ * Directories with per-message meta files.
+ */
+const char *log_queue_names[] = {
+ MAIL_QUEUE_BOUNCE,
+ MAIL_QUEUE_DEFER,
+ MAIL_QUEUE_TRACE,
+ 0,
+};
+
+ /*
+ * Cruft that we append to a file name when a queue ID is named after the
+ * message file inode number. This cruft must not pass mail_queue_id_ok() so
+ * that the queue manager will ignore it, should people be so unwise as to
+ * run this operation on a live mail system.
+ */
+#define SUFFIX "#FIX"
+#define SUFFIX_LEN 4
+
+ /*
+ * Grr. These counters are global, because C only has clumsy ways to return
+ * multiple results from a function.
+ */
+static int message_requeued = 0; /* requeued messages */
+static int message_held = 0; /* messages put on hold */
+static int message_released = 0; /* messages released from hold */
+static int message_deleted = 0; /* deleted messages */
+static int message_expired = 0; /* expired messages */
+static int inode_fixed = 0; /* queue id matched to inode number */
+static int inode_mismatch = 0; /* queue id inode mismatch */
+static int position_mismatch = 0; /* file position mismatch */
+
+ /*
+ * Silly little macros. These translate arcane expressions into something
+ * more at a conceptual level.
+ */
+#define MESSAGE_QUEUE(qp) ((qp)->perms == MAIL_QUEUE_STAT_READY)
+#define READY_MESSAGE(st) (((st).st_mode & S_IRWXU) == MAIL_QUEUE_STAT_READY)
+
+/* find_queue_info - look up expected permissions field by queue name */
+
+static struct queue_info *find_queue_info(const char *queue_name)
+{
+ struct queue_info *qp;
+
+ for (qp = queue_info; qp->name; qp++)
+ if (strcmp(queue_name, qp->name) == 0)
+ return (qp);
+ msg_fatal("invalid directory name: %s", queue_name);
+}
+
+/* postremove - remove file with extreme prejudice */
+
+static int postremove(const char *path)
+{
+ int ret;
+
+ if ((ret = remove(path)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("remove file %s: %m", path);
+ } else {
+ if (msg_verbose)
+ msg_info("removed file %s", path);
+ }
+ return (ret);
+}
+
+/* postexpire - expire file, setting the group execute permission */
+
+static int postexpire(const char *path)
+{
+ static VSTRING *why = 0;
+ VSTREAM *fp;
+ struct stat st;
+
+ /*
+ * Initialize.
+ */
+ if (why == 0)
+ why = vstring_alloc(100);
+
+ /*
+ * We don't actually verify the file content, therefore safe_open() the
+ * queue file so that we won't add group execute permission to some file
+ * outside of the mail queue.
+ */
+ if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) == 0) {
+ if (errno != ENOENT)
+ msg_warn("expire file %s: %s", path, vstring_str(why));
+ return (-1);
+ }
+#define POSTEXPIRE_RETURN(x) do { \
+ (void) vstream_fclose(fp); \
+ return (x); \
+ } while (0)
+
+ if (!READY_MESSAGE(st))
+ POSTEXPIRE_RETURN(-1); /* must not expire */
+ if ((st.st_mode & MAIL_QUEUE_STAT_EXPIRE) != 0)
+ POSTEXPIRE_RETURN(-1); /* already expired */
+ if (fchmod(vstream_fileno(fp),
+ (st.st_mode | MAIL_QUEUE_STAT_EXPIRE) & ~S_IFMT) < 0) {
+ msg_warn("expire file %s: cannot set permission: %m", path);
+ POSTEXPIRE_RETURN(-1);
+ }
+ POSTEXPIRE_RETURN(0);
+}
+
+/* postrename - rename file with extreme prejudice */
+
+static int postrename(const char *old, const char *new)
+{
+ int ret;
+
+ if ((ret = sane_rename(old, new)) < 0) {
+ if (errno != ENOENT
+ || mail_queue_mkdirs(new) < 0
+ || (ret = sane_rename(old, new)) < 0)
+ if (errno != ENOENT)
+ msg_fatal("rename file %s as %s: %m", old, new);
+ } else {
+ if (msg_verbose)
+ msg_info("renamed file %s as %s", old, new);
+ }
+ return (ret);
+}
+
+/* postrmdir - remove directory with extreme prejudice */
+
+static int postrmdir(const char *path)
+{
+ int ret;
+
+ if ((ret = rmdir(path)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("remove directory %s: %m", path);
+ } else {
+ if (msg_verbose)
+ msg_info("remove directory %s", path);
+ }
+ return (ret);
+}
+
+/* delete_one - delete one message instance and all its associated files */
+
+static void delete_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char **log_qpp;
+ const char *msg_path;
+ VSTRING *log_path_buf;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ log_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Delete trace/defer/bounce logfiles before
+ * deleting the corresponding message file, and only if the message file
+ * exists. This minimizes but does not eliminate a race condition with
+ * queue ID reuse which results in deleting the wrong files.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES)
+ continue;
+ for (log_qpp = log_queue_names; *log_qpp != 0; log_qpp++)
+ postremove(mail_queue_path(log_path_buf, *log_qpp, queue_id));
+ if (postremove(msg_path) == 0) {
+ found = 1;
+ msg_info("%s: removed", queue_id);
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(log_path_buf);
+ message_deleted += found;
+}
+
+/* requeue_one - requeue one message instance and delete its logfiles */
+
+static void requeue_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+ int tries;
+ struct utimbuf tbuf;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Like the mass requeue operation, we not
+ * delete defer or bounce logfiles, to avoid losing a race where the
+ * queue manager decides to bounce mail after all recipients have been
+ * tried.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_MAILDROP, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ tbuf.actime = tbuf.modtime = time((time_t *) 0);
+ if (utime(STR(new_path_buf), &tbuf) < 0)
+ msg_warn("%s: reset time stamps: %m", STR(new_path_buf));
+ msg_info("%s: requeued", queue_id);
+ found = 1;
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(new_path_buf);
+ message_requeued += found;
+}
+
+/* hold_one - put "on hold" one message instance */
+
+static void hold_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Like the mass requeue operation, we not
+ * delete defer or bounce logfiles, to avoid losing a race where the
+ * queue manager decides to bounce mail after all recipients have been
+ * tried.
+ *
+ * XXX We must not put maildrop mail on hold because that would mix already
+ * sanitized mail with mail that still needs to be sanitized.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) == 0)
+ continue;
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_HOLD, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ msg_info("%s: placed on hold", queue_id);
+ found = 1;
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(new_path_buf);
+ message_held += found;
+}
+
+/* release_one - release one message instance that was placed "on hold" */
+
+static void release_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip inapplicable directories. This can happen when -H is combined
+ * with other operations.
+ */
+ found = 0;
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) != 0)
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_DEFERRED, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ msg_info("%s: released from hold", queue_id);
+ found = 1;
+ break;
+ }
+ }
+ vstring_free(new_path_buf);
+ message_released += found;
+}
+
+/* expire_one - expire one message instance */
+
+static void expire_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *msg_path;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+
+ /*
+ * Skip meta file directories.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES)
+ continue;
+ if (postexpire(msg_path) == 0) {
+ found = 1;
+ msg_info("%s: expired", queue_id);
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ message_expired += found;
+}
+
+/* exp_rel_one - expire or release one message instance */
+
+static void exp_rel_one(const char **queue_names, const char *queue_id)
+{
+ expire_one(queue_names, queue_id);
+ release_one(queue_names, queue_id);
+}
+
+/* operate_stream - operate on queue IDs given on stream */
+
+static void operate_stream(VSTREAM *fp,
+ void (*operator) (const char **, const char *),
+ const char **queues)
+{
+ VSTRING *buf = vstring_alloc(20);
+
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
+ operator(queues, STR(buf));
+
+ vstring_free(buf);
+}
+
+/* fix_queue_id - make message queue ID match inode number */
+
+static int fix_queue_id(const char *actual_path, const char *actual_queue,
+ const char *actual_id, struct stat * st)
+{
+ VSTRING *old_path = vstring_alloc(10);
+ VSTRING *new_path = vstring_alloc(10);
+ VSTRING *new_id = vstring_alloc(10);
+ const char **log_qpp;
+ char *cp;
+ int ret;
+
+ /*
+ * Create the new queue ID from the existing time digits and from the new
+ * inode number. Since we are renaming multiple files, the new name must
+ * be deterministic so that we can recover even when the renaming
+ * operation is interrupted in the middle.
+ */
+ if (MQID_FIND_LG_INUM_SEPARATOR(cp, actual_id) == 0) {
+ /* Short->short queue ID. Replace the inode portion. */
+ vstring_sprintf(new_id, "%.*s%s",
+ MQID_SH_USEC_PAD, actual_id,
+ get_file_id_st(st, 0));
+ } else if (var_long_queue_ids) {
+ /* Long->long queue ID. Replace the inode portion. */
+ vstring_sprintf(new_id, "%.*s%c%s",
+ (int) (cp - actual_id), actual_id, MQID_LG_INUM_SEP,
+ get_file_id_st(st, 1));
+ } else {
+ /* Long->short queue ID. Reformat time and replace inode portion. */
+ MQID_LG_GET_HEX_USEC(new_id, cp);
+ vstring_strcat(new_id, get_file_id_st(st, 0));
+ }
+
+ /*
+ * Rename logfiles before renaming the message file, so that we can
+ * recover when a previous attempt was interrupted.
+ */
+ for (log_qpp = log_queue_names; *log_qpp; log_qpp++) {
+ mail_queue_path(old_path, *log_qpp, actual_id);
+ mail_queue_path(new_path, *log_qpp, STR(new_id));
+ vstring_strcat(new_path, SUFFIX);
+ postrename(STR(old_path), STR(new_path));
+ }
+
+ /*
+ * Rename the message file last, so that we know that we are done with
+ * this message and with all its logfiles.
+ */
+ mail_queue_path(new_path, actual_queue, STR(new_id));
+ vstring_strcat(new_path, SUFFIX);
+ ret = postrename(actual_path, STR(new_path));
+
+ /*
+ * Clean up.
+ */
+ vstring_free(old_path);
+ vstring_free(new_path);
+ vstring_free(new_id);
+
+ return (ret);
+}
+
+/* super - check queue structure, clean up, do wild-card operations */
+
+static void super(const char **queues, int action)
+{
+ ARGV *hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP);
+ VSTRING *actual_path = vstring_alloc(10);
+ VSTRING *wanted_path = vstring_alloc(10);
+ struct stat st;
+ const char *queue_name;
+ SCAN_DIR *info;
+ char *path;
+ int actual_depth;
+ int wanted_depth;
+ char **cpp;
+ struct queue_info *qp;
+ unsigned long inum;
+ int long_name;
+ int error;
+
+ /*
+ * This routine was originally written to do multiple mass operations in
+ * one pass. However this hard-coded the order of operations which became
+ * difficult to explain. As of Postfix 3.5 this routine is called for one
+ * mass operation at a time, in the user-specified order. The exception
+ * is that repair operations (purging stale files, queue hashing, and
+ * file-inode match) are combined and done before other mass operations.
+ */
+
+ /*
+ * Make sure every file is in the right place, clean out stale files, and
+ * remove non-file/non-directory objects.
+ */
+ while ((queue_name = *queues++) != 0) {
+
+ if (msg_verbose)
+ msg_info("queue: %s", queue_name);
+
+ /*
+ * Look up queue-specific properties: desired hashing depth, what
+ * file permissions to look for, and whether or not it is desirable
+ * to step into subdirectories.
+ */
+ qp = find_queue_info(queue_name);
+ for (cpp = hash_queue_names->argv; /* void */ ; cpp++) {
+ if (*cpp == 0) {
+ wanted_depth = 0;
+ break;
+ }
+ if (strcmp(*cpp, queue_name) == 0) {
+ wanted_depth = var_hash_queue_depth;
+ break;
+ }
+ }
+
+ /*
+ * Sanity check. Some queues just cannot be recursive.
+ */
+ if (wanted_depth > 0 && (qp->flags & RECURSE) == 0)
+ msg_fatal("%s queue must not be hashed", queue_name);
+
+ /*
+ * Other per-directory initialization.
+ */
+ info = scan_dir_open(queue_name);
+ actual_depth = 0;
+
+ for (;;) {
+
+ /*
+ * If we reach the end of a subdirectory, return to its parent.
+ * Delete subdirectories that are no longer needed.
+ */
+ if ((path = scan_dir_next(info)) == 0) {
+ if (actual_depth == 0)
+ break;
+ if (actual_depth > wanted_depth)
+ postrmdir(scan_dir_path(info));
+ scan_dir_pop(info);
+ actual_depth--;
+ continue;
+ }
+
+ /*
+ * If we stumble upon a subdirectory, enter it, if it is
+ * considered safe to do so. Otherwise, try to remove the
+ * subdirectory at a later stage.
+ */
+ if (strlen(path) == 1 && (qp->flags & RECURSE) != 0) {
+ actual_depth++;
+ scan_dir_push(info, path);
+ continue;
+ }
+
+ /*
+ * From here on we need to keep track of operations that
+ * invalidate or revalidate the actual_path and path variables,
+ * otherwise we can hit the wrong files.
+ */
+ vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path);
+ if (lstat(STR(actual_path), &st) < 0)
+ continue;
+
+ /*
+ * Remove alien directories. If maildrop is compromised, then we
+ * cannot abort just because we cannot remove someone's
+ * directory.
+ */
+ if (S_ISDIR(st.st_mode)) {
+ if (rmdir(STR(actual_path)) < 0) {
+ if (errno != ENOENT)
+ msg_warn("remove subdirectory %s: %m", STR(actual_path));
+ } else {
+ if (msg_verbose)
+ msg_info("remove subdirectory %s", STR(actual_path));
+ }
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Mass deletion. We count the deletion of mail that this system
+ * has taken responsibility for. XXX This option does not use
+ * mail_queue_remove(), so that it can avoid having to first move
+ * queue files to the "right" subdirectory level.
+ */
+ if (action & ACTION_DELETE_ALL) {
+ if (postremove(STR(actual_path)) == 0)
+ if (MESSAGE_QUEUE(qp) && READY_MESSAGE(st))
+ message_deleted++;
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Remove non-file objects and old temporary files. Be careful
+ * not to delete bounce or defer logs just because they are more
+ * than a couple days old.
+ */
+ if (!S_ISREG(st.st_mode)
+ || ((action & ACTION_PURGE) != 0
+ && MESSAGE_QUEUE(qp)
+ && !READY_MESSAGE(st)
+ && time((time_t *) 0) > st.st_mtime + MAX_TEMP_AGE)) {
+ (void) postremove(STR(actual_path));
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Fix queueid#FIX names that were left from a previous pass over
+ * the queue where message queue file names were matched to their
+ * inode number. We strip the suffix and move the file into the
+ * proper subdirectory level. Make sure that the name minus
+ * suffix is well formed and that the name matches the file inode
+ * number.
+ */
+ if ((action & ACTION_STRUCT)
+ && strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) {
+ path[strlen(path) - SUFFIX_LEN] = 0; /* XXX */
+ if (!mail_queue_id_ok(path)) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if (MESSAGE_QUEUE(qp)) {
+ MQID_GET_INUM(path, inum, long_name, error);
+ if (error) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if (inum != (unsigned long) st.st_ino) {
+ msg_warn("name/inode mismatch: %s", STR(actual_path));
+ continue;
+ }
+ }
+ (void) mail_queue_path(wanted_path, queue_name, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) < 0) {
+ /* No further work on this object is possible. */
+ continue;
+ } else {
+ if (MESSAGE_QUEUE(qp))
+ inode_fixed++;
+ vstring_strcpy(actual_path, STR(wanted_path));
+ /* At this point, path and actual_path are revalidated. */
+ }
+ }
+
+ /*
+ * Skip over files with illegal names. The library routines
+ * refuse to operate on them.
+ */
+ if (!mail_queue_id_ok(path)) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+
+ /*
+ * See if the file name matches the file inode number. Skip meta
+ * file directories. This option requires that meta files be put
+ * into their proper place before queue files, so that we can
+ * rename queue files and meta files at the same time. Mis-named
+ * files are renamed to newqueueid#FIX on the first pass, and
+ * upon the second pass the #FIX is stripped off the name. Of
+ * course we have to be prepared that the program is interrupted
+ * before it completes, so any left-over newqueueid#FIX files
+ * have to be handled properly. XXX This option cannot use
+ * mail_queue_rename(), because the queue file name violates
+ * normal queue file syntax.
+ *
+ * By design there is no need to "fix" non-repeating names. What
+ * follows is applicable only when reverting from long names to
+ * short names, or when migrating short names from one queue to
+ * another.
+ */
+ if ((action & ACTION_STRUCT) != 0 && MESSAGE_QUEUE(qp)) {
+ MQID_GET_INUM(path, inum, long_name, error);
+ if (error) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if ((long_name != 0 && var_long_queue_ids == 0)
+ || (inum != (unsigned long) st.st_ino
+ && (long_name == 0 || (action & ACTION_STRUCT_RED)))) {
+ inode_mismatch++; /* before we fix */
+ action &= ~ACTIONS_BY_WILDCARD;
+ fix_queue_id(STR(actual_path), queue_name, path, &st);
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+ }
+
+ /*
+ * Mass requeuing. The pickup daemon will copy requeued mail to a
+ * new queue file, so that address rewriting is applied again.
+ * XXX This option does not use mail_queue_rename(), so that it
+ * can avoid having to first move queue files to the "right"
+ * subdirectory level. Like the requeue_one() routine, this code
+ * does not touch logfiles.
+ */
+ if ((action & ACTION_REQUEUE_ALL)
+ && MESSAGE_QUEUE(qp)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_MAILDROP, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_requeued++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * Many of the following actions may move queue files. To avoid
+ * loss of email due to file name collisions. we should do such
+ * actions only when the queue file names are known to match
+ * their inode number. Even with non-repeating queue IDs a name
+ * collision may happen when different queues are merged.
+ */
+
+ /*
+ * Mass expiration. We count the expiration of mail that this
+ * system has taken responsibility for.
+ */
+ if ((action & (ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL))
+ && MESSAGE_QUEUE(qp) && READY_MESSAGE(st)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0
+ && postexpire(STR(actual_path)) == 0)
+ message_expired++;
+
+ /*
+ * Mass renaming to the "on hold" queue. XXX This option does not
+ * use mail_queue_rename(), so that it can avoid having to first
+ * move queue files to the "right" subdirectory level. Like the
+ * hold_one() routine, this code does not touch logfiles, and
+ * must not touch files in the maildrop queue, because maildrop
+ * files contain data that has not yet been sanitized and
+ * therefore must not be mixed with already sanitized mail.
+ */
+ if ((action & ACTION_HOLD_ALL)
+ && MESSAGE_QUEUE(qp)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0
+ && strcmp(queue_name, MAIL_QUEUE_HOLD) != 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_HOLD, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_held++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * Mass release from the "on hold" queue. XXX This option does
+ * not use mail_queue_rename(), so that it can avoid having to
+ * first move queue files to the "right" subdirectory level. Like
+ * the release_one() routine, this code must not touch logfiles.
+ */
+ if ((action & (ACTION_RELEASE_ALL | ACTION_EXP_REL_ALL))
+ && strcmp(queue_name, MAIL_QUEUE_HOLD) == 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_DEFERRED, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_released++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * See if this file sits in the right place in the file system
+ * hierarchy. Its place may be wrong after a change to the
+ * hash_queue_{names,depth} parameter settings. This requires
+ * that the bounce/defer logfiles be at the right subdirectory
+ * level first, otherwise we would fail to properly rename
+ * bounce/defer logfiles.
+ */
+ if (action & ACTION_STRUCT) {
+ (void) mail_queue_path(wanted_path, queue_name, path);
+ if (strcmp(STR(actual_path), STR(wanted_path)) != 0) {
+ position_mismatch++; /* before we fix */
+ (void) postrename(STR(actual_path), STR(wanted_path));
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+ }
+ }
+ scan_dir_close(info);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(wanted_path);
+ vstring_free(actual_path);
+ argv_free(hash_queue_names);
+}
+
+/* interrupted - signal handler */
+
+static void interrupted(int sig)
+{
+
+ /*
+ * This commands requires root privileges. We therefore do not worry
+ * about hostile signals, and report problems via msg_warn().
+ *
+ * We use the in-kernel SIGINT handler address as an atomic variable to
+ * prevent nested interrupted() calls. For this reason, main() must
+ * configure interrupted() as SIGINT handler before other signal handlers
+ * are allowed to invoke interrupted(). See also similar code in
+ * postdrop.
+ */
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ (void) signal(SIGQUIT, SIG_IGN);
+ (void) signal(SIGTERM, SIG_IGN);
+ (void) signal(SIGHUP, SIG_IGN);
+ if (inode_mismatch > 0 || inode_fixed > 0 || position_mismatch > 0)
+ msg_warn("OPERATION INCOMPLETE -- RERUN COMMAND TO FIX THE QUEUE FIRST");
+ if (sig)
+ _exit(sig);
+ }
+}
+
+/* fatal_warning - print warning if queue fix is incomplete */
+
+static void fatal_warning(void)
+{
+ interrupted(0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int fd;
+ struct stat st;
+ char *slash;
+ int action = 0;
+ const char **queues;
+ int c;
+ ARGV *import_env;
+ int saved_optind;
+
+ /*
+ * Defaults. The structural checks must fix the directory levels of "log
+ * file" directories (bounce, defer) before doing structural checks on
+ * the "message file" directories, so that we can find the logfiles in
+ * the right place when message files need to be renamed to match their
+ * inode number.
+ */
+ static char *default_queues[] = {
+ MAIL_QUEUE_DEFER, /* before message directories */
+ MAIL_QUEUE_BOUNCE, /* before message directories */
+ MAIL_QUEUE_MAILDROP,
+ MAIL_QUEUE_INCOMING,
+ MAIL_QUEUE_ACTIVE,
+ MAIL_QUEUE_DEFERRED,
+ MAIL_QUEUE_HOLD,
+ MAIL_QUEUE_FLUSH,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process this environment option as early as we can, to aid debugging.
+ */
+ if (safe_getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Initialize logging.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(mail_task(argv[0]),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Disallow unsafe practices, and refuse to run set-uid (or as the child
+ * of a set-uid process). Whenever a privileged wrapper program is
+ * needed, it must properly sanitize the real/effective/saved UID/GID,
+ * the secondary groups, the process environment, and so on. Otherwise,
+ * accidents can happen. If not with Postfix, then with other software.
+ */
+ if (unsafe() != 0)
+ msg_fatal("this postfix command must not run as a set-uid process");
+ if (getuid())
+ msg_fatal("use of this command is reserved for the superuser");
+
+ /*
+ * Parse JCL.
+ *
+ * First, find out what kind of actions are requested, without executing
+ * them. Later, we execute actions in mostly user-specified order.
+ */
+#define GETOPT_LIST "c:d:e:f:h:H:pr:sSv"
+
+ saved_optind = optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ switch (c) {
+ default:
+ msg_fatal("usage: %s "
+ "[-c config_dir] "
+ "[-d queue_id (delete)] "
+ "[-e queue_id (expire)] "
+ "[-f queue_id (expire and/or un-hold)] "
+ "[-h queue_id (hold)] [-H queue_id (un-hold)] "
+ "[-p (purge temporary files)] [-r queue_id (requeue)] "
+ "[-s (structure fix)] [-S (redundant structure fix)]"
+ "[-v (verbose)] [queue...]", argv[0]);
+ case 'c':
+ if (*optarg != '/')
+ msg_fatal("-c requires absolute pathname");
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("setenv: %m");
+ break;
+ case 'd':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_DELETE_ALL : ACTION_DELETE_ONE);
+ break;
+ case 'e':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXPIRE_ALL : ACTION_EXPIRE_ONE);
+ break;
+ case 'f':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXP_REL_ALL : ACTION_EXP_REL_ONE);
+ break;
+ case 'h':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_HOLD_ALL : ACTION_HOLD_ONE);
+ break;
+ case 'H':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_RELEASE_ALL : ACTION_RELEASE_ONE);
+ break;
+ case 'p':
+ action |= ACTION_PURGE;
+ break;
+ case 'r':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_REQUEUE_ALL : ACTION_REQUEUE_ONE);
+ break;
+ case 'S':
+ action |= ACTION_STRUCT_RED;
+ /* FALLTHROUGH */
+ case 's':
+ action |= ACTION_STRUCT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+
+ /*
+ * Read the global configuration file and extract configuration
+ * information. The -c command option can override the default
+ * configuration directory location.
+ */
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task(argv[0]),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ /*
+ * All file/directory updates must be done as the mail system owner. This
+ * is because Postfix daemons manipulate the queue with those same
+ * privileges, so directories must be created with the right ownership.
+ *
+ * Running as a non-root user is also required for security reasons. When
+ * the Postfix queue hierarchy is compromised, an attacker could trick us
+ * into entering other file hierarchies and afflicting damage. Running as
+ * a non-root user limits the damage to the already compromised mail
+ * owner.
+ */
+ set_ugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * Be sure to log a warning if we do not finish structural repair. Maybe
+ * we should have an fsck-style "clean" flag so Postfix will not start
+ * with a broken queue.
+ *
+ * Set up signal handlers after permanently dropping super-user privileges,
+ * so that signal handlers will always run with the correct privileges.
+ *
+ * XXX Don't enable SIGHUP or SIGTERM if it was ignored by the parent.
+ *
+ * interrupted() uses the in-kernel SIGINT handler address as an atomic
+ * variable to prevent nested interrupted() calls. For this reason, the
+ * SIGINT handler must be configured before other signal handlers are
+ * allowed to invoke interrupted(). See also similar code in postdrop.
+ */
+ signal(SIGINT, interrupted);
+ signal(SIGQUIT, interrupted);
+ if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
+ signal(SIGTERM, interrupted);
+ if (signal(SIGHUP, SIG_IGN) == SIG_DFL)
+ signal(SIGHUP, interrupted);
+ msg_cleanup(fatal_warning);
+
+ /*
+ * Execute the explicitly specified (or default) action, on the
+ * explicitly specified (or default) queues.
+ *
+ * XXX Work around gcc const brain damage.
+ *
+ * XXX The file name/inode number fix should always run over all message
+ * file directories, and should always be preceded by a subdirectory
+ * level check of the bounce and defer logfile directories.
+ */
+ if (action == 0)
+ action = ACTION_DEFAULT;
+ if (argv[optind] != 0)
+ queues = (const char **) argv + optind;
+ else
+ queues = (const char **) default_queues;
+
+ /*
+ * Basic queue maintenance, including mass name-to-inode fixing. This
+ * ensures that queue files are in the right place before any other
+ * operations are done.
+ */
+ if (action & ACTIONS_FOR_REPAIR)
+ super(queues, action & ACTIONS_FOR_REPAIR);
+
+ /*
+ * If any file names needed changing to match the message file inode
+ * number, those files were named newqeueid#FIX. We need a second pass to
+ * strip the suffix from the new queue ID, and to complete any requested
+ * operations that had to be skipped in the first pass.
+ */
+ if (inode_mismatch > 0)
+ super(queues, action & ACTIONS_FOR_REPAIR);
+
+ /*
+ * Don't do actions by queue file name if any queue files changed name
+ * because they did not match the queue file inode number. We could be
+ * acting on the wrong queue file and lose mail.
+ */
+ if ((action & ACTIONS_BY_QUEUE_ID)
+ && (inode_mismatch > 0 || inode_fixed > 0)) {
+ msg_error("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS");
+ msg_fatal("CHECK YOUR QUEUE IDS AND RE-ISSUE THE COMMAND");
+ }
+
+ /*
+ * Execute actions by queue ID and by wildcard in the user-specified
+ * order.
+ */
+ optind = saved_optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ switch (c) {
+ default:
+ msg_panic("%s: unexpected option: %c", argv[0], c);
+ case 'c':
+ case 'p':
+ case 'S':
+ case 's':
+ case 'v':
+ /* Already handled. */
+ break;
+ case 'd':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_DELETE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, delete_one, queues);
+ else
+ delete_one(queues, optarg);
+ break;
+ case 'e':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXPIRE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, expire_one, queues);
+ else
+ expire_one(queues, optarg);
+ break;
+ case 'f':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXP_REL_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, exp_rel_one, queues);
+ else
+ exp_rel_one(queues, optarg);
+ break;
+ case 'h':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_HOLD_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, hold_one, queues);
+ else
+ hold_one(queues, optarg);
+ break;
+ case 'H':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_RELEASE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, release_one, queues);
+ else
+ release_one(queues, optarg);
+ break;
+ case 'r':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_REQUEUE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, requeue_one, queues);
+ else
+ requeue_one(queues, optarg);
+ break;
+ }
+ }
+
+ /*
+ * Report.
+ */
+ if (action & (ACTION_REQUEUE_ONE | ACTION_REQUEUE_ALL))
+ msg_info("Requeued: %d message%s", message_requeued,
+ message_requeued != 1 ? "s" : "");
+ if (action & (ACTION_DELETE_ONE | ACTION_DELETE_ALL))
+ msg_info("Deleted: %d message%s", message_deleted,
+ message_deleted != 1 ? "s" : "");
+ if (action & (ACTION_EXPIRE_ONE | ACTION_EXPIRE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
+ msg_info("Force-expired: %d message%s", message_expired,
+ message_expired != 1 ? "s" : "");
+ if (action & (ACTION_HOLD_ONE | ACTION_HOLD_ALL))
+ msg_info("Placed on hold: %d message%s",
+ message_held, message_held != 1 ? "s" : "");
+ if (action & (ACTION_RELEASE_ONE | ACTION_RELEASE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
+ msg_info("Released from hold: %d message%s",
+ message_released, message_released != 1 ? "s" : "");
+ if (inode_fixed > 0)
+ msg_info("Renamed to match inode number: %d message%s", inode_fixed,
+ inode_fixed != 1 ? "s" : "");
+ if (inode_mismatch > 0 || inode_fixed > 0)
+ msg_warn("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS");
+
+ exit(0);
+}
diff --git a/src/posttls-finger/.indent.pro b/src/posttls-finger/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/posttls-finger/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/posttls-finger/Makefile.in b/src/posttls-finger/Makefile.in
new file mode 100644
index 0000000..d246303
--- /dev/null
+++ b/src/posttls-finger/Makefile.in
@@ -0,0 +1,117 @@
+SHELL = /bin/sh
+SRCS = posttls-finger.c tlsmgrmem.c
+OBJS = posttls-finger.o tlsmgrmem.o
+HDRS = tlsmgrmem.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+INC_DIR = ../../include
+PROG = posttls-finger
+LIBS = ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG)
+
+$(OBJS): ../../conf/makedefs.out $(HDRS)
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+posttls-finger: $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../bin/posttls-finger
+
+../../bin/posttls-finger: posttls-finger
+ cp $? $@
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+posttls-finger.o: ../../include/argv.h
+posttls-finger.o: ../../include/attr.h
+posttls-finger.o: ../../include/check_arg.h
+posttls-finger.o: ../../include/chroot_uid.h
+posttls-finger.o: ../../include/clean_env.h
+posttls-finger.o: ../../include/dns.h
+posttls-finger.o: ../../include/dsn.h
+posttls-finger.o: ../../include/dsn_buf.h
+posttls-finger.o: ../../include/host_port.h
+posttls-finger.o: ../../include/htable.h
+posttls-finger.o: ../../include/inet_proto.h
+posttls-finger.o: ../../include/iostuff.h
+posttls-finger.o: ../../include/known_tcp_ports.h
+posttls-finger.o: ../../include/mail_conf.h
+posttls-finger.o: ../../include/mail_params.h
+posttls-finger.o: ../../include/mail_parm_split.h
+posttls-finger.o: ../../include/mail_proto.h
+posttls-finger.o: ../../include/mail_server.h
+posttls-finger.o: ../../include/midna_domain.h
+posttls-finger.o: ../../include/msg.h
+posttls-finger.o: ../../include/msg_vstream.h
+posttls-finger.o: ../../include/myaddrinfo.h
+posttls-finger.o: ../../include/mymalloc.h
+posttls-finger.o: ../../include/name_code.h
+posttls-finger.o: ../../include/name_mask.h
+posttls-finger.o: ../../include/nvtable.h
+posttls-finger.o: ../../include/sane_connect.h
+posttls-finger.o: ../../include/smtp_stream.h
+posttls-finger.o: ../../include/sock_addr.h
+posttls-finger.o: ../../include/stringops.h
+posttls-finger.o: ../../include/sys_defs.h
+posttls-finger.o: ../../include/timed_connect.h
+posttls-finger.o: ../../include/tls.h
+posttls-finger.o: ../../include/tls_proxy.h
+posttls-finger.o: ../../include/vbuf.h
+posttls-finger.o: ../../include/vstream.h
+posttls-finger.o: ../../include/vstring.h
+posttls-finger.o: ../../include/vstring_vstream.h
+posttls-finger.o: posttls-finger.c
+posttls-finger.o: tlsmgrmem.h
+tlsmgrmem.o: ../../include/argv.h
+tlsmgrmem.o: ../../include/check_arg.h
+tlsmgrmem.o: ../../include/dict.h
+tlsmgrmem.o: ../../include/htable.h
+tlsmgrmem.o: ../../include/myflock.h
+tlsmgrmem.o: ../../include/sys_defs.h
+tlsmgrmem.o: ../../include/tls_mgr.h
+tlsmgrmem.o: ../../include/tls_scache.h
+tlsmgrmem.o: ../../include/vbuf.h
+tlsmgrmem.o: ../../include/vstream.h
+tlsmgrmem.o: ../../include/vstring.h
+tlsmgrmem.o: tlsmgrmem.c
+tlsmgrmem.o: tlsmgrmem.h
diff --git a/src/posttls-finger/posttls-finger.c b/src/posttls-finger/posttls-finger.c
new file mode 100644
index 0000000..502645c
--- /dev/null
+++ b/src/posttls-finger/posttls-finger.c
@@ -0,0 +1,2165 @@
+/*++
+/* NAME
+/* posttls-finger 1
+/* SUMMARY
+/* Probe the TLS properties of an ESMTP or LMTP server.
+/* SYNOPSIS
+/* \fBposttls-finger\fR [\fIoptions\fR] [\fBinet:\fR]\fIdomain\fR[:\fIport\fR] [\fImatch ...\fR]
+/* .br
+/* \fBposttls-finger\fR -S [\fIoptions\fR] \fBunix:\fIpathname\fR [\fImatch ...\fR]
+/* DESCRIPTION
+/* \fBposttls-finger\fR(1) connects to the specified destination
+/* and reports TLS-related information about the server. With SMTP, the
+/* destination is a domainname; with LMTP it is either a domainname
+/* prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR. If
+/* Postfix is built without TLS support, the resulting \fBposttls-finger\fR(1)
+/* program has very limited functionality, and only the \fB-a\fR, \fB-c\fR,
+/* \fB-h\fR, \fB-o\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options
+/* are available.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* For SMTP servers that don't support ESMTP, only the greeting banner
+/* and the negative EHLO response are reported. Otherwise, the reported
+/* EHLO response details further server capabilities.
+/*
+/* If TLS support is enabled when \fBposttls-finger\fR(1) is compiled, and
+/* the server supports \fBSTARTTLS\fR, a TLS handshake is attempted.
+/*
+/* If DNSSEC support is available, the connection TLS security level
+/* (\fB-l\fR option) defaults to \fBdane\fR; see TLS_README for
+/* details. Otherwise, it defaults to \fBsecure\fR. This setting
+/* determines the certificate matching policy.
+/*
+/* If TLS negotiation succeeds, the TLS protocol and cipher details are
+/* reported. The server certificate is then verified in accordance with
+/* the policy at the chosen (or default) security level. With public
+/* CA-based trust, when the \fB-L\fR option includes \fBcertmatch\fR,
+/* (true by default) name matching is performed even if the certificate
+/* chain is not trusted. This logs the names found in the remote SMTP
+/* server certificate and which if any would match, were the certificate
+/* chain trusted.
+/*
+/* Note: \fBposttls-finger\fR(1) does not perform any table lookups, so
+/* the TLS policy table and obsolete per-site tables are not consulted.
+/* It does not communicate with the \fBtlsmgr\fR(8) daemon (or any other
+/* Postfix daemons); its TLS session cache is held in private memory, and
+/* disappears when the process exits.
+/*
+/* With the \fB-r \fIdelay\fR option, if the server assigns a TLS
+/* session id, the TLS session is cached. The connection is then closed
+/* and re-opened after the specified delay, and \fBposttls-finger\fR(1)
+/* then reports whether the cached TLS session was re-used.
+/*
+/* When the destination is a load balancer, it may be distributing
+/* load between multiple server caches. Typically, each server returns
+/* its unique name in its EHLO response. If, upon reconnecting with
+/* \fB-r\fR, a new server name is detected, another session is cached
+/* for the new server, and the reconnect is repeated up to a maximum
+/* number of times (default 5) that can be specified via the \fB-m\fR
+/* option.
+/*
+/* The choice of SMTP or LMTP (\fB-S\fR option) determines the syntax of
+/* the destination argument. With SMTP, one can specify a service on a
+/* non-default port as \fIhost\fR:\fIservice\fR, and disable MX (mail
+/* exchanger) DNS lookups with [\fIhost\fR] or [\fIhost\fR]:\fIport\fR.
+/* The [] form is required when you specify an IP address instead of a
+/* hostname. An IPv6 address takes the form [\fBipv6:\fIaddress\fR].
+/* The default port for SMTP is taken from the \fBsmtp/tcp\fR entry in
+/* /etc/services, defaulting to 25 if the entry is not found.
+/*
+/* With LMTP, specify \fBunix:\fIpathname\fR to connect to a local server
+/* listening on a unix-domain socket bound to the specified pathname;
+/* otherwise, specify an optional \fBinet:\fR prefix followed by a
+/* \fIdomain\fR and an optional port, with the same syntax as for
+/* SMTP. The default TCP port for LMTP is 24.
+/*
+/* Arguments:
+/* .IP "\fB-a\fR \fIfamily\fR (default: \fBany\fR)"
+/* Address family preference: \fBipv4\fR, \fBipv6\fR or \fBany\fR. When
+/* using \fBany\fR, \fBposttls-finger\fR(1) will randomly select one of
+/* the two as the more preferred, and exhaust all MX preferences for the
+/* first address family before trying any addresses for the other.
+/* .IP "\fB-A\fR \fItrust-anchor.pem\fR (default: none)"
+/* A list of PEM trust-anchor files that overrides CAfile and CApath
+/* trust chain verification. Specify the option multiple times to
+/* specify multiple files. See the main.cf documentation for
+/* smtp_tls_trust_anchor_file for details.
+/* .IP "\fB-c\fR"
+/* Disable SMTP chat logging; only TLS-related information is logged.
+/* .IP "\fB-C\fR"
+/* Print the remote SMTP server certificate trust chain in PEM format.
+/* The issuer DN, subject DN, certificate and public key fingerprints
+/* (see \fB-d \fImdalg\fR option below) are printed above each PEM
+/* certificate block. If you specify \fB-F \fICAfile\fR or
+/* \fB-P \fICApath\fR, the OpenSSL library may augment the chain with
+/* missing issuer certificates. To see the actual chain sent by the
+/* remote SMTP server leave \fICAfile\fR and \fICApath\fR unset.
+/* .IP "\fB-d \fImdalg\fR (default: \fB$smtp_tls_fingerprint_digest\fR)"
+/* The message digest algorithm to use for reporting remote SMTP server
+/* fingerprints and matching against user provided certificate
+/* fingerprints (with DANE TLSA records the algorithm is specified
+/* in the DNS). In Postfix versions prior to 3.6, the default value
+/* was "md5".
+/* .IP "\fB-f\fR"
+/* Lookup the associated DANE TLSA RRset even when a hostname is not an
+/* alias and its address records lie in an unsigned zone. See
+/* smtp_tls_force_insecure_host_tlsa_lookup for details.
+/* .IP "\fB-F \fICAfile.pem\fR (default: none)"
+/* The PEM formatted CAfile for remote SMTP server certificate
+/* verification. By default no CAfile is used and no public CAs
+/* are trusted.
+/* .IP "\fB-g \fIgrade\fR (default: medium)"
+/* The minimum TLS cipher grade used by \fBposttls-finger\fR(1).
+/* See smtp_tls_mandatory_ciphers for details.
+/* .IP "\fB-h \fIhost_lookup\fR (default: \fBdns\fR)"
+/* The hostname lookup methods used for the connection. See the
+/* documentation of smtp_host_lookup for syntax and semantics.
+/* .IP "\fB-H \fIchainfiles\fR (default: \fInone\fR)\fR"
+/* List of files with a sequence PEM-encoded TLS client certificate
+/* chains. The list can be built-up incrementally, by specifying
+/* the option multiple times, or all at once via a comma or
+/* whitespace separated list of filenames. Each chain starts with
+/* a private key, which is followed immediately by the
+/* corresponding certificate, and optionally by additional issuer
+/* certificates. Each new key begins a new chain for the
+/* corresponding algorithm. This option is mutually exclusive with
+/* the below \fB-k\fR and \fB-K\fR options.
+/* .IP "\fB-k \fIcertfile\fR (default: \fIkeyfile\fR)\fR"
+/* File with PEM-encoded TLS client certificate chain. This
+/* defaults to \fIkeyfile\fR if one is specified.
+/* .IP "\fB-K \fIkeyfile\fR (default: \fIcertfile\fR)"
+/* File with PEM-encoded TLS client private key.
+/* This defaults to \fIcertfile\fR if one is specified.
+/* .IP "\fB-l \fIlevel\fR (default: \fBdane\fR or \fBsecure\fR)"
+/* The security level for the connection, default \fBdane\fR or
+/* \fBsecure\fR depending on whether DNSSEC is available. For syntax
+/* and semantics, see the documentation of smtp_tls_security_level.
+/* When \fBdane\fR or \fBdane-only\fR is supported and selected, if no
+/* TLSA records are found, or all the records found are unusable, the
+/* \fIsecure\fR level will be used instead. The \fBfingerprint\fR
+/* security level allows you to test certificate or public-key
+/* fingerprint matches before you deploy them in the policy table.
+/* .IP
+/* Note, since \fBposttls-finger\fR(1) does not actually deliver any email,
+/* the \fBnone\fR, \fBmay\fR and \fBencrypt\fR security levels are not
+/* very useful. Since \fBmay\fR and \fBencrypt\fR don't require peer
+/* certificates, they will often negotiate anonymous TLS ciphersuites,
+/* so you won't learn much about the remote SMTP server's certificates
+/* at these levels if it also supports anonymous TLS (though you may
+/* learn that the server supports anonymous TLS).
+/* .IP "\fB-L \fIlogopts\fR (default: \fBroutine,certmatch\fR)"
+/* Fine-grained TLS logging options. To tune the TLS features logged
+/* during the TLS handshake, specify one or more of:
+/* .RS
+/* .IP "\fB0, none\fR"
+/* These yield no TLS logging; you'll generally want more, but this
+/* is handy if you just want the trust chain:
+/* .RS
+/* .ad
+/* .nf
+/* $ posttls-finger -cC -L none destination
+/* .fi
+/* .RE
+/* .IP "\fB1, routine, summary\fR"
+/* These synonymous values yield a normal one-line summary of the TLS
+/* connection.
+/* .IP "\fB2, debug\fR"
+/* These synonymous values combine routine, ssl-debug, cache and verbose.
+/* .IP "\fB3, ssl-expert\fR"
+/* These synonymous values combine debug with ssl-handshake-packet-dump.
+/* For experts only.
+/* .IP "\fB4, ssl-developer\fR"
+/* These synonymous values combine ssl-expert with ssl-session-packet-dump.
+/* For experts only, and in most cases, use wireshark instead.
+/* .IP "\fBssl-debug\fR"
+/* Turn on OpenSSL logging of the progress of the SSL handshake.
+/* .IP "\fBssl-handshake-packet-dump\fR"
+/* Log hexadecimal packet dumps of the SSL handshake; for experts only.
+/* .IP "\fBssl-session-packet-dump\fR"
+/* Log hexadecimal packet dumps of the entire SSL session; only useful
+/* to those who can debug SSL protocol problems from hex dumps.
+/* .IP "\fBuntrusted\fR"
+/* Logs trust chain verification problems. This is turned on
+/* automatically at security levels that use peer names signed
+/* by Certification Authorities to validate certificates. So while
+/* this setting is recognized, you should never need to set it
+/* explicitly.
+/* .IP "\fBpeercert\fR"
+/* This logs a one line summary of the remote SMTP server certificate
+/* subject, issuer, and fingerprints.
+/* .IP "\fBcertmatch\fR"
+/* This logs remote SMTP server certificate matching, showing the CN
+/* and each subjectAltName and which name matched. With DANE, logs
+/* matching of TLSA record trust-anchor and end-entity certificates.
+/* .IP "\fBcache\fR"
+/* This logs session cache operations, showing whether session caching
+/* is effective with the remote SMTP server. Automatically used when
+/* reconnecting with the \fB-r\fR option; rarely needs to be set
+/* explicitly.
+/* .IP "\fBverbose\fR"
+/* Enables verbose logging in the Postfix TLS driver; includes all of
+/* peercert..cache and more.
+/* .RE
+/* .IP
+/* The default is \fBroutine,certmatch\fR. After a reconnect,
+/* \fBpeercert\fR, \fBcertmatch\fR and \fBverbose\fR are automatically
+/* disabled while \fBcache\fR and \fBsummary\fR are enabled.
+/* .IP "\fB-m \fIcount\fR (default: \fB5\fR)"
+/* When the \fB-r \fIdelay\fR option is specified, the \fB-m\fR option
+/* determines the maximum number of reconnect attempts to use with
+/* a server behind a load balancer, to see whether connection caching
+/* is likely to be effective for this destination. Some MTAs
+/* don't expose the underlying server identity in their EHLO
+/* response; with these servers there will never be more than
+/* 1 reconnection attempt.
+/* .IP "\fB-M \fIinsecure_mx_policy\fR (default: \fBdane\fR)"
+/* The TLS policy for MX hosts with "secure" TLSA records when the
+/* nexthop destination security level is \fBdane\fR, but the MX
+/* record was found via an "insecure" MX lookup. See the main.cf
+/* documentation for smtp_tls_dane_insecure_mx_policy for details.
+/* .IP "\fB-o \fIname=value\fR"
+/* Specify zero or more times to override the value of the main.cf
+/* parameter \fIname\fR with \fIvalue\fR. Possible use-cases include
+/* overriding the values of TLS library parameters, or "myhostname" to
+/* configure the SMTP EHLO name sent to the remote server.
+/* .IP "\fB-p \fIprotocols\fR (default: >=TLSv1)"
+/* TLS protocols that \fBposttls-finger\fR(1) will exclude or include. See
+/* smtp_tls_mandatory_protocols for details.
+/* .IP "\fB-P \fICApath/\fR (default: none)"
+/* The OpenSSL CApath/ directory (indexed via c_rehash(1)) for remote
+/* SMTP server certificate verification. By default no CApath is used
+/* and no public CAs are trusted.
+/* .IP "\fB-r \fIdelay\fR"
+/* With a cacheable TLS session, disconnect and reconnect after \fIdelay\fR
+/* seconds. Report whether the session is re-used. Retry if a new server
+/* is encountered, up to 5 times or as specified with the \fB-m\fR option.
+/* By default reconnection is disabled, specify a positive delay to
+/* enable this behavior.
+/* .IP "\fB-s \fIservername\fR"
+/* The server name to send with the TLS Server Name Indication (SNI)
+/* extension. When the server has DANE TLSA records, this parameter
+/* is ignored and the TLSA base domain is used instead. Otherwise, SNI is
+/* not used by default, but can be enabled by specifying the desired value
+/* with this option.
+/* .IP "\fB-S\fR"
+/* Disable SMTP; that is, connect to an LMTP server. The default port for
+/* LMTP over TCP is 24. Alternative ports can specified by appending
+/* "\fI:servicename\fR" or ":\fIportnumber\fR" to the destination
+/* argument.
+/* .IP "\fB-t \fItimeout\fR (default: \fB30\fR)"
+/* The TCP connection timeout to use. This is also the timeout for
+/* reading the remote server's 220 banner.
+/* .IP "\fB-T \fItimeout\fR (default: \fB30\fR)"
+/* The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT.
+/* .IP "\fB-v\fR"
+/* Enable verbose Postfix logging. Specify more than once to increase
+/* the level of verbose logging.
+/* .IP "\fB-w\fR"
+/* Enable outgoing TLS wrapper mode, or SUBMISSIONS/SMTPS support. This
+/* is typically provided on port 465 by servers that are compatible with
+/* the SMTP-in-SSL protocol, rather than the STARTTLS protocol.
+/* The destination \fIdomain\fR:\fIport\fR must of course provide such
+/* a service.
+/* .IP "\fB-X\fR"
+/* Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode,
+/* for program development only.
+/* .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]"
+/* Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default
+/* port is \fBsmtp\fR (or 24 with LMTP). With SMTP an MX lookup is
+/* performed to resolve the domain to a host, unless the domain is
+/* enclosed in \fB[]\fR. If you want to connect to a specific MX host,
+/* for instance \fImx1.example.com\fR, specify [\fImx1.example.com\fR]
+/* as the destination and \fIexample.com\fR as a \fBmatch\fR argument.
+/* When using DNS, the destination domain is assumed fully qualified
+/* and no default domain or search suffixes are applied; you must use
+/* fully-qualified names or also enable \fBnative\fR host lookups
+/* (these don't support \fBdane\fR or \fBdane-only\fR as no DNSSEC
+/* validation information is available via \fBnative\fR lookups).
+/* .IP "\fBunix:\fIpathname\fR"
+/* Connect to the UNIX-domain socket at \fIpathname\fR. LMTP only.
+/* .IP "\fBmatch ...\fR"
+/* With no match arguments specified, certificate peername matching uses
+/* the compiled-in default strategies for each security level. If you
+/* specify one or more arguments, these will be used as the list of
+/* certificate or public-key digests to match for the \fBfingerprint\fR
+/* level, or as the list of DNS names to match in the certificate at the
+/* \fBverify\fR and \fBsecure\fR levels. If the security level is
+/* \fBdane\fR, or \fBdane-only\fR the match names are ignored, and
+/* \fBhostname, nexthop\fR strategies are used.
+/* .ad
+/* .fi
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Read configuration parameters from a non-default location.
+/* .IP \fBMAIL_VERBOSE\fR
+/* Same as \fB-v\fR option.
+/* SEE ALSO
+/* smtp-source(1), SMTP/LMTP message source
+/* smtp-sink(1), SMTP/LMTP message dump
+/*
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+/* html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* TLS_README, Postfix STARTTLS howto
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <chroot_uid.h>
+#include <host_port.h>
+#include <inet_proto.h>
+#include <iostuff.h>
+#include <timed_connect.h>
+#include <sane_connect.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <midna_domain.h>
+#include <clean_env.h>
+#include <known_tcp_ports.h>
+
+#define STR(x) vstring_str(x)
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <smtp_stream.h>
+#include <dsn_buf.h>
+#include <mail_parm_split.h>
+#include <mail_proto.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+ /*
+ * master library
+ */
+#include <mail_server.h>
+
+ /*
+ * TLS Library
+ */
+#define TLS_INTERNAL
+#include <tls.h>
+
+#ifdef USE_TLS
+#include <tls_proxy.h>
+#include <openssl/engine.h>
+#endif
+
+ /*
+ * Application specific
+ */
+#include "tlsmgrmem.h"
+
+static int conn_tmout = 30;
+static int smtp_tmout = 30;
+
+#define HOST_FLAG_DNS (1<<0)
+#define HOST_FLAG_NATIVE (1<<1)
+
+#define MISC_FLAG_PREF_IPV6 (1<<0)
+#define MISC_FLAG_PREF_IPV4 (1<<1)
+
+static const NAME_MASK lookup_masks[] = {
+ "dns", HOST_FLAG_DNS,
+ "native", HOST_FLAG_NATIVE,
+ 0,
+};
+
+static const NAME_CODE addr_pref_map[] = {
+ INET_PROTO_NAME_IPV6, MISC_FLAG_PREF_IPV6,
+ INET_PROTO_NAME_IPV4, MISC_FLAG_PREF_IPV4,
+ INET_PROTO_NAME_ANY, 0,
+ 0, -1,
+};
+
+typedef struct OPTIONS {
+ char *logopts;
+ char *level;
+ ARGV *tas;
+ char *host_lookup;
+ char *addr_pref;
+} OPTIONS;
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same SMTP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct STATE {
+ int smtp; /* SMTP or LMTP? */
+ int host_lookup; /* dns|native|dns,native */
+ int addr_pref; /* v4, v6, both */
+ int log_mask; /* via tls_log_mask() */
+ int reconnect; /* -r option */
+ int max_reconnect; /* -m option */
+ int force_tlsa; /* -f option */
+ unsigned port; /* TCP port */
+ char *dest; /* Full destination spec */
+ char *paddr; /* XXX printable addr for proxy */
+ char *addrport; /* [addr]:port */
+ char *namaddrport; /* name[addr]:port */
+ char *nexthop; /* Nexthop domain for verification */
+ char *hostname; /* Hostname for verification */
+ DNS_RR *addr; /* IPv[46] Address to (re)connect to */
+ DNS_RR *mx; /* MX RRset qname, rname, valid */
+ int pass; /* Pass number, 2 for reconnect */
+ int nochat; /* disable chat logging */
+ char *helo; /* Server name from EHLO reply */
+ DSN_BUF *why; /* SMTP-style error message */
+ VSTRING *buffer; /* Response buffer */
+ VSTREAM *stream; /* Open connection */
+ int level; /* TLS security level */
+ int wrapper_mode; /* SMTPS support */
+#ifdef USE_TLS
+ char *mdalg; /* fingerprint digest algorithm */
+ char *CAfile; /* Trusted public CAs */
+ char *CApath; /* Trusted public CAs */
+ char *chains; /* TLS client certificate chain files */
+ char *certfile; /* TLS client certificate file */
+ char *keyfile; /* TLS client key file */
+ char *sni; /* Server SNI name */
+ ARGV *match; /* match arguments */
+ int print_trust; /* -C option */
+ BIO *tls_bio; /* BIO wrapper for stdout */
+ TLS_APPL_STATE *tls_ctx; /* Application TLS context */
+ TLS_SESS_STATE *tls_context; /* Session TLS context */
+ TLS_DANE *dane; /* DANE TLSA validation structure */
+ TLS_DANE *ddane; /* DANE TLSA from DNS */
+ char *grade; /* Minimum cipher grade */
+ char *protocols; /* Protocol inclusion/exclusion */
+ int mxinsec_level; /* DANE for insecure MX RRs? */
+ int tlsproxy_mode;
+#endif
+ OPTIONS options; /* JCL */
+} STATE;
+
+static DNS_RR *host_addr(STATE *, const char *);
+
+#define HNAME(addr) (addr->qname)
+
+ /*
+ * Structure with broken-up SMTP server response.
+ */
+typedef struct { /* server response */
+ int code; /* status */
+ char *str; /* text */
+ VSTRING *buf; /* origin of text */
+} RESPONSE;
+
+
+/* command - send an SMTP command */
+
+static void PRINTFLIKE(3, 4) command(STATE *state, int verbose, char *fmt,...)
+{
+ VSTREAM *stream = state->stream;
+ VSTRING *buf;
+ va_list ap;
+ char *line;
+
+ buf = vstring_alloc(100);
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ line = vstring_str(buf);
+
+ while (line && *line) {
+ char *nextline = strchr(line, '\n');
+
+ if (nextline)
+ *nextline++ = '\0';
+ if (verbose && !state->nochat)
+ msg_info("> %s", line);
+ smtp_printf(stream, "%s", line);
+ line = nextline;
+ }
+
+ vstring_free(buf);
+}
+
+/* response - read and process SMTP server response */
+
+static RESPONSE *response(STATE *state, int verbose)
+{
+ VSTREAM *stream = state->stream;
+ VSTRING *buf = state->buffer;
+ static RESPONSE rdata;
+ int more;
+ char *cp;
+
+ /*
+ * Initialize the response data buffer. smtp_get() defends against a
+ * denial of service attack by limiting the amount of single-line text,
+ * and the loop below limits the amount of multi-line text that we are
+ * willing to store.
+ */
+ if (rdata.buf == 0)
+ rdata.buf = vstring_alloc(100);
+
+ /*
+ * Censor out non-printable characters in server responses. Concatenate
+ * multi-line server responses. Separate the status code from the text.
+ * Leave further parsing up to the application.
+ */
+#define BUF ((char *) vstring_str(buf))
+ VSTRING_RESET(rdata.buf);
+ for (;;) {
+ smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
+ for (cp = BUF; *cp != 0; cp++)
+ if (!ISPRINT(*cp) && !ISSPACE(*cp))
+ *cp = '?';
+ cp = BUF;
+ if (verbose && !state->nochat)
+ msg_info("< %s", cp);
+ while (ISDIGIT(*cp))
+ cp++;
+ rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
+ if ((more = (*cp == '-')) != 0)
+ cp++;
+ while (ISSPACE(*cp))
+ cp++;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ vstring_strcat(rdata.buf, cp);
+ if (more == 0)
+ break;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ VSTRING_ADDCH(rdata.buf, '\n');
+ }
+ VSTRING_TERMINATE(rdata.buf);
+ rdata.str = vstring_str(rdata.buf);
+ return (&rdata);
+}
+
+/* exception_text - translate exceptions from the smtp_stream module */
+
+static char *exception_text(int except)
+{
+ switch (except) {
+ case SMTP_ERR_EOF:
+ return ("lost connection");
+ case SMTP_ERR_TIME:
+ return ("timeout");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+}
+
+/* greeting - read server's 220 greeting */
+
+static int greeting(STATE *state)
+{
+ VSTREAM *stream = state->stream;
+ int except;
+ RESPONSE *resp;
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(stream, conn_tmout, /* deadline */ 1, /* minrate */ 0);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_info("%s while reading server greeting", exception_text(except));
+ return (1);
+ }
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ if (((resp = response(state, 1))->code / 100) != 2) {
+ msg_info("SMTP service not available: %d %s", resp->code, resp->str);
+ return (1);
+ }
+ return (0);
+}
+
+/* ehlo - send EHLO/LHLO */
+
+static RESPONSE *ehlo(STATE *state)
+{
+ int except;
+ int verbose;
+ volatile char *ehlo = state->smtp ? "EHLO" : "LHLO";
+ VSTREAM *stream = state->stream;
+ RESPONSE *resp;
+
+#ifdef USE_TLS
+ verbose = (state->pass == 1 && state->nochat == 0);
+#else
+ verbose = 1;
+#endif
+
+ /*
+ * Send the standard greeting with our hostname
+ */
+ smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_info("%s while sending %s", exception_text(except), ehlo);
+ return (0);
+ }
+ command(state, verbose, "%s %s", ehlo, var_myhostname);
+
+ resp = response(state, verbose);
+ if (resp->code / 100 != 2) {
+ msg_info("%s rejected: %d %s", ehlo, resp->code, resp->str);
+ return (0);
+ }
+ return resp;
+}
+
+#ifdef USE_TLS
+
+static void print_stack(STATE *state, x509_stack_t *sk, int trustout)
+{
+ int i;
+
+ for (i = 0; i < sk_X509_num(sk); i++) {
+ X509 *cert = sk_X509_value(sk, i);
+ char buf[CCERT_BUFSIZ];
+ X509_NAME *xn;
+ char *digest;
+
+ if ((xn = X509_get_subject_name(cert)) != 0) {
+ X509_NAME_oneline(xn, buf, sizeof buf);
+ BIO_printf(state->tls_bio, "%2d subject: %s\n", i, buf);
+ }
+ if ((xn = X509_get_issuer_name(cert)) != 0) {
+ X509_NAME_oneline(xn, buf, sizeof buf);
+ BIO_printf(state->tls_bio, " issuer: %s\n", buf);
+ }
+ digest = tls_cert_fprint(cert, state->mdalg);
+ BIO_printf(state->tls_bio, " cert digest=%s\n", digest);
+ myfree(digest);
+
+ digest = tls_pkey_fprint(cert, state->mdalg);
+ BIO_printf(state->tls_bio, " pkey digest=%s\n", digest);
+ myfree(digest);
+
+ if (trustout)
+ PEM_write_bio_X509_AUX(state->tls_bio, cert);
+ else
+ PEM_write_bio_X509(state->tls_bio, cert);
+ }
+}
+
+static void print_trust_info(STATE *state)
+{
+ x509_stack_t *sk = SSL_get_peer_cert_chain(state->tls_context->con);
+
+ if (sk != 0) {
+ BIO_printf(state->tls_bio, "\n---\nCertificate chain\n");
+ print_stack(state, sk, 0);
+ }
+#ifdef dane_verify_debug
+ /* print internally constructed untrusted chain */
+ if ((sk = state->tls_context->untrusted) != 0) {
+ BIO_printf(state->tls_bio, "\n---\nUntrusted chain\n");
+ print_stack(state, sk, 0);
+ }
+ /* print associated root CA */
+ if ((sk = state->tls_context->trusted) != 0) {
+ BIO_printf(state->tls_bio, "\n---\nTrusted chain\n");
+ print_stack(state, sk, 1);
+ }
+#endif
+}
+
+/* starttls - SMTP STARTTLS handshake */
+
+static int starttls(STATE *state)
+{
+ VSTRING *cipher_exclusions;
+ int except;
+ RESPONSE *resp;
+ VSTREAM *stream = state->stream;
+ TLS_CLIENT_START_PROPS start_props;
+ TLS_CLIENT_INIT_PROPS init_props;
+ VSTREAM *tlsproxy;
+ VSTRING *port_buf;
+ int cwd_fd;
+
+ if (state->wrapper_mode == 0) {
+ /* SMTP stream with deadline timeouts */
+ smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_fatal("%s while sending STARTTLS", exception_text(except));
+ return (1);
+ }
+ command(state, state->pass == 1, "STARTTLS");
+
+ resp = response(state, state->pass == 1);
+ if (resp->code / 100 != 2) {
+ msg_info("STARTTLS rejected: %d %s", resp->code, resp->str);
+ return (1);
+ }
+
+ /*
+ * Discard any plain-text data that may be piggybacked after the
+ * server's 220 STARTTLS reply. Should we abort the session instead?
+ */
+ vstream_fpurge(stream, VSTREAM_PURGE_READ);
+ }
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ cipher_exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_EXCL_CIPH);
+ if (TLS_REQUIRED(state->level))
+ ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_MAND_EXCL);
+
+ /*
+ * If we're authenticating suppress anonymous ciphersuites, otherwise at
+ * least encrypt, not much point in doing neither.
+ */
+ if (TLS_MUST_MATCH(state->level))
+ ADD_EXCLUDE(cipher_exclusions, "aNULL");
+ else
+ ADD_EXCLUDE(cipher_exclusions, "eNULL");
+
+ smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0);
+ if (state->tlsproxy_mode) {
+ TLS_CLIENT_PARAMS tls_params;
+
+ /*
+ * Send all our wishes in one big request.
+ */
+ TLS_PROXY_CLIENT_INIT_PROPS(&init_props,
+ log_param = "-L option",
+ log_level = state->options.logopts,
+ verifydepth = DEF_SMTP_TLS_SCERT_VD,
+ cache_type = TLS_MGR_SCACHE_SMTP,
+ chain_files = state->chains,
+ cert_file = state->certfile,
+ key_file = state->keyfile,
+ dcert_file = "",
+ dkey_file = "",
+ eccert_file = "",
+ eckey_file = "",
+ CAfile = state->CAfile,
+ CApath = state->CApath,
+ mdalg = state->mdalg);
+ TLS_PROXY_CLIENT_START_PROPS(&start_props,
+ timeout = smtp_tmout,
+ tls_level = state->level,
+ nexthop = state->nexthop,
+ host = state->hostname,
+ namaddr = state->namaddrport,
+ sni = state->sni,
+ serverid = state->addrport,
+ helo = state->helo ? state->helo : "",
+ protocols = state->protocols,
+ cipher_grade = state->grade,
+ cipher_exclusions
+ = vstring_str(cipher_exclusions),
+ matchargv = state->match,
+ mdalg = state->mdalg,
+ dane = state->ddane ?
+ state->ddane : state->dane);
+
+#define PROXY_OPEN_FLAGS \
+ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)
+#define var_tlsproxy_service
+
+ if ((cwd_fd = open(".", O_RDONLY)) < 0)
+ msg_fatal("open(\".\", O_RDONLY): %m");
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(%s): %m", var_queue_dir);
+ port_buf = vstring_alloc(100);
+ vstring_sprintf(port_buf, "%d", ntohs(state->port));
+ tlsproxy =
+ tls_proxy_open(DEF_TLSPROXY_SERVICE /* TODO */ , PROXY_OPEN_FLAGS,
+ state->stream, state->paddr, STR(port_buf),
+ smtp_tmout, smtp_tmout, state->addrport,
+ tls_proxy_client_param_from_config(&tls_params),
+ &init_props, &start_props);
+ vstring_free(port_buf);
+ if (fchdir(cwd_fd) < 0)
+ msg_fatal("fchdir: %m");
+ (void) close(cwd_fd);
+
+ /*
+ * To insert tlsproxy(8) between this process and the remote SMTP
+ * server, we swap the file descriptors between the tlsproxy and
+ * session->stream VSTREAMS, so that we don't lose all the
+ * user-configurable session->stream attributes (such as longjump
+ * buffers or timeouts).
+ *
+ * TODO: the tlsproxy RPCs should return more error detail than a "NO"
+ * result.
+ */
+ if (tlsproxy == 0) {
+ state->tls_context = 0;
+ } else {
+ vstream_control(tlsproxy,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_END);
+ vstream_control(state->stream,
+ CA_VSTREAM_CTL_SWAP_FD(tlsproxy),
+ CA_VSTREAM_CTL_END);
+ (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */
+
+ /*
+ * There must not be any pending data in the stream buffers
+ * before we read the TLS context attributes.
+ */
+ vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH);
+
+ /*
+ * After plumbing the plaintext stream, receive the TLS context
+ * object. For this we use the same VSTREAM buffer that we also
+ * use to receive subsequent SMTP commands, therefore we must be
+ * prepared for the possibility that the remote SMTP server
+ * starts talking immediately. The tlsproxy implementation sends
+ * the TLS context before remote content. The attribute protocol
+ * is robust enough that an adversary cannot insert their own TLS
+ * context attributes.
+ */
+ state->tls_context = tls_proxy_context_receive(state->stream);
+ if (state->tls_context) {
+ if (state->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s",
+ state->namaddrport, state->tls_context->peer_CN,
+ state->tls_context->issuer_CN,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
+ state->tls_context);
+ } else {
+ msg_warn("error receiving TLS proxy context");
+ }
+ }
+ } else { /* tls_proxy_mode */
+ state->tls_context =
+ TLS_CLIENT_START(&start_props,
+ ctx = state->tls_ctx,
+ stream = stream,
+ fd = -1,
+ timeout = smtp_tmout,
+ tls_level = state->level,
+ nexthop = state->nexthop,
+ host = state->hostname,
+ namaddr = state->namaddrport,
+ sni = state->sni,
+ serverid = state->addrport,
+ helo = state->helo ? state->helo : "",
+ protocols = state->protocols,
+ cipher_grade = state->grade,
+ cipher_exclusions
+ = vstring_str(cipher_exclusions),
+ matchargv = state->match,
+ mdalg = state->mdalg,
+ dane = state->ddane ? state->ddane : state->dane);
+ } /* tlsproxy_mode */
+ vstring_free(cipher_exclusions);
+ if (state->helo) {
+ myfree(state->helo);
+ state->helo = 0;
+ }
+ if (state->tls_context == 0) {
+ /* We must avoid further I/O, the peer is in an undefined state. */
+ (void) vstream_fpurge(stream, VSTREAM_PURGE_BOTH);
+ (void) vstream_fclose(stream);
+ state->stream = 0;
+ return (1);
+ }
+ if (state->wrapper_mode && greeting(state) != 0)
+ return (1);
+
+ if (state->pass == 1) {
+ ehlo(state);
+ if (!TLS_CERT_IS_PRESENT(state->tls_context))
+ msg_info("Server is anonymous");
+ else if (state->tlsproxy_mode == 0) {
+ if (state->print_trust)
+ print_trust_info(state);
+ state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT |
+ TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED);
+ }
+ state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY;
+ tls_update_app_logmask(state->tls_ctx, state->log_mask);
+ }
+ return (0);
+}
+
+#endif
+
+/* doproto - do SMTP handshake */
+
+static int doproto(STATE *state)
+{
+ VSTREAM *stream = state->stream;
+ RESPONSE *resp;
+ int except;
+ int n;
+ char *lines;
+ char *words = 0;
+ char *word;
+
+ if (!state->wrapper_mode) {
+ if (greeting(state) != 0)
+ return (1);
+ if ((resp = ehlo(state)) == 0)
+ return (1);
+
+ lines = resp->str;
+ for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) {
+ if ((word = mystrtok(&words, " \t=")) != 0) {
+ if (n == 0)
+ state->helo = mystrdup(word);
+ if (strcasecmp(word, "STARTTLS") == 0)
+ break;
+ }
+ }
+ }
+#ifdef USE_TLS
+ if ((state->wrapper_mode || words) && state->tls_ctx)
+ if (starttls(state))
+ return (1);
+#endif
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_warn("%s while sending QUIT command", exception_text(except));
+ return (0);
+ }
+ command(state, 1, "QUIT");
+ (void) response(state, 1);
+ return (0);
+}
+
+/* connect_sock - connect a socket over some transport */
+
+static VSTREAM *connect_sock(int sock, struct sockaddr *sa, int salen,
+ const char *name, const char *addr, STATE *state)
+{
+ DSN_BUF *why = state->why;
+ int conn_stat;
+ int saved_errno;
+ VSTREAM *stream;
+
+ if (conn_tmout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ conn_stat = timed_connect(sock, sa, salen, conn_tmout);
+ saved_errno = errno;
+ non_blocking(sock, BLOCKING);
+ errno = saved_errno;
+ } else {
+ conn_stat = sane_connect(sock, sa, salen);
+ }
+ if (conn_stat < 0) {
+ if (state->port)
+ dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
+ name, addr, ntohs(state->port));
+ else
+ dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
+ close(sock);
+ return (0);
+ }
+ stream = vstream_fdopen(sock, O_RDWR);
+ state->namaddrport =
+ vstring_export(state->port == 0 ?
+ vstring_sprintf(vstring_alloc(10), "%s[%s]", name, addr) :
+ vstring_sprintf(vstring_alloc(10), "%s[%s]:%u",
+ name, addr, ntohs(state->port)));
+ state->addrport =
+ vstring_export(state->port == 0 ?
+ vstring_sprintf(vstring_alloc(10), "%s", addr) :
+ vstring_sprintf(vstring_alloc(10), "[%s]:%u",
+ addr, ntohs(state->port)));
+
+ state->paddr = mystrdup(addr); /* XXX for tlsproxy */
+
+ /*
+ * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+ */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )
+ vstream_tweak_tcp(stream);
+
+ return (stream);
+}
+
+/* connect_unix - connect to a unix-domain socket */
+
+static VSTREAM *connect_unix(STATE *state, const char *path)
+{
+ static const char *myname = "connect_unix";
+ DSN_BUF *why = state->why;
+ struct sockaddr_un sock_un;
+ int len = strlen(path);
+ int sock;
+
+ if (!state->nexthop)
+ state->nexthop = mystrdup(var_myhostname);
+ state->hostname = mystrdup(var_myhostname);
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (len >= (int) sizeof(sock_un.sun_path)) {
+ dsb_simple(why, "4.3.5", "unix-domain name too long: %s", path);
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ memset((void *) &sock_un, 0, sizeof(sock_un));
+ sock_un.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sock_un.sun_len = len + 1;
+#endif
+ memcpy(sock_un.sun_path, path, len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+ /*
+ * Connect to the server.
+ */
+ if (msg_verbose)
+ msg_info("%s: trying: %s...", myname, path);
+
+ return (connect_sock(sock, (struct sockaddr *) &sock_un, sizeof(sock_un),
+ var_myhostname, path, state));
+}
+
+/* connect_addr - connect to explicit address */
+
+static VSTREAM *connect_addr(STATE *state, DNS_RR *addr)
+{
+ static const char *myname = "connect_addr";
+ DSN_BUF *why = state->why;
+ struct sockaddr_storage ss; /* remote */
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE salen = sizeof(ss);
+ MAI_HOSTADDR_STR hostaddr;
+ int sock;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (dns_rr_to_sa(addr, state->port, sa, &salen) != 0) {
+ msg_warn("%s: skip address type %s: %m",
+ myname, dns_strtype(addr->type));
+ dsb_simple(why, "4.4.0", "network address conversion failed: %m");
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+
+ /*
+ * Connect to the server.
+ */
+ SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ if (msg_verbose)
+ msg_info("%s: trying: %s[%s] port %d...",
+ myname, HNAME(addr), hostaddr.buf, ntohs(state->port));
+
+ return (connect_sock(sock, sa, salen, HNAME(addr), hostaddr.buf, state));
+}
+
+#define HAS_DSN(why) (STR((why)->status)[0] != 0)
+#define HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4')
+#define HAS_HARD_DSN(why) (STR((why)->status)[0] == '5')
+#define HAS_LOOP_DSN(why) \
+ (HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0)
+
+#define SET_SOFT_DSN(why) (STR((why)->status)[0] = '4')
+#define SET_HARD_DSN(why) (STR((why)->status)[0] = '5')
+
+/* addr_one - address lookup for one host name */
+
+static DNS_RR *addr_one(STATE *state, DNS_RR *addr_list, const char *host,
+ int res_opt, unsigned pref)
+{
+ static const char *myname = "addr_one";
+ DSN_BUF *why = state->why;
+ DNS_RR *addr = 0;
+ DNS_RR *rr;
+ int aierr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+ int found;
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * Interpret a numerical name as an address.
+ */
+ if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0
+ && strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) {
+ if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family %d: %m",
+ host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ freeaddrinfo(res0);
+ return (addr_list);
+ }
+
+ /*
+ * Use DNS lookup, but keep the option open to use native name service.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+ if (state->host_lookup & HOST_FLAG_DNS) {
+ switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
+ why->reason, DNS_REQ_FLAG_NONE,
+ proto_info->dns_atype_list)) {
+ case DNS_OK:
+ for (rr = addr; rr; rr = rr->next)
+ rr->pref = pref;
+ addr_list = dns_rr_append(addr_list, addr);
+ return (addr_list);
+ default:
+ dsb_status(why, "4.4.3");
+ return (addr_list);
+ case DNS_FAIL:
+ dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
+ return (addr_list);
+ case DNS_INVAL:
+ dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ return (addr_list);
+ case DNS_NOTFOUND:
+ dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ /* maybe native naming service will succeed */
+ break;
+ }
+ }
+
+ /*
+ * Use the native name service which also looks in /etc/hosts.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+#define RETRY_AI_ERROR(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+#ifdef EAI_NODATA
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
+#else
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NONAME)
+#endif
+
+ if (state->host_lookup & HOST_FLAG_NATIVE) {
+ if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
+ dsb_simple(why, (HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
+ (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
+ (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
+ "unable to look up host %s: %s",
+ host, MAI_STRERROR(aierr));
+ } else {
+ for (found = 0, res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ found++;
+ if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family %d: %m",
+ host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ }
+ freeaddrinfo(res0);
+ if (found == 0) {
+ dsb_simple(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
+ "%s: host not found", host);
+ }
+ return (addr_list);
+ }
+ }
+
+ /*
+ * No further alternatives for host lookup.
+ */
+ return (addr_list);
+}
+
+/* mx_addr_list - address lookup for a list of mail exchangers */
+
+static DNS_RR *mx_addr_list(STATE *state, DNS_RR *mx_names)
+{
+ static const char *myname = "mx_addr_list";
+ DNS_RR *addr_list = 0;
+ DNS_RR *rr;
+ int res_opt = 0;
+
+ if (mx_names->dnssec_valid)
+ res_opt = RES_USE_DNSSEC;
+#ifdef USE_TLS
+ else if (state->mxinsec_level > TLS_LEV_MAY)
+ res_opt = RES_USE_DNSSEC;
+#endif
+
+ for (rr = mx_names; rr; rr = rr->next) {
+ if (rr->type != T_MX)
+ msg_panic("%s: bad resource type: %d", myname, rr->type);
+ addr_list = addr_one(state, addr_list, (char *) rr->data, res_opt,
+ rr->pref);
+ }
+ return (addr_list);
+}
+
+/* smtp_domain_addr - mail exchanger address lookup */
+
+static DNS_RR *domain_addr(STATE *state, char *domain)
+{
+ DNS_RR *mx_names;
+ DNS_RR *addr_list = 0;
+ int r = 0; /* Resolver flags */
+ const char *aname;
+
+ dsb_reset(state->why);
+
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+ r |= RES_USE_DNSSEC;
+#endif
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (aname = midna_domain_to_ascii(domain)) != 0) {
+ msg_info("%s asciified to %s", domain, aname);
+ } else
+#endif
+ aname = domain;
+
+ switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0,
+ state->why->reason)) {
+ default:
+ dsb_status(state->why, "4.4.3");
+ break;
+ case DNS_INVAL:
+ dsb_status(state->why, "5.4.4");
+ break;
+ case DNS_NULLMX:
+ dsb_status(state->why, "5.1.0");
+ break;
+ case DNS_FAIL:
+ dsb_status(state->why, "5.4.3");
+ break;
+ case DNS_OK:
+ mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
+ addr_list = mx_addr_list(state, mx_names);
+ state->mx = dns_rr_copy(mx_names);
+ dns_rr_free(mx_names);
+ if (addr_list == 0) {
+ msg_warn("no MX host for %s has a valid address record", domain);
+ break;
+ }
+#define COMPARE_ADDR(flags) \
+ ((flags & MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
+ (flags & MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
+ dns_rr_compare_pref_any)
+ if (addr_list && addr_list->next) {
+ addr_list = dns_rr_shuffle(addr_list);
+ addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
+ }
+ break;
+ case DNS_NOTFOUND:
+ addr_list = host_addr(state, domain);
+ break;
+ }
+
+ return (addr_list);
+}
+
+/* host_addr - direct host lookup */
+
+static DNS_RR *host_addr(STATE *state, const char *host)
+{
+ DSN_BUF *why = state->why;
+ DNS_RR *addr_list;
+ int res_opt = 0;
+ const char *ahost;
+
+ dsb_reset(why); /* Paranoia */
+
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+ res_opt |= RES_USE_DNSSEC;
+#endif
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
+ msg_info("%s asciified to %s", host, ahost);
+ } else
+#endif
+ ahost = host;
+
+#define PREF0 0
+ addr_list = addr_one(state, (DNS_RR *) 0, ahost, res_opt, PREF0);
+ if (addr_list && addr_list->next) {
+ addr_list = dns_rr_shuffle(addr_list);
+ if (inet_proto_info()->ai_family_list[1] != 0)
+ addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
+ }
+ return (addr_list);
+}
+
+/* dane_host_level - candidate host "dane" or degraded security level */
+
+static int dane_host_level(STATE *state, DNS_RR *addr)
+{
+ int level = state->level;
+
+#ifdef USE_TLS
+ if (TLS_DANE_BASED(level)) {
+ if (state->mx == 0 || state->mx->dnssec_valid ||
+ state->mxinsec_level > TLS_LEV_MAY) {
+
+ /* See addr loop in connect_remote() */
+ if (state->ddane)
+ tls_dane_free(state->ddane);
+
+ /*
+ * When TLSA lookups fail, next host. If unusable or not found,
+ * fallback to "secure"
+ */
+ state->ddane = tls_dane_resolve(state->port, "tcp", addr,
+ state->force_tlsa);
+ if (!state->ddane) {
+ dsb_simple(state->why, "4.7.5",
+ "TLSA lookup error for %s:%u",
+ HNAME(addr), ntohs(state->port));
+ level = TLS_LEV_INVALID;
+ } else if (tls_dane_notfound(state->ddane)
+ || tls_dane_unusable(state->ddane)) {
+ if (msg_verbose || level == TLS_LEV_DANE_ONLY)
+ msg_info("no %sTLSA records found, "
+ "resorting to \"secure\"",
+ tls_dane_unusable(state->ddane) ?
+ "usable " : "");
+ level = TLS_LEV_SECURE;
+ } else if (state->ddane->tlsa == 0) {
+ msg_panic("DANE activated with no TLSA records to match");
+ } else if (state->mx && !state->mx->dnssec_valid &&
+ state->mxinsec_level == TLS_LEV_ENCRYPT) {
+ msg_info("TLSA RRs found, MX RRset insecure: just encrypt");
+ tls_dane_free(state->ddane);
+ state->ddane = 0;
+ level = TLS_LEV_ENCRYPT;
+ } else {
+ if (state->match)
+ argv_free(state->match);
+ argv_add(state->match = argv_alloc(2),
+ state->ddane->base_domain, ARGV_END);
+ if (state->mx) {
+ if (!state->mx->dnssec_valid) {
+ msg_info("MX RRset insecure: log verified as trusted");
+ level = TLS_LEV_HALF_DANE;
+ }
+ if (strcmp(state->mx->qname, state->mx->rname) == 0)
+ argv_add(state->match, state->mx->qname, ARGV_END);
+ else
+ argv_add(state->match, state->mx->rname,
+ state->mx->qname, ARGV_END);
+ }
+ }
+ } else if (state->mx && !state->mx->dnssec_valid &&
+ state->mxinsec_level == TLS_LEV_MAY) {
+ msg_info("MX RRset is insecure: try to encrypt");
+ level = TLS_LEV_MAY;
+ } else {
+ level = TLS_LEV_SECURE;
+ }
+ }
+#endif
+
+ return (level);
+}
+
+/* parse_destination - parse host/port destination */
+
+static char *parse_destination(char *destination, char *def_service,
+ char **hostp, unsigned *portp)
+{
+ char *buf = mystrdup(destination);
+ char *service;
+ struct servent *sp;
+ char *protocol = "tcp";
+ unsigned port;
+ const char *err;
+
+ if (msg_verbose)
+ msg_info("parse_destination: %s %s", destination, def_service);
+
+ /*
+ * Parse the host/port information. We're working with a copy of the
+ * destination argument so the parsing can be destructive.
+ */
+ if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0)
+ msg_fatal("%s in server description: %s", err, destination);
+
+ /*
+ * Convert service to port number, network byte order.
+ */
+ service = (char *) filter_known_tcp_port(service);
+ if (alldig(service)) {
+ if ((port = atoi(service)) >= 65536 || port == 0)
+ msg_fatal("bad network port: %s for destination: %s",
+ service, destination);
+ *portp = htons(port);
+ } else {
+ if ((sp = getservbyname(service, protocol)) != 0)
+ *portp = sp->s_port;
+ else if (strcmp(service, "smtp") == 0)
+ *portp = htons(25);
+ else
+ msg_fatal("unknown service: %s/%s", service, protocol);
+ }
+ return (buf);
+}
+
+/* connect_remote - connect to TCP destination or log an error */
+
+static void connect_remote(STATE *state, char *dest)
+{
+ DNS_RR *addr;
+ char *buf;
+ char *domain;
+
+ /* When reconnecting use IP address of previous session */
+ if (state->addr == 0) {
+ buf = parse_destination(dest, state->smtp ? "smtp" : "24",
+ &domain, &state->port);
+ if (!state->nexthop)
+ state->nexthop = mystrdup(domain);
+ if (state->smtp == 0 || *dest == '[')
+ state->addr = host_addr(state, domain);
+ else
+ state->addr = domain_addr(state, domain);
+ myfree(buf);
+
+ if (state->addr == 0) {
+ msg_info("Destination address lookup failed: %s",
+ vstring_str(state->why->reason));
+ return;
+ }
+ }
+ for (addr = state->addr; addr; addr = addr->next) {
+ int level = dane_host_level(state, addr);
+
+ if (level == TLS_LEV_INVALID
+ || (state->stream = connect_addr(state, addr)) == 0) {
+ msg_info("Failed to establish session to %s via %s: %s",
+ dest, HNAME(addr), vstring_str(state->why->reason));
+ continue;
+ }
+ /* We have a connection */
+ state->level = level;
+ state->hostname = mystrdup(HNAME(addr));
+
+ /* We use the same address when reconnecting, so flush the rest. */
+ addr = dns_rr_copy(addr);
+ dns_rr_free(state->addr);
+ state->addr = addr;
+ break;
+ }
+}
+
+/* connect_dest - connect to given inet: or unix: destination */
+
+static int connect_dest(STATE *state)
+{
+ char *dest = state->dest;
+
+ /*
+ * With LMTP we have direct-to-host delivery only. The destination may
+ * have multiple IP addresses.
+ */
+ if (state->smtp == 0) {
+ if (strncmp(dest, "unix:", 5) == 0) {
+ state->stream = connect_unix(state, dest + 5);
+ if (!state->stream)
+ msg_info("Failed to establish session to %s: %s",
+ dest, vstring_str(state->why->reason));
+ return (1);
+ }
+ if (strncmp(dest, "inet:", 5) == 0)
+ dest += 5;
+ }
+ connect_remote(state, dest);
+
+ return (state->stream == 0);
+}
+
+static void disconnect_dest(STATE *state)
+{
+#ifdef USE_TLS
+ if (state->tls_context) {
+ if (state->tlsproxy_mode) {
+ tls_proxy_context_free(state->tls_context);
+ } else {
+ tls_client_stop(state->tls_ctx, state->stream,
+ smtp_tmout, 0, state->tls_context);
+ }
+ }
+ state->tls_context = 0;
+ if (state->ddane)
+ tls_dane_free(state->ddane);
+ state->ddane = 0;
+#endif
+
+ if (state->stream)
+ vstream_fclose(state->stream);
+ state->stream = 0;
+
+ if (state->namaddrport)
+ myfree(state->namaddrport);
+ state->namaddrport = 0;
+
+ if (state->addrport)
+ myfree(state->addrport);
+ state->addrport = 0;
+
+ if (state->paddr)
+ myfree(state->paddr);
+ state->paddr = 0;
+
+ /* Reused on reconnect */
+ if (state->reconnect <= 0) {
+ if (state->addr)
+ dns_rr_free(state->addr);
+ state->addr = 0;
+ if (state->mx)
+ dns_rr_free(state->mx);
+ state->mx = 0;
+
+ if (state->nexthop)
+ myfree(state->nexthop);
+ state->nexthop = 0;
+ }
+ if (state->hostname)
+ myfree(state->hostname);
+ state->hostname = 0;
+
+ dsb_free(state->why);
+ vstring_free(state->buffer);
+}
+
+static int finger(STATE *state)
+{
+ int err;
+
+ /*
+ * smtp_get() makes sure the SMTP server cannot run us out of memory by
+ * sending never-ending lines of text.
+ */
+ state->buffer = vstring_alloc(100);
+ state->why = dsb_create();
+
+ if (!(err = connect_dest(state))) {
+ if (state->pass == 1 && !state->nochat)
+ msg_info("Connected to %s", state->namaddrport);
+ err = doproto(state);
+ }
+ disconnect_dest(state);
+
+ if (err != 0)
+ return (1);
+
+#ifdef USE_TLS
+ if (state->tlsproxy_mode == 0 && state->reconnect > 0) {
+ int cache_enabled;
+ int cache_count;
+ int cache_hits;
+
+ tlsmgrmem_status(&cache_enabled, &cache_count, &cache_hits);
+ if (cache_enabled && cache_count == 0) {
+ msg_info("Server declined session caching. Done reconnecting.");
+ state->reconnect = 0;
+ } else if (cache_hits > 0 && (state->log_mask & TLS_LOG_CACHE) != 0) {
+ msg_info("Found a previously used server. Done reconnecting.");
+ state->reconnect = 0;
+ } else if (state->max_reconnect-- <= 0) {
+ msg_info("Maximum reconnect count reached.");
+ state->reconnect = 0;
+ }
+ }
+#endif
+
+ return (0);
+}
+
+/* run - do what we were asked to do. */
+
+static int run(STATE *state)
+{
+
+ while (1) {
+ if (finger(state) != 0)
+ break;
+ if (state->reconnect <= 0)
+ break;
+ msg_info("Reconnecting after %d seconds", state->reconnect);
+ ++state->pass;
+ sleep(state->reconnect);
+ }
+
+ return (0);
+}
+
+/* cleanup - free memory allocated in main */
+
+static void cleanup(STATE *state)
+{
+#ifdef USE_TLS
+ if (state->tls_ctx != 0)
+ tls_free_app_context(state->tls_ctx);
+ if (state->tls_bio)
+ (void) BIO_free(state->tls_bio);
+ state->tls_bio = 0;
+
+ myfree(state->mdalg);
+ myfree(state->CApath);
+ myfree(state->CAfile);
+ myfree(state->certfile);
+ myfree(state->keyfile);
+ myfree(state->sni);
+ if (state->options.level)
+ myfree(state->options.level);
+ myfree(state->options.logopts);
+ if (state->match)
+ argv_free(state->match);
+ if (state->options.tas)
+ argv_free(state->options.tas);
+ if (state->dane)
+ tls_dane_free(state->dane);
+
+ /* Flush and free DANE TLSA cache */
+ tls_dane_flush();
+ /* Flush and free memory tlsmgr cache */
+ tlsmgrmem_flush();
+ myfree(state->grade);
+ myfree(state->protocols);
+#endif
+ myfree(state->options.host_lookup);
+ myfree(state->dest);
+
+ mail_conf_flush();
+}
+
+/* usage - explain */
+
+static void usage(void)
+{
+#ifdef USE_TLS
+ fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s \\\n\t%s"
+ " destination [match ...]\n", var_procname,
+ "[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
+ "[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]",
+ "[-A tafile] [-F CAfile.pem] [-P CApath/] [-s servername]",
+ "[ [-H chainfiles] | [-k certfile [-K keyfile]] ]",
+ "[-m count] [-r delay] [-o name=value]");
+#else
+ fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n",
+ var_procname);
+#endif
+ exit(1);
+}
+
+/* tls_init - initialize application TLS library context */
+
+static void tls_init(STATE *state)
+{
+#ifdef USE_TLS
+ TLS_CLIENT_INIT_PROPS props;
+
+ if (state->level <= TLS_LEV_NONE)
+ return;
+
+ /* Needed for tls_dane_avail() and other DANE-related processing. */
+ state->tls_ctx =
+ TLS_CLIENT_INIT(&props,
+ log_param = "-L option",
+ log_level = state->options.logopts,
+ verifydepth = DEF_SMTP_TLS_SCERT_VD,
+ cache_type = "memory",
+ chain_files = state->chains,
+ cert_file = state->certfile,
+ key_file = state->keyfile,
+ dcert_file = "",
+ dkey_file = "",
+ eccert_file = "",
+ eckey_file = "",
+ CAfile = state->CAfile,
+ CApath = state->CApath,
+ mdalg = state->mdalg);
+#endif
+}
+
+/* override - update main.cf parameter */
+
+static void override(const char *nameval)
+{
+ char *param_name;
+ char *param_value;
+ char *save = mystrdup(nameval);
+
+ if (split_nameval(save, &param_name, &param_value) != 0)
+ usage();
+ mail_conf_update(param_name, param_value);
+ myfree(save);
+}
+
+/* parse_options - (argc, argv) -> state */
+
+static void parse_options(STATE *state, int argc, char *argv[])
+{
+ int c;
+
+ state->smtp = 1;
+ state->pass = 1;
+ state->reconnect = -1;
+ state->max_reconnect = 5;
+ state->wrapper_mode = 0;
+#ifdef USE_TLS
+ state->protocols = mystrdup(">=TLSv1");
+ state->grade = mystrdup("medium");
+#endif
+ memset((void *) &state->options, 0, sizeof(state->options));
+ state->options.host_lookup = mystrdup("dns");
+
+#define OPTS "a:ch:o:St:T:v"
+#ifdef USE_TLS
+#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wX"
+
+ state->mdalg = 0;
+ state->CApath = mystrdup("");
+ state->CAfile = mystrdup("");
+ state->chains = mystrdup("");
+ state->certfile = mystrdup("");
+ state->keyfile = mystrdup("");
+ state->sni = mystrdup("");
+ state->options.tas = argv_alloc(1);
+ state->options.logopts = 0;
+ state->level = TLS_LEV_DANE;
+ state->mxinsec_level = TLS_LEV_DANE;
+ state->tlsproxy_mode = 0;
+#else
+#define TLSOPTS ""
+ state->level = TLS_LEV_NONE;
+#endif
+
+ while ((c = GETOPT(argc, argv, OPTS TLSOPTS)) > 0) {
+ switch (c) {
+ default:
+ usage();
+ break;
+ case 'a':
+ state->options.addr_pref = mystrdup(optarg);
+ break;
+ case 'c':
+ state->nochat = 1;
+ break;
+ case 'h':
+ myfree(state->options.host_lookup);
+ state->options.host_lookup = mystrdup(optarg);
+ break;
+ case 'o':
+ override(optarg);
+ break;
+ case 'S':
+ state->smtp = 0;
+ break;
+ case 't':
+ conn_tmout = atoi(optarg);
+ break;
+ case 'T':
+ smtp_tmout = atoi(optarg);
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+#ifdef USE_TLS
+ case 'A':
+ argv_add(state->options.tas, optarg, ARGV_END);
+ break;
+ case 'C':
+ state->print_trust = 1;
+ break;
+ case 'd':
+ if (state->mdalg)
+ myfree(state->mdalg);
+ state->mdalg = mystrdup(optarg);
+ break;
+ case 'f':
+ state->force_tlsa = 1;
+ break;
+ case 'F':
+ myfree(state->CAfile);
+ state->CAfile = mystrdup(optarg);
+ break;
+ case 'g':
+ myfree(state->grade);
+ state->grade = mystrdup(optarg);
+ break;
+ case 'H':
+ {
+ char *tmp;
+
+ if (*state->chains)
+ tmp = concatenate(state->chains, ", ", optarg, (char *) 0);
+ else
+ tmp = mystrdup(optarg);
+ myfree(state->chains);
+ state->chains = tmp;
+ }
+ break;
+ case 'k':
+ myfree(state->certfile);
+ state->certfile = mystrdup(optarg);
+ if (!*state->keyfile) {
+ myfree(state->keyfile);
+ state->keyfile = mystrdup(optarg);
+ }
+ break;
+ case 'K':
+ myfree(state->keyfile);
+ state->keyfile = mystrdup(optarg);
+ if (!*state->certfile) {
+ myfree(state->certfile);
+ state->certfile = mystrdup(optarg);
+ }
+ break;
+ case 'l':
+ if (state->options.level)
+ myfree(state->options.level);
+ state->options.level = mystrdup(optarg);
+ break;
+ case 'L':
+ if (state->options.logopts)
+ myfree(state->options.logopts);
+ state->options.logopts = mystrdup(optarg);
+ break;
+ case 'm':
+ state->max_reconnect = atoi(optarg);
+ break;
+ case 'M':
+ switch (state->mxinsec_level = tls_level_lookup(optarg)) {
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_DANE:
+ break;
+ default:
+ msg_fatal("bad '-M' option value: %s", optarg);
+ }
+ break;
+ case 'p':
+ myfree(state->protocols);
+ state->protocols = mystrdup(optarg);
+ break;
+ case 'P':
+ myfree(state->CApath);
+ state->CApath = mystrdup(optarg);
+ break;
+ case 'r':
+ state->reconnect = atoi(optarg);
+ break;
+ case 's':
+ myfree(state->sni);
+ state->sni = mystrdup(optarg);
+ break;
+ case 'w':
+ state->wrapper_mode = 1;
+ break;
+ case 'X':
+ state->tlsproxy_mode = 1;
+ break;
+#endif
+ }
+ }
+
+ /*
+ * Address family preference.
+ */
+ state->addr_pref =
+ name_code(addr_pref_map, NAME_CODE_FLAG_NONE, state->options.addr_pref ?
+ state->options.addr_pref : "any");
+ if (state->addr_pref < 0)
+ msg_fatal("bad '-a' option value: %s", state->options.addr_pref);
+
+#ifdef USE_TLS
+ if (state->tlsproxy_mode && state->reconnect >= 0)
+ msg_fatal("The -X and -r options are mutually exclusive");
+#endif
+
+ /*
+ * Select hostname lookup mechanisms.
+ */
+ state->host_lookup =
+ name_mask("-h option", lookup_masks, state->options.host_lookup ?
+ state->options.host_lookup : "dns");
+
+#ifdef USE_TLS
+
+ if (*state->chains && *state->certfile)
+ msg_fatal("When the '-H' option is used, neither the '-k',"
+ " nor the '-K' options may be used");
+
+ if (state->reconnect < 0)
+ tlsmgrmem_disable();
+
+ if (state->options.logopts == 0)
+ state->options.logopts = mystrdup("routine,certmatch");
+ state->log_mask = tls_log_mask("-L option", state->options.logopts);
+ tls_dane_loglevel("-L option", state->options.logopts);
+
+ if (state->options.level) {
+ state->level = tls_level_lookup(state->options.level);
+
+ switch (state->level) {
+ case TLS_LEV_NONE:
+ if (state->wrapper_mode)
+ msg_fatal("SSL wrapper mode requires that TLS not be disabled");
+ return;
+ case TLS_LEV_INVALID:
+ msg_fatal("Invalid TLS level \"%s\"", state->options.level);
+ }
+ }
+#endif
+}
+
+/* parse_match - process match arguments */
+
+static void parse_match(STATE *state, int argc, char *argv[])
+{
+#ifdef USE_TLS
+ int smtp_mode = 1;
+
+ switch (state->level) {
+ case TLS_LEV_SECURE:
+ state->match = argv_alloc(2);
+ while (*argv)
+ argv_split_append(state->match, *argv++, "");
+ if (state->match->argc == 0)
+ argv_add(state->match, "nexthop", "dot-nexthop", ARGV_END);
+ break;
+ case TLS_LEV_VERIFY:
+ state->match = argv_alloc(1);
+ while (*argv)
+ argv_split_append(state->match, *argv++, "");
+ if (state->match->argc == 0)
+ argv_add(state->match, "hostname", ARGV_END);
+ break;
+ case TLS_LEV_FPRINT:
+ state->dane = tls_dane_alloc();
+ while (*argv)
+ tls_dane_add_fpt_digests((TLS_DANE *) state->dane, *argv++, "",
+ smtp_mode);
+ break;
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+ state->match = argv_alloc(2);
+ argv_add(state->match, "nexthop", "hostname", ARGV_END);
+ break;
+ }
+#endif
+}
+
+/* parse_tas - process '-A' trust anchor file option */
+
+static void parse_tas(STATE *state)
+{
+#ifdef USE_TLS
+ char **file;
+
+ if (!state->options.tas->argc)
+ return;
+
+ switch (state->level) {
+ default:
+ return;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ state->dane = tls_dane_alloc();
+ for (file = state->options.tas->argv; *file; ++file) {
+ if (!tls_dane_load_trustfile((TLS_DANE *) state->dane, *file))
+ break;
+ }
+ if (*file)
+ msg_fatal("Failed to load trust anchor file: %s", *file);
+ break;
+ }
+#endif
+}
+
+
+int main(int argc, char *argv[])
+{
+ static STATE state;
+ char *loopenv = getenv("VALGRINDLOOP");
+ int loop = loopenv ? atoi(loopenv) : 1;
+ ARGV *import_env;
+ static char *var_smtp_tls_fpt_dgst;
+ static const CONFIG_STR_TABLE smtp_str_table[] = {
+#ifdef USE_TLS
+ VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+#endif
+ 0,
+ };
+
+ /* Don't die when a peer goes away unexpectedly. */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* We're a diagnostic utility, so diagnostic messages go to stdout. */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ msg_vstream_init(var_procname, VSTREAM_OUT);
+
+ /*
+ * Load main.cf, parse command-line options, then process main.cf
+ * settings plus any command-line "-o" overrides.
+ */
+ mail_conf_suck();
+ parse_options(&state, argc, argv);
+ mail_params_init();
+ get_mail_conf_str_table(smtp_str_table);
+ parse_tas(&state);
+
+#ifdef USE_TLS
+ /* Less surprising to default to the same fingerprint digest as smtp(8) */
+ if (state.mdalg)
+ warn_compat_break_smtp_tls_fpt_dgst = 0;
+ else
+ state.mdalg = mystrdup(var_smtp_tls_fpt_dgst);
+
+ /*
+ * We first call tls_init(), which ultimately calls SSL_library_init(),
+ * since otherwise we can't tell whether we have the message digests
+ * required for DANE support.
+ */
+ tls_init(&state);
+ if (TLS_DANE_BASED(state.level) && !tls_dane_avail()) {
+ msg_warn("DANE TLS support is not available, resorting to \"secure\"");
+ state.level = TLS_LEV_SECURE;
+ }
+ state.tls_bio = 0;
+ if (state.print_trust)
+ state.tls_bio = BIO_new_fp(stdout, BIO_NOCLOSE);
+#endif
+
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+
+ argc -= optind;
+ argv += optind;
+
+ /* The first non-option argument is the destination. */
+ if (!argc)
+ usage();
+
+ state.dest = mystrdup(argv[0]);
+ parse_match(&state, --argc, ++argv);
+
+ /* Don't talk to remote systems as root */
+ if (!geteuid())
+ chroot_uid(0, var_mail_owner);
+
+ while (loop-- > 0)
+ run(&state);
+
+ /* Be valgrind friendly and clean-up */
+ cleanup(&state);
+
+ return (0);
+}
diff --git a/src/posttls-finger/tlsmgrmem.c b/src/posttls-finger/tlsmgrmem.c
new file mode 100644
index 0000000..bfbc3a1
--- /dev/null
+++ b/src/posttls-finger/tlsmgrmem.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* tlsmgrmem 3
+/* SUMMARY
+/* Memory-based TLS manager interface for tlsfinger(1).
+/* SYNOPSIS
+/* #ifdef USE_TLS
+/* #include <tlsmgrmem.h>
+/*
+/* void tlsmgrmem_disable()
+/*
+/* void tlsmgrmem_status(enable, count, hits)
+/* int *enable;
+/* int *count;
+/* int *hits;
+/*
+/* void tlsmgrmem_flush()
+/* #endif
+/* DESCRIPTION
+/* tlsmgrmem_disable() disables the in-memory TLS session cache.
+/*
+/* tlsmgrmem_status() reports whether the cache is enabled, the
+/* number of entries in the cache, and the number of cache hits.
+/* If any of the return pointers are null, that item is not reported.
+/*
+/* tlsmgrmem_flush() flushes any cached data and frees the cache.
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <htable.h>
+#include <vstring.h>
+#include <tls_mgr.h>
+
+#include "tlsmgrmem.h"
+
+static HTABLE *tls_cache;
+static int cache_enabled = 1;
+static int cache_count;
+static int cache_hits;
+typedef void (*free_func) (void *);
+static free_func free_value = (free_func) vstring_free;
+
+void tlsmgrmem_disable(void)
+{
+ cache_enabled = 0;
+}
+
+void tlsmgrmem_flush(void)
+{
+ if (!tls_cache)
+ return;
+ htable_free(tls_cache, free_value);
+}
+
+void tlsmgrmem_status(int *enabled, int *count, int *hits)
+{
+ if (enabled)
+ *enabled = cache_enabled;
+ if (count)
+ *count = cache_count;
+ if (hits)
+ *hits = cache_hits;
+}
+
+/* tls_mgr_* - Local cache and stubs that do not talk to the TLS manager */
+
+int tls_mgr_seed(VSTRING *buf, int len)
+{
+ return (TLS_MGR_STAT_OK);
+}
+
+int tls_mgr_policy(const char *unused_type, int *cachable, int *timeout)
+{
+ if (cache_enabled && tls_cache == 0)
+ tls_cache = htable_create(1);
+ *cachable = cache_enabled;
+ *timeout = TLS_SESSION_LIFEMIN;
+ return (TLS_MGR_STAT_OK);
+}
+
+int tls_mgr_lookup(const char *unused_type, const char *key, VSTRING *buf)
+{
+ VSTRING *s;
+
+ if (tls_cache == 0)
+ return TLS_MGR_STAT_ERR;
+
+ if ((s = (VSTRING *) htable_find(tls_cache, key)) == 0)
+ return TLS_MGR_STAT_ERR;
+
+ vstring_memcpy(buf, vstring_str(s), VSTRING_LEN(s));
+
+ ++cache_hits;
+ return (TLS_MGR_STAT_OK);
+}
+
+int tls_mgr_update(const char *unused_type, const char *key,
+ const char *buf, ssize_t len)
+{
+ HTABLE_INFO *ent;
+ VSTRING *s;
+
+ if (tls_cache == 0)
+ return TLS_MGR_STAT_ERR;
+
+ if ((ent = htable_locate(tls_cache, key)) == 0) {
+ s = vstring_alloc(len);
+ ent = htable_enter(tls_cache, key, (void *) s);
+ } else {
+ s = (VSTRING *) ent->value;
+ }
+ vstring_memcpy(s, buf, len);
+
+ ++cache_count;
+ return (TLS_MGR_STAT_OK);
+}
+
+int tls_mgr_delete(const char *unused_type, const char *key)
+{
+ if (tls_cache == 0)
+ return TLS_MGR_STAT_ERR;
+
+ if (htable_locate(tls_cache, key)) {
+ htable_delete(tls_cache, key, free_value);
+ --cache_count;
+ }
+ return (TLS_MGR_STAT_OK);
+}
+
+#endif
diff --git a/src/posttls-finger/tlsmgrmem.h b/src/posttls-finger/tlsmgrmem.h
new file mode 100644
index 0000000..706b206
--- /dev/null
+++ b/src/posttls-finger/tlsmgrmem.h
@@ -0,0 +1,28 @@
+/*++
+/* NAME
+/* tlsmgrmem 3
+/* SUMMARY
+/* Memory-based TLS manager interface for tlsfinger(1).
+/* SYNOPSIS
+/* #include <tlsmgrmem.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void tlsmgrmem_disable(void);
+extern void tlsmgrmem_status(int *, int *, int *);
+extern void tlsmgrmem_flush(void);
+
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
diff --git a/src/proxymap/.indent.pro b/src/proxymap/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/proxymap/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/proxymap/Makefile.in b/src/proxymap/Makefile.in
new file mode 100644
index 0000000..925f98f
--- /dev/null
+++ b/src/proxymap/Makefile.in
@@ -0,0 +1,85 @@
+SHELL = /bin/sh
+SRCS = proxymap.c
+OBJS = proxymap.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = proxymap
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+proxymap.o: ../../include/argv.h
+proxymap.o: ../../include/attr.h
+proxymap.o: ../../include/check_arg.h
+proxymap.o: ../../include/dict.h
+proxymap.o: ../../include/dict_pipe.h
+proxymap.o: ../../include/dict_proxy.h
+proxymap.o: ../../include/dict_union.h
+proxymap.o: ../../include/htable.h
+proxymap.o: ../../include/iostuff.h
+proxymap.o: ../../include/mail_conf.h
+proxymap.o: ../../include/mail_params.h
+proxymap.o: ../../include/mail_proto.h
+proxymap.o: ../../include/mail_server.h
+proxymap.o: ../../include/mail_version.h
+proxymap.o: ../../include/msg.h
+proxymap.o: ../../include/myflock.h
+proxymap.o: ../../include/mymalloc.h
+proxymap.o: ../../include/nvtable.h
+proxymap.o: ../../include/stringops.h
+proxymap.o: ../../include/sys_defs.h
+proxymap.o: ../../include/vbuf.h
+proxymap.o: ../../include/vstream.h
+proxymap.o: ../../include/vstring.h
+proxymap.o: proxymap.c
diff --git a/src/proxymap/proxymap.c b/src/proxymap/proxymap.c
new file mode 100644
index 0000000..abdcf3a
--- /dev/null
+++ b/src/proxymap/proxymap.c
@@ -0,0 +1,851 @@
+/*++
+/* NAME
+/* proxymap 8
+/* SUMMARY
+/* Postfix lookup table proxy server
+/* SYNOPSIS
+/* \fBproxymap\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBproxymap\fR(8) server provides read-only or read-write
+/* table lookup service to Postfix processes. These services are
+/* implemented with distinct service names: \fBproxymap\fR and
+/* \fBproxywrite\fR, respectively. The purpose of these services is:
+/* .IP \(bu
+/* To overcome chroot restrictions. For example, a chrooted SMTP
+/* server needs access to the system passwd file in order to
+/* reject mail for non-existent local addresses, but it is not
+/* practical to maintain a copy of the passwd file in the chroot
+/* jail. The solution:
+/* .sp
+/* .nf
+/* local_recipient_maps =
+/* proxy:unix:passwd.byname $alias_maps
+/* .fi
+/* .IP \(bu
+/* To consolidate the number of open lookup tables by sharing
+/* one open table among multiple processes. For example, making
+/* mysql connections from every Postfix daemon process results
+/* in "too many connections" errors. The solution:
+/* .sp
+/* .nf
+/* virtual_alias_maps =
+/* proxy:mysql:/etc/postfix/virtual_alias.cf
+/* .fi
+/* .sp
+/* The total number of connections is limited by the number of
+/* proxymap server processes.
+/* .IP \(bu
+/* To provide single-updater functionality for lookup tables
+/* that do not reliably support multiple writers (i.e. all
+/* file-based tables).
+/* .PP
+/* The \fBproxymap\fR(8) server implements the following requests:
+/* .IP "\fBopen\fR \fImaptype:mapname flags\fR"
+/* Open the table with type \fImaptype\fR and name \fImapname\fR,
+/* as controlled by \fIflags\fR. The reply includes the \fImaptype\fR
+/* dependent flags (to distinguish a fixed string table from a regular
+/* expression table).
+/* .IP "\fBlookup\fR \fImaptype:mapname flags key\fR"
+/* Look up the data stored under the requested key.
+/* The reply is the request completion status code and
+/* the lookup result value.
+/* The \fImaptype:mapname\fR and \fIflags\fR are the same
+/* as with the \fBopen\fR request.
+/* .IP "\fBupdate\fR \fImaptype:mapname flags key value\fR"
+/* Update the data stored under the requested key.
+/* The reply is the request completion status code.
+/* The \fImaptype:mapname\fR and \fIflags\fR are the same
+/* as with the \fBopen\fR request.
+/* .sp
+/* To implement single-updater maps, specify a process limit
+/* of 1 in the master.cf file entry for the \fBproxywrite\fR
+/* service.
+/* .sp
+/* This request is supported in Postfix 2.5 and later.
+/* .IP "\fBdelete\fR \fImaptype:mapname flags key\fR"
+/* Delete the data stored under the requested key.
+/* The reply is the request completion status code.
+/* The \fImaptype:mapname\fR and \fIflags\fR are the same
+/* as with the \fBopen\fR request.
+/* .sp
+/* This request is supported in Postfix 2.5 and later.
+/* .IP "\fBsequence\fR \fImaptype:mapname flags function\fR"
+/* Iterate over the specified database. The \fIfunction\fR
+/* is one of DICT_SEQ_FUN_FIRST or DICT_SEQ_FUN_NEXT.
+/* The reply is the request completion status code and
+/* a lookup key and result value, if found.
+/* .sp
+/* This request is supported in Postfix 2.9 and later.
+/* .PP
+/* The request completion status is one of OK, RETRY, NOKEY
+/* (lookup failed because the key was not found), BAD (malformed
+/* request) or DENY (the table is not approved for proxy read
+/* or update access).
+/*
+/* There is no \fBclose\fR command, nor are tables implicitly closed
+/* when a client disconnects. The purpose is to share tables among
+/* multiple client processes.
+/* SERVER PROCESS MANAGEMENT
+/* .ad
+/* .fi
+/* \fBproxymap\fR(8) servers run under control by the Postfix
+/* \fBmaster\fR(8)
+/* server. Each server can handle multiple simultaneous connections.
+/* When all servers are busy while a client connects, the \fBmaster\fR(8)
+/* creates a new \fBproxymap\fR(8) server process, provided that the
+/* process limit is not exceeded.
+/* Each server terminates after serving at least \fB$max_use\fR clients
+/* or after \fB$max_idle\fR seconds of idle time.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBproxymap\fR(8) server opens only tables that are
+/* approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR
+/* configuration parameters, does not talk to
+/* users, and can run at fixed low privilege, chrooted or not.
+/* However, running the proxymap server chrooted severely limits
+/* usability, because it can open only chrooted tables.
+/*
+/* The \fBproxymap\fR(8) server is not a trusted daemon process, and must
+/* not be used to look up sensitive information such as UNIX user or
+/* group IDs, mailbox file/directory names or external commands.
+/*
+/* In Postfix version 2.2 and later, the proxymap client recognizes
+/* requests to access a table for security-sensitive purposes,
+/* and opens the table directly. This allows the same main.cf
+/* setting to be used by sensitive and non-sensitive processes.
+/*
+/* Postfix-writable data files should be stored under a dedicated
+/* directory that is writable only by the Postfix mail system,
+/* such as the Postfix-owned \fBdata_directory\fR.
+/*
+/* In particular, Postfix-writable files should never exist
+/* in root-owned directories. That would open up a particular
+/* type of security hole where ownership of a file or directory
+/* does not match the provider of its content.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The \fBproxymap\fR(8) server provides service to multiple clients,
+/* and must therefore not be used for tables that have high-latency
+/* lookups.
+/*
+/* The \fBproxymap\fR(8) read-write service does not explicitly
+/* close lookup tables (even if it did, this could not be relied on,
+/* because the process may be terminated between table updates).
+/* The read-write service should therefore not be used with tables that
+/* leave persistent storage in an inconsistent state between
+/* updates (for example, CDB). Tables that support "sync on
+/* update" should be safe (for example, Berkeley DB) as should
+/* tables that are implemented by a real DBMS.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* On busy mail systems a long time may pass before
+/* \fBproxymap\fR(8) relevant
+/* changes to \fBmain.cf\fR are picked up. 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.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix-writable data files (for example:
+/* caches, pseudo-random numbers).
+/* .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 "\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 "\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 "\fBproxy_read_maps (see 'postconf -d' output)\fR"
+/* The lookup tables that the \fBproxymap\fR(8) server is allowed to
+/* access for the read-only service.
+/* .PP
+/* Available in Postfix 2.5 and later:
+/* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix-writable data files (for example:
+/* caches, pseudo-random numbers).
+/* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR"
+/* The lookup tables that the \fBproxymap\fR(8) server is allowed to
+/* access for the read-write service.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* DATABASE_README, Postfix lookup table overview
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The proxymap service was introduced with Postfix 2.0.
+/* 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 <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <htable.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_pipe.h>
+#include <dict_union.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <dict_proxy.h>
+
+/* Server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * XXX All but the last are needed here so that $name expansion dependencies
+ * aren't too broken. The fix is to gather all parameter default settings in
+ * one place.
+ */
+char *var_alias_maps;
+char *var_local_rcpt_maps;
+char *var_virt_alias_maps;
+char *var_virt_alias_doms;
+char *var_virt_mailbox_maps;
+char *var_virt_mailbox_doms;
+char *var_relay_rcpt_maps;
+char *var_canonical_maps;
+char *var_send_canon_maps;
+char *var_rcpt_canon_maps;
+char *var_relocated_maps;
+char *var_transport_maps;
+char *var_verify_map;
+char *var_smtpd_snd_auth_maps;
+char *var_psc_cache_map;
+char *var_proxy_read_maps;
+char *var_proxy_write_maps;
+
+ /*
+ * The pre-approved, pre-parsed list of maps.
+ */
+static HTABLE *proxy_auth_maps;
+
+ /*
+ * Shared and static to reduce memory allocation overhead.
+ */
+static VSTRING *request;
+static VSTRING *request_map;
+static VSTRING *request_key;
+static VSTRING *request_value;
+static VSTRING *map_type_name_flags;
+
+ /*
+ * Are we a proxy writer or not?
+ */
+static int proxy_writer;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define VSTREQ(x,y) (strcmp(STR(x),y) == 0)
+
+/* get_nested_dict_name - return nested dictionary name pointer, or null */
+
+static char *get_nested_dict_name(char *type_name)
+{
+ const struct {
+ const char *type_col;
+ ssize_t type_col_len;
+ } *prefix, prefixes[] = {
+ DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1),
+ DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1),
+ };
+
+#define COUNT_OF(x) (sizeof(x)/sizeof((x)[0]))
+
+ for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) {
+ if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0)
+ return (type_name + prefix->type_col_len);
+ }
+ return (0);
+}
+
+/* proxy_map_find - look up or open table */
+
+static DICT *proxy_map_find(const char *map_type_name, int request_flags,
+ int *statp)
+{
+ DICT *dict;
+
+#define PROXY_COLON DICT_TYPE_PROXY ":"
+#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1)
+#define READ_OPEN_FLAGS O_RDONLY
+#define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)
+
+ /*
+ * Canonicalize the map name. If the map is not on the approved list,
+ * deny the request.
+ */
+#define PROXY_MAP_FIND_ERROR_RETURN(x) { *statp = (x); return (0); }
+#define PROXY_MAP_PARAM_NAME(proxy_writer) \
+ ((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS)
+
+ while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
+ map_type_name += PROXY_COLON_LEN;
+ /* XXX The following breaks with maps that have ':' in their name. */
+ if (strchr(map_type_name, ':') == 0)
+ PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
+ if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
+ msg_warn("request for unapproved table: \"%s\"", map_type_name);
+ msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
+ proxy_writer == 0 ? "read-only" : "read-write",
+ DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
+ PROXY_MAP_PARAM_NAME(proxy_writer));
+ PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
+ }
+
+ /*
+ * Open one instance of a map for each combination of name+flags.
+ *
+ * Assume that a map instance can be shared among clients with different
+ * paranoia flag settings and with different map lookup flag settings.
+ *
+ * XXX The open() flags are passed implicitly, via the selection of the
+ * service name. For a more sophisticated interface, appropriate subsets
+ * of open() flags should be received directly from the client.
+ */
+ vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
+ dict_flags_str(request_flags & DICT_FLAG_INST_MASK));
+ if (msg_verbose)
+ msg_info("proxy_map_find: %s", STR(map_type_name_flags));
+ if ((dict = dict_handle(STR(map_type_name_flags))) == 0) {
+ dict = dict_open(map_type_name, proxy_writer ?
+ WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
+ request_flags);
+ if (dict == 0)
+ msg_panic("proxy_map_find: dict_open null result");
+ dict_register(STR(map_type_name_flags), dict);
+ }
+ dict->error = 0;
+ return (dict);
+}
+
+/* proxymap_sequence_service - remote sequence service */
+
+static void proxymap_sequence_service(VSTREAM *client_stream)
+{
+ int request_flags;
+ DICT *dict;
+ int request_func;
+ const char *reply_key;
+ const char *reply_value;
+ int dict_status;
+ int reply_status;
+
+ /*
+ * Process the request.
+ */
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+ RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func),
+ ATTR_TYPE_END) != 3
+ || (request_func != DICT_SEQ_FUN_FIRST
+ && request_func != DICT_SEQ_FUN_NEXT)) {
+ reply_status = PROXY_STAT_BAD;
+ reply_key = reply_value = "";
+ } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+ &reply_status)) == 0) {
+ reply_key = reply_value = "";
+ } else {
+ dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
+ | (request_flags & DICT_FLAG_RQST_MASK));
+ dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
+ if (dict_status == 0) {
+ reply_status = PROXY_STAT_OK;
+ } else if (dict->error == 0) {
+ reply_status = PROXY_STAT_NOKEY;
+ reply_key = reply_value = "";
+ } else {
+ reply_status = (dict->error == DICT_ERR_RETRY ?
+ PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
+ reply_key = reply_value = "";
+ }
+ }
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+ SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key),
+ SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
+ ATTR_TYPE_END);
+}
+
+/* proxymap_lookup_service - remote lookup service */
+
+static void proxymap_lookup_service(VSTREAM *client_stream)
+{
+ int request_flags;
+ DICT *dict;
+ const char *reply_value;
+ int reply_status;
+
+ /*
+ * Process the request.
+ */
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+ RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
+ ATTR_TYPE_END) != 3) {
+ reply_status = PROXY_STAT_BAD;
+ reply_value = "";
+ } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+ &reply_status)) == 0) {
+ reply_value = "";
+ } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
+ | (request_flags & DICT_FLAG_RQST_MASK)),
+ (reply_value = dict_get(dict, STR(request_key))) != 0) {
+ reply_status = PROXY_STAT_OK;
+ } else if (dict->error == 0) {
+ reply_status = PROXY_STAT_NOKEY;
+ reply_value = "";
+ } else {
+ reply_status = (dict->error == DICT_ERR_RETRY ?
+ PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
+ reply_value = "";
+ }
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+ SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
+ ATTR_TYPE_END);
+}
+
+/* proxymap_update_service - remote update service */
+
+static void proxymap_update_service(VSTREAM *client_stream)
+{
+ int request_flags;
+ DICT *dict;
+ int dict_status;
+ int reply_status;
+
+ /*
+ * Process the request.
+ *
+ * XXX We don't close maps, so we must turn on synchronous update to ensure
+ * that the on-disk data is in a consistent state between updates.
+ *
+ * XXX We ignore duplicates, because the proxymap server would abort
+ * otherwise.
+ */
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+ RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
+ RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value),
+ ATTR_TYPE_END) != 4) {
+ reply_status = PROXY_STAT_BAD;
+ } else if (proxy_writer == 0) {
+ msg_warn("refusing %s update request on non-%s service",
+ STR(request_map), MAIL_SERVICE_PROXYWRITE);
+ reply_status = PROXY_STAT_DENY;
+ } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+ &reply_status)) == 0) {
+ /* void */ ;
+ } else {
+ dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
+ | (request_flags & DICT_FLAG_RQST_MASK)
+ | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
+ dict_status = dict_put(dict, STR(request_key), STR(request_value));
+ if (dict_status == 0) {
+ reply_status = PROXY_STAT_OK;
+ } else if (dict->error == 0) {
+ reply_status = PROXY_STAT_NOKEY;
+ } else {
+ reply_status = (dict->error == DICT_ERR_RETRY ?
+ PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
+ }
+ }
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+ ATTR_TYPE_END);
+}
+
+/* proxymap_delete_service - remote delete service */
+
+static void proxymap_delete_service(VSTREAM *client_stream)
+{
+ int request_flags;
+ DICT *dict;
+ int dict_status;
+ int reply_status;
+
+ /*
+ * Process the request.
+ *
+ * XXX We don't close maps, so we must turn on synchronous update to ensure
+ * that the on-disk data is in a consistent state between updates.
+ */
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+ RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
+ ATTR_TYPE_END) != 3) {
+ reply_status = PROXY_STAT_BAD;
+ } else if (proxy_writer == 0) {
+ msg_warn("refusing %s delete request on non-%s service",
+ STR(request_map), MAIL_SERVICE_PROXYWRITE);
+ reply_status = PROXY_STAT_DENY;
+ } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+ &reply_status)) == 0) {
+ /* void */ ;
+ } else {
+ dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
+ | (request_flags & DICT_FLAG_RQST_MASK)
+ | DICT_FLAG_SYNC_UPDATE);
+ dict_status = dict_del(dict, STR(request_key));
+ if (dict_status == 0) {
+ reply_status = PROXY_STAT_OK;
+ } else if (dict->error == 0) {
+ reply_status = PROXY_STAT_NOKEY;
+ } else {
+ reply_status = (dict->error == DICT_ERR_RETRY ?
+ PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
+ }
+ }
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+ ATTR_TYPE_END);
+}
+
+/* proxymap_open_service - open remote lookup table */
+
+static void proxymap_open_service(VSTREAM *client_stream)
+{
+ int request_flags;
+ DICT *dict;
+ int reply_status;
+ int reply_flags;
+
+ /*
+ * Process the request.
+ */
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+ ATTR_TYPE_END) != 2) {
+ reply_status = PROXY_STAT_BAD;
+ reply_flags = 0;
+ } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+ &reply_status)) == 0) {
+ reply_flags = 0;
+ } else {
+ reply_status = PROXY_STAT_OK;
+ reply_flags = dict->flags;
+ }
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags),
+ ATTR_TYPE_END);
+}
+
+/* proxymap_service - perform service for client */
+
+static void proxymap_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Deadline enforcement.
+ */
+ if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0)
+ vstream_control(client_stream,
+ CA_VSTREAM_CTL_TIMEOUT(1),
+ CA_VSTREAM_CTL_END);
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the proxymap service. All connection-management stuff is handled by
+ * the common code in multi_server.c.
+ */
+ vstream_control(client_stream,
+ CA_VSTREAM_CTL_START_DEADLINE,
+ CA_VSTREAM_CTL_END);
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, request),
+ ATTR_TYPE_END) == 1) {
+ if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
+ proxymap_lookup_service(client_stream);
+ } else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
+ proxymap_update_service(client_stream);
+ } else if (VSTREQ(request, PROXY_REQ_DELETE)) {
+ proxymap_delete_service(client_stream);
+ } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) {
+ proxymap_sequence_service(client_stream);
+ } else if (VSTREQ(request, PROXY_REQ_OPEN)) {
+ proxymap_open_service(client_stream);
+ } else {
+ msg_warn("unrecognized request: \"%s\", ignored", STR(request));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD),
+ ATTR_TYPE_END);
+ }
+ }
+ vstream_control(client_stream,
+ CA_VSTREAM_CTL_START_DEADLINE,
+ CA_VSTREAM_CTL_END);
+ vstream_fflush(client_stream);
+}
+
+/* dict_proxy_open - intercept remote map request from inside library */
+
+DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags)
+{
+ if (msg_verbose)
+ msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine",
+ map, open_flags, dict_flags);
+ while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0)
+ map += PROXY_COLON_LEN;
+ return (dict_open(map, open_flags, dict_flags));
+}
+
+/* authorize_proxied_maps - recursively authorize maps */
+
+static void authorize_proxied_maps(char *bp)
+{
+ const char *sep = CHARS_COMMA_SP;
+ const char *parens = CHARS_BRACE;
+ char *type_name;
+
+ while ((type_name = mystrtokq(&bp, sep, parens)) != 0) {
+ char *nested_info;
+
+ /* Maybe { maptype:mapname attr=value... } */
+ if (*type_name == parens[0]) {
+ char *err;
+
+ /* Warn about blatant syntax error. */
+ if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) {
+ msg_warn("bad %s parameter value: %s",
+ PROXY_MAP_PARAM_NAME(proxy_writer), err);
+ myfree(err);
+ continue;
+ }
+ /* Don't try to second-guess the semantics of { }. */
+ if ((type_name = mystrtokq(&type_name, sep, parens)) == 0)
+ continue;
+ }
+ /* Recurse into nested map (pipemap, unionmap). */
+ if ((nested_info = get_nested_dict_name(type_name)) != 0) {
+ char *err;
+
+ if (*nested_info != parens[0])
+ continue;
+ /* Warn about blatant syntax error. */
+ if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) {
+ msg_warn("bad %s parameter value: %s",
+ PROXY_MAP_PARAM_NAME(proxy_writer), err);
+ myfree(err);
+ continue;
+ }
+ authorize_proxied_maps(nested_info);
+ continue;
+ }
+ if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
+ continue;
+ do {
+ type_name += PROXY_COLON_LEN;
+ } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
+ if (strchr(type_name, ':') != 0
+ && htable_locate(proxy_auth_maps, type_name) == 0) {
+ (void) htable_enter(proxy_auth_maps, type_name, (void *) 0);
+ if (msg_verbose)
+ msg_info("allowlisting %s from %s", type_name,
+ PROXY_MAP_PARAM_NAME(proxy_writer));
+ }
+ }
+}
+
+/* post_jail_init - initialization after privilege drop */
+
+static void post_jail_init(char *service_name, char **unused_argv)
+{
+ char *saved_filter;
+
+ /*
+ * Are we proxy writer?
+ */
+ if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
+ proxy_writer = 1;
+ else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
+ msg_fatal("service name must be one of %s or %s",
+ MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP);
+
+ /*
+ * Pre-allocate buffers.
+ */
+ request = vstring_alloc(10);
+ request_map = vstring_alloc(10);
+ request_key = vstring_alloc(10);
+ request_value = vstring_alloc(10);
+ map_type_name_flags = vstring_alloc(10);
+
+ /*
+ * Prepare the pre-approved list of proxied tables.
+ */
+ saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps :
+ var_proxy_read_maps);
+ proxy_auth_maps = htable_create(13);
+ authorize_proxied_maps(saved_filter);
+ myfree(saved_filter);
+
+ /*
+ * Never, ever, get killed by a master signal, as that could corrupt a
+ * persistent database when we're in the middle of an update.
+ */
+ if (proxy_writer != 0)
+ setsid();
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ const char *table;
+
+ if (proxy_writer == 0 && (table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+}
+
+/* post_accept - announce our protocol name */
+
+static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv,
+ HTABLE *unused_attr)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
+ VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
+ VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
+ 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_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
+ VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
+ VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
+ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
+ VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
+ /* The following two must be last for $mapname to work as expected. */
+ VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
+ VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, proxymap_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_POST_ACCEPT(post_accept),
+ /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */
+ 0);
+}
diff --git a/src/qmgr/.indent.pro b/src/qmgr/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/qmgr/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/qmgr/.printfck b/src/qmgr/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/qmgr/.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/qmgr/Makefile.in b/src/qmgr/Makefile.in
new file mode 100644
index 0000000..79d92b2
--- /dev/null
+++ b/src/qmgr/Makefile.in
@@ -0,0 +1,390 @@
+SHELL = /bin/sh
+SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \
+ qmgr_message.c qmgr_deliver.c qmgr_move.c \
+ qmgr_job.c qmgr_peer.c \
+ qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c qmgr_error.c \
+ qmgr_feedback.c
+OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \
+ qmgr_message.o qmgr_deliver.o qmgr_move.o \
+ qmgr_job.o qmgr_peer.o \
+ qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o qmgr_error.o \
+ qmgr_feedback.o
+HDRS = qmgr.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = qmgr
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec/$(PROG)
+
+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
+
+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'
+qmgr.o: ../../include/argv.h
+qmgr.o: ../../include/attr.h
+qmgr.o: ../../include/check_arg.h
+qmgr.o: ../../include/dict.h
+qmgr.o: ../../include/dsn.h
+qmgr.o: ../../include/events.h
+qmgr.o: ../../include/flush_clnt.h
+qmgr.o: ../../include/htable.h
+qmgr.o: ../../include/iostuff.h
+qmgr.o: ../../include/mail_conf.h
+qmgr.o: ../../include/mail_flow.h
+qmgr.o: ../../include/mail_params.h
+qmgr.o: ../../include/mail_proto.h
+qmgr.o: ../../include/mail_queue.h
+qmgr.o: ../../include/mail_server.h
+qmgr.o: ../../include/mail_version.h
+qmgr.o: ../../include/master_proto.h
+qmgr.o: ../../include/msg.h
+qmgr.o: ../../include/myflock.h
+qmgr.o: ../../include/mymalloc.h
+qmgr.o: ../../include/nvtable.h
+qmgr.o: ../../include/recipient_list.h
+qmgr.o: ../../include/scan_dir.h
+qmgr.o: ../../include/sys_defs.h
+qmgr.o: ../../include/vbuf.h
+qmgr.o: ../../include/vstream.h
+qmgr.o: ../../include/vstring.h
+qmgr.o: qmgr.c
+qmgr.o: qmgr.h
+qmgr_active.o: ../../include/abounce.h
+qmgr_active.o: ../../include/attr.h
+qmgr_active.o: ../../include/bounce.h
+qmgr_active.o: ../../include/check_arg.h
+qmgr_active.o: ../../include/defer.h
+qmgr_active.o: ../../include/deliver_request.h
+qmgr_active.o: ../../include/dsn.h
+qmgr_active.o: ../../include/dsn_buf.h
+qmgr_active.o: ../../include/dsn_mask.h
+qmgr_active.o: ../../include/events.h
+qmgr_active.o: ../../include/htable.h
+qmgr_active.o: ../../include/info_log_addr_form.h
+qmgr_active.o: ../../include/mail_open_ok.h
+qmgr_active.o: ../../include/mail_params.h
+qmgr_active.o: ../../include/mail_queue.h
+qmgr_active.o: ../../include/msg.h
+qmgr_active.o: ../../include/msg_stats.h
+qmgr_active.o: ../../include/mymalloc.h
+qmgr_active.o: ../../include/nvtable.h
+qmgr_active.o: ../../include/qmgr_user.h
+qmgr_active.o: ../../include/rec_type.h
+qmgr_active.o: ../../include/recipient_list.h
+qmgr_active.o: ../../include/scan_dir.h
+qmgr_active.o: ../../include/sys_defs.h
+qmgr_active.o: ../../include/trace.h
+qmgr_active.o: ../../include/vbuf.h
+qmgr_active.o: ../../include/vstream.h
+qmgr_active.o: ../../include/vstring.h
+qmgr_active.o: ../../include/warn_stat.h
+qmgr_active.o: qmgr.h
+qmgr_active.o: qmgr_active.c
+qmgr_bounce.o: ../../include/attr.h
+qmgr_bounce.o: ../../include/bounce.h
+qmgr_bounce.o: ../../include/check_arg.h
+qmgr_bounce.o: ../../include/deliver_completed.h
+qmgr_bounce.o: ../../include/deliver_request.h
+qmgr_bounce.o: ../../include/dsn.h
+qmgr_bounce.o: ../../include/dsn_buf.h
+qmgr_bounce.o: ../../include/htable.h
+qmgr_bounce.o: ../../include/msg_stats.h
+qmgr_bounce.o: ../../include/mymalloc.h
+qmgr_bounce.o: ../../include/nvtable.h
+qmgr_bounce.o: ../../include/recipient_list.h
+qmgr_bounce.o: ../../include/scan_dir.h
+qmgr_bounce.o: ../../include/sys_defs.h
+qmgr_bounce.o: ../../include/vbuf.h
+qmgr_bounce.o: ../../include/vstream.h
+qmgr_bounce.o: ../../include/vstring.h
+qmgr_bounce.o: qmgr.h
+qmgr_bounce.o: qmgr_bounce.c
+qmgr_defer.o: ../../include/attr.h
+qmgr_defer.o: ../../include/bounce.h
+qmgr_defer.o: ../../include/check_arg.h
+qmgr_defer.o: ../../include/defer.h
+qmgr_defer.o: ../../include/deliver_request.h
+qmgr_defer.o: ../../include/dsn.h
+qmgr_defer.o: ../../include/dsn_buf.h
+qmgr_defer.o: ../../include/htable.h
+qmgr_defer.o: ../../include/iostuff.h
+qmgr_defer.o: ../../include/mail_proto.h
+qmgr_defer.o: ../../include/msg.h
+qmgr_defer.o: ../../include/msg_stats.h
+qmgr_defer.o: ../../include/mymalloc.h
+qmgr_defer.o: ../../include/nvtable.h
+qmgr_defer.o: ../../include/recipient_list.h
+qmgr_defer.o: ../../include/scan_dir.h
+qmgr_defer.o: ../../include/sys_defs.h
+qmgr_defer.o: ../../include/vbuf.h
+qmgr_defer.o: ../../include/vstream.h
+qmgr_defer.o: ../../include/vstring.h
+qmgr_defer.o: qmgr.h
+qmgr_defer.o: qmgr_defer.c
+qmgr_deliver.o: ../../include/attr.h
+qmgr_deliver.o: ../../include/check_arg.h
+qmgr_deliver.o: ../../include/deliver_request.h
+qmgr_deliver.o: ../../include/dsb_scan.h
+qmgr_deliver.o: ../../include/dsn.h
+qmgr_deliver.o: ../../include/dsn_buf.h
+qmgr_deliver.o: ../../include/dsn_util.h
+qmgr_deliver.o: ../../include/events.h
+qmgr_deliver.o: ../../include/htable.h
+qmgr_deliver.o: ../../include/iostuff.h
+qmgr_deliver.o: ../../include/mail_params.h
+qmgr_deliver.o: ../../include/mail_proto.h
+qmgr_deliver.o: ../../include/mail_queue.h
+qmgr_deliver.o: ../../include/msg.h
+qmgr_deliver.o: ../../include/msg_stats.h
+qmgr_deliver.o: ../../include/mymalloc.h
+qmgr_deliver.o: ../../include/nvtable.h
+qmgr_deliver.o: ../../include/rcpt_print.h
+qmgr_deliver.o: ../../include/recipient_list.h
+qmgr_deliver.o: ../../include/scan_dir.h
+qmgr_deliver.o: ../../include/smtputf8.h
+qmgr_deliver.o: ../../include/stringops.h
+qmgr_deliver.o: ../../include/sys_defs.h
+qmgr_deliver.o: ../../include/vbuf.h
+qmgr_deliver.o: ../../include/verp_sender.h
+qmgr_deliver.o: ../../include/vstream.h
+qmgr_deliver.o: ../../include/vstring.h
+qmgr_deliver.o: ../../include/vstring_vstream.h
+qmgr_deliver.o: qmgr.h
+qmgr_deliver.o: qmgr_deliver.c
+qmgr_enable.o: ../../include/check_arg.h
+qmgr_enable.o: ../../include/dsn.h
+qmgr_enable.o: ../../include/msg.h
+qmgr_enable.o: ../../include/recipient_list.h
+qmgr_enable.o: ../../include/scan_dir.h
+qmgr_enable.o: ../../include/sys_defs.h
+qmgr_enable.o: ../../include/vbuf.h
+qmgr_enable.o: ../../include/vstream.h
+qmgr_enable.o: qmgr.h
+qmgr_enable.o: qmgr_enable.c
+qmgr_entry.o: ../../include/attr.h
+qmgr_entry.o: ../../include/check_arg.h
+qmgr_entry.o: ../../include/deliver_request.h
+qmgr_entry.o: ../../include/dsn.h
+qmgr_entry.o: ../../include/events.h
+qmgr_entry.o: ../../include/htable.h
+qmgr_entry.o: ../../include/mail_params.h
+qmgr_entry.o: ../../include/msg.h
+qmgr_entry.o: ../../include/msg_stats.h
+qmgr_entry.o: ../../include/mymalloc.h
+qmgr_entry.o: ../../include/nvtable.h
+qmgr_entry.o: ../../include/recipient_list.h
+qmgr_entry.o: ../../include/scan_dir.h
+qmgr_entry.o: ../../include/sys_defs.h
+qmgr_entry.o: ../../include/vbuf.h
+qmgr_entry.o: ../../include/vstream.h
+qmgr_entry.o: ../../include/vstring.h
+qmgr_entry.o: qmgr.h
+qmgr_entry.o: qmgr_entry.c
+qmgr_error.o: ../../include/check_arg.h
+qmgr_error.o: ../../include/dsn.h
+qmgr_error.o: ../../include/mymalloc.h
+qmgr_error.o: ../../include/recipient_list.h
+qmgr_error.o: ../../include/scan_dir.h
+qmgr_error.o: ../../include/stringops.h
+qmgr_error.o: ../../include/sys_defs.h
+qmgr_error.o: ../../include/vbuf.h
+qmgr_error.o: ../../include/vstream.h
+qmgr_error.o: ../../include/vstring.h
+qmgr_error.o: qmgr.h
+qmgr_error.o: qmgr_error.c
+qmgr_feedback.o: ../../include/check_arg.h
+qmgr_feedback.o: ../../include/dsn.h
+qmgr_feedback.o: ../../include/mail_conf.h
+qmgr_feedback.o: ../../include/mail_params.h
+qmgr_feedback.o: ../../include/msg.h
+qmgr_feedback.o: ../../include/mymalloc.h
+qmgr_feedback.o: ../../include/name_code.h
+qmgr_feedback.o: ../../include/recipient_list.h
+qmgr_feedback.o: ../../include/scan_dir.h
+qmgr_feedback.o: ../../include/stringops.h
+qmgr_feedback.o: ../../include/sys_defs.h
+qmgr_feedback.o: ../../include/vbuf.h
+qmgr_feedback.o: ../../include/vstream.h
+qmgr_feedback.o: ../../include/vstring.h
+qmgr_feedback.o: qmgr.h
+qmgr_feedback.o: qmgr_feedback.c
+qmgr_job.o: ../../include/check_arg.h
+qmgr_job.o: ../../include/dsn.h
+qmgr_job.o: ../../include/htable.h
+qmgr_job.o: ../../include/msg.h
+qmgr_job.o: ../../include/mymalloc.h
+qmgr_job.o: ../../include/recipient_list.h
+qmgr_job.o: ../../include/sane_time.h
+qmgr_job.o: ../../include/scan_dir.h
+qmgr_job.o: ../../include/sys_defs.h
+qmgr_job.o: ../../include/vbuf.h
+qmgr_job.o: ../../include/vstream.h
+qmgr_job.o: qmgr.h
+qmgr_job.o: qmgr_job.c
+qmgr_message.o: ../../include/argv.h
+qmgr_message.o: ../../include/attr.h
+qmgr_message.o: ../../include/bounce.h
+qmgr_message.o: ../../include/canon_addr.h
+qmgr_message.o: ../../include/check_arg.h
+qmgr_message.o: ../../include/deliver_completed.h
+qmgr_message.o: ../../include/deliver_request.h
+qmgr_message.o: ../../include/dict.h
+qmgr_message.o: ../../include/dsn.h
+qmgr_message.o: ../../include/dsn_buf.h
+qmgr_message.o: ../../include/dsn_mask.h
+qmgr_message.o: ../../include/htable.h
+qmgr_message.o: ../../include/iostuff.h
+qmgr_message.o: ../../include/mail_params.h
+qmgr_message.o: ../../include/mail_proto.h
+qmgr_message.o: ../../include/mail_queue.h
+qmgr_message.o: ../../include/msg.h
+qmgr_message.o: ../../include/msg_stats.h
+qmgr_message.o: ../../include/myflock.h
+qmgr_message.o: ../../include/mymalloc.h
+qmgr_message.o: ../../include/nvtable.h
+qmgr_message.o: ../../include/opened.h
+qmgr_message.o: ../../include/qmgr_user.h
+qmgr_message.o: ../../include/rec_attr_map.h
+qmgr_message.o: ../../include/rec_type.h
+qmgr_message.o: ../../include/recipient_list.h
+qmgr_message.o: ../../include/record.h
+qmgr_message.o: ../../include/resolve_clnt.h
+qmgr_message.o: ../../include/rewrite_clnt.h
+qmgr_message.o: ../../include/sane_time.h
+qmgr_message.o: ../../include/scan_dir.h
+qmgr_message.o: ../../include/sent.h
+qmgr_message.o: ../../include/split_addr.h
+qmgr_message.o: ../../include/split_at.h
+qmgr_message.o: ../../include/stringops.h
+qmgr_message.o: ../../include/sys_defs.h
+qmgr_message.o: ../../include/valid_hostname.h
+qmgr_message.o: ../../include/vbuf.h
+qmgr_message.o: ../../include/verp_sender.h
+qmgr_message.o: ../../include/vstream.h
+qmgr_message.o: ../../include/vstring.h
+qmgr_message.o: qmgr.h
+qmgr_message.o: qmgr_message.c
+qmgr_move.o: ../../include/check_arg.h
+qmgr_move.o: ../../include/dsn.h
+qmgr_move.o: ../../include/mail_queue.h
+qmgr_move.o: ../../include/mail_scan_dir.h
+qmgr_move.o: ../../include/msg.h
+qmgr_move.o: ../../include/recipient_list.h
+qmgr_move.o: ../../include/scan_dir.h
+qmgr_move.o: ../../include/sys_defs.h
+qmgr_move.o: ../../include/vbuf.h
+qmgr_move.o: ../../include/vstream.h
+qmgr_move.o: ../../include/vstring.h
+qmgr_move.o: qmgr.h
+qmgr_move.o: qmgr_move.c
+qmgr_peer.o: ../../include/check_arg.h
+qmgr_peer.o: ../../include/dsn.h
+qmgr_peer.o: ../../include/htable.h
+qmgr_peer.o: ../../include/msg.h
+qmgr_peer.o: ../../include/mymalloc.h
+qmgr_peer.o: ../../include/recipient_list.h
+qmgr_peer.o: ../../include/scan_dir.h
+qmgr_peer.o: ../../include/sys_defs.h
+qmgr_peer.o: ../../include/vbuf.h
+qmgr_peer.o: ../../include/vstream.h
+qmgr_peer.o: qmgr.h
+qmgr_peer.o: qmgr_peer.c
+qmgr_queue.o: ../../include/attr.h
+qmgr_queue.o: ../../include/check_arg.h
+qmgr_queue.o: ../../include/dsn.h
+qmgr_queue.o: ../../include/events.h
+qmgr_queue.o: ../../include/htable.h
+qmgr_queue.o: ../../include/iostuff.h
+qmgr_queue.o: ../../include/mail_params.h
+qmgr_queue.o: ../../include/mail_proto.h
+qmgr_queue.o: ../../include/msg.h
+qmgr_queue.o: ../../include/mymalloc.h
+qmgr_queue.o: ../../include/nvtable.h
+qmgr_queue.o: ../../include/recipient_list.h
+qmgr_queue.o: ../../include/scan_dir.h
+qmgr_queue.o: ../../include/sys_defs.h
+qmgr_queue.o: ../../include/vbuf.h
+qmgr_queue.o: ../../include/vstream.h
+qmgr_queue.o: ../../include/vstring.h
+qmgr_queue.o: qmgr.h
+qmgr_queue.o: qmgr_queue.c
+qmgr_scan.o: ../../include/check_arg.h
+qmgr_scan.o: ../../include/dsn.h
+qmgr_scan.o: ../../include/mail_scan_dir.h
+qmgr_scan.o: ../../include/msg.h
+qmgr_scan.o: ../../include/mymalloc.h
+qmgr_scan.o: ../../include/recipient_list.h
+qmgr_scan.o: ../../include/scan_dir.h
+qmgr_scan.o: ../../include/sys_defs.h
+qmgr_scan.o: ../../include/vbuf.h
+qmgr_scan.o: ../../include/vstream.h
+qmgr_scan.o: qmgr.h
+qmgr_scan.o: qmgr_scan.c
+qmgr_transport.o: ../../include/attr.h
+qmgr_transport.o: ../../include/check_arg.h
+qmgr_transport.o: ../../include/dsn.h
+qmgr_transport.o: ../../include/events.h
+qmgr_transport.o: ../../include/htable.h
+qmgr_transport.o: ../../include/iostuff.h
+qmgr_transport.o: ../../include/mail_conf.h
+qmgr_transport.o: ../../include/mail_params.h
+qmgr_transport.o: ../../include/mail_proto.h
+qmgr_transport.o: ../../include/msg.h
+qmgr_transport.o: ../../include/mymalloc.h
+qmgr_transport.o: ../../include/nvtable.h
+qmgr_transport.o: ../../include/recipient_list.h
+qmgr_transport.o: ../../include/scan_dir.h
+qmgr_transport.o: ../../include/sys_defs.h
+qmgr_transport.o: ../../include/vbuf.h
+qmgr_transport.o: ../../include/vstream.h
+qmgr_transport.o: ../../include/vstring.h
+qmgr_transport.o: qmgr.h
+qmgr_transport.o: qmgr_transport.c
diff --git a/src/qmgr/qmgr.c b/src/qmgr/qmgr.c
new file mode 100644
index 0000000..9d90a6e
--- /dev/null
+++ b/src/qmgr/qmgr.c
@@ -0,0 +1,827 @@
+/*++
+/* NAME
+/* qmgr 8
+/* SUMMARY
+/* Postfix queue manager
+/* SYNOPSIS
+/* \fBqmgr\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBqmgr\fR(8) daemon awaits the arrival of incoming mail
+/* and arranges for its delivery via Postfix delivery processes.
+/* The actual mail routing strategy is delegated to the
+/* \fBtrivial-rewrite\fR(8) daemon.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* Mail addressed to the local \fBdouble-bounce\fR address is
+/* logged and discarded. This stops potential loops caused by
+/* undeliverable bounce notifications.
+/* MAIL QUEUES
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon maintains the following queues:
+/* .IP \fBincoming\fR
+/* Inbound mail from the network, or mail picked up by the
+/* local \fBpickup\fR(8) daemon from the \fBmaildrop\fR directory.
+/* .IP \fBactive\fR
+/* Messages that the queue manager has opened for delivery. Only
+/* a limited number of messages is allowed to enter the \fBactive\fR
+/* queue (leaky bucket strategy, for a fixed delivery rate).
+/* .IP \fBdeferred\fR
+/* Mail that could not be delivered upon the first attempt. The queue
+/* manager implements exponential backoff by doubling the time between
+/* delivery attempts.
+/* .IP \fBcorrupt\fR
+/* Unreadable or damaged queue files are moved here for inspection.
+/* .IP \fBhold\fR
+/* Messages that are kept "on hold" are kept here until someone
+/* sets them free.
+/* DELIVERY STATUS REPORTS
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon keeps an eye on per-message delivery status
+/* reports in the following directories. Each status report file has
+/* the same name as the corresponding message file:
+/* .IP \fBbounce\fR
+/* Per-recipient status information about why mail is bounced.
+/* These files are maintained by the \fBbounce\fR(8) daemon.
+/* .IP \fBdefer\fR
+/* Per-recipient status information about why mail is delayed.
+/* These files are maintained by the \fBdefer\fR(8) daemon.
+/* .IP \fBtrace\fR
+/* Per-recipient status information as requested with the
+/* Postfix "\fBsendmail -v\fR" or "\fBsendmail -bv\fR" command.
+/* These files are maintained by the \fBtrace\fR(8) daemon.
+/* .PP
+/* The \fBqmgr\fR(8) daemon is responsible for asking the
+/* \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemons to
+/* send delivery reports.
+/* STRATEGIES
+/* .ad
+/* .fi
+/* The queue manager implements a variety of strategies for
+/* either opening queue files (input) or for message delivery (output).
+/* .IP "\fBleaky bucket\fR"
+/* This strategy limits the number of messages in the \fBactive\fR queue
+/* and prevents the queue manager from running out of memory under
+/* heavy load.
+/* .IP \fBfairness\fR
+/* When the \fBactive\fR queue has room, the queue manager takes one
+/* message from the \fBincoming\fR queue and one from the \fBdeferred\fR
+/* queue. This prevents a large mail backlog from blocking the delivery
+/* of new mail.
+/* .IP "\fBslow start\fR"
+/* This strategy eliminates "thundering herd" problems by slowly
+/* adjusting the number of parallel deliveries to the same destination.
+/* .IP "\fBround robin\fR"
+/* The queue manager sorts delivery requests by destination.
+/* Round-robin selection prevents one destination from dominating
+/* deliveries to other destinations.
+/* .IP "\fBexponential backoff\fR"
+/* Mail that cannot be delivered upon the first attempt is deferred.
+/* The time interval between delivery attempts is doubled after each
+/* attempt.
+/* .IP "\fBdestination status cache\fR"
+/* The queue manager avoids unnecessary delivery attempts by
+/* maintaining a short-term, in-memory list of unreachable destinations.
+/* .IP "\fBpreemptive message scheduling\fR"
+/* The queue manager attempts to minimize the average per-recipient delay
+/* while still preserving the correct per-message delays, using
+/* a sophisticated preemptive message scheduling.
+/* TRIGGERS
+/* .ad
+/* .fi
+/* On an idle system, the queue manager waits for the arrival of
+/* trigger events, or it waits for a timer to go off. A trigger
+/* is a one-byte message.
+/* Depending on the message received, the queue manager performs
+/* one of the following actions (the message is followed by the
+/* symbolic constant used internally by the software):
+/* .IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR"
+/* Start a deferred queue scan. If a deferred queue scan is already
+/* in progress, that scan will be restarted as soon as it finishes.
+/* .IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR"
+/* Start an incoming queue scan. If an incoming queue scan is already
+/* in progress, that scan will be restarted as soon as it finishes.
+/* .IP "\fBA (QMGR_REQ_SCAN_ALL)\fR"
+/* Ignore deferred queue file time stamps. The request affects
+/* the next deferred queue scan.
+/* .IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR"
+/* Purge all information about dead transports and destinations.
+/* .IP "\fBW (TRIGGER_REQ_WAKEUP)\fR"
+/* Wakeup call, This is used by the master server to instantiate
+/* servers that should not go away forever. The action is to start
+/* an incoming queue scan.
+/* .PP
+/* The \fBqmgr\fR(8) daemon reads an entire buffer worth of triggers.
+/* Multiple identical trigger requests are collapsed into one, and
+/* trigger requests are sorted so that \fBA\fR and \fBF\fR precede
+/* \fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run,
+/* one would request \fBA F D\fR; in order to notify the queue manager
+/* of the arrival of new mail one would request \fBI\fR.
+/* STANDARDS
+/* RFC 3463 (Enhanced status codes)
+/* RFC 3464 (Delivery status notifications)
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBqmgr\fR(8) daemon is not security sensitive. It reads
+/* single-character messages from untrusted local users, and thus may
+/* be susceptible to denial of service attacks. The \fBqmgr\fR(8) daemon
+/* does not talk to the outside world, and it can be run at fixed low
+/* privilege in a chrooted environment.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are saved to the \fBcorrupt\fR queue
+/* for further inspection.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* BUGS
+/* A single queue manager process has to compete for disk access with
+/* multiple front-end processes such as \fBcleanup\fR(8). A sudden burst of
+/* inbound mail can negatively impact outbound delivery rates.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically
+/* as \fBqmgr\fR(8)
+/* is a persistent process. Use the "\fBpostfix reload\fR" command after
+/* a configuration change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/*
+/* In the text below, \fItransport\fR is the first field in a
+/* \fBmaster.cf\fR entry.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* Available before Postfix version 2.5:
+/* .IP "\fBallow_min_user (no)\fR"
+/* Allow a sender or recipient address to have `-' as the first
+/* character.
+/* .PP
+/* Available with Postfix version 2.7 and later:
+/* .IP "\fBdefault_filter_nexthop (empty)\fR"
+/* When a content_filter or FILTER request specifies no explicit
+/* next-hop destination, use $default_filter_nexthop instead; when
+/* that value is empty, use the domain in the recipient address.
+/* ACTIVE QUEUE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmgr_clog_warn_time (300s)\fR"
+/* The minimal delay between warnings that a specific destination is
+/* clogging up the Postfix active queue.
+/* .IP "\fBqmgr_message_active_limit (20000)\fR"
+/* The maximal number of messages in the active queue.
+/* .IP "\fBqmgr_message_recipient_limit (20000)\fR"
+/* The maximal number of recipients held in memory by the Postfix
+/* queue manager, and the maximal size of the short-term,
+/* in-memory "dead" destination status cache.
+/* .IP "\fBqmgr_message_recipient_minimum (10)\fR"
+/* The minimal number of in-memory recipients for any message.
+/* .IP "\fBdefault_recipient_limit (20000)\fR"
+/* The default per-transport upper limit on the number of in-memory
+/* recipients.
+/* .IP "\fBtransport_recipient_limit ($default_recipient_limit)\fR"
+/* A transport-specific override for the default_recipient_limit
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_extra_recipient_limit (1000)\fR"
+/* The default value for the extra per-transport limit imposed on the
+/* number of in-memory recipients.
+/* .IP "\fBtransport_extra_recipient_limit ($default_extra_recipient_limit)\fR"
+/* A transport-specific override for the default_extra_recipient_limit
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .PP
+/* Available in Postfix version 2.4 and later:
+/* .IP "\fBdefault_recipient_refill_limit (100)\fR"
+/* The default per-transport limit on the number of recipients refilled at
+/* once.
+/* .IP "\fBtransport_recipient_refill_limit ($default_recipient_refill_limit)\fR"
+/* A transport-specific override for the default_recipient_refill_limit
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_recipient_refill_delay (5s)\fR"
+/* The default per-transport maximum delay between recipients refills.
+/* .IP "\fBtransport_recipient_refill_delay ($default_recipient_refill_delay)\fR"
+/* A transport-specific override for the default_recipient_refill_delay
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* DELIVERY CONCURRENCY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBinitial_destination_concurrency (5)\fR"
+/* The initial per-destination concurrency level for parallel delivery
+/* to the same destination.
+/* .IP "\fBdefault_destination_concurrency_limit (20)\fR"
+/* The default maximal number of parallel deliveries to the same
+/* destination.
+/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBtransport_initial_destination_concurrency ($initial_destination_concurrency)\fR"
+/* A transport-specific override for the initial_destination_concurrency
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_destination_concurrency_failed_cohort_limit (1)\fR"
+/* How many pseudo-cohorts must suffer connection or handshake
+/* failure before a specific destination is considered unavailable
+/* (and further delivery is suspended).
+/* .IP "\fBtransport_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_failed_cohort_limit parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR"
+/* The per-destination amount of delivery concurrency negative
+/* feedback, after a delivery completes with a connection or handshake
+/* failure.
+/* .IP "\fBtransport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_negative_feedback parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR"
+/* The per-destination amount of delivery concurrency positive
+/* feedback, after a delivery completes without connection or handshake
+/* failure.
+/* .IP "\fBtransport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_positive_feedback parameter value,
+/* where \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBdestination_concurrency_feedback_debug (no)\fR"
+/* Make the queue manager's feedback algorithm verbose for performance
+/* analysis purposes.
+/* RECIPIENT SCHEDULING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdefault_destination_recipient_limit (50)\fR"
+/* The default maximal number of recipients per message delivery.
+/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_recipient_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* MESSAGE SCHEDULING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdefault_delivery_slot_cost (5)\fR"
+/* How often the Postfix queue manager's scheduler is allowed to
+/* preempt delivery of one message with another.
+/* .IP "\fBtransport_delivery_slot_cost ($default_delivery_slot_cost)\fR"
+/* A transport-specific override for the default_delivery_slot_cost
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_minimum_delivery_slots (3)\fR"
+/* How many recipients a message must have in order to invoke the
+/* Postfix queue manager's scheduling algorithm at all.
+/* .IP "\fBtransport_minimum_delivery_slots ($default_minimum_delivery_slots)\fR"
+/* A transport-specific override for the default_minimum_delivery_slots
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_delivery_slot_discount (50)\fR"
+/* The default value for transport-specific _delivery_slot_discount
+/* settings.
+/* .IP "\fBtransport_delivery_slot_discount ($default_delivery_slot_discount)\fR"
+/* A transport-specific override for the default_delivery_slot_discount
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .IP "\fBdefault_delivery_slot_loan (3)\fR"
+/* The default value for transport-specific _delivery_slot_loan
+/* settings.
+/* .IP "\fBtransport_delivery_slot_loan ($default_delivery_slot_loan)\fR"
+/* A transport-specific override for the default_delivery_slot_loan
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* OTHER RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBminimal_backoff_time (300s)\fR"
+/* The minimal time between attempts to deliver a deferred message;
+/* prior to Postfix 2.4 the default value was 1000s.
+/* .IP "\fBmaximal_backoff_time (4000s)\fR"
+/* The maximal time between attempts to deliver a deferred message.
+/* .IP "\fBmaximal_queue_lifetime (5d)\fR"
+/* Consider a message as undeliverable, when delivery fails with a
+/* temporary error, and the time in the queue has reached the
+/* maximal_queue_lifetime limit.
+/* .IP "\fBqueue_run_delay (300s)\fR"
+/* The time between deferred queue scans by the queue manager;
+/* prior to Postfix 2.4 the default value was 1000s.
+/* .IP "\fBtransport_retry_time (60s)\fR"
+/* The time between attempts by the Postfix queue manager to contact
+/* a malfunctioning message delivery transport.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBbounce_queue_lifetime (5d)\fR"
+/* Consider a bounce message as undeliverable, when delivery fails
+/* with a temporary error, and the time in the queue has reached the
+/* bounce_queue_lifetime limit.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBdefault_destination_rate_delay (0s)\fR"
+/* The default amount of delay that is inserted between individual
+/* message deliveries to the same destination and over the same message
+/* delivery transport.
+/* .IP "\fBtransport_destination_rate_delay ($default_destination_rate_delay)\fR"
+/* A transport-specific override for the default_destination_rate_delay
+/* parameter value, where \fItransport\fR is the master.cf name of
+/* the message delivery transport.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBdefault_transport_rate_delay (0s)\fR"
+/* The default amount of delay that is inserted between individual
+/* message deliveries over the same message delivery transport,
+/* regardless of destination.
+/* .IP "\fBtransport_transport_rate_delay ($default_transport_rate_delay)\fR"
+/* A transport-specific override for the default_transport_rate_delay
+/* parameter value, where the initial \fItransport\fR in the parameter
+/* name is the master.cf name of the message delivery transport.
+/* SAFETY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmgr_daemon_timeout (1000s)\fR"
+/* How much time a Postfix queue manager process may take to handle
+/* a request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBqmgr_ipc_timeout (60s)\fR"
+/* The time limit for the queue manager to send or receive information
+/* over an internal communication channel.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBaddress_verify_pending_request_limit (see 'postconf -d' output)\fR"
+/* A safety limit that prevents address verification requests from
+/* overwhelming the Postfix queue.
+/* 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 "\fBdefer_transports (empty)\fR"
+/* The names of message delivery transports that should not deliver mail
+/* unless someone issues "\fBsendmail -q\fR" or equivalent.
+/* .IP "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBhelpful_warnings (yes)\fR"
+/* Log warnings about problematic configuration settings, and provide
+/* helpful suggestions.
+/* .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 "\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 3.0 and later:
+/* .IP "\fBconfirm_delay_cleared (no)\fR"
+/* After sending a "your message is delayed" notification, inform
+/* the sender when the delay clears up.
+/* .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
+/* /var/spool/postfix/incoming, incoming queue
+/* /var/spool/postfix/active, active queue
+/* /var/spool/postfix/deferred, deferred queue
+/* /var/spool/postfix/bounce, non-delivery status
+/* /var/spool/postfix/defer, non-delivery status
+/* /var/spool/postfix/trace, delivery status
+/* SEE ALSO
+/* trivial-rewrite(8), address routing
+/* bounce(8), delivery status reports
+/* 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
+/* SCHEDULER_README, scheduling algorithm
+/* QSHAPE_README, Postfix queue analysis
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <vstream.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h> /* QMGR_SCAN constants */
+#include <mail_flow.h>
+#include <flush_clnt.h>
+
+/* Master process interface */
+
+#include <master_proto.h>
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Tunables.
+ */
+int var_queue_run_delay;
+int var_min_backoff_time;
+int var_max_backoff_time;
+int var_max_queue_time;
+int var_dsn_queue_time;
+int var_qmgr_active_limit;
+int var_qmgr_rcpt_limit;
+int var_qmgr_msg_rcpt_limit;
+int var_xport_rcpt_limit;
+int var_stack_rcpt_limit;
+int var_xport_refill_limit;
+int var_xport_refill_delay;
+int var_delivery_slot_cost;
+int var_delivery_slot_loan;
+int var_delivery_slot_discount;
+int var_min_delivery_slots;
+int var_init_dest_concurrency;
+int var_transport_retry_time;
+int var_dest_con_limit;
+int var_dest_rcpt_limit;
+char *var_defer_xports;
+int var_local_con_lim;
+int var_local_rcpt_lim;
+bool var_verp_bounce_off;
+int var_qmgr_clog_warn_time;
+char *var_conc_pos_feedback;
+char *var_conc_neg_feedback;
+int var_conc_cohort_limit;
+int var_conc_feedback_debug;
+int var_xport_rate_delay;
+int var_dest_rate_delay;
+char *var_def_filter_nexthop;
+int var_qmgr_daemon_timeout;
+int var_qmgr_ipc_timeout;
+int var_dsn_delay_cleared;
+int var_vrfy_pend_limit;
+
+static QMGR_SCAN *qmgr_scans[2];
+
+#define QMGR_SCAN_IDX_INCOMING 0
+#define QMGR_SCAN_IDX_DEFERRED 1
+#define QMGR_SCAN_IDX_COUNT (sizeof(qmgr_scans) / sizeof(qmgr_scans[0]))
+
+/* qmgr_deferred_run_event - queue manager heartbeat */
+
+static void qmgr_deferred_run_event(int unused_event, void *dummy)
+{
+
+ /*
+ * This routine runs when it is time for another deferred queue scan.
+ * Make sure this routine gets called again in the future.
+ */
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], QMGR_SCAN_START);
+ event_request_timer(qmgr_deferred_run_event, dummy, var_queue_run_delay);
+}
+
+/* qmgr_trigger_event - respond to external trigger(s) */
+
+static void qmgr_trigger_event(char *buf, ssize_t len,
+ char *unused_service, char **argv)
+{
+ int incoming_flag = 0;
+ int deferred_flag = 0;
+ int i;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Collapse identical requests that have arrived since we looked last
+ * time. There is no client feedback so there is no need to process each
+ * request in order. And as long as we don't have conflicting requests we
+ * are free to sort them into the most suitable order.
+ */
+#define QMGR_FLUSH_BEFORE (QMGR_FLUSH_ONCE | QMGR_FLUSH_DFXP)
+
+ for (i = 0; i < len; i++) {
+ if (msg_verbose)
+ msg_info("request: %d (%c)",
+ buf[i], ISALNUM(buf[i]) ? buf[i] : '?');
+ switch (buf[i]) {
+ case TRIGGER_REQ_WAKEUP:
+ case QMGR_REQ_SCAN_INCOMING:
+ incoming_flag |= QMGR_SCAN_START;
+ break;
+ case QMGR_REQ_SCAN_DEFERRED:
+ deferred_flag |= QMGR_SCAN_START;
+ break;
+ case QMGR_REQ_FLUSH_DEAD:
+ deferred_flag |= QMGR_FLUSH_BEFORE;
+ incoming_flag |= QMGR_FLUSH_BEFORE;
+ break;
+ case QMGR_REQ_SCAN_ALL:
+ deferred_flag |= QMGR_SCAN_ALL;
+ incoming_flag |= QMGR_SCAN_ALL;
+ break;
+ default:
+ if (msg_verbose)
+ msg_info("request ignored");
+ break;
+ }
+ }
+
+ /*
+ * Process each request type at most once. Modifiers take effect upon the
+ * next queue run. If no queue run is in progress, and a queue scan is
+ * requested, the request takes effect immediately.
+ */
+ if (incoming_flag != 0)
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], incoming_flag);
+ if (deferred_flag != 0)
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], deferred_flag);
+}
+
+/* qmgr_loop - queue manager main loop */
+
+static int qmgr_loop(char *unused_name, char **unused_argv)
+{
+ char *path;
+ ssize_t token_count;
+ int feed = 0;
+ int scan_idx; /* Priority order scan index */
+ static int first_scan_idx = QMGR_SCAN_IDX_INCOMING;
+ int last_scan_idx = QMGR_SCAN_IDX_COUNT - 1;
+ int delay;
+
+ /*
+ * This routine runs as part of the event handling loop, after the event
+ * manager has delivered a timer or I/O event (including the completion
+ * of a connection to a delivery process), or after it has waited for a
+ * specified amount of time. The result value of qmgr_loop() specifies
+ * how long the event manager should wait for the next event.
+ */
+#define DONT_WAIT 0
+#define WAIT_FOR_EVENT (-1)
+
+ /*
+ * Attempt to drain the active queue by allocating a suitable delivery
+ * process and by delivering mail via it. Delivery process allocation and
+ * mail delivery are asynchronous.
+ */
+ qmgr_active_drain();
+
+ /*
+ * Let some new blood into the active queue when the queue size is
+ * smaller than some configurable limit.
+ *
+ * We import one message per interrupt, to optimally tune the input count
+ * for the number of delivery agent protocol wait states, as explained in
+ * qmgr_transport.c.
+ */
+ delay = WAIT_FOR_EVENT;
+ for (scan_idx = 0; qmgr_message_count < var_qmgr_active_limit
+ && scan_idx < QMGR_SCAN_IDX_COUNT; ++scan_idx) {
+ last_scan_idx = (scan_idx + first_scan_idx) % QMGR_SCAN_IDX_COUNT;
+ if ((path = qmgr_scan_next(qmgr_scans[last_scan_idx])) != 0) {
+ delay = DONT_WAIT;
+ if ((feed = qmgr_active_feed(qmgr_scans[last_scan_idx], path)) != 0)
+ break;
+ }
+ }
+
+ /*
+ * Round-robin the queue scans. When the active queue becomes full,
+ * prefer new mail over deferred mail.
+ */
+ if (qmgr_message_count < var_qmgr_active_limit) {
+ first_scan_idx = (last_scan_idx + 1) % QMGR_SCAN_IDX_COUNT;
+ } else if (first_scan_idx != QMGR_SCAN_IDX_INCOMING) {
+ first_scan_idx = QMGR_SCAN_IDX_INCOMING;
+ }
+
+ /*
+ * Global flow control. If enabled, slow down receiving processes that
+ * get ahead of the queue manager, but don't block them completely.
+ */
+ if (var_in_flow_delay > 0) {
+ token_count = mail_flow_count();
+ if (token_count < var_proc_limit) {
+ if (feed != 0 && last_scan_idx == QMGR_SCAN_IDX_INCOMING)
+ mail_flow_put(1);
+ else if (qmgr_scans[QMGR_SCAN_IDX_INCOMING]->handle == 0)
+ mail_flow_put(var_proc_limit - token_count);
+ } else if (token_count > var_proc_limit) {
+ mail_flow_get(token_count - var_proc_limit);
+ }
+ }
+ return (delay);
+}
+
+/* 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);
+ }
+}
+
+/* qmgr_pre_init - pre-jail initialization */
+
+static void qmgr_pre_init(char *unused_name, char **unused_argv)
+{
+ flush_init();
+}
+
+/* qmgr_post_init - post-jail initialization */
+
+static void qmgr_post_init(char *name, char **unused_argv)
+{
+
+ /*
+ * Backwards compatibility.
+ */
+ if (strcmp(var_procname, "nqmgr") == 0) {
+ msg_warn("please update the %s/%s file; the new queue manager",
+ var_config_dir, MASTER_CONF_FILE);
+ msg_warn("(old name: nqmgr) has become the standard queue manager (new name: qmgr)");
+ msg_warn("support for the name old name (nqmgr) will be removed from Postfix");
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (var_qmgr_rcpt_limit < var_qmgr_active_limit) {
+ msg_warn("%s is smaller than %s - adjusting %s",
+ VAR_QMGR_RCPT_LIMIT, VAR_QMGR_ACT_LIMIT, VAR_QMGR_RCPT_LIMIT);
+ var_qmgr_rcpt_limit = var_qmgr_active_limit;
+ }
+ if (var_dsn_queue_time > var_max_queue_time) {
+ msg_warn("%s is larger than %s - adjusting %s",
+ VAR_DSN_QUEUE_TIME, VAR_MAX_QUEUE_TIME, VAR_DSN_QUEUE_TIME);
+ var_dsn_queue_time = var_max_queue_time;
+ }
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail.
+ * Prevent automatic process suicide after a limited number of client
+ * requests or after a limited amount of idle time. Move any left-over
+ * entries from the active queue to the incoming queue, and give them a
+ * time stamp into the future, in order to allow ongoing deliveries to
+ * finish first. Start scanning the incoming and deferred queues.
+ * Left-over active queue entries are moved to the incoming queue because
+ * the incoming queue has priority; moving left-overs to the deferred
+ * queue could cause anomalous delays when "postfix reload/start" are
+ * issued often. Override the IPC timeout (default 3600s) so that the
+ * queue manager can reset a broken IPC channel before the watchdog timer
+ * goes off.
+ */
+ var_ipc_timeout = var_qmgr_ipc_timeout;
+ var_use_limit = 0;
+ var_idle_limit = 0;
+ qmgr_move(MAIL_QUEUE_ACTIVE, MAIL_QUEUE_INCOMING, event_time());
+ qmgr_scans[QMGR_SCAN_IDX_INCOMING] = qmgr_scan_create(MAIL_QUEUE_INCOMING);
+ qmgr_scans[QMGR_SCAN_IDX_DEFERRED] = qmgr_scan_create(MAIL_QUEUE_DEFERRED);
+ qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], QMGR_SCAN_START);
+ qmgr_deferred_run_event(0, (void *) 0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_DEFER_XPORTS, DEF_DEFER_XPORTS, &var_defer_xports, 0, 0,
+ VAR_CONC_POS_FDBACK, DEF_CONC_POS_FDBACK, &var_conc_pos_feedback, 1, 0,
+ VAR_CONC_NEG_FDBACK, DEF_CONC_NEG_FDBACK, &var_conc_neg_feedback, 1, 0,
+ VAR_DEF_FILTER_NEXTHOP, DEF_DEF_FILTER_NEXTHOP, &var_def_filter_nexthop, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0,
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000,
+ VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0,
+ VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0,
+ VAR_XPORT_REFILL_DELAY, DEF_XPORT_REFILL_DELAY, &var_xport_refill_delay, 1, 0,
+ VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0,
+ VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0,
+ VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0,
+ VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
+ VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0,
+ VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0,
+ VAR_XPORT_RCPT_LIMIT, DEF_XPORT_RCPT_LIMIT, &var_xport_rcpt_limit, 0, 0,
+ VAR_STACK_RCPT_LIMIT, DEF_STACK_RCPT_LIMIT, &var_stack_rcpt_limit, 0, 0,
+ VAR_XPORT_REFILL_LIMIT, DEF_XPORT_REFILL_LIMIT, &var_xport_refill_limit, 1, 0,
+ VAR_DELIVERY_SLOT_COST, DEF_DELIVERY_SLOT_COST, &var_delivery_slot_cost, 0, 0,
+ VAR_DELIVERY_SLOT_LOAN, DEF_DELIVERY_SLOT_LOAN, &var_delivery_slot_loan, 0, 0,
+ VAR_DELIVERY_SLOT_DISCOUNT, DEF_DELIVERY_SLOT_DISCOUNT, &var_delivery_slot_discount, 0, 100,
+ VAR_MIN_DELIVERY_SLOTS, DEF_MIN_DELIVERY_SLOTS, &var_min_delivery_slots, 0, 0,
+ VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0,
+ VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0,
+ VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0,
+ VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0,
+ VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0,
+ VAR_CONC_COHORT_LIM, DEF_CONC_COHORT_LIM, &var_conc_cohort_limit, 0, 0,
+ VAR_VRFY_PEND_LIMIT, DEF_VRFY_PEND_LIMIT, &var_vrfy_pend_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off,
+ VAR_CONC_FDBACK_DEBUG, DEF_CONC_FDBACK_DEBUG, &var_conc_feedback_debug,
+ VAR_DSN_DELAY_CLEARED, DEF_DSN_DELAY_CLEARED, &var_dsn_delay_cleared,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Use the trigger service skeleton, because no-one else should be
+ * monitoring our service port while this process runs, and because we do
+ * not talk back to the client.
+ */
+ trigger_server_main(argc, argv, qmgr_trigger_event,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(qmgr_pre_init),
+ CA_MAIL_SERVER_POST_INIT(qmgr_post_init),
+ CA_MAIL_SERVER_LOOP(qmgr_loop),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_WATCHDOG(&var_qmgr_daemon_timeout),
+ 0);
+}
diff --git a/src/qmgr/qmgr.h b/src/qmgr/qmgr.h
new file mode 100644
index 0000000..4a205b4
--- /dev/null
+++ b/src/qmgr/qmgr.h
@@ -0,0 +1,546 @@
+/*++
+/* NAME
+/* qmgr 3h
+/* SUMMARY
+/* queue manager data structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <scan_dir.h>
+
+ /*
+ * Global library.
+ */
+#include <recipient_list.h>
+#include <dsn.h>
+
+ /*
+ * The queue manager is built around lots of mutually-referring structures.
+ * These typedefs save some typing.
+ */
+typedef struct QMGR_TRANSPORT QMGR_TRANSPORT;
+typedef struct QMGR_QUEUE QMGR_QUEUE;
+typedef struct QMGR_ENTRY QMGR_ENTRY;
+typedef struct QMGR_MESSAGE QMGR_MESSAGE;
+typedef struct QMGR_JOB QMGR_JOB;
+typedef struct QMGR_PEER QMGR_PEER;
+typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST;
+typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST;
+typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST;
+typedef struct QMGR_JOB_LIST QMGR_JOB_LIST;
+typedef struct QMGR_PEER_LIST QMGR_PEER_LIST;
+typedef struct QMGR_SCAN QMGR_SCAN;
+typedef struct QMGR_FEEDBACK QMGR_FEEDBACK;
+
+ /*
+ * Hairy macros to update doubly-linked lists.
+ */
+#define QMGR_LIST_ROTATE(head, object, peers) { \
+ head.next->peers.prev = head.prev; \
+ head.prev->peers.next = head.next; \
+ head.next = object->peers.next; \
+ head.next->peers.prev = 0; \
+ head.prev = object; \
+ object->peers.next = 0; \
+}
+
+#define QMGR_LIST_UNLINK(head, type, object, peers) { \
+ type _next = object->peers.next; \
+ type _prev = object->peers.prev; \
+ if (_prev) _prev->peers.next = _next; \
+ else head.next = _next; \
+ if (_next) _next->peers.prev = _prev; \
+ else head.prev = _prev; \
+ object->peers.next = object->peers.prev = 0; \
+}
+
+#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \
+ object->peers.prev = pred; \
+ object->peers.next = succ; \
+ if (pred) pred->peers.next = object; \
+ else head.next = object; \
+ if (succ) succ->peers.prev = object; \
+ else head.prev = object; \
+}
+
+#define QMGR_LIST_PREPEND(head, object, peers) { \
+ object->peers.next = head.next; \
+ object->peers.prev = 0; \
+ if (head.next) { \
+ head.next->peers.prev = object; \
+ } else { \
+ head.prev = object; \
+ } \
+ head.next = object; \
+}
+
+#define QMGR_LIST_APPEND(head, object, peers) { \
+ object->peers.prev = head.prev; \
+ object->peers.next = 0; \
+ if (head.prev) { \
+ head.prev->peers.next = object; \
+ } else { \
+ head.next = object; \
+ } \
+ head.prev = object; \
+}
+
+#define QMGR_LIST_INIT(head) { \
+ head.prev = 0; \
+ head.next = 0; \
+}
+
+ /*
+ * Transports are looked up by name (when we have resolved a message), or
+ * round-robin wise (when we want to distribute resources fairly).
+ */
+struct QMGR_TRANSPORT_LIST {
+ QMGR_TRANSPORT *next;
+ QMGR_TRANSPORT *prev;
+};
+
+extern struct HTABLE *qmgr_transport_byname; /* transport by name */
+extern QMGR_TRANSPORT_LIST qmgr_transport_list; /* transports, round robin */
+
+ /*
+ * Delivery agents provide feedback, as hints that Postfix should expend
+ * more or fewer resources on a specific destination domain. The main.cf
+ * file specifies how feedback affects delivery concurrency: add/subtract a
+ * constant, a ratio of constants, or a constant divided by the delivery
+ * concurrency; and it specifies how much feedback must accumulate between
+ * concurrency updates.
+ */
+struct QMGR_FEEDBACK {
+ int hysteresis; /* to pass, need to be this tall */
+ double base; /* pre-computed from main.cf */
+ int index; /* none, window, sqrt(window) */
+};
+
+#define QMGR_FEEDBACK_IDX_NONE 0 /* no window dependence */
+#define QMGR_FEEDBACK_IDX_WIN 1 /* 1/window dependence */
+#if 0
+#define QMGR_FEEDBACK_IDX_SQRT_WIN 2 /* 1/sqrt(window) dependence */
+#endif
+
+#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN
+#include <math.h>
+#endif
+
+extern void qmgr_feedback_init(QMGR_FEEDBACK *, const char *, const char *, const char *, const char *);
+
+#ifndef QMGR_FEEDBACK_IDX_SQRT_WIN
+#define QMGR_FEEDBACK_VAL(fb, win) \
+ ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : (fb).base / (win))
+#else
+#define QMGR_FEEDBACK_VAL(fb, win) \
+ ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : \
+ (fb).index == QMGR_FEEDBACK_IDX_WIN ? (fb).base / (win) : \
+ (fb).base / sqrt(win))
+#endif
+
+ /*
+ * Each transport (local, smtp-out, bounce) can have one queue per next hop
+ * name. Queues are looked up by next hop name (when we have resolved a
+ * message destination), or round-robin wise (when we want to deliver
+ * messages fairly).
+ */
+struct QMGR_QUEUE_LIST {
+ QMGR_QUEUE *next;
+ QMGR_QUEUE *prev;
+};
+
+struct QMGR_JOB_LIST {
+ QMGR_JOB *next;
+ QMGR_JOB *prev;
+};
+
+struct QMGR_TRANSPORT {
+ int flags; /* blocked, etc. */
+ int pending; /* incomplete DA connections */
+ char *name; /* transport name */
+ int dest_concurrency_limit; /* concurrency per domain */
+ int init_dest_concurrency; /* init. per-domain concurrency */
+ int recipient_limit; /* recipients per transaction */
+ int rcpt_per_stack; /* extra slots reserved for jobs put
+ * on the job stack */
+ int rcpt_unused; /* available in-core recipient slots */
+ int refill_limit; /* recipient batch size for message
+ * refill */
+ int refill_delay; /* delay before message refill */
+ int slot_cost; /* cost of new preemption slot (# of
+ * selected entries) */
+ int slot_loan; /* preemption boost offset and */
+ int slot_loan_factor; /* factor, see qmgr_job_preempt() */
+ int min_slots; /* when preemption can take effect at
+ * all */
+ struct HTABLE *queue_byname; /* queues indexed by domain */
+ QMGR_QUEUE_LIST queue_list; /* queues, round robin order */
+ struct HTABLE *job_byname; /* jobs indexed by queue id */
+ QMGR_JOB_LIST job_list; /* list of message jobs (1 per
+ * message) ordered by scheduler */
+ QMGR_JOB_LIST job_bytime; /* jobs ordered by time since queued */
+ QMGR_JOB *job_current; /* keeps track of the current job */
+ QMGR_JOB *job_next_unread; /* next job with unread recipients */
+ QMGR_JOB *candidate_cache; /* cached result from
+ * qmgr_job_candidate() */
+ QMGR_JOB *candidate_cache_current; /* current job tied to the candidate */
+ time_t candidate_cache_time; /* when candidate_cache was last
+ * updated */
+ int blocker_tag; /* for marking blocker jobs */
+ QMGR_TRANSPORT_LIST peers; /* linkage */
+ DSN *dsn; /* why unavailable */
+ QMGR_FEEDBACK pos_feedback; /* positive feedback control */
+ QMGR_FEEDBACK neg_feedback; /* negative feedback control */
+ int fail_cohort_limit; /* flow shutdown control */
+ int xport_rate_delay; /* suspend per delivery */
+ int rate_delay; /* suspend per delivery */
+};
+
+#define QMGR_TRANSPORT_STAT_DEAD (1<<1)
+#define QMGR_TRANSPORT_STAT_RATE_LOCK (1<<2)
+
+typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *);
+extern QMGR_TRANSPORT *qmgr_transport_select(void);
+extern void qmgr_transport_alloc(QMGR_TRANSPORT *, QMGR_TRANSPORT_ALLOC_NOTIFY);
+extern void qmgr_transport_throttle(QMGR_TRANSPORT *, DSN *);
+extern void qmgr_transport_unthrottle(QMGR_TRANSPORT *);
+extern QMGR_TRANSPORT *qmgr_transport_create(const char *);
+extern QMGR_TRANSPORT *qmgr_transport_find(const char *);
+
+#define QMGR_TRANSPORT_THROTTLED(t) ((t)->flags & QMGR_TRANSPORT_STAT_DEAD)
+
+ /*
+ * Each next hop (e.g., a domain name) has its own queue of pending message
+ * transactions. The "todo" queue contains messages that are to be delivered
+ * to this next hop. When a message is elected for transmission, it is moved
+ * from the "todo" queue to the "busy" queue. Messages are taken from the
+ * "todo" queue in round-robin order.
+ */
+struct QMGR_ENTRY_LIST {
+ QMGR_ENTRY *next;
+ QMGR_ENTRY *prev;
+};
+
+struct QMGR_QUEUE {
+ int dflags; /* delivery request options */
+ time_t last_done; /* last delivery completion */
+ char *name; /* domain name or address */
+ char *nexthop; /* domain name */
+ int todo_refcount; /* queue entries (todo list) */
+ int busy_refcount; /* queue entries (busy list) */
+ int window; /* slow open algorithm */
+ double success; /* accumulated positive feedback */
+ double failure; /* accumulated negative feedback */
+ double fail_cohorts; /* pseudo-cohort failure count */
+ QMGR_TRANSPORT *transport; /* transport linkage */
+ QMGR_ENTRY_LIST todo; /* todo queue entries */
+ QMGR_ENTRY_LIST busy; /* messages on the wire */
+ QMGR_QUEUE_LIST peers; /* neighbor queues */
+ DSN *dsn; /* why unavailable */
+ time_t clog_time_to_warn; /* time of last warning */
+ int blocker_tag; /* tagged if blocks job list */
+};
+
+#define QMGR_QUEUE_TODO 1 /* waiting for service */
+#define QMGR_QUEUE_BUSY 2 /* recipients on the wire */
+
+extern int qmgr_queue_count;
+
+extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *);
+extern void qmgr_queue_done(QMGR_QUEUE *);
+extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *);
+extern void qmgr_queue_unthrottle(QMGR_QUEUE *);
+extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *);
+extern void qmgr_queue_suspend(QMGR_QUEUE *, int);
+
+ /*
+ * Exclusive queue states. Originally there were only two: "throttled" and
+ * "not throttled". It was natural to encode these in the queue window size.
+ * After 10 years it's not practical to rip out all the working code and
+ * change representations, so we just clean up the names a little.
+ *
+ * Note: only the "ready" state can reach every state (including itself);
+ * non-ready states can reach only the "ready" state. Other transitions are
+ * forbidden, because they would result in dangling event handlers.
+ */
+#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */
+#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */
+#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */
+#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */
+
+#define QMGR_QUEUE_READY(q) ((q)->window > 0)
+#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED)
+#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED)
+#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED)
+#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD)
+
+#define QMGR_QUEUE_STATUS(q) ( \
+ QMGR_QUEUE_READY(q) ? "ready" : \
+ QMGR_QUEUE_THROTTLED(q) ? "throttled" : \
+ QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \
+ QMGR_QUEUE_SAVED(q) ? "saved" : \
+ "invalid queue status" \
+ )
+
+ /*
+ * Structure of one next-hop queue entry. In order to save some copying
+ * effort we allow multiple recipients per transaction.
+ */
+struct QMGR_ENTRY {
+ VSTREAM *stream; /* delivery process */
+ QMGR_MESSAGE *message; /* message info */
+ RECIPIENT_LIST rcpt_list; /* as many as it takes */
+ QMGR_QUEUE *queue; /* parent linkage */
+ QMGR_PEER *peer; /* parent linkage */
+ QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */
+ QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */
+};
+
+extern QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *);
+extern void qmgr_entry_unselect(QMGR_ENTRY *);
+extern void qmgr_entry_move_todo(QMGR_QUEUE *, QMGR_ENTRY *);
+extern void qmgr_entry_done(QMGR_ENTRY *, int);
+extern QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *, QMGR_MESSAGE *);
+
+ /*
+ * All common in-core information about a message is kept here. When all
+ * recipients have been tried the message file is linked to the "deferred"
+ * queue (some hosts not reachable), to the "bounce" queue (some recipients
+ * were rejected), and is then removed from the "active" queue.
+ */
+struct QMGR_MESSAGE {
+ int flags; /* delivery problems */
+ int qflags; /* queuing flags */
+ int tflags; /* tracing flags */
+ long tflags_offset; /* offset for killing */
+ int rflags; /* queue file read flags */
+ VSTREAM *fp; /* open queue file or null */
+ int refcount; /* queue entries */
+ int single_rcpt; /* send one rcpt at a time */
+ struct timeval arrival_time; /* start of receive transaction */
+ time_t create_time; /* queue file create time */
+ struct timeval active_time; /* time of entry into active queue */
+ time_t queued_time; /* sanitized time when moved to the
+ * active queue */
+ time_t refill_time; /* sanitized time of last message
+ * refill */
+ long warn_offset; /* warning bounce flag offset */
+ time_t warn_time; /* time next warning to be sent */
+ long data_offset; /* data seek offset */
+ char *queue_name; /* queue name */
+ char *queue_id; /* queue file */
+ char *encoding; /* content encoding */
+ char *sender; /* complete address */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN headers/full */
+ int smtputf8; /* requires unicode */
+ char *verp_delims; /* VERP delimiters */
+ char *filter_xport; /* filtering transport */
+ char *inspect_xport; /* inspecting transport */
+ char *redirect_addr; /* info@spammer.tld */
+ long data_size; /* data segment size */
+ long cont_length; /* message content length */
+ long rcpt_offset; /* more recipients here */
+ char *client_name; /* client hostname */
+ char *client_addr; /* client address */
+ char *client_port; /* client port */
+ char *client_proto; /* client protocol */
+ char *client_helo; /* helo parameter */
+ char *sasl_method; /* SASL method */
+ char *sasl_username; /* SASL user name */
+ char *sasl_sender; /* SASL sender */
+ char *log_ident; /* up-stream queue ID */
+ char *rewrite_context; /* address qualification */
+ RECIPIENT_LIST rcpt_list; /* complete addresses */
+ int rcpt_count; /* used recipient slots */
+ int rcpt_limit; /* maximum read in-core */
+ int rcpt_unread; /* # of recipients left in queue file */
+ QMGR_JOB_LIST job_list; /* jobs delivering this message (1
+ * per transport) */
+};
+
+ /*
+ * Flags 0-15 are reserved for qmgr_user.h.
+ */
+#define QMGR_READ_FLAG_SEEN_ALL_NON_RCPT (1<<16)
+
+#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1)
+
+extern int qmgr_message_count;
+extern int qmgr_recipient_count;
+extern int qmgr_vrfy_pend_count;
+
+extern void qmgr_message_free(QMGR_MESSAGE *);
+extern void qmgr_message_update_warn(QMGR_MESSAGE *);
+extern void qmgr_message_kill_record(QMGR_MESSAGE *, long);
+extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int, mode_t);
+extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *);
+
+#define QMGR_MSG_STATS(stats, message) \
+ MSG_STATS_INIT2(stats, \
+ incoming_arrival, message->arrival_time, \
+ active_arrival, message->active_time)
+
+ /*
+ * Sometimes it's required to access the transport queues and entries on per
+ * message basis. That's what the QMGR_JOB structure is for - it groups all
+ * per message information within each transport using a list of QMGR_PEER
+ * structures. These structures in turn correspond with per message
+ * QMGR_QUEUE structure and list all per message QMGR_ENTRY structures.
+ */
+struct QMGR_PEER_LIST {
+ QMGR_PEER *next;
+ QMGR_PEER *prev;
+};
+
+struct QMGR_JOB {
+ QMGR_MESSAGE *message; /* message delivered by this job */
+ QMGR_TRANSPORT *transport; /* transport this job belongs to */
+ QMGR_JOB_LIST message_peers; /* per message neighbor linkage */
+ QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */
+ QMGR_JOB_LIST time_peers; /* by time neighbor linkage */
+ QMGR_JOB *stack_parent; /* stack parent */
+ QMGR_JOB_LIST stack_children; /* all stack children */
+ QMGR_JOB_LIST stack_siblings; /* stack children linkage */
+ int stack_level; /* job stack nesting level (-1 means
+ * it's not on the lists at all) */
+ int blocker_tag; /* tagged if blocks the job list */
+ struct HTABLE *peer_byname; /* message job peers, indexed by
+ * domain */
+ QMGR_PEER_LIST peer_list; /* list of message job peers */
+ int slots_used; /* slots used during preemption */
+ int slots_available; /* slots available for preemption (in
+ * multiples of slot_cost) */
+ int selected_entries; /* # of entries selected for delivery
+ * so far */
+ int read_entries; /* # of entries read in-core so far */
+ int rcpt_count; /* used recipient slots */
+ int rcpt_limit; /* available recipient slots */
+};
+
+struct QMGR_PEER {
+ QMGR_JOB *job; /* job handling this peer */
+ QMGR_QUEUE *queue; /* queue corresponding with this peer */
+ int refcount; /* peer entries */
+ QMGR_ENTRY_LIST entry_list; /* todo message entries queued for
+ * this peer */
+ QMGR_PEER_LIST peers; /* neighbor linkage */
+};
+
+extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *);
+extern QMGR_PEER *qmgr_peer_select(QMGR_JOB *);
+extern void qmgr_job_blocker_update(QMGR_QUEUE *);
+
+extern QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *, QMGR_TRANSPORT *);
+extern void qmgr_job_free(QMGR_JOB *);
+extern void qmgr_job_move_limits(QMGR_JOB *);
+
+extern QMGR_PEER *qmgr_peer_create(QMGR_JOB *, QMGR_QUEUE *);
+extern QMGR_PEER *qmgr_peer_find(QMGR_JOB *, QMGR_QUEUE *);
+extern QMGR_PEER *qmgr_peer_obtain(QMGR_JOB *, QMGR_QUEUE *);
+extern void qmgr_peer_free(QMGR_PEER *);
+
+ /*
+ * qmgr_defer.c
+ */
+extern void qmgr_defer_transport(QMGR_TRANSPORT *, DSN *);
+extern void qmgr_defer_todo(QMGR_QUEUE *, DSN *);
+extern void qmgr_defer_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *);
+
+ /*
+ * qmgr_bounce.c
+ */
+extern void qmgr_bounce_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *);
+
+ /*
+ * qmgr_deliver.c
+ */
+extern int qmgr_deliver_concurrency;
+extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *);
+
+ /*
+ * qmgr_active.c
+ */
+extern int qmgr_active_feed(QMGR_SCAN *, const char *);
+extern void qmgr_active_drain(void);
+extern void qmgr_active_done(QMGR_MESSAGE *);
+
+ /*
+ * qmgr_move.c
+ */
+extern void qmgr_move(const char *, const char *, time_t);
+
+ /*
+ * qmgr_enable.c
+ */
+extern void qmgr_enable_all(void);
+extern void qmgr_enable_transport(QMGR_TRANSPORT *);
+extern void qmgr_enable_queue(QMGR_QUEUE *);
+
+ /*
+ * Queue scan context.
+ */
+struct QMGR_SCAN {
+ char *queue; /* queue name */
+ int flags; /* private, this run */
+ int nflags; /* private, next run */
+ struct SCAN_DIR *handle; /* scan */
+};
+
+ /*
+ * Flags that control queue scans or destination selection. These are
+ * similar to the QMGR_REQ_XXX request codes.
+ */
+#define QMGR_SCAN_START (1<<0) /* start now/restart when done */
+#define QMGR_SCAN_ALL (1<<1) /* all queue file time stamps */
+#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */
+#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */
+#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */
+#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */
+
+ /*
+ * qmgr_scan.c
+ */
+extern QMGR_SCAN *qmgr_scan_create(const char *);
+extern void qmgr_scan_request(QMGR_SCAN *, int);
+extern char *qmgr_scan_next(QMGR_SCAN *);
+
+ /*
+ * qmgr_error.c
+ */
+extern QMGR_TRANSPORT *qmgr_error_transport(const char *);
+extern QMGR_QUEUE *qmgr_error_queue(const char *, DSN *);
+extern char *qmgr_error_nexthop(DSN *);
+
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
diff --git a/src/qmgr/qmgr_active.c b/src/qmgr/qmgr_active.c
new file mode 100644
index 0000000..b1c1a4a
--- /dev/null
+++ b/src/qmgr/qmgr_active.c
@@ -0,0 +1,607 @@
+/*++
+/* NAME
+/* qmgr_active 3
+/* SUMMARY
+/* active queue management
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_active_feed(scan_info, queue_id)
+/* QMGR_SCAN *scan_info;
+/* const char *queue_id;
+/*
+/* void qmgr_active_drain()
+/*
+/* int qmgr_active_done(message)
+/* QMGR_MESSAGE *message;
+/* DESCRIPTION
+/* These functions maintain the active message queue: the set
+/* of messages that the queue manager is actually working on.
+/* The active queue is limited in size. Messages are drained
+/* from the active queue by allocating a delivery process and
+/* by delivering mail via that process. Messages leak into the
+/* active queue only when the active queue is small enough.
+/* Damaged message files are saved to the "corrupt" directory.
+/*
+/* qmgr_active_feed() inserts the named message file into
+/* the active queue. Message files with the wrong name or
+/* with other wrong properties are skipped but not removed.
+/* The following queue flags are recognized, other flags being
+/* ignored:
+/* .IP QMGR_SCAN_ALL
+/* Examine all queue files. Normally, deferred queue files with
+/* future time stamps are ignored, and incoming queue files with
+/* future time stamps are frowned upon.
+/* .PP
+/* qmgr_active_drain() allocates one delivery process.
+/* Process allocation is asynchronous. Once the delivery
+/* process is available, an attempt is made to deliver
+/* a message via it. Message delivery is asynchronous, too.
+/*
+/* qmgr_active_done() deals with a message after delivery
+/* has been tried for all in-core recipients. If the message
+/* was bounced, a bounce message is sent to the sender, or
+/* to the Errors-To: address if one was specified.
+/* If there are more on-file recipients, a new batch of
+/* in-core recipients is read from the queue file. Otherwise,
+/* if a delivery agent marked the queue file as corrupt,
+/* the queue file is moved to the "corrupt" queue (surprise);
+/* if at least one delivery failed, the message is moved
+/* to the deferred queue. The time stamps of a deferred queue
+/* file are set to the nearest wakeup time of its recipient
+/* sites (if delivery failed due to a problem with a next-hop
+/* host), are set into the future by the amount of time the
+/* message was queued (per-message exponential backoff), or are set
+/* into the future by a minimal backoff time, whichever is more.
+/* The minimal_backoff_time parameter specifies the minimal
+/* amount of time between delivery attempts; maximal_backoff_time
+/* specifies an upper limit.
+/* DIAGNOSTICS
+/* Fatal: queue file access failures, out of memory.
+/* Panic: interface violations, internal consistency errors.
+/* Warnings: corrupt message file. A corrupt message is saved
+/* to the "corrupt" queue for further 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 <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <utime.h>
+#include <errno.h>
+
+#ifndef S_IRWXU /* What? no POSIX system? */
+#define S_IRWXU 0700
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_open_ok.h>
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <bounce.h>
+#include <defer.h>
+#include <trace.h>
+#include <abounce.h>
+#include <rec_type.h>
+#include <qmgr_user.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * A bunch of call-back routines.
+ */
+static void qmgr_active_done_2_bounce_flush(int, void *);
+static void qmgr_active_done_2_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_25_trace_flush(int, void *);
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *);
+static void qmgr_active_done_3_defer_flush(int, void *);
+static void qmgr_active_done_3_defer_warn(int, void *);
+static void qmgr_active_done_3_generic(QMGR_MESSAGE *);
+
+/* qmgr_active_corrupt - move corrupted file out of the way */
+
+static void qmgr_active_corrupt(const char *queue_id)
+{
+ const char *myname = "qmgr_active_corrupt";
+
+ if (mail_queue_rename(queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: save corrupt file queue %s id %s: %m",
+ myname, MAIL_QUEUE_ACTIVE, queue_id);
+ } else {
+ msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"",
+ queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT);
+ }
+}
+
+/* qmgr_active_defer - defer queue file */
+
+static void qmgr_active_defer(const char *queue_name, const char *queue_id,
+ const char *dest_queue, int delay)
+{
+ const char *myname = "qmgr_active_defer";
+ const char *path;
+ struct utimbuf tbuf;
+
+ if (msg_verbose)
+ msg_info("wakeup %s after %ld secs", queue_id, (long) delay);
+
+ tbuf.actime = tbuf.modtime = event_time() + delay;
+ path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
+ if (utime(path, &tbuf) < 0 && errno != ENOENT)
+ msg_fatal("%s: update %s time stamps: %m", myname, path);
+ if (mail_queue_rename(queue_id, queue_name, dest_queue)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: rename %s from %s to %s: %m", myname,
+ queue_id, queue_name, dest_queue);
+ msg_warn("%s: rename %s from %s to %s: %m", myname,
+ queue_id, queue_name, dest_queue);
+ } else if (msg_verbose) {
+ msg_info("%s: defer %s", myname, queue_id);
+ }
+}
+
+/* qmgr_active_feed - feed one message into active queue */
+
+int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id)
+{
+ const char *myname = "qmgr_active_feed";
+ QMGR_MESSAGE *message;
+ struct stat st;
+ const char *path;
+
+ if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0)
+ msg_panic("%s: bad queue %s", myname, scan_info->queue);
+ if (msg_verbose)
+ msg_info("%s: queue %s", myname, scan_info->queue);
+
+ /*
+ * Make sure this is something we are willing to open.
+ */
+ if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO)
+ return (0);
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, path);
+
+ /*
+ * Skip files that have time stamps into the future. They need to cool
+ * down. Incoming and deferred files can have future time stamps.
+ */
+ if ((scan_info->flags & QMGR_SCAN_ALL) == 0
+ && st.st_mtime > time((time_t *) 0) + 1) {
+ if (msg_verbose)
+ msg_info("%s: skip %s (%ld seconds)", myname, queue_id,
+ (long) (st.st_mtime - event_time()));
+ return (0);
+ }
+
+ /*
+ * Move the message to the active queue. File access errors are fatal.
+ */
+ if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: %s: rename from %s to %s: %m", myname,
+ queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE);
+ msg_warn("%s: %s: rename from %s to %s: %m", myname,
+ queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE);
+ return (0);
+ }
+
+ /*
+ * Extract envelope information: sender and recipients. At this point,
+ * mail addresses have been processed by the cleanup service so they
+ * should be in canonical form. Generate requests to deliver this
+ * message.
+ *
+ * Throwing away queue files seems bad, especially when they made it this
+ * far into the mail system. Therefore we save bad files to a separate
+ * directory for further inspection.
+ *
+ * After queue manager restart it is possible that a queue file is still
+ * being delivered. In that case (the file is locked), defer delivery by
+ * a minimal amount of time.
+ */
+#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP)
+#define MAYBE_FLUSH_AFTER(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0)
+#define MAYBE_FORCE_EXPIRE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0)
+#define MAYBE_UPDATE_MODE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \
+ (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0)
+
+ if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id,
+ scan_info->flags
+ | MAYBE_FLUSH_AFTER(st.st_mode)
+ | MAYBE_FORCE_EXPIRE(st.st_mode),
+ MAYBE_UPDATE_MODE(st.st_mode))) == 0) {
+ qmgr_active_corrupt(queue_id);
+ return (0);
+ } else if (message == QMGR_MESSAGE_LOCKED) {
+ qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60);
+ return (0);
+ } else {
+
+ /*
+ * Special case if all recipients were already delivered. Send any
+ * bounces and clean up.
+ */
+ if (message->refcount == 0)
+ qmgr_active_done(message);
+ return (1);
+ }
+}
+
+/* qmgr_active_done - dispose of message after recipients have been tried */
+
+void qmgr_active_done(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done";
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, message->queue_id);
+
+ /*
+ * During a previous iteration, an attempt to bounce this message may
+ * have failed, so there may still be a bounce log lying around. XXX By
+ * groping around in the bounce queue, we're trespassing on the bounce
+ * service's territory. But doing so is more robust than depending on the
+ * bounce daemon to do the lookup for us, and for us to do the deleting
+ * after we have received a successful status from the bounce service.
+ * The bounce queue directory blocks are most likely in memory anyway. If
+ * these lookups become a performance problem we will have to build an
+ * in-core cache into the bounce daemon.
+ *
+ * Don't bounce when the bounce log is empty. The bounce process obviously
+ * failed, and the delivery agent will have requested that the message be
+ * deferred.
+ *
+ * Bounces are sent asynchronously to avoid stalling while the cleanup
+ * daemon waits for the qmgr to accept the "new mail" trigger.
+ *
+ * See also code in cleanup_bounce.c.
+ */
+ if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) {
+ if (st.st_size == 0) {
+ if (mail_queue_remove(MAIL_QUEUE_BOUNCE, message->queue_id))
+ msg_fatal("remove %s %s: %m",
+ MAIL_QUEUE_BOUNCE, message->queue_id);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: bounce %s", myname, message->queue_id);
+ if (message->verp_delims == 0 || var_verp_bounce_off)
+ abounce_flush(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_2_bounce_flush,
+ (void *) message);
+ else
+ abounce_flush_verp(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ message->verp_delims,
+ qmgr_active_done_2_bounce_flush,
+ (void *) message);
+ return;
+ }
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_2_generic(message);
+}
+
+/* qmgr_active_done_2_bounce_flush - process abounce_flush() status */
+
+static void qmgr_active_done_2_bounce_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process abounce_flush() status and continue processing.
+ */
+ message->flags |= status;
+ qmgr_active_done_2_generic(message);
+}
+
+/* qmgr_active_done_2_generic - continue processing */
+
+static void qmgr_active_done_2_generic(QMGR_MESSAGE *message)
+{
+ const char *path;
+ struct stat st;
+
+ /*
+ * A delivery agent marks a queue file as corrupt by changing its
+ * attributes, and by pretending that delivery was deferred.
+ */
+ if (message->flags
+ && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) {
+ qmgr_active_corrupt(message->queue_id);
+ qmgr_message_free(message);
+ return;
+ }
+
+ /*
+ * If we did not read all recipients from this file, go read some more,
+ * but remember whether some recipients have to be tried again.
+ *
+ * Throwing away queue files seems bad, especially when they made it this
+ * far into the mail system. Therefore we save bad files to a separate
+ * directory for further inspection by a human being.
+ */
+ if (message->rcpt_offset > 0) {
+ if (qmgr_message_realloc(message) == 0) {
+ qmgr_active_corrupt(message->queue_id);
+ qmgr_message_free(message);
+ } else {
+ if (message->refcount == 0)
+ qmgr_active_done(message); /* recurse for consistency */
+ }
+ return;
+ }
+
+ /*
+ * XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS
+ * and others not. Depending on what subset of recipients are delivered,
+ * a trace file may or may not be created. Even when the last partial
+ * delivery attempt had no NOTIFY=SUCCESS recipients, a trace file may
+ * still exist from a previous partial delivery attempt. So as long as
+ * any recipient has NOTIFY=SUCCESS we have to always look for the trace
+ * file and be prepared for the file not to exist.
+ *
+ * See also comments in bounce/bounce_notify_util.c.
+ */
+ if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD
+ | DEL_REQ_FLAG_REC_DLY_SENT))
+ || (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) {
+ atrace_flush(message->tflags,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_25_trace_flush,
+ (void *) message);
+ return;
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */
+
+static void qmgr_active_done_25_trace_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process atrace_flush() status and continue processing.
+ */
+ if (status == 0 && message->tflags_offset)
+ qmgr_message_kill_record(message, message->tflags_offset);
+ message->flags |= status;
+ qmgr_active_done_25_generic(message);
+}
+
+/* qmgr_active_done_25_generic - continue processing */
+
+static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_25_generic";
+ const char *expire_status = 0;
+
+ /*
+ * If we get to this point we have tried all recipients for this message.
+ * If the message is too old, try to bounce it.
+ *
+ * Bounces are sent asynchronously to avoid stalling while the cleanup
+ * daemon waits for the qmgr to accept the "new mail" trigger.
+ */
+ if (message->flags) {
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ expire_status = "force-expired";
+ } else if (event_time() >= message->create_time +
+ (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
+ expire_status = "expired";
+ } else {
+ expire_status = 0;
+ }
+ if (expire_status != 0) {
+ msg_info("%s: from=<%s>, status=%s, returned to sender",
+ message->queue_id, info_log_addr_form_sender(message->sender),
+ expire_status);
+ if (message->verp_delims == 0 || var_verp_bounce_off)
+ adefer_flush(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_3_defer_flush,
+ (void *) message);
+ else
+ adefer_flush_verp(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ message->verp_delims,
+ qmgr_active_done_3_defer_flush,
+ (void *) message);
+ return;
+ } else if (message->warn_time > 0
+ && event_time() >= message->warn_time - 1) {
+ if (msg_verbose)
+ msg_info("%s: sending defer warning for %s", myname, message->queue_id);
+ adefer_warn(BOUNCE_FLAG_KEEP,
+ message->queue_name,
+ message->queue_id,
+ message->encoding,
+ message->smtputf8,
+ message->sender,
+ message->dsn_envid,
+ message->dsn_ret,
+ qmgr_active_done_3_defer_warn,
+ (void *) message);
+ return;
+ }
+ }
+
+ /*
+ * Asynchronous processing does not reach this point.
+ */
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */
+
+static void qmgr_active_done_3_defer_warn(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process adefer_warn() completion status and continue processing.
+ */
+ if (status == 0)
+ qmgr_message_update_warn(message);
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */
+
+static void qmgr_active_done_3_defer_flush(int status, void *context)
+{
+ QMGR_MESSAGE *message = (QMGR_MESSAGE *) context;
+
+ /*
+ * Process adefer_flush() status and continue processing.
+ */
+ message->flags = status;
+ qmgr_active_done_3_generic(message);
+}
+
+/* qmgr_active_done_3_generic - continue processing */
+
+static void qmgr_active_done_3_generic(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_active_done_3_generic";
+ int delay;
+
+ /*
+ * Some recipients need to be tried again. Move the queue file time
+ * stamps into the future by the amount of time that the message is
+ * delayed, and move the message to the deferred queue. Impose minimal
+ * and maximal backoff times.
+ *
+ * Since we look at actual time in queue, not time since last delivery
+ * attempt, backoff times will be distributed. However, we can still see
+ * spikes in delivery activity because the interval between deferred
+ * queue scans is finite.
+ */
+ if (message->flags) {
+ if (message->create_time > 0) {
+ delay = event_time() - message->create_time;
+ if (delay > var_max_backoff_time)
+ delay = var_max_backoff_time;
+ if (delay < var_min_backoff_time)
+ delay = var_min_backoff_time;
+ } else {
+ delay = var_min_backoff_time;
+ }
+ qmgr_active_defer(message->queue_name, message->queue_id,
+ MAIL_QUEUE_DEFERRED, delay);
+ }
+
+ /*
+ * All recipients done. Remove the queue file.
+ */
+ else {
+ if (mail_queue_remove(message->queue_name, message->queue_id)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: remove %s from %s: %m", myname,
+ message->queue_id, message->queue_name);
+ msg_warn("%s: remove %s from %s: %m", myname,
+ message->queue_id, message->queue_name);
+ } else {
+ /* Same format as logged by postsuper. */
+ msg_info("%s: removed", message->queue_id);
+ }
+ }
+
+ /*
+ * Finally, delete the in-core message structure.
+ */
+ qmgr_message_free(message);
+}
+
+/* qmgr_active_drain - drain active queue by allocating a delivery process */
+
+void qmgr_active_drain(void)
+{
+ QMGR_TRANSPORT *transport;
+
+ /*
+ * Allocate one delivery process for every transport with pending mail.
+ * The process allocation completes asynchronously.
+ */
+ while ((transport = qmgr_transport_select()) != 0) {
+ if (msg_verbose)
+ msg_info("qmgr_active_drain: allocate %s", transport->name);
+ qmgr_transport_alloc(transport, qmgr_deliver);
+ }
+}
diff --git a/src/qmgr/qmgr_bounce.c b/src/qmgr/qmgr_bounce.c
new file mode 100644
index 0000000..00ba885
--- /dev/null
+++ b/src/qmgr/qmgr_bounce.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* qmgr_bounce
+/* SUMMARY
+/* deal with mail that will not be delivered
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_QUEUE *qmgr_bounce_recipient(message, recipient, dsn)
+/* QMGR_MESSAGE *message;
+/* RECIPIENT *recipient;
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_bounce_recipient() produces a bounce log record.
+/* Once the bounce record is written successfully, the recipient
+/* is marked as done. When the bounce record cannot be written,
+/* the message structure is updated to reflect that the mail is
+/* deferred.
+/*
+/* Arguments:
+/* .IP message
+/* Open queue file with the message being bounced.
+/* .IP recipient
+/* The recipient that will not be delivered.
+/* .IP dsn
+/* Delivery status information. See dsn(3).
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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. */
+
+/* Global library. */
+
+#include <bounce.h>
+#include <deliver_completed.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_bounce_recipient - bounce one message recipient */
+
+void qmgr_bounce_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+ int status;
+
+ status = bounce_append(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", dsn);
+
+ if (status == 0)
+ deliver_completed(message->fp, recipient->offset);
+ else
+ message->flags |= status;
+}
diff --git a/src/qmgr/qmgr_defer.c b/src/qmgr/qmgr_defer.c
new file mode 100644
index 0000000..79615cc
--- /dev/null
+++ b/src/qmgr/qmgr_defer.c
@@ -0,0 +1,163 @@
+/*++
+/* NAME
+/* qmgr_defer
+/* SUMMARY
+/* deal with mail that must be delivered later
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_defer_recipient(message, recipient, dsn)
+/* QMGR_MESSAGE *message;
+/* RECIPIENT *recipient;
+/* DSN *dsn;
+/*
+/* void qmgr_defer_todo(queue, dsn)
+/* QMGR_QUEUE *queue;
+/* DSN *dsn;
+/*
+/* void qmgr_defer_transport(transport, dsn)
+/* QMGR_TRANSPORT *transport;
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_defer_recipient() defers delivery of the named message to
+/* the named recipient. It updates the message structure and writes
+/* a log entry.
+/*
+/* qmgr_defer_todo() iterates over all "todo" deliveries queued for
+/* the named site, and calls qmgr_defer_recipient() for each recipient
+/* found. Side effects caused by qmgr_entry_done(), qmgr_queue_done(),
+/* and by qmgr_active_done(): in-core queue entries will disappear,
+/* in-core queues may disappear, in-core and on-disk messages may
+/* disappear, bounces may be sent, new in-core queues, queue entries
+/* and recipients may appear.
+/*
+/* qmgr_defer_transport() calls qmgr_defer_todo() for each queue
+/* that depends on the named transport. See there for side effects.
+/*
+/* Arguments:
+/* .IP recipient
+/* A recipient address; used for logging purposes, and for updating
+/* the message-specific \fIdefer\fR log.
+/* .IP queue
+/* Specifies a queue with delivery requests for a specific next-hop
+/* host (or local user).
+/* .IP transport
+/* Specifies a message delivery transport.
+/* .IP dsn
+/* See dsn(3).
+/* BUGS
+/* The side effects of calling this routine are quite dramatic.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <defer.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_defer_transport - defer todo entries for named transport */
+
+void qmgr_defer_transport(QMGR_TRANSPORT *transport, DSN *dsn)
+{
+ QMGR_QUEUE *queue;
+ QMGR_QUEUE *next;
+
+ if (msg_verbose)
+ msg_info("defer transport %s: %s %s",
+ transport->name, dsn->status, dsn->reason);
+
+ /*
+ * Proceed carefully. Queues may disappear as a side effect.
+ */
+ for (queue = transport->queue_list.next; queue; queue = next) {
+ next = queue->peers.next;
+ qmgr_defer_todo(queue, dsn);
+ }
+}
+
+/* qmgr_defer_todo - defer all todo queue entries for specific site */
+
+void qmgr_defer_todo(QMGR_QUEUE *queue, DSN *dsn)
+{
+ QMGR_ENTRY *entry;
+ QMGR_ENTRY *next;
+ QMGR_MESSAGE *message;
+ RECIPIENT *recipient;
+ int nrcpt;
+ QMGR_QUEUE *retry_queue;
+
+ /*
+ * Sanity checks.
+ */
+ if (msg_verbose)
+ msg_info("defer site %s: %s %s",
+ queue->name, dsn->status, dsn->reason);
+
+ /*
+ * See if we can redirect the deliveries to the retry(8) delivery agent,
+ * so that they can be handled asynchronously. If the retry(8) service is
+ * unavailable, use the synchronous defer(8) server. With a large todo
+ * queue, this blocks the queue manager for a significant time.
+ */
+ retry_queue = qmgr_error_queue(MAIL_SERVICE_RETRY, dsn);
+
+ /*
+ * Proceed carefully. Queue entries may disappear as a side effect.
+ */
+ for (entry = queue->todo.next; entry != 0; entry = next) {
+ next = entry->queue_peers.next;
+ if (retry_queue != 0) {
+ qmgr_entry_move_todo(retry_queue, entry);
+ continue;
+ }
+ message = entry->message;
+ for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) {
+ recipient = entry->rcpt_list.info + nrcpt;
+ qmgr_defer_recipient(message, recipient, dsn);
+ }
+ qmgr_entry_done(entry, QMGR_QUEUE_TODO);
+ }
+}
+
+/* qmgr_defer_recipient - defer delivery of specific recipient */
+
+void qmgr_defer_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+
+ /*
+ * Update the message structure and log the message disposition.
+ */
+ message->flags |= defer_append(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", dsn);
+}
diff --git a/src/qmgr/qmgr_deliver.c b/src/qmgr/qmgr_deliver.c
new file mode 100644
index 0000000..07e89d4
--- /dev/null
+++ b/src/qmgr/qmgr_deliver.c
@@ -0,0 +1,456 @@
+/*++
+/* NAME
+/* qmgr_deliver 3
+/* SUMMARY
+/* deliver one per-site queue entry to that site
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_deliver_concurrency;
+/*
+/* int qmgr_deliver(transport, fp)
+/* QMGR_TRANSPORT *transport;
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* This module implements the client side of the `queue manager
+/* to delivery agent' protocol. The queue manager uses
+/* asynchronous I/O so that it can drive multiple delivery
+/* agents in parallel. Depending on the outcome of a delivery
+/* attempt, the status of messages, queues and transports is
+/* updated.
+/*
+/* qmgr_deliver_concurrency is a global counter that says how
+/* many delivery processes are in use. This can be used, for
+/* example, to control the size of the `active' message queue.
+/*
+/* qmgr_deliver() executes when a delivery process announces its
+/* availability for the named transport. It arranges for delivery
+/* of a suitable queue entry. The \fIfp\fR argument specifies a
+/* stream that is connected to a delivery process, or a null
+/* pointer if the transport accepts no connection. Upon completion
+/* of delivery (successful or not), the stream is closed, so that the
+/* delivery process is released.
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <recipient_list.h>
+#include <mail_params.h>
+#include <deliver_request.h>
+#include <verp_sender.h>
+#include <dsn_util.h>
+#include <dsn_buf.h>
+#include <dsb_scan.h>
+#include <rcpt_print.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Important note on the _transport_rate_delay implementation: after
+ * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+ * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+ * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+ * _transport_rate_delay will become stuck.
+ */
+
+int qmgr_deliver_concurrency;
+
+ /*
+ * Message delivery status codes.
+ */
+#define DELIVER_STAT_OK 0 /* all recipients delivered */
+#define DELIVER_STAT_DEFER 1 /* try some recipients later */
+#define DELIVER_STAT_CRASH 2 /* mailer internal problem */
+
+/* qmgr_deliver_initial_reply - retrieve initial delivery process response */
+
+static int qmgr_deliver_initial_reply(VSTREAM *stream)
+{
+ if (peekfd(vstream_fileno(stream)) < 0) {
+ msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_DEFER);
+ } else {
+ return (0);
+ }
+}
+
+/* qmgr_deliver_final_reply - retrieve final delivery process response */
+
+static int qmgr_deliver_final_reply(VSTREAM *stream, DSN_BUF *dsb)
+{
+ int stat;
+
+ if (peekfd(vstream_fileno(stream)) < 0) {
+ msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsb),
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &stat),
+ ATTR_TYPE_END) != 2) {
+ msg_warn("%s: malformed response", VSTREAM_PATH(stream));
+ return (DELIVER_STAT_CRASH);
+ } else {
+ return (stat ? DELIVER_STAT_DEFER : 0);
+ }
+}
+
+/* qmgr_deliver_send_request - send delivery request to delivery process */
+
+static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream)
+{
+ RECIPIENT_LIST list = entry->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_MESSAGE *message = entry->message;
+ VSTRING *sender_buf = 0;
+ MSG_STATS stats;
+ char *sender;
+ int flags;
+ int smtputf8 = message->smtputf8;
+ const char *addr;
+
+ /*
+ * Todo: integrate with code up-stream that builds the delivery request.
+ */
+ for (recipient = list.info; recipient < list.info + list.len; recipient++)
+ if (var_smtputf8_enable && (addr = recipient->address)[0]
+ && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) {
+ smtputf8 |= SMTPUTF8_FLAG_RECIPIENT;
+ if (message->verp_delims)
+ smtputf8 |= SMTPUTF8_FLAG_SENDER;
+ }
+
+ /*
+ * If variable envelope return path is requested, change prefix+@origin
+ * into prefix+user=domain@origin. Note that with VERP there is only one
+ * recipient per delivery.
+ */
+ if (message->verp_delims == 0) {
+ sender = message->sender;
+ } else {
+ sender_buf = vstring_alloc(100);
+ verp_sender(sender_buf, message->verp_delims,
+ message->sender, list.info);
+ sender = vstring_str(sender_buf);
+ }
+
+ flags = message->tflags
+ | entry->queue->dflags
+ | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT);
+ (void) QMGR_MSG_STATS(&stats, message);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, message->queue_name),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, message->queue_id),
+ SEND_ATTR_LONG(MAIL_ATTR_OFFSET, message->data_offset),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE, message->cont_length),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, entry->queue->nexthop),
+ SEND_ATTR_STR(MAIL_ATTR_ENCODING, message->encoding),
+ SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, message->dsn_envid),
+ SEND_ATTR_INT(MAIL_ATTR_DSN_RET, message->dsn_ret),
+ SEND_ATTR_FUNC(msg_stats_print, (const void *) &stats),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, message->client_name),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, message->client_addr),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, message->client_port),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, message->client_proto),
+ SEND_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, message->client_helo),
+ /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
+ SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, message->sasl_method),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, message->sasl_username),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, message->sasl_sender),
+ /* XXX Ditto if we want to pass TLS certificate info. */
+ SEND_ATTR_STR(MAIL_ATTR_LOG_IDENT, message->log_ident),
+ SEND_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, message->rewrite_context),
+ SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, list.len),
+ ATTR_TYPE_END);
+ if (sender_buf != 0)
+ vstring_free(sender_buf);
+ for (recipient = list.info; recipient < list.info + list.len; recipient++)
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(rcpt_print, (const void *) recipient),
+ ATTR_TYPE_END);
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write to process (%s): %m", entry->queue->transport->name);
+ return (-1);
+ } else {
+ if (msg_verbose)
+ msg_info("qmgr_deliver: site `%s'", entry->queue->name);
+ return (0);
+ }
+}
+
+/* qmgr_deliver_abort - transport response watchdog */
+
+static void qmgr_deliver_abort(int unused_event, void *context)
+{
+ QMGR_ENTRY *entry = (QMGR_ENTRY *) context;
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_TRANSPORT *transport = queue->transport;
+ QMGR_MESSAGE *message = entry->message;
+
+ msg_fatal("%s: timeout receiving delivery status from transport: %s",
+ message->queue_id, transport->name);
+}
+
+/* qmgr_deliver_update - process delivery status report */
+
+static void qmgr_deliver_update(int unused_event, void *context)
+{
+ QMGR_ENTRY *entry = (QMGR_ENTRY *) context;
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_TRANSPORT *transport = queue->transport;
+ QMGR_MESSAGE *message = entry->message;
+ static DSN_BUF *dsb;
+ int status;
+
+ /*
+ * Release the delivery agent from a "hot" queue entry.
+ */
+#define QMGR_DELIVER_RELEASE_AGENT(entry) do { \
+ event_disable_readwrite(vstream_fileno(entry->stream)); \
+ (void) vstream_fclose(entry->stream); \
+ entry->stream = 0; \
+ qmgr_deliver_concurrency--; \
+ } while (0)
+
+ if (dsb == 0)
+ dsb = dsb_create();
+
+ /*
+ * The message transport has responded. Stop the watchdog timer.
+ */
+ event_cancel_timer(qmgr_deliver_abort, context);
+
+ /*
+ * Retrieve the delivery agent status report. The numerical status code
+ * indicates if delivery should be tried again. The reason text is sent
+ * only when a site should be avoided for a while, so that the queue
+ * manager can log why it does not even try to schedule delivery to the
+ * affected recipients.
+ */
+ status = qmgr_deliver_final_reply(entry->stream, dsb);
+
+ /*
+ * The mail delivery process failed for some reason (although delivery
+ * may have been successful). Back off with this transport type for a
+ * while. Dispose of queue entries for this transport that await
+ * selection (the todo lists). Stay away from queue entries that have
+ * been selected (the busy lists), or we would have dangling pointers.
+ * The queue itself won't go away before we dispose of the current queue
+ * entry.
+ */
+ if (status == DELIVER_STAT_CRASH) {
+ message->flags |= DELIVER_STAT_DEFER;
+#if 0
+ whatsup = concatenate("unknown ", transport->name,
+ " mail transport error", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsb->dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsb->dsn, "4.3.0",
+ "unknown mail transport error"));
+#endif
+ msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description",
+ transport->name);
+
+ /*
+ * Assume the worst and write a defer logfile record for each
+ * recipient. This omission was already present in the first queue
+ * manager implementation of 199703, and was fixed 200511.
+ *
+ * To avoid the synchronous qmgr_defer_recipient() operation for each
+ * recipient of this queue entry, release the delivery process and
+ * move the entry back to the todo queue. Let qmgr_defer_transport()
+ * log the recipient asynchronously if possible, and get out of here.
+ * Note: if asynchronous logging is not possible,
+ * qmgr_defer_transport() eventually invokes qmgr_entry_done() and
+ * the entry becomes a dangling pointer.
+ */
+ QMGR_DELIVER_RELEASE_AGENT(entry);
+ qmgr_entry_unselect(entry);
+ qmgr_defer_transport(transport, &dsb->dsn);
+ return;
+ }
+
+ /*
+ * This message must be tried again.
+ *
+ * If we have a problem talking to this site, back off with this site for a
+ * while; dispose of queue entries for this site that await selection
+ * (the todo list); stay away from queue entries that have been selected
+ * (the busy list), or we would have dangling pointers. The queue itself
+ * won't go away before we dispose of the current queue entry.
+ *
+ * XXX Caution: DSN_COPY() will panic on empty status or reason.
+ */
+#define SUSPENDED "delivery temporarily suspended: "
+
+ if (status == DELIVER_STAT_DEFER) {
+ message->flags |= DELIVER_STAT_DEFER;
+ if (VSTRING_LEN(dsb->status)) {
+ /* Sanitize the DSN status/reason from the delivery agent. */
+ if (!dsn_valid(vstring_str(dsb->status)))
+ vstring_strcpy(dsb->status, "4.0.0");
+ if (VSTRING_LEN(dsb->reason) == 0)
+ vstring_strcpy(dsb->reason, "unknown error");
+ vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1);
+ if (QMGR_QUEUE_READY(queue)) {
+ qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb));
+ if (QMGR_QUEUE_THROTTLED(queue))
+ qmgr_defer_todo(queue, &dsb->dsn);
+ }
+ }
+ }
+
+ /*
+ * No problems detected. Mark the transport and queue as alive. The queue
+ * itself won't go away before we dispose of the current queue entry.
+ */
+ if (status != DELIVER_STAT_CRASH) {
+ qmgr_transport_unthrottle(transport);
+ if (VSTRING_LEN(dsb->reason) == 0)
+ qmgr_queue_unthrottle(queue);
+ }
+
+ /*
+ * Release the delivery process, and give some other queue entry a chance
+ * to be delivered. When all recipients for a message have been tried,
+ * decide what to do next with this message: defer, bounce, delete.
+ */
+ QMGR_DELIVER_RELEASE_AGENT(entry);
+ qmgr_entry_done(entry, QMGR_QUEUE_BUSY);
+}
+
+/* qmgr_deliver - deliver one per-site queue entry */
+
+void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
+{
+ QMGR_ENTRY *entry;
+ DSN dsn;
+
+ /*
+ * Find out if this delivery process is really available. Once elected,
+ * the delivery process is supposed to express its happiness. If there is
+ * a problem, wipe the pending deliveries for this transport. This
+ * routine runs in response to an external event, so it does not run
+ * while some other queue manipulation is happening.
+ */
+ if (stream == 0 || qmgr_deliver_initial_reply(stream) != 0) {
+#if 0
+ whatsup = concatenate(transport->name,
+ " mail transport unavailable", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0",
+ "mail transport unavailable"));
+#endif
+ qmgr_defer_transport(transport, &dsn);
+ if (stream)
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * Find a suitable queue entry. Things may have changed since this
+ * transport was allocated. If no suitable entry is found,
+ * unceremoniously disconnect from the delivery process. The delivery
+ * agent request reading routine is prepared for the queue manager to
+ * change its mind for no apparent reason.
+ */
+ if ((entry = qmgr_job_entry_select(transport)) == 0) {
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * Send the queue file info and recipient info to the delivery process.
+ * If there is a problem, wipe the pending deliveries for this transport.
+ * This routine runs in response to an external event, so it does not run
+ * while some other queue manipulation is happening.
+ */
+ if (qmgr_deliver_send_request(entry, stream) < 0) {
+ qmgr_entry_unselect(entry);
+#if 0
+ whatsup = concatenate(transport->name,
+ " mail transport unavailable", (char *) 0);
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0", whatsup));
+ myfree(whatsup);
+#else
+ qmgr_transport_throttle(transport,
+ DSN_SIMPLE(&dsn, "4.3.0",
+ "mail transport unavailable"));
+#endif
+ qmgr_defer_transport(transport, &dsn);
+ /* warning: entry may be a dangling pointer here */
+ (void) vstream_fclose(stream);
+ return;
+ }
+
+ /*
+ * If we get this far, go wait for the delivery status report.
+ */
+ qmgr_deliver_concurrency++;
+ entry->stream = stream;
+ event_enable_read(vstream_fileno(stream),
+ qmgr_deliver_update, (void *) entry);
+
+ /*
+ * Guard against broken systems.
+ */
+ event_request_timer(qmgr_deliver_abort, (void *) entry, var_daemon_timeout);
+}
diff --git a/src/qmgr/qmgr_enable.c b/src/qmgr/qmgr_enable.c
new file mode 100644
index 0000000..a35e46e
--- /dev/null
+++ b/src/qmgr/qmgr_enable.c
@@ -0,0 +1,107 @@
+/*++
+/* NAME
+/* qmgr_enable
+/* SUMMARY
+/* enable dead transports or sites
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_enable_queue(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_QUEUE *qmgr_enable_transport(transport)
+/* QMGR_TRANSPORT *transport;
+/*
+/* void qmgr_enable_all(void)
+/* DESCRIPTION
+/* This module purges dead in-core state information, effectively
+/* re-enabling delivery.
+/*
+/* qmgr_enable_queue() enables deliveries to the named dead site.
+/* Empty queues are destroyed. The existed solely to indicate that
+/* a site is dead.
+/*
+/* qmgr_enable_transport() enables deliveries via the specified
+/* transport, and calls qmgr_enable_queue() for each destination
+/* on that transport. Empty queues are destroyed.
+/*
+/* qmgr_enable_all() enables all transports and queues.
+/* See above for the side effects caused by doing this.
+/* BUGS
+/* The side effects of calling this module can be quite dramatic.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <vstream.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_enable_all - enable transports and queues */
+
+void qmgr_enable_all(void)
+{
+ QMGR_TRANSPORT *xport;
+
+ if (msg_verbose)
+ msg_info("qmgr_enable_all");
+
+ /*
+ * The number of transports does not change as a side effect, so this can
+ * be a straightforward loop.
+ */
+ for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next)
+ qmgr_enable_transport(xport);
+}
+
+/* qmgr_enable_transport - defer todo entries for named transport */
+
+void qmgr_enable_transport(QMGR_TRANSPORT *transport)
+{
+ QMGR_QUEUE *queue;
+ QMGR_QUEUE *next;
+
+ /*
+ * Proceed carefully. Queues may disappear as a side effect.
+ */
+ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) {
+ if (msg_verbose)
+ msg_info("enable transport %s", transport->name);
+ qmgr_transport_unthrottle(transport);
+ }
+ for (queue = transport->queue_list.next; queue; queue = next) {
+ next = queue->peers.next;
+ qmgr_enable_queue(queue);
+ }
+}
+
+/* qmgr_enable_queue - enable and possibly delete queue */
+
+void qmgr_enable_queue(QMGR_QUEUE *queue)
+{
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ if (msg_verbose)
+ msg_info("enable site %s/%s", queue->transport->name, queue->name);
+ qmgr_queue_unthrottle(queue);
+ }
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+}
diff --git a/src/qmgr/qmgr_entry.c b/src/qmgr/qmgr_entry.c
new file mode 100644
index 0000000..e0673a9
--- /dev/null
+++ b/src/qmgr/qmgr_entry.c
@@ -0,0 +1,452 @@
+/*++
+/* NAME
+/* qmgr_entry 3
+/* SUMMARY
+/* per-site queue entries
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_ENTRY *qmgr_entry_create(peer, message)
+/* QMGR_PEER *peer;
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_entry_done(entry, which)
+/* QMGR_ENTRY *entry;
+/* int which;
+/*
+/* QMGR_ENTRY *qmgr_entry_select(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* void qmgr_entry_unselect(queue, entry)
+/* QMGR_QUEUE *queue;
+/* QMGR_ENTRY *entry;
+/*
+/* void qmgr_entry_move_todo(dst, entry)
+/* QMGR_QUEUE *dst;
+/* QMGR_ENTRY *entry;
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-site message
+/* delivery requests.
+/*
+/* qmgr_entry_create() creates an entry for the named peer and message,
+/* and appends the entry to the peer's list and its queue's todo list.
+/* Filling in and cleaning up the recipients is the responsibility
+/* of the caller.
+/*
+/* qmgr_entry_done() discards a per-site queue entry. The
+/* \fIwhich\fR argument is either QMGR_QUEUE_BUSY for an entry
+/* of the site's `busy' list (i.e. queue entries that have been
+/* selected for actual delivery), or QMGR_QUEUE_TODO for an entry
+/* of the site's `todo' list (i.e. queue entries awaiting selection
+/* for actual delivery).
+/*
+/* qmgr_entry_done() discards its peer structure when the peer
+/* is not referenced anymore.
+/*
+/* qmgr_entry_done() triggers cleanup of the per-site queue when
+/* the site has no pending deliveries, and the site is either
+/* alive, or the site is dead and the number of in-core queues
+/* exceeds a configurable limit (see qmgr_queue_done()).
+/*
+/* qmgr_entry_done() triggers special action when the last in-core
+/* queue entry for a message is done with: either read more
+/* recipients from the queue file, delete the queue file, or move
+/* the queue file to the deferred queue; send bounce reports to the
+/* message originator (see qmgr_active_done()).
+/*
+/* qmgr_entry_select() selects first entry from the named
+/* per-site queue's `todo' list for actual delivery. The entry is
+/* moved to the queue's `busy' list: the list of messages being
+/* delivered. The entry is also removed from its peer list.
+/*
+/* qmgr_entry_unselect() takes the named entry off the named
+/* per-site queue's `busy' list and moves it to the queue's
+/* `todo' list. The entry is also prepended to its peer list again.
+/*
+/* qmgr_entry_move_todo() moves the specified "todo" queue entry
+/* to the specified "todo" queue.
+/* DIAGNOSTICS
+/* Panic: interface violations, internal inconsistencies.
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <deliver_request.h> /* opportunistic session caching */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_entry_select - select queue entry for delivery */
+
+QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer)
+{
+ const char *myname = "qmgr_entry_select";
+ QMGR_ENTRY *entry;
+ QMGR_QUEUE *queue;
+
+ if ((entry = peer->entry_list.next) != 0) {
+ queue = entry->queue;
+ QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
+ queue->todo_refcount--;
+ QMGR_LIST_APPEND(queue->busy, entry, queue_peers);
+ queue->busy_refcount++;
+ QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
+ peer->job->selected_entries++;
+
+ /*
+ * With opportunistic session caching, the delivery agent must not
+ * only 1) save a session upon completion, but also 2) reuse a cached
+ * session upon the next delivery request. In order to not miss out
+ * on 2), we have to make caching sticky or else we get silly
+ * behavior when the in-memory queue drains. Specifically, new
+ * connections must not be made as long as cached connections exist.
+ *
+ * Safety: don't enable opportunistic session caching unless the queue
+ * manager is able to schedule concurrent or back-to-back deliveries
+ * (we need to recognize back-to-back deliveries for transports with
+ * concurrency 1).
+ *
+ * If caching has previously been enabled, but is not now, fetch any
+ * existing entries from the cache, but don't add new ones.
+ */
+#define CONCURRENT_OR_BACK_TO_BACK_DELIVERY() \
+ (queue->busy_refcount > 1 || BACK_TO_BACK_DELIVERY())
+
+#define BACK_TO_BACK_DELIVERY() \
+ (queue->last_done + 1 >= event_time())
+
+ /*
+ * Turn on session caching after we get up to speed. Don't enable
+ * session caching just because we have concurrent deliveries. This
+ * prevents unnecessary session caching when we have a burst of mail
+ * <= the initial concurrency limit.
+ */
+ if ((queue->dflags & DEL_REQ_FLAG_CONN_STORE) == 0) {
+ if (BACK_TO_BACK_DELIVERY()) {
+ if (msg_verbose)
+ msg_info("%s: allowing on-demand session caching for %s",
+ myname, queue->name);
+ queue->dflags |= DEL_REQ_FLAG_CONN_MASK;
+ }
+ }
+
+ /*
+ * Turn off session caching when concurrency drops and we're running
+ * out of steam. This is what prevents from turning off session
+ * caching too early, and from making new connections while old ones
+ * are still cached.
+ */
+ else {
+ if (!CONCURRENT_OR_BACK_TO_BACK_DELIVERY()) {
+ if (msg_verbose)
+ msg_info("%s: disallowing on-demand session caching for %s",
+ myname, queue->name);
+ queue->dflags &= ~DEL_REQ_FLAG_CONN_STORE;
+ }
+ }
+ }
+ return (entry);
+}
+
+/* qmgr_entry_unselect - unselect queue entry for delivery */
+
+void qmgr_entry_unselect(QMGR_ENTRY *entry)
+{
+ QMGR_PEER *peer = entry->peer;
+ QMGR_QUEUE *queue = entry->queue;
+
+ /*
+ * Move the entry back to the todo lists. In case of the peer list, put
+ * it back to the beginning, so the select()/unselect() does not reorder
+ * entries. We use this in qmgr_message_assign() to put recipients into
+ * existing entries when possible.
+ */
+ QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
+ queue->busy_refcount--;
+ QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
+ queue->todo_refcount++;
+ QMGR_LIST_PREPEND(peer->entry_list, entry, peer_peers);
+ peer->job->selected_entries--;
+}
+
+/* qmgr_entry_move_todo - move entry between todo queues */
+
+void qmgr_entry_move_todo(QMGR_QUEUE *dst_queue, QMGR_ENTRY *entry)
+{
+ const char *myname = "qmgr_entry_move_todo";
+ QMGR_TRANSPORT *dst_transport = dst_queue->transport;
+ QMGR_MESSAGE *message = entry->message;
+ QMGR_QUEUE *src_queue = entry->queue;
+ QMGR_PEER *dst_peer, *src_peer = entry->peer;
+ QMGR_JOB *dst_job, *src_job = src_peer->job;
+ QMGR_ENTRY *new_entry;
+ int rcpt_count = entry->rcpt_list.len;
+
+ if (entry->stream != 0)
+ msg_panic("%s: queue %s entry is busy", myname, src_queue->name);
+ if (QMGR_QUEUE_THROTTLED(dst_queue))
+ msg_panic("%s: destination queue %s is throttled", myname, dst_queue->name);
+ if (QMGR_TRANSPORT_THROTTLED(dst_transport))
+ msg_panic("%s: destination transport %s is throttled",
+ myname, dst_transport->name);
+
+ /*
+ * Create new entry, swap the recipients between the two entries,
+ * adjusting the job counters accordingly, then dispose of the old entry.
+ *
+ * Note that qmgr_entry_done() will also take care of adjusting the
+ * recipient limits of all the message jobs, so we do not have to do that
+ * explicitly for the new job here.
+ *
+ * XXX This does not enforce the per-entry recipient limit, but that is not
+ * a problem as long as qmgr_entry_move_todo() is called only to bounce
+ * or defer mail.
+ */
+ dst_job = qmgr_job_obtain(message, dst_transport);
+ dst_peer = qmgr_peer_obtain(dst_job, dst_queue);
+
+ new_entry = qmgr_entry_create(dst_peer, message);
+
+ recipient_list_swap(&entry->rcpt_list, &new_entry->rcpt_list);
+
+ src_job->rcpt_count -= rcpt_count;
+ dst_job->rcpt_count += rcpt_count;
+
+ qmgr_entry_done(entry, QMGR_QUEUE_TODO);
+}
+
+/* qmgr_entry_done - dispose of queue entry */
+
+void qmgr_entry_done(QMGR_ENTRY *entry, int which)
+{
+ const char *myname = "qmgr_entry_done";
+ QMGR_QUEUE *queue = entry->queue;
+ QMGR_MESSAGE *message = entry->message;
+ QMGR_PEER *peer = entry->peer;
+ QMGR_JOB *sponsor, *job = peer->job;
+ QMGR_TRANSPORT *transport = job->transport;
+
+ /*
+ * Take this entry off the in-core queue.
+ */
+ if (entry->stream != 0)
+ msg_panic("%s: file is open", myname);
+ if (which == QMGR_QUEUE_BUSY) {
+ QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
+ queue->busy_refcount--;
+ } else if (which == QMGR_QUEUE_TODO) {
+ QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
+ job->selected_entries++;
+ QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
+ queue->todo_refcount--;
+ } else {
+ msg_panic("%s: bad queue spec: %d", myname, which);
+ }
+
+ /*
+ * Decrease the in-core recipient counts and free the recipient list and
+ * the structure itself.
+ */
+ job->rcpt_count -= entry->rcpt_list.len;
+ message->rcpt_count -= entry->rcpt_list.len;
+ qmgr_recipient_count -= entry->rcpt_list.len;
+ recipient_list_free(&entry->rcpt_list);
+ myfree((void *) entry);
+
+ /*
+ * Make sure that the transport of any retired or finishing job that
+ * donated recipient slots to this message gets them back first. Then, if
+ * possible, pass the remaining unused recipient slots to the next job on
+ * the job list.
+ */
+ for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) {
+ if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job)
+ continue;
+ if (sponsor->stack_level < 0 || message->rcpt_offset == 0)
+ qmgr_job_move_limits(sponsor);
+ }
+ if (message->rcpt_offset == 0) {
+ qmgr_job_move_limits(job);
+ }
+
+ /*
+ * We implement a rate-limited queue by emulating a slow delivery
+ * channel. We insert the artificial delays with qmgr_queue_suspend().
+ *
+ * When a queue is suspended, we must postpone any job scheduling decisions
+ * until the queue is resumed. Otherwise, we make those decisions now.
+ * The job scheduling decisions are made by qmgr_job_blocker_update().
+ */
+ if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) {
+ if (queue->window > 1)
+ msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service",
+ myname, transport->name, queue->name, queue->window);
+ if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue))
+ qmgr_queue_suspend(queue, transport->rate_delay);
+ }
+ if (!QMGR_QUEUE_SUSPENDED(queue)
+ && queue->blocker_tag == transport->blocker_tag)
+ qmgr_job_blocker_update(queue);
+
+ /*
+ * When there are no more entries for this peer, discard the peer
+ * structure.
+ */
+ peer->refcount--;
+ if (peer->refcount == 0)
+ qmgr_peer_free(peer);
+
+ /*
+ * Maintain back-to-back delivery status.
+ */
+ if (which == QMGR_QUEUE_BUSY)
+ queue->last_done = event_time();
+
+ /*
+ * When the in-core queue for this site is empty and when this site is
+ * not dead or suspended, discard the in-core queue. When this site is
+ * dead, but the number of in-core queues exceeds some threshold, get rid
+ * of this in-core queue anyway, in order to avoid running out of memory.
+ */
+ if (queue->todo.next == 0 && queue->busy.next == 0) {
+ if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit)
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue))
+ qmgr_queue_done(queue);
+ }
+
+ /*
+ * Update the in-core message reference count. When the in-core message
+ * structure has no more references, dispose of the message.
+ */
+ message->refcount--;
+ if (message->refcount == 0)
+ qmgr_active_done(message);
+}
+
+/* qmgr_entry_create - create queue todo entry */
+
+QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message)
+{
+ QMGR_ENTRY *entry;
+ QMGR_QUEUE *queue = peer->queue;
+
+ /*
+ * Sanity check.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue))
+ msg_panic("qmgr_entry_create: dead queue: %s", queue->name);
+
+ /*
+ * Create the delivery request.
+ */
+ entry = (QMGR_ENTRY *) mymalloc(sizeof(QMGR_ENTRY));
+ entry->stream = 0;
+ entry->message = message;
+ recipient_list_init(&entry->rcpt_list, RCPT_LIST_INIT_QUEUE);
+ message->refcount++;
+ entry->peer = peer;
+ QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
+ peer->refcount++;
+ entry->queue = queue;
+ QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
+ queue->todo_refcount++;
+ peer->job->read_entries++;
+
+ /*
+ * Warn if a destination is falling behind while the active queue
+ * contains a non-trivial amount of single-recipient email. When a
+ * destination takes up more and more space in the active queue, then
+ * other mail will not get through and delivery performance will suffer.
+ *
+ * XXX At this point in the code, the busy reference count is still less
+ * than the concurrency limit (otherwise this code would not be invoked
+ * in the first place) so we have to make some awkward adjustments
+ * below.
+ *
+ * XXX The queue length test below looks at the active queue share of an
+ * individual destination. This catches the case where mail for one
+ * destination is falling behind because it has to round-robin compete
+ * with many other destinations. However, Postfix will also perform
+ * poorly when most of the active queue is tied up by a small number of
+ * concurrency limited destinations. The queue length test below detects
+ * such conditions only indirectly.
+ *
+ * XXX This code does not detect the case that the active queue is being
+ * starved because incoming mail is pounding the disk.
+ */
+ if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) {
+ int queue_length = queue->todo_refcount + queue->busy_refcount;
+ time_t now;
+ QMGR_TRANSPORT *transport;
+ double active_share;
+
+ if (queue_length > var_qmgr_active_limit / 5
+ && (now = event_time()) >= queue->clog_time_to_warn) {
+ active_share = queue_length / (double) qmgr_message_count;
+ msg_warn("mail for %s is using up %d of %d active queue entries",
+ queue->nexthop, queue_length, qmgr_message_count);
+ if (active_share < 0.9)
+ msg_warn("this may slow down other mail deliveries");
+ transport = queue->transport;
+ if (transport->dest_concurrency_limit > 0
+ && transport->dest_concurrency_limit <= queue->busy_refcount + 1)
+ msg_warn("you may need to increase the main.cf %s%s from %d",
+ transport->name, _DEST_CON_LIMIT,
+ transport->dest_concurrency_limit);
+ else if (queue->window > var_qmgr_active_limit * active_share)
+ msg_warn("you may need to increase the main.cf %s from %d",
+ VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit);
+ else if (queue->peers.next != queue->peers.prev)
+ msg_warn("you may need a separate master.cf transport for %s",
+ queue->nexthop);
+ else {
+ msg_warn("you may need to reduce %s connect and helo timeouts",
+ transport->name);
+ msg_warn("so that Postfix quickly skips unavailable hosts");
+ msg_warn("you may need to increase the main.cf %s and %s",
+ VAR_MIN_BACKOFF_TIME, VAR_MAX_BACKOFF_TIME);
+ msg_warn("so that Postfix wastes less time on undeliverable mail");
+ msg_warn("you may need to increase the master.cf %s process limit",
+ transport->name);
+ }
+ msg_warn("please avoid flushing the whole queue when you have");
+ msg_warn("lots of deferred mail, that is bad for performance");
+ msg_warn("to turn off these warnings specify: %s = 0",
+ VAR_QMGR_CLOG_WARN_TIME);
+ queue->clog_time_to_warn = now + var_qmgr_clog_warn_time;
+ }
+ }
+ return (entry);
+}
diff --git a/src/qmgr/qmgr_error.c b/src/qmgr/qmgr_error.c
new file mode 100644
index 0000000..6541c35
--- /dev/null
+++ b/src/qmgr/qmgr_error.c
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* qmgr_error 3
+/* SUMMARY
+/* look up/create error/retry queue
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_TRANSPORT *qmgr_error_transport(service)
+/* const char *service;
+/*
+/* QMGR_QUEUE *qmgr_error_queue(service, dsn)
+/* const char *service;
+/* DSN *dsn;
+/*
+/* char *qmgr_error_nexthop(dsn)
+/* DSN *dsn;
+/* DESCRIPTION
+/* qmgr_error_transport() looks up the error transport for the
+/* specified service. The result is null if the transport is
+/* not available.
+/*
+/* qmgr_error_queue() looks up an error queue for the specified
+/* service and problem. The result is null if the queue is not
+/* available.
+/*
+/* qmgr_error_nexthop() computes the next-hop information for
+/* the specified problem. The result must be passed to myfree().
+/*
+/* Arguments:
+/* .IP dsn
+/* See dsn(3).
+/* .IP service
+/* One of MAIL_SERVICE_ERROR or MAIL_SERVICE_RETRY.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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 <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_error_transport - look up error transport for specified service */
+
+QMGR_TRANSPORT *qmgr_error_transport(const char *service)
+{
+ QMGR_TRANSPORT *transport;
+
+ /*
+ * Find or create retry transport.
+ */
+ if ((transport = qmgr_transport_find(service)) == 0)
+ transport = qmgr_transport_create(service);
+ if (QMGR_TRANSPORT_THROTTLED(transport))
+ return (0);
+
+ /*
+ * Done.
+ */
+ return (transport);
+}
+
+/* qmgr_error_queue - look up error queue for specified service and problem */
+
+QMGR_QUEUE *qmgr_error_queue(const char *service, DSN *dsn)
+{
+ QMGR_TRANSPORT *transport;
+ QMGR_QUEUE *queue;
+ char *nexthop;
+
+ /*
+ * Find or create transport.
+ */
+ if ((transport = qmgr_error_transport(service)) == 0)
+ return (0);
+
+ /*
+ * Find or create queue.
+ */
+ nexthop = qmgr_error_nexthop(dsn);
+ if ((queue = qmgr_queue_find(transport, nexthop)) == 0)
+ queue = qmgr_queue_create(transport, nexthop, nexthop);
+ myfree(nexthop);
+ if (QMGR_QUEUE_THROTTLED(queue))
+ return (0);
+
+ /*
+ * Done.
+ */
+ return (queue);
+}
+
+/* qmgr_error_nexthop - compute next-hop information from problem description */
+
+char *qmgr_error_nexthop(DSN *dsn)
+{
+ char *nexthop;
+
+ nexthop = concatenate(dsn->status, " ", dsn->reason, (char *) 0);
+ return (nexthop);
+}
diff --git a/src/qmgr/qmgr_feedback.c b/src/qmgr/qmgr_feedback.c
new file mode 100644
index 0000000..f8019f8
--- /dev/null
+++ b/src/qmgr/qmgr_feedback.c
@@ -0,0 +1,182 @@
+/*++
+/* NAME
+/* qmgr_feedback 3
+/* SUMMARY
+/* delivery agent feedback management
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_feedback_init(fbck_ctl, name_prefix, name_tail,
+/* def_name, def_value)
+/* QMGR_FEEDBACK *fbck_ctl;
+/* const char *name_prefix;
+/* const char *name_tail;
+/* const char *def_name;
+/* const char *def_value;
+/*
+/* double QMGR_FEEDBACK_VAL(fbck_ctl, concurrency)
+/* QMGR_FEEDBACK *fbck_ctl;
+/* const int concurrency;
+/* DESCRIPTION
+/* Upon completion of a delivery request, a delivery agent
+/* provides a hint that the scheduler should dedicate fewer or
+/* more resources to a specific destination.
+/*
+/* qmgr_feedback_init() looks up transport-dependent positive
+/* or negative concurrency feedback control information from
+/* main.cf, and converts it to internal form.
+/*
+/* QMGR_FEEDBACK_VAL() computes a concurrency adjustment based
+/* on a preprocessed feedback control information and the
+/* current concurrency window. This is an "unsafe" macro that
+/* evaluates some arguments multiple times.
+/*
+/* Arguments:
+/* .IP fbck_ctl
+/* Pointer to QMGR_FEEDBACK structure where the result will
+/* be stored.
+/* .IP name_prefix
+/* Mail delivery transport name, used as the initial portion
+/* of a transport-dependent concurrency feedback parameter
+/* name.
+/* .IP name_tail
+/* The second, and fixed, portion of a transport-dependent
+/* concurrency feedback parameter.
+/* .IP def_name
+/* The name of a default feedback parameter.
+/* .IP def_val
+/* The value of the default feedback parameter.
+/* .IP concurrency
+/* Delivery concurrency for concurrency-dependent feedback calculation.
+/* DIAGNOSTICS
+/* Warning: configuration error or unreasonable input. The program
+/* uses name_tail feedback instead.
+/* Panic: consistency check failure.
+/* 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 <stdlib.h>
+#include <limits.h> /* INT_MAX */
+#include <stdio.h> /* sscanf() */
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+ /*
+ * Lookup tables for main.cf feedback method names.
+ */
+const NAME_CODE qmgr_feedback_map[] = {
+ CONC_FDBACK_NAME_WIN, QMGR_FEEDBACK_IDX_WIN,
+#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN
+ CONC_FDBACK_NAME_SQRT_WIN, QMGR_FEEDBACK_IDX_SQRT_WIN,
+#endif
+ 0, QMGR_FEEDBACK_IDX_NONE,
+};
+
+/* qmgr_feedback_init - initialize feedback control */
+
+void qmgr_feedback_init(QMGR_FEEDBACK *fb,
+ const char *name_prefix,
+ const char *name_tail,
+ const char *def_name,
+ const char *def_val)
+{
+ double enum_val;
+ char denom_str[30 + 1];
+ double denom_val;
+ char slash[1 + 1];
+ char junk;
+ char *fbck_name;
+ char *fbck_val;
+
+ /*
+ * Look up the transport-dependent feedback value.
+ */
+ fbck_name = concatenate(name_prefix, name_tail, (char *) 0);
+ fbck_val = get_mail_conf_str(fbck_name, def_val, 1, 0);
+
+ /*
+ * We allow users to express feedback as 1/8, as a more user-friendly
+ * alternative to 0.125 (or worse, having users specify the number of
+ * events in a feedback hysteresis cycle).
+ *
+ * We use some sscanf() fu to parse the value into numerator and optional
+ * "/" followed by denominator. We're doing this only a few times during
+ * the process life time, so we strive for convenience instead of speed.
+ */
+#define INCLUSIVE_BOUNDS(val, low, high) ((val) >= (low) && (val) <= (high))
+
+ fb->hysteresis = 1; /* legacy */
+ fb->base = -1; /* assume error */
+
+ switch (sscanf(fbck_val, "%lf %1[/] %30s%c",
+ &enum_val, slash, denom_str, &junk)) {
+ case 1:
+ fb->index = QMGR_FEEDBACK_IDX_NONE;
+ fb->base = enum_val;
+ break;
+ case 3:
+ if ((fb->index = name_code(qmgr_feedback_map, NAME_CODE_FLAG_NONE,
+ denom_str)) != QMGR_FEEDBACK_IDX_NONE) {
+ fb->base = enum_val;
+ } else if (INCLUSIVE_BOUNDS(enum_val, 0, INT_MAX)
+ && sscanf(denom_str, "%lf%c", &denom_val, &junk) == 1
+ && INCLUSIVE_BOUNDS(denom_val, 1.0 / INT_MAX, INT_MAX)) {
+ fb->base = enum_val / denom_val;
+ }
+ break;
+ }
+
+ /*
+ * Sanity check. If input is bad, we just warn and use a reasonable
+ * default.
+ */
+ if (!INCLUSIVE_BOUNDS(fb->base, 0, 1)) {
+ msg_warn("%s: ignoring malformed or unreasonable feedback: %s",
+ strcmp(fbck_val, def_val) ? fbck_name : def_name, fbck_val);
+ fb->index = QMGR_FEEDBACK_IDX_NONE;
+ fb->base = 1;
+ }
+
+ /*
+ * Performance debugging/analysis.
+ */
+ if (var_conc_feedback_debug)
+ msg_info("%s: %s feedback type %d value at %d: %g",
+ name_prefix, strcmp(fbck_val, def_val) ?
+ fbck_name : def_name, fb->index, var_init_dest_concurrency,
+ QMGR_FEEDBACK_VAL(*fb, var_init_dest_concurrency));
+
+ myfree(fbck_name);
+ myfree(fbck_val);
+}
diff --git a/src/qmgr/qmgr_job.c b/src/qmgr/qmgr_job.c
new file mode 100644
index 0000000..24065f3
--- /dev/null
+++ b/src/qmgr/qmgr_job.c
@@ -0,0 +1,978 @@
+/*++
+/* NAME
+/* qmgr_job 3
+/* SUMMARY
+/* per-transport jobs
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_JOB *qmgr_job_obtain(message, transport)
+/* QMGR_MESSAGE *message;
+/* QMGR_TRANSPORT *transport;
+/*
+/* void qmgr_job_free(job)
+/* QMGR_JOB *job;
+/*
+/* void qmgr_job_move_limits(job)
+/* QMGR_JOB *job;
+/*
+/* QMGR_ENTRY *qmgr_job_entry_select(transport)
+/* QMGR_TRANSPORT *transport;
+/*
+/* void qmgr_job_blocker_update(queue)
+/* QMGR_QUEUE *queue;
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-transport jobs.
+/* Each job corresponds to a specific transport and message.
+/* Each job has a peer list containing all pending delivery
+/* requests for that message.
+/*
+/* qmgr_job_obtain() finds an existing job for named message and
+/* transport combination. New empty job is created if no existing can
+/* be found. In either case, the job is prepared for assignment of
+/* (more) message recipients.
+/*
+/* qmgr_job_free() disposes of a per-transport job after all
+/* its entries have been taken care of. It is an error to dispose
+/* of a job that is still in use.
+/*
+/* qmgr_job_entry_select() attempts to find the next entry suitable
+/* for delivery. The job preempting algorithm is also exercised.
+/* If necessary, an attempt to read more recipients into core is made.
+/* This can result in creation of more job, queue and entry structures.
+/*
+/* qmgr_job_blocker_update() updates the status of blocked
+/* jobs after a decrease in the queue's concurrency level,
+/* after the queue is throttled, or after the queue is resumed
+/* from suspension.
+/*
+/* qmgr_job_move_limits() takes care of proper distribution of the
+/* per-transport recipients limit among the per-transport jobs.
+/* Should be called whenever a job's recipient slot becomes available.
+/* DIAGNOSTICS
+/* Panic: consistency check failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* patrik@raxoft.cz
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <mymalloc.h>
+#include <sane_time.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* Forward declarations */
+
+static void qmgr_job_pop(QMGR_JOB *);
+
+/* Helper macros */
+
+#define HAS_ENTRIES(job) ((job)->selected_entries < (job)->read_entries)
+
+/*
+ * The MIN_ENTRIES macro may underestimate a lot but we can't use message->rcpt_unread
+ * because we don't know if all those unread recipients go to our transport yet.
+ */
+
+#define MIN_ENTRIES(job) ((job)->read_entries)
+#define MAX_ENTRIES(job) ((job)->read_entries + (job)->message->rcpt_unread)
+
+#define RESET_CANDIDATE_CACHE(transport) ((transport)->candidate_cache_current = 0)
+
+#define IS_BLOCKER(job,transport) ((job)->blocker_tag == (transport)->blocker_tag)
+
+/* qmgr_job_create - create and initialize message job structure */
+
+static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
+{
+ QMGR_JOB *job;
+
+ job = (QMGR_JOB *) mymalloc(sizeof(QMGR_JOB));
+ job->message = message;
+ QMGR_LIST_APPEND(message->job_list, job, message_peers);
+ htable_enter(transport->job_byname, message->queue_id, (void *) job);
+ job->transport = transport;
+ QMGR_LIST_INIT(job->transport_peers);
+ QMGR_LIST_INIT(job->time_peers);
+ job->stack_parent = 0;
+ QMGR_LIST_INIT(job->stack_children);
+ QMGR_LIST_INIT(job->stack_siblings);
+ job->stack_level = -1;
+ job->blocker_tag = 0;
+ job->peer_byname = htable_create(0);
+ QMGR_LIST_INIT(job->peer_list);
+ job->slots_used = 0;
+ job->slots_available = 0;
+ job->selected_entries = 0;
+ job->read_entries = 0;
+ job->rcpt_count = 0;
+ job->rcpt_limit = 0;
+ return (job);
+}
+
+/* qmgr_job_link - append the job to the job lists based on the time it was queued */
+
+static void qmgr_job_link(QMGR_JOB *job)
+{
+ QMGR_TRANSPORT *transport = job->transport;
+ QMGR_MESSAGE *message = job->message;
+ QMGR_JOB *prev, *next, *list_prev, *list_next, *unread, *current;
+ int delay;
+
+ /*
+ * Sanity checks.
+ */
+ if (job->stack_level >= 0)
+ msg_panic("qmgr_job_link: already on the job lists (%d)", job->stack_level);
+
+ /*
+ * Traverse the time list and the scheduler list from the end and stop
+ * when we found job older than the one being linked.
+ *
+ * During the traversals keep track if we have come across either the
+ * current job or the first unread job on the job list. If this is the
+ * case, these pointers will be adjusted below as required.
+ *
+ * Although both lists are exactly the same when only jobs on the stack
+ * level zero are considered, it's easier to traverse them separately.
+ * Otherwise it's impossible to keep track of the current job pointer
+ * effectively.
+ *
+ * This may look inefficient but under normal operation it is expected that
+ * the loops will stop right away, resulting in normal list appends
+ * below. However, this code is necessary for reviving retired jobs and
+ * for jobs which are created long after the first chunk of recipients
+ * was read in-core (either of these can happen only for multi-transport
+ * messages).
+ *
+ * XXX Note that we test stack_parent rather than stack_level below. This
+ * subtle difference allows us to enqueue the job in correct time order
+ * with respect to orphaned children even after their original parent on
+ * level zero is gone. Consequently, the early loop stop in candidate
+ * selection works reliably, too. These are the reasons why we care to
+ * bother with children adoption at all.
+ */
+ current = transport->job_current;
+ for (next = 0, prev = transport->job_list.prev; prev;
+ next = prev, prev = prev->transport_peers.prev) {
+ if (prev->stack_parent == 0) {
+ delay = message->queued_time - prev->message->queued_time;
+ if (delay >= 0)
+ break;
+ }
+ if (current == prev)
+ current = 0;
+ }
+ list_prev = prev;
+ list_next = next;
+
+ unread = transport->job_next_unread;
+ for (next = 0, prev = transport->job_bytime.prev; prev;
+ next = prev, prev = prev->time_peers.prev) {
+ delay = message->queued_time - prev->message->queued_time;
+ if (delay >= 0)
+ break;
+ if (unread == prev)
+ unread = 0;
+ }
+
+ /*
+ * Link the job into the proper place on the job lists and mark it so we
+ * know it has been linked.
+ */
+ job->stack_level = 0;
+ QMGR_LIST_LINK(transport->job_list, list_prev, job, list_next, transport_peers);
+ QMGR_LIST_LINK(transport->job_bytime, prev, job, next, time_peers);
+
+ /*
+ * Update the current job pointer if necessary.
+ */
+ if (current == 0)
+ transport->job_current = job;
+
+ /*
+ * Update the pointer to the first unread job on the job list and steal
+ * the unused recipient slots from the old one.
+ */
+ if (unread == 0) {
+ unread = transport->job_next_unread;
+ transport->job_next_unread = job;
+ if (unread != 0)
+ qmgr_job_move_limits(unread);
+ }
+
+ /*
+ * Get as much recipient slots as possible. The excess will be returned
+ * to the transport pool as soon as the exact amount required is known
+ * (which is usually after all recipients have been read in core).
+ */
+ if (transport->rcpt_unused > 0) {
+ job->rcpt_limit += transport->rcpt_unused;
+ message->rcpt_limit += transport->rcpt_unused;
+ transport->rcpt_unused = 0;
+ }
+}
+
+/* qmgr_job_find - lookup job associated with named message and transport */
+
+static QMGR_JOB *qmgr_job_find(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
+{
+
+ /*
+ * Instead of traversing the message job list, we use single per
+ * transport hash table. This is better (at least with respect to memory
+ * usage) than having single hash table (usually almost empty) for each
+ * message.
+ */
+ return ((QMGR_JOB *) htable_find(transport->job_byname, message->queue_id));
+}
+
+/* qmgr_job_obtain - find/create the appropriate job and make it ready for new recipients */
+
+QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
+{
+ QMGR_JOB *job;
+
+ /*
+ * Try finding an existing job, reviving it if it was already retired.
+ * Create a new job for this transport/message combination otherwise. In
+ * either case, the job ends linked on the job lists.
+ */
+ if ((job = qmgr_job_find(message, transport)) == 0)
+ job = qmgr_job_create(message, transport);
+ if (job->stack_level < 0)
+ qmgr_job_link(job);
+
+ /*
+ * Reset the candidate cache because of the new expected recipients. Make
+ * sure the job is not marked as a blocker for the same reason. Note that
+ * this can result in having a non-blocker followed by more blockers.
+ * Consequently, we can't just update the current job pointer, we have to
+ * reset it. Fortunately qmgr_job_entry_select() will easily deal with
+ * this and will lookup the real current job for us.
+ */
+ RESET_CANDIDATE_CACHE(transport);
+ if (IS_BLOCKER(job, transport)) {
+ job->blocker_tag = 0;
+ transport->job_current = transport->job_list.next;
+ }
+ return (job);
+}
+
+/* qmgr_job_move_limits - move unused recipient slots to the next unread job */
+
+void qmgr_job_move_limits(QMGR_JOB *job)
+{
+ QMGR_TRANSPORT *transport = job->transport;
+ QMGR_MESSAGE *message = job->message;
+ QMGR_JOB *next = transport->job_next_unread;
+ int rcpt_unused, msg_rcpt_unused;
+
+ /*
+ * Find next unread job on the job list if necessary. Cache it for later.
+ * This makes the amortized efficiency of this routine O(1) per job. Note
+ * that we use the time list whose ordering doesn't change over time.
+ */
+ if (job == next) {
+ for (next = next->time_peers.next; next; next = next->time_peers.next)
+ if (next->message->rcpt_offset != 0)
+ break;
+ transport->job_next_unread = next;
+ }
+
+ /*
+ * Calculate the number of available unused slots.
+ */
+ rcpt_unused = job->rcpt_limit - job->rcpt_count;
+ msg_rcpt_unused = message->rcpt_limit - message->rcpt_count;
+ if (msg_rcpt_unused < rcpt_unused)
+ rcpt_unused = msg_rcpt_unused;
+
+ /*
+ * Transfer the unused recipient slots back to the transport pool and to
+ * the next not-fully-read job. Job's message limits are adjusted
+ * accordingly. Note that the transport pool can be negative if we used
+ * some of the rcpt_per_stack slots.
+ */
+ if (rcpt_unused > 0) {
+ job->rcpt_limit -= rcpt_unused;
+ message->rcpt_limit -= rcpt_unused;
+ transport->rcpt_unused += rcpt_unused;
+ if (next != 0 && (rcpt_unused = transport->rcpt_unused) > 0) {
+ next->rcpt_limit += rcpt_unused;
+ next->message->rcpt_limit += rcpt_unused;
+ transport->rcpt_unused = 0;
+ }
+ }
+}
+
+/* qmgr_job_parent_gone - take care of orphaned stack children */
+
+static void qmgr_job_parent_gone(QMGR_JOB *job, QMGR_JOB *parent)
+{
+ QMGR_JOB *child;
+
+ while ((child = job->stack_children.next) != 0) {
+ QMGR_LIST_UNLINK(job->stack_children, QMGR_JOB *, child, stack_siblings);
+ if (parent != 0)
+ QMGR_LIST_APPEND(parent->stack_children, child, stack_siblings);
+ child->stack_parent = parent;
+ }
+}
+
+/* qmgr_job_unlink - unlink the job from the job lists */
+
+static void qmgr_job_unlink(QMGR_JOB *job)
+{
+ const char *myname = "qmgr_job_unlink";
+ QMGR_TRANSPORT *transport = job->transport;
+
+ /*
+ * Sanity checks.
+ */
+ if (job->stack_level != 0)
+ msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level);
+ if (job->stack_parent != 0)
+ msg_panic("%s: parent present", myname);
+ if (job->stack_siblings.next != 0)
+ msg_panic("%s: siblings present", myname);
+
+ /*
+ * Make sure that children of job on zero stack level are informed that
+ * their parent is gone too.
+ */
+ qmgr_job_parent_gone(job, 0);
+
+ /*
+ * Update the current job pointer if necessary.
+ */
+ if (transport->job_current == job)
+ transport->job_current = job->transport_peers.next;
+
+ /*
+ * Invalidate the candidate selection cache if necessary.
+ */
+ if (job == transport->candidate_cache
+ || job == transport->candidate_cache_current)
+ RESET_CANDIDATE_CACHE(transport);
+
+ /*
+ * Remove the job from the job lists and mark it as unlinked.
+ */
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ QMGR_LIST_UNLINK(transport->job_bytime, QMGR_JOB *, job, time_peers);
+ job->stack_level = -1;
+}
+
+/* qmgr_job_retire - remove the job from the job lists while waiting for recipients to deliver */
+
+static void qmgr_job_retire(QMGR_JOB *job)
+{
+ if (msg_verbose)
+ msg_info("qmgr_job_retire: %s", job->message->queue_id);
+
+ /*
+ * Pop the job from the job stack if necessary.
+ */
+ if (job->stack_level > 0)
+ qmgr_job_pop(job);
+
+ /*
+ * Make sure this job is not cached as the next unread job for this
+ * transport. The qmgr_entry_done() will make sure that the slots donated
+ * by this job are moved back to the transport pool as soon as possible.
+ */
+ qmgr_job_move_limits(job);
+
+ /*
+ * Remove the job from the job lists. Note that it remains on the message
+ * job list, though, and that it can be revived by using
+ * qmgr_job_obtain(). Also note that the available slot counter is left
+ * intact.
+ */
+ qmgr_job_unlink(job);
+}
+
+/* qmgr_job_free - release the job structure */
+
+void qmgr_job_free(QMGR_JOB *job)
+{
+ const char *myname = "qmgr_job_free";
+ QMGR_MESSAGE *message = job->message;
+ QMGR_TRANSPORT *transport = job->transport;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, message->queue_id, transport->name);
+
+ /*
+ * Sanity checks.
+ */
+ if (job->rcpt_count)
+ msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count);
+
+ /*
+ * Pop the job from the job stack if necessary.
+ */
+ if (job->stack_level > 0)
+ qmgr_job_pop(job);
+
+ /*
+ * Return any remaining recipient slots back to the recipient slots pool.
+ */
+ qmgr_job_move_limits(job);
+ if (job->rcpt_limit)
+ msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit);
+
+ /*
+ * Unlink and discard the structure. Check if the job is still linked on
+ * the job lists or if it was already retired before unlinking it.
+ */
+ if (job->stack_level >= 0)
+ qmgr_job_unlink(job);
+ QMGR_LIST_UNLINK(message->job_list, QMGR_JOB *, job, message_peers);
+ htable_delete(transport->job_byname, message->queue_id, (void (*) (void *)) 0);
+ htable_free(job->peer_byname, (void (*) (void *)) 0);
+ myfree((void *) job);
+}
+
+/* qmgr_job_count_slots - maintain the delivery slot counters */
+
+static void qmgr_job_count_slots(QMGR_JOB *job)
+{
+
+ /*
+ * Count the number of delivery slots used during the delivery of the
+ * selected job. Also count the number of delivery slots available for
+ * its preemption.
+ *
+ * Despite its trivial look, this is one of the key parts of the theory
+ * behind this preempting scheduler.
+ */
+ job->slots_available++;
+ job->slots_used++;
+
+ /*
+ * If the selected job is not the original current job, reset the
+ * candidate cache because the change above have slightly increased the
+ * chance of this job becoming a candidate next time.
+ *
+ * Don't expect that the change of the current jobs this turn will render
+ * the candidate cache invalid the next turn - it can happen that the
+ * next turn the original current job will be selected again and the
+ * cache would be considered valid in such case.
+ */
+ if (job != job->transport->candidate_cache_current)
+ RESET_CANDIDATE_CACHE(job->transport);
+}
+
+/* qmgr_job_candidate - find best job candidate for preempting given job */
+
+static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
+{
+ QMGR_TRANSPORT *transport = current->transport;
+ QMGR_JOB *job, *best_job = 0;
+ double score, best_score = 0.0;
+ int max_slots, max_needed_entries, max_total_entries;
+ int delay;
+ time_t now = sane_time();
+
+ /*
+ * Fetch the result directly from the cache if the cache is still valid.
+ *
+ * Note that we cache negative results too, so the cache must be invalidated
+ * by resetting the cached current job pointer, not the candidate pointer
+ * itself.
+ *
+ * In case the cache is valid and contains no candidate, we can ignore the
+ * time change, as it affects only which candidate is the best, not if
+ * one exists. However, this feature requires that we no longer relax the
+ * cache resetting rules, depending on the automatic cache timeout.
+ */
+ if (transport->candidate_cache_current == current
+ && (transport->candidate_cache_time == now
+ || transport->candidate_cache == 0))
+ return (transport->candidate_cache);
+
+ /*
+ * Estimate the minimum amount of delivery slots that can ever be
+ * accumulated for the given job. All jobs that won't fit into these
+ * slots are excluded from the candidate selection.
+ */
+ max_slots = (MIN_ENTRIES(current) - current->selected_entries
+ + current->slots_available) / transport->slot_cost;
+
+ /*
+ * Select the candidate with best time_since_queued/total_recipients
+ * score. In addition to jobs which don't meet the max_slots limit, skip
+ * also jobs which don't have any selectable entries at the moment.
+ *
+ * Instead of traversing the whole job list we traverse it just from the
+ * current job forward. This has several advantages. First, we skip some
+ * of the blocker jobs and the current job itself right away. But the
+ * really important advantage is that we are sure that we don't consider
+ * any jobs that are already stack children of the current job. Thanks to
+ * this we can easily include all encountered jobs which are leaf
+ * children of some of the preempting stacks as valid candidates. All we
+ * need to do is to make sure we do not include any of the stack parents.
+ * And, because the leaf children are not ordered by the time since
+ * queued, we have to exclude them from the early loop end test.
+ *
+ * However, don't bother searching if we can't find anything suitable
+ * anyway.
+ */
+ if (max_slots > 0) {
+ for (job = current->transport_peers.next; job; job = job->transport_peers.next) {
+ if (job->stack_children.next != 0 || IS_BLOCKER(job, transport))
+ continue;
+ max_total_entries = MAX_ENTRIES(job);
+ max_needed_entries = max_total_entries - job->selected_entries;
+ delay = now - job->message->queued_time + 1;
+ if (max_needed_entries > 0 && max_needed_entries <= max_slots) {
+ score = (double) delay / max_total_entries;
+ if (score > best_score) {
+ best_score = score;
+ best_job = job;
+ }
+ }
+
+ /*
+ * Stop early if the best score is as good as it can get.
+ */
+ if (delay <= best_score && job->stack_level == 0)
+ break;
+ }
+ }
+
+ /*
+ * Cache the result for later use.
+ */
+ transport->candidate_cache = best_job;
+ transport->candidate_cache_current = current;
+ transport->candidate_cache_time = now;
+
+ return (best_job);
+}
+
+/* qmgr_job_preempt - preempt large message with smaller one */
+
+static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current)
+{
+ const char *myname = "qmgr_job_preempt";
+ QMGR_TRANSPORT *transport = current->transport;
+ QMGR_JOB *job, *prev;
+ int expected_slots;
+ int rcpt_slots;
+
+ /*
+ * Suppress preempting completely if the current job is not big enough to
+ * accumulate even the minimal number of slots required.
+ *
+ * Also, don't look for better job candidate if there are no available slots
+ * yet (the count can get negative due to the slot loans below).
+ */
+ if (current->slots_available <= 0
+ || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost)
+ return (current);
+
+ /*
+ * Find best candidate for preempting the current job.
+ *
+ * Note that the function also takes care that the candidate fits within the
+ * number of delivery slots which the current job is still able to
+ * accumulate.
+ */
+ if ((job = qmgr_job_candidate(current)) == 0)
+ return (current);
+
+ /*
+ * Sanity checks.
+ */
+ if (job == current)
+ msg_panic("%s: attempt to preempt itself", myname);
+ if (job->stack_children.next != 0)
+ msg_panic("%s: already on the job stack (%d)", myname, job->stack_level);
+ if (job->stack_level < 0)
+ msg_panic("%s: not on the job list (%d)", myname, job->stack_level);
+
+ /*
+ * Check if there is enough available delivery slots accumulated to
+ * preempt the current job.
+ *
+ * The slot loaning scheme improves the average message response time. Note
+ * that the loan only allows the preemption happen earlier, though. It
+ * doesn't affect how many slots have to be "paid" - in either case the
+ * full number of slots required has to be accumulated later before the
+ * current job can be preempted again.
+ */
+ expected_slots = MAX_ENTRIES(job) - job->selected_entries;
+ if (current->slots_available / transport->slot_cost + transport->slot_loan
+ < expected_slots * transport->slot_loan_factor / 100.0)
+ return (current);
+
+ /*
+ * Preempt the current job.
+ *
+ * This involves placing the selected candidate in front of the current job
+ * on the job list and updating the stack parent/child/sibling pointers
+ * appropriately. But first we need to make sure that the candidate is
+ * taken from its previous job stack which it might be top of.
+ */
+ if (job->stack_level > 0)
+ qmgr_job_pop(job);
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ prev = current->transport_peers.prev;
+ QMGR_LIST_LINK(transport->job_list, prev, job, current, transport_peers);
+ job->stack_parent = current;
+ QMGR_LIST_APPEND(current->stack_children, job, stack_siblings);
+ job->stack_level = current->stack_level + 1;
+
+ /*
+ * Update the current job pointer and explicitly reset the candidate
+ * cache.
+ */
+ transport->job_current = job;
+ RESET_CANDIDATE_CACHE(transport);
+
+ /*
+ * Since the single job can be preempted by several jobs at the same
+ * time, we have to adjust the available slot count now to prevent using
+ * the same slots multiple times. To do that we subtract the number of
+ * slots the preempting job will supposedly use. This number will be
+ * corrected later when that job is popped from the stack to reflect the
+ * number of slots really used.
+ *
+ * As long as we don't need to keep track of how many slots were really
+ * used, we can (ab)use the slots_used counter for counting the
+ * difference between the real and expected amounts instead of the
+ * absolute amount.
+ */
+ current->slots_available -= expected_slots * transport->slot_cost;
+ job->slots_used = -expected_slots;
+
+ /*
+ * Add part of extra recipient slots reserved for preempting jobs to the
+ * new current job if necessary.
+ *
+ * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such
+ * case.
+ */
+ if (job->message->rcpt_offset != 0) {
+ rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2;
+ job->rcpt_limit += rcpt_slots;
+ job->message->rcpt_limit += rcpt_slots;
+ transport->rcpt_unused -= rcpt_slots;
+ }
+ if (msg_verbose)
+ msg_info("%s: %s by %s, level %d", myname, current->message->queue_id,
+ job->message->queue_id, job->stack_level);
+
+ return (job);
+}
+
+/* qmgr_job_pop - remove the job from its job preemption stack */
+
+static void qmgr_job_pop(QMGR_JOB *job)
+{
+ const char *myname = "qmgr_job_pop";
+ QMGR_TRANSPORT *transport = job->transport;
+ QMGR_JOB *parent;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, job->message->queue_id);
+
+ /*
+ * Sanity checks.
+ */
+ if (job->stack_level <= 0)
+ msg_panic("%s: not on the job stack (%d)", myname, job->stack_level);
+
+ /*
+ * Adjust the number of delivery slots available to preempt job's parent.
+ * Note that the -= actually adds back any unused slots, as we have
+ * already subtracted the expected amount of slots from both counters
+ * when we did the preemption.
+ *
+ * Note that we intentionally do not adjust slots_used of the parent. Doing
+ * so would decrease the maximum per message inflation factor if the
+ * preemption appeared near the end of parent delivery.
+ *
+ * For the same reason we do not adjust parent's slots_available if the
+ * parent is not the original parent that was preempted by this job
+ * (i.e., the original parent job has already completed).
+ *
+ * This is another key part of the theory behind this preempting scheduler.
+ */
+ if ((parent = job->stack_parent) != 0
+ && job->stack_level == parent->stack_level + 1)
+ parent->slots_available -= job->slots_used * transport->slot_cost;
+
+ /*
+ * Remove the job from its parent's children list.
+ */
+ if (parent != 0) {
+ QMGR_LIST_UNLINK(parent->stack_children, QMGR_JOB *, job, stack_siblings);
+ job->stack_parent = 0;
+ }
+
+ /*
+ * If there is a parent, let it adopt all those orphaned children.
+ * Otherwise at least notify the children that their parent is gone.
+ */
+ qmgr_job_parent_gone(job, parent);
+
+ /*
+ * Put the job back to stack level zero.
+ */
+ job->stack_level = 0;
+
+ /*
+ * Explicitly reset the candidate cache. It's not worth trying to skip
+ * this under some complicated conditions - in most cases the popped job
+ * is the current job so we would have to reset it anyway.
+ */
+ RESET_CANDIDATE_CACHE(transport);
+
+ /*
+ * Here we leave the remaining work involving the proper placement on the
+ * job list to the caller. The most important reason for this is that it
+ * allows us not to look up where exactly to place the job.
+ *
+ * The caller is also made responsible for invalidating the current job
+ * cache if necessary.
+ */
+#if 0
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ QMGR_LIST_LINK(transport->job_list, some_prev, job, some_next, transport_peers);
+
+ if (transport->job_current == job)
+ transport->job_current = job->transport_peers.next;
+#endif
+}
+
+/* qmgr_job_peer_select - select next peer suitable for delivery */
+
+static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB *job)
+{
+ QMGR_PEER *peer;
+ QMGR_MESSAGE *message = job->message;
+
+ /*
+ * Try reading in more recipients. We do that as soon as possible
+ * (almost, see below), to make sure there is enough new blood pouring
+ * in. Otherwise single recipient for slow destination might starve the
+ * entire message delivery, leaving lot of fast destination recipients
+ * sitting idle in the queue file.
+ *
+ * Ideally we would like to read in recipients whenever there is a space,
+ * but to prevent excessive I/O, we read them only when enough time has
+ * passed or we can read enough of them at once.
+ *
+ * Note that even if we read the recipients few at a time, the message
+ * loading code tries to put them to existing recipient entries whenever
+ * possible, so the per-destination recipient grouping is not grossly
+ * affected.
+ *
+ * XXX Workaround for logic mismatch. The message->refcount test needs
+ * explanation. If the refcount is zero, it means that qmgr_active_done()
+ * is being completed asynchronously. In such case, we can't read in
+ * more recipients as bad things would happen after qmgr_active_done()
+ * continues processing. Note that this results in the given job being
+ * stalled for some time, but fortunately this particular situation is so
+ * rare that it is not critical. Still we seek for better solution.
+ */
+ if (message->rcpt_offset != 0
+ && message->refcount > 0
+ && (message->rcpt_limit - message->rcpt_count >= job->transport->refill_limit
+ || (message->rcpt_limit > message->rcpt_count
+ && sane_time() - message->refill_time >= job->transport->refill_delay)))
+ qmgr_message_realloc(message);
+
+ /*
+ * Get the next suitable peer, if there is any.
+ */
+ if (HAS_ENTRIES(job) && (peer = qmgr_peer_select(job)) != 0)
+ return (peer);
+
+ /*
+ * There is no suitable peer in-core, so try reading in more recipients
+ * if possible. This is our last chance to get suitable peer before
+ * giving up on this job for now.
+ *
+ * XXX For message->refcount, see above.
+ */
+ if (message->rcpt_offset != 0
+ && message->refcount > 0
+ && message->rcpt_limit > message->rcpt_count) {
+ qmgr_message_realloc(message);
+ if (HAS_ENTRIES(job))
+ return (qmgr_peer_select(job));
+ }
+ return (0);
+}
+
+/* qmgr_job_entry_select - select next entry suitable for delivery */
+
+QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport)
+{
+ QMGR_JOB *job, *next;
+ QMGR_PEER *peer;
+ QMGR_ENTRY *entry;
+
+ /*
+ * Get the current job if there is one.
+ */
+ if ((job = transport->job_current) == 0)
+ return (0);
+
+ /*
+ * Exercise the preempting algorithm if enabled.
+ *
+ * The slot_cost equal to 1 causes the algorithm to degenerate and is
+ * therefore disabled too.
+ */
+ if (transport->slot_cost >= 2)
+ job = qmgr_job_preempt(job);
+
+ /*
+ * Select next entry suitable for delivery. In case the current job can't
+ * provide one because of the per-destination concurrency limits, we mark
+ * it as a "blocker" job and continue with the next job on the job list.
+ *
+ * Note that the loop also takes care of getting the "stall" jobs (job with
+ * no entries currently available) out of the way if necessary. Stall
+ * jobs can appear in case of multi-transport messages whose recipients
+ * don't fit in-core at once. Some jobs created by such message may have
+ * only few recipients and would stay on the job list until all other
+ * jobs of that message are delivered, blocking precious recipient slots
+ * available to this transport. Or it can happen that the job has some
+ * more entries but suddenly they all get deferred. Whatever the reason,
+ * we retire such jobs below if we happen to come across some.
+ */
+ for ( /* empty */ ; job; job = next) {
+ next = job->transport_peers.next;
+
+ /*
+ * Don't bother if the job is known to have no available entries
+ * because of the per-destination concurrency limits.
+ */
+ if (IS_BLOCKER(job, transport))
+ continue;
+
+ if ((peer = qmgr_job_peer_select(job)) != 0) {
+
+ /*
+ * We have found a suitable peer. Select one of its entries and
+ * adjust the delivery slot counters.
+ */
+ entry = qmgr_entry_select(peer);
+ qmgr_job_count_slots(job);
+
+ /*
+ * Remember the current job for the next time so we don't have to
+ * crawl over all those blockers again. They will be reconsidered
+ * when the concurrency limit permits.
+ */
+ transport->job_current = job;
+
+ /*
+ * In case we selected the very last job entry, remove the job
+ * from the job lists right now.
+ *
+ * This action uses the assumption that once the job entry has been
+ * selected, it can be unselected only before the message ifself
+ * is deferred. Thus the job with all entries selected can't
+ * re-appear with more entries available for selection again
+ * (without reading in more entries from the queue file, which in
+ * turn invokes qmgr_job_obtain() which re-links the job back on
+ * the lists if necessary).
+ *
+ * Note that qmgr_job_move_limits() transfers the recipients slots
+ * correctly even if the job is unlinked from the job list thanks
+ * to the job_next_unread caching.
+ */
+ if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0)
+ qmgr_job_retire(job);
+
+ /*
+ * Finally. Hand back the fruit of our tedious effort.
+ */
+ return (entry);
+ } else if (HAS_ENTRIES(job)) {
+
+ /*
+ * The job can't be selected due the concurrency limits. Mark it
+ * together with its queues so we know they are blocking the job
+ * list and they get the appropriate treatment. In particular,
+ * all blockers will be reconsidered when one of the problematic
+ * queues will accept more deliveries. And the job itself will be
+ * reconsidered if it is assigned some more entries.
+ */
+ job->blocker_tag = transport->blocker_tag;
+ for (peer = job->peer_list.next; peer; peer = peer->peers.next)
+ if (peer->entry_list.next != 0)
+ peer->queue->blocker_tag = transport->blocker_tag;
+ } else {
+
+ /*
+ * The job is "stalled". Retire it until it either gets freed or
+ * gets more entries later.
+ */
+ qmgr_job_retire(job);
+ }
+ }
+
+ /*
+ * We have not found any entry we could use for delivery. Well, things
+ * must have changed since this transport was selected for asynchronous
+ * allocation. Never mind. Clear the current job pointer and reluctantly
+ * report back that we have failed in our task.
+ */
+ transport->job_current = 0;
+ return (0);
+}
+
+/* qmgr_job_blocker_update - update "blocked job" status */
+
+void qmgr_job_blocker_update(QMGR_QUEUE *queue)
+{
+ QMGR_TRANSPORT *transport = queue->transport;
+
+ /*
+ * If the queue was blocking some of the jobs on the job list, check if
+ * the concurrency limit has lifted. If there are still some pending
+ * deliveries, give it a try and unmark all transport blockers at once.
+ * The qmgr_job_entry_select() will do the rest. In either case make sure
+ * the queue is not marked as a blocker anymore, with extra handling of
+ * queues which were declared dead.
+ *
+ * Note that changing the blocker status also affects the candidate cache.
+ * Most of the cases would be automatically recognized by the current job
+ * change, but we play safe and reset the cache explicitly below.
+ *
+ * Keeping the transport blocker tag odd is an easy way to make sure the tag
+ * never matches jobs that are not explicitly marked as blockers.
+ */
+ if (queue->blocker_tag == transport->blocker_tag) {
+ if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
+ transport->blocker_tag += 2;
+ transport->job_current = transport->job_list.next;
+ transport->candidate_cache_current = 0;
+ }
+ if (queue->window > queue->busy_refcount || QMGR_QUEUE_THROTTLED(queue))
+ queue->blocker_tag = 0;
+ }
+}
diff --git a/src/qmgr/qmgr_message.c b/src/qmgr/qmgr_message.c
new file mode 100644
index 0000000..79143f3
--- /dev/null
+++ b/src/qmgr/qmgr_message.c
@@ -0,0 +1,1622 @@
+/*++
+/* NAME
+/* qmgr_message 3
+/* SUMMARY
+/* in-core message structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_message_count;
+/* int qmgr_recipient_count;
+/* int qmgr_vrfy_pend_count;
+/*
+/* QMGR_MESSAGE *qmgr_message_alloc(class, name, qflags, mode)
+/* const char *class;
+/* const char *name;
+/* int qflags;
+/* mode_t mode;
+/*
+/* QMGR_MESSAGE *qmgr_message_realloc(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_free(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_update_warn(message)
+/* QMGR_MESSAGE *message;
+/*
+/* void qmgr_message_kill_record(message, offset)
+/* QMGR_MESSAGE *message;
+/* long offset;
+/* DESCRIPTION
+/* This module performs en-gross operations on queue messages.
+/*
+/* qmgr_message_count is a global counter for the total number
+/* of in-core message structures (i.e. the total size of the
+/* `active' message queue).
+/*
+/* qmgr_recipient_count is a global counter for the total number
+/* of in-core recipient structures (i.e. the sum of all recipients
+/* in all in-core message structures).
+/*
+/* qmgr_vrfy_pend_count is a global counter for the total
+/* number of in-core message structures that are associated
+/* with an address verification request. Requests that exceed
+/* the address_verify_pending_limit are deferred immediately.
+/* This is a backup mechanism for a more refined enforcement
+/* mechanism in the verify(8) daemon.
+/*
+/* qmgr_message_alloc() creates an in-core message structure
+/* with sender and recipient information taken from the named queue
+/* file. A null result means the queue file could not be read or
+/* that the queue file contained incorrect information. A result
+/* QMGR_MESSAGE_LOCKED means delivery must be deferred. The number
+/* of recipients read from a queue file is limited by the global
+/* var_qmgr_rcpt_limit configuration parameter. When the limit
+/* is reached, the \fIrcpt_offset\fR structure member is set to
+/* the position where the read was terminated. Recipients are
+/* run through the resolver, and are assigned to destination
+/* queues. Recipients that cannot be assigned are deferred or
+/* bounced. Mail that has bounced twice is silently absorbed.
+/* A non-zero mode means change the queue file permissions.
+/*
+/* qmgr_message_realloc() resumes reading recipients from the queue
+/* file, and updates the recipient list and \fIrcpt_offset\fR message
+/* structure members. A null result means that the file could not be
+/* read or that the file contained incorrect information. Recipient
+/* limit imposed this time is based on the position of the message
+/* job(s) on corresponding transport job list(s). It's considered
+/* an error to call this when the recipient slots can't be allocated.
+/*
+/* qmgr_message_free() destroys an in-core message structure and makes
+/* the resources available for reuse. It is an error to destroy
+/* a message structure that is still referenced by queue entry structures.
+/*
+/* qmgr_message_update_warn() takes a closed message, opens it, updates
+/* the warning field, and closes it again.
+/*
+/* qmgr_message_kill_record() takes a closed message, opens it, updates
+/* the record type at the given offset to "killed", and closes the file.
+/* A killed envelope record is ignored. Killed records are not allowed
+/* inside the message content.
+/* DIAGNOSTICS
+/* Warnings: malformed message file. Fatal errors: out of memory.
+/* SEE ALSO
+/* envelope(3) message envelope parser
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h> /* sscanf() */
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <split_at.h>
+#include <valid_hostname.h>
+#include <argv.h>
+#include <stringops.h>
+#include <myflock.h>
+#include <sane_time.h>
+
+/* Global library. */
+
+#include <dict.h>
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <canon_addr.h>
+#include <record.h>
+#include <rec_type.h>
+#include <sent.h>
+#include <deliver_completed.h>
+#include <opened.h>
+#include <verp_sender.h>
+#include <mail_proto.h>
+#include <qmgr_user.h>
+#include <split_addr.h>
+#include <dsn_mask.h>
+#include <rec_attr_map.h>
+
+/* Client stubs. */
+
+#include <rewrite_clnt.h>
+#include <resolve_clnt.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+int qmgr_message_count;
+int qmgr_recipient_count;
+int qmgr_vrfy_pend_count;
+
+/* qmgr_message_create - create in-core message structure */
+
+static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
+ const char *queue_id, int qflags)
+{
+ QMGR_MESSAGE *message;
+
+ message = (QMGR_MESSAGE *) mymalloc(sizeof(QMGR_MESSAGE));
+ qmgr_message_count++;
+ message->flags = 0;
+ message->qflags = qflags;
+ message->tflags = 0;
+ message->tflags_offset = 0;
+ message->rflags = QMGR_READ_FLAG_DEFAULT;
+ message->fp = 0;
+ message->refcount = 0;
+ message->single_rcpt = 0;
+ message->arrival_time.tv_sec = message->arrival_time.tv_usec = 0;
+ message->create_time = 0;
+ GETTIMEOFDAY(&message->active_time);
+ message->queued_time = sane_time();
+ message->refill_time = 0;
+ message->data_offset = 0;
+ message->queue_id = mystrdup(queue_id);
+ message->queue_name = mystrdup(queue_name);
+ message->encoding = 0;
+ message->sender = 0;
+ message->dsn_envid = 0;
+ message->dsn_ret = 0;
+ message->smtputf8 = 0;
+ message->filter_xport = 0;
+ message->inspect_xport = 0;
+ message->redirect_addr = 0;
+ message->data_size = 0;
+ message->cont_length = 0;
+ message->warn_offset = 0;
+ message->warn_time = 0;
+ message->rcpt_offset = 0;
+ message->verp_delims = 0;
+ message->client_name = 0;
+ message->client_addr = 0;
+ message->client_port = 0;
+ message->client_proto = 0;
+ message->client_helo = 0;
+ message->sasl_method = 0;
+ message->sasl_username = 0;
+ message->sasl_sender = 0;
+ message->log_ident = 0;
+ message->rewrite_context = 0;
+ recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
+ message->rcpt_count = 0;
+ message->rcpt_limit = var_qmgr_msg_rcpt_limit;
+ message->rcpt_unread = 0;
+ QMGR_LIST_INIT(message->job_list);
+ return (message);
+}
+
+/* qmgr_message_close - close queue file */
+
+static void qmgr_message_close(QMGR_MESSAGE *message)
+{
+ vstream_fclose(message->fp);
+ message->fp = 0;
+}
+
+/* qmgr_message_open - open queue file */
+
+static int qmgr_message_open(QMGR_MESSAGE *message)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (message->fp)
+ msg_panic("%s: queue file is open", message->queue_id);
+
+ /*
+ * Open this queue file. Skip files that we cannot open. Back off when
+ * the system appears to be running out of resources.
+ */
+ if ((message->fp = mail_queue_open(message->queue_name,
+ message->queue_id,
+ O_RDWR, 0)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", message->queue_name, message->queue_id);
+ msg_warn("open %s %s: %m", message->queue_name, message->queue_id);
+ return (-1);
+ }
+ return (0);
+}
+
+/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */
+
+static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
+{
+ VSTRING *buf;
+ long orig_offset, extra_offset;
+ int rec_type;
+ char *start;
+
+ /*
+ * Initialize. No early returns or we have a memory leak.
+ */
+ buf = vstring_alloc(100);
+ if ((orig_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * Rewind to the very beginning to make sure we see all records.
+ */
+ if (vstream_fseek(message->fp, 0, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * Scan through the old style queue file. Count the total number of
+ * recipients and find the data/extra sections offsets. Note that the new
+ * queue files require that data_size equals extra_offset - data_offset,
+ * so we set data_size to this as well and ignore the size record itself
+ * completely.
+ */
+ message->rcpt_unread = 0;
+ for (;;) {
+ rec_type = rec_get(message->fp, buf, 0);
+ if (rec_type <= 0)
+ /* Report missing end record later. */
+ break;
+ start = vstring_str(buf);
+ if (msg_verbose > 1)
+ msg_info("old-style scan record %c %s", rec_type, start);
+ if (rec_type == REC_TYPE_END)
+ break;
+ if (rec_type == REC_TYPE_DONE
+ || rec_type == REC_TYPE_RCPT
+ || rec_type == REC_TYPE_DRCP) {
+ message->rcpt_unread++;
+ continue;
+ }
+ if (rec_type == REC_TYPE_MESG) {
+ if (message->data_offset == 0) {
+ if ((message->data_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+ if ((extra_offset = atol(start)) <= message->data_offset)
+ msg_fatal("bad extra offset %s file %s",
+ start, VSTREAM_PATH(message->fp));
+ if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ message->data_size = extra_offset - message->data_offset;
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ vstring_free(buf);
+
+ /*
+ * Sanity checks. Verify that all required information was found,
+ * including the queue file end marker.
+ */
+ if (message->data_offset == 0 || rec_type != REC_TYPE_END)
+ msg_fatal("%s: envelope records out of order", message->queue_id);
+}
+
+/* qmgr_message_read - read envelope records */
+
+static int qmgr_message_read(QMGR_MESSAGE *message)
+{
+ VSTRING *buf;
+ int rec_type;
+ long curr_offset;
+ long save_offset = message->rcpt_offset; /* save a flag */
+ int save_unread = message->rcpt_unread; /* save a count */
+ char *start;
+ int recipient_limit;
+ const char *error_text;
+ char *name;
+ char *value;
+ char *orig_rcpt = 0;
+ int count;
+ int dsn_notify = 0;
+ char *dsn_orcpt = 0;
+ int n;
+ int have_log_client_attr = 0;
+ static const char env_rec_types[] = REC_TYPE_ENVELOPE REC_TYPE_EXTRACT;
+ static const char extra_rec_type[] = {REC_TYPE_XTRA, 0};
+ const char *expected_rec_types;
+
+ /*
+ * Initialize. No early returns or we have a memory leak.
+ */
+ buf = vstring_alloc(100);
+
+ /*
+ * If we re-open this file, skip over on-file recipient records that we
+ * already looked at, and refill the in-core recipient address list.
+ *
+ * For the first time, the message recipient limit is calculated from the
+ * global recipient limit. This is to avoid reading little recipients
+ * when the active queue is near empty. When the queue becomes full, only
+ * the necessary amount is read in core. Such priming is necessary
+ * because there are no message jobs yet.
+ *
+ * For the next time, the recipient limit is based solely on the message
+ * jobs' positions in the job lists and/or job stacks.
+ */
+ if (message->rcpt_offset) {
+ if (message->rcpt_list.len)
+ msg_panic("%s: recipient list not empty on recipient reload",
+ message->queue_id);
+ if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ message->rcpt_offset = 0;
+ recipient_limit = message->rcpt_limit - message->rcpt_count;
+ } else {
+ recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
+ if (recipient_limit < message->rcpt_limit)
+ recipient_limit = message->rcpt_limit;
+ }
+ /* Keep interrupt latency in check. */
+ if (recipient_limit > 5000)
+ recipient_limit = 5000;
+ if (recipient_limit <= 0)
+ msg_panic("%s: no recipient slots available", message->queue_id);
+ if (msg_verbose)
+ msg_info("%s: recipient limit %d", message->queue_id, recipient_limit);
+
+ /*
+ * Read envelope records. XXX Rely on the front-end programs to enforce
+ * record size limits. Read up to recipient_limit recipients from the
+ * queue file, to protect against memory exhaustion. Recipient records
+ * may appear before or after the message content, so we keep reading
+ * from the queue file until we have enough recipients (rcpt_offset != 0)
+ * and until we know all the non-recipient information.
+ *
+ * Note that the total recipient count record is accurate only for fresh
+ * queue files. After some of the recipients are marked as done and the
+ * queue file is deferred, it can be used as upper bound estimate only.
+ * Fortunately, this poses no major problem on the scheduling algorithm,
+ * as the only impact is that the already deferred messages are not
+ * chosen by qmgr_job_candidate() as often as they could.
+ *
+ * On the first open, we must examine all non-recipient records.
+ *
+ * Optimization: when we know that recipient records are not mixed with
+ * non-recipient records, as is typical with mailing list mail, then we
+ * can avoid having to examine all the queue file records before we can
+ * start deliveries. This avoids some file system thrashing with huge
+ * mailing lists.
+ */
+ for (;;) {
+ expected_rec_types = env_rec_types;
+ if ((curr_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+ if (curr_offset == message->data_offset && curr_offset > 0) {
+ if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ curr_offset += message->data_size;
+ expected_rec_types = extra_rec_type;
+ }
+ rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE);
+ start = vstring_str(buf);
+ if (msg_verbose > 1)
+ msg_info("record %c %s", rec_type, start);
+ if (rec_type == REC_TYPE_PTR) {
+ if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR)
+ break;
+ /* Need to update curr_offset after pointer jump. */
+ continue;
+ }
+ if (rec_type <= 0) {
+ msg_warn("%s: message rejected: missing end record",
+ message->queue_id);
+ break;
+ }
+ if (strchr(expected_rec_types, rec_type) == 0) {
+ msg_warn("Unexpected record type '%c' at offset %ld",
+ rec_type, (long) curr_offset);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if (rec_type == REC_TYPE_END) {
+ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
+ break;
+ }
+
+ /*
+ * Map named attributes to pseudo record types, 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 (rec_type == REC_TYPE_ATTR) {
+ if ((error_text = split_nameval(start, &name, &value)) != 0) {
+ msg_warn("%s: bad attribute record: %s: %.200s",
+ message->queue_id, error_text, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if ((n = rec_attr_map(name)) != 0) {
+ start = value;
+ rec_type = n;
+ }
+ }
+
+ /*
+ * Process recipient records.
+ */
+ if (rec_type == REC_TYPE_RCPT) {
+ /* See also below for code setting orig_rcpt etc. */
+ if (message->rcpt_offset == 0) {
+ message->rcpt_unread--;
+ recipient_list_add(&message->rcpt_list, curr_offset,
+ dsn_orcpt ? dsn_orcpt : "",
+ dsn_notify ? dsn_notify : 0,
+ orig_rcpt ? orig_rcpt : "", start);
+ if (dsn_orcpt) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (orig_rcpt) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_notify)
+ dsn_notify = 0;
+ if (message->rcpt_list.len >= recipient_limit) {
+ if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
+ msg_fatal("vstream_ftell %s: %m",
+ VSTREAM_PATH(message->fp));
+ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
+ /* We already examined all non-recipient records. */
+ break;
+ if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER)
+ /* Examine all remaining non-recipient records. */
+ continue;
+ /* Optimizations for "pure recipient" record sections. */
+ if (curr_offset > message->data_offset) {
+ /* We already examined all non-recipient records. */
+ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
+ break;
+ }
+
+ /*
+ * Examine non-recipient records in the extracted
+ * segment. Note that this skips to the message start
+ * record, because the handler for that record changes
+ * the expectations for allowed record types.
+ */
+ if (vstream_fseek(message->fp, message->data_offset,
+ SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ continue;
+ }
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) {
+ if (message->rcpt_offset == 0) {
+ message->rcpt_unread--;
+ if (dsn_orcpt) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (orig_rcpt) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_notify)
+ dsn_notify = 0;
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_ORCPT) {
+ /* See also above for code clearing dsn_orcpt. */
+ if (dsn_orcpt != 0) {
+ msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>",
+ message->queue_id, dsn_orcpt);
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ if (message->rcpt_offset == 0)
+ dsn_orcpt = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_NOTIFY) {
+ /* See also above for code clearing dsn_notify. */
+ if (dsn_notify != 0) {
+ msg_warn("%s: ignoring out-of-order DSN notify flags <%d>",
+ message->queue_id, dsn_notify);
+ dsn_notify = 0;
+ }
+ if (message->rcpt_offset == 0) {
+ if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n))
+ msg_warn("%s: ignoring malformed DSN notify flags <%.200s>",
+ message->queue_id, start);
+ else
+ dsn_notify = n;
+ continue;
+ }
+ }
+ if (rec_type == REC_TYPE_ORCP) {
+ /* See also above for code clearing orig_rcpt. */
+ if (orig_rcpt != 0) {
+ msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
+ message->queue_id, orig_rcpt);
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (message->rcpt_offset == 0)
+ orig_rcpt = mystrdup(start);
+ continue;
+ }
+
+ /*
+ * Process non-recipient records.
+ */
+ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
+ /* We already examined all non-recipient records. */
+ continue;
+ if (rec_type == REC_TYPE_SIZE) {
+ if (message->data_offset == 0) {
+ if ((count = sscanf(start, "%ld %ld %d %d %ld %d",
+ &message->data_size, &message->data_offset,
+ &message->rcpt_unread, &message->rflags,
+ &message->cont_length,
+ &message->smtputf8)) >= 3) {
+ /* Postfix >= 1.0 (a.k.a. 20010228). */
+ if (message->data_offset <= 0 || message->data_size <= 0) {
+ msg_warn("%s: invalid size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ if (message->rflags & ~QMGR_READ_FLAG_USER) {
+ msg_warn("%s: invalid flags in size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ } else if (count == 1) {
+ /* Postfix < 1.0 (a.k.a. 20010228). */
+ qmgr_message_oldstyle_scan(message);
+ } else {
+ /* Can't happen. */
+ msg_warn("%s: message rejected: weird size record",
+ message->queue_id);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ }
+ /* Postfix < 2.4 compatibility. */
+ if (message->cont_length == 0) {
+ message->cont_length = message->data_size;
+ } else if (message->cont_length < 0) {
+ msg_warn("%s: invalid size record: %.100s",
+ message->queue_id, start);
+ rec_type = REC_TYPE_ERROR;
+ break;
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_TIME) {
+ if (message->arrival_time.tv_sec == 0)
+ REC_TYPE_TIME_SCAN(start, message->arrival_time);
+ continue;
+ }
+ if (rec_type == REC_TYPE_CTIME) {
+ if (message->create_time == 0)
+ message->create_time = atol(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_FILT) {
+ if (message->filter_xport != 0)
+ myfree(message->filter_xport);
+ message->filter_xport = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_INSP) {
+ if (message->inspect_xport != 0)
+ myfree(message->inspect_xport);
+ message->inspect_xport = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_RDR) {
+ if (message->redirect_addr != 0)
+ myfree(message->redirect_addr);
+ message->redirect_addr = mystrdup(start);
+ continue;
+ }
+ if (rec_type == REC_TYPE_FROM) {
+ if (message->sender == 0) {
+ message->sender = mystrdup(start);
+ opened(message->queue_id, message->sender,
+ message->cont_length, message->rcpt_unread,
+ "queue %s", message->queue_name);
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_DSN_ENVID) {
+ /* Allow Milter override. */
+ if (message->dsn_envid != 0)
+ myfree(message->dsn_envid);
+ message->dsn_envid = mystrdup(start);
+ }
+ if (rec_type == REC_TYPE_DSN_RET) {
+ /* Allow Milter override. */
+ if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n))
+ msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s",
+ message->queue_id, start);
+ else
+ message->dsn_ret = n;
+ }
+ if (rec_type == REC_TYPE_ATTR) {
+ /* Allow extra segment to override envelope segment info. */
+ if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
+ if (message->encoding != 0)
+ myfree(message->encoding);
+ message->encoding = mystrdup(value);
+ }
+
+ /*
+ * Backwards compatibility. Before Postfix 2.3, the logging
+ * attributes were called client_name, etc. Now they are called
+ * log_client_name. etc., and client_name is used for the actual
+ * client information. To support old queue files we accept both
+ * names for the purpose of logging; the new name overrides the
+ * old one.
+ *
+ * XXX Do not use the "legacy" client_name etc. attribute values for
+ * initializing the logging attributes, when this file already
+ * contains the "modern" log_client_name etc. logging attributes.
+ * Otherwise, logging attributes that are not present in the
+ * queue file would be set with information from the real client.
+ */
+ else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_name == 0)
+ message->client_name = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) {
+ if (have_log_client_attr == 0 && message->client_addr == 0)
+ message->client_addr = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) {
+ if (have_log_client_attr == 0 && message->client_port == 0)
+ message->client_port = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_proto == 0)
+ message->client_proto = mystrdup(value);
+ } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) {
+ if (have_log_client_attr == 0 && message->client_helo == 0)
+ message->client_helo = mystrdup(value);
+ }
+ /* Original client attributes. */
+ else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) {
+ if (message->client_name != 0)
+ myfree(message->client_name);
+ message->client_name = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) {
+ if (message->client_addr != 0)
+ myfree(message->client_addr);
+ message->client_addr = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) {
+ if (message->client_port != 0)
+ myfree(message->client_port);
+ message->client_port = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) {
+ if (message->client_proto != 0)
+ myfree(message->client_proto);
+ message->client_proto = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) {
+ if (message->client_helo != 0)
+ myfree(message->client_helo);
+ message->client_helo = mystrdup(value);
+ have_log_client_attr = 1;
+ } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) {
+ if (message->sasl_method == 0)
+ message->sasl_method = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_METHOD, value);
+ } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) {
+ if (message->sasl_username == 0)
+ message->sasl_username = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_USERNAME, value);
+ } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) {
+ if (message->sasl_sender == 0)
+ message->sasl_sender = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_SASL_SENDER, value);
+ } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) {
+ if (message->log_ident == 0)
+ message->log_ident = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_LOG_IDENT, value);
+ } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) {
+ if (message->rewrite_context == 0)
+ message->rewrite_context = mystrdup(value);
+ else
+ msg_warn("%s: ignoring multiple %s attribute: %s",
+ message->queue_id, MAIL_ATTR_RWR_CONTEXT, value);
+ }
+
+ /*
+ * Optional tracing flags (verify, sendmail -v, sendmail -bv).
+ * This record is killed after a trace logfile report is sent and
+ * after the logfile is deleted.
+ */
+ else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
+ if (message->tflags == 0) {
+ message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
+ if (message->tflags == DEL_REQ_FLAG_RECORD)
+ message->tflags_offset = curr_offset;
+ else
+ message->tflags_offset = 0;
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0)
+ qmgr_vrfy_pend_count++;
+ }
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_WARN) {
+ if (message->warn_offset == 0) {
+ message->warn_offset = curr_offset;
+ REC_TYPE_WARN_SCAN(start, message->warn_time);
+ }
+ continue;
+ }
+ if (rec_type == REC_TYPE_VERP) {
+ if (message->verp_delims == 0) {
+ if (message->sender == 0 || message->sender[0] == 0) {
+ msg_warn("%s: ignoring VERP request for null sender",
+ message->queue_id);
+ } else if (verp_delims_verify(start) != 0) {
+ msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
+ message->queue_id, start);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: enabling VERP for sender \"%.100s\"",
+ message->queue_id, message->sender);
+ message->single_rcpt = 1;
+ message->verp_delims = mystrdup(start);
+ }
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Grr.
+ */
+ if (dsn_orcpt != 0) {
+ if (rec_type > 0)
+ msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>",
+ message->queue_id, dsn_orcpt);
+ myfree(dsn_orcpt);
+ }
+ if (orig_rcpt != 0) {
+ if (rec_type > 0)
+ msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
+ message->queue_id, orig_rcpt);
+ myfree(orig_rcpt);
+ }
+
+ /*
+ * After sending a "delayed" warning, request sender notification when
+ * message delivery is completed. While "mail delayed" notifications are
+ * bad enough because they multiply the amount of email traffic, "delay
+ * cleared" notifications are even worse because they come in a sudden
+ * burst when the queue drains after a network outage.
+ */
+ if (var_dsn_delay_cleared && message->warn_time < 0)
+ message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT;
+
+ /*
+ * Remember when we have read the last recipient batch. Note that we do
+ * it here after reading as reading might have used considerable amount
+ * of time.
+ */
+ message->refill_time = sane_time();
+
+ /*
+ * Avoid clumsiness elsewhere in the program. When sending data across an
+ * IPC channel, sending an empty string is more convenient than sending a
+ * null pointer.
+ */
+ if (message->dsn_envid == 0)
+ message->dsn_envid = mystrdup("");
+ if (message->encoding == 0)
+ message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
+ if (message->client_name == 0)
+ message->client_name = mystrdup("");
+ if (message->client_addr == 0)
+ message->client_addr = mystrdup("");
+ if (message->client_port == 0)
+ message->client_port = mystrdup("");
+ if (message->client_proto == 0)
+ message->client_proto = mystrdup("");
+ if (message->client_helo == 0)
+ message->client_helo = mystrdup("");
+ if (message->sasl_method == 0)
+ message->sasl_method = mystrdup("");
+ if (message->sasl_username == 0)
+ message->sasl_username = mystrdup("");
+ if (message->sasl_sender == 0)
+ message->sasl_sender = mystrdup("");
+ if (message->log_ident == 0)
+ message->log_ident = mystrdup("");
+ if (message->rewrite_context == 0)
+ message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL);
+ /* Postfix < 2.3 compatibility. */
+ if (message->create_time == 0)
+ message->create_time = message->arrival_time.tv_sec;
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+
+ /*
+ * Sanity checks. Verify that all required information was found,
+ * including the queue file end marker.
+ */
+ if (message->rcpt_unread < 0
+ || (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
+ msg_warn("%s: rcpt count mismatch (%d)",
+ message->queue_id, message->rcpt_unread);
+ message->rcpt_unread = 0;
+ }
+ if (rec_type <= 0) {
+ /* Already logged warning. */
+ } else if (message->arrival_time.tv_sec == 0) {
+ msg_warn("%s: message rejected: missing arrival time record",
+ message->queue_id);
+ } else if (message->sender == 0) {
+ msg_warn("%s: message rejected: missing sender record",
+ message->queue_id);
+ } else if (message->data_offset == 0) {
+ msg_warn("%s: message rejected: missing size record",
+ message->queue_id);
+ } else {
+ return (0);
+ }
+ message->rcpt_offset = save_offset; /* restore flag */
+ message->rcpt_unread = save_unread; /* restore count */
+ recipient_list_free(&message->rcpt_list);
+ recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
+ return (-1);
+}
+
+/* qmgr_message_update_warn - update the time of next delay warning */
+
+void qmgr_message_update_warn(QMGR_MESSAGE *message)
+{
+
+ /*
+ * After the "mail delayed" warning, optionally send a "delay cleared"
+ * notification.
+ */
+ if (qmgr_message_open(message)
+ || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0
+ || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
+ REC_TYPE_WARN_ARG(-1)) < 0
+ || vstream_fflush(message->fp))
+ msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
+ qmgr_message_close(message);
+}
+
+/* qmgr_message_kill_record - mark one message record as killed */
+
+void qmgr_message_kill_record(QMGR_MESSAGE *message, long offset)
+{
+ if (offset <= 0)
+ msg_panic("qmgr_message_kill_record: bad offset 0x%lx", offset);
+ if (qmgr_message_open(message)
+ || rec_put_type(message->fp, REC_TYPE_KILL, offset) < 0
+ || vstream_fflush(message->fp))
+ msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
+ qmgr_message_close(message);
+}
+
+/* qmgr_message_sort_compare - compare recipient information */
+
+static int qmgr_message_sort_compare(const void *p1, const void *p2)
+{
+ RECIPIENT *rcpt1 = (RECIPIENT *) p1;
+ RECIPIENT *rcpt2 = (RECIPIENT *) p2;
+ QMGR_QUEUE *queue1;
+ QMGR_QUEUE *queue2;
+ char *at1;
+ char *at2;
+ int result;
+
+ /*
+ * Compare most significant to least significant recipient attributes.
+ * The comparison function must be transitive, so NULL values need to be
+ * assigned an ordinal (we set NULL last).
+ */
+
+ queue1 = rcpt1->u.queue;
+ queue2 = rcpt2->u.queue;
+ if (queue1 != 0 && queue2 == 0)
+ return (-1);
+ if (queue1 == 0 && queue2 != 0)
+ return (1);
+ if (queue1 != 0 && queue2 != 0) {
+
+ /*
+ * Compare message transport.
+ */
+ if ((result = strcmp(queue1->transport->name,
+ queue2->transport->name)) != 0)
+ return (result);
+
+ /*
+ * Compare queue name (nexthop or recipient@nexthop).
+ */
+ if ((result = strcmp(queue1->name, queue2->name)) != 0)
+ return (result);
+ }
+
+ /*
+ * Compare recipient domain.
+ */
+ at1 = strrchr(rcpt1->address, '@');
+ at2 = strrchr(rcpt2->address, '@');
+ if (at1 == 0 && at2 != 0)
+ return (1);
+ if (at1 != 0 && at2 == 0)
+ return (-1);
+ if (at1 != 0 && at2 != 0
+ && (result = strcasecmp_utf8(at1, at2)) != 0)
+ return (result);
+
+ /*
+ * Compare recipient address.
+ */
+ return (strcmp(rcpt1->address, rcpt2->address));
+}
+
+/* qmgr_message_sort - sort message recipient addresses by domain */
+
+static void qmgr_message_sort(QMGR_MESSAGE *message)
+{
+ qsort((void *) message->rcpt_list.info, message->rcpt_list.len,
+ sizeof(message->rcpt_list.info[0]), qmgr_message_sort_compare);
+ if (msg_verbose) {
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *rcpt;
+
+ msg_info("start sorted recipient list");
+ for (rcpt = list.info; rcpt < list.info + list.len; rcpt++)
+ msg_info("qmgr_message_sort: %s", rcpt->address);
+ msg_info("end sorted recipient list");
+ }
+}
+
+/* qmgr_resolve_one - resolve or skip one recipient */
+
+static int qmgr_resolve_one(QMGR_MESSAGE *message, RECIPIENT *recipient,
+ const char *addr, RESOLVE_REPLY *reply)
+{
+#define QMGR_REDIRECT(rp, tp, np) do { \
+ (rp)->flags = 0; \
+ vstring_strcpy((rp)->transport, (tp)); \
+ vstring_strcpy((rp)->nexthop, (np)); \
+ } while (0)
+
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) == 0)
+ resolve_clnt_query_from(message->sender, addr, reply);
+ else
+ resolve_clnt_verify_from(message->sender, addr, reply);
+ if (reply->flags & RESOLVE_FLAG_FAIL) {
+ QMGR_REDIRECT(reply, MAIL_SERVICE_RETRY,
+ "4.3.0 address resolver failure");
+ return (0);
+ } else if (reply->flags & RESOLVE_FLAG_ERROR) {
+ QMGR_REDIRECT(reply, MAIL_SERVICE_ERROR,
+ "5.1.3 bad address syntax");
+ return (0);
+ } else {
+ return (0);
+ }
+}
+
+/* qmgr_message_resolve - resolve recipients */
+
+static void qmgr_message_resolve(QMGR_MESSAGE *message)
+{
+ static ARGV *defer_xport_argv;
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_TRANSPORT *transport = 0;
+ QMGR_QUEUE *queue = 0;
+ RESOLVE_REPLY reply;
+ VSTRING *queue_name;
+ char *at;
+ char **cpp;
+ char *nexthop;
+ ssize_t len;
+ int status;
+ DSN dsn;
+ MSG_STATS stats;
+ DSN *saved_dsn;
+
+#define STREQ(x,y) (strcmp(x,y) == 0)
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+ resolve_clnt_init(&reply);
+ queue_name = vstring_alloc(1);
+ for (recipient = list.info; recipient < list.info + list.len; recipient++) {
+
+ /*
+ * Redirect overrides all else. But only once (per entire message).
+ * For consistency with the remainder of Postfix, rewrite the address
+ * to canonical form before resolving it.
+ */
+ if (message->redirect_addr) {
+ if (recipient > list.info) {
+ recipient->u.queue = 0;
+ continue;
+ }
+ message->rcpt_offset = 0;
+ message->rcpt_unread = 0;
+
+ rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr,
+ reply.recipient);
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ if (qmgr_resolve_one(message, recipient,
+ recipient->address, &reply) < 0)
+ continue;
+ if (!STREQ(recipient->address, STR(reply.recipient)))
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ }
+
+ /*
+ * Content filtering overrides the address resolver.
+ *
+ * XXX Bypass content_filter inspection for user-generated probes
+ * (sendmail -bv). MTA-generated probes never have the "please filter
+ * me" bits turned on, but we handle them here anyway for the sake of
+ * future proofing.
+ */
+#define FILTER_WITHOUT_NEXTHOP(filter, next) \
+ (((next) = split_at((filter), ':')) == 0 || *(next) == 0)
+
+#define RCPT_WITHOUT_DOMAIN(rcpt, next) \
+ ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0)
+
+ else if (message->filter_xport
+ && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) {
+ reply.flags = 0;
+ vstring_strcpy(reply.transport, message->filter_xport);
+ if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop)
+ && *(nexthop = var_def_filter_nexthop) == 0
+ && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop))
+ nexthop = var_myhostname;
+ vstring_strcpy(reply.nexthop, nexthop);
+ vstring_strcpy(reply.recipient, recipient->address);
+ }
+
+ /*
+ * Resolve the destination to (transport, nexthop, address). The
+ * result address may differ from the one specified by the sender.
+ */
+ else {
+ if (qmgr_resolve_one(message, recipient,
+ recipient->address, &reply) < 0)
+ continue;
+ if (!STREQ(recipient->address, STR(reply.recipient)))
+ RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
+ }
+
+ /*
+ * Bounce null recipients. This should never happen, but is most
+ * likely the result of a fault in a different program, so aborting
+ * the queue manager process does not help.
+ */
+ if (recipient->address[0] == 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR,
+ "5.1.3 null recipient address");
+ }
+
+ /*
+ * Redirect a forced-to-expire message without defer log to the retry
+ * service, so that its defer log will contain an appropriate reason.
+ * Do not redirect such a message to the error service, because if
+ * that request fails, a defer log would be created with reason
+ * "bounce or trace service failure" which would make no sense. Note
+ * that if the bounce service fails to create a defer log, the
+ * message will be returned as undeliverable anyway, because it is
+ * expired.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.7.0 message is administratively expired");
+ }
+
+ /*
+ * Discard mail to the local double bounce address here, so this
+ * system can run without a local delivery agent. They'd still have
+ * to configure something for mail directed to the local postmaster,
+ * though, but that is an RFC requirement anyway.
+ *
+ * XXX This lookup should be done in the resolver, and the mail should
+ * be directed to a general-purpose null delivery agent.
+ */
+ if (reply.flags & RESOLVE_CLASS_LOCAL) {
+ at = strrchr(STR(reply.recipient), '@');
+ len = (at ? (at - STR(reply.recipient))
+ : strlen(STR(reply.recipient)));
+ if (strncasecmp_utf8(STR(reply.recipient),
+ var_double_bounce_sender, len) == 0
+ && !var_double_bounce_sender[len]) {
+ status = sent(message->tflags, message->queue_id,
+ QMGR_MSG_STATS(&stats, message), recipient,
+ "none", DSN_SIMPLE(&dsn, "2.0.0",
+ "undeliverable postmaster notification discarded"));
+ if (status == 0) {
+ deliver_completed(message->fp, recipient->offset);
+#if 0
+ /* It's the default verification probe sender address. */
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ message->queue_id);
+#endif
+ } else
+ message->flags |= status;
+ continue;
+ }
+ }
+
+ /*
+ * Optionally defer deliveries over specific transports, unless the
+ * restriction is lifted temporarily.
+ */
+ if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) {
+ if (defer_xport_argv == 0)
+ defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP);
+ for (cpp = defer_xport_argv->argv; *cpp; cpp++)
+ if (strcmp(*cpp, STR(reply.transport)) == 0)
+ break;
+ if (*cpp) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.3.2 deferred transport");
+ }
+ }
+
+ /*
+ * Safety: defer excess address verification requests.
+ */
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0
+ && qmgr_vrfy_pend_count > var_vrfy_pend_limit)
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.3.2 Too many address verification requests");
+
+ /*
+ * Look up or instantiate the proper transport.
+ */
+ if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) {
+ if ((transport = qmgr_transport_find(STR(reply.transport))) == 0)
+ transport = qmgr_transport_create(STR(reply.transport));
+ queue = 0;
+ }
+
+ /*
+ * This message is being flushed. If need-be unthrottle the
+ * transport.
+ */
+ if ((message->qflags & QMGR_FLUSH_EACH) != 0
+ && QMGR_TRANSPORT_THROTTLED(transport))
+ qmgr_transport_unthrottle(transport);
+
+ /*
+ * This transport is dead. Defer delivery to this recipient.
+ */
+ if (QMGR_TRANSPORT_THROTTLED(transport)) {
+ saved_dsn = transport->dsn;
+ if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) {
+ nexthop = qmgr_error_nexthop(saved_dsn);
+ vstring_strcpy(reply.nexthop, nexthop);
+ myfree(nexthop);
+ queue = 0;
+ } else {
+ qmgr_defer_recipient(message, recipient, saved_dsn);
+ continue;
+ }
+ }
+
+ /*
+ * The nexthop destination provides the default name for the
+ * per-destination queue. When the delivery agent accepts only one
+ * recipient per delivery, give each recipient its own queue, so that
+ * deliveries to different recipients of the same message can happen
+ * in parallel, and so that we can enforce per-recipient concurrency
+ * limits and prevent one recipient from tying up all the delivery
+ * agent resources. We use recipient@nexthop as queue name rather
+ * than the actual recipient domain name, so that one recipient in
+ * multiple equivalent domains cannot evade the per-recipient
+ * concurrency limit. Split the address on the recipient delimiter if
+ * one is defined, so that extended addresses don't get extra
+ * delivery slots.
+ *
+ * Fold the result to lower case so that we don't have multiple queues
+ * for the same name.
+ *
+ * Important! All recipients in a queue must have the same nexthop
+ * value. It is OK to have multiple queues with the same nexthop
+ * value, but only when those queues are named after recipients.
+ *
+ * The single-recipient code below was written for local(8) like
+ * delivery agents, and assumes that all domains that deliver to the
+ * same (transport + nexthop) are aliases for $nexthop. Delivery
+ * concurrency is changed from per-domain into per-recipient, by
+ * changing the queue name from nexthop into localpart@nexthop.
+ *
+ * XXX This assumption is incorrect when different destinations share
+ * the same (transport + nexthop). In reality, such transports are
+ * rarely configured to use single-recipient deliveries. The fix is
+ * to decouple the per-destination recipient limit from the
+ * per-destination concurrency.
+ */
+ vstring_strcpy(queue_name, STR(reply.nexthop));
+ if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0
+ && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0
+ && transport->recipient_limit == 1) {
+ /* Copy the recipient localpart. */
+ at = strrchr(STR(reply.recipient), '@');
+ len = (at ? (at - STR(reply.recipient))
+ : strlen(STR(reply.recipient)));
+ vstring_strncpy(queue_name, STR(reply.recipient), len);
+ /* Remove the address extension from the recipient localpart. */
+ if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim))
+ vstring_truncate(queue_name, strlen(STR(queue_name)));
+ /* Assume the recipient domain is equivalent to nexthop. */
+ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
+ }
+ lowercase(STR(queue_name));
+
+ /*
+ * This transport is alive. Find or instantiate a queue for this
+ * recipient.
+ */
+ if (queue == 0 || !STREQ(queue->name, STR(queue_name))) {
+ if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0)
+ queue = qmgr_queue_create(transport, STR(queue_name),
+ STR(reply.nexthop));
+ }
+
+ /*
+ * This message is being flushed. If need-be unthrottle the queue.
+ */
+ if ((message->qflags & QMGR_FLUSH_EACH) != 0
+ && QMGR_QUEUE_THROTTLED(queue))
+ qmgr_queue_unthrottle(queue);
+
+ /*
+ * This queue is dead. Defer delivery to this recipient.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ saved_dsn = queue->dsn;
+ if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) {
+ qmgr_defer_recipient(message, recipient, saved_dsn);
+ continue;
+ }
+ }
+
+ /*
+ * This queue is alive. Bind this recipient to this queue instance.
+ */
+ recipient->u.queue = queue;
+ }
+ resolve_clnt_free(&reply);
+ vstring_free(queue_name);
+}
+
+/* qmgr_message_assign - assign recipients to specific delivery requests */
+
+static void qmgr_message_assign(QMGR_MESSAGE *message)
+{
+ RECIPIENT_LIST list = message->rcpt_list;
+ RECIPIENT *recipient;
+ QMGR_ENTRY *entry = 0;
+ QMGR_QUEUE *queue;
+ QMGR_JOB *job = 0;
+ QMGR_PEER *peer = 0;
+
+ /*
+ * Try to bundle as many recipients in a delivery request as we can. When
+ * the recipient resolves to the same site and transport as an existing
+ * recipient, do not create a new queue entry, just move that recipient
+ * to the recipient list of the existing queue entry. All this provided
+ * that we do not exceed the transport-specific limit on the number of
+ * recipients per transaction.
+ */
+#define LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit)))
+
+ for (recipient = list.info; recipient < list.info + list.len; recipient++) {
+
+ /*
+ * Skip recipients with a dead transport or destination.
+ */
+ if ((queue = recipient->u.queue) == 0)
+ continue;
+
+ /*
+ * Lookup or instantiate the message job if necessary.
+ */
+ if (job == 0 || queue->transport != job->transport) {
+ job = qmgr_job_obtain(message, queue->transport);
+ peer = 0;
+ }
+
+ /*
+ * Lookup or instantiate job peer if necessary.
+ */
+ if (peer == 0 || queue != peer->queue)
+ peer = qmgr_peer_obtain(job, queue);
+
+ /*
+ * Lookup old or instantiate new recipient entry. We try to reuse the
+ * last existing entry whenever the recipient limit permits.
+ */
+ entry = peer->entry_list.prev;
+ if (message->single_rcpt || entry == 0
+ || !LIMIT_OK(queue->transport->recipient_limit, entry->rcpt_list.len))
+ entry = qmgr_entry_create(peer, message);
+
+ /*
+ * Add the recipient to the current entry and increase all those
+ * recipient counters accordingly.
+ */
+ recipient_list_add(&entry->rcpt_list, recipient->offset,
+ recipient->dsn_orcpt, recipient->dsn_notify,
+ recipient->orig_addr, recipient->address);
+ job->rcpt_count++;
+ message->rcpt_count++;
+ qmgr_recipient_count++;
+ }
+
+ /*
+ * Release the message recipient list and reinitialize it for the next
+ * time.
+ */
+ recipient_list_free(&message->rcpt_list);
+ recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
+
+ /*
+ * Note that even if qmgr_job_obtain() reset the job candidate cache of
+ * all transports to which we assigned new recipients, this message may
+ * have other jobs which we didn't touch at all this time. But the number
+ * of unread recipients affecting the candidate selection might have
+ * changed considerably, so we must invalidate the caches if it might be
+ * of some use.
+ */
+ for (job = message->job_list.next; job; job = job->message_peers.next)
+ if (job->selected_entries < job->read_entries
+ && job->blocker_tag != job->transport->blocker_tag)
+ job->transport->candidate_cache_current = 0;
+}
+
+/* qmgr_message_move_limits - recycle unused recipient slots */
+
+static void qmgr_message_move_limits(QMGR_MESSAGE *message)
+{
+ QMGR_JOB *job;
+
+ for (job = message->job_list.next; job; job = job->message_peers.next)
+ qmgr_job_move_limits(job);
+}
+
+/* qmgr_message_free - release memory for in-core message structure */
+
+void qmgr_message_free(QMGR_MESSAGE *message)
+{
+ QMGR_JOB *job;
+
+ if (message->refcount != 0)
+ msg_panic("qmgr_message_free: reference len: %d", message->refcount);
+ if (message->fp)
+ msg_panic("qmgr_message_free: queue file is open");
+ while ((job = message->job_list.next) != 0)
+ qmgr_job_free(job);
+ myfree(message->queue_id);
+ myfree(message->queue_name);
+ if (message->dsn_envid)
+ myfree(message->dsn_envid);
+ if (message->encoding)
+ myfree(message->encoding);
+ if (message->sender)
+ myfree(message->sender);
+ if (message->verp_delims)
+ myfree(message->verp_delims);
+ if (message->filter_xport)
+ myfree(message->filter_xport);
+ if (message->inspect_xport)
+ myfree(message->inspect_xport);
+ if (message->redirect_addr)
+ myfree(message->redirect_addr);
+ if (message->client_name)
+ myfree(message->client_name);
+ if (message->client_addr)
+ myfree(message->client_addr);
+ if (message->client_port)
+ myfree(message->client_port);
+ if (message->client_proto)
+ myfree(message->client_proto);
+ if (message->client_helo)
+ myfree(message->client_helo);
+ if (message->sasl_method)
+ myfree(message->sasl_method);
+ if (message->sasl_username)
+ myfree(message->sasl_username);
+ if (message->sasl_sender)
+ myfree(message->sasl_sender);
+ if (message->log_ident)
+ myfree(message->log_ident);
+ if (message->rewrite_context)
+ myfree(message->rewrite_context);
+ recipient_list_free(&message->rcpt_list);
+ qmgr_message_count--;
+ if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0)
+ qmgr_vrfy_pend_count--;
+ myfree((void *) message);
+}
+
+/* qmgr_message_alloc - create in-core message structure */
+
+QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
+ int qflags, mode_t mode)
+{
+ const char *myname = "qmgr_message_alloc";
+ QMGR_MESSAGE *message;
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, queue_name, queue_id);
+
+ /*
+ * Create an in-core message structure.
+ */
+ message = qmgr_message_create(queue_name, queue_id, qflags);
+
+ /*
+ * Extract message envelope information: time of arrival, sender address,
+ * recipient addresses. Skip files with malformed envelope information.
+ */
+#define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT)
+
+ if (qmgr_message_open(message) < 0) {
+ qmgr_message_free(message);
+ return (0);
+ }
+ if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) {
+ msg_info("%s: skipped, still being delivered", queue_id);
+ qmgr_message_close(message);
+ qmgr_message_free(message);
+ return (QMGR_MESSAGE_LOCKED);
+ }
+ if (qmgr_message_read(message) < 0) {
+ qmgr_message_close(message);
+ qmgr_message_free(message);
+ return (0);
+ } else {
+
+ /*
+ * We have validated the queue file content, so it is safe to modify
+ * the file properties now.
+ */
+ if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0)
+ msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp));
+
+ /*
+ * If this message is forced to expire, use the existing defer
+ * logfile records and do not assign any deliveries, leaving the
+ * refcount at zero. If this message is forced to expire, but no
+ * defer logfile records are available, assign deliveries to the
+ * retry transport so that the sender will still find out what
+ * recipients are affected and why. Either way, do not assign normal
+ * deliveries because that would be undesirable especially with mail
+ * that was expired in the 'hold' queue.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0
+ && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER,
+ queue_id), &st) == 0 && st.st_size > 0) {
+ /* Use this defer log; don't assign deliveries (refcount == 0). */
+ message->flags = 1; /* simplify downstream code */
+ qmgr_message_close(message);
+ return (message);
+ }
+
+ /*
+ * Reset the defer log. This code should not be here, but we must
+ * reset the defer log *after* acquiring the exclusive lock on the
+ * queue file and *before* resolving new recipients. Since all those
+ * operations are encapsulated so nicely by this routine, the defer
+ * log reset has to be done here as well.
+ *
+ * Note: it is safe to remove the defer logfile from a previous queue
+ * run of this queue file, because the defer log contains information
+ * about recipients that still exist in this queue file.
+ */
+ if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT)
+ msg_fatal("%s: %s: remove %s %s: %m", myname,
+ queue_id, MAIL_QUEUE_DEFER, queue_id);
+ qmgr_message_sort(message);
+ qmgr_message_resolve(message);
+ qmgr_message_sort(message);
+ qmgr_message_assign(message);
+ qmgr_message_close(message);
+ if (message->rcpt_offset == 0)
+ qmgr_message_move_limits(message);
+ return (message);
+ }
+}
+
+/* qmgr_message_realloc - refresh in-core message structure */
+
+QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message)
+{
+ const char *myname = "qmgr_message_realloc";
+
+ /*
+ * Sanity checks.
+ */
+ if (message->rcpt_offset <= 0)
+ msg_panic("%s: invalid offset: %ld", myname, message->rcpt_offset);
+ if (msg_verbose)
+ msg_info("%s: %s %s offset %ld", myname, message->queue_name,
+ message->queue_id, message->rcpt_offset);
+
+ /*
+ * Extract recipient addresses. Skip files with malformed envelope
+ * information.
+ */
+ if (qmgr_message_open(message) < 0)
+ return (0);
+ if (qmgr_message_read(message) < 0) {
+ qmgr_message_close(message);
+ return (0);
+ } else {
+ qmgr_message_sort(message);
+ qmgr_message_resolve(message);
+ qmgr_message_sort(message);
+ qmgr_message_assign(message);
+ qmgr_message_close(message);
+ if (message->rcpt_offset == 0)
+ qmgr_message_move_limits(message);
+ return (message);
+ }
+}
diff --git a/src/qmgr/qmgr_move.c b/src/qmgr/qmgr_move.c
new file mode 100644
index 0000000..e68f803
--- /dev/null
+++ b/src/qmgr/qmgr_move.c
@@ -0,0 +1,104 @@
+/*++
+/* NAME
+/* qmgr_move 3
+/* SUMMARY
+/* move queue entries to another queue
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* void qmgr_move(from, to, time_stamp)
+/* const char *from;
+/* const char *to;
+/* time_t time_stamp;
+/* DESCRIPTION
+/* The \fBqmgr_move\fR routine scans the \fIfrom\fR queue for entries
+/* with valid queue names and moves them to the \fIto\fR queue.
+/* If \fItime_stamp\fR is non-zero, the queue file time stamps are
+/* set to the specified value.
+/* Entries with invalid names are left alone. No attempt is made to
+/* look for other badness such as multiple links or weird file types.
+/* These issues are dealt with when a queue file is actually opened.
+/* 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>
+#include <string.h>
+#include <utime.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <scan_dir.h>
+#include <recipient_list.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_scan_dir.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_move - move queue entries to another queue, leave bad files alone */
+
+void qmgr_move(const char *src_queue, const char *dst_queue,
+ time_t time_stamp)
+{
+ const char *myname = "qmgr_move";
+ SCAN_DIR *queue_dir;
+ char *queue_id;
+ struct utimbuf tbuf;
+ const char *path;
+
+ if (strcmp(src_queue, dst_queue) == 0)
+ msg_panic("%s: source queue %s is destination", myname, src_queue);
+ if (msg_verbose)
+ msg_info("start move queue %s -> %s", src_queue, dst_queue);
+
+ queue_dir = scan_dir_open(src_queue);
+ while ((queue_id = mail_scan_dir_next(queue_dir)) != 0) {
+ if (mail_queue_id_ok(queue_id)) {
+ if (time_stamp > 0) {
+ tbuf.actime = tbuf.modtime = time_stamp;
+ path = mail_queue_path((VSTRING *) 0, src_queue, queue_id);
+ if (utime(path, &tbuf) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("%s: update %s time stamps: %m", myname, path);
+ msg_warn("%s: update %s time stamps: %m", myname, path);
+ continue;
+ }
+ }
+ if (mail_queue_rename(queue_id, src_queue, dst_queue)) {
+ if (errno != ENOENT)
+ msg_fatal("%s: rename %s from %s to %s: %m",
+ myname, queue_id, src_queue, dst_queue);
+ msg_warn("%s: rename %s from %s to %s: %m",
+ myname, queue_id, src_queue, dst_queue);
+ continue;
+ }
+ if (msg_verbose)
+ msg_info("%s: moved %s from %s to %s",
+ myname, queue_id, src_queue, dst_queue);
+ } else {
+ msg_warn("%s: ignored: queue %s id %s",
+ myname, src_queue, queue_id);
+ }
+ }
+ scan_dir_close(queue_dir);
+
+ if (msg_verbose)
+ msg_info("end move queue %s -> %s", src_queue, dst_queue);
+}
diff --git a/src/qmgr/qmgr_peer.c b/src/qmgr/qmgr_peer.c
new file mode 100644
index 0000000..ebf9632
--- /dev/null
+++ b/src/qmgr/qmgr_peer.c
@@ -0,0 +1,154 @@
+/*++
+/* NAME
+/* qmgr_peer 3
+/* SUMMARY
+/* per-job peers
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_PEER *qmgr_peer_create(job, queue)
+/* QMGR_JOB *job;
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_PEER *qmgr_peer_find(job, queue)
+/* QMGR_JOB *job;
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_PEER *qmgr_peer_obtain(job, queue)
+/* QMGR_JOB *job;
+/* QMGR_QUEUE *queue;
+/*
+/* void qmgr_peer_free(peer)
+/* QMGR_PEER *peer;
+/*
+/* QMGR_PEER *qmgr_peer_select(job)
+/* QMGR_JOB *job;
+/*
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-job peers.
+/* Each peer corresponds to a specific job and destination.
+/* It is similar to per-transport queue structure, but groups
+/* only the entries of the given job.
+/*
+/* qmgr_peer_create() creates an empty peer structure for the named
+/* job and destination. It is an error to call this function
+/* if a peer for given combination already exists.
+/*
+/* qmgr_peer_find() looks up the peer for the named destination
+/* for the named job. A null result means that the peer
+/* was not found.
+/*
+/* qmgr_peer_obtain() looks up the peer for the named destination
+/* for the named job. If it doesn't exist yet, it creates it.
+/*
+/* qmgr_peer_free() disposes of a per-job peer after all
+/* its entries have been taken care of. It is an error to dispose
+/* of a peer still in use.
+/*
+/* qmgr_peer_select() attempts to find a peer of named job that
+/* has messages pending delivery. This routine implements
+/* round-robin search among job's peers.
+/* DIAGNOSTICS
+/* Panic: consistency check failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* patrik@raxoft.cz
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <mymalloc.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_peer_create - create and initialize message peer structure */
+
+QMGR_PEER *qmgr_peer_create(QMGR_JOB *job, QMGR_QUEUE *queue)
+{
+ QMGR_PEER *peer;
+
+ peer = (QMGR_PEER *) mymalloc(sizeof(QMGR_PEER));
+ peer->queue = queue;
+ peer->job = job;
+ QMGR_LIST_APPEND(job->peer_list, peer, peers);
+ htable_enter(job->peer_byname, queue->name, (void *) peer);
+ peer->refcount = 0;
+ QMGR_LIST_INIT(peer->entry_list);
+ return (peer);
+}
+
+/* qmgr_peer_free - release peer structure */
+
+void qmgr_peer_free(QMGR_PEER *peer)
+{
+ const char *myname = "qmgr_peer_free";
+ QMGR_JOB *job = peer->job;
+ QMGR_QUEUE *queue = peer->queue;
+
+ /*
+ * Sanity checks. It is an error to delete a referenced peer structure.
+ */
+ if (peer->refcount != 0)
+ msg_panic("%s: refcount: %d", myname, peer->refcount);
+ if (peer->entry_list.next != 0)
+ msg_panic("%s: entry list not empty: %s", myname, queue->name);
+
+ QMGR_LIST_UNLINK(job->peer_list, QMGR_PEER *, peer, peers);
+ htable_delete(job->peer_byname, queue->name, (void (*) (void *)) 0);
+ myfree((void *) peer);
+}
+
+/* qmgr_peer_find - lookup peer associated with given job and queue */
+
+QMGR_PEER *qmgr_peer_find(QMGR_JOB *job, QMGR_QUEUE *queue)
+{
+ return ((QMGR_PEER *) htable_find(job->peer_byname, queue->name));
+}
+
+/* qmgr_peer_obtain - find/create peer associated with given job and queue */
+
+QMGR_PEER *qmgr_peer_obtain(QMGR_JOB *job, QMGR_QUEUE *queue)
+{
+ QMGR_PEER *peer;
+
+ if ((peer = qmgr_peer_find(job, queue)) == 0)
+ peer = qmgr_peer_create(job, queue);
+ return (peer);
+}
+
+/* qmgr_peer_select - select next peer suitable for delivery within given job */
+
+QMGR_PEER *qmgr_peer_select(QMGR_JOB *job)
+{
+ QMGR_PEER *peer;
+ QMGR_QUEUE *queue;
+
+ /*
+ * If we find a suitable site, rotate the list to enforce round-robin
+ * selection. See similar selection code in qmgr_transport_select().
+ */
+ for (peer = job->peer_list.next; peer; peer = peer->peers.next) {
+ queue = peer->queue;
+ if (queue->window > queue->busy_refcount && peer->entry_list.next != 0) {
+ QMGR_LIST_ROTATE(job->peer_list, peer, peers);
+ if (msg_verbose)
+ msg_info("qmgr_peer_select: %s %s %s (%d of %d)",
+ job->message->queue_id, queue->transport->name, queue->name,
+ queue->busy_refcount + 1, queue->window);
+ return (peer);
+ }
+ }
+ return (0);
+}
diff --git a/src/qmgr/qmgr_queue.c b/src/qmgr/qmgr_queue.c
new file mode 100644
index 0000000..d2ffe09
--- /dev/null
+++ b/src/qmgr/qmgr_queue.c
@@ -0,0 +1,439 @@
+/*++
+/* NAME
+/* qmgr_queue 3
+/* SUMMARY
+/* per-destination queues
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* int qmgr_queue_count;
+/*
+/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop)
+/* QMGR_TRANSPORT *transport;
+/* const char *name;
+/* const char *nexthop;
+/*
+/* void qmgr_queue_done(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* QMGR_QUEUE *qmgr_queue_find(transport, name)
+/* QMGR_TRANSPORT *transport;
+/* const char *name;
+/*
+/* void qmgr_queue_throttle(queue, dsn)
+/* QMGR_QUEUE *queue;
+/* DSN *dsn;
+/*
+/* void qmgr_queue_unthrottle(queue)
+/* QMGR_QUEUE *queue;
+/*
+/* void qmgr_queue_suspend(queue, delay)
+/* QMGR_QUEUE *queue;
+/* int delay;
+/* DESCRIPTION
+/* These routines add/delete/manipulate per-destination queues.
+/* Each queue corresponds to a specific transport and destination.
+/* Each queue has a `todo' list of delivery requests for that
+/* destination, and a `busy' list of delivery requests in progress.
+/*
+/* qmgr_queue_count is a global counter for the total number
+/* of in-core queue structures.
+/*
+/* qmgr_queue_create() creates an empty named queue for the named
+/* transport and destination. The queue is given an initial
+/* concurrency limit as specified with the
+/* \fIinitial_destination_concurrency\fR configuration parameter,
+/* provided that it does not exceed the transport-specific
+/* concurrency limit.
+/*
+/* qmgr_queue_done() disposes of a per-destination queue after all
+/* its entries have been taken care of. It is an error to dispose
+/* of a dead queue.
+/*
+/* qmgr_queue_find() looks up the named queue for the named
+/* transport. A null result means that the queue was not found.
+/*
+/* qmgr_queue_throttle() handles a delivery error, and decrements the
+/* concurrency limit for the destination, with a lower bound of 1.
+/* When the cohort failure bound is reached, qmgr_queue_throttle()
+/* sets the concurrency limit to zero and starts a timer
+/* to re-enable delivery to the destination after a configurable delay.
+/*
+/* qmgr_queue_unthrottle() undoes qmgr_queue_throttle()'s effects.
+/* The concurrency limit for the destination is incremented,
+/* provided that it does not exceed the destination concurrency
+/* limit specified for the transport. This routine implements
+/* "slow open" mode, and eliminates the "thundering herd" problem.
+/*
+/* qmgr_queue_suspend() suspends delivery for this destination
+/* briefly. This function invalidates any scheduling decisions
+/* that are based on the present queue's concurrency window.
+/* To compensate for work skipped by qmgr_entry_done(), the
+/* status of blocker jobs is re-evaluated after the queue is
+/* resumed.
+/* DIAGNOSTICS
+/* Panic: consistency check failure.
+/* 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
+/*
+/* Pre-emptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*
+/* Concurrency scheduler enhancements with:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <recipient_list.h>
+#include <mail_proto.h> /* QMGR_LOG_WINDOW */
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+int qmgr_queue_count;
+
+#define QMGR_ERROR_OR_RETRY_QUEUE(queue) \
+ (strcmp(queue->transport->name, MAIL_SERVICE_RETRY) == 0 \
+ || strcmp(queue->transport->name, MAIL_SERVICE_ERROR) == 0)
+
+#define QMGR_LOG_FEEDBACK(feedback) \
+ if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \
+ msg_info("%s: feedback %g", myname, feedback);
+
+#define QMGR_LOG_WINDOW(queue) \
+ if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \
+ msg_info("%s: queue %s: limit %d window %d success %g failure %g fail_cohorts %g", \
+ myname, queue->name, queue->transport->dest_concurrency_limit, \
+ queue->window, queue->success, queue->failure, queue->fail_cohorts);
+
+/* qmgr_queue_resume - resume delivery to destination */
+
+static void qmgr_queue_resume(int event, void *context)
+{
+ QMGR_QUEUE *queue = (QMGR_QUEUE *) context;
+ const char *myname = "qmgr_queue_resume";
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_SUSPENDED(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+
+ /*
+ * We can't simply force delivery on this queue: the transport's pending
+ * count may already be maxed out, and there may be other constraints
+ * that definitely should be none of our business. The best we can do is
+ * to play by the same rules as everyone else: let qmgr_active_drain()
+ * and round-robin selection take care of message selection.
+ */
+ queue->window = 1;
+
+ /*
+ * Every event handler that leaves a queue in the "ready" state should
+ * remove the queue when it is empty.
+ *
+ * XXX Do not omit the redundant test below. It is here to simplify code
+ * consistency checks. The check is trivially eliminated by the compiler
+ * optimizer. There is no need to sacrifice code clarity for the sake of
+ * performance.
+ *
+ * XXX Do not expose the blocker job logic here. Rate-limited queues are not
+ * a performance-critical feature. Here, too, there is no need to
+ * sacrifice code clarity for the sake of performance.
+ */
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+ else
+ qmgr_job_blocker_update(queue);
+}
+
+/* qmgr_queue_suspend - briefly suspend a destination */
+
+void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay)
+{
+ const char *myname = "qmgr_queue_suspend";
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->busy_refcount > 0)
+ msg_panic("%s: queue is busy", myname);
+
+ /*
+ * Set the queue status to "suspended". No-one is supposed to remove a
+ * queue in suspended state.
+ */
+ queue->window = QMGR_QUEUE_STAT_SUSPENDED;
+ event_request_timer(qmgr_queue_resume, (void *) queue, delay);
+}
+
+/* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */
+
+static void qmgr_queue_unthrottle_wrapper(int unused_event, void *context)
+{
+ QMGR_QUEUE *queue = (QMGR_QUEUE *) context;
+
+ /*
+ * This routine runs when a wakeup timer goes off; it does not run in the
+ * context of some queue manipulation. Therefore, it is safe to discard
+ * this in-core queue when it is empty and when this site is not dead.
+ */
+ qmgr_queue_unthrottle(queue);
+ if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0)
+ qmgr_queue_done(queue);
+}
+
+/* qmgr_queue_unthrottle - give this destination another chance */
+
+void qmgr_queue_unthrottle(QMGR_QUEUE *queue)
+{
+ const char *myname = "qmgr_queue_unthrottle";
+ QMGR_TRANSPORT *transport = queue->transport;
+ double feedback;
+
+ if (msg_verbose)
+ msg_info("%s: queue %s", myname, queue->name);
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_READY(queue) && !QMGR_QUEUE_THROTTLED(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+
+ /*
+ * Don't restart the negative feedback hysteresis cycle with every
+ * positive feedback. Restart it only when we make a positive concurrency
+ * adjustment (i.e. at the end of a positive feedback hysteresis cycle).
+ * Otherwise negative feedback would be too aggressive: negative feedback
+ * takes effect immediately at the start of its hysteresis cycle.
+ */
+ queue->fail_cohorts = 0;
+
+ /*
+ * Special case when this site was dead.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ event_cancel_timer(qmgr_queue_unthrottle_wrapper, (void *) queue);
+ if (queue->dsn == 0)
+ msg_panic("%s: queue %s: window 0 status 0", myname, queue->name);
+ dsn_free(queue->dsn);
+ queue->dsn = 0;
+ /* Back from the almost grave, best concurrency is anyone's guess. */
+ if (queue->busy_refcount > 0)
+ queue->window = queue->busy_refcount;
+ else
+ queue->window = transport->init_dest_concurrency;
+ queue->success = queue->failure = 0;
+ QMGR_LOG_WINDOW(queue);
+ return;
+ }
+
+ /*
+ * Increase the destination's concurrency limit until we reach the
+ * transport's concurrency limit. Allow for a margin the size of the
+ * initial destination concurrency, so that we're not too gentle.
+ *
+ * Why is the concurrency increment based on preferred concurrency and not
+ * on the number of outstanding delivery requests? The latter fluctuates
+ * wildly when deliveries complete in bursts (artificial benchmark
+ * measurements), and does not account for cached connections.
+ *
+ * Keep the window within reasonable distance from actual concurrency
+ * otherwise negative feedback will be ineffective. This expression
+ * assumes that busy_refcount changes gradually. This is invalid when
+ * deliveries complete in bursts (artificial benchmark measurements).
+ */
+ if (transport->dest_concurrency_limit == 0
+ || transport->dest_concurrency_limit > queue->window)
+ if (queue->window < queue->busy_refcount + transport->init_dest_concurrency) {
+ feedback = QMGR_FEEDBACK_VAL(transport->pos_feedback, queue->window);
+ QMGR_LOG_FEEDBACK(feedback);
+ queue->success += feedback;
+ /* Prepare for overshoot (feedback > hysteresis, rounding error). */
+ while (queue->success + feedback / 2 >= transport->pos_feedback.hysteresis) {
+ queue->window += transport->pos_feedback.hysteresis;
+ queue->success -= transport->pos_feedback.hysteresis;
+ queue->failure = 0;
+ }
+ /* Prepare for overshoot. */
+ if (transport->dest_concurrency_limit > 0
+ && queue->window > transport->dest_concurrency_limit)
+ queue->window = transport->dest_concurrency_limit;
+ }
+ QMGR_LOG_WINDOW(queue);
+}
+
+/* qmgr_queue_throttle - handle destination delivery failure */
+
+void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn)
+{
+ const char *myname = "qmgr_queue_throttle";
+ QMGR_TRANSPORT *transport = queue->transport;
+ double feedback;
+
+ /*
+ * Sanity checks.
+ */
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->dsn)
+ msg_panic("%s: queue %s: spurious reason %s",
+ myname, queue->name, queue->dsn->reason);
+ if (msg_verbose)
+ msg_info("%s: queue %s: %s %s",
+ myname, queue->name, dsn->status, dsn->reason);
+
+ /*
+ * Don't restart the positive feedback hysteresis cycle with every
+ * negative feedback. Restart it only when we make a negative concurrency
+ * adjustment (i.e. at the start of a negative feedback hysteresis
+ * cycle). Otherwise positive feedback would be too weak (positive
+ * feedback does not take effect until the end of its hysteresis cycle).
+ */
+
+ /*
+ * This queue is declared dead after a configurable number of
+ * pseudo-cohort failures.
+ */
+ if (QMGR_QUEUE_READY(queue)) {
+ queue->fail_cohorts += 1.0 / queue->window;
+ if (transport->fail_cohort_limit > 0
+ && queue->fail_cohorts >= transport->fail_cohort_limit)
+ queue->window = QMGR_QUEUE_STAT_THROTTLED;
+ }
+
+ /*
+ * Decrease the destination's concurrency limit until we reach 1. Base
+ * adjustments on the concurrency limit itself, instead of using the
+ * actual concurrency. The latter fluctuates wildly when deliveries
+ * complete in bursts (artificial benchmark measurements).
+ *
+ * Even after reaching 1, we maintain the negative hysteresis cycle so that
+ * negative feedback can cancel out positive feedback.
+ */
+ if (QMGR_QUEUE_READY(queue)) {
+ feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window);
+ QMGR_LOG_FEEDBACK(feedback);
+ queue->failure -= feedback;
+ /* Prepare for overshoot (feedback > hysteresis, rounding error). */
+ while (queue->failure - feedback / 2 < 0) {
+ queue->window -= transport->neg_feedback.hysteresis;
+ queue->success = 0;
+ queue->failure += transport->neg_feedback.hysteresis;
+ }
+ /* Prepare for overshoot. */
+ if (queue->window < 1)
+ queue->window = 1;
+ }
+
+ /*
+ * Special case for a site that just was declared dead.
+ */
+ if (QMGR_QUEUE_THROTTLED(queue)) {
+ queue->dsn = DSN_COPY(dsn);
+ event_request_timer(qmgr_queue_unthrottle_wrapper,
+ (void *) queue, var_min_backoff_time);
+ queue->dflags = 0;
+ }
+ QMGR_LOG_WINDOW(queue);
+}
+
+/* qmgr_queue_done - delete in-core queue for site */
+
+void qmgr_queue_done(QMGR_QUEUE *queue)
+{
+ const char *myname = "qmgr_queue_done";
+ QMGR_TRANSPORT *transport = queue->transport;
+
+ /*
+ * Sanity checks. It is an error to delete an in-core queue with pending
+ * messages or timers.
+ */
+ if (queue->busy_refcount != 0 || queue->todo_refcount != 0)
+ msg_panic("%s: refcount: %d", myname,
+ queue->busy_refcount + queue->todo_refcount);
+ if (queue->todo.next || queue->busy.next)
+ msg_panic("%s: queue not empty: %s", myname, queue->name);
+ if (!QMGR_QUEUE_READY(queue))
+ msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue));
+ if (queue->dsn)
+ msg_panic("%s: queue %s: spurious reason %s",
+ myname, queue->name, queue->dsn->reason);
+
+ /*
+ * Clean up this in-core queue.
+ */
+ QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers);
+ htable_delete(transport->queue_byname, queue->name, (void (*) (void *)) 0);
+ myfree(queue->name);
+ myfree(queue->nexthop);
+ qmgr_queue_count--;
+ myfree((void *) queue);
+}
+
+/* qmgr_queue_create - create in-core queue for site */
+
+QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name,
+ const char *nexthop)
+{
+ QMGR_QUEUE *queue;
+
+ /*
+ * If possible, choose an initial concurrency of > 1 so that one bad
+ * message or one bad network won't slow us down unnecessarily.
+ */
+
+ queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE));
+ qmgr_queue_count++;
+ queue->dflags = 0;
+ queue->last_done = 0;
+ queue->name = mystrdup(name);
+ queue->nexthop = mystrdup(nexthop);
+ queue->todo_refcount = 0;
+ queue->busy_refcount = 0;
+ queue->transport = transport;
+ queue->window = transport->init_dest_concurrency;
+ queue->success = queue->failure = queue->fail_cohorts = 0;
+ QMGR_LIST_INIT(queue->todo);
+ QMGR_LIST_INIT(queue->busy);
+ queue->dsn = 0;
+ queue->clog_time_to_warn = 0;
+ queue->blocker_tag = 0;
+ QMGR_LIST_APPEND(transport->queue_list, queue, peers);
+ htable_enter(transport->queue_byname, name, (void *) queue);
+ return (queue);
+}
+
+/* qmgr_queue_find - find in-core named queue */
+
+QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name)
+{
+ return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name));
+}
diff --git a/src/qmgr/qmgr_scan.c b/src/qmgr/qmgr_scan.c
new file mode 100644
index 0000000..0665a23
--- /dev/null
+++ b/src/qmgr/qmgr_scan.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* qmgr_scan 3
+/* SUMMARY
+/* queue scanning
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_SCAN *qmgr_scan_create(queue_name)
+/* const char *queue_name;
+/*
+/* char *qmgr_scan_next(scan_info)
+/* QMGR_SCAN *scan_info;
+/*
+/* void qmgr_scan_request(scan_info, flags)
+/* QMGR_SCAN *scan_info;
+/* int flags;
+/* DESCRIPTION
+/* This module implements queue scans. A queue scan always runs
+/* to completion, so that all files get a fair chance. The caller
+/* can request that a queue scan be restarted once it completes.
+/*
+/* qmgr_scan_create() creates a context for scanning the named queue,
+/* but does not start a queue scan.
+/*
+/* qmgr_scan_next() returns the base name of the next queue file.
+/* A null pointer means that no file was found. qmgr_scan_next()
+/* automagically restarts a queue scan when a scan request had
+/* arrived while the scan was in progress.
+/*
+/* qmgr_scan_request() records a request for the next queue scan. The
+/* flags argument is the bit-wise OR of zero or more of the following,
+/* unrecognized flags being ignored:
+/* .IP QMGR_FLUSH_ONCE
+/* Forget state information about dead hosts or transports.
+/* This request takes effect immediately.
+/* .IP QMGR_FLUSH_DFXP
+/* Override the defer_transports setting. This takes effect
+/* immediately when a queue scan is in progress, and affects
+/* the next queue scan.
+/* .IP QMGR_SCAN_ALL
+/* Ignore queue file time stamps. This takes effect immediately
+/* when a queue scan is in progress, and affects the next queue
+/* scan.
+/* .IP QMGR_SCAN_START
+/* Start a queue scan when none is in progress, or restart the
+/* current scan upon completion.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* Panic: interface violations, internal consistency errors.
+/* 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 <mymalloc.h>
+#include <scan_dir.h>
+
+/* Global library. */
+
+#include <mail_scan_dir.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+/* qmgr_scan_start - start queue scan */
+
+static void qmgr_scan_start(QMGR_SCAN *scan_info)
+{
+ const char *myname = "qmgr_scan_start";
+
+ /*
+ * Sanity check.
+ */
+ if (scan_info->handle)
+ msg_panic("%s: %s queue scan in progress",
+ myname, scan_info->queue);
+
+ /*
+ * Give the poor tester a clue.
+ */
+ if (msg_verbose)
+ msg_info("%s: %sstart %s queue scan",
+ myname,
+ scan_info->nflags & QMGR_SCAN_START ? "re" : "",
+ scan_info->queue);
+
+ /*
+ * Start or restart the scan.
+ */
+ scan_info->flags = scan_info->nflags;
+ scan_info->nflags = 0;
+ scan_info->handle = scan_dir_open(scan_info->queue);
+}
+
+/* qmgr_scan_request - request for future scan */
+
+void qmgr_scan_request(QMGR_SCAN *scan_info, int flags)
+{
+
+ /*
+ * Apply "forget all dead destinations" requests immediately. Throttle
+ * dead transports and queues at the earliest opportunity: preferably
+ * during an already ongoing queue scan, otherwise the throttling will
+ * have to wait until a "start scan" trigger arrives.
+ *
+ * The QMGR_FLUSH_ONCE request always comes with QMGR_FLUSH_DFXP, and
+ * sometimes it also comes with QMGR_SCAN_ALL. It becomes a completely
+ * different story when a flush request is encoded in file permissions.
+ */
+ if (flags & QMGR_FLUSH_ONCE)
+ qmgr_enable_all();
+
+ /*
+ * Apply "ignore time stamp" requests also towards the scan that is
+ * already in progress.
+ */
+ if (scan_info->handle != 0 && (flags & QMGR_SCAN_ALL))
+ scan_info->flags |= QMGR_SCAN_ALL;
+
+ /*
+ * Apply "override defer_transports" requests also towards the scan that
+ * is already in progress.
+ */
+ if (scan_info->handle != 0 && (flags & QMGR_FLUSH_DFXP))
+ scan_info->flags |= QMGR_FLUSH_DFXP;
+
+ /*
+ * If a scan is in progress, just record the request.
+ */
+ scan_info->nflags |= flags;
+ if (scan_info->handle == 0 && (flags & QMGR_SCAN_START) != 0) {
+ scan_info->nflags &= ~QMGR_SCAN_START;
+ qmgr_scan_start(scan_info);
+ }
+}
+
+/* qmgr_scan_next - look for next queue file */
+
+char *qmgr_scan_next(QMGR_SCAN *scan_info)
+{
+ char *path = 0;
+
+ /*
+ * Restart the scan if we reach the end and a queue scan request has
+ * arrived in the mean time.
+ */
+ if (scan_info->handle && (path = mail_scan_dir_next(scan_info->handle)) == 0) {
+ scan_info->handle = scan_dir_close(scan_info->handle);
+ if (msg_verbose && (scan_info->nflags & QMGR_SCAN_START) == 0)
+ msg_info("done %s queue scan", scan_info->queue);
+ }
+ if (!scan_info->handle && (scan_info->nflags & QMGR_SCAN_START)) {
+ qmgr_scan_start(scan_info);
+ path = mail_scan_dir_next(scan_info->handle);
+ }
+ return (path);
+}
+
+/* qmgr_scan_create - create queue scan context */
+
+QMGR_SCAN *qmgr_scan_create(const char *queue)
+{
+ QMGR_SCAN *scan_info;
+
+ scan_info = (QMGR_SCAN *) mymalloc(sizeof(*scan_info));
+ scan_info->queue = mystrdup(queue);
+ scan_info->flags = scan_info->nflags = 0;
+ scan_info->handle = 0;
+ return (scan_info);
+}
diff --git a/src/qmgr/qmgr_transport.c b/src/qmgr/qmgr_transport.c
new file mode 100644
index 0000000..fe99806
--- /dev/null
+++ b/src/qmgr/qmgr_transport.c
@@ -0,0 +1,504 @@
+/*++
+/* NAME
+/* qmgr_transport 3
+/* SUMMARY
+/* per-transport data structures
+/* SYNOPSIS
+/* #include "qmgr.h"
+/*
+/* QMGR_TRANSPORT *qmgr_transport_create(name)
+/* const char *name;
+/*
+/* QMGR_TRANSPORT *qmgr_transport_find(name)
+/* const char *name;
+/*
+/* QMGR_TRANSPORT *qmgr_transport_select()
+/*
+/* void qmgr_transport_alloc(transport, notify)
+/* QMGR_TRANSPORT *transport;
+/* void (*notify)(QMGR_TRANSPORT *transport, VSTREAM *fp);
+/*
+/* void qmgr_transport_throttle(transport, dsn)
+/* QMGR_TRANSPORT *transport;
+/* DSN *dsn;
+/*
+/* void qmgr_transport_unthrottle(transport)
+/* QMGR_TRANSPORT *transport;
+/* DESCRIPTION
+/* This module organizes the world by message transport type.
+/* Each transport can have zero or more destination queues
+/* associated with it.
+/*
+/* qmgr_transport_create() instantiates a data structure for the
+/* named transport type.
+/*
+/* qmgr_transport_find() looks up an existing message transport
+/* data structure.
+/*
+/* qmgr_transport_select() attempts to find a transport that
+/* has messages pending delivery. This routine implements
+/* round-robin search among transports.
+/*
+/* qmgr_transport_alloc() allocates a delivery process for the
+/* specified transport type. Allocation is performed asynchronously.
+/* When a process becomes available, the application callback routine
+/* is invoked with as arguments the transport and a stream that
+/* is connected to a delivery process. It is an error to call
+/* qmgr_transport_alloc() while delivery process allocation for
+/* the same transport is in progress.
+/*
+/* qmgr_transport_throttle blocks further allocation of delivery
+/* processes for the named transport. Attempts to throttle a
+/* throttled transport are ignored.
+/*
+/* qmgr_transport_unthrottle() undoes qmgr_transport_throttle().
+/* Attempts to unthrottle a non-throttled transport are ignored.
+/* DIAGNOSTICS
+/* Panic: consistency check failure. Fatal: out of memory.
+/* 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
+/*
+/* Preemptive scheduler enhancements:
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+
+#include <sys/time.h> /* FD_SETSIZE */
+#include <sys/types.h> /* FD_SETSIZE */
+#include <unistd.h> /* FD_SETSIZE */
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h> /* FD_SETSIZE */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <recipient_list.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "qmgr.h"
+
+HTABLE *qmgr_transport_byname; /* transport by name */
+QMGR_TRANSPORT_LIST qmgr_transport_list;/* transports, round robin */
+
+ /*
+ * A local structure to remember a delivery process allocation request.
+ */
+typedef struct QMGR_TRANSPORT_ALLOC QMGR_TRANSPORT_ALLOC;
+
+struct QMGR_TRANSPORT_ALLOC {
+ QMGR_TRANSPORT *transport; /* transport context */
+ VSTREAM *stream; /* delivery service stream */
+ QMGR_TRANSPORT_ALLOC_NOTIFY notify; /* application call-back routine */
+};
+
+ /*
+ * Connections to delivery agents are managed asynchronously. Each delivery
+ * agent connection goes through multiple wait states:
+ *
+ * - With Linux/Solaris and old queue manager implementations only, wait for
+ * the server to invoke accept().
+ *
+ * - Wait for the delivery agent's announcement that it is ready to receive a
+ * delivery request.
+ *
+ * - Wait for the delivery request completion status.
+ *
+ * Older queue manager implementations had only one pending delivery agent
+ * connection per transport. With low-latency destinations, the output rates
+ * were reduced on Linux/Solaris systems that had the extra wait state.
+ *
+ * To maximize delivery agent output rates with low-latency destinations, the
+ * following changes were made to the queue manager by the end of the 2.4
+ * development cycle:
+ *
+ * - The Linux/Solaris accept() wait state was eliminated.
+ *
+ * - A pipeline was implemented for pending delivery agent connections. The
+ * number of pending delivery agent connections was increased from one to
+ * two: the number of before-delivery wait states, plus one extra pipeline
+ * slot to prevent the pipeline from stalling easily. Increasing the
+ * pipeline much further actually hurt performance.
+ *
+ * - To reduce queue manager disk competition with delivery agents, the queue
+ * scanning algorithm was modified to import only one message per interrupt.
+ * The incoming and deferred queue scans now happen on alternate interrupts.
+ *
+ * Simplistically reasoned, a non-zero (incoming + active) queue length is
+ * equivalent to a time shift for mail deliveries; this is undesirable when
+ * delivery agents are not fully utilized.
+ *
+ * On the other hand a non-empty active queue is what allows us to do clever
+ * things such as queue file prefetch, concurrency windows, and connection
+ * caching; the idea is that such "thinking time" is affordable only after
+ * the output channels are maxed out.
+ */
+#ifndef QMGR_TRANSPORT_MAX_PEND
+#define QMGR_TRANSPORT_MAX_PEND 2
+#endif
+
+ /*
+ * Important note on the _transport_rate_delay implementation: after
+ * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+ * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+ * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+ * _transport_rate_delay will become stuck.
+ */
+
+/* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */
+
+static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context)
+{
+ qmgr_transport_unthrottle((QMGR_TRANSPORT *) context);
+}
+
+/* qmgr_transport_unthrottle - open the throttle */
+
+void qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
+{
+ const char *myname = "qmgr_transport_unthrottle";
+
+ /*
+ * This routine runs after expiration of the timer set by
+ * qmgr_transport_throttle(), or whenever a delivery transport has been
+ * used without malfunction. In either case, we enable delivery again if
+ * the transport was throttled. We always reset the transport rate lock.
+ */
+ if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) {
+ if (msg_verbose)
+ msg_info("%s: transport %s", myname, transport->name);
+ transport->flags &= ~QMGR_TRANSPORT_STAT_DEAD;
+ if (transport->dsn == 0)
+ msg_panic("%s: transport %s: null reason",
+ myname, transport->name);
+ dsn_free(transport->dsn);
+ transport->dsn = 0;
+ event_cancel_timer(qmgr_transport_unthrottle_wrapper,
+ (void *) transport);
+ }
+ if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+ transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK;
+}
+
+/* qmgr_transport_throttle - disable delivery process allocation */
+
+void qmgr_transport_throttle(QMGR_TRANSPORT *transport, DSN *dsn)
+{
+ const char *myname = "qmgr_transport_throttle";
+
+ /*
+ * We are unable to connect to a deliver process for this type of message
+ * transport. Instead of hosing the system by retrying in a tight loop,
+ * back off and disable this transport type for a while.
+ */
+ if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) == 0) {
+ if (msg_verbose)
+ msg_info("%s: transport %s: status: %s reason: %s",
+ myname, transport->name, dsn->status, dsn->reason);
+ transport->flags |= QMGR_TRANSPORT_STAT_DEAD;
+ if (transport->dsn)
+ msg_panic("%s: transport %s: spurious reason: %s",
+ myname, transport->name, transport->dsn->reason);
+ transport->dsn = DSN_COPY(dsn);
+ event_request_timer(qmgr_transport_unthrottle_wrapper,
+ (void *) transport, var_transport_retry_time);
+ }
+}
+
+/* qmgr_transport_abort - transport connect watchdog */
+
+static void qmgr_transport_abort(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ msg_fatal("timeout connecting to transport: %s", alloc->transport->name);
+}
+
+/* qmgr_transport_rate_event - delivery process availability notice */
+
+static void qmgr_transport_rate_event(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ alloc->notify(alloc->transport, alloc->stream);
+ myfree((void *) alloc);
+}
+
+/* qmgr_transport_event - delivery process availability notice */
+
+static void qmgr_transport_event(int unused_event, void *context)
+{
+ QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+ /*
+ * This routine notifies the application when the request given to
+ * qmgr_transport_alloc() completes.
+ */
+ if (msg_verbose)
+ msg_info("transport_event: %s", alloc->transport->name);
+
+ /*
+ * Connection request completed. Stop the watchdog timer.
+ */
+ event_cancel_timer(qmgr_transport_abort, context);
+
+ /*
+ * Disable further read events that end up calling this function, and
+ * free up this pending connection pipeline slot.
+ */
+ if (alloc->stream) {
+ event_disable_readwrite(vstream_fileno(alloc->stream));
+ non_blocking(vstream_fileno(alloc->stream), BLOCKING);
+ }
+ alloc->transport->pending -= 1;
+
+ /*
+ * Notify the requestor.
+ */
+ if (alloc->transport->xport_rate_delay > 0) {
+ if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0)
+ msg_panic("transport_event: missing rate lock for transport %s",
+ alloc->transport->name);
+ event_request_timer(qmgr_transport_rate_event, (void *) alloc,
+ alloc->transport->xport_rate_delay);
+ } else {
+ alloc->notify(alloc->transport, alloc->stream);
+ myfree((void *) alloc);
+ }
+}
+
+/* qmgr_transport_select - select transport for allocation */
+
+QMGR_TRANSPORT *qmgr_transport_select(void)
+{
+ QMGR_TRANSPORT *xport;
+ QMGR_QUEUE *queue;
+ int need;
+
+ /*
+ * If we find a suitable transport, rotate the list of transports to
+ * effectuate round-robin selection. See similar selection code in
+ * qmgr_peer_select().
+ *
+ * This function is called repeatedly until all transports have maxed out
+ * the number of pending delivery agent connections, until all delivery
+ * agent concurrency windows are maxed out, or until we run out of "todo"
+ * queue entries.
+ */
+#define MIN5af51743e4eef(x, y) ((x) < (y) ? (x) : (y))
+
+ for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) {
+ if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0
+ || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0
+ || xport->pending >= QMGR_TRANSPORT_MAX_PEND)
+ continue;
+ need = xport->pending + 1;
+ for (queue = xport->queue_list.next; queue; queue = queue->peers.next) {
+ if (QMGR_QUEUE_READY(queue) == 0)
+ continue;
+ if ((need -= MIN5af51743e4eef(queue->window - queue->busy_refcount,
+ queue->todo_refcount)) <= 0) {
+ QMGR_LIST_ROTATE(qmgr_transport_list, xport, peers);
+ if (msg_verbose)
+ msg_info("qmgr_transport_select: %s", xport->name);
+ return (xport);
+ }
+ }
+ }
+ return (0);
+}
+
+/* qmgr_transport_alloc - allocate delivery process */
+
+void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify)
+{
+ QMGR_TRANSPORT_ALLOC *alloc;
+
+ /*
+ * Sanity checks.
+ */
+ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD)
+ msg_panic("qmgr_transport: dead transport: %s", transport->name);
+ if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+ msg_panic("qmgr_transport: rate-locked transport: %s", transport->name);
+ if (transport->pending >= QMGR_TRANSPORT_MAX_PEND)
+ msg_panic("qmgr_transport: excess allocation: %s", transport->name);
+
+ /*
+ * When this message delivery transport is rate-limited, do not select it
+ * again before the end of a message delivery transaction.
+ */
+ if (transport->xport_rate_delay > 0)
+ transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK;
+
+ /*
+ * Connect to the well-known port for this delivery service, and wake up
+ * when a process announces its availability. Allow only a limited number
+ * of delivery process allocation attempts for this transport. In case of
+ * problems, back off. Do not hose the system when it is in trouble
+ * already.
+ *
+ * Use non-blocking connect(), so that Linux won't block the queue manager
+ * until the delivery agent calls accept().
+ *
+ * When the connection to delivery agent cannot be completed, notify the
+ * event handler so that it can throttle the transport and defer the todo
+ * queues, just like it does when communication fails *after* connection
+ * completion.
+ *
+ * Before Postfix 2.4, the event handler was not invoked after connect()
+ * error, and mail was not deferred. Because of this, mail would be stuck
+ * in the active queue after triggering a "connection refused" condition.
+ */
+ alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc));
+ alloc->transport = transport;
+ alloc->notify = notify;
+ transport->pending += 1;
+ if ((alloc->stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name,
+ NON_BLOCKING)) == 0) {
+ msg_warn("connect to transport %s/%s: %m",
+ MAIL_CLASS_PRIVATE, transport->name);
+ event_request_timer(qmgr_transport_event, (void *) alloc, 0);
+ return;
+ }
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) && defined(CA_VSTREAM_CTL_DUPFD)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ vstream_control(alloc->stream,
+ CA_VSTREAM_CTL_DUPFD(THRESHOLD_FD_WORKAROUND),
+ CA_VSTREAM_CTL_END);
+#endif
+ event_enable_read(vstream_fileno(alloc->stream), qmgr_transport_event,
+ (void *) alloc);
+
+ /*
+ * Guard against broken systems.
+ */
+ event_request_timer(qmgr_transport_abort, (void *) alloc,
+ var_daemon_timeout);
+}
+
+/* qmgr_transport_create - create transport instance */
+
+QMGR_TRANSPORT *qmgr_transport_create(const char *name)
+{
+ QMGR_TRANSPORT *transport;
+
+ if (htable_find(qmgr_transport_byname, name) != 0)
+ msg_panic("qmgr_transport_create: transport exists: %s", name);
+ transport = (QMGR_TRANSPORT *) mymalloc(sizeof(QMGR_TRANSPORT));
+ transport->flags = 0;
+ transport->pending = 0;
+ transport->name = mystrdup(name);
+
+ /*
+ * Use global configuration settings or transport-specific settings.
+ */
+ transport->dest_concurrency_limit =
+ get_mail_conf_int2(name, _DEST_CON_LIMIT,
+ var_dest_con_limit, 0, 0);
+ transport->recipient_limit =
+ get_mail_conf_int2(name, _DEST_RCPT_LIMIT,
+ var_dest_rcpt_limit, 0, 0);
+ transport->init_dest_concurrency =
+ get_mail_conf_int2(name, _INIT_DEST_CON,
+ var_init_dest_concurrency, 1, 0);
+ transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY,
+ var_xport_rate_delay,
+ 's', 0, 0);
+ transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY,
+ var_dest_rate_delay,
+ 's', 0, 0);
+
+ if (transport->rate_delay > 0)
+ transport->dest_concurrency_limit = 1;
+ if (transport->dest_concurrency_limit != 0
+ && transport->dest_concurrency_limit < transport->init_dest_concurrency)
+ transport->init_dest_concurrency = transport->dest_concurrency_limit;
+
+ transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST,
+ var_delivery_slot_cost, 0, 0);
+ transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN,
+ var_delivery_slot_loan, 0, 0);
+ transport->slot_loan_factor =
+ 100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT,
+ var_delivery_slot_discount, 0, 100);
+ transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS,
+ var_min_delivery_slots, 0, 0);
+ transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT,
+ var_xport_rcpt_limit, 0, 0);
+ transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT,
+ var_stack_rcpt_limit, 0, 0);
+ transport->refill_limit = get_mail_conf_int2(name, _XPORT_REFILL_LIMIT,
+ var_xport_refill_limit, 1, 0);
+ transport->refill_delay = get_mail_conf_time2(name, _XPORT_REFILL_DELAY,
+ var_xport_refill_delay, 's', 1, 0);
+
+ transport->queue_byname = htable_create(0);
+ QMGR_LIST_INIT(transport->queue_list);
+ transport->job_byname = htable_create(0);
+ QMGR_LIST_INIT(transport->job_list);
+ QMGR_LIST_INIT(transport->job_bytime);
+ transport->job_current = 0;
+ transport->job_next_unread = 0;
+ transport->candidate_cache = 0;
+ transport->candidate_cache_current = 0;
+ transport->candidate_cache_time = (time_t) 0;
+ transport->blocker_tag = 1;
+ transport->dsn = 0;
+ qmgr_feedback_init(&transport->pos_feedback, name, _CONC_POS_FDBACK,
+ VAR_CONC_POS_FDBACK, var_conc_pos_feedback);
+ qmgr_feedback_init(&transport->neg_feedback, name, _CONC_NEG_FDBACK,
+ VAR_CONC_NEG_FDBACK, var_conc_neg_feedback);
+ transport->fail_cohort_limit =
+ get_mail_conf_int2(name, _CONC_COHORT_LIM,
+ var_conc_cohort_limit, 0, 0);
+ if (qmgr_transport_byname == 0)
+ qmgr_transport_byname = htable_create(10);
+ htable_enter(qmgr_transport_byname, name, (void *) transport);
+ QMGR_LIST_PREPEND(qmgr_transport_list, transport, peers);
+ if (msg_verbose)
+ msg_info("qmgr_transport_create: %s concurrency %d recipients %d",
+ transport->name, transport->dest_concurrency_limit,
+ transport->recipient_limit);
+ return (transport);
+}
+
+/* qmgr_transport_find - find transport instance */
+
+QMGR_TRANSPORT *qmgr_transport_find(const char *name)
+{
+ return ((QMGR_TRANSPORT *) htable_find(qmgr_transport_byname, name));
+}
diff --git a/src/qmqpd/.indent.pro b/src/qmqpd/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/qmqpd/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/qmqpd/.printfck b/src/qmqpd/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/qmqpd/.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/qmqpd/Makefile.in b/src/qmqpd/Makefile.in
new file mode 100644
index 0000000..d4cdf33
--- /dev/null
+++ b/src/qmqpd/Makefile.in
@@ -0,0 +1,139 @@
+SHELL = /bin/sh
+SRCS = qmqpd.c qmqpd_state.c qmqpd_peer.c
+OBJS = qmqpd.o qmqpd_state.o qmqpd_peer.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = qmqpd
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+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 *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+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'
+qmqpd.o: ../../include/argv.h
+qmqpd.o: ../../include/attr.h
+qmqpd.o: ../../include/check_arg.h
+qmqpd.o: ../../include/cleanup_user.h
+qmqpd.o: ../../include/debug_peer.h
+qmqpd.o: ../../include/dict.h
+qmqpd.o: ../../include/htable.h
+qmqpd.o: ../../include/inet_proto.h
+qmqpd.o: ../../include/input_transp.h
+qmqpd.o: ../../include/iostuff.h
+qmqpd.o: ../../include/lex_822.h
+qmqpd.o: ../../include/mail_conf.h
+qmqpd.o: ../../include/mail_date.h
+qmqpd.o: ../../include/mail_params.h
+qmqpd.o: ../../include/mail_proto.h
+qmqpd.o: ../../include/mail_server.h
+qmqpd.o: ../../include/mail_stream.h
+qmqpd.o: ../../include/mail_version.h
+qmqpd.o: ../../include/match_list.h
+qmqpd.o: ../../include/match_parent_style.h
+qmqpd.o: ../../include/msg.h
+qmqpd.o: ../../include/myflock.h
+qmqpd.o: ../../include/mymalloc.h
+qmqpd.o: ../../include/namadr_list.h
+qmqpd.o: ../../include/netstring.h
+qmqpd.o: ../../include/nvtable.h
+qmqpd.o: ../../include/quote_822_local.h
+qmqpd.o: ../../include/quote_flags.h
+qmqpd.o: ../../include/rec_type.h
+qmqpd.o: ../../include/recipient_list.h
+qmqpd.o: ../../include/record.h
+qmqpd.o: ../../include/smtputf8.h
+qmqpd.o: ../../include/sys_defs.h
+qmqpd.o: ../../include/vbuf.h
+qmqpd.o: ../../include/verp_sender.h
+qmqpd.o: ../../include/vstream.h
+qmqpd.o: ../../include/vstring.h
+qmqpd.o: qmqpd.c
+qmqpd.o: qmqpd.h
+qmqpd_peer.o: ../../include/attr.h
+qmqpd_peer.o: ../../include/check_arg.h
+qmqpd_peer.o: ../../include/htable.h
+qmqpd_peer.o: ../../include/inet_proto.h
+qmqpd_peer.o: ../../include/iostuff.h
+qmqpd_peer.o: ../../include/mail_params.h
+qmqpd_peer.o: ../../include/mail_proto.h
+qmqpd_peer.o: ../../include/mail_stream.h
+qmqpd_peer.o: ../../include/msg.h
+qmqpd_peer.o: ../../include/myaddrinfo.h
+qmqpd_peer.o: ../../include/mymalloc.h
+qmqpd_peer.o: ../../include/nvtable.h
+qmqpd_peer.o: ../../include/sock_addr.h
+qmqpd_peer.o: ../../include/split_at.h
+qmqpd_peer.o: ../../include/stringops.h
+qmqpd_peer.o: ../../include/sys_defs.h
+qmqpd_peer.o: ../../include/valid_hostname.h
+qmqpd_peer.o: ../../include/valid_mailhost_addr.h
+qmqpd_peer.o: ../../include/vbuf.h
+qmqpd_peer.o: ../../include/vstream.h
+qmqpd_peer.o: ../../include/vstring.h
+qmqpd_peer.o: qmqpd.h
+qmqpd_peer.o: qmqpd_peer.c
+qmqpd_state.o: ../../include/attr.h
+qmqpd_state.o: ../../include/check_arg.h
+qmqpd_state.o: ../../include/cleanup_user.h
+qmqpd_state.o: ../../include/htable.h
+qmqpd_state.o: ../../include/iostuff.h
+qmqpd_state.o: ../../include/mail_proto.h
+qmqpd_state.o: ../../include/mail_stream.h
+qmqpd_state.o: ../../include/mymalloc.h
+qmqpd_state.o: ../../include/nvtable.h
+qmqpd_state.o: ../../include/sys_defs.h
+qmqpd_state.o: ../../include/vbuf.h
+qmqpd_state.o: ../../include/vstream.h
+qmqpd_state.o: ../../include/vstring.h
+qmqpd_state.o: qmqpd.h
+qmqpd_state.o: qmqpd_state.c
diff --git a/src/qmqpd/qmqpd.c b/src/qmqpd/qmqpd.c
new file mode 100644
index 0000000..d94d33d
--- /dev/null
+++ b/src/qmqpd/qmqpd.c
@@ -0,0 +1,865 @@
+/*++
+/* NAME
+/* qmqpd 8
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* \fBqmqpd\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix QMQP server receives one message per connection.
+/* Each message is piped through the \fBcleanup\fR(8)
+/* daemon, and is placed into the \fBincoming\fR queue as one
+/* single queue file. The program expects to be run from the
+/* \fBmaster\fR(8) process manager.
+/*
+/* The QMQP server implements one access policy: only explicitly
+/* authorized client hosts are allowed to use the service.
+/* SECURITY
+/* .ad
+/* .fi
+/* The QMQP server is moderately security-sensitive. It talks to QMQP
+/* clients and to DNS servers on the network. The QMQP server can be
+/* run chrooted at fixed low privilege.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The QMQP protocol provides only one server reply per message
+/* delivery. It is therefore not possible to reject individual
+/* recipients.
+/*
+/* The QMQP protocol requires the server to receive the entire
+/* message before replying. If a message is malformed, or if any
+/* netstring component is longer than acceptable, Postfix replies
+/* immediately and closes the connection. It is left up to the
+/* client to handle the situation.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBqmqpd\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.
+/* CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBcontent_filter (empty)\fR"
+/* After the message is queued, send the entire message to the
+/* specified \fItransport:destination\fR.
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* 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.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBhopcount_limit (50)\fR"
+/* The maximal number of Received: message headers that is allowed
+/* in the primary message headers.
+/* .IP "\fBmessage_size_limit (10240000)\fR"
+/* The maximal size in bytes of a message, including envelope information.
+/* .IP "\fBqmqpd_timeout (300s)\fR"
+/* The time limit for sending or receiving information over the network.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, cause the verbose
+/* logging level to increase by the amount specified in $debug_peer_level.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* TARPIT CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmqpd_error_delay (1s)\fR"
+/* How long the Postfix QMQP server will pause before sending a negative
+/* reply to the remote QMQP client.
+/* 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 "\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 "\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 "\fBqmqpd_authorized_clients (empty)\fR"
+/* What remote QMQP clients are allowed to connect to the Postfix QMQP
+/* server port.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .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".
+/* .IP "\fBverp_delimiter_filter (-=+)\fR"
+/* The characters Postfix accepts as VERP delimiter characters on the
+/* Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBqmqpd_client_port_logging (no)\fR"
+/* Enable logging of the remote QMQP client port in addition to
+/* the hostname and IP address.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* http://cr.yp.to/proto/qmqp.html, QMQP protocol
+/* cleanup(8), message canonicalization
+/* 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
+/* QMQP_README, Postfix ezmlm-idx howto.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The qmqpd service was introduced with Postfix version 1.1.
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <netstring.h>
+#include <dict.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_conf.h>
+#include <debug_peer.h>
+#include <mail_stream.h>
+#include <namadr_list.h>
+#include <quote_822_local.h>
+#include <match_parent_style.h>
+#include <lex_822.h>
+#include <verp_sender.h>
+#include <input_transp.h>
+#include <smtputf8.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific */
+
+#include <qmqpd.h>
+
+ /*
+ * Tunable parameters. Make sure that there is some bound on the length of a
+ * netstring, so that the mail system stays in control even when a malicious
+ * client sends netstrings of unreasonable length. The recipient count limit
+ * is enforced by the message size limit.
+ */
+int var_qmqpd_timeout;
+int var_qmqpd_err_sleep;
+char *var_filter_xport;
+char *var_qmqpd_clients;
+char *var_input_transp;
+bool var_qmqpd_client_port_log;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+#define DO_LOG 1
+#define DONT_LOG 0
+
+ /*
+ * Access control. This service should be exposed only to explicitly
+ * authorized clients. There is no default authorization.
+ */
+static NAMADR_LIST *qmqpd_clients;
+
+ /*
+ * Transparency: before mail is queued, do we allow address mapping,
+ * automatic bcc, header/body checks?
+ */
+int qmqpd_input_transp_mask;
+
+/* qmqpd_open_file - open a queue file */
+
+static void qmqpd_open_file(QMQPD_STATE *state)
+{
+ int cleanup_flags;
+
+ /*
+ * Connect to the cleanup server. Log client name/address with queue ID.
+ */
+ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL,
+ qmqpd_input_transp_mask);
+ cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_QMQPD);
+ state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, var_cleanup_service);
+ if (state->dest == 0
+ || attr_print(state->dest->stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to connect to the %s %s service",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ state->cleanup = state->dest->stream;
+ state->queue_id = mystrdup(state->dest->id);
+ msg_info("%s: client=%s", state->queue_id, state->namaddr);
+
+ /*
+ * Record the time of arrival. Optionally, enable content filtering (not
+ * bloody likely, but present for the sake of consistency with all other
+ * Postfix points of entrance).
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(state->arrival_time));
+ if (*var_filter_xport)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+}
+
+/* qmqpd_read_content - receive message content */
+
+static void qmqpd_read_content(QMQPD_STATE *state)
+{
+ state->where = "receiving message content";
+ netstring_get(state->client, state->message, var_message_limit);
+}
+
+/* qmqpd_copy_sender - copy envelope sender */
+
+static void qmqpd_copy_sender(QMQPD_STATE *state)
+{
+ char *end_prefix;
+ char *end_origin;
+ int verp_requested;
+ static char verp_delims[] = "-=";
+
+ /*
+ * If the sender address looks like prefix@origin-@[], then request
+ * variable envelope return path delivery, with an envelope sender
+ * address of prefi@origin, and with VERP delimiters of x and =. This
+ * way, the recipients will see envelope sender addresses that look like:
+ * prefixuser=domain@origin.
+ */
+ state->where = "receiving sender address";
+ netstring_get(state->client, state->buf, var_line_limit);
+ VSTRING_TERMINATE(state->buf);
+ verp_requested =
+ ((end_origin = vstring_end(state->buf) - 4) > STR(state->buf)
+ && strcmp(end_origin, "-@[]") == 0
+ && (end_prefix = strchr(STR(state->buf), '@')) != 0 /* XXX */
+ && --end_prefix < end_origin - 2 /* non-null origin */
+ && end_prefix > STR(state->buf)); /* non-null prefix */
+ if (verp_requested) {
+ verp_delims[0] = end_prefix[0];
+ if (verp_delims_verify(verp_delims) != 0) {
+ state->err |= CLEANUP_STAT_CONT; /* XXX */
+ vstring_sprintf(state->why_rejected, "Invalid VERP delimiters: \"%s\". Need two characters from \"%s\"",
+ verp_delims, var_verp_filter);
+ }
+ memmove(end_prefix, end_prefix + 1, end_origin - end_prefix - 1);
+ vstring_truncate(state->buf, end_origin - STR(state->buf) - 1);
+ }
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ if (verp_requested)
+ if (state->err == CLEANUP_STAT_OK
+ && rec_put(state->cleanup, REC_TYPE_VERP, verp_delims, 2) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->sender = mystrndup(STR(state->buf), LEN(state->buf));
+}
+
+/* qmqpd_write_attributes - save session attributes */
+
+static void qmqpd_write_attributes(QMQPD_STATE *state)
+{
+
+ /*
+ * Logging attributes, also used for XFORWARD.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_ADDR, state->rfc_addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, state->namaddr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_PROTO_NAME, state->protocol);
+
+ /*
+ * For consistency with the smtpd Milter client, we need to provide the
+ * real client attributes to the cleanup Milter client. This does not
+ * matter much with qmqpd which speaks to trusted clients only, but we
+ * want to be sure that the cleanup input protocol is ready when a new
+ * type of network daemon is added to receive mail from the Internet.
+ *
+ * See also the comments in smtpd.c.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_ADDR, state->addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u",
+ MAIL_ATTR_ACT_CLIENT_AF, state->addr_family);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_PROTO_NAME, state->protocol);
+
+ /* XXX What about the address rewriting context? */
+}
+
+/* qmqpd_copy_recipients - copy message recipients */
+
+static void qmqpd_copy_recipients(QMQPD_STATE *state)
+{
+ int ch;
+
+ /*
+ * Remember the first recipient. We are done when we read the over-all
+ * netstring terminator.
+ *
+ * XXX This approach violates abstractions, but it is a heck of a lot more
+ * convenient than counting the over-all byte count down to zero, like
+ * qmail does.
+ */
+ state->where = "receiving recipient address";
+ while ((ch = VSTREAM_GETC(state->client)) != ',') {
+ vstream_ungetc(state->client, ch);
+ netstring_get(state->client, state->buf, var_line_limit);
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_RCPT, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->rcpt_count++;
+ if (state->recipient == 0)
+ state->recipient = mystrndup(STR(state->buf), LEN(state->buf));
+ }
+}
+
+/* qmqpd_next_line - get line from buffer, return last char, newline, or -1 */
+
+static int qmqpd_next_line(VSTRING *message, char **start, int *len,
+ char **next)
+{
+ char *beyond = STR(message) + LEN(message);
+ char *enough = *next + var_line_limit;
+ char *cp;
+
+ /*
+ * Stop at newline or at some limit. Don't look beyond the end of the
+ * buffer.
+ */
+#define UCHARPTR(x) ((unsigned char *) (x))
+
+ for (cp = *start = *next; /* void */ ; cp++) {
+ if (cp >= beyond)
+ return ((*len = (*next = cp) - *start) > 0 ? UCHARPTR(cp)[-1] : -1);
+ if (*cp == '\n')
+ return ((*len = cp - *start), (*next = cp + 1), '\n');
+ if (cp >= enough)
+ return ((*len = cp - *start), (*next = cp), UCHARPTR(cp)[-1]);
+ }
+}
+
+/* qmqpd_write_content - write the message content segment */
+
+static void qmqpd_write_content(QMQPD_STATE *state)
+{
+ char *start;
+ char *next;
+ int len;
+ int rec_type;
+ int first = 1;
+ int ch;
+
+ /*
+ * Start the message content segment. Prepend our own Received: header to
+ * the message content. List the recipient only when a message has one
+ * recipient. Otherwise, don't list the recipient to avoid revealing Bcc:
+ * recipients that are supposed to be invisible.
+ */
+ rec_fputs(state->cleanup, REC_TYPE_MESG, "");
+ rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])",
+ state->name, state->name, state->rfc_addr);
+ if (state->rcpt_count == 1 && state->recipient) {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s id %s",
+ var_myhostname, var_mail_name,
+ state->protocol, state->queue_id);
+ quote_822_local(state->buf, state->recipient);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tfor <%s>; %s", STR(state->buf),
+ mail_date(state->arrival_time.tv_sec));
+ } else {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s",
+ var_myhostname, var_mail_name, state->protocol);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tid %s; %s", state->queue_id,
+ mail_date(state->arrival_time.tv_sec));
+ }
+#ifdef RECEIVED_ENVELOPE_FROM
+ quote_822_local(state->buf, state->sender);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\t(envelope-from <%s>)", STR(state->buf));
+#endif
+
+ /*
+ * Write the message content.
+ *
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our own
+ * Received: header. What an ugly Kluge.
+ *
+ * XXX Deal with UNIX-style From_ lines at the start of message content just
+ * in case.
+ */
+ for (next = STR(state->message); /* void */ ; /* void */ ) {
+ if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0)
+ break;
+ if (ch == '\n')
+ rec_type = REC_TYPE_NORM;
+ else
+ rec_type = REC_TYPE_CONT;
+ if (first) {
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
+ rec_fprintf(state->cleanup, rec_type,
+ "X-Mailbox-Line: %.*s", len, start);
+ continue;
+ }
+ first = 0;
+ if (len > 0 && IS_SPACE_TAB(start[0]))
+ rec_put(state->cleanup, REC_TYPE_NORM, "", 0);
+ }
+ if (rec_put(state->cleanup, rec_type, start, len) < 0) {
+ state->err = CLEANUP_STAT_WRITE;
+ return;
+ }
+ }
+}
+
+/* qmqpd_close_file - close queue file */
+
+static void qmqpd_close_file(QMQPD_STATE *state)
+{
+
+ /*
+ * Send the end-of-segment markers.
+ */
+ if (state->err == CLEANUP_STAT_OK)
+ if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0
+ || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
+ || vstream_fflush(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+
+ /*
+ * Finish the queue file or finish the cleanup conversation.
+ */
+ if (state->err == 0)
+ state->err = mail_stream_finish(state->dest, state->why_rejected);
+ else
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+}
+
+/* qmqpd_reply - send status to client and optionally log message */
+
+static void qmqpd_reply(QMQPD_STATE *state, int log_message,
+ int status_code, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Optionally change hard errors into retryable ones. Send the reply and
+ * optionally log it. Always insert a delay before reporting a problem.
+ * This slows down software run-away conditions.
+ */
+ if (status_code == QMQPD_STAT_HARD && var_soft_bounce)
+ status_code = QMQPD_STAT_RETRY;
+ VSTRING_RESET(state->buf);
+ VSTRING_ADDCH(state->buf, status_code);
+ va_start(ap, fmt);
+ vstring_vsprintf_append(state->buf, fmt, ap);
+ va_end(ap);
+ NETSTRING_PUT_BUF(state->client, state->buf);
+ if (log_message)
+ (status_code == QMQPD_STAT_OK ? msg_info : msg_warn) ("%s: %s: %s",
+ state->queue_id, state->namaddr, STR(state->buf) + 1);
+ if (status_code != QMQPD_STAT_OK)
+ sleep(var_qmqpd_err_sleep);
+ netstring_fflush(state->client);
+}
+
+/* qmqpd_send_status - send mail transaction completion status */
+
+static void qmqpd_send_status(QMQPD_STATE *state)
+{
+
+ /*
+ * One message may suffer from multiple errors, so complain only about
+ * the most severe error.
+ *
+ * See also: smtpd.c
+ */
+ state->where = "sending completion status";
+
+ if (state->err == CLEANUP_STAT_OK) {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_OK,
+ "Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_DEFER) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: %s", STR(state->why_rejected));
+ } else if ((state->err & CLEANUP_STAT_BAD) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: internal error %d", state->err);
+ } else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: message too large");
+ } else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: too many hops");
+ } else if ((state->err & CLEANUP_STAT_CONT) != 0) {
+ qmqpd_reply(state, DO_LOG, STR(state->why_rejected)[0] == '4' ?
+ QMQPD_STAT_RETRY : QMQPD_STAT_HARD,
+ "Error: %s", STR(state->why_rejected));
+ } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: queue file write error");
+ } else if ((state->err & CLEANUP_STAT_RCPT) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: no recipients specified");
+ } else {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: internal error %d", state->err);
+ }
+}
+
+/* qmqpd_receive - receive QMQP message+sender+recipients */
+
+static void qmqpd_receive(QMQPD_STATE *state)
+{
+
+ /*
+ * Open a queue file. This must be first so that we can simplify the
+ * error logging and always include the queue ID information.
+ */
+ qmqpd_open_file(state);
+
+ /*
+ * Read and ignore the over-all netstring length indicator.
+ */
+ state->where = "receiving QMQP packet header";
+ (void) netstring_get_length(state->client);
+
+ /*
+ * XXX Read the message content into memory, because Postfix expects to
+ * store the sender before storing the message content. Fixing that
+ * requires changes to pickup, cleanup, qmgr, and perhaps elsewhere, so
+ * that will have to happen later when I have more time. However, QMQP is
+ * used for mailing list distribution, so the bulk of the volume is
+ * expected to be not message content but recipients, and recipients are
+ * not accumulated in memory.
+ */
+ qmqpd_read_content(state);
+
+ /*
+ * Read and write the envelope sender.
+ */
+ qmqpd_copy_sender(state);
+
+ /*
+ * Record some session attributes.
+ */
+ qmqpd_write_attributes(state);
+
+ /*
+ * Read and write the envelope recipients, including the optional big
+ * brother recipient.
+ */
+ qmqpd_copy_recipients(state);
+
+ /*
+ * Start the message content segment, prepend our own Received: header,
+ * and write the message content.
+ */
+ if (state->err == 0)
+ qmqpd_write_content(state);
+
+ /*
+ * Close the queue file.
+ */
+ qmqpd_close_file(state);
+
+ /*
+ * Report the completion status to the client.
+ */
+ qmqpd_send_status(state);
+}
+
+/* qmqpd_proto - speak the QMQP "protocol" */
+
+static void qmqpd_proto(QMQPD_STATE *state)
+{
+ int status;
+
+ netstring_setup(state->client, var_qmqpd_timeout);
+
+ switch (status = vstream_setjmp(state->client)) {
+
+ default:
+ msg_panic("qmqpd_proto: unknown status %d", status);
+
+ case NETSTRING_ERR_EOF:
+ state->reason = "lost connection";
+ break;
+
+ case NETSTRING_ERR_TIME:
+ state->reason = "read/write timeout";
+ break;
+
+ case NETSTRING_ERR_FORMAT:
+ state->reason = "netstring format error";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case NETSTRING_ERR_SIZE:
+ state->reason = "netstring length exceeds storage limit";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case 0:
+
+ /*
+ * See if we want to talk to this client at all.
+ */
+ if (namadr_list_match(qmqpd_clients, state->name, state->addr) != 0) {
+ qmqpd_receive(state);
+ } else if (qmqpd_clients->error == 0) {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD,
+ "Error: %s is not authorized to use this service",
+ state->namaddr);
+ } else {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_RETRY,
+ "Error: server configuration error");
+ }
+ break;
+ }
+
+ /*
+ * Log abnormal session termination. Indicate the last recognized state
+ * before things went wrong.
+ */
+ if (state->reason && state->where)
+ msg_info("%s: %s: %s while %s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr, state->reason, state->where);
+}
+
+/* qmqpd_service - service one client */
+
+static void qmqpd_service(VSTREAM *stream, char *unused_service, char **argv)
+{
+ QMQPD_STATE *state;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This routine runs when a client has connected to our network port.
+ * Look up and sanitize the peer name and initialize some connection-
+ * specific state.
+ */
+ state = qmqpd_state_alloc(stream);
+
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+ debug_peer_check(state->name, state->addr);
+
+ /*
+ * Provide the QMQP service.
+ */
+ msg_info("connect from %s", state->namaddr);
+ qmqpd_proto(state);
+ msg_info("disconnect from %s", state->namaddr);
+
+ /*
+ * After the client has gone away, clean up whatever we have set up at
+ * connection time.
+ */
+ debug_peer_restore();
+ qmqpd_state_free(state);
+}
+
+/* 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);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ debug_peer_init();
+ qmqpd_clients =
+ namadr_list_init(VAR_QMQPD_CLIENTS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_QMQPD_CLIENTS),
+ var_qmqpd_clients);
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize the receive transparency options: do we want unknown
+ * recipient checks, do we want address mapping.
+ */
+ qmqpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_QMTPD_TMOUT, DEF_QMTPD_TMOUT, &var_qmqpd_timeout, 1, 0,
+ VAR_QMTPD_ERR_SLEEP, DEF_QMTPD_ERR_SLEEP, &var_qmqpd_err_sleep, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_QMQPD_CLIENTS, DEF_QMQPD_CLIENTS, &var_qmqpd_clients, 0, 0,
+ VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_QMQPD_CLIENT_PORT_LOG, DEF_QMQPD_CLIENT_PORT_LOG, &var_qmqpd_client_port_log,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, qmqpd_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ 0);
+}
diff --git a/src/qmqpd/qmqpd.h b/src/qmqpd/qmqpd.h
new file mode 100644
index 0000000..aad185b
--- /dev/null
+++ b/src/qmqpd/qmqpd.h
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* qmqpd 3h
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* include "qmqpd.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_stream.h>
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ int err; /* error flags */
+ VSTREAM *client; /* client connection */
+ VSTRING *message; /* message buffer */
+ VSTRING *buf; /* line buffer */
+ struct timeval arrival_time; /* start of session */
+ char *name; /* client name */
+ char *addr; /* client IP address */
+ char *port; /* client TCP port */
+ char *namaddr; /* name[addr]:port */
+ char *rfc_addr; /* RFC 2821 client IP address */
+ int addr_family; /* address family */
+ char *queue_id; /* queue file ID */
+ VSTREAM *cleanup; /* cleanup server */
+ MAIL_STREAM *dest; /* cleanup server */
+ int rcpt_count; /* recipient count */
+ char *reason; /* exception name */
+ char *sender; /* sender address */
+ char *recipient; /* recipient address */
+ char *protocol; /* protocol name */
+ char *where; /* protocol state */
+ VSTRING *why_rejected; /* REJECT reason */
+} QMQPD_STATE;
+
+ /*
+ * Representation of unknown upstream client or message information within
+ * qmqpd processes. This is not the representation that Postfix uses in
+ * queue files, in queue manager delivery requests, or in XCLIENT/XFORWARD
+ * commands!
+ */
+#define CLIENT_ATTR_UNKNOWN "unknown"
+
+#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+
+ /*
+ * QMQP protocol status codes.
+ */
+#define QMQPD_STAT_OK 'K'
+#define QMQPD_STAT_RETRY 'Z'
+#define QMQPD_STAT_HARD 'D'
+
+ /*
+ * qmqpd_state.c
+ */
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *);
+void qmqpd_state_free(QMQPD_STATE *);
+
+ /*
+ * qmqpd_peer.c
+ */
+void qmqpd_peer_init(QMQPD_STATE *);
+void qmqpd_peer_reset(QMQPD_STATE *);
+
+/* 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
+/*--*/
diff --git a/src/qmqpd/qmqpd_peer.c b/src/qmqpd/qmqpd_peer.c
new file mode 100644
index 0000000..41cd009
--- /dev/null
+++ b/src/qmqpd/qmqpd_peer.c
@@ -0,0 +1,309 @@
+/*++
+/* NAME
+/* qmqpd_peer 3
+/* SUMMARY
+/* look up peer name/address information
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* void qmqpd_peer_init(state)
+/* QMQPD_STATE *state;
+/*
+/* void qmqpd_peer_reset(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* The qmqpd_peer_init() routine attempts to produce a printable
+/* version of the peer name and address of the specified socket.
+/* Where information is unavailable, the name and/or address
+/* are set to "unknown".
+/*
+/* qmqpd_peer_init() updates the following fields:
+/* .IP name
+/* The client hostname. An unknown name is represented by the
+/* string "unknown".
+/* .IP addr
+/* Printable representation of the client address.
+/* .IP namaddr
+/* String of the form: "name[addr]:port".
+/* .PP
+/* qmqpd_peer_reset() releases memory allocated by qmqpd_peer_init().
+/* 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <valid_mailhost_addr.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "qmqpd.h"
+
+/* qmqpd_peer_init - initialize peer information */
+
+void qmqpd_peer_init(QMQPD_STATE *state)
+{
+ const char *myname = "qmqpd_peer_init";
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ SOCKADDR_SIZE sa_length;
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+
+ sa = (struct sockaddr *) &ss;
+ sa_length = sizeof(ss);
+
+ /*
+ * Look up the peer address information.
+ */
+ if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) {
+ errno = 0;
+ }
+
+ /*
+ * If peer went away, give up.
+ */
+ if (errno != 0 && errno != ENOTSOCK) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->addr_family = AF_UNSPEC;
+ state->port = mystrdup(CLIENT_PORT_UNKNOWN);
+ }
+
+ /*
+ * Convert the client address to printable address and hostname.
+ *
+ * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while
+ * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final
+ * else clause, pretend the origin is localhost[127.0.0.1], and become an
+ * open relay).
+ */
+ else if (errno == 0
+ && (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )) {
+ MAI_HOSTNAME_STR client_name;
+ MAI_HOSTADDR_STR client_addr;
+ MAI_SERVPORT_STR client_port;
+ int aierr;
+ char *colonp;
+
+ /*
+ * Sanity check: we can't use sockets that we're not configured for.
+ */
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
+ msg_fatal("cannot handle socket type %s with \"%s = %s\"",
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+ "other", VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Sorry, but there are some things that we just cannot do while
+ * connected to the network.
+ */
+ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
+ msg_error("incorrect QMQP server privileges: uid=%lu euid=%lu",
+ (unsigned long) getuid(), (unsigned long) geteuid());
+ msg_fatal("the Postfix QMQP server must run with $%s privileges",
+ VAR_MAIL_OWNER);
+ }
+
+ /*
+ * Convert the client address to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
+ &client_port, 0)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ state->port = mystrdup(client_port.buf);
+
+ /*
+ * XXX Require that the infrastructure strips off the IPv6 datalink
+ * suffix to avoid false alarms with strict address syntax checks.
+ */
+#ifdef HAS_IPV6
+ if (strchr(client_addr.buf, '%') != 0)
+ msg_panic("%s: address %s has datalink suffix",
+ myname, client_addr.buf);
+#endif
+
+ /*
+ * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
+ * but only if IPv4 support is enabled (why would anyone want to turn
+ * it off)? With IPv4 support enabled we have no need for the IPv6
+ * form in logging, hostname verification and access checks.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
+ && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
+ && (colonp = strrchr(client_addr.buf, ':')) != 0) {
+ struct addrinfo *res0;
+
+ if (msg_verbose > 1)
+ msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
+ myname, client_addr.buf, colonp + 1);
+
+ state->addr = mystrdup(colonp + 1);
+ state->rfc_addr = mystrdup(colonp + 1);
+ state->addr_family = AF_INET;
+ aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
+ if (aierr)
+ msg_fatal("%s: cannot convert %s from string to binary: %s",
+ myname, state->addr, MAI_STRERROR(aierr));
+ sa_length = res0->ai_addrlen;
+ if (sa_length > sizeof(ss))
+ sa_length = sizeof(ss);
+ memcpy((void *) sa, res0->ai_addr, sa_length);
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
+ * a prefix of 'IPv6:'. We do this consistently for all IPv6
+ * addresses that appear in headers or envelopes. The fact
+ * that valid_mailhost_addr() enforces the form helps of course.
+ * We use the form without IPV6: prefix when doing access
+ * control, or when accessing the connection cache.
+ */
+ else {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr =
+ concatenate(IPV6_COL, client_addr.buf, (char *) 0);
+ state->addr_family = sa->sa_family;
+ }
+ }
+
+ /*
+ * An IPv4 address is in dotted quad decimal form.
+ */
+ else
+#endif
+ {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr = mystrdup(client_addr.buf);
+ state->addr_family = sa->sa_family;
+ }
+
+ /*
+ * Look up and sanity check the client hostname.
+ *
+ * It is unsafe to allow numeric hostnames, especially because there
+ * exists pressure to turn off the name->addr double check. In that
+ * case an attacker could trivally bypass access restrictions.
+ *
+ * sockaddr_to_hostname() already rejects malformed or numeric names.
+ */
+#define REJECT_PEER_NAME(state) { \
+ myfree(state->name); \
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
+ }
+
+ if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ } else {
+ struct addrinfo *res0;
+ struct addrinfo *res;
+
+ state->name = mystrdup(client_name.buf);
+
+ /*
+ * Reject the hostname if it does not list the peer address.
+ */
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
+ if (aierr) {
+ msg_warn("hostname %s does not resolve to address %s: %s",
+ state->name, state->addr, MAI_STRERROR(aierr));
+ REJECT_PEER_NAME(state);
+ } else {
+ for (res = res0; /* void */ ; res = res->ai_next) {
+ if (res == 0) {
+ msg_warn("hostname %s does not resolve to address %s",
+ state->addr, state->name);
+ REJECT_PEER_NAME(state);
+ break;
+ }
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, state->name);
+ continue;
+ }
+ if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
+ break; /* keep peer name */
+ }
+ freeaddrinfo(res0);
+ }
+ }
+ }
+
+ /*
+ * If it's not Internet, assume the client is local, and avoid using the
+ * naming service because that can hang when the machine is disconnected.
+ */
+ else {
+ state->name = mystrdup("localhost");
+ state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */
+ state->addr_family = AF_UNSPEC;
+ state->port = mystrdup("0"); /* XXX bogus. */
+ }
+
+ /*
+ * Do the name[addr]:port formatting for pretty reports.
+ */
+ state->namaddr =
+ concatenate(state->name, "[", state->addr, "]",
+ var_qmqpd_client_port_log ? ":" : (char *) 0,
+ state->port, (char *) 0);
+}
+
+/* qmqpd_peer_reset - destroy peer information */
+
+void qmqpd_peer_reset(QMQPD_STATE *state)
+{
+ myfree(state->name);
+ myfree(state->addr);
+ myfree(state->namaddr);
+ myfree(state->rfc_addr);
+ myfree(state->port);
+}
diff --git a/src/qmqpd/qmqpd_state.c b/src/qmqpd/qmqpd_state.c
new file mode 100644
index 0000000..9bee879
--- /dev/null
+++ b/src/qmqpd/qmqpd_state.c
@@ -0,0 +1,99 @@
+/*++
+/* NAME
+/* qmqpd_state 3
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* QMQPD_STATE *qmqpd_state_alloc(stream)
+/* VSTREAM *stream;
+/*
+/* void qmqpd_state_free(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* qmqpd_state_alloc() creates and initializes session context.
+/*
+/* qmqpd_state_free() destroys session context.
+/*
+/* Arguments:
+/* .IP stream
+/* Stream connected to peer. The stream is not copied.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_stream.h>
+#include <cleanup_user.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <qmqpd.h>
+
+/* qmqpd_state_alloc - allocate and initialize session state */
+
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream)
+{
+ QMQPD_STATE *state;
+
+ state = (QMQPD_STATE *) mymalloc(sizeof(*state));
+ state->err = CLEANUP_STAT_OK;
+ state->client = stream;
+ state->message = vstring_alloc(1000);
+ state->buf = vstring_alloc(100);
+ GETTIMEOFDAY(&state->arrival_time);
+ qmqpd_peer_init(state);
+ state->queue_id = 0;
+ state->cleanup = 0;
+ state->dest = 0;
+ state->rcpt_count = 0;
+ state->reason = 0;
+ state->sender = 0;
+ state->recipient = 0;
+ state->protocol = MAIL_PROTO_QMQP;
+ state->where = "initializing client connection";
+ state->why_rejected = vstring_alloc(10);
+ return (state);
+}
+
+/* qmqpd_state_free - destroy session state */
+
+void qmqpd_state_free(QMQPD_STATE *state)
+{
+ vstring_free(state->message);
+ vstring_free(state->buf);
+ qmqpd_peer_reset(state);
+ if (state->queue_id)
+ myfree(state->queue_id);
+ if (state->dest)
+ mail_stream_cleanup(state->dest);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->recipient)
+ myfree(state->recipient);
+ vstring_free(state->why_rejected);
+ myfree((void *) state);
+}
diff --git a/src/scache/.indent.pro b/src/scache/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/scache/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/scache/Makefile.in b/src/scache/Makefile.in
new file mode 100644
index 0000000..882f0c6
--- /dev/null
+++ b/src/scache/Makefile.in
@@ -0,0 +1,81 @@
+SHELL = /bin/sh
+SRCS = scache.c
+OBJS = scache.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = scache
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+scache.o: ../../include/attr.h
+scache.o: ../../include/check_arg.h
+scache.o: ../../include/events.h
+scache.o: ../../include/htable.h
+scache.o: ../../include/iostuff.h
+scache.o: ../../include/mail_conf.h
+scache.o: ../../include/mail_params.h
+scache.o: ../../include/mail_proto.h
+scache.o: ../../include/mail_server.h
+scache.o: ../../include/mail_version.h
+scache.o: ../../include/msg.h
+scache.o: ../../include/mymalloc.h
+scache.o: ../../include/nvtable.h
+scache.o: ../../include/ring.h
+scache.o: ../../include/scache.h
+scache.o: ../../include/sys_defs.h
+scache.o: ../../include/vbuf.h
+scache.o: ../../include/vstream.h
+scache.o: ../../include/vstring.h
+scache.o: scache.c
diff --git a/src/scache/scache.c b/src/scache/scache.c
new file mode 100644
index 0000000..85f32ef
--- /dev/null
+++ b/src/scache/scache.c
@@ -0,0 +1,586 @@
+/*++
+/* NAME
+/* scache 8
+/* SUMMARY
+/* Postfix shared connection cache server
+/* SYNOPSIS
+/* \fBscache\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBscache\fR(8) server maintains a shared multi-connection
+/* cache. This information can be used by, for example, Postfix
+/* SMTP clients or other Postfix delivery agents.
+/*
+/* The connection cache is organized into logical destination
+/* names, physical endpoint names, and connections.
+/*
+/* As a specific example, logical SMTP destinations specify
+/* (transport, domain, port), and physical SMTP endpoints
+/* specify (transport, IP address, port). An SMTP connection
+/* may be saved after a successful mail transaction.
+/*
+/* In the general case, one logical destination may refer to
+/* zero or more physical endpoints, one physical endpoint may
+/* be referenced by zero or more logical destinations, and
+/* one endpoint may refer to zero or more connections.
+/*
+/* The exact syntax of a logical destination or endpoint name
+/* is application dependent; the \fBscache\fR(8) server does
+/* not care. A connection is stored as a file descriptor together
+/* with application-dependent information that is needed to
+/* re-activate a connection object. Again, the \fBscache\fR(8)
+/* server is completely unaware of the details of that
+/* information.
+/*
+/* All information is stored with a finite time to live (ttl).
+/* The connection cache daemon terminates when no client is
+/* connected for \fBmax_idle\fR time units.
+/*
+/* This server implements the following requests:
+/* .IP "\fBsave_endp\fI ttl endpoint endpoint_properties file_descriptor\fR"
+/* Save the specified file descriptor and connection property data
+/* under the specified endpoint name. The endpoint properties
+/* are used by the client to re-activate a passivated connection
+/* object.
+/* .IP "\fBfind_endp\fI endpoint\fR"
+/* Look up cached properties and a cached file descriptor for the
+/* specified endpoint.
+/* .IP "\fBsave_dest\fI ttl destination destination_properties endpoint\fR"
+/* Save the binding between a logical destination and an
+/* endpoint under the destination name, together with destination
+/* specific connection properties. The destination properties
+/* are used by the client to re-activate a passivated connection
+/* object.
+/* .IP "\fBfind_dest\fI destination\fR"
+/* Look up cached destination properties, cached endpoint properties,
+/* and a cached file descriptor for the specified logical destination.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBscache\fR(8) server is not security-sensitive. It does not
+/* talk to the network, and it does not talk to local users.
+/* The \fBscache\fR(8) server can run chrooted at fixed low privilege.
+/*
+/* The \fBscache\fR(8) server is not a trusted process. It must
+/* not be used to store information that is security sensitive.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The session cache cannot be shared among multiple machines.
+/*
+/* When a connection expires from the cache, it is closed without
+/* the appropriate protocol specific handshake.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBscache\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.
+/* RESOURCE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconnection_cache_ttl_limit (2s)\fR"
+/* The maximal time-to-live value that the \fBscache\fR(8) connection
+/* cache server
+/* allows.
+/* .IP "\fBconnection_cache_status_update_time (600s)\fR"
+/* How frequently the \fBscache\fR(8) server logs usage statistics with
+/* connection cache hit and miss rates for logical destinations and for
+/* physical endpoints.
+/* 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 "\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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtp(8), SMTP client
+/* postconf(5), configuration parameters
+/* 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
+/* CONNECTION_CACHE_README, Postfix connection cache
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 2.2.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <htable.h>
+#include <ring.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <scache.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+int var_scache_ttl_lim;
+int var_scache_stat_time;
+
+ /*
+ * Request parameters.
+ */
+static VSTRING *scache_request;
+static VSTRING *scache_dest_label;
+static VSTRING *scache_dest_prop;
+static VSTRING *scache_endp_label;
+static VSTRING *scache_endp_prop;
+
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+static VSTRING *scache_dummy;
+
+#endif
+
+ /*
+ * Session cache instance.
+ */
+static SCACHE *scache;
+
+ /*
+ * Statistics.
+ */
+static int scache_dest_hits;
+static int scache_dest_miss;
+static int scache_dest_count;
+static int scache_endp_hits;
+static int scache_endp_miss;
+static int scache_endp_count;
+static int scache_sess_count;
+time_t scache_start_time;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define VSTREQ(x,y) (strcmp(STR(x),y) == 0)
+
+/* scache_save_endp_service - protocol to save endpoint->stream binding */
+
+static void scache_save_endp_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_save_endp_service";
+ int ttl;
+ int fd;
+ SCACHE_SIZE size;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, scache_endp_prop),
+ ATTR_TYPE_END) != 3
+ || ttl <= 0) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ return;
+ } else if (
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(client_stream) != 0
+ || read_wait(vstream_fileno(client_stream),
+ client_stream->timeout) < 0 /* XXX */
+ ||
+#endif
+ (fd = LOCAL_RECV_FD(vstream_fileno(client_stream))) < 0) {
+ msg_warn("%s: unable to receive file descriptor: %m", myname);
+ (void) attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ ATTR_TYPE_END);
+ return;
+ } else {
+ scache_save_endp(scache,
+ ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl,
+ STR(scache_endp_label), STR(scache_endp_prop), fd);
+ (void) attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ ATTR_TYPE_END);
+ scache_size(scache, &size);
+ if (size.endp_count > scache_endp_count)
+ scache_endp_count = size.endp_count;
+ if (size.sess_count > scache_sess_count)
+ scache_sess_count = size.sess_count;
+ return;
+ }
+}
+
+/* scache_find_endp_service - protocol to find connection for endpoint */
+
+static void scache_find_endp_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_find_endp_service";
+ int fd;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ return;
+ } else if ((fd = scache_find_endp(scache, STR(scache_endp_label),
+ scache_endp_prop)) < 0) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ scache_endp_miss++;
+ return;
+ } else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)),
+ ATTR_TYPE_END);
+ if (vstream_fflush(client_stream) != 0
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0
+#ifdef MUST_READ_AFTER_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ )
+ msg_warn("%s: cannot send file descriptor: %m", myname);
+ if (close(fd) < 0)
+ msg_warn("close(%d): %m", fd);
+ scache_endp_hits++;
+ return;
+ }
+}
+
+/* scache_save_dest_service - protocol to save destination->endpoint binding */
+
+static void scache_save_dest_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_save_dest_service";
+ int ttl;
+ SCACHE_SIZE size;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, scache_dest_prop),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ ATTR_TYPE_END) != 4
+ || ttl <= 0) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ return;
+ } else {
+ scache_save_dest(scache,
+ ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl,
+ STR(scache_dest_label), STR(scache_dest_prop),
+ STR(scache_endp_label));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ ATTR_TYPE_END);
+ scache_size(scache, &size);
+ if (size.dest_count > scache_dest_count)
+ scache_dest_count = size.dest_count;
+ if (size.endp_count > scache_endp_count)
+ scache_endp_count = size.endp_count;
+ return;
+ }
+}
+
+/* scache_find_dest_service - protocol to find connection for destination */
+
+static void scache_find_dest_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_find_dest_service";
+ int fd;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ return;
+ } else if ((fd = scache_find_dest(scache, STR(scache_dest_label),
+ scache_dest_prop,
+ scache_endp_prop)) < 0) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ scache_dest_miss++;
+ return;
+ } else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_dest_prop)),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)),
+ ATTR_TYPE_END);
+ if (vstream_fflush(client_stream) != 0
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0
+#ifdef MUST_READ_AFTER_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ )
+ msg_warn("%s: cannot send file descriptor: %m", myname);
+ if (close(fd) < 0)
+ msg_warn("close(%d): %m", fd);
+ scache_dest_hits++;
+ return;
+ }
+}
+
+/* scache_service - perform service for client */
+
+static void scache_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to the scache service. All connection-management stuff is
+ * handled by the common code in multi_server.c.
+ *
+ * XXX Workaround: with some requests, the client sends a dummy message
+ * after the server replies (yes that's a botch). When the scache server
+ * is slow, this dummy message may become concatenated with the next
+ * request from the same client. The do-while loop below will repeat
+ * instead of discarding the client request. We must process it now
+ * because there will be no select() notification.
+ */
+ do {
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, scache_request),
+ ATTR_TYPE_END) == 1) {
+ if (VSTREQ(scache_request, SCACHE_REQ_SAVE_DEST)) {
+ scache_save_dest_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_DEST)) {
+ scache_find_dest_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_SAVE_ENDP)) {
+ scache_save_endp_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_ENDP)) {
+ scache_find_endp_service(client_stream);
+ } else {
+ msg_warn("unrecognized request: \"%s\", ignored",
+ STR(scache_request));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ }
+ }
+ } while (vstream_peek(client_stream) > 0);
+ vstream_fflush(client_stream);
+}
+
+/* scache_status_dump - log and reset cache statistics */
+
+static void scache_status_dump(char *unused_name, char **unused_argv)
+{
+ if (scache_dest_hits || scache_dest_miss
+ || scache_endp_hits || scache_endp_miss
+ || scache_dest_count || scache_endp_count
+ || scache_sess_count)
+ msg_info("statistics: start interval %.15s",
+ ctime(&scache_start_time) + 4);
+
+ if (scache_dest_hits || scache_dest_miss) {
+ msg_info("statistics: domain lookup hits=%d miss=%d success=%d%%",
+ scache_dest_hits, scache_dest_miss,
+ scache_dest_hits * 100
+ / (scache_dest_hits + scache_dest_miss));
+ scache_dest_hits = scache_dest_miss = 0;
+ }
+ if (scache_endp_hits || scache_endp_miss) {
+ msg_info("statistics: address lookup hits=%d miss=%d success=%d%%",
+ scache_endp_hits, scache_endp_miss,
+ scache_endp_hits * 100
+ / (scache_endp_hits + scache_endp_miss));
+ scache_endp_hits = scache_endp_miss = 0;
+ }
+ if (scache_dest_count || scache_endp_count || scache_sess_count) {
+ msg_info("statistics: max simultaneous domains=%d addresses=%d connection=%d",
+ scache_dest_count, scache_endp_count, scache_sess_count);
+ scache_dest_count = 0;
+ scache_endp_count = 0;
+ scache_sess_count = 0;
+ }
+ scache_start_time = event_time();
+}
+
+/* scache_status_update - log and reset cache statistics periodically */
+
+static void scache_status_update(int unused_event, void *context)
+{
+ scache_status_dump((char *) 0, (char **) 0);
+ event_request_timer(scache_status_update, context, var_scache_stat_time);
+}
+
+/* post_jail_init - initialization after privilege drop */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Pre-allocate the cache instance.
+ */
+ scache = scache_multi_create();
+
+ /*
+ * Pre-allocate buffers.
+ */
+ scache_request = vstring_alloc(10);
+ scache_dest_label = vstring_alloc(10);
+ scache_dest_prop = vstring_alloc(10);
+ scache_endp_label = vstring_alloc(10);
+ scache_endp_prop = vstring_alloc(10);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ scache_dummy = vstring_alloc(10);
+#endif
+
+ /*
+ * Disable the max_use limit. We still terminate when no client is
+ * connected for $idle_limit time units.
+ */
+ var_use_limit = 0;
+
+ /*
+ * Dump and reset cache statistics every so often.
+ */
+ event_request_timer(scache_status_update, (void *) 0, var_scache_stat_time);
+ scache_start_time = event_time();
+}
+
+/* scache_post_accept - announce our protocol */
+
+static void scache_post_accept(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_SCACHE_TTL_LIM, DEF_SCACHE_TTL_LIM, &var_scache_ttl_lim, 1, 0,
+ VAR_SCACHE_STAT_TIME, DEF_SCACHE_STAT_TIME, &var_scache_stat_time, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, scache_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_POST_ACCEPT(scache_post_accept),
+ CA_MAIL_SERVER_EXIT(scache_status_dump),
+ CA_MAIL_SERVER_SOLITARY,
+ 0);
+}
diff --git a/src/sendmail/.indent.pro b/src/sendmail/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/sendmail/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/sendmail/.printfck b/src/sendmail/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/sendmail/.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/sendmail/Makefile.in b/src/sendmail/Makefile.in
new file mode 100644
index 0000000..bb0298a
--- /dev/null
+++ b/src/sendmail/Makefile.in
@@ -0,0 +1,113 @@
+SHELL = /bin/sh
+SRCS = sendmail.c
+OBJS = sendmail.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = sendmail
+INC_DIR = ../../include
+LIBS = ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../bin/$(PROG)
+
+../../bin/$(PROG): $(PROG)
+ cp $(PROG) ../../bin
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+sendmail.o: ../../include/argv.h
+sendmail.o: ../../include/attr.h
+sendmail.o: ../../include/check_arg.h
+sendmail.o: ../../include/clean_env.h
+sendmail.o: ../../include/cleanup_user.h
+sendmail.o: ../../include/connect.h
+sendmail.o: ../../include/debug_process.h
+sendmail.o: ../../include/deliver_request.h
+sendmail.o: ../../include/dsn.h
+sendmail.o: ../../include/dsn_mask.h
+sendmail.o: ../../include/fullname.h
+sendmail.o: ../../include/header_opts.h
+sendmail.o: ../../include/htable.h
+sendmail.o: ../../include/iostuff.h
+sendmail.o: ../../include/mail_conf.h
+sendmail.o: ../../include/mail_dict.h
+sendmail.o: ../../include/mail_flush.h
+sendmail.o: ../../include/mail_params.h
+sendmail.o: ../../include/mail_parm_split.h
+sendmail.o: ../../include/mail_proto.h
+sendmail.o: ../../include/mail_queue.h
+sendmail.o: ../../include/mail_run.h
+sendmail.o: ../../include/mail_stream.h
+sendmail.o: ../../include/mail_task.h
+sendmail.o: ../../include/mail_version.h
+sendmail.o: ../../include/maillog_client.h
+sendmail.o: ../../include/mime_state.h
+sendmail.o: ../../include/msg.h
+sendmail.o: ../../include/msg_stats.h
+sendmail.o: ../../include/msg_vstream.h
+sendmail.o: ../../include/mymalloc.h
+sendmail.o: ../../include/name_code.h
+sendmail.o: ../../include/nvtable.h
+sendmail.o: ../../include/rec_streamlf.h
+sendmail.o: ../../include/rec_type.h
+sendmail.o: ../../include/recipient_list.h
+sendmail.o: ../../include/record.h
+sendmail.o: ../../include/resolve_clnt.h
+sendmail.o: ../../include/safe.h
+sendmail.o: ../../include/set_ugid.h
+sendmail.o: ../../include/split_at.h
+sendmail.o: ../../include/stringops.h
+sendmail.o: ../../include/sys_defs.h
+sendmail.o: ../../include/tok822.h
+sendmail.o: ../../include/user_acl.h
+sendmail.o: ../../include/username.h
+sendmail.o: ../../include/vbuf.h
+sendmail.o: ../../include/verp_sender.h
+sendmail.o: ../../include/vstream.h
+sendmail.o: ../../include/vstring.h
+sendmail.o: ../../include/vstring_vstream.h
+sendmail.o: ../../include/warn_stat.h
+sendmail.o: sendmail.c
diff --git a/src/sendmail/sendmail.c b/src/sendmail/sendmail.c
new file mode 100644
index 0000000..27b3543
--- /dev/null
+++ b/src/sendmail/sendmail.c
@@ -0,0 +1,1510 @@
+/*++
+/* NAME
+/* sendmail 1
+/* SUMMARY
+/* Postfix to Sendmail compatibility interface
+/* SYNOPSIS
+/* \fBsendmail\fR [\fIoption ...\fR] [\fIrecipient ...\fR]
+/*
+/* \fBmailq\fR
+/* \fBsendmail -bp\fR
+/*
+/* \fBnewaliases\fR
+/* \fBsendmail -I\fR
+/* DESCRIPTION
+/* The Postfix \fBsendmail\fR(1) command implements the Postfix
+/* to Sendmail compatibility interface.
+/* For the sake of compatibility with existing applications, some
+/* Sendmail command-line options are recognized but silently ignored.
+/*
+/* By default, Postfix \fBsendmail\fR(1) reads a message from
+/* standard input
+/* until EOF or until it reads a line with only a \fB.\fR character,
+/* and arranges for delivery. Postfix \fBsendmail\fR(1) relies on the
+/* \fBpostdrop\fR(1) command to create a queue file in the \fBmaildrop\fR
+/* directory.
+/*
+/* Specific command aliases are provided for other common modes of
+/* operation:
+/* .IP \fBmailq\fR
+/* List the mail queue. Each entry shows the queue file ID, message
+/* size, arrival time, sender, and the recipients that still need to
+/* be delivered. If mail could not be delivered upon the last attempt,
+/* the reason for failure is shown. The queue ID string is
+/* followed by an optional status character:
+/* .RS
+/* .IP \fB*\fR
+/* The message is in the \fBactive\fR queue, i.e. the message is
+/* selected for delivery.
+/* .IP \fB!\fR
+/* The message is in the \fBhold\fR queue, i.e. no further delivery
+/* attempt will be made until the mail is taken off hold.
+/* .IP \fB#\fR
+/* The message is forced to expire. See the \fBpostsuper\fR(1)
+/* options \fB-e\fR or \fB-f\fR.
+/* .RE
+/* .IP
+/* This mode of operation is implemented by executing the
+/* \fBpostqueue\fR(1) command.
+/* .IP \fBnewaliases\fR
+/* Initialize the alias database. If no input file is specified (with
+/* the \fB-oA\fR option, see below), the program processes the file(s)
+/* specified with the \fBalias_database\fR configuration parameter.
+/* If no alias database type is specified, the program uses the type
+/* specified with the \fBdefault_database_type\fR configuration parameter.
+/* This mode of operation is implemented by running the \fBpostalias\fR(1)
+/* command.
+/* .sp
+/* Note: it may take a minute or so before an alias database update
+/* becomes visible. Use the "\fBpostfix reload\fR" command to eliminate
+/* this delay.
+/* .PP
+/* These and other features can be selected by specifying the
+/* appropriate combination of command-line options. Some features are
+/* controlled by parameters in the \fBmain.cf\fR configuration file.
+/*
+/* The following options are recognized:
+/* .IP "\fB-Am\fR (ignored)"
+/* .IP "\fB-Ac\fR (ignored)"
+/* Postfix sendmail uses the same configuration file regardless of
+/* whether or not a message is an initial submission.
+/* .IP "\fB-B \fIbody_type\fR"
+/* The message body MIME type: \fB7BIT\fR or \fB8BITMIME\fR.
+/* .IP \fB-bd\fR
+/* Go into daemon mode. This mode of operation is implemented by
+/* executing the "\fBpostfix start\fR" command.
+/* .IP "\fB-bh\fR (ignored)"
+/* .IP "\fB-bH\fR (ignored)"
+/* Postfix has no persistent host status database.
+/* .IP \fB-bi\fR
+/* Initialize alias database. See the \fBnewaliases\fR
+/* command above.
+/* .IP \fB-bl\fR
+/* Go into daemon mode. To accept only local connections as
+/* with Sendmail's \fB-bl\fR option, specify "\fBinet_interfaces
+/* = loopback\fR" in the Postfix \fBmain.cf\fR configuration
+/* file.
+/* .IP \fB-bm\fR
+/* Read mail from standard input and arrange for delivery.
+/* This is the default mode of operation.
+/* .IP \fB-bp\fR
+/* List the mail queue. See the \fBmailq\fR command above.
+/* .IP \fB-bs\fR
+/* Stand-alone SMTP server mode. Read SMTP commands from
+/* standard input, and write responses to standard output.
+/* In stand-alone SMTP server mode, mail relaying and other
+/* access controls are disabled by default. To enable them,
+/* run the process as the \fBmail_owner\fR user.
+/* .sp
+/* This mode of operation is implemented by running the
+/* \fBsmtpd\fR(8) daemon.
+/* .IP \fB-bv\fR
+/* Do not collect or deliver a message. Instead, send an email
+/* report after verifying each recipient address. This is useful
+/* for testing address rewriting and routing configurations.
+/* .sp
+/* This feature is available in Postfix version 2.1 and later.
+/* .IP "\fB-C \fIconfig_file\fR"
+/* .IP "\fB-C \fIconfig_dir\fR"
+/* The path name of the Postfix \fBmain.cf\fR file, or of its
+/* parent directory. This information is ignored with Postfix
+/* versions before 2.3.
+/*
+/* With Postfix version 3.2 and later, a non-default directory
+/* must be authorized in the default \fBmain.cf\fR file, through
+/* the alternate_config_directories or multi_instance_directories
+/* parameters.
+/*
+/* With all Postfix versions, you can specify a directory pathname
+/* with the MAIL_CONFIG environment variable to override the
+/* location of configuration files.
+/* .IP "\fB-F \fIfull_name\fR"
+/* Set the sender full name. This overrides the NAME environment
+/* variable, and is used only with messages that
+/* have no \fBFrom:\fR message header.
+/* .IP "\fB-f \fIsender\fR"
+/* Set the envelope sender address. This is the address where
+/* delivery problems are sent to. With Postfix versions before 2.1, the
+/* \fBErrors-To:\fR message header overrides the error return address.
+/* .IP \fB-G\fR
+/* Gateway (relay) submission, as opposed to initial user
+/* submission. Either do not rewrite addresses at all, or
+/* update incomplete addresses with the domain information
+/* specified with \fBremote_header_rewrite_domain\fR.
+/*
+/* This option is ignored before Postfix version 2.3.
+/* .IP "\fB-h \fIhop_count\fR (ignored)"
+/* Hop count limit. Use the \fBhopcount_limit\fR configuration
+/* parameter instead.
+/* .IP \fB-I\fR
+/* Initialize alias database. See the \fBnewaliases\fR
+/* command above.
+/* .IP "\fB-i\fR"
+/* When reading a message from standard input, don't treat a line
+/* with only a \fB.\fR character as the end of input.
+/* .IP "\fB-L \fIlabel\fR (ignored)"
+/* The logging label. Use the \fBsyslog_name\fR configuration
+/* parameter instead.
+/* .IP "\fB-m\fR (ignored)"
+/* Backwards compatibility.
+/* .IP "\fB-N \fIdsn\fR (default: 'delay, failure')"
+/* Delivery status notification control. Specify either a
+/* comma-separated list with one or more of \fBfailure\fR (send
+/* notification when delivery fails), \fBdelay\fR (send
+/* notification when delivery is delayed), or \fBsuccess\fR
+/* (send notification when the message is delivered); or specify
+/* \fBnever\fR (don't send any notifications at all).
+/*
+/* This feature is available in Postfix 2.3 and later.
+/* .IP "\fB-n\fR (ignored)"
+/* Backwards compatibility.
+/* .IP "\fB-oA\fIalias_database\fR"
+/* Non-default alias database. Specify \fIpathname\fR or
+/* \fItype\fR:\fIpathname\fR. See \fBpostalias\fR(1) for
+/* details.
+/* .IP "\fB-O \fIoption=value\fR (ignored)"
+/* Set the named \fIoption\fR to \fIvalue\fR. Use the equivalent
+/* configuration parameter in \fBmain.cf\fR instead.
+/* .IP "\fB-o7\fR (ignored)"
+/* .IP "\fB-o8\fR (ignored)"
+/* To send 8-bit or binary content, use an appropriate MIME encapsulation
+/* and specify the appropriate \fB-B\fR command-line option.
+/* .IP "\fB-oi\fR"
+/* When reading a message from standard input, don't treat a line
+/* with only a \fB.\fR character as the end of input.
+/* .IP "\fB-om\fR (ignored)"
+/* The sender is never eliminated from alias etc. expansions.
+/* .IP "\fB-o \fIx value\fR (ignored)"
+/* Set option \fIx\fR to \fIvalue\fR. Use the equivalent
+/* configuration parameter in \fBmain.cf\fR instead.
+/* .IP "\fB-r \fIsender\fR"
+/* Set the envelope sender address. This is the address where
+/* delivery problems are sent to. With Postfix versions before 2.1, the
+/* \fBErrors-To:\fR message header overrides the error return address.
+/* .IP "\fB-R \fIreturn\fR"
+/* Delivery status notification control. Specify "hdrs" to
+/* return only the header when a message bounces, "full" to
+/* return a full copy (the default behavior).
+/*
+/* The \fB-R\fR option specifies an upper bound; Postfix will
+/* return only the header, when a full copy would exceed the
+/* bounce_size_limit setting.
+/*
+/* This option is ignored before Postfix version 2.10.
+/* .IP \fB-q\fR
+/* Attempt to deliver all queued mail. This is implemented by
+/* executing the \fBpostqueue\fR(1) command.
+/*
+/* Warning: flushing undeliverable mail frequently will result in
+/* poor delivery performance of all other mail.
+/* .IP "\fB-q\fIinterval\fR (ignored)"
+/* The interval between queue runs. Use the \fBqueue_run_delay\fR
+/* configuration parameter instead.
+/* .IP \fB-qI\fIqueueid\fR
+/* Schedule immediate delivery of mail with the specified queue
+/* ID. This option is implemented by executing the
+/* \fBpostqueue\fR(1) command, and is available with Postfix
+/* version 2.4 and later.
+/* .IP \fB-qR\fIsite\fR
+/* Schedule immediate delivery of all mail that is queued for the named
+/* \fIsite\fR. This option accepts only \fIsite\fR names that are
+/* eligible for the "fast flush" service, and is implemented by
+/* executing the \fBpostqueue\fR(1) command.
+/* See \fBflush\fR(8) for more information about the "fast flush"
+/* service.
+/* .IP \fB-qS\fIsite\fR
+/* This command is not implemented. Use the slower "\fBsendmail -q\fR"
+/* command instead.
+/* .IP \fB-t\fR
+/* Extract recipients from message headers. These are added to any
+/* recipients specified on the command line.
+/*
+/* With Postfix versions prior to 2.1, this option requires that
+/* no recipient addresses are specified on the command line.
+/* .IP "\fB-U\fR (ignored)"
+/* Initial user submission.
+/* .IP "\fB-V \fIenvid\fR"
+/* Specify the envelope ID for notification by servers that
+/* support DSN.
+/*
+/* This feature is available in Postfix 2.3 and later.
+/* .IP "\fB-XV\fR (Postfix 2.2 and earlier: \fB-V\fR)"
+/* Variable Envelope Return Path. Given an envelope sender address
+/* of the form \fIowner-listname\fR@\fIorigin\fR, each recipient
+/* \fIuser\fR@\fIdomain\fR receives mail with a personalized envelope
+/* sender address.
+/* .sp
+/* By default, the personalized envelope sender address is
+/* \fIowner-listname\fB+\fIuser\fB=\fIdomain\fR@\fIorigin\fR. The default
+/* \fB+\fR and \fB=\fR characters are configurable with the
+/* \fBdefault_verp_delimiters\fR configuration parameter.
+/* .IP "\fB-XV\fIxy\fR (Postfix 2.2 and earlier: \fB-V\fIxy\fR)"
+/* As \fB-XV\fR, but uses \fIx\fR and \fIy\fR as the VERP delimiter
+/* characters, instead of the characters specified with the
+/* \fBdefault_verp_delimiters\fR configuration parameter.
+/* .IP \fB-v\fR
+/* Send an email report of the first delivery attempt (Postfix
+/* versions 2.1 and later). Mail delivery
+/* always happens in the background. When multiple \fB-v\fR
+/* options are given, enable verbose logging for debugging purposes.
+/* .IP "\fB-X \fIlog_file\fR (ignored)"
+/* Log mailer traffic. Use the \fBdebug_peer_list\fR and
+/* \fBdebug_peer_level\fR configuration parameters instead.
+/* SECURITY
+/* .ad
+/* .fi
+/* By design, this program is not set-user (or group) id.
+/* It is prepared to handle message content from untrusted,
+/* possibly remote, users.
+/*
+/* However, like most Postfix programs, this program does not
+/* enforce a security policy on its command-line arguments.
+/* Instead, it relies on the UNIX system to enforce access
+/* policies based on the effective user and group IDs of the
+/* process. Concretely, this means that running Postfix commands
+/* as root (from sudo or equivalent) on behalf of a non-root
+/* user is likely to create privilege escalation opportunities.
+/*
+/* If an application runs any Postfix programs on behalf of
+/* users that do not have normal shell access to Postfix
+/* commands, then that application MUST restrict user-specified
+/* command-line arguments to avoid privilege escalation.
+/* .IP \(bu
+/* Filter all command-line arguments, for example arguments
+/* that contain a pathname or that specify a database access
+/* method. These pathname checks must reject user-controlled
+/* symlinks or hardlinks to sensitive files, and must not be
+/* vulnerable to TOCTOU race attacks.
+/* .IP \(bu
+/* Disable command options processing for all command arguments
+/* that contain user-specified data. For example, the Postfix
+/* \fBsendmail\fR(1) command line MUST be structured as follows:
+/*
+/* .nf
+/* \fB/path/to/sendmail\fR \fIsystem-arguments\fR \fB--\fR \fIuser-arguments\fR
+/* .fi
+/*
+/* Here, the "\fB--\fR" disables command option processing for
+/* all \fIuser-arguments\fR that follow.
+/* .IP
+/* Without the "\fB--\fR", a malicious user could enable Postfix
+/* \fBsendmail\fR(1) command options, by specifying an email
+/* address that starts with "\fB-\fR".
+/* DIAGNOSTICS
+/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8),
+/* and to the standard error stream.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* .IP "\fBMAIL_VERBOSE\fR (value does not matter)"
+/* Enable verbose logging for debugging purposes.
+/* .IP "\fBMAIL_DEBUG\fR (value does not matter)"
+/* Enable debugging with an external command, as specified with the
+/* \fBdebugger_command\fR configuration parameter.
+/* .IP \fBNAME\fR
+/* The sender full name. This is used only with messages that
+/* have no \fBFrom:\fR message header. See also the \fB-F\fR
+/* option above.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* Available with Postfix 2.9 and later:
+/* .IP "\fBsendmail_fix_line_endings (always)\fR"
+/* Controls how the Postfix sendmail command converts email message
+/* line endings from <CR><LF> into UNIX format (<LF>).
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* The DEBUG_README file gives examples of how to troubleshoot a
+/* Postfix system.
+/* .IP "\fBdebugger_command (empty)\fR"
+/* The external command to execute when a Postfix daemon program is
+/* invoked with the -D option.
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, cause the verbose
+/* logging level to increase by the amount specified in $debug_peer_level.
+/* ACCESS CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBauthorized_flush_users (static:anyone)\fR"
+/* List of users who are authorized to flush the queue.
+/* .IP "\fBauthorized_mailq_users (static:anyone)\fR"
+/* List of users who are authorized to view the queue.
+/* .IP "\fBauthorized_submit_users (static:anyone)\fR"
+/* List of users who are authorized to submit mail with the \fBsendmail\fR(1)
+/* command (and with the privileged \fBpostdrop\fR(1) helper command).
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBbounce_size_limit (50000)\fR"
+/* The maximal amount of original message text that is sent in a
+/* non-delivery notification.
+/* .IP "\fBfork_attempts (5)\fR"
+/* The maximal number of attempts to fork() a child process.
+/* .IP "\fBfork_delay (1s)\fR"
+/* The delay between attempts to fork() a child process.
+/* .IP "\fBhopcount_limit (50)\fR"
+/* The maximal number of Received: message headers that is allowed
+/* in the primary message headers.
+/* .IP "\fBqueue_run_delay (300s)\fR"
+/* The time between deferred queue scans by the queue manager;
+/* prior to Postfix 2.4 the default value was 1000s.
+/* FAST FLUSH CONTROLS
+/* .ad
+/* .fi
+/* The ETRN_README file describes configuration and operation
+/* details for the Postfix "fast flush" service.
+/* .IP "\fBfast_flush_domains ($relay_domains)\fR"
+/* Optional list of destinations that are eligible for per-destination
+/* logfiles with mail that is queued to those destinations.
+/* VERP CONTROLS
+/* .ad
+/* .fi
+/* The VERP_README file describes configuration and operation
+/* details of Postfix support for variable envelope return
+/* path addresses.
+/* .IP "\fBdefault_verp_delimiters (+=)\fR"
+/* The two default VERP delimiter characters.
+/* .IP "\fBverp_delimiter_filter (-=+)\fR"
+/* The characters Postfix accepts as VERP delimiter characters on the
+/* Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBalias_database (see 'postconf -d' output)\fR"
+/* The alias databases for \fBlocal\fR(8) delivery that are updated with
+/* "\fBnewaliases\fR" or with "\fBsendmail -bi\fR".
+/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix support programs and daemon programs.
+/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR"
+/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1)
+/* and \fBpostmap\fR(1) commands.
+/* .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 "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .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.
+/* .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
+/* Postfix 3.2 and later:
+/* .IP "\fBalternate_config_directories (empty)\fR"
+/* A list of non-default Postfix configuration directories that may
+/* be specified with "-c config_directory" on the command line (in the
+/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG
+/* environment parameter.
+/* .IP "\fBmulti_instance_directories (empty)\fR"
+/* An optional list of non-default Postfix configuration directories;
+/* these directories belong to additional Postfix instances that share
+/* the Postfix executable files and documentation with the default
+/* Postfix instance, and that are started, stopped, etc., together
+/* with the default Postfix instance.
+/* FILES
+/* /var/spool/postfix, mail queue
+/* /etc/postfix, configuration files
+/* SEE ALSO
+/* pickup(8), mail pickup daemon
+/* qmgr(8), queue manager
+/* smtpd(8), SMTP server
+/* flush(8), fast flush service
+/* postsuper(1), queue maintenance
+/* postalias(1), create/update/query alias database
+/* postdrop(1), mail posting utility
+/* postfix(1), mail system control
+/* postqueue(1), mail queue control
+/* 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
+/* DEBUG_README, Postfix debugging howto
+/* ETRN_README, Postfix ETRN howto
+/* VERP_README, Postfix VERP howto
+/* 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/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h> /* remove() */
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <sysexits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <username.h>
+#include <fullname.h>
+#include <argv.h>
+#include <safe.h>
+#include <iostuff.h>
+#include <stringops.h>
+#include <set_ugid.h>
+#include <connect.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+#include <maillog_client.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <record.h>
+#include <rec_type.h>
+#include <rec_streamlf.h>
+#include <mail_conf.h>
+#include <cleanup_user.h>
+#include <mail_task.h>
+#include <mail_run.h>
+#include <debug_process.h>
+#include <tok822.h>
+#include <mail_flush.h>
+#include <mail_stream.h>
+#include <verp_sender.h>
+#include <deliver_request.h>
+#include <mime_state.h>
+#include <header_opts.h>
+#include <mail_dict.h>
+#include <user_acl.h>
+#include <dsn_mask.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+ /*
+ * Modes of operation.
+ */
+#define SM_MODE_ENQUEUE 1 /* delivery mode */
+#define SM_MODE_NEWALIAS 2 /* initialize alias database */
+#define SM_MODE_MAILQ 3 /* list mail queue */
+#define SM_MODE_DAEMON 4 /* daemon mode */
+#define SM_MODE_USER 5 /* user (stand-alone) mode */
+#define SM_MODE_FLUSHQ 6 /* user (stand-alone) mode */
+#define SM_MODE_IGNORE 7 /* ignore this mode */
+
+ /*
+ * Flag parade. Flags 8-15 are reserved for delivery request trace flags.
+ */
+#define SM_FLAG_AEOF (1<<0) /* archaic EOF */
+#define SM_FLAG_XRCPT (1<<1) /* extract recipients from headers */
+
+#define SM_FLAG_DEFAULT (SM_FLAG_AEOF)
+
+ /*
+ * VERP support.
+ */
+static char *verp_delims;
+
+ /*
+ * Callback context for extracting recipients.
+ */
+typedef struct SM_STATE {
+ VSTREAM *dst; /* output stream */
+ ARGV *recipients; /* recipients from regular headers */
+ ARGV *resent_recip; /* recipients from resent headers */
+ int resent; /* resent flag */
+ const char *saved_sender; /* for error messages */
+ uid_t uid; /* for error messages */
+ VSTRING *temp; /* scratch buffer */
+} SM_STATE;
+
+ /*
+ * Mail submission ACL, line-end fixing.
+ */
+char *var_submit_acl;
+char *var_sm_fix_eol;
+
+static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SUBMIT_ACL, DEF_SUBMIT_ACL, &var_submit_acl, 0, 0,
+ VAR_SM_FIX_EOL, DEF_SM_FIX_EOL, &var_sm_fix_eol, 1, 0,
+ 0,
+};
+
+ /*
+ * Silly little macros (SLMs).
+ */
+#define STR vstring_str
+
+/* output_text - output partial or complete text line */
+
+static void output_text(void *context, int rec_type, const char *buf, ssize_t len,
+ off_t unused_offset)
+{
+ SM_STATE *state = (SM_STATE *) context;
+
+ if (rec_put(state->dst, rec_type, buf, len) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ state->saved_sender, (long) state->uid);
+}
+
+/* output_header - output one message header */
+
+static void output_header(void *context, int header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t offset)
+{
+ SM_STATE *state = (SM_STATE *) context;
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+ ARGV *rcpt;
+ char *start;
+ char *line;
+ char *next_line;
+ ssize_t len;
+
+ /*
+ * Parse the header line, and save copies of recipient addresses in the
+ * appropriate place.
+ */
+ if (header_class == MIME_HDR_PRIMARY
+ && header_info
+ && (header_info->flags & HDR_OPT_RECIP)
+ && (header_info->flags & HDR_OPT_EXTRACT)
+ && (state->resent == 0 || (header_info->flags & HDR_OPT_RR))) {
+ if (header_info->flags & HDR_OPT_RR) {
+ rcpt = state->resent_recip;
+ if (state->resent == 0)
+ state->resent = 1;
+ } else
+ rcpt = state->recipients;
+ tree = tok822_parse(STR(buf) + strlen(header_info->name) + 1);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++) {
+ tok822_internalize(state->temp, tpp[0]->head, TOK822_STR_DEFL);
+ argv_add(rcpt, STR(state->temp), (char *) 0);
+ }
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ }
+
+ /*
+ * Pipe the unmodified message header through the header line folding
+ * routine, and ensure that long lines are chopped appropriately.
+ */
+ for (line = start = STR(buf); line; line = next_line) {
+ next_line = split_at(line, '\n');
+ len = next_line ? next_line - line - 1 : strlen(line);
+ do {
+ if (len > var_line_limit) {
+ output_text(context, REC_TYPE_CONT, line, var_line_limit, offset);
+ line += var_line_limit;
+ len -= var_line_limit;
+ offset += var_line_limit;
+ } else {
+ output_text(context, REC_TYPE_NORM, line, len, offset);
+ offset += len;
+ break;
+ }
+ } while (len > 0);
+ offset += 1;
+ }
+}
+
+/* enqueue - post one message */
+
+static void enqueue(const int flags, const char *encoding,
+ const char *dsn_envid, int dsn_ret, int dsn_notify,
+ const char *rewrite_context, const char *sender,
+ const char *full_name, char **recipients)
+{
+ VSTRING *buf;
+ VSTREAM *dst;
+ char *saved_sender;
+ char **cpp;
+ int type;
+ char *start;
+ int skip_from_;
+ TOK822 *tree;
+ TOK822 *tp;
+ int rcpt_count = 0;
+ enum {
+ STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR
+ } strip_cr;
+ MAIL_STREAM *handle;
+ VSTRING *postdrop_command;
+ uid_t uid = getuid();
+ int status;
+ VSTRING *why; /* postdrop status message */
+ int naddr;
+ int prev_type;
+ MIME_STATE *mime_state = 0;
+ SM_STATE state;
+ int mime_errs;
+ const char *errstr;
+ int addr_count;
+ int level;
+ static NAME_CODE sm_fix_eol_table[] = {
+ SM_FIX_EOL_ALWAYS, STRIP_CR_DO,
+ SM_FIX_EOL_STRICT, STRIP_CR_DUNNO,
+ SM_FIX_EOL_NEVER, STRIP_CR_DONT,
+ 0, STRIP_CR_ERROR,
+ };
+
+ /*
+ * Access control is enforced in the postdrop command. The code here
+ * merely produces a more user-friendly interface.
+ */
+ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL,
+ var_submit_acl, uid)) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to submit mail", errstr, (long) uid);
+
+ /*
+ * Initialize.
+ */
+ buf = vstring_alloc(100);
+
+ /*
+ * Stop run-away process accidents by limiting the queue file size. This
+ * is not a defense against DOS attack.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && get_file_limit() > var_message_limit)
+ set_file_limit((off_t) var_message_limit);
+
+ /*
+ * The sender name is provided by the user. In principle, the mail pickup
+ * service could deduce the sender name from queue file ownership, but:
+ * pickup would not be able to run chrooted, and it may not be desirable
+ * to use login names at all.
+ */
+ if (sender != 0) {
+ VSTRING_RESET(buf);
+ VSTRING_TERMINATE(buf);
+ tree = tok822_parse(sender);
+ for (naddr = 0, tp = tree; tp != 0; tp = tp->next)
+ if (tp->type == TOK822_ADDR && naddr++ == 0)
+ tok822_internalize(buf, tp->head, TOK822_STR_DEFL);
+ tok822_free_tree(tree);
+ saved_sender = mystrdup(STR(buf));
+ if (naddr > 1)
+ msg_warn("-f option specified malformed sender: %s", sender);
+ } else {
+ if ((sender = username()) == 0)
+ msg_fatal_status(EX_OSERR, "no login name found for user ID %lu",
+ (unsigned long) uid);
+ saved_sender = mystrdup(sender);
+ }
+
+ /*
+ * Let the postdrop command open the queue file for us, and sanity check
+ * the content. XXX Make postdrop a manifest constant.
+ */
+ errno = 0;
+ postdrop_command = vstring_alloc(1000);
+ vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir);
+ for (level = 0; level < msg_verbose; level++)
+ vstring_strcat(postdrop_command, " -v");
+ if ((handle = mail_stream_command(STR(postdrop_command))) == 0)
+ msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m",
+ saved_sender, (long) uid, STR(postdrop_command));
+ vstring_free(postdrop_command);
+ dst = handle->stream;
+
+ /*
+ * First, write envelope information to the output stream.
+ *
+ * For sendmail compatibility, parse each command-line recipient as if it
+ * were an RFC 822 message header; some MUAs specify comma-separated
+ * recipient lists; and some MUAs even specify "word word <address>".
+ *
+ * Sort-uniq-ing the recipient list is done after address canonicalization,
+ * before recipients are written to queue file. That's cleaner than
+ * having the queue manager nuke duplicate recipient status records.
+ *
+ * XXX Should limit the size of envelope records.
+ *
+ * With "sendmail -N", instead of a per-message NOTIFY record we store one
+ * per recipient so that we can simplify the implementation somewhat.
+ */
+ if (dsn_envid)
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, dsn_envid);
+ if (dsn_ret)
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, dsn_ret);
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_RWR_CONTEXT, rewrite_context);
+ if (full_name || (full_name = fullname()) != 0)
+ rec_fputs(dst, REC_TYPE_FULL, full_name);
+ rec_fputs(dst, REC_TYPE_FROM, saved_sender);
+ if (verp_delims && *saved_sender == 0)
+ msg_fatal_status(EX_USAGE,
+ "%s(%ld): -V option requires non-null sender address",
+ saved_sender, (long) uid);
+ if (encoding)
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding);
+ if (DEL_REQ_TRACE_FLAGS(flags))
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS,
+ DEL_REQ_TRACE_FLAGS(flags));
+ if (verp_delims)
+ rec_fputs(dst, REC_TYPE_VERP, verp_delims);
+ if (recipients) {
+ for (cpp = recipients; *cpp != 0; cpp++) {
+ tree = tok822_parse(*cpp);
+ for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ tok822_internalize(buf, tp->head, TOK822_STR_DEFL);
+ if (dsn_notify)
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ saved_sender, (long) uid);
+ ++rcpt_count;
+ ++addr_count;
+ }
+ }
+ tok822_free_tree(tree);
+ if (addr_count == 0) {
+ if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ saved_sender, (long) uid);
+ ++rcpt_count;
+ }
+ }
+ }
+
+ /*
+ * Append the message contents to the queue file. Write chunks of at most
+ * 1kbyte. Internally, we use different record types for data ending in
+ * LF and for data that doesn't, so we can actually be binary transparent
+ * for local mail. Unfortunately, SMTP has no record continuation
+ * convention, so there is no guarantee that arbitrary data will be
+ * delivered intact via SMTP. Strip leading From_ lines. For the benefit
+ * of UUCP environments, also get rid of leading >>>From_ lines.
+ */
+ rec_fputs(dst, REC_TYPE_MESG, "");
+ if (DEL_REQ_TRACE_ONLY(flags) != 0) {
+ if (flags & SM_FLAG_XRCPT)
+ msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv",
+ saved_sender, (long) uid);
+ if (*saved_sender)
+ rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender);
+ rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe");
+ if (recipients) {
+ rec_fprintf(dst, REC_TYPE_CONT, "To:");
+ for (cpp = recipients; *cpp != 0; cpp++) {
+ rec_fprintf(dst, REC_TYPE_NORM, " %s%s",
+ *cpp, cpp[1] ? "," : "");
+ }
+ }
+ } else {
+
+ /*
+ * Initialize the MIME processor and set up the callback context.
+ */
+ if (flags & SM_FLAG_XRCPT) {
+ state.dst = dst;
+ state.recipients = argv_alloc(2);
+ state.resent_recip = argv_alloc(2);
+ state.resent = 0;
+ state.saved_sender = saved_sender;
+ state.uid = uid;
+ state.temp = vstring_alloc(10);
+ mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME
+ | MIME_OPT_REPORT_TRUNC_HEADER,
+ output_header,
+ (MIME_STATE_ANY_END) 0,
+ output_text,
+ (MIME_STATE_ANY_END) 0,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) &state);
+ }
+
+ /*
+ * Process header/body lines.
+ */
+ skip_from_ = 1;
+ strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE,
+ var_sm_fix_eol);
+ if (strip_cr == STRIP_CR_ERROR)
+ msg_fatal_status(EX_USAGE,
+ "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol);
+ for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit))
+ != REC_TYPE_EOF; prev_type = type) {
+ if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) {
+ if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r')
+ strip_cr = STRIP_CR_DO;
+ else
+ strip_cr = STRIP_CR_DONT;
+ }
+ if (skip_from_) {
+ if (type == REC_TYPE_NORM) {
+ start = STR(buf);
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0)
+ continue;
+ }
+ skip_from_ = 0;
+ }
+ if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM)
+ while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r')
+ vstring_truncate(buf, VSTRING_LEN(buf) - 1);
+ if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT
+ && VSTRING_LEN(buf) == 1 && *STR(buf) == '.')
+ break;
+ if (mime_state) {
+ mime_errs = mime_state_update(mime_state, type, STR(buf),
+ VSTRING_LEN(buf));
+ if (mime_errs)
+ msg_fatal_status(EX_DATAERR,
+ "%s(%ld): unable to extract recipients: %s",
+ saved_sender, (long) uid,
+ mime_state_error(mime_errs));
+ } else {
+ if (REC_PUT_BUF(dst, type, buf) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ saved_sender, (long) uid);
+ }
+ }
+ }
+
+ /*
+ * Finish MIME processing. We need a final mime_state_update() call in
+ * order to flush text that is still buffered. That can happen when the
+ * last line did not end in newline.
+ */
+ if (mime_state) {
+ mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0);
+ if (mime_errs)
+ msg_fatal_status(EX_DATAERR,
+ "%s(%ld): unable to extract recipients: %s",
+ saved_sender, (long) uid,
+ mime_state_error(mime_errs));
+ mime_state = mime_state_free(mime_state);
+ }
+
+ /*
+ * Append recipient addresses that were extracted from message headers.
+ */
+ rec_fputs(dst, REC_TYPE_XTRA, "");
+ if (flags & SM_FLAG_XRCPT) {
+ for (cpp = state.resent ? state.resent_recip->argv :
+ state.recipients->argv; *cpp; cpp++) {
+ if (dsn_notify)
+ rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ saved_sender, (long) uid);
+ ++rcpt_count;
+ }
+ argv_free(state.recipients);
+ argv_free(state.resent_recip);
+ vstring_free(state.temp);
+ }
+ if (rcpt_count == 0)
+ msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ?
+ "%s(%ld): No recipient addresses found in message header" :
+ "%s(%ld): Recipient addresses must be specified on"
+ " the command line or via the -t option",
+ saved_sender, (long) uid);
+
+ /*
+ * Identify the end of the queue file.
+ */
+ rec_fputs(dst, REC_TYPE_END, "");
+
+ /*
+ * Make sure that the message makes it to the file system. Once we have
+ * terminated with successful exit status we cannot lose the message due
+ * to "frivolous reasons". If all goes well, prevent the run-time error
+ * handler from removing the file.
+ */
+ if (vstream_ferror(VSTREAM_IN))
+ msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m",
+ saved_sender, (long) uid);
+ why = vstring_alloc(100);
+ if ((status = mail_stream_finish(handle, why)) != CLEANUP_STAT_OK)
+ msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE :
+ (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL :
+ (status & CLEANUP_STAT_NOPERM) ? EX_NOPERM :
+ EX_UNAVAILABLE, "%s(%ld): %s", saved_sender,
+ (long) uid, VSTRING_LEN(why) ?
+ STR(why) : cleanup_strerror(status));
+ vstring_free(why);
+
+ /*
+ * Don't leave them in the dark.
+ */
+ if (DEL_REQ_TRACE_FLAGS(flags)) {
+ vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n",
+ saved_sender);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Cleanup. Not really necessary as we're about to exit, but good for
+ * debugging purposes.
+ */
+ vstring_free(buf);
+ myfree(saved_sender);
+}
+
+/* tempfail - sanitize exit status after library run-time error */
+
+static void tempfail(void)
+{
+ exit(EX_TEMPFAIL);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static char *full_name = 0; /* sendmail -F */
+ struct stat st;
+ char *slash;
+ char *sender = 0; /* sendmail -f */
+ int c;
+ int fd;
+ int mode;
+ ARGV *ext_argv;
+ int debug_me = 0;
+ int err;
+ int n;
+ int flags = SM_FLAG_DEFAULT;
+ char *site_to_flush = 0;
+ char *id_to_flush = 0;
+ char *encoding = 0;
+ char *qtime = 0;
+ const char *errstr;
+ uid_t uid;
+ const char *rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ int dsn_notify = 0;
+ int dsn_ret = 0;
+ const char *dsn_envid = 0;
+ int saved_optind;
+ ARGV *import_env;
+ char *alias_map_from_args = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal_status(EX_OSERR, "open /dev/null: %m");
+
+ /*
+ * The CDE desktop calendar manager leaks a parent file descriptor into
+ * the child process. For the sake of sendmail compatibility we have to
+ * close the file descriptor otherwise mail notification will hang.
+ */
+ for ( /* void */ ; fd < 100; fd++)
+ (void) close(fd);
+
+ /*
+ * Process environment options as early as we can. We might be called
+ * from a set-uid (set-gid) program, so be careful with importing
+ * environment variables.
+ */
+ if (safe_getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (safe_getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Initialize. Set up logging. Read the global configuration file after
+ * command-line processing. Set up signal handlers so that we can clean
+ * up incomplete output.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_cleanup(tempfail);
+ maillog_client_init(mail_task("sendmail"), MAILLOG_CLIENT_FLAG_NONE);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Some sites mistakenly install Postfix sendmail as set-uid root. Drop
+ * set-uid privileges only when root, otherwise some systems will not
+ * reset the saved set-userid, which would be a security vulnerability.
+ */
+ if (geteuid() == 0 && getuid() != 0) {
+ msg_warn("the Postfix sendmail command has set-uid root file permissions");
+ msg_warn("or the command is run from a set-uid root process");
+ msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions");
+ set_ugid(getuid(), getgid());
+ }
+
+ /*
+ * Further initialization. Load main.cf first, so that command-line
+ * options can override main.cf settings. Pre-scan the argument list so
+ * that we load the right main.cf file.
+ */
+#define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx"
+
+ saved_optind = optind;
+ while (argv[OPTIND] != 0) {
+ if (strcmp(argv[OPTIND], "-q") == 0) { /* not getopt compatible */
+ optind++;
+ continue;
+ }
+ if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0)
+ break;
+ if (c == 'C') {
+ VSTRING *buf = vstring_alloc(1);
+ char *dir;
+
+ dir = strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ?
+ sane_dirname(buf, optarg) : optarg;
+ if (strcmp(dir, DEF_CONFIG_DIR) != 0 && geteuid() != 0)
+ mail_conf_checkdir(dir);
+ if (setenv(CONF_ENV_PATH, dir, 1) < 0)
+ msg_fatal_status(EX_UNAVAILABLE, "out of memory");
+ vstring_free(buf);
+ }
+ }
+ optind = saved_optind;
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task("sendmail"), MAILLOG_CLIENT_FLAG_NONE);
+ get_mail_conf_str_table(str_table);
+
+ mail_dict_init();
+
+ if (chdir(var_queue_dir))
+ msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir);
+
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Optionally start the debugger on ourself. This must be done after
+ * reading the global configuration file, because that file specifies
+ * what debugger command to execute.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * The default mode of operation is determined by the process name. It
+ * can, however, be changed via command-line options (for example,
+ * "newaliases -bp" will show the mail queue).
+ */
+ if (strcmp(argv[0], "mailq") == 0) {
+ mode = SM_MODE_MAILQ;
+ } else if (strcmp(argv[0], "newaliases") == 0) {
+ mode = SM_MODE_NEWALIAS;
+ } else if (strcmp(argv[0], "smtpd") == 0) {
+ mode = SM_MODE_DAEMON;
+ } else {
+ mode = SM_MODE_ENQUEUE;
+ }
+
+ /*
+ * Parse JCL. Sendmail has been around for a long time, and has acquired
+ * a large number of options in the course of time. Some options such as
+ * -q are not parsable with GETOPT() and get special treatment.
+ */
+#define OPTIND (optind > 0 ? optind : 1)
+
+ while (argv[OPTIND] != 0) {
+ if (strcmp(argv[OPTIND], "-q") == 0) {
+ if (mode == SM_MODE_DAEMON)
+ msg_warn("ignoring -q option in daemon mode");
+ else
+ mode = SM_MODE_FLUSHQ;
+ optind++;
+ continue;
+ }
+ if (strcmp(argv[OPTIND], "-V") == 0
+ && argv[OPTIND + 1] != 0 && strlen(argv[OPTIND + 1]) == 2) {
+ msg_warn("option -V is deprecated with Postfix 2.3; "
+ "specify -XV instead");
+ argv[OPTIND] = "-XV";
+ }
+ if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) {
+ msg_warn("option %s is deprecated with Postfix 2.3; "
+ "specify -X%s instead",
+ argv[OPTIND], argv[OPTIND] + 1);
+ argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0);
+ }
+ if (strcmp(argv[OPTIND], "-XV") == 0) {
+ verp_delims = var_verp_delims;
+ optind++;
+ continue;
+ }
+ if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0)
+ break;
+ switch (c) {
+ default:
+ if (msg_verbose)
+ msg_info("-%c option ignored", c);
+ break;
+ case 'n':
+ msg_fatal_status(EX_USAGE, "-%c option not supported", c);
+ case 'B':
+ if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */
+ encoding = MAIL_ATTR_ENC_8BIT;
+ else if (strcmp(optarg, "7BIT") == 0) /* RFC 1652 */
+ encoding = MAIL_ATTR_ENC_7BIT;
+ else
+ msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT");
+ break;
+ case 'F': /* full name */
+ full_name = optarg;
+ break;
+ case 'G': /* gateway submission */
+ rewrite_context = MAIL_ATTR_RWR_REMOTE;
+ break;
+ case 'I': /* newaliases */
+ mode = SM_MODE_NEWALIAS;
+ break;
+ case 'N':
+ if ((dsn_notify = dsn_notify_mask(optarg)) == 0)
+ msg_warn("bad -N option value -- ignored");
+ break;
+ case 'R':
+ if ((dsn_ret = dsn_ret_code(optarg)) == 0)
+ msg_warn("bad -R option value -- ignored");
+ break;
+ case 'V': /* DSN, was: VERP */
+ if (strlen(optarg) > 100)
+ msg_warn("too long -V option value -- ignored");
+ else if (!allprint(optarg))
+ msg_warn("bad syntax in -V option value -- ignored");
+ else
+ dsn_envid = optarg;
+ break;
+ case 'X':
+ switch (*optarg) {
+ default:
+ msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg);
+ case 'V': /* VERP */
+ if (verp_delims_verify(optarg + 1) != 0)
+ msg_fatal_status(EX_USAGE, "-V requires two characters from %s",
+ var_verp_filter);
+ verp_delims = optarg + 1;
+ break;
+ }
+ break;
+ case 'b':
+ switch (*optarg) {
+ default:
+ msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg);
+ case 'd': /* daemon mode */
+ case 'l': /* daemon mode */
+ if (mode == SM_MODE_FLUSHQ)
+ msg_warn("ignoring -q option in daemon mode");
+ mode = SM_MODE_DAEMON;
+ break;
+ case 'h': /* print host status */
+ case 'H': /* flush host status */
+ mode = SM_MODE_IGNORE;
+ break;
+ case 'i': /* newaliases */
+ mode = SM_MODE_NEWALIAS;
+ break;
+ case 'm': /* deliver mail */
+ mode = SM_MODE_ENQUEUE;
+ break;
+ case 'p': /* mailq */
+ mode = SM_MODE_MAILQ;
+ break;
+ case 's': /* stand-alone mode */
+ mode = SM_MODE_USER;
+ break;
+ case 'v': /* expand recipients */
+ flags |= DEL_REQ_FLAG_USR_VRFY;
+ break;
+ }
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'i':
+ flags &= ~SM_FLAG_AEOF;
+ break;
+ case 'o':
+ switch (*optarg) {
+ default:
+ if (msg_verbose)
+ msg_info("-%c%c option ignored", c, *optarg);
+ break;
+ case 'A':
+ if (optarg[1] == 0)
+ msg_fatal_status(EX_USAGE, "-oA requires pathname");
+ alias_map_from_args = optarg + 1;
+ break;
+ case '7':
+ case '8':
+ break;
+ case 'i':
+ flags &= ~SM_FLAG_AEOF;
+ break;
+ case 'm':
+ break;
+ }
+ break;
+ case 'r': /* obsoleted by -f */
+ sender = optarg;
+ break;
+ case 'q':
+ if (ISDIGIT(optarg[0])) {
+ qtime = optarg;
+ } else if (optarg[0] == 'R') {
+ site_to_flush = optarg + 1;
+ if (*site_to_flush == 0)
+ msg_fatal_status(EX_USAGE, "specify: -qRsitename");
+ } else if (optarg[0] == 'I') {
+ id_to_flush = optarg + 1;
+ if (*id_to_flush == 0)
+ msg_fatal_status(EX_USAGE, "specify: -qIqueueid");
+ } else {
+ msg_fatal_status(EX_USAGE, "-q%c is not implemented",
+ optarg[0]);
+ }
+ break;
+ case 't':
+ flags |= SM_FLAG_XRCPT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case '?':
+ msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]);
+ }
+ }
+
+ /*
+ * Look for conflicting options and arguments.
+ */
+ if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE)
+ msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode");
+
+ if (site_to_flush && mode != SM_MODE_ENQUEUE)
+ msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode");
+
+ if (id_to_flush && mode != SM_MODE_ENQUEUE)
+ msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode");
+
+ if (flags & DEL_REQ_FLAG_USR_VRFY) {
+ if (flags & SM_FLAG_XRCPT)
+ msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv");
+ if (dsn_notify)
+ msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv");
+ if (dsn_ret)
+ msg_fatal_status(EX_USAGE, "-R option cannot be used with -bv");
+ if (msg_verbose == 1)
+ msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv");
+ }
+
+ /*
+ * The -v option plays double duty. One requests verbose delivery, more
+ * than one requests verbose logging.
+ */
+ if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) {
+ msg_verbose = 0;
+ flags |= DEL_REQ_FLAG_RECORD;
+ }
+
+ /*
+ * Start processing. Everything is delegated to external commands.
+ */
+ if (qtime && mode != SM_MODE_DAEMON)
+ exit(0);
+ switch (mode) {
+ default:
+ msg_panic("unknown operation mode: %d", mode);
+ /* NOTREACHED */
+ case SM_MODE_ENQUEUE:
+ if (site_to_flush) {
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE, "flush site requires no recipient");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_command_dir, ext_argv->argv);
+ /* NOTREACHED */
+ } else if (id_to_flush) {
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_command_dir, ext_argv->argv);
+ /* NOTREACHED */
+ } else {
+ enqueue(flags, encoding, dsn_envid, dsn_ret, dsn_notify,
+ rewrite_context, sender, full_name, argv + OPTIND);
+ exit(0);
+ /* NOTREACHED */
+ }
+ break;
+ case SM_MODE_MAILQ:
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE,
+ "display queue mode requires no recipient");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "postqueue", "-p", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_command_dir, ext_argv->argv);
+ /* NOTREACHED */
+ case SM_MODE_FLUSHQ:
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE,
+ "flush queue mode requires no recipient");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "postqueue", "-f", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_command_dir, ext_argv->argv);
+ /* NOTREACHED */
+ case SM_MODE_DAEMON:
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE, "daemon mode requires no recipient");
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "postfix", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_add(ext_argv, "start", (char *) 0);
+ argv_terminate(ext_argv);
+ err = (mail_run_background(var_command_dir, ext_argv->argv) < 0);
+ argv_free(ext_argv);
+ exit(err);
+ break;
+ case SM_MODE_NEWALIAS:
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE,
+ "alias initialization mode requires no recipient");
+ if (alias_map_from_args == 0 && *var_alias_db_map == 0)
+ return (0);
+ ext_argv = argv_alloc(3);
+ argv_add(ext_argv, "postalias", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_add(ext_argv, "--", (char *) 0);
+ if (alias_map_from_args != 0)
+ argv_add(ext_argv, alias_map_from_args, (char *) 0);
+ else
+ argv_split_append(ext_argv, var_alias_db_map, CHARS_COMMA_SP);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_command_dir, ext_argv->argv);
+ /* NOTREACHED */
+ case SM_MODE_USER:
+ if (argv[OPTIND])
+ msg_fatal_status(EX_USAGE,
+ "stand-alone mode requires no recipient");
+ /* The actual enforcement happens in the postdrop command. */
+ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl,
+ uid = getuid())) != 0)
+ msg_fatal_status(EX_NOPERM,
+ "User %s(%ld) is not allowed to submit mail",
+ errstr, (long) uid);
+ ext_argv = argv_alloc(2);
+ argv_add(ext_argv, "smtpd", "-S", (char *) 0);
+ for (n = 0; n < msg_verbose; n++)
+ argv_add(ext_argv, "-v", (char *) 0);
+ argv_terminate(ext_argv);
+ mail_run_replace(var_daemon_dir, ext_argv->argv);
+ /* NOTREACHED */
+ case SM_MODE_IGNORE:
+ exit(0);
+ /* NOTREACHED */
+ }
+}
diff --git a/src/showq/.indent.pro b/src/showq/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/showq/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/showq/.printfck b/src/showq/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/showq/.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/showq/Makefile.in b/src/showq/Makefile.in
new file mode 100644
index 0000000..47b82e7
--- /dev/null
+++ b/src/showq/Makefile.in
@@ -0,0 +1,95 @@
+SHELL = /bin/sh
+SRCS = showq.c
+OBJS = showq.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = showq
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+showq.o: ../../include/attr.h
+showq.o: ../../include/bounce_log.h
+showq.o: ../../include/check_arg.h
+showq.o: ../../include/dsn.h
+showq.o: ../../include/dsn_buf.h
+showq.o: ../../include/htable.h
+showq.o: ../../include/iostuff.h
+showq.o: ../../include/mail_addr.h
+showq.o: ../../include/mail_conf.h
+showq.o: ../../include/mail_date.h
+showq.o: ../../include/mail_open_ok.h
+showq.o: ../../include/mail_params.h
+showq.o: ../../include/mail_proto.h
+showq.o: ../../include/mail_queue.h
+showq.o: ../../include/mail_scan_dir.h
+showq.o: ../../include/mail_server.h
+showq.o: ../../include/mail_version.h
+showq.o: ../../include/msg.h
+showq.o: ../../include/mymalloc.h
+showq.o: ../../include/nvtable.h
+showq.o: ../../include/quote_822_local.h
+showq.o: ../../include/quote_flags.h
+showq.o: ../../include/rcpt_buf.h
+showq.o: ../../include/rec_type.h
+showq.o: ../../include/recipient_list.h
+showq.o: ../../include/record.h
+showq.o: ../../include/scan_dir.h
+showq.o: ../../include/stringops.h
+showq.o: ../../include/sys_defs.h
+showq.o: ../../include/vbuf.h
+showq.o: ../../include/vstream.h
+showq.o: ../../include/vstring.h
+showq.o: ../../include/vstring_vstream.h
+showq.o: showq.c
diff --git a/src/showq/showq.c b/src/showq/showq.c
new file mode 100644
index 0000000..b8dd7e9
--- /dev/null
+++ b/src/showq/showq.c
@@ -0,0 +1,438 @@
+/*++
+/* NAME
+/* showq 8
+/* SUMMARY
+/* list the Postfix mail queue
+/* SYNOPSIS
+/* \fBshowq\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBshowq\fR(8) daemon reports the Postfix mail queue status.
+/* The output is meant to be formatted by the postqueue(1) command,
+/* as it emulates the Sendmail `mailq' command.
+/*
+/* The \fBshowq\fR(8) daemon can also be run in stand-alone mode
+/* by the superuser. This mode of operation is used to emulate
+/* the `mailq' command while the Postfix mail system is down.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low
+/* privilege, and takes no input from the client. Its service port
+/* is accessible to local untrusted users, so the service can be
+/* susceptible to denial of service attacks.
+/* STANDARDS
+/* .ad
+/* .fi
+/* None. The \fBshowq\fR(8) daemon does not interact with the
+/* outside world.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\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.
+/* .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 "\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 "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+/* The recipient of mail addressed to the null address.
+/* .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 "\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 "\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.9 and later:
+/* .IP "\fBenable_long_queue_ids (no)\fR"
+/* Enable long, non-repeating, queue IDs (queue file names).
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* FILES
+/* /var/spool/postfix, queue directories
+/* SEE ALSO
+/* pickup(8), local mail pickup service
+/* cleanup(8), canonicalize and enqueue mail
+/* qmgr(8), queue manager
+/* postconf(5), configuration parameters
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <scan_dir.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_open_ok.h>
+#include <mail_proto.h>
+#include <mail_date.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_scan_dir.h>
+#include <mail_conf.h>
+#include <record.h>
+#include <rec_type.h>
+#include <quote_822_local.h>
+#include <mail_addr.h>
+#include <bounce_log.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+int var_dup_filter_limit;
+char *var_empty_addr;
+
+static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
+ HTABLE *);
+
+#define STR(x) vstring_str(x)
+
+/* showq_report - report status of sender and recipients */
+
+static void showq_report(VSTREAM *client, char *queue, char *id,
+ VSTREAM *qfile, long size, time_t mtime,
+ mode_t mode)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *printable_quoted_addr = vstring_alloc(100);
+ int rec_type;
+ time_t arrival_time = 0;
+ char *start;
+ long msg_size = size;
+ BOUNCE_LOG *logfile;
+ HTABLE *dup_filter = 0;
+ RCPT_BUF *rcpt_buf = 0;
+ DSN_BUF *dsn_buf = 0;
+ int sender_seen = 0;
+ int msg_size_ok = 0;
+
+ /*
+ * Let the optimizer worry about eliminating duplicate code.
+ */
+#define SHOWQ_CLEANUP_AND_RETURN { \
+ if (sender_seen > 0) \
+ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \
+ vstring_free(buf); \
+ vstring_free(printable_quoted_addr); \
+ if (rcpt_buf) \
+ rcpb_free(rcpt_buf); \
+ if (dsn_buf) \
+ dsb_free(dsn_buf); \
+ if (dup_filter) \
+ htable_free(dup_filter, (void (*) (void *)) 0); \
+ }
+
+ /*
+ * XXX addresses in defer logfiles are in printable quoted form, while
+ * addresses in message envelope records are in raw unquoted form. This
+ * may change once we replace the present ad-hoc bounce/defer logfile
+ * format by one that is transparent for control etc. characters. See
+ * also: bounce/bounce_append_service.c.
+ *
+ * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
+ * from previous cleanup runs. Skip the obsolete size records.
+ */
+ while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
+ start = vstring_str(buf);
+ if (msg_verbose)
+ msg_info("record %c %s", rec_type, printable(start, '?'));
+ switch (rec_type) {
+ case REC_TYPE_TIME:
+ /* TODO: parse seconds and microseconds. */
+ if (arrival_time == 0)
+ arrival_time = atol(start);
+ break;
+ case REC_TYPE_SIZE:
+ if (msg_size_ok == 0) {
+ msg_size_ok = (start[strspn(start, "0123456789 ")] == 0
+ && (msg_size = atol(start)) >= 0);
+ if (msg_size_ok == 0) {
+ msg_warn("%s: malformed size record: %.100s "
+ "-- using file size instead",
+ id, printable(start, '?'));
+ msg_size = size;
+ }
+ }
+ break;
+ case REC_TYPE_FROM:
+ if (*start == 0)
+ start = var_empty_addr;
+ quote_822_local(printable_quoted_addr, start);
+ printable(STR(printable_quoted_addr), '?');
+ if (sender_seen++ > 0) {
+ msg_warn("%s: duplicate sender address: %s "
+ "-- skipping remainder of this file",
+ id, STR(printable_quoted_addr));
+ SHOWQ_CLEANUP_AND_RETURN;
+ }
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ?
+ arrival_time : mtime),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size),
+ SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE,
+ (mode & MAIL_QUEUE_STAT_EXPIRE) != 0),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER,
+ STR(printable_quoted_addr)),
+ ATTR_TYPE_END);
+ break;
+ case REC_TYPE_RCPT:
+ if (sender_seen == 0) {
+ msg_warn("%s: missing sender address: %s "
+ "-- skipping remainder of this file",
+ id, STR(printable_quoted_addr));
+ SHOWQ_CLEANUP_AND_RETURN;
+ }
+ if (*start == 0) /* can't happen? */
+ start = var_empty_addr;
+ quote_822_local(printable_quoted_addr, start);
+ printable(STR(printable_quoted_addr), '?');
+ if (dup_filter == 0
+ || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_RECIP,
+ STR(printable_quoted_addr)),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, ""),
+ ATTR_TYPE_END);
+ break;
+ case REC_TYPE_MESG:
+ if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
+ break;
+ case REC_TYPE_END:
+ break;
+ }
+
+ /*
+ * Before listing any recipients from the queue file, try to list
+ * recipients from the corresponding defer logfile with per-recipient
+ * descriptions why delivery was deferred.
+ *
+ * The defer logfile is not necessarily complete: delivery may be
+ * interrupted (postfix stop or reload) before all recipients have
+ * been tried.
+ *
+ * Therefore we keep a record of recipients found in the defer logfile,
+ * and try to avoid listing those recipients again when processing
+ * recipients from the queue file.
+ */
+ if (rec_type == REC_TYPE_FROM
+ && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
+ if (dup_filter != 0)
+ msg_panic("showq_report: attempt to reuse duplicate filter");
+ dup_filter = htable_create(var_dup_filter_limit);
+ if (rcpt_buf == 0)
+ rcpt_buf = rcpb_create();
+ if (dsn_buf == 0)
+ dsn_buf = dsb_create();
+ showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
+ if (bounce_log_close(logfile))
+ msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
+ }
+ }
+ SHOWQ_CLEANUP_AND_RETURN;
+}
+
+/* showq_reasons - show deferral reasons */
+
+static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf, HTABLE *dup_filter)
+{
+ RECIPIENT *rcpt = &rcpt_buf->rcpt;
+ DSN *dsn = &dsn_buf->dsn;
+
+ while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
+
+ /*
+ * Update the duplicate filter.
+ */
+ if (var_dup_filter_limit == 0
+ || dup_filter->used < var_dup_filter_limit)
+ if (htable_locate(dup_filter, rcpt->address) == 0)
+ htable_enter(dup_filter, rcpt->address, (void *) 0);
+
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason),
+ ATTR_TYPE_END);
+ }
+}
+
+
+/* showq_service - service client */
+
+static void showq_service(VSTREAM *client, char *unused_service, char **argv)
+{
+ VSTREAM *qfile;
+ const char *path;
+ int status;
+ char *id;
+ struct stat st;
+ struct queue_info {
+ char *name; /* queue name */
+ char *(*scan_next) (SCAN_DIR *); /* flat or recursive */
+ };
+ struct queue_info *qp;
+
+ static struct queue_info queue_info[] = {
+ MAIL_QUEUE_MAILDROP, scan_dir_next,
+ MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
+ MAIL_QUEUE_INCOMING, mail_scan_dir_next,
+ MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
+ MAIL_QUEUE_HOLD, mail_scan_dir_next,
+ 0,
+ };
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Protocol identification.
+ */
+ (void) attr_print(client, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ),
+ ATTR_TYPE_END);
+
+ /*
+ * Skip any files that have the wrong permissions. If we can't open an
+ * existing file, assume the system is out of resources or that it is
+ * mis-configured, and force backoff by raising a fatal error.
+ */
+ for (qp = queue_info; qp->name != 0; qp++) {
+ SCAN_DIR *scan = scan_dir_open(qp->name);
+ char *saved_id = 0;
+
+ while ((id = qp->scan_next(scan)) != 0) {
+
+ /*
+ * XXX I have seen showq loop on the same queue id. That would be
+ * an operating system bug, but who cares whose fault it is. Make
+ * sure this will never happen again.
+ */
+ if (saved_id) {
+ if (strcmp(saved_id, id) == 0) {
+ msg_warn("readdir loop on queue %s id %s", qp->name, id);
+ break;
+ }
+ myfree(saved_id);
+ }
+ saved_id = mystrdup(id);
+ status = mail_open_ok(qp->name, id, &st, &path);
+ if (status == MAIL_OPEN_YES) {
+ if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
+ showq_report(client, qp->name, id, qfile, (long) st.st_size,
+ st.st_mtime, st.st_mode);
+ if (vstream_fclose(qfile))
+ msg_warn("close file %s %s: %m", qp->name, id);
+ } else if (errno != ENOENT) {
+ msg_warn("open %s %s: %m", qp->name, id);
+ }
+ }
+ vstream_fflush(client);
+ }
+ if (saved_id)
+ myfree(saved_id);
+ scan_dir_close(scan);
+ }
+ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded server skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
+ 0,
+ };
+ CONFIG_STR_TABLE str_table[] = {
+ VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, showq_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ 0);
+}
diff --git a/src/smtp/.indent.pro b/src/smtp/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtp/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtp/.printfck b/src/smtp/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtp/.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/smtp/Makefile.in b/src/smtp/Makefile.in
new file mode 100644
index 0000000..65f4198
--- /dev/null
+++ b/src/smtp/Makefile.in
@@ -0,0 +1,878 @@
+SHELL = /bin/sh
+SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \
+ smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_policy.c \
+ smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \
+ smtp_sasl_auth_cache.c smtp_key.c smtp_misc.c
+OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \
+ smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_policy.o \
+ smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \
+ smtp_sasl_auth_cache.o smtp_key.o smtp_misc.o
+HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= smtp_unalias smtp_map11
+PROG = smtp
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/libxsasl.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)
+
+tests: smtp_map11_test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+smtp.o: smtp.c smtp_params.c lmtp_params.c
+
+lmtp_params.c: smtp_params.c
+ egrep -v -f smtp-only smtp_params.c | \
+ sed 's/SMTP/LMTP/g; s/smtp_\([a-z]*_table\)/lmtp_\1/' >$@
+
+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
+
+smtp_unalias: smtp_unalias.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtp_map11: smtp_map11.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtp_map11_test: smtp_map11 smtp_map11.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtp_map11 <smtp_map11.in >smtp_map11.tmp 2>&1
+ diff smtp_map11.ref smtp_map11.tmp
+ rm -f smtp_map11.tmp
+
+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'
+lmtp_params.o: lmtp_params.c
+smtp.o: ../../include/argv.h
+smtp.o: ../../include/attr.h
+smtp.o: ../../include/byte_mask.h
+smtp.o: ../../include/check_arg.h
+smtp.o: ../../include/debug_peer.h
+smtp.o: ../../include/deliver_request.h
+smtp.o: ../../include/delivered_hdr.h
+smtp.o: ../../include/dict.h
+smtp.o: ../../include/dns.h
+smtp.o: ../../include/dsn.h
+smtp.o: ../../include/dsn_buf.h
+smtp.o: ../../include/ext_prop.h
+smtp.o: ../../include/flush_clnt.h
+smtp.o: ../../include/fold_addr.h
+smtp.o: ../../include/header_body_checks.h
+smtp.o: ../../include/header_opts.h
+smtp.o: ../../include/hfrom_format.h
+smtp.o: ../../include/htable.h
+smtp.o: ../../include/iostuff.h
+smtp.o: ../../include/mail_conf.h
+smtp.o: ../../include/mail_params.h
+smtp.o: ../../include/mail_proto.h
+smtp.o: ../../include/mail_server.h
+smtp.o: ../../include/mail_version.h
+smtp.o: ../../include/maps.h
+smtp.o: ../../include/match_list.h
+smtp.o: ../../include/mime_state.h
+smtp.o: ../../include/msg.h
+smtp.o: ../../include/msg_stats.h
+smtp.o: ../../include/myaddrinfo.h
+smtp.o: ../../include/myflock.h
+smtp.o: ../../include/mymalloc.h
+smtp.o: ../../include/name_code.h
+smtp.o: ../../include/name_mask.h
+smtp.o: ../../include/nvtable.h
+smtp.o: ../../include/recipient_list.h
+smtp.o: ../../include/resolve_clnt.h
+smtp.o: ../../include/scache.h
+smtp.o: ../../include/sock_addr.h
+smtp.o: ../../include/string_list.h
+smtp.o: ../../include/stringops.h
+smtp.o: ../../include/sys_defs.h
+smtp.o: ../../include/tls.h
+smtp.o: ../../include/tls_proxy.h
+smtp.o: ../../include/tok822.h
+smtp.o: ../../include/vbuf.h
+smtp.o: ../../include/vstream.h
+smtp.o: ../../include/vstring.h
+smtp.o: lmtp_params.c
+smtp.o: smtp.c
+smtp.o: smtp.h
+smtp.o: smtp_params.c
+smtp.o: smtp_sasl.h
+smtp_addr.o: ../../include/argv.h
+smtp_addr.o: ../../include/attr.h
+smtp_addr.o: ../../include/check_arg.h
+smtp_addr.o: ../../include/deliver_request.h
+smtp_addr.o: ../../include/dict.h
+smtp_addr.o: ../../include/dns.h
+smtp_addr.o: ../../include/dsn.h
+smtp_addr.o: ../../include/dsn_buf.h
+smtp_addr.o: ../../include/header_body_checks.h
+smtp_addr.o: ../../include/header_opts.h
+smtp_addr.o: ../../include/htable.h
+smtp_addr.o: ../../include/inet_addr_list.h
+smtp_addr.o: ../../include/inet_proto.h
+smtp_addr.o: ../../include/mail_params.h
+smtp_addr.o: ../../include/maps.h
+smtp_addr.o: ../../include/match_list.h
+smtp_addr.o: ../../include/midna_domain.h
+smtp_addr.o: ../../include/mime_state.h
+smtp_addr.o: ../../include/msg.h
+smtp_addr.o: ../../include/msg_stats.h
+smtp_addr.o: ../../include/myaddrinfo.h
+smtp_addr.o: ../../include/myflock.h
+smtp_addr.o: ../../include/mymalloc.h
+smtp_addr.o: ../../include/name_code.h
+smtp_addr.o: ../../include/name_mask.h
+smtp_addr.o: ../../include/nvtable.h
+smtp_addr.o: ../../include/own_inet_addr.h
+smtp_addr.o: ../../include/recipient_list.h
+smtp_addr.o: ../../include/resolve_clnt.h
+smtp_addr.o: ../../include/scache.h
+smtp_addr.o: ../../include/sock_addr.h
+smtp_addr.o: ../../include/string_list.h
+smtp_addr.o: ../../include/stringops.h
+smtp_addr.o: ../../include/sys_defs.h
+smtp_addr.o: ../../include/tls.h
+smtp_addr.o: ../../include/tls_proxy.h
+smtp_addr.o: ../../include/tok822.h
+smtp_addr.o: ../../include/vbuf.h
+smtp_addr.o: ../../include/vstream.h
+smtp_addr.o: ../../include/vstring.h
+smtp_addr.o: smtp.h
+smtp_addr.o: smtp_addr.c
+smtp_addr.o: smtp_addr.h
+smtp_chat.o: ../../include/argv.h
+smtp_chat.o: ../../include/attr.h
+smtp_chat.o: ../../include/check_arg.h
+smtp_chat.o: ../../include/cleanup_user.h
+smtp_chat.o: ../../include/deliver_request.h
+smtp_chat.o: ../../include/dict.h
+smtp_chat.o: ../../include/dns.h
+smtp_chat.o: ../../include/dsn.h
+smtp_chat.o: ../../include/dsn_buf.h
+smtp_chat.o: ../../include/dsn_util.h
+smtp_chat.o: ../../include/header_body_checks.h
+smtp_chat.o: ../../include/header_opts.h
+smtp_chat.o: ../../include/hfrom_format.h
+smtp_chat.o: ../../include/htable.h
+smtp_chat.o: ../../include/int_filt.h
+smtp_chat.o: ../../include/iostuff.h
+smtp_chat.o: ../../include/line_wrap.h
+smtp_chat.o: ../../include/mail_addr.h
+smtp_chat.o: ../../include/mail_error.h
+smtp_chat.o: ../../include/mail_params.h
+smtp_chat.o: ../../include/mail_proto.h
+smtp_chat.o: ../../include/maps.h
+smtp_chat.o: ../../include/match_list.h
+smtp_chat.o: ../../include/mime_state.h
+smtp_chat.o: ../../include/msg.h
+smtp_chat.o: ../../include/msg_stats.h
+smtp_chat.o: ../../include/myaddrinfo.h
+smtp_chat.o: ../../include/myflock.h
+smtp_chat.o: ../../include/mymalloc.h
+smtp_chat.o: ../../include/name_code.h
+smtp_chat.o: ../../include/name_mask.h
+smtp_chat.o: ../../include/nvtable.h
+smtp_chat.o: ../../include/post_mail.h
+smtp_chat.o: ../../include/recipient_list.h
+smtp_chat.o: ../../include/resolve_clnt.h
+smtp_chat.o: ../../include/scache.h
+smtp_chat.o: ../../include/smtp_stream.h
+smtp_chat.o: ../../include/smtputf8.h
+smtp_chat.o: ../../include/sock_addr.h
+smtp_chat.o: ../../include/string_list.h
+smtp_chat.o: ../../include/stringops.h
+smtp_chat.o: ../../include/sys_defs.h
+smtp_chat.o: ../../include/tls.h
+smtp_chat.o: ../../include/tls_proxy.h
+smtp_chat.o: ../../include/tok822.h
+smtp_chat.o: ../../include/vbuf.h
+smtp_chat.o: ../../include/vstream.h
+smtp_chat.o: ../../include/vstring.h
+smtp_chat.o: smtp.h
+smtp_chat.o: smtp_chat.c
+smtp_connect.o: ../../include/argv.h
+smtp_connect.o: ../../include/attr.h
+smtp_connect.o: ../../include/check_arg.h
+smtp_connect.o: ../../include/deliver_pass.h
+smtp_connect.o: ../../include/deliver_request.h
+smtp_connect.o: ../../include/dict.h
+smtp_connect.o: ../../include/dns.h
+smtp_connect.o: ../../include/dsn.h
+smtp_connect.o: ../../include/dsn_buf.h
+smtp_connect.o: ../../include/header_body_checks.h
+smtp_connect.o: ../../include/header_opts.h
+smtp_connect.o: ../../include/host_port.h
+smtp_connect.o: ../../include/htable.h
+smtp_connect.o: ../../include/inet_addr_list.h
+smtp_connect.o: ../../include/inet_proto.h
+smtp_connect.o: ../../include/iostuff.h
+smtp_connect.o: ../../include/known_tcp_ports.h
+smtp_connect.o: ../../include/mail_addr.h
+smtp_connect.o: ../../include/mail_error.h
+smtp_connect.o: ../../include/mail_params.h
+smtp_connect.o: ../../include/mail_proto.h
+smtp_connect.o: ../../include/maps.h
+smtp_connect.o: ../../include/match_list.h
+smtp_connect.o: ../../include/mime_state.h
+smtp_connect.o: ../../include/msg.h
+smtp_connect.o: ../../include/msg_stats.h
+smtp_connect.o: ../../include/myaddrinfo.h
+smtp_connect.o: ../../include/myflock.h
+smtp_connect.o: ../../include/mymalloc.h
+smtp_connect.o: ../../include/name_code.h
+smtp_connect.o: ../../include/name_mask.h
+smtp_connect.o: ../../include/nvtable.h
+smtp_connect.o: ../../include/own_inet_addr.h
+smtp_connect.o: ../../include/recipient_list.h
+smtp_connect.o: ../../include/resolve_clnt.h
+smtp_connect.o: ../../include/sane_connect.h
+smtp_connect.o: ../../include/scache.h
+smtp_connect.o: ../../include/sock_addr.h
+smtp_connect.o: ../../include/split_at.h
+smtp_connect.o: ../../include/string_list.h
+smtp_connect.o: ../../include/stringops.h
+smtp_connect.o: ../../include/sys_defs.h
+smtp_connect.o: ../../include/timed_connect.h
+smtp_connect.o: ../../include/tls.h
+smtp_connect.o: ../../include/tls_proxy.h
+smtp_connect.o: ../../include/tok822.h
+smtp_connect.o: ../../include/vbuf.h
+smtp_connect.o: ../../include/vstream.h
+smtp_connect.o: ../../include/vstring.h
+smtp_connect.o: smtp.h
+smtp_connect.o: smtp_addr.h
+smtp_connect.o: smtp_connect.c
+smtp_connect.o: smtp_reuse.h
+smtp_key.o: ../../include/argv.h
+smtp_key.o: ../../include/attr.h
+smtp_key.o: ../../include/base64_code.h
+smtp_key.o: ../../include/check_arg.h
+smtp_key.o: ../../include/deliver_request.h
+smtp_key.o: ../../include/dict.h
+smtp_key.o: ../../include/dns.h
+smtp_key.o: ../../include/dsn.h
+smtp_key.o: ../../include/dsn_buf.h
+smtp_key.o: ../../include/header_body_checks.h
+smtp_key.o: ../../include/header_opts.h
+smtp_key.o: ../../include/htable.h
+smtp_key.o: ../../include/mail_params.h
+smtp_key.o: ../../include/maps.h
+smtp_key.o: ../../include/match_list.h
+smtp_key.o: ../../include/mime_state.h
+smtp_key.o: ../../include/msg.h
+smtp_key.o: ../../include/msg_stats.h
+smtp_key.o: ../../include/myaddrinfo.h
+smtp_key.o: ../../include/myflock.h
+smtp_key.o: ../../include/mymalloc.h
+smtp_key.o: ../../include/name_code.h
+smtp_key.o: ../../include/name_mask.h
+smtp_key.o: ../../include/nvtable.h
+smtp_key.o: ../../include/recipient_list.h
+smtp_key.o: ../../include/resolve_clnt.h
+smtp_key.o: ../../include/scache.h
+smtp_key.o: ../../include/sock_addr.h
+smtp_key.o: ../../include/string_list.h
+smtp_key.o: ../../include/sys_defs.h
+smtp_key.o: ../../include/tls.h
+smtp_key.o: ../../include/tls_proxy.h
+smtp_key.o: ../../include/tok822.h
+smtp_key.o: ../../include/vbuf.h
+smtp_key.o: ../../include/vstream.h
+smtp_key.o: ../../include/vstring.h
+smtp_key.o: smtp.h
+smtp_key.o: smtp_key.c
+smtp_map11.o: ../../include/argv.h
+smtp_map11.o: ../../include/attr.h
+smtp_map11.o: ../../include/check_arg.h
+smtp_map11.o: ../../include/deliver_request.h
+smtp_map11.o: ../../include/dict.h
+smtp_map11.o: ../../include/dns.h
+smtp_map11.o: ../../include/dsn.h
+smtp_map11.o: ../../include/dsn_buf.h
+smtp_map11.o: ../../include/header_body_checks.h
+smtp_map11.o: ../../include/header_opts.h
+smtp_map11.o: ../../include/htable.h
+smtp_map11.o: ../../include/mail_addr_form.h
+smtp_map11.o: ../../include/mail_addr_map.h
+smtp_map11.o: ../../include/maps.h
+smtp_map11.o: ../../include/match_list.h
+smtp_map11.o: ../../include/mime_state.h
+smtp_map11.o: ../../include/msg.h
+smtp_map11.o: ../../include/msg_stats.h
+smtp_map11.o: ../../include/myaddrinfo.h
+smtp_map11.o: ../../include/myflock.h
+smtp_map11.o: ../../include/mymalloc.h
+smtp_map11.o: ../../include/name_code.h
+smtp_map11.o: ../../include/name_mask.h
+smtp_map11.o: ../../include/nvtable.h
+smtp_map11.o: ../../include/quote_822_local.h
+smtp_map11.o: ../../include/quote_flags.h
+smtp_map11.o: ../../include/recipient_list.h
+smtp_map11.o: ../../include/resolve_clnt.h
+smtp_map11.o: ../../include/scache.h
+smtp_map11.o: ../../include/sock_addr.h
+smtp_map11.o: ../../include/string_list.h
+smtp_map11.o: ../../include/sys_defs.h
+smtp_map11.o: ../../include/tls.h
+smtp_map11.o: ../../include/tls_proxy.h
+smtp_map11.o: ../../include/tok822.h
+smtp_map11.o: ../../include/vbuf.h
+smtp_map11.o: ../../include/vstream.h
+smtp_map11.o: ../../include/vstring.h
+smtp_map11.o: smtp.h
+smtp_map11.o: smtp_map11.c
+smtp_misc.o: ../../include/argv.h
+smtp_misc.o: ../../include/attr.h
+smtp_misc.o: ../../include/check_arg.h
+smtp_misc.o: ../../include/deliver_request.h
+smtp_misc.o: ../../include/dict.h
+smtp_misc.o: ../../include/dns.h
+smtp_misc.o: ../../include/dsn.h
+smtp_misc.o: ../../include/dsn_buf.h
+smtp_misc.o: ../../include/ext_prop.h
+smtp_misc.o: ../../include/header_body_checks.h
+smtp_misc.o: ../../include/header_opts.h
+smtp_misc.o: ../../include/htable.h
+smtp_misc.o: ../../include/mail_params.h
+smtp_misc.o: ../../include/maps.h
+smtp_misc.o: ../../include/match_list.h
+smtp_misc.o: ../../include/mime_state.h
+smtp_misc.o: ../../include/msg_stats.h
+smtp_misc.o: ../../include/myaddrinfo.h
+smtp_misc.o: ../../include/myflock.h
+smtp_misc.o: ../../include/mymalloc.h
+smtp_misc.o: ../../include/name_code.h
+smtp_misc.o: ../../include/name_mask.h
+smtp_misc.o: ../../include/nvtable.h
+smtp_misc.o: ../../include/quote_821_local.h
+smtp_misc.o: ../../include/quote_822_local.h
+smtp_misc.o: ../../include/quote_flags.h
+smtp_misc.o: ../../include/recipient_list.h
+smtp_misc.o: ../../include/resolve_clnt.h
+smtp_misc.o: ../../include/scache.h
+smtp_misc.o: ../../include/sock_addr.h
+smtp_misc.o: ../../include/string_list.h
+smtp_misc.o: ../../include/sys_defs.h
+smtp_misc.o: ../../include/tls.h
+smtp_misc.o: ../../include/tls_proxy.h
+smtp_misc.o: ../../include/tok822.h
+smtp_misc.o: ../../include/vbuf.h
+smtp_misc.o: ../../include/vstream.h
+smtp_misc.o: ../../include/vstring.h
+smtp_misc.o: smtp.h
+smtp_misc.o: smtp_misc.c
+smtp_params.o: smtp_params.c
+smtp_proto.o: ../../include/argv.h
+smtp_proto.o: ../../include/attr.h
+smtp_proto.o: ../../include/bounce.h
+smtp_proto.o: ../../include/check_arg.h
+smtp_proto.o: ../../include/defer.h
+smtp_proto.o: ../../include/deliver_request.h
+smtp_proto.o: ../../include/dict.h
+smtp_proto.o: ../../include/dns.h
+smtp_proto.o: ../../include/dsn.h
+smtp_proto.o: ../../include/dsn_buf.h
+smtp_proto.o: ../../include/dsn_mask.h
+smtp_proto.o: ../../include/ehlo_mask.h
+smtp_proto.o: ../../include/ext_prop.h
+smtp_proto.o: ../../include/header_body_checks.h
+smtp_proto.o: ../../include/header_opts.h
+smtp_proto.o: ../../include/htable.h
+smtp_proto.o: ../../include/iostuff.h
+smtp_proto.o: ../../include/lex_822.h
+smtp_proto.o: ../../include/mail_addr_form.h
+smtp_proto.o: ../../include/mail_addr_map.h
+smtp_proto.o: ../../include/mail_params.h
+smtp_proto.o: ../../include/mail_proto.h
+smtp_proto.o: ../../include/mail_queue.h
+smtp_proto.o: ../../include/maps.h
+smtp_proto.o: ../../include/mark_corrupt.h
+smtp_proto.o: ../../include/match_list.h
+smtp_proto.o: ../../include/match_parent_style.h
+smtp_proto.o: ../../include/mime_state.h
+smtp_proto.o: ../../include/msg.h
+smtp_proto.o: ../../include/msg_stats.h
+smtp_proto.o: ../../include/myaddrinfo.h
+smtp_proto.o: ../../include/myflock.h
+smtp_proto.o: ../../include/mymalloc.h
+smtp_proto.o: ../../include/namadr_list.h
+smtp_proto.o: ../../include/name_code.h
+smtp_proto.o: ../../include/name_mask.h
+smtp_proto.o: ../../include/nvtable.h
+smtp_proto.o: ../../include/off_cvt.h
+smtp_proto.o: ../../include/quote_822_local.h
+smtp_proto.o: ../../include/quote_flags.h
+smtp_proto.o: ../../include/rec_type.h
+smtp_proto.o: ../../include/recipient_list.h
+smtp_proto.o: ../../include/record.h
+smtp_proto.o: ../../include/resolve_clnt.h
+smtp_proto.o: ../../include/scache.h
+smtp_proto.o: ../../include/smtp_stream.h
+smtp_proto.o: ../../include/smtputf8.h
+smtp_proto.o: ../../include/sock_addr.h
+smtp_proto.o: ../../include/split_at.h
+smtp_proto.o: ../../include/string_list.h
+smtp_proto.o: ../../include/stringops.h
+smtp_proto.o: ../../include/sys_defs.h
+smtp_proto.o: ../../include/tls.h
+smtp_proto.o: ../../include/tls_proxy.h
+smtp_proto.o: ../../include/tok822.h
+smtp_proto.o: ../../include/uxtext.h
+smtp_proto.o: ../../include/vbuf.h
+smtp_proto.o: ../../include/vstream.h
+smtp_proto.o: ../../include/vstring.h
+smtp_proto.o: ../../include/vstring_vstream.h
+smtp_proto.o: ../../include/xtext.h
+smtp_proto.o: smtp.h
+smtp_proto.o: smtp_proto.c
+smtp_proto.o: smtp_sasl.h
+smtp_rcpt.o: ../../include/argv.h
+smtp_rcpt.o: ../../include/attr.h
+smtp_rcpt.o: ../../include/bounce.h
+smtp_rcpt.o: ../../include/check_arg.h
+smtp_rcpt.o: ../../include/deliver_completed.h
+smtp_rcpt.o: ../../include/deliver_request.h
+smtp_rcpt.o: ../../include/dict.h
+smtp_rcpt.o: ../../include/dns.h
+smtp_rcpt.o: ../../include/dsn.h
+smtp_rcpt.o: ../../include/dsn_buf.h
+smtp_rcpt.o: ../../include/dsn_mask.h
+smtp_rcpt.o: ../../include/header_body_checks.h
+smtp_rcpt.o: ../../include/header_opts.h
+smtp_rcpt.o: ../../include/htable.h
+smtp_rcpt.o: ../../include/mail_params.h
+smtp_rcpt.o: ../../include/maps.h
+smtp_rcpt.o: ../../include/match_list.h
+smtp_rcpt.o: ../../include/mime_state.h
+smtp_rcpt.o: ../../include/msg.h
+smtp_rcpt.o: ../../include/msg_stats.h
+smtp_rcpt.o: ../../include/myaddrinfo.h
+smtp_rcpt.o: ../../include/myflock.h
+smtp_rcpt.o: ../../include/mymalloc.h
+smtp_rcpt.o: ../../include/name_code.h
+smtp_rcpt.o: ../../include/name_mask.h
+smtp_rcpt.o: ../../include/nvtable.h
+smtp_rcpt.o: ../../include/recipient_list.h
+smtp_rcpt.o: ../../include/resolve_clnt.h
+smtp_rcpt.o: ../../include/scache.h
+smtp_rcpt.o: ../../include/sent.h
+smtp_rcpt.o: ../../include/sock_addr.h
+smtp_rcpt.o: ../../include/string_list.h
+smtp_rcpt.o: ../../include/stringops.h
+smtp_rcpt.o: ../../include/sys_defs.h
+smtp_rcpt.o: ../../include/tls.h
+smtp_rcpt.o: ../../include/tls_proxy.h
+smtp_rcpt.o: ../../include/tok822.h
+smtp_rcpt.o: ../../include/vbuf.h
+smtp_rcpt.o: ../../include/vstream.h
+smtp_rcpt.o: ../../include/vstring.h
+smtp_rcpt.o: smtp.h
+smtp_rcpt.o: smtp_rcpt.c
+smtp_reuse.o: ../../include/argv.h
+smtp_reuse.o: ../../include/attr.h
+smtp_reuse.o: ../../include/check_arg.h
+smtp_reuse.o: ../../include/deliver_request.h
+smtp_reuse.o: ../../include/dict.h
+smtp_reuse.o: ../../include/dns.h
+smtp_reuse.o: ../../include/dsn.h
+smtp_reuse.o: ../../include/dsn_buf.h
+smtp_reuse.o: ../../include/header_body_checks.h
+smtp_reuse.o: ../../include/header_opts.h
+smtp_reuse.o: ../../include/htable.h
+smtp_reuse.o: ../../include/mail_params.h
+smtp_reuse.o: ../../include/maps.h
+smtp_reuse.o: ../../include/match_list.h
+smtp_reuse.o: ../../include/mime_state.h
+smtp_reuse.o: ../../include/msg.h
+smtp_reuse.o: ../../include/msg_stats.h
+smtp_reuse.o: ../../include/myaddrinfo.h
+smtp_reuse.o: ../../include/myflock.h
+smtp_reuse.o: ../../include/mymalloc.h
+smtp_reuse.o: ../../include/name_code.h
+smtp_reuse.o: ../../include/name_mask.h
+smtp_reuse.o: ../../include/nvtable.h
+smtp_reuse.o: ../../include/recipient_list.h
+smtp_reuse.o: ../../include/resolve_clnt.h
+smtp_reuse.o: ../../include/scache.h
+smtp_reuse.o: ../../include/sock_addr.h
+smtp_reuse.o: ../../include/string_list.h
+smtp_reuse.o: ../../include/stringops.h
+smtp_reuse.o: ../../include/sys_defs.h
+smtp_reuse.o: ../../include/tls.h
+smtp_reuse.o: ../../include/tls_proxy.h
+smtp_reuse.o: ../../include/tok822.h
+smtp_reuse.o: ../../include/vbuf.h
+smtp_reuse.o: ../../include/vstream.h
+smtp_reuse.o: ../../include/vstring.h
+smtp_reuse.o: smtp.h
+smtp_reuse.o: smtp_reuse.c
+smtp_reuse.o: smtp_reuse.h
+smtp_sasl_auth_cache.o: ../../include/argv.h
+smtp_sasl_auth_cache.o: ../../include/attr.h
+smtp_sasl_auth_cache.o: ../../include/base64_code.h
+smtp_sasl_auth_cache.o: ../../include/check_arg.h
+smtp_sasl_auth_cache.o: ../../include/deliver_request.h
+smtp_sasl_auth_cache.o: ../../include/dict.h
+smtp_sasl_auth_cache.o: ../../include/dict_proxy.h
+smtp_sasl_auth_cache.o: ../../include/dns.h
+smtp_sasl_auth_cache.o: ../../include/dsn.h
+smtp_sasl_auth_cache.o: ../../include/dsn_buf.h
+smtp_sasl_auth_cache.o: ../../include/dsn_util.h
+smtp_sasl_auth_cache.o: ../../include/header_body_checks.h
+smtp_sasl_auth_cache.o: ../../include/header_opts.h
+smtp_sasl_auth_cache.o: ../../include/htable.h
+smtp_sasl_auth_cache.o: ../../include/maps.h
+smtp_sasl_auth_cache.o: ../../include/match_list.h
+smtp_sasl_auth_cache.o: ../../include/mime_state.h
+smtp_sasl_auth_cache.o: ../../include/msg.h
+smtp_sasl_auth_cache.o: ../../include/msg_stats.h
+smtp_sasl_auth_cache.o: ../../include/myaddrinfo.h
+smtp_sasl_auth_cache.o: ../../include/myflock.h
+smtp_sasl_auth_cache.o: ../../include/mymalloc.h
+smtp_sasl_auth_cache.o: ../../include/name_code.h
+smtp_sasl_auth_cache.o: ../../include/name_mask.h
+smtp_sasl_auth_cache.o: ../../include/nvtable.h
+smtp_sasl_auth_cache.o: ../../include/recipient_list.h
+smtp_sasl_auth_cache.o: ../../include/resolve_clnt.h
+smtp_sasl_auth_cache.o: ../../include/scache.h
+smtp_sasl_auth_cache.o: ../../include/sock_addr.h
+smtp_sasl_auth_cache.o: ../../include/string_list.h
+smtp_sasl_auth_cache.o: ../../include/stringops.h
+smtp_sasl_auth_cache.o: ../../include/sys_defs.h
+smtp_sasl_auth_cache.o: ../../include/tls.h
+smtp_sasl_auth_cache.o: ../../include/tls_proxy.h
+smtp_sasl_auth_cache.o: ../../include/tok822.h
+smtp_sasl_auth_cache.o: ../../include/vbuf.h
+smtp_sasl_auth_cache.o: ../../include/vstream.h
+smtp_sasl_auth_cache.o: ../../include/vstring.h
+smtp_sasl_auth_cache.o: smtp.h
+smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.c
+smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.h
+smtp_sasl_glue.o: ../../include/argv.h
+smtp_sasl_glue.o: ../../include/attr.h
+smtp_sasl_glue.o: ../../include/check_arg.h
+smtp_sasl_glue.o: ../../include/deliver_request.h
+smtp_sasl_glue.o: ../../include/dict.h
+smtp_sasl_glue.o: ../../include/dns.h
+smtp_sasl_glue.o: ../../include/dsn.h
+smtp_sasl_glue.o: ../../include/dsn_buf.h
+smtp_sasl_glue.o: ../../include/header_body_checks.h
+smtp_sasl_glue.o: ../../include/header_opts.h
+smtp_sasl_glue.o: ../../include/htable.h
+smtp_sasl_glue.o: ../../include/mail_addr_find.h
+smtp_sasl_glue.o: ../../include/mail_addr_form.h
+smtp_sasl_glue.o: ../../include/mail_params.h
+smtp_sasl_glue.o: ../../include/maps.h
+smtp_sasl_glue.o: ../../include/match_list.h
+smtp_sasl_glue.o: ../../include/mime_state.h
+smtp_sasl_glue.o: ../../include/msg.h
+smtp_sasl_glue.o: ../../include/msg_stats.h
+smtp_sasl_glue.o: ../../include/myaddrinfo.h
+smtp_sasl_glue.o: ../../include/myflock.h
+smtp_sasl_glue.o: ../../include/mymalloc.h
+smtp_sasl_glue.o: ../../include/name_code.h
+smtp_sasl_glue.o: ../../include/name_mask.h
+smtp_sasl_glue.o: ../../include/nvtable.h
+smtp_sasl_glue.o: ../../include/recipient_list.h
+smtp_sasl_glue.o: ../../include/resolve_clnt.h
+smtp_sasl_glue.o: ../../include/scache.h
+smtp_sasl_glue.o: ../../include/smtp_stream.h
+smtp_sasl_glue.o: ../../include/sock_addr.h
+smtp_sasl_glue.o: ../../include/split_at.h
+smtp_sasl_glue.o: ../../include/string_list.h
+smtp_sasl_glue.o: ../../include/stringops.h
+smtp_sasl_glue.o: ../../include/sys_defs.h
+smtp_sasl_glue.o: ../../include/tls.h
+smtp_sasl_glue.o: ../../include/tls_proxy.h
+smtp_sasl_glue.o: ../../include/tok822.h
+smtp_sasl_glue.o: ../../include/vbuf.h
+smtp_sasl_glue.o: ../../include/vstream.h
+smtp_sasl_glue.o: ../../include/vstring.h
+smtp_sasl_glue.o: ../../include/xsasl.h
+smtp_sasl_glue.o: smtp.h
+smtp_sasl_glue.o: smtp_sasl.h
+smtp_sasl_glue.o: smtp_sasl_auth_cache.h
+smtp_sasl_glue.o: smtp_sasl_glue.c
+smtp_sasl_proto.o: ../../include/argv.h
+smtp_sasl_proto.o: ../../include/attr.h
+smtp_sasl_proto.o: ../../include/check_arg.h
+smtp_sasl_proto.o: ../../include/deliver_request.h
+smtp_sasl_proto.o: ../../include/dict.h
+smtp_sasl_proto.o: ../../include/dns.h
+smtp_sasl_proto.o: ../../include/dsn.h
+smtp_sasl_proto.o: ../../include/dsn_buf.h
+smtp_sasl_proto.o: ../../include/header_body_checks.h
+smtp_sasl_proto.o: ../../include/header_opts.h
+smtp_sasl_proto.o: ../../include/htable.h
+smtp_sasl_proto.o: ../../include/mail_params.h
+smtp_sasl_proto.o: ../../include/maps.h
+smtp_sasl_proto.o: ../../include/match_list.h
+smtp_sasl_proto.o: ../../include/mime_state.h
+smtp_sasl_proto.o: ../../include/msg.h
+smtp_sasl_proto.o: ../../include/msg_stats.h
+smtp_sasl_proto.o: ../../include/myaddrinfo.h
+smtp_sasl_proto.o: ../../include/myflock.h
+smtp_sasl_proto.o: ../../include/mymalloc.h
+smtp_sasl_proto.o: ../../include/name_code.h
+smtp_sasl_proto.o: ../../include/name_mask.h
+smtp_sasl_proto.o: ../../include/nvtable.h
+smtp_sasl_proto.o: ../../include/recipient_list.h
+smtp_sasl_proto.o: ../../include/resolve_clnt.h
+smtp_sasl_proto.o: ../../include/sasl_mech_filter.h
+smtp_sasl_proto.o: ../../include/scache.h
+smtp_sasl_proto.o: ../../include/sock_addr.h
+smtp_sasl_proto.o: ../../include/string_list.h
+smtp_sasl_proto.o: ../../include/stringops.h
+smtp_sasl_proto.o: ../../include/sys_defs.h
+smtp_sasl_proto.o: ../../include/tls.h
+smtp_sasl_proto.o: ../../include/tls_proxy.h
+smtp_sasl_proto.o: ../../include/tok822.h
+smtp_sasl_proto.o: ../../include/vbuf.h
+smtp_sasl_proto.o: ../../include/vstream.h
+smtp_sasl_proto.o: ../../include/vstring.h
+smtp_sasl_proto.o: smtp.h
+smtp_sasl_proto.o: smtp_sasl.h
+smtp_sasl_proto.o: smtp_sasl_proto.c
+smtp_session.o: ../../include/argv.h
+smtp_session.o: ../../include/attr.h
+smtp_session.o: ../../include/check_arg.h
+smtp_session.o: ../../include/debug_peer.h
+smtp_session.o: ../../include/deliver_request.h
+smtp_session.o: ../../include/dict.h
+smtp_session.o: ../../include/dns.h
+smtp_session.o: ../../include/dsn.h
+smtp_session.o: ../../include/dsn_buf.h
+smtp_session.o: ../../include/header_body_checks.h
+smtp_session.o: ../../include/header_opts.h
+smtp_session.o: ../../include/htable.h
+smtp_session.o: ../../include/mail_params.h
+smtp_session.o: ../../include/maps.h
+smtp_session.o: ../../include/match_list.h
+smtp_session.o: ../../include/mime_state.h
+smtp_session.o: ../../include/msg.h
+smtp_session.o: ../../include/msg_stats.h
+smtp_session.o: ../../include/myaddrinfo.h
+smtp_session.o: ../../include/myflock.h
+smtp_session.o: ../../include/mymalloc.h
+smtp_session.o: ../../include/name_code.h
+smtp_session.o: ../../include/name_mask.h
+smtp_session.o: ../../include/nvtable.h
+smtp_session.o: ../../include/recipient_list.h
+smtp_session.o: ../../include/resolve_clnt.h
+smtp_session.o: ../../include/scache.h
+smtp_session.o: ../../include/sock_addr.h
+smtp_session.o: ../../include/string_list.h
+smtp_session.o: ../../include/stringops.h
+smtp_session.o: ../../include/sys_defs.h
+smtp_session.o: ../../include/tls.h
+smtp_session.o: ../../include/tls_proxy.h
+smtp_session.o: ../../include/tok822.h
+smtp_session.o: ../../include/vbuf.h
+smtp_session.o: ../../include/vstream.h
+smtp_session.o: ../../include/vstring.h
+smtp_session.o: smtp.h
+smtp_session.o: smtp_sasl.h
+smtp_session.o: smtp_session.c
+smtp_state.o: ../../include/argv.h
+smtp_state.o: ../../include/attr.h
+smtp_state.o: ../../include/check_arg.h
+smtp_state.o: ../../include/debug_peer.h
+smtp_state.o: ../../include/deliver_request.h
+smtp_state.o: ../../include/dict.h
+smtp_state.o: ../../include/dns.h
+smtp_state.o: ../../include/dsn.h
+smtp_state.o: ../../include/dsn_buf.h
+smtp_state.o: ../../include/header_body_checks.h
+smtp_state.o: ../../include/header_opts.h
+smtp_state.o: ../../include/htable.h
+smtp_state.o: ../../include/mail_params.h
+smtp_state.o: ../../include/maps.h
+smtp_state.o: ../../include/match_list.h
+smtp_state.o: ../../include/mime_state.h
+smtp_state.o: ../../include/msg.h
+smtp_state.o: ../../include/msg_stats.h
+smtp_state.o: ../../include/myaddrinfo.h
+smtp_state.o: ../../include/myflock.h
+smtp_state.o: ../../include/mymalloc.h
+smtp_state.o: ../../include/name_code.h
+smtp_state.o: ../../include/name_mask.h
+smtp_state.o: ../../include/nvtable.h
+smtp_state.o: ../../include/recipient_list.h
+smtp_state.o: ../../include/resolve_clnt.h
+smtp_state.o: ../../include/scache.h
+smtp_state.o: ../../include/sock_addr.h
+smtp_state.o: ../../include/string_list.h
+smtp_state.o: ../../include/sys_defs.h
+smtp_state.o: ../../include/tls.h
+smtp_state.o: ../../include/tls_proxy.h
+smtp_state.o: ../../include/tok822.h
+smtp_state.o: ../../include/vbuf.h
+smtp_state.o: ../../include/vstream.h
+smtp_state.o: ../../include/vstring.h
+smtp_state.o: smtp.h
+smtp_state.o: smtp_sasl.h
+smtp_state.o: smtp_state.c
+smtp_tls_policy.o: ../../include/argv.h
+smtp_tls_policy.o: ../../include/attr.h
+smtp_tls_policy.o: ../../include/check_arg.h
+smtp_tls_policy.o: ../../include/ctable.h
+smtp_tls_policy.o: ../../include/deliver_request.h
+smtp_tls_policy.o: ../../include/dict.h
+smtp_tls_policy.o: ../../include/dns.h
+smtp_tls_policy.o: ../../include/dsn.h
+smtp_tls_policy.o: ../../include/dsn_buf.h
+smtp_tls_policy.o: ../../include/header_body_checks.h
+smtp_tls_policy.o: ../../include/header_opts.h
+smtp_tls_policy.o: ../../include/htable.h
+smtp_tls_policy.o: ../../include/mail_params.h
+smtp_tls_policy.o: ../../include/maps.h
+smtp_tls_policy.o: ../../include/match_list.h
+smtp_tls_policy.o: ../../include/mime_state.h
+smtp_tls_policy.o: ../../include/msg.h
+smtp_tls_policy.o: ../../include/msg_stats.h
+smtp_tls_policy.o: ../../include/myaddrinfo.h
+smtp_tls_policy.o: ../../include/myflock.h
+smtp_tls_policy.o: ../../include/mymalloc.h
+smtp_tls_policy.o: ../../include/name_code.h
+smtp_tls_policy.o: ../../include/name_mask.h
+smtp_tls_policy.o: ../../include/nvtable.h
+smtp_tls_policy.o: ../../include/recipient_list.h
+smtp_tls_policy.o: ../../include/resolve_clnt.h
+smtp_tls_policy.o: ../../include/scache.h
+smtp_tls_policy.o: ../../include/sock_addr.h
+smtp_tls_policy.o: ../../include/string_list.h
+smtp_tls_policy.o: ../../include/stringops.h
+smtp_tls_policy.o: ../../include/sys_defs.h
+smtp_tls_policy.o: ../../include/tls.h
+smtp_tls_policy.o: ../../include/tls_proxy.h
+smtp_tls_policy.o: ../../include/tok822.h
+smtp_tls_policy.o: ../../include/valid_hostname.h
+smtp_tls_policy.o: ../../include/valid_utf8_hostname.h
+smtp_tls_policy.o: ../../include/vbuf.h
+smtp_tls_policy.o: ../../include/vstream.h
+smtp_tls_policy.o: ../../include/vstring.h
+smtp_tls_policy.o: smtp.h
+smtp_tls_policy.o: smtp_tls_policy.c
+smtp_trouble.o: ../../include/argv.h
+smtp_trouble.o: ../../include/attr.h
+smtp_trouble.o: ../../include/bounce.h
+smtp_trouble.o: ../../include/check_arg.h
+smtp_trouble.o: ../../include/defer.h
+smtp_trouble.o: ../../include/deliver_completed.h
+smtp_trouble.o: ../../include/deliver_request.h
+smtp_trouble.o: ../../include/dict.h
+smtp_trouble.o: ../../include/dns.h
+smtp_trouble.o: ../../include/dsn.h
+smtp_trouble.o: ../../include/dsn_buf.h
+smtp_trouble.o: ../../include/header_body_checks.h
+smtp_trouble.o: ../../include/header_opts.h
+smtp_trouble.o: ../../include/htable.h
+smtp_trouble.o: ../../include/mail_error.h
+smtp_trouble.o: ../../include/mail_params.h
+smtp_trouble.o: ../../include/maps.h
+smtp_trouble.o: ../../include/match_list.h
+smtp_trouble.o: ../../include/mime_state.h
+smtp_trouble.o: ../../include/msg.h
+smtp_trouble.o: ../../include/msg_stats.h
+smtp_trouble.o: ../../include/myaddrinfo.h
+smtp_trouble.o: ../../include/myflock.h
+smtp_trouble.o: ../../include/mymalloc.h
+smtp_trouble.o: ../../include/name_code.h
+smtp_trouble.o: ../../include/name_mask.h
+smtp_trouble.o: ../../include/nvtable.h
+smtp_trouble.o: ../../include/recipient_list.h
+smtp_trouble.o: ../../include/resolve_clnt.h
+smtp_trouble.o: ../../include/scache.h
+smtp_trouble.o: ../../include/smtp_stream.h
+smtp_trouble.o: ../../include/sock_addr.h
+smtp_trouble.o: ../../include/string_list.h
+smtp_trouble.o: ../../include/stringops.h
+smtp_trouble.o: ../../include/sys_defs.h
+smtp_trouble.o: ../../include/tls.h
+smtp_trouble.o: ../../include/tls_proxy.h
+smtp_trouble.o: ../../include/tok822.h
+smtp_trouble.o: ../../include/vbuf.h
+smtp_trouble.o: ../../include/vstream.h
+smtp_trouble.o: ../../include/vstring.h
+smtp_trouble.o: smtp.h
+smtp_trouble.o: smtp_sasl.h
+smtp_trouble.o: smtp_trouble.c
+smtp_unalias.o: ../../include/argv.h
+smtp_unalias.o: ../../include/attr.h
+smtp_unalias.o: ../../include/check_arg.h
+smtp_unalias.o: ../../include/deliver_request.h
+smtp_unalias.o: ../../include/dict.h
+smtp_unalias.o: ../../include/dns.h
+smtp_unalias.o: ../../include/dsn.h
+smtp_unalias.o: ../../include/dsn_buf.h
+smtp_unalias.o: ../../include/header_body_checks.h
+smtp_unalias.o: ../../include/header_opts.h
+smtp_unalias.o: ../../include/htable.h
+smtp_unalias.o: ../../include/maps.h
+smtp_unalias.o: ../../include/match_list.h
+smtp_unalias.o: ../../include/mime_state.h
+smtp_unalias.o: ../../include/msg.h
+smtp_unalias.o: ../../include/msg_stats.h
+smtp_unalias.o: ../../include/myaddrinfo.h
+smtp_unalias.o: ../../include/myflock.h
+smtp_unalias.o: ../../include/mymalloc.h
+smtp_unalias.o: ../../include/name_code.h
+smtp_unalias.o: ../../include/name_mask.h
+smtp_unalias.o: ../../include/nvtable.h
+smtp_unalias.o: ../../include/recipient_list.h
+smtp_unalias.o: ../../include/resolve_clnt.h
+smtp_unalias.o: ../../include/scache.h
+smtp_unalias.o: ../../include/sock_addr.h
+smtp_unalias.o: ../../include/string_list.h
+smtp_unalias.o: ../../include/sys_defs.h
+smtp_unalias.o: ../../include/tls.h
+smtp_unalias.o: ../../include/tls_proxy.h
+smtp_unalias.o: ../../include/tok822.h
+smtp_unalias.o: ../../include/vbuf.h
+smtp_unalias.o: ../../include/vstream.h
+smtp_unalias.o: ../../include/vstring.h
+smtp_unalias.o: smtp.h
+smtp_unalias.o: smtp_unalias.c
diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c
new file mode 100644
index 0000000..cc33646
--- /dev/null
+++ b/src/smtp/lmtp_params.c
@@ -0,0 +1,136 @@
+ static const CONFIG_STR_TABLE lmtp_str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_LMTP_FALLBACK, DEF_LMTP_FALLBACK, &var_fallback_relay, 0, 0,
+ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
+ VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
+#ifdef USE_TLS
+ VAR_LMTP_SASL_TLS_OPTS, DEF_LMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0,
+ VAR_LMTP_SASL_TLSV_OPTS, DEF_LMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0,
+ VAR_LMTP_TLS_CHAIN_FILES, DEF_LMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0,
+ VAR_LMTP_TLS_CERT_FILE, DEF_LMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0,
+ VAR_LMTP_TLS_KEY_FILE, DEF_LMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0,
+ VAR_LMTP_TLS_DCERT_FILE, DEF_LMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0,
+ VAR_LMTP_TLS_DKEY_FILE, DEF_LMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0,
+ VAR_LMTP_TLS_CA_FILE, DEF_LMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0,
+ VAR_LMTP_TLS_CA_PATH, DEF_LMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0,
+ VAR_LMTP_TLS_MAND_CIPH, DEF_LMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0,
+ VAR_LMTP_TLS_EXCL_CIPH, DEF_LMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0,
+ VAR_LMTP_TLS_MAND_EXCL, DEF_LMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0,
+ VAR_LMTP_TLS_MAND_PROTO, DEF_LMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0,
+ VAR_LMTP_TLS_VFY_CMATCH, DEF_LMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0,
+ VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0,
+ VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0,
+ VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+ VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0,
+ VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0,
+ VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0,
+ VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
+ VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
+ VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_LMTP_TLS_SNI, DEF_LMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0,
+#endif
+ VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
+ VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
+ VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0,
+ VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0,
+ VAR_LMTP_VRFY_TGT, DEF_LMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0,
+ VAR_LMTP_HELO_NAME, DEF_LMTP_HELO_NAME, &var_smtp_helo_name, 1, 0,
+ VAR_LMTP_HOST_LOOKUP, DEF_LMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0,
+ VAR_LMTP_DNS_SUPPORT, DEF_LMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0,
+ VAR_LMTP_CACHE_DEST, DEF_LMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0,
+ VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0,
+ VAR_LMTP_EHLO_DIS_WORDS, DEF_LMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0,
+ VAR_LMTP_EHLO_DIS_MAPS, DEF_LMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0,
+ VAR_LMTP_TLS_PER_SITE, DEF_LMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0,
+ VAR_LMTP_TLS_LEVEL, DEF_LMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0,
+ VAR_LMTP_TLS_POLICY, DEF_LMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_LMTP_GENERIC_MAPS, DEF_LMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0,
+ VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0,
+ VAR_LMTP_PIX_BUG_WORDS, DEF_LMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0,
+ VAR_LMTP_PIX_BUG_MAPS, DEF_LMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0,
+ VAR_LMTP_SASL_AUTH_CACHE_NAME, DEF_LMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_LMTP_HEAD_CHKS, DEF_LMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0,
+ VAR_LMTP_MIME_CHKS, DEF_LMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0,
+ VAR_LMTP_NEST_CHKS, DEF_LMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0,
+ VAR_LMTP_BODY_CHKS, DEF_LMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0,
+ VAR_LMTP_RESP_FILTER, DEF_LMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0,
+ VAR_LMTP_ADDR_PREF, DEF_LMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0,
+ VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0,
+ VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0,
+ VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE lmtp_time_table[] = {
+ VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0,
+ VAR_LMTP_HELO_TMOUT, DEF_LMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0,
+ VAR_LMTP_XFWD_TMOUT, DEF_LMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0,
+ VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0,
+ VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0,
+ VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0,
+ VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0,
+ VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0,
+ VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0,
+ VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0,
+ VAR_LMTP_PIX_THRESH, DEF_LMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0,
+ VAR_LMTP_PIX_DELAY, DEF_LMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0,
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_LMTP_CACHE_CONNT, DEF_LMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0,
+ VAR_LMTP_REUSE_TIME, DEF_LMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0,
+#ifdef USE_TLS
+ VAR_LMTP_STARTTLS_TMOUT, DEF_LMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0,
+#endif
+ VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0,
+ VAR_LMTP_SASL_AUTH_CACHE_TIME, DEF_LMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE lmtp_int_table[] = {
+ VAR_LMTP_LINE_LIMIT, DEF_LMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0,
+ VAR_LMTP_MXADDR_LIMIT, DEF_LMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0,
+ VAR_LMTP_MXSESS_LIMIT, DEF_LMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0,
+ VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0,
+#ifdef USE_TLS
+ VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0,
+#endif
+ VAR_LMTP_MIN_DATA_RATE, DEF_LMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE lmtp_bool_table[] = {
+ VAR_LMTP_SKIP_5XX, DEF_LMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting,
+ VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_skip_quit_resp,
+ VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_smtp_sasl_enable,
+ VAR_LMTP_RAND_ADDR, DEF_LMTP_RAND_ADDR, &var_smtp_rand_addr,
+ VAR_LMTP_QUOTE_821_ENV, DEF_LMTP_QUOTE_821_ENV, &var_smtp_quote_821_env,
+ VAR_LMTP_DEFER_MXADDR, DEF_LMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr,
+ VAR_LMTP_SEND_XFORWARD, DEF_LMTP_SEND_XFORWARD, &var_smtp_send_xforward,
+ VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand,
+ VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls,
+ VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
+ VAR_LMTP_TLS_CONN_REUSE, DEF_LMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse,
+#ifdef USE_TLS
+ VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername,
+ VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
+ VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
+ VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+#endif
+ VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
+ VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth,
+ VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr,
+ VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce,
+ VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final,
+ VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
+ VAR_LMTP_BALANCE_INET_PROTO, DEF_LMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto,
+ VAR_LMTP_BIND_ADDR_ENFORCE, DEF_LMTP_BIND_ADDR_ENFORCE, &var_smtp_bind_addr_enforce,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE lmtp_nbool_table[] = {
+ VAR_LMTP_REQ_DEADLINE, DEF_LMTP_REQ_DEADLINE, &var_smtp_req_deadline,
+ 0,
+ };
diff --git a/src/smtp/smtp-only b/src/smtp/smtp-only
new file mode 100644
index 0000000..ee9288d
--- /dev/null
+++ b/src/smtp/smtp-only
@@ -0,0 +1,4 @@
+_ALWAYS_EHLO
+_NEVER_EHLO
+_IGN_MX_LOOKUP_ERR
+_INSECURE_MX_POLICY
diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c
new file mode 100644
index 0000000..791ec89
--- /dev/null
+++ b/src/smtp/smtp.c
@@ -0,0 +1,1652 @@
+/*++
+/* NAME
+/* smtp 8
+/* SUMMARY
+/* Postfix SMTP+LMTP client
+/* SYNOPSIS
+/* \fBsmtp\fR [generic Postfix daemon options] [flags=DORX]
+/* DESCRIPTION
+/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail
+/* delivery protocols. It processes message delivery requests from
+/* the queue manager. Each request specifies a queue file, a sender
+/* address, a domain or host to deliver to, and recipient information.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The SMTP+LMTP client updates the queue file and marks recipients
+/* as finished, or it informs the queue manager that delivery should
+/* be tried again at a later time. Delivery status reports are sent
+/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as
+/* appropriate.
+/*
+/* The SMTP+LMTP client looks up a list of mail exchanger addresses for
+/* the destination host, sorts the list by preference, and connects
+/* to each listed address until it finds a server that responds.
+/*
+/* When a server is not reachable, or when mail delivery fails due
+/* to a recoverable error condition, the SMTP+LMTP client will try to
+/* deliver the mail to an alternate host.
+/*
+/* After a successful mail transaction, a connection may be saved
+/* to the \fBscache\fR(8) connection cache server, so that it
+/* may be used by any SMTP+LMTP client for a subsequent transaction.
+/*
+/* By default, connection caching is enabled temporarily for
+/* destinations that have a high volume of mail in the active
+/* queue. Connection caching can be enabled permanently for
+/* specific destinations.
+/* SMTP DESTINATION SYNTAX
+/* .ad
+/* .fi
+/* The Postfix SMTP+LMTP client supports multiple destinations
+/* separated by comma or whitespace (Postfix 3.5 and later).
+/* SMTP destinations have the following form:
+/* .IP \fIdomainname\fR
+/* .IP \fIdomainname\fR:\fIport\fR
+/* Look up the mail exchangers for the specified domain, and
+/* connect to the specified port (default: \fBsmtp\fR).
+/* .IP [\fIhostname\fR]
+/* .IP [\fIhostname\fR]:\fIport\fR
+/* Look up the address(es) of the specified host, and connect to
+/* the specified port (default: \fBsmtp\fR).
+/* .IP [\fIaddress\fR]
+/* .IP [\fIaddress\fR]:\fIport\fR
+/* Connect to the host at the specified address, and connect
+/* to the specified port (default: \fBsmtp\fR). An IPv6 address
+/* must be formatted as [\fBipv6\fR:\fIaddress\fR].
+/* LMTP DESTINATION SYNTAX
+/* .ad
+/* .fi
+/* The Postfix SMTP+LMTP client supports multiple destinations
+/* separated by comma or whitespace (Postfix 3.5 and later).
+/* LMTP destinations have the following form:
+/* .IP \fBunix\fR:\fIpathname\fR
+/* Connect to the local UNIX-domain server that is bound to the specified
+/* \fIpathname\fR. If the process runs chrooted, an absolute pathname
+/* is interpreted relative to the Postfix queue directory.
+/* .IP \fBinet\fR:\fIhostname\fR
+/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR
+/* .IP \fBinet\fR:[\fIaddress\fR]
+/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR
+/* Connect to the specified TCP port on the specified local or
+/* remote host. If no port is specified, connect to the port defined as
+/* \fBlmtp\fR in \fBservices\fR(4).
+/* If no such service is found, the \fBlmtp_tcp_port\fR configuration
+/* parameter (default value of 24) will be used.
+/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR].
+/* SINGLE-RECIPIENT DELIVERY
+/* .ad
+/* .fi
+/* By default, the Postfix SMTP+LMTP client delivers mail to
+/* multiple recipients per delivery request. This is undesirable
+/* when prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR
+/* message header. To prevent Postfix from sending multiple
+/* recipients per delivery request, specify
+/* .sp
+/* .nf
+/* \fItransport\fB_destination_recipient_limit = 1\fR
+/* .fi
+/*
+/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR
+/* is the name in the first column of the Postfix \fBmaster.cf\fR
+/* entry for this mail delivery service.
+/* COMMAND ATTRIBUTE SYNTAX
+/* .ad
+/* .fi
+/* .IP "\fBflags=DORX\fR (optional)"
+/* Optional message processing flags.
+/* .RS
+/* .IP \fBD\fR
+/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header
+/* with the envelope recipient address. Note: for this to work,
+/* the \fItransport\fB_destination_recipient_limit\fR must be
+/* 1 (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* The \fBD\fR flag also enforces loop detection: if a message
+/* already contains a \fBDelivered-To:\fR header with the same
+/* recipient address, then the message is returned as
+/* undeliverable. The address comparison is case insensitive.
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBO\fR
+/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message
+/* header with the recipient address as given to Postfix. Note:
+/* for this to work, the
+/* \fItransport\fB_destination_recipient_limit\fR must be 1
+/* (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBR\fR
+/* Prepend a "\fBReturn-Path: <\fIsender\fB>\fR" message header
+/* with the envelope sender address.
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBX\fR
+/* Indicates that the delivery is final. This flag affects
+/* the status reported in "success" DSN (delivery status
+/* notification) messages, and changes it from "relayed" into
+/* "delivered".
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .RE
+/* SECURITY
+/* The SMTP+LMTP client is moderately security-sensitive. It
+/* talks to SMTP or LMTP servers and to DNS servers on the
+/* network. The SMTP+LMTP client can be run chrooted at fixed
+/* low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 1651 (SMTP service extensions)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1870 (Message Size Declaration)
+/* RFC 2033 (LMTP protocol)
+/* RFC 2034 (SMTP Enhanced Error Codes)
+/* RFC 2045 (MIME: Format of Internet Message Bodies)
+/* RFC 2046 (MIME: Media Types)
+/* RFC 2554 (AUTH command)
+/* RFC 2821 (SMTP protocol)
+/* RFC 2920 (SMTP Pipelining)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN Extension)
+/* RFC 3463 (Enhanced Status Codes)
+/* RFC 4954 (AUTH command)
+/* RFC 5321 (SMTP protocol)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* RFC 7672 (SMTP security via opportunistic DANE TLS)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are marked so that the queue manager can
+/* move them to the \fBcorrupt\fR queue for further inspection.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces, protocol problems, and of
+/* other trouble.
+/* BUGS
+/* SMTP and LMTP connection reuse for TLS (without closing the
+/* SMTP or LMTP connection) is not supported before Postfix 3.4.
+/*
+/* SMTP and LMTP connection reuse assumes that SASL credentials
+/* are valid for all destinations that map onto the same IP
+/* address and TCP port.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Before Postfix version 2.3, the LMTP client is a separate
+/* program that implements only a subset of the functionality
+/* available with SMTP: there is no support for TLS, and
+/* connections are cached in-process, making it ineffective
+/* when the client is used for multiple domains.
+/*
+/* Most smtp_\fIxxx\fR configuration parameters have an
+/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP
+/* feature. This document describes only those LMTP-related
+/* parameters that aren't simply "mirror" parameters.
+/*
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtp\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 "\fBignore_mx_lookup_error (no)\fR"
+/* Ignore DNS MX lookups that produce no response.
+/* .IP "\fBsmtp_always_send_ehlo (yes)\fR"
+/* Always send EHLO at the start of an SMTP session.
+/* .IP "\fBsmtp_never_send_ehlo (no)\fR"
+/* Never send EHLO at the start of an SMTP session.
+/* .IP "\fBsmtp_defer_if_no_mx_address_found (no)\fR"
+/* Defer mail delivery when no MX record resolves to an IP address.
+/* .IP "\fBsmtp_line_length_limit (998)\fR"
+/* The maximal length of message header and body lines that Postfix
+/* will send via SMTP.
+/* .IP "\fBsmtp_pix_workaround_delay_time (10s)\fR"
+/* How long the Postfix SMTP client pauses before sending
+/* ".<CR><LF>" in order to work around the PIX firewall
+/* "<CR><LF>.<CR><LF>" bug.
+/* .IP "\fBsmtp_pix_workaround_threshold_time (500s)\fR"
+/* How long a message must be queued before the Postfix SMTP client
+/* turns on the PIX firewall "<CR><LF>.<CR><LF>"
+/* bug workaround for delivery through firewalls with "smtp fixup"
+/* mode turned on.
+/* .IP "\fBsmtp_pix_workarounds (disable_esmtp, delay_dotcrlf)\fR"
+/* A list that specifies zero or more workarounds for CISCO PIX
+/* firewall bugs.
+/* .IP "\fBsmtp_pix_workaround_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP server address, with
+/* per-destination workarounds for CISCO PIX firewall bugs.
+/* .IP "\fBsmtp_quote_rfc821_envelope (yes)\fR"
+/* Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands
+/* as required
+/* by RFC 5321.
+/* .IP "\fBsmtp_reply_filter (empty)\fR"
+/* A mechanism to transform replies from remote SMTP servers one
+/* line at a time.
+/* .IP "\fBsmtp_skip_5xx_greeting (yes)\fR"
+/* Skip remote SMTP servers that greet with a 5XX status code.
+/* .IP "\fBsmtp_skip_quit_response (yes)\fR"
+/* Do not wait for the response to the SMTP QUIT command.
+/* .PP
+/* Available in Postfix version 2.0 and earlier:
+/* .IP "\fBsmtp_skip_4xx_greeting (yes)\fR"
+/* Skip SMTP servers that greet with a 4XX status code (go away, try
+/* again later).
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_discard_ehlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP server address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the Postfix SMTP client will ignore in the EHLO response from a
+/* remote SMTP server.
+/* .IP "\fBsmtp_discard_ehlo_keywords (empty)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix SMTP client will ignore in the EHLO
+/* response from a remote SMTP server.
+/* .IP "\fBsmtp_generic_maps (empty)\fR"
+/* Optional lookup tables that perform address rewriting in the
+/* Postfix SMTP client, typically to transform a locally valid address into
+/* a globally valid address when sending mail across the Internet.
+/* .PP
+/* Available in Postfix version 2.2.9 and later:
+/* .IP "\fBsmtp_cname_overrides_servername (version dependent)\fR"
+/* When the remote SMTP servername is a DNS CNAME, replace the
+/* servername with the result from CNAME expansion for the purpose of
+/* logging, SASL password lookup, TLS
+/* policy decisions, or TLS certificate verification.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBlmtp_discard_lhlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote LMTP server address, with
+/* case insensitive lists of LHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO
+/* response
+/* from a remote LMTP server.
+/* .IP "\fBlmtp_discard_lhlo_keywords (empty)\fR"
+/* A case insensitive list of LHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO
+/* response
+/* from a remote LMTP server.
+/* .PP
+/* Available in Postfix version 2.4.4 and later:
+/* .IP "\fBsend_cyrus_sasl_authzid (no)\fR"
+/* When authenticating to a remote SMTP or LMTP server with the
+/* default setting "no", send no SASL authoriZation ID (authzid); send
+/* only the SASL authentiCation ID (authcid) plus the authcid's password.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_header_checks (empty)\fR"
+/* Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client.
+/* .IP "\fBsmtp_mime_header_checks (empty)\fR"
+/* Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP
+/* client.
+/* .IP "\fBsmtp_nested_header_checks (empty)\fR"
+/* Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP
+/* client.
+/* .IP "\fBsmtp_body_checks (empty)\fR"
+/* Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBtcp_windowsize (0)\fR"
+/* An optional workaround for routers that break TCP window scaling.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBsmtp_dns_resolver_options (empty)\fR"
+/* DNS Resolver options for the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 2.9 - 3.6:
+/* .IP "\fBsmtp_per_record_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR"
+/* Whether or not to append the "AUTH=<>" option to the MAIL
+/* FROM command in SASL-authenticated SMTP sessions.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_dns_support_level (empty)\fR"
+/* Level of DNS support in the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtp_delivery_status_filter ($default_delivery_status_filter)\fR"
+/* Optional filter for the \fBsmtp\fR(8) delivery agent to change the
+/* delivery status code or explanatory text of successful or unsuccessful
+/* deliveries.
+/* .IP "\fBsmtp_dns_reply_filter (empty)\fR"
+/* Optional filter for Postfix SMTP client DNS lookup results.
+/* .PP
+/* Available in Postfix version 3.3 and later:
+/* .IP "\fBsmtp_balance_inet_protocols (yes)\fR"
+/* When a remote destination resolves to a combination of IPv4 and
+/* IPv6 addresses, ensure that the Postfix SMTP client can try both
+/* address types before it runs into the smtp_mx_address_limit.
+/* .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.).
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBdnssec_probe (ns:.)\fR"
+/* The DNS query type (default: "ns") and DNS query name (default:
+/* ".") that Postfix may use to determine whether DNSSEC validation
+/* is available.
+/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR"
+/* Optional setting that avoids lookups in the \fBservices\fR(5) database.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBsmtp_per_request_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per plaintext or TLS read or write call, to a combined
+/* time limit for sending a complete SMTP request and for receiving a
+/* complete SMTP response.
+/* .IP "\fBsmtp_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA requests, when deadlines are enabled with smtp_per_request_deadline.
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* MIME PROCESSING CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBdisable_mime_output_conversion (no)\fR"
+/* Disable the conversion of 8BITMIME format to 7BIT format.
+/* .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.
+/* EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtp_send_xforward_command (no)\fR"
+/* Send the non-standard XFORWARD command when the Postfix SMTP server
+/* EHLO response announces XFORWARD support.
+/* SASL AUTHENTICATION CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBsmtp_sasl_auth_enable (no)\fR"
+/* Enable SASL authentication in the Postfix SMTP client.
+/* .IP "\fBsmtp_sasl_password_maps (empty)\fR"
+/* Optional Postfix SMTP client lookup tables with one username:password
+/* entry per sender, remote hostname or next-hop domain.
+/* .IP "\fBsmtp_sasl_security_options (noplaintext, noanonymous)\fR"
+/* Postfix SMTP client SASL security options; as of Postfix 2.3
+/* the list of available
+/* features depends on the SASL client implementation that is selected
+/* with \fBsmtp_sasl_type\fR.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_sasl_mechanism_filter (empty)\fR"
+/* If non-empty, a Postfix SMTP client filter for the remote SMTP
+/* server's list of offered SASL mechanisms.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtp_sender_dependent_authentication (no)\fR"
+/* Enable sender-dependent authentication in the Postfix SMTP client; this is
+/* available only with SASL authentication, and disables SMTP connection
+/* caching to ensure that mail from different senders will use the
+/* appropriate credentials.
+/* .IP "\fBsmtp_sasl_path (empty)\fR"
+/* Implementation-specific information that the Postfix SMTP client
+/* passes through to
+/* the SASL plug-in implementation that is selected with
+/* \fBsmtp_sasl_type\fR.
+/* .IP "\fBsmtp_sasl_type (cyrus)\fR"
+/* The SASL plug-in type that the Postfix SMTP client should use
+/* for authentication.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_sasl_auth_cache_name (empty)\fR"
+/* An optional table to prevent repeated SASL authentication
+/* failures with the same remote SMTP server hostname, username and
+/* password.
+/* .IP "\fBsmtp_sasl_auth_cache_time (90d)\fR"
+/* The maximal age of an smtp_sasl_auth_cache_name entry before it
+/* is removed.
+/* .IP "\fBsmtp_sasl_auth_soft_bounce (yes)\fR"
+/* When a remote SMTP server rejects a SASL authentication request
+/* with a 535 reply code, defer mail delivery instead of returning
+/* mail as undeliverable.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR"
+/* Whether or not to append the "AUTH=<>" option to the MAIL
+/* FROM command in SASL-authenticated SMTP sessions.
+/* STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* Detailed information about STARTTLS configuration may be found
+/* in the TLS_README document.
+/* .IP "\fBsmtp_tls_security_level (empty)\fR"
+/* The default SMTP TLS security level for the Postfix SMTP client;
+/* when a non-empty value is specified, this overrides the obsolete
+/* parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername.
+/* .IP "\fBsmtp_sasl_tls_security_options ($smtp_sasl_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* client uses for TLS encrypted SMTP sessions.
+/* .IP "\fBsmtp_starttls_timeout (300s)\fR"
+/* Time limit for Postfix SMTP client write and read operations
+/* during TLS startup and shutdown handshake procedures.
+/* .IP "\fBsmtp_tls_CAfile (empty)\fR"
+/* A file containing CA certificates of root CAs trusted to sign
+/* either remote SMTP server certificates or intermediate CA certificates.
+/* .IP "\fBsmtp_tls_CApath (empty)\fR"
+/* Directory with PEM format Certification Authority certificates
+/* that the Postfix SMTP client uses to verify a remote SMTP server
+/* certificate.
+/* .IP "\fBsmtp_tls_cert_file (empty)\fR"
+/* File with the Postfix SMTP client RSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_mandatory_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP client will
+/* use with
+/* mandatory TLS encryption.
+/* .IP "\fBsmtp_tls_exclude_ciphers (empty)\fR"
+/* List of ciphers or cipher types to exclude from the Postfix
+/* SMTP client cipher
+/* list at all TLS security levels.
+/* .IP "\fBsmtp_tls_mandatory_exclude_ciphers (empty)\fR"
+/* Additional list of ciphers or cipher types to exclude from the
+/* Postfix SMTP client cipher list at mandatory TLS security levels.
+/* .IP "\fBsmtp_tls_dcert_file (empty)\fR"
+/* File with the Postfix SMTP client DSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_dkey_file ($smtp_tls_dcert_file)\fR"
+/* File with the Postfix SMTP client DSA private key in PEM format.
+/* .IP "\fBsmtp_tls_key_file ($smtp_tls_cert_file)\fR"
+/* File with the Postfix SMTP client RSA private key in PEM format.
+/* .IP "\fBsmtp_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP client logging of TLS activity.
+/* .IP "\fBsmtp_tls_note_starttls_offer (no)\fR"
+/* Log the hostname of a remote SMTP server that offers STARTTLS,
+/* when TLS is not already enabled for that server.
+/* .IP "\fBsmtp_tls_policy_maps (empty)\fR"
+/* Optional lookup tables with the Postfix SMTP client TLS security
+/* policy by next-hop destination; when a non-empty value is specified,
+/* this overrides the obsolete smtp_tls_per_site parameter.
+/* .IP "\fBsmtp_tls_mandatory_protocols (see 'postconf -d' output)\fR"
+/* TLS protocols that the Postfix SMTP client will use with mandatory
+/* TLS encryption.
+/* .IP "\fBsmtp_tls_scert_verifydepth (9)\fR"
+/* The verification depth for remote SMTP server certificates.
+/* .IP "\fBsmtp_tls_secure_cert_match (nexthop, dot-nexthop)\fR"
+/* How the Postfix SMTP client verifies the server certificate
+/* peername for the "secure" TLS security level.
+/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP client
+/* TLS session cache.
+/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP client TLS session cache
+/* information.
+/* .IP "\fBsmtp_tls_verify_cert_match (hostname)\fR"
+/* How the Postfix SMTP client verifies the server certificate
+/* peername for the
+/* "verify" TLS security level.
+/* .IP "\fBtls_daemon_random_bytes (32)\fR"
+/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8)
+/* process requests from the \fBtlsmgr\fR(8) server in order to seed its
+/* internal pseudo random number generator (PRNG).
+/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "high" grade ciphers.
+/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "medium" or higher grade ciphers.
+/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "low" or higher grade ciphers.
+/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "export" or higher grade ciphers.
+/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR"
+/* The OpenSSL cipherlist for "NULL" grade ciphers that provide
+/* authentication without encryption.
+/* .PP
+/* Available in Postfix version 2.4 and later:
+/* .IP "\fBsmtp_sasl_tls_verified_security_options ($smtp_sasl_tls_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* client uses for TLS encrypted SMTP sessions with a verified server
+/* certificate.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_tls_fingerprint_cert_match (empty)\fR"
+/* List of acceptable remote SMTP server certificate fingerprints for
+/* the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR =
+/* fingerprint).
+/* .IP "\fBsmtp_tls_fingerprint_digest (see 'postconf -d' output)\fR"
+/* The message digest algorithm used to construct remote SMTP server
+/* certificate fingerprints.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBsmtp_tls_protocols (see postconf -d output)\fR"
+/* TLS protocols that the Postfix SMTP client will use with
+/* opportunistic TLS encryption.
+/* .IP "\fBsmtp_tls_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP client
+/* will use with opportunistic TLS encryption.
+/* .IP "\fBsmtp_tls_eccert_file (empty)\fR"
+/* File with the Postfix SMTP client ECDSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_eckey_file ($smtp_tls_eccert_file)\fR"
+/* File with the Postfix SMTP client ECDSA private key in PEM format.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBsmtp_tls_block_early_mail_reply (no)\fR"
+/* Try to detect a mail hijacking attack based on a TLS protocol
+/* vulnerability (CVE-2009-3555), where an attacker prepends malicious
+/* HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client TLS session.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR"
+/* List or bit-mask of OpenSSL bug work-arounds to disable.
+/* .PP
+/* Available in Postfix version 2.11-3.1:
+/* .IP "\fBtls_dane_digest_agility (on)\fR"
+/* Configure RFC7671 DANE TLSA digest algorithm agility.
+/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR"
+/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain
+/* digests of trust-anchors with certificate usage "2".
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR"
+/* Zero or more PEM-format files with trust-anchor certificates
+/* and/or public keys.
+/* .IP "\fBsmtp_tls_force_insecure_host_tlsa_lookup (no)\fR"
+/* Lookup the associated DANE TLSA RRset even when a hostname is
+/* not an alias and its address records lie in an unsigned zone.
+/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR"
+/* The name of the \fBtlsmgr\fR(8) service entry in master.cf.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtp_tls_wrappermode (no)\fR"
+/* Request that the Postfix SMTP client connects using the
+/* legacy SMTPS protocol instead of using the STARTTLS command.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBsmtp_tls_dane_insecure_mx_policy (see 'postconf -d' output)\fR"
+/* The TLS policy for MX hosts with "secure" TLSA records when the
+/* nexthop destination security level is \fBdane\fR, but the MX
+/* record was found via an "insecure" MX lookup.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtp_tls_connection_reuse (no)\fR"
+/* Try to make multiple deliveries per TLS-encrypted connection.
+/* .IP "\fBsmtp_tls_chain_files (empty)\fR"
+/* List of one or more PEM files, each holding one or more private keys
+/* directly followed by a corresponding certificate chain.
+/* .IP "\fBsmtp_tls_servername (empty)\fR"
+/* Optional name to send to the remote SMTP server in the TLS Server
+/* Name Indication (SNI) extension.
+/* .PP
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* OBSOLETE STARTTLS CONTROLS
+/* .ad
+/* .fi
+/* The following configuration parameters exist for compatibility
+/* with Postfix versions before 2.3. Support for these will
+/* be removed in a future release.
+/* .IP "\fBsmtp_use_tls (no)\fR"
+/* Opportunistic mode: use TLS when a remote SMTP server announces
+/* STARTTLS support, otherwise send the mail in the clear.
+/* .IP "\fBsmtp_enforce_tls (no)\fR"
+/* Enforcement mode: require that remote SMTP servers use TLS
+/* encryption, and never send mail in the clear.
+/* .IP "\fBsmtp_tls_enforce_peername (yes)\fR"
+/* With mandatory TLS encryption, require that the remote SMTP
+/* server hostname matches the information in the remote SMTP server
+/* certificate.
+/* .IP "\fBsmtp_tls_per_site (empty)\fR"
+/* Optional lookup tables with the Postfix SMTP client TLS usage
+/* policy by next-hop destination and by remote SMTP server hostname.
+/* .IP "\fBsmtp_tls_cipherlist (empty)\fR"
+/* Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS
+/* cipher list.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBsmtp_connect_timeout (30s)\fR"
+/* The Postfix SMTP client time limit for completing a TCP connection, or
+/* zero (use the operating system built-in time limit).
+/* .IP "\fBsmtp_helo_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the HELO or EHLO command,
+/* and for receiving the initial remote SMTP server response.
+/* .IP "\fBlmtp_lhlo_timeout (300s)\fR"
+/* The Postfix LMTP client time limit for sending the LHLO command,
+/* and for receiving the initial remote LMTP server response.
+/* .IP "\fBsmtp_xforward_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the XFORWARD command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_mail_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the MAIL FROM command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_rcpt_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP RCPT TO
+/* command, and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_data_init_timeout (120s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP DATA command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_data_xfer_timeout (180s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP message content.
+/* .IP "\fBsmtp_data_done_timeout (600s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP ".", and
+/* for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_quit_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the QUIT command,
+/* and for receiving the remote SMTP server response.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtp_mx_address_limit (5)\fR"
+/* The maximal number of MX (mail exchanger) IP addresses that can
+/* result from Postfix SMTP client mail exchanger lookups, or zero (no
+/* limit).
+/* .IP "\fBsmtp_mx_session_limit (2)\fR"
+/* The maximal number of SMTP sessions per delivery request before
+/* the Postfix SMTP client
+/* gives up or delivers to a fall-back relay host, or zero (no
+/* limit).
+/* .IP "\fBsmtp_rset_timeout (20s)\fR"
+/* The Postfix SMTP client time limit for sending the RSET command,
+/* and for receiving the remote SMTP server response.
+/* .PP
+/* Available in Postfix version 2.2 and earlier:
+/* .IP "\fBlmtp_cache_connection (yes)\fR"
+/* Keep Postfix LMTP client connections open for up to $max_idle
+/* seconds.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_connection_cache_destinations (empty)\fR"
+/* Permanently enable SMTP connection caching for the specified
+/* destinations.
+/* .IP "\fBsmtp_connection_cache_on_demand (yes)\fR"
+/* Temporarily enable SMTP connection caching while a destination
+/* has a high volume of mail in the active queue.
+/* .IP "\fBsmtp_connection_reuse_time_limit (300s)\fR"
+/* The amount of time during which Postfix will use an SMTP
+/* connection repeatedly.
+/* .IP "\fBsmtp_connection_cache_time_limit (2s)\fR"
+/* When SMTP connection caching is enabled, the amount of time that
+/* an unused SMTP client socket is kept open before it is closed.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBconnection_cache_protocol_timeout (5s)\fR"
+/* Time limit for connection cache connect, send or receive
+/* operations.
+/* .PP
+/* Available in Postfix version 2.9 - 3.6:
+/* .IP "\fBsmtp_per_record_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_connection_reuse_count_limit (0)\fR"
+/* When SMTP connection caching is enabled, the number of times
+/* that an SMTP session may be reused before it is closed, or zero (no
+/* limit).
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtp_tls_connection_reuse (no)\fR"
+/* Try to make multiple deliveries per TLS-encrypted connection.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBsmtp_per_request_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per plaintext or TLS read or write call, to a combined
+/* time limit for sending a complete SMTP request and for receiving a
+/* complete SMTP response.
+/* .IP "\fBsmtp_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA requests, when deadlines are enabled with smtp_per_request_deadline.
+/* .PP
+/* Implemented in the qmgr(8) daemon:
+/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_recipient_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* 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.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, cause the verbose
+/* logging level to increase by the amount specified in $debug_peer_level.
+/* .IP "\fBerror_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications about mail delivery
+/* problems that are caused by policy, resource, software or protocol
+/* errors.
+/* .IP "\fBinternal_mail_filter_classes (empty)\fR"
+/* What categories of Postfix-generated mail are subject to
+/* before-queue content inspection by non_smtpd_milters, header_checks
+/* and body_checks.
+/* .IP "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBbest_mx_transport (empty)\fR"
+/* Where the Postfix SMTP client should deliver mail when it detects
+/* a "mail loops back to myself" error condition.
+/* .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 "\fBdisable_dns_lookups (no)\fR"
+/* Disable DNS lookups in the Postfix SMTP and LMTP clients.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBlmtp_assume_final (no)\fR"
+/* When a remote LMTP server announces no DSN support, assume that
+/* the
+/* server performs final delivery, and send "delivered" delivery status
+/* notifications instead of "relayed".
+/* .IP "\fBlmtp_tcp_port (24)\fR"
+/* The default TCP port that the Postfix LMTP client connects to.
+/* .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 "\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 "\fBproxy_interfaces (empty)\fR"
+/* The network interface addresses that this mail system receives mail
+/* on by way of a proxy or network address translation unit.
+/* .IP "\fBsmtp_address_preference (any)\fR"
+/* The address type ("ipv6", "ipv4" or "any") that the Postfix
+/* SMTP client will try first, when a destination has IPv6 and IPv4
+/* addresses with equal MX preference.
+/* .IP "\fBsmtp_bind_address (empty)\fR"
+/* An optional numerical network address that the Postfix SMTP client
+/* should bind to when making an IPv4 connection.
+/* .IP "\fBsmtp_bind_address6 (empty)\fR"
+/* An optional numerical network address that the Postfix SMTP client
+/* should bind to when making an IPv6 connection.
+/* .IP "\fBsmtp_helo_name ($myhostname)\fR"
+/* The hostname to send in the SMTP HELO or EHLO command.
+/* .IP "\fBlmtp_lhlo_name ($myhostname)\fR"
+/* The hostname to send in the LMTP LHLO command.
+/* .IP "\fBsmtp_host_lookup (dns)\fR"
+/* What mechanisms the Postfix SMTP client uses to look up a host's
+/* IP address.
+/* .IP "\fBsmtp_randomize_addresses (yes)\fR"
+/* Randomize the order of equal-preference MX host addresses.
+/* .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 with Postfix 2.2 and earlier:
+/* .IP "\fBfallback_relay (empty)\fR"
+/* Optional list of relay hosts for SMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 2.3 and later:
+/* .IP "\fBsmtp_fallback_relay ($fallback_relay)\fR"
+/* Optional list of relay hosts for SMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 3.0 and later:
+/* .IP "\fBsmtp_address_verify_target (rcpt)\fR"
+/* In the context of email address verification, the SMTP protocol
+/* stage that determines whether an email address is deliverable.
+/* .PP
+/* Available with Postfix 3.1 and later:
+/* .IP "\fBlmtp_fallback_relay (empty)\fR"
+/* Optional list of relay hosts for LMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 3.2 and later:
+/* .IP "\fBsmtp_tcp_port (smtp)\fR"
+/* The default TCP port that the Postfix SMTP client connects to.
+/* .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.7 and later:
+/* .IP "\fBsmtp_bind_address_enforce (no)\fR"
+/* Defer delivery when the Postfix SMTP client cannot apply the
+/* smtp_bind_address or smtp_bind_address6 setting.
+/* SEE ALSO
+/* generic(5), output address rewriting
+/* header_checks(5), message header content inspection
+/* body_checks(5), body parts content inspection
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* scache(8), connection cache server
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* tlsmgr(8), TLS session and PRNG management
+/* 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
+/* SASL_README, Postfix SASL howto
+/* TLS_README, Postfix STARTTLS howto
+/* 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
+/*
+/* Command pipelining in cooperation with:
+/* Jon Ribbens
+/* Oaktree Internet Solutions Ltd.,
+/* Internet House,
+/* Canal Basin,
+/* Coventry,
+/* CV1 4LY, United Kingdom.
+/*
+/* SASL support originally by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Revised TLS and SMTP connection cache support by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <byte_mask.h>
+
+/* Global library. */
+
+#include <deliver_request.h>
+#include <delivered_hdr.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <debug_peer.h>
+#include <flush_clnt.h>
+#include <scache.h>
+#include <string_list.h>
+#include <maps.h>
+#include <ext_prop.h>
+#include <hfrom_format.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Tunable parameters. These have compiled-in defaults that can be overruled
+ * by settings in the global Postfix configuration file.
+ */
+int var_smtp_conn_tmout;
+int var_smtp_helo_tmout;
+int var_smtp_xfwd_tmout;
+int var_smtp_mail_tmout;
+int var_smtp_rcpt_tmout;
+int var_smtp_data0_tmout;
+int var_smtp_data1_tmout;
+int var_smtp_data2_tmout;
+int var_smtp_rset_tmout;
+int var_smtp_quit_tmout;
+char *var_notify_classes;
+int var_smtp_skip_5xx_greeting;
+int var_ign_mx_lookup_err;
+int var_skip_quit_resp;
+char *var_fallback_relay;
+char *var_bestmx_transp;
+char *var_error_rcpt;
+int var_smtp_always_ehlo;
+int var_smtp_never_ehlo;
+char *var_smtp_sasl_opts;
+char *var_smtp_sasl_path;
+char *var_smtp_sasl_passwd;
+bool var_smtp_sasl_enable;
+char *var_smtp_sasl_mechs;
+char *var_smtp_sasl_type;
+char *var_smtp_bind_addr;
+char *var_smtp_bind_addr6;
+char *var_smtp_vrfy_tgt;
+bool var_smtp_rand_addr;
+int var_smtp_pix_thresh;
+int var_queue_run_delay;
+int var_min_backoff_time;
+int var_smtp_pix_delay;
+int var_smtp_line_limit;
+char *var_smtp_helo_name;
+char *var_smtp_host_lookup;
+bool var_smtp_quote_821_env;
+bool var_smtp_defer_mxaddr;
+bool var_smtp_send_xforward;
+int var_smtp_mxaddr_limit;
+int var_smtp_mxsess_limit;
+int var_smtp_cache_conn;
+int var_smtp_reuse_time;
+int var_smtp_reuse_count;
+char *var_smtp_cache_dest;
+char *var_scache_service; /* You can now leave this here. */
+bool var_smtp_cache_demand;
+char *var_smtp_ehlo_dis_words;
+char *var_smtp_ehlo_dis_maps;
+char *var_smtp_addr_pref;
+
+char *var_smtp_tls_level;
+bool var_smtp_use_tls;
+bool var_smtp_enforce_tls;
+char *var_smtp_tls_per_site;
+char *var_smtp_tls_policy;
+bool var_smtp_tls_wrappermode;
+bool var_smtp_tls_conn_reuse;
+char *var_tlsproxy_service;
+
+#ifdef USE_TLS
+char *var_smtp_sasl_tls_opts;
+char *var_smtp_sasl_tlsv_opts;
+int var_smtp_starttls_tmout;
+char *var_smtp_tls_CAfile;
+char *var_smtp_tls_CApath;
+char *var_smtp_tls_chain_files;
+char *var_smtp_tls_cert_file;
+char *var_smtp_tls_mand_ciph;
+char *var_smtp_tls_excl_ciph;
+char *var_smtp_tls_mand_excl;
+char *var_smtp_tls_dcert_file;
+char *var_smtp_tls_dkey_file;
+bool var_smtp_tls_enforce_peername;
+char *var_smtp_tls_key_file;
+char *var_smtp_tls_loglevel;
+bool var_smtp_tls_note_starttls_offer;
+char *var_smtp_tls_mand_proto;
+char *var_smtp_tls_sec_cmatch;
+int var_smtp_tls_scert_vd;
+char *var_smtp_tls_vfy_cmatch;
+char *var_smtp_tls_fpt_cmatch;
+char *var_smtp_tls_fpt_dgst;
+char *var_smtp_tls_tafile;
+char *var_smtp_tls_proto;
+char *var_smtp_tls_ciph;
+char *var_smtp_tls_eccert_file;
+char *var_smtp_tls_eckey_file;
+char *var_smtp_tls_sni;
+bool var_smtp_tls_blk_early_mail_reply;
+bool var_smtp_tls_force_tlsa;
+char *var_smtp_tls_insecure_mx_policy;
+
+#endif
+
+char *var_smtp_generic_maps;
+char *var_prop_extension;
+bool var_smtp_sender_auth;
+char *var_smtp_tcp_port;
+int var_scache_proto_tmout;
+bool var_smtp_cname_overr;
+char *var_smtp_pix_bug_words;
+char *var_smtp_pix_bug_maps;
+char *var_cyrus_conf_path;
+char *var_smtp_head_chks;
+char *var_smtp_mime_chks;
+char *var_smtp_nest_chks;
+char *var_smtp_body_chks;
+char *var_smtp_resp_filter;
+bool var_lmtp_assume_final;
+char *var_smtp_dns_res_opt;
+char *var_smtp_dns_support;
+bool var_smtp_dummy_mail_auth;
+char *var_smtp_dsn_filter;
+char *var_smtp_dns_re_filter;
+bool var_smtp_balance_inet_proto;
+bool var_smtp_req_deadline;
+int var_smtp_min_data_rate;
+
+ /* Special handling of 535 AUTH errors. */
+char *var_smtp_sasl_auth_cache_name;
+int var_smtp_sasl_auth_cache_time;
+bool var_smtp_sasl_auth_soft_bounce;
+
+char *var_hfrom_format;
+bool var_smtp_bind_addr_enforce;
+
+ /*
+ * Global variables.
+ */
+int smtp_mode;
+int smtp_host_lookup_mask;
+int smtp_dns_support;
+STRING_LIST *smtp_cache_dest;
+SCACHE *smtp_scache;
+MAPS *smtp_ehlo_dis_maps;
+MAPS *smtp_generic_maps;
+int smtp_ext_prop_mask;
+unsigned smtp_dns_res_opt;
+MAPS *smtp_pix_bug_maps;
+HBC_CHECKS *smtp_header_checks; /* limited header checks */
+HBC_CHECKS *smtp_body_checks; /* limited body checks */
+SMTP_CLI_ATTR smtp_cli_attr; /* parsed command-line */
+int smtp_hfrom_format; /* postmaster notifications */
+
+#ifdef USE_TLS
+
+ /*
+ * OpenSSL client state (opaque handle)
+ */
+TLS_APPL_STATE *smtp_tls_ctx;
+int smtp_tls_insecure_mx_policy;
+
+#endif
+
+ /*
+ * IPv6 preference.
+ */
+static int smtp_addr_pref;
+
+/* get_cli_attr - get command-line attributes */
+
+static void get_cli_attr(SMTP_CLI_ATTR *attr, char **argv)
+{
+ const char myname[] = "get_cli_attr";
+ const char *last_flags = "flags="; /* i.e. empty */
+ static const BYTE_MASK flags_map[] = {
+ 'D', SMTP_CLI_FLAG_DELIVERED_TO,
+ 'O', SMTP_CLI_FLAG_ORIG_RCPT,
+ 'R', SMTP_CLI_FLAG_RETURN_PATH,
+ 'X', SMTP_CLI_FLAG_FINAL_DELIVERY,
+ 0,
+ };
+
+ /*
+ * Initialize.
+ */
+ attr->flags = 0;
+
+ /*
+ * Iterate over the command-line attribute list. Errors are fatal.
+ */
+ for ( /* void */ ; *argv != 0; argv++) {
+
+ /*
+ * flags=stuff. Errors are fatal.
+ */
+ if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) {
+ last_flags = *argv;
+ if (msg_verbose)
+ msg_info("%s: %s", myname, last_flags);
+ attr->flags = byte_mask(*argv, flags_map,
+ *argv + sizeof("flags=") - 1);
+ }
+
+ /*
+ * Bad.
+ */
+ else
+ msg_fatal("unknown attribute name: %s", *argv);
+ }
+
+ /*
+ * Backwards compatibility, redundancy, and obsolescence.
+ */
+ if (!smtp_mode && var_lmtp_assume_final
+ && (attr->flags & SMTP_CLI_FLAG_FINAL_DELIVERY) == 0) {
+ attr->flags |= SMTP_CLI_FLAG_FINAL_DELIVERY;
+ msg_warn("%s is obsolete; instead, specify \"%sX\" in %s",
+ VAR_LMTP_ASSUME_FINAL, last_flags, MASTER_CONF_FILE);
+ }
+}
+
+/* deliver_message - deliver message with extreme prejudice */
+
+static int deliver_message(const char *service, DELIVER_REQUEST *request)
+{
+ SMTP_STATE *state;
+ int result;
+
+ if (msg_verbose)
+ msg_info("deliver_message: from %s", request->sender);
+
+ /*
+ * Sanity checks. The smtp server is unprivileged and chrooted, so we can
+ * afford to distribute the data censoring code, instead of having it all
+ * in one place.
+ */
+ if (request->nexthop[0] == 0)
+ msg_fatal("empty nexthop hostname");
+ if (request->rcpt_list.len <= 0)
+ msg_fatal("recipient count: %d", request->rcpt_list.len);
+
+ /*
+ * D flag checks.
+ */
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO) {
+
+ /*
+ * The D flag cannot be specified for multi-recipient deliveries.
+ */
+ if (request->rcpt_list.len > 1) {
+ msg_warn("flag `D' requires %s_destination_recipient_limit = 1",
+ service);
+ return (reject_deliver_request(service, request, "4.3.5",
+ "mail system configuration error"));
+ }
+
+ /*
+ * The recipient cannot appear in a Delivered-To: header.
+ */
+ else {
+ DELIVERED_HDR_INFO *delivered_info = delivered_hdr_init(
+ request->fp, request->data_offset, FOLD_ADDR_ALL);
+ VSTRING *generic_rcpt = vstring_alloc(100);
+ int have_delivered_loop;
+
+ smtp_rewrite_generic_internal(generic_rcpt,
+ request->rcpt_list.info->address);
+ have_delivered_loop = delivered_hdr_find(
+ delivered_info, STR(generic_rcpt));
+ vstring_free(generic_rcpt);
+ delivered_hdr_free(delivered_info);
+ if (have_delivered_loop) {
+ return (reject_deliver_request(service, request, "5.4.6",
+ "mail forwarding loop for %s",
+ request->rcpt_list.info->address));
+ }
+ }
+ }
+
+ /*
+ * The O flag cannot be specified for multi-recipient deliveries.
+ */
+ if ((smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT)
+ && request->rcpt_list.len > 1) {
+ msg_warn("flag `O' requires %s_destination_recipient_limit = 1",
+ service);
+ return (reject_deliver_request(service, request, "4.3.5",
+ "mail system configuration error"));
+ }
+
+ /*
+ * Initialize. Bundle all information about the delivery request, so that
+ * we can produce understandable diagnostics when something goes wrong
+ * many levels below. The alternative would be to make everything global.
+ */
+ state = smtp_state_alloc();
+ state->request = request;
+ state->src = request->fp;
+ state->service = service;
+ state->misc_flags |= smtp_addr_pref;
+ state->debug_peer_per_nexthop =
+ debug_peer_check(request->nexthop, "noaddr");
+ SMTP_RCPT_INIT(state);
+
+ /*
+ * Establish an SMTP session and deliver this message to all requested
+ * recipients. At the end, notify the postmaster of any protocol errors.
+ * Optionally deliver mail locally when this machine is the best mail
+ * exchanger.
+ */
+ result = smtp_connect(state);
+
+ /*
+ * Clean up.
+ */
+ smtp_state_free(state);
+
+ return (result);
+}
+
+/* smtp_service - perform service for client */
+
+static void smtp_service(VSTREAM *client_stream, char *service,
+ char **unused_argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to remote SMTP delivery service. What we see below is a
+ * little protocol to (1) tell the queue manager that we are ready, (2)
+ * read a request from the queue manager, and (3) report the completion
+ * status of that request. All connection-management stuff is handled by
+ * the common code in single_server.c.
+ */
+ if ((request = deliver_request_read(client_stream)) != 0) {
+ status = deliver_message(service, request);
+ deliver_request_done(client_stream, request, status);
+ }
+}
+
+/* post_init - post-jail initialization */
+
+static void post_init(char *unused_name, char **argv)
+{
+ static const NAME_MASK lookup_masks[] = {
+ SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS,
+ SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE,
+ 0,
+ };
+ static const NAME_MASK dns_res_opt_masks[] = {
+ SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES,
+ SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH,
+ 0,
+ };
+ static const NAME_CODE dns_support[] = {
+ SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED,
+ SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED,
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+ SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC,
+#endif
+ 0, SMTP_DNS_INVALID,
+ };
+
+ if (*var_smtp_dns_support == 0) {
+ /* Backwards compatible empty setting */
+ smtp_dns_support =
+ var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED;
+ } else {
+ smtp_dns_support =
+ name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support);
+ if (smtp_dns_support == SMTP_DNS_INVALID)
+ msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT),
+ var_smtp_dns_support);
+ var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED);
+ }
+
+#ifdef USE_TLS
+ if (smtp_mode) {
+ smtp_tls_insecure_mx_policy =
+ tls_level_lookup(var_smtp_tls_insecure_mx_policy);
+ switch (smtp_tls_insecure_mx_policy) {
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_DANE:
+ break;
+ default:
+ msg_fatal("invalid %s: \"%s\"", VAR_SMTP_TLS_INSECURE_MX_POLICY,
+ var_smtp_tls_insecure_mx_policy);
+ }
+ }
+#endif
+
+ /*
+ * Select hostname lookup mechanisms.
+ */
+ if (smtp_dns_support == SMTP_DNS_DISABLED)
+ smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE;
+ else
+ smtp_host_lookup_mask =
+ name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks,
+ var_smtp_host_lookup);
+ if (msg_verbose)
+ msg_info("host name lookup methods: %s",
+ str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks,
+ smtp_host_lookup_mask));
+
+ /*
+ * Session cache instance.
+ */
+ if (*var_smtp_cache_dest || var_smtp_cache_demand)
+#if 0
+ smtp_scache = scache_multi_create();
+#else
+ smtp_scache = scache_clnt_create(var_scache_service,
+ var_scache_proto_tmout,
+ var_ipc_idle_limit,
+ var_ipc_ttl_limit);
+#endif
+
+ /*
+ * Select DNS query flags.
+ */
+ smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks,
+ var_smtp_dns_res_opt);
+
+ /*
+ * Address verification.
+ */
+ smtp_vrfy_init();
+
+ /*
+ * Look up service command-line attributes; these do not change during
+ * the process lifetime.
+ */
+ get_cli_attr(&smtp_cli_attr, argv);
+
+ /*
+ * header_from format, for postmaster notifications.
+ */
+ smtp_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+ int use_tls;
+ static const NAME_CODE addr_pref_map[] = {
+ INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6,
+ INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4,
+ INET_PROTO_NAME_ANY, 0,
+ 0, -1,
+ };
+
+ /*
+ * Turn on per-peer debugging.
+ */
+ debug_peer_init();
+
+ /*
+ * SASL initialization.
+ */
+ if (var_smtp_sasl_enable)
+#ifdef USE_SASL_AUTH
+ smtp_sasl_initialize();
+#else
+ msg_warn("%s is true, but SASL support is not compiled in",
+ VAR_LMTP_SMTP(SASL_ENABLE));
+#endif
+
+ if (*var_smtp_tls_level != 0)
+ switch (tls_level_lookup(var_smtp_tls_level)) {
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_DANE_ONLY:
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_ENCRYPT:
+ var_smtp_use_tls = var_smtp_enforce_tls = 1;
+ break;
+ case TLS_LEV_DANE:
+ case TLS_LEV_MAY:
+ var_smtp_use_tls = 1;
+ var_smtp_enforce_tls = 0;
+ break;
+ case TLS_LEV_NONE:
+ var_smtp_use_tls = var_smtp_enforce_tls = 0;
+ break;
+ default:
+ /* tls_level_lookup() logs no warning. */
+ /* session_tls_init() assumes that var_smtp_tls_level is sane. */
+ msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level);
+ }
+ use_tls = (var_smtp_use_tls || var_smtp_enforce_tls);
+
+ /*
+ * Initialize the TLS data before entering the chroot jail
+ */
+ if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) {
+#ifdef USE_TLS
+ TLS_CLIENT_INIT_PROPS props;
+
+ tls_pre_jail_init(TLS_ROLE_CLIENT);
+
+ /*
+ * We get stronger type safety and a cleaner interface by combining
+ * the various parameters into a single tls_client_props structure.
+ *
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ *
+ * With tlsproxy(8) turned on, this is still needed for DANE-related
+ * initializations.
+ */
+ smtp_tls_ctx =
+ TLS_CLIENT_INIT(&props,
+ log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL),
+ log_level = var_smtp_tls_loglevel,
+ verifydepth = var_smtp_tls_scert_vd,
+ cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE),
+ chain_files = var_smtp_tls_chain_files,
+ cert_file = var_smtp_tls_cert_file,
+ key_file = var_smtp_tls_key_file,
+ dcert_file = var_smtp_tls_dcert_file,
+ dkey_file = var_smtp_tls_dkey_file,
+ eccert_file = var_smtp_tls_eccert_file,
+ eckey_file = var_smtp_tls_eckey_file,
+ CAfile = var_smtp_tls_CAfile,
+ CApath = var_smtp_tls_CApath,
+ mdalg = var_smtp_tls_fpt_dgst);
+ smtp_tls_list_init();
+ tls_dane_loglevel(VAR_LMTP_SMTP(TLS_LOGLEVEL), var_smtp_tls_loglevel);
+#else
+ msg_warn("TLS has been selected, but TLS support is not compiled in");
+#endif
+ }
+
+ /*
+ * Flush client.
+ */
+ flush_init();
+
+ /*
+ * Session cache domain list.
+ */
+ if (*var_smtp_cache_dest)
+ smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST,
+ MATCH_FLAG_RETURN,
+ var_smtp_cache_dest);
+
+ /*
+ * EHLO keyword filter.
+ */
+ if (*var_smtp_ehlo_dis_maps)
+ smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS),
+ var_smtp_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * PIX bug workarounds.
+ */
+ if (*var_smtp_pix_bug_maps)
+ smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS),
+ var_smtp_pix_bug_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * Generic maps.
+ */
+ if (*var_prop_extension)
+ smtp_ext_prop_mask =
+ ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension);
+ if (*var_smtp_generic_maps)
+ smtp_generic_maps =
+ maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * Header/body checks.
+ */
+ smtp_header_checks = hbc_header_checks_create(
+ VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks,
+ VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks,
+ VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks,
+ smtp_hbc_callbacks);
+ smtp_body_checks = hbc_body_checks_create(
+ VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks,
+ smtp_hbc_callbacks);
+
+ /*
+ * Server reply filter.
+ */
+ if (*var_smtp_resp_filter)
+ smtp_chat_resp_filter =
+ dict_open(var_smtp_resp_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * Address family preference.
+ */
+ if (*var_smtp_addr_pref) {
+ smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE,
+ var_smtp_addr_pref);
+ if (smtp_addr_pref < 0)
+ msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF),
+ var_smtp_addr_pref);
+ }
+
+ /*
+ * DNS reply filter.
+ */
+ if (*var_smtp_dns_re_filter)
+ dns_rr_filter_compile(VAR_LMTP_SMTP(DNS_RE_FILTER),
+ var_smtp_dns_re_filter);
+}
+
+/* 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 - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ char *sane_procname;
+
+#include "smtp_params.c"
+#include "lmtp_params.c"
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * XXX At this point, var_procname etc. are not initialized.
+ *
+ * The process name, "smtp" or "lmtp", determines the protocol, the DSN
+ * server reply type, SASL service information lookup, and more. Prepare
+ * for the possibility there may be another personality.
+ */
+ sane_procname = sane_basename((VSTRING *) 0, argv[0]);
+ if (strcmp(sane_procname, "smtp") == 0)
+ smtp_mode = 1;
+ else if (strcmp(sane_procname, "lmtp") == 0)
+ smtp_mode = 0;
+ else
+ /* TODO: logging is not initialized. */
+ msg_fatal("unexpected process name \"%s\" - "
+ "specify \"smtp\" or \"lmtp\"", var_procname);
+
+ /*
+ * Initialize with the LMTP or SMTP parameter name space.
+ */
+ single_server_main(argc, argv, smtp_service,
+ CA_MAIL_SERVER_TIME_TABLE(smtp_mode ?
+ smtp_time_table : lmtp_time_table),
+ CA_MAIL_SERVER_INT_TABLE(smtp_mode ?
+ smtp_int_table : lmtp_int_table),
+ CA_MAIL_SERVER_STR_TABLE(smtp_mode ?
+ smtp_str_table : lmtp_str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(smtp_mode ?
+ smtp_bool_table : lmtp_bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(smtp_mode ?
+ smtp_nbool_table : lmtp_nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ CA_MAIL_SERVER_POST_INIT(post_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_BOUNCE_INIT(VAR_SMTP_DSN_FILTER,
+ &var_smtp_dsn_filter),
+ 0);
+}
diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h
new file mode 100644
index 0000000..0d5c80a
--- /dev/null
+++ b/src/smtp/smtp.h
@@ -0,0 +1,771 @@
+/*++
+/* NAME
+/* smtp 3h
+/* SUMMARY
+/* smtp client program
+/* SYNOPSIS
+/* #include "smtp.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <htable.h>
+#include <dict.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <scache.h>
+#include <string_list.h>
+#include <maps.h>
+#include <tok822.h>
+#include <dsn_buf.h>
+#include <header_body_checks.h>
+
+ /*
+ * Postfix TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * tlsproxy client.
+ */
+#include <tls_proxy.h>
+
+ /*
+ * Global iterator support. This is updated by the connection-management
+ * loop, and contains dynamic context that appears in lookup keys for SASL
+ * passwords, TLS policy, cached SMTP connections, and cached TLS session
+ * keys.
+ *
+ * For consistency and maintainability, context that is used in more than one
+ * lookup key is formatted with smtp_key_format().
+ */
+typedef struct SMTP_ITERATOR {
+ /* Public members. */
+ VSTRING *request_nexthop; /* delivery request nexhop or empty */
+ VSTRING *dest; /* current nexthop */
+ VSTRING *host; /* hostname or empty */
+ VSTRING *addr; /* printable address or empty */
+ unsigned port; /* network byte order or null */
+ struct DNS_RR *rr; /* DNS resource record or null */
+ struct DNS_RR *mx; /* DNS resource record or null */
+ /* Private members. */
+ VSTRING *saved_dest; /* saved current nexthop */
+ struct SMTP_STATE *parent; /* parent linkage */
+} SMTP_ITERATOR;
+
+#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \
+ vstring_strcpy((iter)->dest, (_dest)); \
+ vstring_strcpy((iter)->host, (_host)); \
+ vstring_strcpy((iter)->addr, (_addr)); \
+ (iter)->port = (_port); \
+ (iter)->mx = (iter)->rr = 0; \
+ vstring_strcpy((iter)->saved_dest, ""); \
+ (iter)->parent = (state); \
+ } while (0)
+
+#define SMTP_ITER_SAVE_DEST(iter) do { \
+ vstring_strcpy((iter)->saved_dest, STR((iter)->dest)); \
+ } while (0)
+
+#define SMTP_ITER_RESTORE_DEST(iter) do { \
+ vstring_strcpy((iter)->dest, STR((iter)->saved_dest)); \
+ } while (0)
+
+ /*
+ * TLS Policy support.
+ */
+#ifdef USE_TLS
+
+typedef struct SMTP_TLS_POLICY {
+ int level; /* TLS enforcement level */
+ char *protocols; /* Acceptable SSL protocols */
+ char *grade; /* Cipher grade: "export", ... */
+ VSTRING *exclusions; /* Excluded SSL ciphers */
+ ARGV *matchargv; /* Cert match patterns */
+ DSN_BUF *why; /* Lookup error status */
+ TLS_DANE *dane; /* DANE TLSA digests */
+ char *sni; /* Optional SNI name when not DANE */
+ int conn_reuse; /* enable connection reuse */
+} SMTP_TLS_POLICY;
+
+ /*
+ * smtp_tls_policy.c
+ */
+extern void smtp_tls_list_init(void);
+extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *);
+extern void smtp_tls_policy_cache_flush(void);
+
+ /*
+ * Macros must use distinct names for local temporary variables, otherwise
+ * there will be bugs due to shadowing. This happened when an earlier
+ * version of smtp_tls_policy_dummy() invoked smtp_tls_policy_init(), but it
+ * could also happen without macro nesting.
+ *
+ * General principle: use all or part of the macro name in each temporary
+ * variable name. Then, append suffixes to the names if needed.
+ */
+#define smtp_tls_policy_dummy(t) do { \
+ SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \
+ smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \
+ _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \
+ } while (0)
+
+ /* This macro is not part of the module external interface. */
+#define smtp_tls_policy_init(t, w) do { \
+ SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \
+ _tls_policy_init_tmp->protocols = 0; \
+ _tls_policy_init_tmp->grade = 0; \
+ _tls_policy_init_tmp->exclusions = 0; \
+ _tls_policy_init_tmp->matchargv = 0; \
+ _tls_policy_init_tmp->why = (w); \
+ _tls_policy_init_tmp->dane = 0; \
+ _tls_policy_init_tmp->sni = 0; \
+ _tls_policy_init_tmp->conn_reuse = 0; \
+ } while (0)
+
+#endif
+
+ /*
+ * State information associated with each SMTP delivery request.
+ * Session-specific state is stored separately.
+ */
+typedef struct SMTP_STATE {
+ int misc_flags; /* processing flags, see below */
+ VSTREAM *src; /* queue file stream */
+ const char *service; /* transport name */
+ DELIVER_REQUEST *request; /* envelope info, offsets */
+ struct SMTP_SESSION *session; /* network connection */
+ int status; /* delivery status */
+ ssize_t space_left; /* output length control */
+
+ /*
+ * Global iterator.
+ */
+ SMTP_ITERATOR iterator[1]; /* Usage: state->iterator->member */
+
+ /*
+ * Global iterator.
+ */
+#ifdef USE_TLS
+ SMTP_TLS_POLICY tls[1]; /* Usage: state->tls->member */
+#endif
+
+ /*
+ * Connection cache support.
+ */
+ HTABLE *cache_used; /* cached addresses that were used */
+ VSTRING *dest_label; /* cached logical/physical binding */
+ VSTRING *dest_prop; /* binding properties, passivated */
+ VSTRING *endp_label; /* cached session physical endpoint */
+ VSTRING *endp_prop; /* endpoint properties, passivated */
+
+ /*
+ * Flags and counters to control the handling of mail delivery errors.
+ * There is some redundancy for sanity checking. At the end of an SMTP
+ * session all recipients should be marked one way or the other.
+ */
+ int rcpt_left; /* recipients left over */
+ int rcpt_drop; /* recipients marked as drop */
+ int rcpt_keep; /* recipients marked as keep */
+
+ /*
+ * DSN Support introduced major bloat in error processing.
+ */
+ DSN_BUF *why; /* on-the-fly formatting buffer */
+
+ /*
+ * Whether per-nexthop debug_peer support was requested. Otherwise,
+ * assume per-server debug_peer support.
+ */
+ int debug_peer_per_nexthop;
+
+ /*
+ * One-bit counters to avoid logging the same warning multiple times per
+ * delivery request.
+ */
+ int logged_line_length_limit:1;
+} SMTP_STATE;
+
+ /*
+ * Primitives to enable/disable/test connection caching and reuse based on
+ * the delivery request next-hop destination (i.e. not smtp_fallback_relay).
+ *
+ * Connection cache lookup by the delivery request next-hop destination allows
+ * a reuse request to skip over bad hosts, and may result in a connection to
+ * a fall-back relay. Once we have found a 'good' host for a delivery
+ * request next-hop, clear the delivery request next-hop destination, to
+ * avoid caching less-preferred connections under that same delivery request
+ * next-hop.
+ */
+#define SET_SCACHE_REQUEST_NEXTHOP(state, nexthop) do { \
+ vstring_strcpy((state)->iterator->request_nexthop, nexthop); \
+ } while (0)
+
+#define CLEAR_SCACHE_REQUEST_NEXTHOP(state) do { \
+ STR((state)->iterator->request_nexthop)[0] = 0; \
+ } while (0)
+
+#define HAVE_SCACHE_REQUEST_NEXTHOP(state) \
+ (STR((state)->iterator->request_nexthop)[0] != 0)
+
+
+ /*
+ * Server features.
+ */
+#define SMTP_FEATURE_ESMTP (1<<0)
+#define SMTP_FEATURE_8BITMIME (1<<1)
+#define SMTP_FEATURE_PIPELINING (1<<2)
+#define SMTP_FEATURE_SIZE (1<<3)
+#define SMTP_FEATURE_STARTTLS (1<<4)
+#define SMTP_FEATURE_AUTH (1<<5)
+#define SMTP_FEATURE_XFORWARD_NAME (1<<7)
+#define SMTP_FEATURE_XFORWARD_ADDR (1<<8)
+#define SMTP_FEATURE_XFORWARD_PROTO (1<<9)
+#define SMTP_FEATURE_XFORWARD_HELO (1<<10)
+#define SMTP_FEATURE_XFORWARD_DOMAIN (1<<11)
+#define SMTP_FEATURE_BEST_MX (1<<12) /* for next-hop or fall-back */
+#define SMTP_FEATURE_RSET_REJECTED (1<<13) /* RSET probe rejected */
+#define SMTP_FEATURE_FROM_CACHE (1<<14) /* cached connection */
+#define SMTP_FEATURE_DSN (1<<15) /* DSN supported */
+#define SMTP_FEATURE_PIX_NO_ESMTP (1<<16) /* PIX smtp fixup mode */
+#define SMTP_FEATURE_PIX_DELAY_DOTCRLF (1<<17) /* PIX smtp fixup mode */
+#define SMTP_FEATURE_XFORWARD_PORT (1<<18)
+#define SMTP_FEATURE_EARLY_TLS_MAIL_REPLY (1<<19) /* CVE-2009-3555 */
+#define SMTP_FEATURE_XFORWARD_IDENT (1<<20)
+#define SMTP_FEATURE_SMTPUTF8 (1<<21) /* RFC 6531 */
+#define SMTP_FEATURE_FROM_PROXY (1<<22) /* proxied connection */
+
+ /*
+ * Features that passivate under the endpoint.
+ */
+#define SMTP_FEATURE_ENDPOINT_MASK \
+ (~(SMTP_FEATURE_BEST_MX | SMTP_FEATURE_RSET_REJECTED \
+ | SMTP_FEATURE_FROM_CACHE))
+
+ /*
+ * Features that passivate under the logical destination.
+ */
+#define SMTP_FEATURE_DESTINATION_MASK (SMTP_FEATURE_BEST_MX)
+
+ /*
+ * Misc flags.
+ */
+#define SMTP_MISC_FLAG_LOOP_DETECT (1<<0)
+#define SMTP_MISC_FLAG_IN_STARTTLS (1<<1)
+#define SMTP_MISC_FLAG_FIRST_NEXTHOP (1<<2)
+#define SMTP_MISC_FLAG_FINAL_NEXTHOP (1<<3)
+#define SMTP_MISC_FLAG_FINAL_SERVER (1<<4)
+#define SMTP_MISC_FLAG_CONN_LOAD (1<<5)
+#define SMTP_MISC_FLAG_CONN_STORE (1<<6)
+#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<7)
+#define SMTP_MISC_FLAG_PREF_IPV6 (1<<8)
+#define SMTP_MISC_FLAG_PREF_IPV4 (1<<9)
+
+#define SMTP_MISC_FLAG_CONN_CACHE_MASK \
+ (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE)
+
+ /*
+ * smtp.c
+ */
+#define SMTP_HAS_DSN(why) (STR((why)->status)[0] != 0)
+#define SMTP_HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4')
+#define SMTP_HAS_HARD_DSN(why) (STR((why)->status)[0] == '5')
+#define SMTP_HAS_LOOP_DSN(why) \
+ (SMTP_HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0)
+
+#define SMTP_SET_SOFT_DSN(why) (STR((why)->status)[0] = '4')
+#define SMTP_SET_HARD_DSN(why) (STR((why)->status)[0] = '5')
+
+extern int smtp_host_lookup_mask; /* host lookup methods to use */
+
+#define SMTP_HOST_FLAG_DNS (1<<0)
+#define SMTP_HOST_FLAG_NATIVE (1<<1)
+
+extern int smtp_dns_support; /* dns support level */
+
+#define SMTP_DNS_INVALID (-1) /* smtp_dns_support_level = <bogus> */
+#define SMTP_DNS_DISABLED 0 /* smtp_dns_support_level = disabled */
+#define SMTP_DNS_ENABLED 1 /* smtp_dns_support_level = enabled */
+#define SMTP_DNS_DNSSEC 2 /* smtp_dns_support_level = dnssec */
+
+extern SCACHE *smtp_scache; /* connection cache instance */
+extern STRING_LIST *smtp_cache_dest; /* cached destinations */
+
+extern MAPS *smtp_ehlo_dis_maps; /* ehlo keyword filter */
+
+extern MAPS *smtp_pix_bug_maps; /* PIX workarounds */
+
+extern MAPS *smtp_generic_maps; /* make internal address valid */
+extern int smtp_ext_prop_mask; /* address extension propagation */
+extern unsigned smtp_dns_res_opt; /* DNS query flags */
+
+#ifdef USE_TLS
+
+extern TLS_APPL_STATE *smtp_tls_ctx; /* client-side TLS engine */
+extern int smtp_tls_insecure_mx_policy; /* DANE post insecure MX? */
+
+#endif
+
+extern HBC_CHECKS *smtp_header_checks; /* limited header checks */
+extern HBC_CHECKS *smtp_body_checks; /* limited body checks */
+
+ /*
+ * smtp_session.c
+ */
+
+typedef struct SMTP_SESSION {
+ VSTREAM *stream; /* network connection */
+ SMTP_ITERATOR *iterator; /* dest, host, addr, port */
+ char *namaddr; /* mail exchanger */
+ char *helo; /* helo response */
+ unsigned port; /* network byte order */
+ char *namaddrport; /* mail exchanger, incl. port */
+
+ VSTRING *buffer; /* I/O buffer */
+ VSTRING *scratch; /* scratch buffer */
+ VSTRING *scratch2; /* scratch buffer */
+
+ int features; /* server features */
+ off_t size_limit; /* server limit or unknown */
+
+ ARGV *history; /* transaction log */
+ int error_mask; /* error classes */
+ struct MIME_STATE *mime_state; /* mime state machine */
+
+ int send_proto_helo; /* XFORWARD support */
+
+ time_t expire_time; /* session reuse expiration time */
+ int reuse_count; /* # of times reused (for logging) */
+ int forbidden; /* No further I/O allowed */
+
+#ifdef USE_SASL_AUTH
+ char *sasl_mechanism_list; /* server mechanism list */
+ char *sasl_username; /* client username */
+ char *sasl_passwd; /* client password */
+ struct XSASL_CLIENT *sasl_client; /* SASL internal state */
+ VSTRING *sasl_reply; /* client response */
+#endif
+
+ /*
+ * TLS related state, don't forget to initialize in session_tls_init()!
+ */
+#ifdef USE_TLS
+ TLS_SESS_STATE *tls_context; /* TLS library session state */
+ char *tls_nexthop; /* Nexthop domain for cert checks */
+ int tls_retry_plain; /* Try plain when TLS handshake fails */
+#endif
+
+ SMTP_STATE *state; /* back link */
+} SMTP_SESSION;
+
+extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, SMTP_ITERATOR *, time_t, int);
+extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int);
+extern int smtp_sess_plaintext_ok(SMTP_ITERATOR *, int);
+extern void smtp_session_free(SMTP_SESSION *);
+extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *);
+extern SMTP_SESSION *smtp_session_activate(int, SMTP_ITERATOR *, VSTRING *, VSTRING *);
+
+ /*
+ * What's in a name?
+ */
+#define SMTP_HNAME(rr) (var_smtp_cname_overr ? (rr)->rname : (rr)->qname)
+
+ /*
+ * smtp_connect.c
+ */
+extern int smtp_connect(SMTP_STATE *);
+
+ /*
+ * smtp_proto.c
+ */
+extern void smtp_vrfy_init(void);
+extern int smtp_helo(SMTP_STATE *);
+extern int smtp_xfer(SMTP_STATE *);
+extern int smtp_rset(SMTP_STATE *);
+extern int smtp_quit(SMTP_STATE *);
+
+extern HBC_CALL_BACKS smtp_hbc_callbacks[];
+
+ /*
+ * A connection is re-usable if session->expire_time is > 0 and the
+ * expiration time has not been reached. This is subtle because the timer
+ * can expire between sending a command and receiving the reply for that
+ * command.
+ *
+ * But wait, there is more! When SMTP command pipelining is enabled, there are
+ * two protocol loops that execute at very different times: one loop that
+ * generates commands, and one loop that receives replies to those commands.
+ * These will be called "sender loop" and "receiver loop", respectively. At
+ * well-defined protocol synchronization points, the sender loop pauses to
+ * let the receiver loop catch up.
+ *
+ * When we choose to reuse a connection, both the sender and receiver protocol
+ * loops end with "." (mail delivery) or "RSET" (address probe). When we
+ * choose not to reuse, both the sender and receiver protocol loops end with
+ * "QUIT". The problem is that we must make the same protocol choices in
+ * both the sender and receiver loops, even though those loops may execute
+ * at completely different times.
+ *
+ * We "freeze" the choice in the sender loop, just before we generate "." or
+ * "RSET". The reader loop leaves the connection cacheable even if the timer
+ * expires by the time the response arrives. The connection cleanup code
+ * will call smtp_quit() for connections with an expired cache expiration
+ * timer.
+ *
+ * We could have made the programmer's life a lot simpler by not making a
+ * choice at all, and always leaving it up to the connection cleanup code to
+ * call smtp_quit() for connections with an expired cache expiration timer.
+ *
+ * As a general principle, neither the sender loop nor the receiver loop must
+ * modify the connection caching state, if that can affect the receiver
+ * state machine for not-yet processed replies to already-generated
+ * commands. This restriction does not apply when we have to exit the
+ * protocol loops prematurely due to e.g., timeout or connection loss, so
+ * that those pending replies will never be received.
+ *
+ * But wait, there is even more! Only the first good connection for a specific
+ * destination may be cached under both the next-hop destination name and
+ * the server address; connections to alternate servers must be cached under
+ * the server address alone. This means we must distinguish between bad
+ * connections and other reasons why connections cannot be cached.
+ */
+#define THIS_SESSION_IS_CACHED \
+ (!THIS_SESSION_IS_FORBIDDEN && session->expire_time > 0)
+
+#define THIS_SESSION_IS_EXPIRED \
+ (THIS_SESSION_IS_CACHED \
+ && (session->expire_time < vstream_ftime(session->stream) \
+ || (var_smtp_reuse_count > 0 \
+ && session->reuse_count >= var_smtp_reuse_count)))
+
+#define THIS_SESSION_IS_THROTTLED \
+ (!THIS_SESSION_IS_FORBIDDEN && session->expire_time < 0)
+
+#define THIS_SESSION_IS_FORBIDDEN \
+ (session->forbidden != 0)
+
+ /* Bring the bad news. */
+
+#define DONT_CACHE_THIS_SESSION \
+ (session->expire_time = 0)
+
+#define DONT_CACHE_THROTTLED_SESSION \
+ (session->expire_time = -1)
+
+#define DONT_USE_FORBIDDEN_SESSION \
+ (session->forbidden = 1)
+
+ /* Initialization. */
+
+#define USE_NEWBORN_SESSION \
+ (session->forbidden = 0)
+
+#define CACHE_THIS_SESSION_UNTIL(when) \
+ (session->expire_time = (when))
+
+ /*
+ * Encapsulate the following so that we don't expose details of
+ * connection management and error handling to the SMTP protocol engine.
+ */
+#ifdef USE_SASL_AUTH
+#define HAVE_SASL_CREDENTIALS \
+ (var_smtp_sasl_enable \
+ && *var_smtp_sasl_passwd \
+ && smtp_sasl_passwd_lookup(session))
+#else
+#define HAVE_SASL_CREDENTIALS (0)
+#endif
+
+#define PREACTIVE_DELAY \
+ (session->state->request->msg_stats.active_arrival.tv_sec - \
+ session->state->request->msg_stats.incoming_arrival.tv_sec)
+
+#define TRACE_REQ_ONLY (DEL_REQ_TRACE_ONLY(state->request->flags))
+
+#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \
+ (session->tls_context == 0 \
+ && state->tls->level == TLS_LEV_MAY \
+ && (TRACE_REQ_ONLY || PREACTIVE_DELAY >= var_min_backoff_time) \
+ && !HAVE_SASL_CREDENTIALS)
+
+#define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \
+ (session->tls_context != 0 \
+ && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \
+ && state->tls->level == TLS_LEV_MAY \
+ && (TRACE_REQ_ONLY || PREACTIVE_DELAY >= var_min_backoff_time) \
+ && !HAVE_SASL_CREDENTIALS)
+
+ /*
+ * XXX The following will not retry recipients that were deferred while the
+ * SMTP_MISC_FLAG_FINAL_SERVER flag was already set. This includes the case
+ * when TLS fails in the middle of a delivery.
+ */
+#define RETRY_AS_PLAINTEXT do { \
+ session->tls_retry_plain = 1; \
+ state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \
+ } while (0)
+
+ /*
+ * smtp_chat.c
+ */
+typedef struct SMTP_RESP { /* server response */
+ int code; /* SMTP code */
+ const char *dsn; /* enhanced status */
+ char *str; /* full reply */
+ VSTRING *dsn_buf; /* status buffer */
+ VSTRING *str_buf; /* reply buffer */
+} SMTP_RESP;
+
+extern void PRINTFLIKE(2, 3) smtp_chat_cmd(SMTP_SESSION *, const char *,...);
+extern DICT *smtp_chat_resp_filter;
+extern SMTP_RESP *smtp_chat_resp(SMTP_SESSION *);
+extern void smtp_chat_init(SMTP_SESSION *);
+extern void smtp_chat_reset(SMTP_SESSION *);
+extern void smtp_chat_notify(SMTP_SESSION *);
+
+#define SMTP_RESP_FAKE(resp, _dsn) \
+ ((resp)->code = 0, \
+ (resp)->dsn = (_dsn), \
+ (resp)->str = DSN_BY_LOCAL_MTA, \
+ (resp))
+
+#define DSN_BY_LOCAL_MTA ((char *) 0) /* DSN issued by local MTA */
+
+#define SMTP_RESP_SET_DSN(resp, _dsn) do { \
+ vstring_strcpy((resp)->dsn_buf, (_dsn)); \
+ (resp)->dsn = STR((resp)->dsn_buf); \
+ } while (0)
+
+ /*
+ * These operations implement a redundant mark-and-sweep algorithm that
+ * explicitly accounts for the fate of every recipient. The interface is
+ * documented in smtp_rcpt.c, which also implements the sweeping. The
+ * smtp_trouble.c module does most of the marking after failure.
+ *
+ * When a delivery fails or succeeds, take one of the following actions:
+ *
+ * - Mark the recipient as KEEP (deliver to alternate MTA) and do not update
+ * the delivery request status.
+ *
+ * - Mark the recipient as DROP (remove from delivery request), log whether
+ * delivery succeeded or failed, delete the recipient from the queue file
+ * and/or update defer or bounce logfiles, and update the delivery request
+ * status.
+ *
+ * At the end of a delivery attempt, all recipients must be marked one way or
+ * the other. Failure to do so will trigger a panic.
+ */
+#define SMTP_RCPT_STATE_KEEP 1 /* send to backup host */
+#define SMTP_RCPT_STATE_DROP 2 /* remove from request */
+#define SMTP_RCPT_INIT(state) do { \
+ (state)->rcpt_drop = (state)->rcpt_keep = 0; \
+ (state)->rcpt_left = state->request->rcpt_list.len; \
+ } while (0)
+
+#define SMTP_RCPT_DROP(state, rcpt) do { \
+ (rcpt)->u.status = SMTP_RCPT_STATE_DROP; (state)->rcpt_drop++; \
+ } while (0)
+
+#define SMTP_RCPT_KEEP(state, rcpt) do { \
+ (rcpt)->u.status = SMTP_RCPT_STATE_KEEP; (state)->rcpt_keep++; \
+ } while (0)
+
+#define SMTP_RCPT_ISMARKED(rcpt) ((rcpt)->u.status != 0)
+
+#define SMTP_RCPT_LEFT(state) (state)->rcpt_left
+
+#define SMTP_RCPT_MARK_COUNT(state) ((state)->rcpt_drop + (state)->rcpt_keep)
+
+extern void smtp_rcpt_cleanup(SMTP_STATE *);
+extern void smtp_rcpt_done(SMTP_STATE *, SMTP_RESP *, RECIPIENT *);
+
+ /*
+ * smtp_trouble.c
+ */
+#define SMTP_THROTTLE 1
+#define SMTP_NOTHROTTLE 0
+extern int smtp_sess_fail(SMTP_STATE *);
+extern int PRINTFLIKE(5, 6) smtp_misc_fail(SMTP_STATE *, int, const char *,
+ SMTP_RESP *, const char *,...);
+extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *,
+ const char *, SMTP_RESP *,
+ const char *,...);
+extern int smtp_stream_except(SMTP_STATE *, int, const char *);
+
+#define smtp_site_fail(state, mta, resp, ...) \
+ smtp_misc_fail((state), SMTP_THROTTLE, (mta), (resp), __VA_ARGS__)
+#define smtp_mesg_fail(state, mta, resp, ...) \
+ smtp_misc_fail((state), SMTP_NOTHROTTLE, (mta), (resp), __VA_ARGS__)
+
+ /*
+ * smtp_unalias.c
+ */
+extern const char *smtp_unalias_name(const char *);
+extern VSTRING *smtp_unalias_addr(VSTRING *, const char *);
+
+ /*
+ * smtp_state.c
+ */
+extern SMTP_STATE *smtp_state_alloc(void);
+extern void smtp_state_free(SMTP_STATE *);
+
+ /*
+ * smtp_map11.c
+ */
+extern int smtp_map11_external(VSTRING *, MAPS *, int);
+extern int smtp_map11_tree(TOK822 *, MAPS *, int);
+extern int smtp_map11_internal(VSTRING *, MAPS *, int);
+
+ /*
+ * smtp_key.c
+ */
+char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int);
+
+#define SMTP_KEY_FLAG_SERVICE (1<<0) /* service name */
+#define SMTP_KEY_FLAG_SENDER (1<<1) /* sender address */
+#define SMTP_KEY_FLAG_REQ_NEXTHOP (1<<2) /* delivery request nexthop */
+#define SMTP_KEY_FLAG_CUR_NEXTHOP (1<<3) /* current nexthop */
+#define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */
+#define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */
+#define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */
+#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */
+
+#define SMTP_KEY_MASK_ALL \
+ (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \
+ SMTP_KEY_FLAG_REQ_NEXTHOP | \
+ SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \
+ SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL)
+
+ /*
+ * Conditional lookup-key flags for cached connections that may be
+ * SASL-authenticated with a per-{sender, nexthop, or hostname} credential.
+ * Each bit corresponds to one type of smtp_sasl_password_file lookup key,
+ * and is turned on only when the corresponding main.cf parameter is turned
+ * on.
+ */
+#define COND_SASL_SMTP_KEY_FLAG_SENDER \
+ ((var_smtp_sender_auth && *var_smtp_sasl_passwd) ? \
+ SMTP_KEY_FLAG_SENDER : 0)
+
+#define COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0)
+
+#ifdef USE_TLS
+#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (TLS_MUST_MATCH(state->tls->level) ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0)
+#else
+#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (0)
+#endif
+
+#define COND_SASL_SMTP_KEY_FLAG_HOSTNAME \
+ (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_HOSTNAME : 0)
+
+ /*
+ * Connection-cache destination lookup key, based on the delivery request
+ * nexthop. The SENDER attribute is a proxy for sender-dependent SASL
+ * credentials (or absence thereof), and prevents false connection sharing
+ * when different SASL credentials may be required for different deliveries
+ * to the same domain and port. Likewise, the delivery request nexthop
+ * (REQ_NEXTHOP) prevents false sharing of TLS identities (the destination
+ * key links only to appropriate endpoint lookup keys). The SERVICE
+ * attribute is a proxy for all request-independent configuration details.
+ */
+#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \
+ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
+ | SMTP_KEY_FLAG_REQ_NEXTHOP)
+
+ /*
+ * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME,
+ * PORT and TLS_LEVEL attributes are proxies for SASL credentials and TLS
+ * authentication (or absence thereof), and prevent false connection sharing
+ * when different SASL credentials or TLS identities may be required for
+ * different deliveries to the same IP address and port. The SERVICE
+ * attribute is a proxy for all request-independent configuration details.
+ */
+#define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \
+ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
+ | COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \
+ | COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_ADDR | \
+ SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL)
+
+ /*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+#define LEN(s) VSTRING_LEN(s)
+
+extern int smtp_mode;
+
+#define VAR_LMTP_SMTP(x) (smtp_mode ? VAR_SMTP_##x : VAR_LMTP_##x)
+#define LMTP_SMTP_SUFFIX(x) (smtp_mode ? x##_SMTP : x##_LMTP)
+
+ /*
+ * Parsed command-line attributes. These do not change during the process
+ * lifetime.
+ */
+typedef struct {
+ int flags; /* from flags=, see below */
+} SMTP_CLI_ATTR;
+
+#define SMTP_CLI_FLAG_DELIVERED_TO (1<<0) /* prepend Delivered-To: */
+#define SMTP_CLI_FLAG_ORIG_RCPT (1<<1) /* prepend X-Original-To: */
+#define SMTP_CLI_FLAG_RETURN_PATH (1<<2) /* prepend Return-Path: */
+#define SMTP_CLI_FLAG_FINAL_DELIVERY (1<<3) /* final, not relay */
+
+#define SMTP_CLI_MASK_ADD_HEADERS (SMTP_CLI_FLAG_DELIVERED_TO | \
+ SMTP_CLI_FLAG_ORIG_RCPT | SMTP_CLI_FLAG_RETURN_PATH)
+
+extern SMTP_CLI_ATTR smtp_cli_attr;
+
+ /*
+ * smtp_misc.c.
+ */
+extern void smtp_rewrite_generic_internal(VSTRING *, const char *);
+extern void smtp_quote_822_address_flags(VSTRING *, const char *, int);
+extern void smtp_quote_821_address(VSTRING *, const char *);
+
+ /*
+ * header_from_format support, for postmaster notifications.
+ */
+extern int smtp_hfrom_format;
+
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c
new file mode 100644
index 0000000..2b5c126
--- /dev/null
+++ b/src/smtp/smtp_addr.c
@@ -0,0 +1,702 @@
+/*++
+/* NAME
+/* smtp_addr 3
+/* SUMMARY
+/* SMTP server address lookup
+/* SYNOPSIS
+/* #include "smtp_addr.h"
+/*
+/* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself)
+/* char *name;
+/* DNS_RR **mxrr;
+/* int misc_flags;
+/* DSN_BUF *why;
+/* int *found_myself;
+/*
+/* DNS_RR *smtp_host_addr(name, misc_flags, why)
+/* char *name;
+/* int misc_flags;
+/* DSN_BUF *why;
+/* DESCRIPTION
+/* This module implements Internet address lookups. By default,
+/* lookups are done via the Internet domain name service (DNS).
+/* A reasonable number of CNAME indirections is permitted. When
+/* DNS lookups are disabled, host address lookup is done with
+/* getnameinfo() or gethostbyname().
+/*
+/* smtp_domain_addr() looks up the network addresses for mail
+/* exchanger hosts listed for the named domain. Addresses are
+/* returned in most-preferred first order. The result is truncated
+/* so that it contains only hosts that are more preferred than the
+/* local mail server itself. The found_myself result parameter
+/* is updated when the local MTA is MX host for the specified
+/* destination. If MX records were found, the rname, qname,
+/* and dnssec validation status of the MX RRset are returned
+/* via mxrr, which the caller must free with dns_rr_free().
+/*
+/* When no mail exchanger is listed in the DNS for \fIname\fR, the
+/* request is passed to smtp_host_addr().
+/*
+/* It is an error to call smtp_domain_addr() when DNS lookups are
+/* disabled.
+/*
+/* smtp_host_addr() looks up all addresses listed for the named
+/* host. The host can be specified as a numerical Internet network
+/* address, or as a symbolic host name.
+/*
+/* Results from smtp_domain_addr() or smtp_host_addr() are
+/* destroyed by dns_rr_free(), including null lists.
+/* DIAGNOSTICS
+/* Panics: interface violations. For example, calling smtp_domain_addr()
+/* when DNS lookups are explicitly disabled.
+/*
+/* All routines either return a DNS_RR pointer, or return a null
+/* pointer and update the \fIwhy\fR argument 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <dsn_buf.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_addr.h"
+
+/* smtp_print_addr - print address list */
+
+static void smtp_print_addr(const char *what, DNS_RR *addr_list)
+{
+ DNS_RR *addr;
+ MAI_HOSTADDR_STR hostaddr;
+
+ msg_info("begin %s address list", what);
+ for (addr = addr_list; addr; addr = addr->next) {
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("skipping record type %s: %m", dns_strtype(addr->type));
+ } else {
+ msg_info("pref %4d host %s/%s",
+ addr->pref, SMTP_HNAME(addr),
+ hostaddr.buf);
+ }
+ }
+ msg_info("end %s address list", what);
+}
+
+/* smtp_addr_one - address lookup for one host name */
+
+static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt,
+ unsigned pref, DSN_BUF *why)
+{
+ const char *myname = "smtp_addr_one";
+ DNS_RR *addr = 0;
+ DNS_RR *rr;
+ int aierr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+ unsigned char *proto_family_list = proto_info->sa_family_list;
+ int found;
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * Interpret a numerical name as an address.
+ */
+ if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) {
+ if (strchr((char *) proto_family_list, res0->ai_family) != 0) {
+ if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family "
+ "%d: %m", host, res0->ai_addr->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ if (msg_verbose)
+ msg_info("%s: using numerical host %s", myname, host);
+ freeaddrinfo(res0);
+ return (addr_list);
+ }
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * Use DNS lookup, but keep the option open to use native name service.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+ if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) {
+ res_opt |= smtp_dns_res_opt;
+ switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
+ why->reason, DNS_REQ_FLAG_NONE,
+ proto_info->dns_atype_list)) {
+ case DNS_OK:
+ for (rr = addr; rr; rr = rr->next)
+ rr->pref = pref;
+ addr_list = dns_rr_append(addr_list, addr);
+ return (addr_list);
+ default:
+ dsb_status(why, "4.4.3");
+ return (addr_list);
+ case DNS_FAIL:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
+ return (addr_list);
+ case DNS_INVAL:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ return (addr_list);
+ case DNS_POLICY:
+ dsb_status(why, "4.7.0");
+ return (addr_list);
+ case DNS_NOTFOUND:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ /* maybe native naming service will succeed */
+ break;
+ }
+ }
+
+ /*
+ * Use the native name service which also looks in /etc/hosts.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+#define RETRY_AI_ERROR(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+#ifdef EAI_NODATA
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
+#else
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NONAME)
+#endif
+
+ if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) {
+ if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
+ dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
+ (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
+ (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
+ "unable to look up host %s: %s",
+ host, MAI_STRERROR(aierr));
+ } else {
+ for (found = 0, res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ found++;
+ if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family "
+ "%d: %m", host, res0->ai_addr->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ if (msg_verbose) {
+ MAI_HOSTADDR_STR hostaddr_str;
+
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr_str, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: native lookup result: %s",
+ myname, hostaddr_str.buf);
+ }
+ }
+ freeaddrinfo(res0);
+ if (found == 0) {
+ dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
+ "%s: host not found", host);
+ }
+ return (addr_list);
+ }
+ }
+
+ /*
+ * No further alternatives for host lookup.
+ */
+ return (addr_list);
+}
+
+/* smtp_addr_list - address lookup for a list of mail exchangers */
+
+static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why)
+{
+ DNS_RR *addr_list = 0;
+ DNS_RR *rr;
+ int res_opt = 0;
+
+ if (mx_names->dnssec_valid)
+ res_opt = RES_USE_DNSSEC;
+#ifdef USE_TLS
+ else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY)
+ res_opt = RES_USE_DNSSEC;
+#endif
+
+ /*
+ * As long as we are able to look up any host address, we ignore problems
+ * with DNS lookups (except if we're backup MX, and all the better MX
+ * hosts can't be found).
+ *
+ * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup
+ * error, any->RETRY upon temporary lookup error) so that we can
+ * correctly handle the case of no resolvable MX host. Currently this is
+ * always treated as a soft error. RFC 2821 wants a more precise
+ * response.
+ *
+ * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in
+ * MX records - we should not append the local domain to dot-less names.
+ *
+ * XXX However, this is not the only problem. If we use the native name
+ * service for host lookup, then it will usually enable RES_DNSRCH which
+ * appends local domain information to all lookups. In particular,
+ * getaddrinfo() may invoke a resolver that runs in a different process
+ * (NIS server, nscd), so we can't even reliably turn this off by
+ * tweaking the in-process resolver flags.
+ */
+ for (rr = mx_names; rr; rr = rr->next) {
+ if (rr->type != T_MX)
+ msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
+ addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
+ rr->pref, why);
+ }
+ return (addr_list);
+}
+
+/* smtp_find_self - spot myself in a crowd of mail exchangers */
+
+static DNS_RR *smtp_find_self(DNS_RR *addr_list)
+{
+ const char *myname = "smtp_find_self";
+ INET_ADDR_LIST *self;
+ INET_ADDR_LIST *proxy;
+ DNS_RR *addr;
+ int i;
+
+ self = own_inet_addr_list();
+ proxy = proxy_inet_addr_list();
+
+ for (addr = addr_list; addr; addr = addr->next) {
+
+ /*
+ * Find out if this mail system is listening on this address.
+ */
+ for (i = 0; i < self->used; i++)
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
+ if (msg_verbose)
+ msg_info("%s: found self at pref %d", myname, addr->pref);
+ return (addr);
+ }
+
+ /*
+ * Find out if this mail system has a proxy listening on this
+ * address.
+ */
+ for (i = 0; i < proxy->used; i++)
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
+ if (msg_verbose)
+ msg_info("%s: found proxy at pref %d", myname, addr->pref);
+ return (addr);
+ }
+ }
+
+ /*
+ * Didn't find myself, or my proxy.
+ */
+ if (msg_verbose)
+ msg_info("%s: not found", myname);
+ return (0);
+}
+
+/* smtp_truncate_self - truncate address list at self and equivalents */
+
+static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref)
+{
+ DNS_RR *addr;
+ DNS_RR *last;
+
+ for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) {
+ if (pref == addr->pref) {
+ if (msg_verbose)
+ smtp_print_addr("truncated", addr);
+ dns_rr_free(addr);
+ if (last == 0) {
+ addr_list = 0;
+ } else {
+ last->next = 0;
+ }
+ break;
+ }
+ }
+ return (addr_list);
+}
+
+/* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */
+
+static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags,
+ int addr_limit)
+{
+ const char myname[] = "smtp_balance_inet_proto";
+ DNS_RR *rr;
+ DNS_RR *stripped_list;
+ DNS_RR *next;
+ int v6_count;
+ int v4_count;
+ int v6_target, v4_target;
+ int *p;
+
+ /*
+ * Precondition: the input is sorted by MX preference (not necessarily IP
+ * address family preference), and addresses with the same or worse
+ * preference than 'myself' have been eliminated. Postcondition: the
+ * relative list order is unchanged, but some elements are removed.
+ */
+
+ /*
+ * Count the number of IPv6 and IPv4 addresses.
+ */
+ for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) {
+ if (rr->type == T_A) {
+ v4_count++;
+ } else if (rr->type == T_AAAA) {
+ v6_count++;
+ } else {
+ msg_panic("%s: unexpected record type: %s",
+ myname, dns_strtype(rr->type));
+ }
+ }
+
+ /*
+ * Ensure that one address type will not out-crowd the other, while
+ * enforcing the address count limit. This works around a current problem
+ * where some destination announces primarily IPv6 MX addresses, the
+ * smtp_address_limit eliminates most or all IPv4 addresses, and the
+ * destination is not reachable over IPv6.
+ *
+ * Maybe: do all smtp_mx_address_limit enforcement here, and remove
+ * pre-existing enforcement elsewhere. That would obsolete the
+ * smtp_balance_inet_protocols configuration parameter.
+ */
+ if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) {
+
+ /*-
+ * Decide how many IPv6 and IPv4 addresses to keep. The code below
+ * has three branches, corresponding to the regions R1, R2 and R3
+ * in the figure.
+ *
+ * L = addr_limit
+ * X = excluded by condition (v4_count + v6_count > addr_limit)
+ *
+ * v4_count
+ * ^
+ * |
+ * L \ R1
+ * |X\ |
+ * |XXX\ |
+ * |XXXXX\ | R2
+ * L/2 +-------\-------
+ * |XXXXXXX|X\
+ * |XXXXXXX|XXX\ R3
+ * |XXXXXXX|XXXXX\
+ * 0 +-------+-------\--> v6_count
+ * 0 L/2 L
+ */
+ if (v6_count <= addr_limit / 2) { /* Region R1 */
+ v6_target = v6_count;
+ v4_target = addr_limit - v6_target;
+ } else if (v4_count <= addr_limit / 2) {/* Region R3 */
+ v4_target = v4_count;
+ v6_target = addr_limit - v4_target;
+ } else { /* Region R2 */
+ /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */
+ v4_target = (addr_limit + (addr_list->type == T_A)) / 2;
+ v6_target = addr_limit - v4_target;
+ }
+ if (msg_verbose)
+ msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target);
+
+ /* Enforce the address count targets. */
+ stripped_list = 0;
+ for (rr = addr_list; rr != 0; rr = next) {
+ next = rr->next;
+ rr->next = 0;
+ if (rr->type == T_A) {
+ p = &v4_target;
+ } else if (rr->type == T_AAAA) {
+ p = &v6_target;
+ } else {
+ msg_panic("%s: unexpected record type: %s",
+ myname, dns_strtype(rr->type));
+ }
+ if (*p > 0) {
+ stripped_list = dns_rr_append(stripped_list, rr);
+ *p -= 1;
+ } else {
+ dns_rr_free(rr);
+ }
+ }
+ if (v4_target > 0 || v6_target > 0)
+ msg_panic("%s: bad target count: v4_target=%d, v6_target=%d",
+ myname, v4_target, v6_target);
+ if (msg_verbose)
+ smtp_print_addr("smtp_balance_inet_proto result", stripped_list);
+ return (stripped_list);
+ } else {
+ return (addr_list);
+ }
+}
+
+/* smtp_domain_addr - mail exchanger address lookup */
+
+DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags,
+ DSN_BUF *why, int *found_myself)
+{
+ DNS_RR *mx_names;
+ DNS_RR *addr_list = 0;
+ DNS_RR *self = 0;
+ unsigned best_pref;
+ unsigned best_found;
+ int r = 0; /* Resolver flags */
+ const char *aname;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Preferences from DNS use 0..32767, fall-backs use 32768+.
+ */
+#define IMPOSSIBLE_PREFERENCE (~0)
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_dns_support == SMTP_DNS_DISABLED)
+ msg_panic("smtp_domain_addr: DNS lookup is disabled");
+ if (smtp_dns_support == SMTP_DNS_DNSSEC)
+ r |= RES_USE_DNSSEC;
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ } else
+#endif
+ aname = name;
+
+ /*
+ * Look up the mail exchanger hosts listed for this name. Sort the
+ * results by preference. Look up the corresponding host addresses, and
+ * truncate the list so that it contains only hosts that are more
+ * preferred than myself. When no MX resource records exist, look up the
+ * addresses listed for this name.
+ *
+ * According to RFC 974: "It is possible that the list of MXs in the
+ * response to the query will be empty. This is a special case. If the
+ * list is empty, mailers should treat it as if it contained one RR, an
+ * MX RR with a preference value of 0, and a host name of REMOTE. (I.e.,
+ * REMOTE is its only MX). In addition, the mailer should do no further
+ * processing on the list, but should attempt to deliver the message to
+ * REMOTE."
+ *
+ * Normally it is OK if an MX host cannot be found in the DNS; we'll just
+ * use a backup one, and silently ignore the better MX host. However, if
+ * the best backup that we can find in the DNS is the local machine, then
+ * we must remember that the local machine is not the primary MX host, or
+ * else we will claim that mail loops back.
+ *
+ * XXX Optionally do A lookups even when the MX lookup didn't complete.
+ * Unfortunately with some DNS servers this is not a transient problem.
+ *
+ * XXX Ideally we would perform A lookups only as far as needed. But as long
+ * as we're looking up all the hosts, it would be better to look up the
+ * least preferred host first, so that DNS lookup error messages make
+ * more sense.
+ *
+ * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
+ * hosts, whereas multiple A records per hostname must be used in the
+ * order as received. They make the bogus assumption that a hostname with
+ * multiple A records corresponds to one machine with multiple network
+ * interfaces.
+ *
+ * XXX 2821: Postfix recognizes the local machine by looking for its own IP
+ * address in the list of mail exchangers. RFC 2821 says one has to look
+ * at the mail exchanger hostname as well, making the bogus assumption
+ * that an IP address is listed only under one hostname. However, looking
+ * at hostnames provides a partial solution for MX hosts behind a NAT
+ * gateway.
+ */
+ switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) {
+ default:
+ dsb_status(why, "4.4.3");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_INVAL:
+ dsb_status(why, "5.4.4");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_NULLMX:
+ dsb_status(why, "5.1.0");
+ break;
+ case DNS_POLICY:
+ dsb_status(why, "4.7.0");
+ break;
+ case DNS_FAIL:
+ dsb_status(why, "5.4.3");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_OK:
+ mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
+ best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
+ addr_list = smtp_addr_list(mx_names, why);
+ if (mxrr)
+ *mxrr = dns_rr_copy(mx_names); /* copies one record! */
+ dns_rr_free(mx_names);
+ if (addr_list == 0) {
+ /* Text does not change. */
+ if (var_smtp_defer_mxaddr) {
+ /* Don't clobber the null terminator. */
+ if (SMTP_HAS_HARD_DSN(why))
+ SMTP_SET_SOFT_DSN(why); /* XXX */
+ /* Require some error status. */
+ else if (!SMTP_HAS_SOFT_DSN(why))
+ msg_panic("smtp_domain_addr: bad status");
+ }
+ msg_warn("no MX host for %s has a valid address record", name);
+ break;
+ }
+ best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
+ if (msg_verbose)
+ smtp_print_addr(name, addr_list);
+ if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ && (self = smtp_find_self(addr_list)) != 0) {
+ addr_list = smtp_truncate_self(addr_list, self->pref);
+ if (addr_list == 0) {
+ if (best_pref != best_found) {
+ dsb_simple(why, "4.4.4",
+ "unable to find primary relay for %s", name);
+ } else {
+ dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
+ name);
+ }
+ }
+ }
+#define SMTP_COMPARE_ADDR(flags) \
+ (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
+ ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
+ dns_rr_compare_pref_any)
+
+ if (addr_list && addr_list->next) {
+ if (var_smtp_rand_addr)
+ addr_list = dns_rr_shuffle(addr_list);
+ addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
+ if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
+ addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
+ var_smtp_mxaddr_limit);
+ }
+ break;
+ case DNS_NOTFOUND:
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ *found_myself |= (self != 0);
+ return (addr_list);
+}
+
+/* smtp_host_addr - direct host lookup */
+
+DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
+{
+ DNS_RR *addr_list;
+ int res_opt = 0;
+ const char *ahost;
+
+ dsb_reset(why); /* Paranoia */
+
+ if (smtp_dns_support == SMTP_DNS_DNSSEC)
+ res_opt |= RES_USE_DNSSEC;
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", host, ahost);
+ } else
+#endif
+ ahost = host;
+
+ /*
+ * If the host is specified by numerical address, just convert the
+ * address to internal form. Otherwise, the host is specified by name.
+ */
+#define PREF0 0
+ addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why);
+ if (addr_list
+ && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ && smtp_find_self(addr_list) != 0) {
+ dns_rr_free(addr_list);
+ dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
+ return (0);
+ }
+ if (addr_list && addr_list->next) {
+ if (var_smtp_rand_addr)
+ addr_list = dns_rr_shuffle(addr_list);
+ /* The following changes the order of equal-preference hosts. */
+ if (inet_proto_info()->ai_family_list[1] != 0)
+ addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
+ if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
+ addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
+ var_smtp_mxaddr_limit);
+ }
+ if (msg_verbose)
+ smtp_print_addr(host, addr_list);
+ return (addr_list);
+}
diff --git a/src/smtp/smtp_addr.h b/src/smtp/smtp_addr.h
new file mode 100644
index 0000000..8f20961
--- /dev/null
+++ b/src/smtp/smtp_addr.h
@@ -0,0 +1,31 @@
+/*++
+/* NAME
+/* smtp_addr 3h
+/* SUMMARY
+/* SMTP server address lookup
+/* SYNOPSIS
+/* #include "smtp_addr.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+ /*
+ * Internal interfaces.
+ */
+extern DNS_RR *smtp_host_addr(const char *, int, DSN_BUF *);
+extern DNS_RR *smtp_domain_addr(const char *, DNS_RR **, int, DSN_BUF *, int *);
+
+/* 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
+/*--*/
diff --git a/src/smtp/smtp_chat.c b/src/smtp/smtp_chat.c
new file mode 100644
index 0000000..81c63e4
--- /dev/null
+++ b/src/smtp/smtp_chat.c
@@ -0,0 +1,503 @@
+/*++
+/* NAME
+/* smtp_chat 3
+/* SUMMARY
+/* SMTP client request/response support
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* typedef struct {
+/* .in +4
+/* int code; /* SMTP code, not sanitized */
+/* char *dsn; /* enhanced status, sanitized */
+/* char *str; /* unmodified SMTP reply */
+/* VSTRING *dsn_buf;
+/* VSTRING *str_buf;
+/* .in -4
+/* } SMTP_RESP;
+/*
+/* void smtp_chat_cmd(session, format, ...)
+/* SMTP_SESSION *session;
+/* const char *format;
+/*
+/* DICT *smtp_chat_resp_filter;
+/*
+/* SMTP_RESP *smtp_chat_resp(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_notify(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_init(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_reset(session)
+/* SMTP_SESSION *session;
+/* DESCRIPTION
+/* This module implements SMTP client support for request/reply
+/* conversations, and maintains a limited SMTP transaction log.
+/*
+/* smtp_chat_cmd() formats a command and sends it to an SMTP server.
+/* Optionally, the command is logged.
+/*
+/* smtp_chat_resp() reads one SMTP server response. It extracts
+/* the SMTP reply code and enhanced status code from the text,
+/* and concatenates multi-line responses to one string, using
+/* a newline as separator. Optionally, the server response
+/* is logged.
+/* .IP \(bu
+/* Postfix never sanitizes the extracted SMTP reply code except
+/* to ensure that it is a three-digit code. A malformed reply
+/* results in a null extracted SMTP reply code value.
+/* .IP \(bu
+/* Postfix always sanitizes the extracted enhanced status code.
+/* When the server's SMTP status code is 2xx, 4xx or 5xx,
+/* Postfix requires that the first digit of the server's
+/* enhanced status code matches the first digit of the server's
+/* SMTP status code. In case of a mis-match, or when the
+/* server specified no status code, the extracted enhanced
+/* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With
+/* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted
+/* enhanced status code is set to a default value of 5.5.0
+/* (protocol error) for reasons outlined under the next bullet.
+/* .IP \(bu
+/* Since the SMTP reply code may violate the protocol even
+/* when it is correctly formatted, Postfix uses the sanitized
+/* extracted enhanced status code to decide whether an error
+/* condition is permanent or transient. This means that the
+/* caller may have to update the enhanced status code when it
+/* discovers that a server reply violates the SMTP protocol,
+/* even though it was correctly formatted. This happens when
+/* the client and server get out of step due to a broken proxy
+/* agent.
+/* .PP
+/* smtp_chat_resp_filter specifies an optional filter to
+/* transform one server reply line before it is parsed. The
+/* filter is invoked once for each line of a multi-line reply.
+/*
+/* smtp_chat_notify() sends a copy of the SMTP transaction log
+/* to the postmaster for review. The postmaster notice is sent only
+/* when delivery is possible immediately. It is an error to call
+/* smtp_chat_notify() when no SMTP transaction log exists.
+/*
+/* smtp_chat_init() initializes the per-session transaction log.
+/* This must be done at the beginning of a new SMTP session.
+/*
+/* smtp_chat_reset() resets the transaction log. This is
+/* typically done at the beginning or end of an SMTP session,
+/* or within a session to discard non-error information.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, server response exceeds
+/* configurable limit.
+/* All other exceptions are handled by long jumps (see smtp_stream(3)).
+/* SEE ALSO
+/* smtp_stream(3) SMTP session I/O support
+/* msg(3) generic logging interface
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <stringops.h>
+#include <line_wrap.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <smtp_stream.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <post_mail.h>
+#include <mail_error.h>
+#include <dsn_util.h>
+#include <hfrom_format.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+ /*
+ * Server reply transformations.
+ */
+DICT *smtp_chat_resp_filter;
+
+/* smtp_chat_init - initialize SMTP transaction log */
+
+void smtp_chat_init(SMTP_SESSION *session)
+{
+ session->history = 0;
+}
+
+/* smtp_chat_reset - reset SMTP transaction log */
+
+void smtp_chat_reset(SMTP_SESSION *session)
+{
+ if (session->history) {
+ argv_free(session->history);
+ session->history = 0;
+ }
+}
+
+/* smtp_chat_append - append record to SMTP transaction log */
+
+static void smtp_chat_append(SMTP_SESSION *session, const char *direction,
+ const char *data)
+{
+ char *line;
+
+ if (session->history == 0)
+ session->history = argv_alloc(10);
+ line = concatenate(direction, data, (char *) 0);
+ argv_add(session->history, line, (char *) 0);
+ myfree(line);
+}
+
+/* smtp_chat_cmd - send an SMTP command */
+
+void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Format the command, and update the transaction log.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(session->buffer, fmt, ap);
+ va_end(ap);
+ smtp_chat_append(session, "Out: ", STR(session->buffer));
+
+ /*
+ * Optionally log the command first, so we can see in the log what the
+ * program is trying to do.
+ */
+ if (msg_verbose)
+ msg_info("> %s: %s", session->namaddrport, STR(session->buffer));
+
+ /*
+ * Send the command to the SMTP server.
+ */
+ smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream);
+
+ /*
+ * Force flushing of output does not belong here. It is done in the
+ * smtp_loop() main protocol loop when reading the server response, and
+ * in smtp_helo() when reading the EHLO response after sending the EHLO
+ * command.
+ *
+ * If we do forced flush here, then we must longjmp() on error, and a
+ * matching "prepare for disaster" error handler must be set up before
+ * every smtp_chat_cmd() call.
+ */
+#if 0
+
+ /*
+ * Flush unsent data to avoid timeouts after slow DNS lookups.
+ */
+ if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
+ vstream_fflush(session->stream);
+
+ /*
+ * Abort immediately if the connection is broken.
+ */
+ if (vstream_ftimeout(session->stream))
+ vstream_longjmp(session->stream, SMTP_ERR_TIME);
+ if (vstream_ferror(session->stream))
+ vstream_longjmp(session->stream, SMTP_ERR_EOF);
+#endif
+}
+
+/* smtp_chat_resp - read and process SMTP server response */
+
+SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
+{
+ static SMTP_RESP rdata;
+ char *cp;
+ int last_char;
+ int three_digs = 0;
+ size_t len;
+ const char *new_reply;
+ int chat_append_flag;
+ int chat_append_skipped = 0;
+
+ /*
+ * Initialize the response data buffer.
+ */
+ if (rdata.str_buf == 0) {
+ rdata.dsn_buf = vstring_alloc(10);
+ rdata.str_buf = vstring_alloc(100);
+ }
+
+ /*
+ * Censor out non-printable characters in server responses. Concatenate
+ * multi-line server responses. Separate the status code from the text.
+ * Leave further parsing up to the application.
+ *
+ * We can't parse or store input that exceeds var_line_limit, so we just
+ * skip over it to simplify the remainder of the code below.
+ */
+ VSTRING_RESET(rdata.str_buf);
+ for (;;) {
+ last_char = smtp_get(session->buffer, session->stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
+ /* XXX Update the per-line time limit. */
+ printable(STR(session->buffer), '?');
+ if (last_char != '\n')
+ msg_warn("%s: response longer than %d: %.30s...",
+ session->namaddrport, var_line_limit, STR(session->buffer));
+ if (msg_verbose)
+ msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));
+
+ /*
+ * Defend against a denial of service attack by limiting the amount
+ * of multi-line text that we are willing to store.
+ */
+ chat_append_flag = (LEN(rdata.str_buf) < var_line_limit);
+ if (chat_append_flag)
+ smtp_chat_append(session, "In: ", STR(session->buffer));
+ else {
+ if (chat_append_skipped == 0)
+ msg_warn("%s: multi-line response longer than %d %.30s...",
+ session->namaddrport, var_line_limit, STR(rdata.str_buf));
+ if (chat_append_skipped < INT_MAX)
+ chat_append_skipped++;
+ }
+
+ /*
+ * Server reply substitution, for fault-injection testing, or for
+ * working around broken systems. Use with care.
+ */
+ if (smtp_chat_resp_filter != 0) {
+ new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer));
+ if (new_reply != 0) {
+ msg_info("%s: replacing server reply \"%s\" with \"%s\"",
+ session->namaddrport, STR(session->buffer), new_reply);
+ vstring_strcpy(session->buffer, new_reply);
+ if (chat_append_flag) {
+ smtp_chat_append(session, "Replaced-by: ", "");
+ smtp_chat_append(session, " ", new_reply);
+ }
+ } else if (smtp_chat_resp_filter->error != 0) {
+ msg_warn("%s: table %s:%s lookup error for %s",
+ session->state->request->queue_id,
+ smtp_chat_resp_filter->type,
+ smtp_chat_resp_filter->name,
+ printable(STR(session->buffer), '?'));
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ }
+ }
+ if (chat_append_flag) {
+ if (LEN(rdata.str_buf))
+ VSTRING_ADDCH(rdata.str_buf, '\n');
+ vstring_strcat(rdata.str_buf, STR(session->buffer));
+ }
+
+ /*
+ * Parse into code and text. Do not ignore garbage (see below).
+ */
+ for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
+ /* void */ ;
+ if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
+ if (*cp == '-')
+ continue;
+ if (*cp == ' ' || *cp == 0)
+ break;
+ }
+
+ /*
+ * XXX Do not simply ignore garbage in the server reply when ESMTP
+ * command pipelining is turned on. For example, after sending
+ * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
+ * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
+ * as the END-OF-DATA reply after garbage, causing mail to be lost.
+ *
+ * Without the ability to store per-domain status information in queue
+ * files, automatic workarounds are problematic:
+ *
+ * - Automatically deferring delivery creates a "repeated delivery"
+ * problem when garbage arrives after the DATA stage. Without the
+ * workaround, Postfix delivers only once.
+ *
+ * - Automatically deferring delivery creates a "no delivery" problem
+ * when the garbage arrives before the DATA stage. Without the
+ * workaround, mail might still get through.
+ *
+ * - Automatically turning off pipelining for delayed mail affects
+ * deliveries to correctly implemented servers, and may also affect
+ * delivery of large mailing lists.
+ *
+ * So we leave the decision with the administrator, but we don't force
+ * them to take action, like we would with automatic deferral. If
+ * loss of mail is not acceptable then they can turn off pipelining
+ * for specific sites, or they can turn off pipelining globally when
+ * they find that there are just too many broken sites.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol
+ * error. The protocol may be in a bad state. Disable caching here so
+ * that the protocol engine will send QUIT.
+ */
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ if (session->features & SMTP_FEATURE_PIPELINING) {
+ msg_warn("%s: non-%s response from %s: %.100s",
+ session->state->request->queue_id,
+ smtp_mode ? "ESMTP" : "LMTP",
+ session->namaddrport, STR(session->buffer));
+ if (var_helpful_warnings)
+ msg_warn("to prevent loss of mail, turn off command pipelining "
+ "for %s with the %s parameter",
+ STR(session->iterator->addr),
+ VAR_LMTP_SMTP(EHLO_DIS_MAPS));
+ }
+ }
+
+ /*
+ * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
+ * code if none was given.
+ *
+ * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
+ * replies, or codes whose initial digit is out of sync with the reply
+ * code.
+ *
+ * XXX Potential stability problem. In order to save memory, the queue
+ * manager stores DSNs in a compact manner:
+ *
+ * - empty strings are represented by null pointers,
+ *
+ * - the status and reason are required to be non-empty.
+ *
+ * Other Postfix daemons inherit this behavior, because they use the same
+ * DSN support code. This means that everything that receives DSNs must
+ * cope with null pointers for the optional DSN attributes, and that
+ * everything that provides DSN information must provide a non-empty
+ * status and reason, otherwise the DSN support code wil panic().
+ *
+ * Thus, when the remote server sends a malformed reply (or 3XX out of
+ * context) we should not panic() in DSN_COPY() just because we don't
+ * have a status. Robustness suggests that we supply a status here, and
+ * that we leave it up to the down-stream code to override the
+ * server-supplied status in case of an error we can't detect here, such
+ * as an out-of-order server reply.
+ */
+ VSTRING_TERMINATE(rdata.str_buf);
+ vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */
+ if (three_digs != 0) {
+ rdata.code = atoi(STR(session->buffer));
+ if (strchr("245", STR(session->buffer)[0]) != 0) {
+ for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
+ /* void */ ;
+ if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
+ vstring_strncpy(rdata.dsn_buf, cp, len);
+ } else {
+ vstring_strcpy(rdata.dsn_buf, "0.0.0");
+ STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
+ }
+ }
+ } else {
+ rdata.code = 0;
+ }
+ rdata.dsn = STR(rdata.dsn_buf);
+ rdata.str = STR(rdata.str_buf);
+ return (&rdata);
+}
+
+/* print_line - line_wrap callback */
+
+static void print_line(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *notice = (VSTREAM *) context;
+
+ post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
+}
+
+/* smtp_chat_notify - notify postmaster */
+
+void smtp_chat_notify(SMTP_SESSION *session)
+{
+ const char *myname = "smtp_chat_notify";
+ VSTREAM *notice;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (session->history == 0)
+ msg_panic("%s: no conversation history", myname);
+ if (msg_verbose)
+ msg_info("%s: notify postmaster", myname);
+
+ /*
+ * Construct a message for the postmaster, explaining what this is all
+ * about. This is junk mail: don't send it when the mail posting service
+ * is unavailable, and use the double bounce sender address, to prevent
+ * mail bounce wars. Always prepend one space to message content that we
+ * generate from untrusted data.
+ */
+#define NULL_TRACE_FLAGS 0
+#define NO_QUEUE_ID ((VSTRING *) 0)
+#define LENGTH 78
+#define INDENT 4
+
+ notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_error_rcpt,
+ MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS,
+ SMTPUTF8_FLAG_NONE, NO_QUEUE_ID);
+ if (notice == 0) {
+ msg_warn("postmaster notify: %m");
+ return;
+ }
+ if (smtp_hfrom_format == HFROM_FORMAT_CODE_STD) {
+ post_mail_fprintf(notice, "From: Mail Delivery System <%s>",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt);
+ } else {
+ post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
+ }
+ post_mail_fprintf(notice, "Subject: %s %s client: errors from %s",
+ var_mail_name, smtp_mode ? "SMTP" : "LMTP",
+ session->namaddrport);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "Unexpected response from %s.",
+ session->namaddrport);
+ post_mail_fputs(notice, "");
+ post_mail_fputs(notice, "Transcript of session follows.");
+ post_mail_fputs(notice, "");
+ argv_terminate(session->history);
+ for (cpp = session->history->argv; *cpp; cpp++)
+ line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
+ (void *) notice);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "For other details, see the local mail logfile");
+ (void) post_mail_fclose(notice);
+}
diff --git a/src/smtp/smtp_connect.c b/src/smtp/smtp_connect.c
new file mode 100644
index 0000000..ed58180
--- /dev/null
+++ b/src/smtp/smtp_connect.c
@@ -0,0 +1,1231 @@
+/*++
+/* NAME
+/* smtp_connect 3
+/* SUMMARY
+/* connect to SMTP/LMTP server and deliver
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_connect(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* This module implements SMTP/LMTP connection management and controls
+/* mail delivery.
+/*
+/* smtp_connect() attempts to establish an SMTP/LMTP session with a host
+/* that represents the destination domain, or with an optional fallback
+/* relay when {the destination cannot be found, or when all the
+/* destination servers are unavailable}. It skips over IP addresses
+/* that fail to complete the SMTP/LMTP handshake and tries to find
+/* an alternate server when an SMTP/LMTP session fails to deliver.
+/*
+/* This layer also controls what connections are retrieved from
+/* the connection cache, and what connections are saved to the cache.
+/*
+/* The destination is either a host (or domain) name or a numeric
+/* address. Symbolic or numeric service port information may be
+/* appended, separated by a colon (":"). In the case of LMTP,
+/* destinations may be specified as "unix:pathname", "inet:host"
+/* or "inet:host:port".
+/*
+/* With SMTP, the Internet domain name service is queried for mail
+/* exchanger hosts. Quote the domain name with `[' and `]' to
+/* suppress mail exchanger lookups.
+/*
+/* Numerical address information should always be quoted with `[]'.
+/* DIAGNOSTICS
+/* The delivery status is the result value.
+/* SEE ALSO
+/* smtp_proto(3) SMTP client protocol
+/* 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
+/*
+/* Connection caching in cooperation with:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#ifndef IPPORT_SMTP
+#define IPPORT_SMTP 25
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <iostuff.h>
+#include <timed_connect.h>
+#include <stringops.h>
+#include <host_port.h>
+#include <sane_connect.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <known_tcp_ports.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <deliver_pass.h>
+#include <mail_error.h>
+#include <dsn_buf.h>
+#include <mail_addr.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+#include <smtp_addr.h>
+#include <smtp_reuse.h>
+
+ /*
+ * Forward declaration.
+ */
+static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int,
+ SMTP_ITERATOR *, DSN_BUF *,
+ int);
+
+/* smtp_connect_unix - connect to UNIX-domain address */
+
+static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why,
+ int sess_flags)
+{
+ const char *myname = "smtp_connect_unix";
+ struct sockaddr_un sock_un;
+ const char *addr = STR(iter->addr);
+ int len = strlen(addr);
+ int sock;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (len >= (int) sizeof(sock_un.sun_path)) {
+ msg_warn("unix-domain name too long: %s", addr);
+ dsb_simple(why, "4.3.5", "Server configuration error");
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ memset((void *) &sock_un, 0, sizeof(sock_un));
+ sock_un.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sock_un.sun_len = len + 1;
+#endif
+ memcpy(sock_un.sun_path, addr, len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+ /*
+ * Connect to the server.
+ */
+ if (msg_verbose)
+ msg_info("%s: trying: %s...", myname, addr);
+
+ return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un,
+ sizeof(sock_un), iter, why, sess_flags));
+}
+
+/* smtp_connect_addr - connect to explicit address */
+
+static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why,
+ int sess_flags)
+{
+ const char *myname = "smtp_connect_addr";
+ struct sockaddr_storage ss; /* remote */
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE salen = sizeof(ss);
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *addr = iter->rr;
+ unsigned port = iter->port;
+ int sock;
+ char *bind_addr;
+ char *bind_var;
+ char *saved_bind_addr = 0;
+ char *tail;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (dns_rr_to_sa(addr, port, sa, &salen) != 0) {
+ msg_warn("%s: skip address type %s: %m",
+ myname, dns_strtype(addr->type));
+ dsb_simple(why, "4.4.0", "network address conversion failed: %m");
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+#define RETURN_EARLY() do { \
+ if (saved_bind_addr) \
+ myfree(saved_bind_addr); \
+ (void) close(sock); \
+ return (0); \
+ } while (0)
+
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+
+ /*
+ * Allow the sysadmin to specify the source address, for example, as "-o
+ * smtp_bind_address=x.x.x.x" in the master.cf file.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ bind_addr = var_smtp_bind_addr6;
+ bind_var = VAR_LMTP_SMTP(BIND_ADDR6);
+ } else
+#endif
+ if (sa->sa_family == AF_INET) {
+ bind_addr = var_smtp_bind_addr;
+ bind_var = VAR_LMTP_SMTP(BIND_ADDR);
+ } else
+ bind_var = bind_addr = "";
+ if (*bind_addr) {
+ int aierr;
+ struct addrinfo *res0;
+
+ if (*bind_addr == '[') {
+ saved_bind_addr = mystrdup(bind_addr + 1);
+ if ((tail = split_at(saved_bind_addr, ']')) == 0 || *tail)
+ msg_fatal("%s: malformed %s parameter: %s",
+ myname, bind_var, bind_addr);
+ bind_addr = saved_bind_addr;
+ }
+ if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0)
+ msg_fatal("%s: bad %s parameter: %s: %s",
+ myname, bind_var, bind_addr, MAI_STRERROR(aierr));
+ if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) {
+ msg_warn("%s: bind %s: %m", myname, bind_addr);
+ if (var_smtp_bind_addr_enforce) {
+ freeaddrinfo(res0);
+ dsb_simple(why, "4.4.0", "server configuration error");
+ RETURN_EARLY();
+ }
+ } else if (msg_verbose)
+ msg_info("%s: bind %s", myname, bind_addr);
+ if (saved_bind_addr)
+ myfree(saved_bind_addr);
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * When running as a virtual host, bind to the virtual interface so that
+ * the mail appears to come from the "right" machine address.
+ *
+ * XXX The IPv6 patch expands the null host (as client endpoint) and uses
+ * the result as the loopback address list.
+ */
+ else {
+ int count = 0;
+ struct sockaddr *own_addr = 0;
+ INET_ADDR_LIST *addr_list = own_inet_addr_list();
+ struct sockaddr_storage *s;
+
+ for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) {
+ if (SOCK_ADDR_FAMILY(s) == sa->sa_family) {
+ if (count++ > 0)
+ break;
+ own_addr = SOCK_ADDR_PTR(s);
+ }
+ }
+ if (count == 1 && !sock_addr_in_loopback(own_addr)) {
+ if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) {
+ SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_warn("%s: bind %s: %m", myname, hostaddr.buf);
+ } else if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: bind %s", myname, hostaddr.buf);
+ }
+ }
+ }
+
+ /*
+ * Connect to the server.
+ */
+ if (msg_verbose)
+ msg_info("%s: trying: %s[%s] port %d...",
+ myname, STR(iter->host), STR(iter->addr), ntohs(port));
+
+ return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags));
+}
+
+/* smtp_connect_sock - connect a socket over some transport */
+
+static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa,
+ int salen,
+ SMTP_ITERATOR *iter,
+ DSN_BUF *why,
+ int sess_flags)
+{
+ int conn_stat;
+ int saved_errno;
+ VSTREAM *stream;
+ time_t start_time;
+ const char *name = STR(iter->host);
+ const char *addr = STR(iter->addr);
+ unsigned port = iter->port;
+
+ start_time = time((time_t *) 0);
+ if (var_smtp_conn_tmout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout);
+ saved_errno = errno;
+ non_blocking(sock, BLOCKING);
+ errno = saved_errno;
+ } else {
+ conn_stat = sane_connect(sock, sa, salen);
+ }
+ if (conn_stat < 0) {
+ if (port)
+ dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
+ name, addr, ntohs(port));
+ else
+ dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
+ close(sock);
+ return (0);
+ }
+ stream = vstream_fdopen(sock, O_RDWR);
+
+ /*
+ * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+ */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )
+ vstream_tweak_tcp(stream);
+
+ /*
+ * Bundle up what we have into a nice SMTP_SESSION object.
+ */
+ return (smtp_session_alloc(stream, iter, start_time, sess_flags));
+}
+
+/* smtp_parse_destination - parse host/port destination */
+
+static char *smtp_parse_destination(char *destination, char *def_service,
+ char **hostp, unsigned *portp)
+{
+ char *buf = mystrdup(destination);
+ char *service;
+ struct servent *sp;
+ char *protocol = "tcp"; /* XXX configurable? */
+ unsigned port;
+ const char *err;
+
+ if (msg_verbose)
+ msg_info("smtp_parse_destination: %s %s", destination, def_service);
+
+ /*
+ * Parse the host/port information. We're working with a copy of the
+ * destination argument so the parsing can be destructive.
+ */
+ if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0)
+ msg_fatal("%s in server description: %s", err, destination);
+
+ /*
+ * Convert service to port number, network byte order.
+ */
+ service = (char *) filter_known_tcp_port(service);
+ if (alldig(service)) {
+ if ((port = atoi(service)) >= 65536 || port == 0)
+ msg_fatal("bad network port: %s for destination: %s",
+ service, destination);
+ *portp = htons(port);
+ } else {
+ if ((sp = getservbyname(service, protocol)) == 0)
+ msg_fatal("unknown service: %s/%s", service, protocol);
+ *portp = sp->s_port;
+ }
+ return (buf);
+}
+
+/* smtp_cleanup_session - clean up after using a session */
+
+static void smtp_cleanup_session(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ int throttled;
+
+ /*
+ * Inform the postmaster of trouble.
+ *
+ * XXX Don't send notifications about errors while sending notifications.
+ */
+#define POSSIBLE_NOTIFICATION(sender) \
+ (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0)
+
+ if (session->history != 0
+ && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES,
+ mail_error_masks,
+ var_notify_classes)) != 0
+ && POSSIBLE_NOTIFICATION(request->sender) == 0)
+ smtp_chat_notify(session);
+
+ /*
+ * When session caching is enabled, cache the first good session for this
+ * delivery request under the next-hop destination, and cache all good
+ * sessions under their server network address (destroying the session in
+ * the process).
+ *
+ * Caching under the next-hop destination name (rather than the fall-back
+ * destination) allows us to skip over non-responding primary or backup
+ * hosts. In fact, this is the only benefit of caching logical to
+ * physical bindings; caching a session under its own hostname provides
+ * no performance benefit, given the way smtp_connect() works.
+ */
+ throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */
+ if (THIS_SESSION_IS_EXPIRED)
+ smtp_quit(state); /* also disables caching */
+ if (THIS_SESSION_IS_CACHED
+ /* Redundant tests for safety... */
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0) {
+ smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL);
+ } else {
+ smtp_session_free(session);
+ }
+ state->session = 0;
+
+ /*
+ * If this session was good, reset the scache next-hop destination, so
+ * that we won't cache connections to less-preferred servers under the
+ * same next-hop destination. Otherwise we could end up skipping over the
+ * available and more-preferred servers.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled)
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+
+ /*
+ * Clean up the lists with todo and dropped recipients.
+ */
+ smtp_rcpt_cleanup(state);
+
+ /*
+ * Reset profiling info.
+ *
+ * XXX When one delivery request results in multiple sessions, the set-up
+ * and transmission latencies of the earlier sessions will count as
+ * connection set-up time for the later sessions.
+ *
+ * XXX On the other hand, when we first try to connect to one or more dead
+ * hosts before we reach a good host, then all that time must be counted
+ * as connection set-up time for the session with the good host.
+ *
+ * XXX So this set-up attribution problem exists only when we actually
+ * engage in a session, spend a lot of time delivering a message, find
+ * that it fails, and then connect to an alternate host.
+ */
+ memset((void *) &request->msg_stats.conn_setup_done, 0,
+ sizeof(request->msg_stats.conn_setup_done));
+ memset((void *) &request->msg_stats.deliver_done, 0,
+ sizeof(request->msg_stats.deliver_done));
+ request->msg_stats.reuse_count = 0;
+}
+
+static void smtp_cache_policy(SMTP_STATE *state, const char *dest)
+{
+ DELIVER_REQUEST *request = state->request;
+
+ state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK;
+
+ if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) {
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK;
+ } else if (var_smtp_cache_demand) {
+ if (request->flags & DEL_REQ_FLAG_CONN_LOAD)
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD;
+ if (request->flags & DEL_REQ_FLAG_CONN_STORE)
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE;
+ }
+}
+
+/* smtp_connect_local - connect to local server */
+
+static void smtp_connect_local(SMTP_STATE *state, const char *path)
+{
+ const char *myname = "smtp_connect_local";
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_SESSION *session;
+ DSN_BUF *why = state->why;
+
+ /*
+ * Do not silently ignore an unused setting.
+ */
+ if (*var_fallback_relay)
+ msg_warn("ignoring \"%s = %s\" setting for non-TCP connections",
+ VAR_LMTP_FALLBACK, var_fallback_relay);
+
+ /*
+ * It's too painful to weave this code into the SMTP connection
+ * management routine.
+ *
+ * Connection cache management is based on the UNIX-domain pathname, without
+ * the "unix:" prefix.
+ */
+ smtp_cache_policy(state, path);
+ if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
+ SET_SCACHE_REQUEST_NEXTHOP(state, path);
+
+ /*
+ * Here we ensure that the iter->addr member refers to a copy of the
+ * UNIX-domain pathname, so that smtp_save_session() will cache the
+ * connection using the pathname as the physical endpoint name.
+ *
+ * We set dest=path for backwards compatibility.
+ */
+#define NO_PORT 0
+
+ SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state);
+
+ /*
+ * Opportunistic TLS for unix domain sockets does not make much sense,
+ * since the channel is private, mere encryption without authentication
+ * is just wasted cycles and opportunity for breakage. Since we are not
+ * willing to retry after TLS handshake failures here, we downgrade "may"
+ * no "none". Nothing is lost, and much waste is avoided.
+ *
+ * We don't know who is authenticating whom, so if a client cert is
+ * available, "encrypt" may be a sensible policy. Otherwise, we also
+ * downgrade "encrypt" to "none", this time just to avoid waste.
+ *
+ * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can
+ * reuse a SASL-authenticated connection (however unlikely this scenario
+ * may be). The smtp_reuse_addr() interface currently supports only reuse
+ * of SASL-unauthenticated connections.
+ */
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup error for %s/%s: %s",
+ STR(iter->host), STR(iter->addr), STR(why->reason));
+ return;
+ }
+#endif
+ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
+ || (session = smtp_reuse_nexthop(state,
+ SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0)
+ session = smtp_connect_unix(iter, why, state->misc_flags);
+ if ((state->session = session) != 0) {
+ session->state = state;
+#ifdef USE_TLS
+ session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */
+ if (state->tls->level == TLS_LEV_MAY) {
+ msg_warn("%s: opportunistic TLS encryption is not appropriate "
+ "for unix-domain destinations.", myname);
+ state->tls->level = TLS_LEV_NONE;
+ }
+#endif
+ /* All delivery errors bounce or defer. */
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+
+ /*
+ * When a TLS handshake fails, the stream is marked "dead" to avoid
+ * further I/O over a broken channel.
+ */
+ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
+ && smtp_helo(state) != 0) {
+ if (!THIS_SESSION_IS_FORBIDDEN
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ smtp_quit(state);
+ } else {
+ smtp_xfer(state);
+ }
+
+ /*
+ * With opportunistic TLS disabled we don't expect to be asked to
+ * retry connections without TLS, and so we expect the final server
+ * flag to stay on.
+ */
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0)
+ msg_panic("%s: unix-domain destination not final!", myname);
+ smtp_cleanup_session(state);
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+}
+
+/* smtp_scrub_address_list - delete all cached addresses from list */
+
+static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *addr;
+ DNS_RR *next;
+
+ /*
+ * XXX Extend the DNS_RR structure with fields for the printable address
+ * and/or binary sockaddr representations, so that we can avoid repeated
+ * binary->string transformations for the same address.
+ */
+ for (addr = *addr_list; addr; addr = next) {
+ next = addr->next;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ continue;
+ }
+ if (htable_locate(cached_addr, hostaddr.buf))
+ *addr_list = dns_rr_remove(*addr_list, addr);
+ }
+}
+
+/* smtp_update_addr_list - common address list update */
+
+static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr,
+ int session_count)
+{
+ DNS_RR *addr;
+ DNS_RR *next;
+ int aierr;
+ struct addrinfo *res0;
+
+ if (*addr_list == 0)
+ return;
+
+ /*
+ * Truncate the address list if we are not going to use it anyway.
+ */
+ if (session_count == var_smtp_mxsess_limit
+ || session_count == var_smtp_mxaddr_limit) {
+ dns_rr_free(*addr_list);
+ *addr_list = 0;
+ return;
+ }
+
+ /*
+ * Convert server address to internal form, and look it up in the address
+ * list.
+ *
+ * XXX smtp_reuse_session() breaks if we remove two or more adjacent list
+ * elements but do not truncate the list to zero length.
+ *
+ * XXX Extend the SMTP_SESSION structure with sockaddr information so that
+ * we can avoid repeated string->binary transformations for the same
+ * address.
+ */
+ if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) {
+ msg_warn("hostaddr_to_sockaddr %s: %s",
+ server_addr, MAI_STRERROR(aierr));
+ } else {
+ for (addr = *addr_list; addr; addr = next) {
+ next = addr->next;
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) {
+ *addr_list = dns_rr_remove(*addr_list, addr);
+ break;
+ }
+ }
+ freeaddrinfo(res0);
+ }
+}
+
+/* smtp_reuse_session - try to use existing connection, return session count */
+
+static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
+ int domain_best_pref)
+{
+ int session_count = 0;
+ DNS_RR *addr;
+ DNS_RR *next;
+ MAI_HOSTADDR_STR hostaddr;
+ SMTP_SESSION *session;
+ SMTP_ITERATOR *iter = state->iterator;
+ DSN_BUF *why = state->why;
+
+ /*
+ * First, search the cache by delivery request nexthop. We truncate the
+ * server address list when all the sessions for this destination are
+ * used up, to reduce the number of variables that need to be checked
+ * later.
+ *
+ * Note: connection reuse by delivery request nexthop restores the "best MX"
+ * bit.
+ *
+ * smtp_reuse_nexthop() clobbers the iterators's "dest" attribute. We save
+ * and restore it here, so that subsequent connections will use the
+ * proper nexthop information.
+ *
+ * We don't use TLS level info for nexthop-based connection cache storage
+ * keys. The combination of (service, nexthop, etc.) should be stable
+ * over the time range of interest, and the policy is still enforced on
+ * an individual connection to an MX host, before that connection is
+ * stored under a nexthop- or host-based storage key.
+ */
+#ifdef USE_TLS
+ smtp_tls_policy_dummy(state->tls);
+#endif
+ SMTP_ITER_SAVE_DEST(state->iterator);
+ if (*addr_list && SMTP_RCPT_LEFT(state) > 0
+ && HAVE_SCACHE_REQUEST_NEXTHOP(state)
+ && (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) {
+ session_count = 1;
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && *addr_list == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+ smtp_cleanup_session(state);
+ }
+ SMTP_ITER_RESTORE_DEST(state->iterator);
+
+ /*
+ * Second, search the cache by primary MX address. Again, we use address
+ * list truncation so that we have to check fewer variables later.
+ *
+ * XXX This loop is safe because smtp_update_addr_list() either truncates
+ * the list to zero length, or removes at most one list element.
+ *
+ * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated
+ * connections. Furthermore, we rely on smtp_reuse_addr() to look up an
+ * existing SASL-unauthenticated connection only when a new connection
+ * would be guaranteed not to require SASL authentication.
+ *
+ * In addition, we rely on smtp_reuse_addr() to look up an existing
+ * plaintext connection only when a new connection would be guaranteed
+ * not to use TLS.
+ *
+ * For more precise control over reuse, the iterator should look up SASL and
+ * TLS policy as it evaluates mail exchangers in order, instead of
+ * relying on duplicate lookup request code in smtp_reuse(3) and
+ * smtp_session(3).
+ */
+ for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
+ if (addr->pref != domain_best_pref)
+ break;
+ next = addr->next;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ /* XXX Assume there is no code at the end of this loop. */
+ continue;
+ }
+ vstring_strcpy(iter->addr, hostaddr.buf);
+ vstring_strcpy(iter->host, SMTP_HNAME(addr));
+ iter->rr = addr;
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup error for %s/%s: %s",
+ STR(iter->dest), STR(iter->host), STR(why->reason));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+#endif
+ if ((session = smtp_reuse_addr(state,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) {
+ session->features |= SMTP_FEATURE_BEST_MX;
+ session_count += 1;
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
+ if (*addr_list == 0)
+ next = 0;
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+ smtp_cleanup_session(state);
+ }
+ }
+ return (session_count);
+}
+
+/* smtp_connect_inet - establish network connection */
+
+static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
+ char *def_service)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_ITERATOR *iter = state->iterator;
+ ARGV *sites;
+ char *dest;
+ char **cpp;
+ int non_fallback_sites;
+ int retry_plain = 0;
+ DSN_BUF *why = state->why;
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0) {
+ dsb_simple(why, "4.4.4", "all network protocols are disabled");
+ return;
+ }
+
+ /*
+ * Do a null destination sanity check in case the primary destination is
+ * a list that consists of only separators.
+ */
+ sites = argv_split(nexthop, CHARS_COMMA_SP);
+ if (sites->argc == 0)
+ msg_panic("null destination: \"%s\"", nexthop);
+ non_fallback_sites = sites->argc;
+ argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP);
+
+ /*
+ * Don't give up after a hard host lookup error until we have tried the
+ * fallback relay servers.
+ *
+ * Don't bounce mail after a host lookup problem with a relayhost or with a
+ * fallback relay.
+ *
+ * Don't give up after a qualifying soft error until we have tried all
+ * qualifying backup mail servers.
+ *
+ * All this means that error handling and error reporting depends on whether
+ * the error qualifies for trying to deliver to a backup mail server, or
+ * whether we're looking up a relayhost or fallback relay. The challenge
+ * then is to build this into the pre-existing SMTP client without
+ * getting lost in the complexity.
+ */
+#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \
+ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites))
+
+ for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP);
+ SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0;
+ cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
+ char *dest_buf;
+ char *domain;
+ unsigned port;
+ DNS_RR *addr_list;
+ DNS_RR *addr;
+ DNS_RR *next;
+ int addr_count;
+ int sess_count;
+ SMTP_SESSION *session;
+ int lookup_mx;
+ unsigned domain_best_pref;
+ MAI_HOSTADDR_STR hostaddr;
+
+ if (cpp[1] == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+
+ /*
+ * Parse the destination. If no TCP port is specified, use the port
+ * that is reserved for the protocol (SMTP or LMTP).
+ */
+ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port);
+ if (var_helpful_warnings && var_smtp_tls_wrappermode == 0
+ && ntohs(port) == 465) {
+ msg_info("SMTPS wrappermode (TCP port 465) requires setting "
+ "\"%s = yes\", and \"%s = encrypt\" (or stronger)",
+ VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
+ }
+#define NO_HOST "" /* safety */
+#define NO_ADDR "" /* safety */
+
+ SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state);
+
+ /*
+ * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail
+ * exchanger lookups when a quoted host is specified or when DNS
+ * lookups are disabled.
+ */
+ if (msg_verbose)
+ msg_info("connecting to %s port %d", domain, ntohs(port));
+ if (smtp_mode) {
+ if (ntohs(port) == IPPORT_SMTP)
+ state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
+ else
+ state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
+ lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '[');
+ } else
+ lookup_mx = 0;
+ if (!lookup_mx) {
+ addr_list = smtp_host_addr(domain, state->misc_flags, why);
+ /* XXX We could be an MX host for this destination... */
+ } else {
+ int i_am_mx = 0;
+
+ addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags,
+ why, &i_am_mx);
+ /* If we're MX host, don't connect to non-MX backups. */
+ if (i_am_mx)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+ }
+
+ /*
+ * Don't try fall-back hosts if mail loops to myself. That would just
+ * make the problem worse.
+ */
+ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why))
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+
+ /*
+ * No early loop exit or we have a memory leak with dest_buf.
+ */
+ if (addr_list)
+ domain_best_pref = addr_list->pref;
+
+ /*
+ * When connection caching is enabled, store the first good
+ * connection for this delivery request under the delivery request
+ * next-hop name. Good connections will also be stored under their
+ * specific server IP address.
+ *
+ * XXX smtp_session_cache_destinations specifies domain names without
+ * :port, because : is already used for maptype:mapname. Because of
+ * this limitation we use the bare domain without the optional [] or
+ * non-default TCP port.
+ *
+ * Opportunistic (a.k.a. on-demand) session caching on request by the
+ * queue manager. This is turned temporarily when a destination has a
+ * high volume of mail in the active queue. When the surge reaches
+ * its end, the queue manager requests that connections be retrieved
+ * but not stored.
+ */
+ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
+ smtp_cache_policy(state, domain);
+ if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
+ SET_SCACHE_REQUEST_NEXTHOP(state, dest);
+ }
+
+ /*
+ * Delete visited cached hosts from the address list.
+ *
+ * Optionally search the connection cache by domain name or by primary
+ * MX address before we try to create new connections.
+ *
+ * Enforce the MX session and MX address counts per next-hop or
+ * fall-back destination. smtp_reuse_session() will truncate the
+ * address list when either limit is reached.
+ */
+ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) {
+ if (state->cache_used->used > 0)
+ smtp_scrub_addr_list(state->cache_used, &addr_list);
+ sess_count = addr_count =
+ smtp_reuse_session(state, &addr_list, domain_best_pref);
+ } else
+ sess_count = addr_count = 0;
+
+ /*
+ * Connect to an SMTP server: create primary MX connections, and
+ * reuse or create backup MX connections.
+ *
+ * At the start of an SMTP session, all recipients are unmarked. In the
+ * course of an SMTP session, recipients are marked as KEEP (deliver
+ * to alternate mail server) or DROP (remove from recipient list). At
+ * the end of an SMTP session, weed out the recipient list. Unmark
+ * any left-over recipients and try to deliver them to a backup mail
+ * server.
+ *
+ * Cache the first good session under the next-hop destination name.
+ * Cache all good sessions under their physical endpoint.
+ *
+ * Don't query the session cache for primary MX hosts. We already did
+ * that in smtp_reuse_session(), and if any were found in the cache,
+ * they were already deleted from the address list.
+ *
+ * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated
+ * connections. Furthermore, we rely on smtp_reuse_addr() to look up
+ * an existing SASL-unauthenticated connection only when a new
+ * connection would be guaranteed not to require SASL authentication.
+ *
+ * In addition, we rely on smtp_reuse_addr() to look up an existing
+ * plaintext connection only when a new connection would be
+ * guaranteed not to use TLS.
+ */
+ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
+ next = addr->next;
+ if (++addr_count == var_smtp_mxaddr_limit)
+ next = 0;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ /* XXX Assume there is no code at the end of this loop. */
+ continue;
+ }
+ vstring_strcpy(iter->addr, hostaddr.buf);
+ vstring_strcpy(iter->host, SMTP_HNAME(addr));
+ iter->rr = addr;
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup for %s/%s: %s",
+ STR(iter->dest), STR(iter->host), STR(why->reason));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+ if (var_smtp_tls_wrappermode
+ && state->tls->level < TLS_LEV_ENCRYPT) {
+ msg_warn("%s requires \"%s = encrypt\" (or stronger)",
+ VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+ /* Disable TLS when retrying after a handshake failure */
+ if (retry_plain) {
+ state->tls->level = TLS_LEV_NONE;
+ retry_plain = 0;
+ }
+#endif
+ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
+ || addr->pref == domain_best_pref
+ || !(session = smtp_reuse_addr(state,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL)))
+ session = smtp_connect_addr(iter, why, state->misc_flags);
+ if ((state->session = session) != 0) {
+ session->state = state;
+#ifdef USE_TLS
+ session->tls_nexthop = domain;
+#endif
+ if (addr->pref == domain_best_pref)
+ session->features |= SMTP_FEATURE_BEST_MX;
+ /* Don't count handshake errors towards the session limit. */
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
+ && smtp_helo(state) != 0) {
+#ifdef USE_TLS
+
+ /*
+ * When an opportunistic TLS handshake fails, try the
+ * same address again, with TLS disabled. See also the
+ * RETRY_AS_PLAINTEXT macro.
+ */
+ if ((retry_plain = session->tls_retry_plain) != 0) {
+ --addr_count;
+ next = addr;
+ }
+#endif
+
+ /*
+ * When a TLS handshake fails, the stream is marked
+ * "dead" to avoid further I/O over a broken channel.
+ */
+ if (!THIS_SESSION_IS_FORBIDDEN
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ smtp_quit(state);
+ } else {
+ /* Do count delivery errors towards the session limit. */
+ if (++sess_count == var_smtp_mxsess_limit)
+ next = 0;
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+#ifdef USE_TLS
+
+ /*
+ * When opportunistic TLS fails after the STARTTLS
+ * handshake, try the same address again, with TLS
+ * disabled. See also the RETRY_AS_PLAINTEXT macro.
+ */
+ if ((retry_plain = session->tls_retry_plain) != 0) {
+ --sess_count;
+ --addr_count;
+ next = addr;
+ }
+#endif
+ }
+ smtp_cleanup_session(state);
+ } else {
+ /* The reason already includes the IP address and TCP port. */
+ msg_info("%s", STR(why->reason));
+ }
+ /* XXX Code above assumes there is no code at this loop ending. */
+ }
+ dns_rr_free(addr_list);
+ if (iter->mx) {
+ dns_rr_free(iter->mx);
+ iter->mx = 0; /* Just in case */
+ }
+ myfree(dest_buf);
+ if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ break;
+ }
+
+ /*
+ * We still need to deliver, bounce or defer some left-over recipients:
+ * either mail loops or some backup mail server was unavailable.
+ */
+ if (SMTP_RCPT_LEFT(state) > 0) {
+
+ /*
+ * In case of a "no error" indication we make up an excuse: we did
+ * find the host address, but we did not attempt to connect to it.
+ * This can happen when the fall-back relay was already tried via a
+ * cached connection, so that the address list scrubber left behind
+ * an empty list.
+ */
+ if (!SMTP_HAS_DSN(why)) {
+ dsb_simple(why, "4.3.0",
+ "server unavailable or unable to receive mail");
+ }
+
+ /*
+ * Pay attention to what could be configuration problems, and pretend
+ * that these are recoverable rather than bouncing the mail.
+ */
+ else if (!SMTP_HAS_SOFT_DSN(why)) {
+
+ /*
+ * The fall-back destination did not resolve as expected, or it
+ * is refusing to talk to us, or mail for it loops back to us.
+ */
+ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) {
+ msg_warn("%s configuration problem", VAR_SMTP_FALLBACK);
+ vstring_strcpy(why->status, "4.3.5");
+ /* XXX Keep the diagnostic code and MTA. */
+ }
+
+ /*
+ * The next-hop relayhost did not resolve as expected, or it is
+ * refusing to talk to us, or mail for it loops back to us.
+ *
+ * XXX There is no equivalent safety net for mis-configured
+ * sender-dependent relay hosts. The trivial-rewrite resolver
+ * would have to flag the result, and the queue manager would
+ * have to provide that information to delivery agents.
+ */
+ else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) {
+ msg_warn("%s configuration problem", VAR_RELAYHOST);
+ vstring_strcpy(why->status, "4.3.5");
+ /* XXX Keep the diagnostic code and MTA. */
+ }
+
+ /*
+ * Mail for the next-hop destination loops back to myself. Pass
+ * the mail to the best_mx_transport or bounce it.
+ */
+ else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) {
+ dsb_reset(why); /* XXX */
+ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
+ var_bestmx_transp,
+ request);
+ SMTP_RCPT_LEFT(state) = 0; /* XXX */
+ }
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+ argv_free(sites);
+}
+
+/* smtp_connect - establish SMTP connection */
+
+int smtp_connect(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ char *destination = request->nexthop;
+
+ /*
+ * All deliveries proceed along the same lines, whether they are over TCP
+ * or UNIX-domain sockets, and whether they use SMTP or LMTP: get a
+ * connection from the cache or create a new connection; deliver mail;
+ * update the connection cache or disconnect.
+ *
+ * The major differences appear at a higher level: the expansion from
+ * destination to address list, and whether to stop before we reach the
+ * end of that list.
+ */
+
+ /*
+ * With LMTP we have direct-to-host delivery only. The destination may
+ * have multiple IP addresses.
+ */
+ if (!smtp_mode) {
+ if (strncmp(destination, "unix:", 5) == 0) {
+ smtp_connect_local(state, destination + 5);
+ } else {
+ if (strncmp(destination, "inet:", 5) == 0)
+ destination += 5;
+ smtp_connect_inet(state, destination, var_smtp_tcp_port);
+ }
+ }
+
+ /*
+ * XXX We don't add support for "unix:" or "inet:" prefixes in SMTP
+ * destinations, because that would break compatibility with existing
+ * Postfix configurations that have a host with such a name.
+ */
+ else {
+ smtp_connect_inet(state, destination, var_smtp_tcp_port);
+ }
+
+ /*
+ * We still need to bounce or defer some left-over recipients: either
+ * (SMTP) mail loops or some server was unavailable.
+ *
+ * We could avoid this (and the "final server" complexity) by keeping one
+ * DSN structure per recipient in memory, by updating those in-memory
+ * structures with each delivery attempt, and by always flushing all
+ * deferred recipients at the end. We'd probably still want to bounce
+ * recipients immediately, so we'd end up with another chunk of code for
+ * defer logging only.
+ */
+ if (SMTP_RCPT_LEFT(state) > 0) {
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */
+ smtp_sess_fail(state);
+
+ /*
+ * Sanity check. Don't silently lose recipients.
+ */
+ smtp_rcpt_cleanup(state);
+ if (SMTP_RCPT_LEFT(state) > 0)
+ msg_panic("smtp_connect: left-over recipients");
+ }
+ return (state->status);
+}
diff --git a/src/smtp/smtp_key.c b/src/smtp/smtp_key.c
new file mode 100644
index 0000000..643f4db
--- /dev/null
+++ b/src/smtp/smtp_key.c
@@ -0,0 +1,215 @@
+/*++
+/* NAME
+/* smtp_key 3
+/* SUMMARY
+/* cache/table lookup key management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* char *smtp_key_prefix(buffer, delim_na, iterator, context_flags)
+/* VSTRING *buffer;
+/* const char *delim_na;
+/* SMTP_ITERATOR *iterator;
+/* int context_flags;
+/* DESCRIPTION
+/* The Postfix SMTP server accesses caches and lookup tables,
+/* using lookup keys that contain information from various
+/* contexts: per-server configuration, per-request envelope,
+/* and results from DNS queries.
+/*
+/* These lookup keys sometimes share the same context information.
+/* The primary purpose of this API is to ensure that this
+/* shared context is used consistently, and that its use is
+/* made explicit (both are needed to verify that there is no
+/* false cache sharing).
+/*
+/* smtp_key_prefix() constructs a lookup key prefix from context
+/* that may be shared with other lookup keys. The user is free
+/* to append additional application-specific context. The result
+/* value is a pointer to the result text.
+/*
+/* Arguments:
+/* .IP buffer
+/* Storage for the result.
+/* .IP delim_na
+/* The field delimiter character, and the optional place holder
+/* character for a) information that is unavailable, b)
+/* information that is inapplicable, or c) that would result
+/* in an empty field. Key fields that contain "delim_na"
+/* characters will be base64-encoded.
+/* Do not specify "delim_na" characters that are part of the
+/* base64 character set.
+/* .IP iterator
+/* Information that will be selected by the specified flags.
+/* .IP context_flags
+/* Bit-wise OR of one or more of the following.
+/* .RS
+/* .IP SMTP_KEY_FLAG_SERVICE
+/* The global service name. This is a proxy for
+/* destination-independent and request-independent context.
+/* .IP SMTP_KEY_FLAG_SENDER
+/* The envelope sender address. This is a proxy for sender-dependent
+/* context, such as per-sender SASL authentication.
+/* .IP SMTP_KEY_FLAG_REQ_NEXTHOP
+/* The delivery request nexthop destination, including optional
+/* [] and :port (the same form that users specify in a SASL
+/* password or TLS policy lookup table). This is a proxy for
+/* destination-dependent, but host-independent context.
+/* .IP SMTP_KEY_FLAG_CUR_NEXTHOP
+/* The current iterator's nexthop destination (delivery request
+/* nexthop or fallback nexthop, including optional [] and
+/* :port).
+/* .IP SMTP_KEY_FLAG_HOSTNAME
+/* The current iterator's remote hostname.
+/* .IP SMTP_KEY_FLAG_ADDR
+/* The current iterator's remote address.
+/* .IP SMTP_KEY_FLAG_PORT
+/* The current iterator's remote port.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: undefined flag or zero flags. Fatal: out of memory.
+/* 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 <netinet/in.h> /* ntohs() for Solaris or BSD */
+#include <arpa/inet.h> /* ntohs() for Linux or BSD */
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <base64_code.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * Application-specific.
+ */
+#include <smtp.h>
+
+ /*
+ * We use a configurable field terminator and optional place holder for data
+ * that is unavailable or inapplicable. We base64-encode content that
+ * contains these characters, and content that needs obfuscation.
+ */
+
+/* smtp_key_append_na - append place-holder key field */
+
+static void smtp_key_append_na(VSTRING *buffer, const char *delim_na)
+{
+ if (delim_na[1] != 0)
+ VSTRING_ADDCH(buffer, delim_na[1]);
+ VSTRING_ADDCH(buffer, delim_na[0]);
+}
+
+/* smtp_key_append_str - append string-valued key field */
+
+static void smtp_key_append_str(VSTRING *buffer, const char *str,
+ const char *delim_na)
+{
+ if (str == 0 || str[0] == 0) {
+ smtp_key_append_na(buffer, delim_na);
+ } else if (str[strcspn(str, delim_na)] != 0) {
+ base64_encode_opt(buffer, str, strlen(str), BASE64_FLAG_APPEND);
+ VSTRING_ADDCH(buffer, delim_na[0]);
+ } else {
+ vstring_sprintf_append(buffer, "%s%c", str, delim_na[0]);
+ }
+}
+
+/* smtp_key_append_uint - append unsigned-valued key field */
+
+static void smtp_key_append_uint(VSTRING *buffer, unsigned num,
+ const char *delim_na)
+{
+ vstring_sprintf_append(buffer, "%u%c", num, delim_na[0]);
+}
+
+/* smtp_key_prefix - format common elements in lookup key */
+
+char *smtp_key_prefix(VSTRING *buffer, const char *delim_na,
+ SMTP_ITERATOR *iter, int flags)
+{
+ static const char myname[] = "smtp_key_prefix";
+ SMTP_STATE *state = iter->parent; /* private member */
+
+ /*
+ * Sanity checks.
+ */
+ if (state == 0)
+ msg_panic("%s: no parent state", myname);
+ if (flags & ~SMTP_KEY_MASK_ALL)
+ msg_panic("%s: unknown key flags 0x%x",
+ myname, flags & ~SMTP_KEY_MASK_ALL);
+ if (flags == 0)
+ msg_panic("%s: zero flags", myname);
+
+ /*
+ * Initialize.
+ */
+ VSTRING_RESET(buffer);
+
+ /*
+ * Per-service and per-request context.
+ */
+ if (flags & SMTP_KEY_FLAG_SERVICE)
+ smtp_key_append_str(buffer, state->service, delim_na);
+ if (flags & SMTP_KEY_FLAG_SENDER)
+ smtp_key_append_str(buffer, state->request->sender, delim_na);
+
+ /*
+ * Per-destination context, non-canonicalized form.
+ */
+ if (flags & SMTP_KEY_FLAG_REQ_NEXTHOP)
+ smtp_key_append_str(buffer, STR(iter->request_nexthop), delim_na);
+ if (flags & SMTP_KEY_FLAG_CUR_NEXTHOP)
+ smtp_key_append_str(buffer, STR(iter->dest), delim_na);
+
+ /*
+ * Per-host context, canonicalized form.
+ */
+ if (flags & SMTP_KEY_FLAG_HOSTNAME)
+ smtp_key_append_str(buffer, STR(iter->host), delim_na);
+ if (flags & SMTP_KEY_FLAG_ADDR)
+ smtp_key_append_str(buffer, STR(iter->addr), delim_na);
+ if (flags & SMTP_KEY_FLAG_PORT)
+ smtp_key_append_uint(buffer, ntohs(iter->port), delim_na);
+
+ /*
+ * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup
+ * engine also try the requested TLS level and 'stronger', in case a
+ * server hosts multiple domains with different TLS requirements?
+ */
+ if (flags & SMTP_KEY_FLAG_TLS_LEVEL)
+#ifdef USE_TLS
+ smtp_key_append_uint(buffer, state->tls->level, delim_na);
+#else
+ smtp_key_append_na(buffer, delim_na);
+#endif
+
+ VSTRING_TERMINATE(buffer);
+
+ return STR(buffer);
+}
diff --git a/src/smtp/smtp_map11.c b/src/smtp/smtp_map11.c
new file mode 100644
index 0000000..bca8641
--- /dev/null
+++ b/src/smtp/smtp_map11.c
@@ -0,0 +1,325 @@
+/*++
+/* NAME
+/* smtp_map11 3
+/* SUMMARY
+/* one-to-one address mapping
+/* SYNOPSIS
+/* #include <smtp.h>
+/*
+/* int smtp_map11_internal(addr, maps, propagate)
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int smtp_map11_external(addr, maps, propagate)
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int smtp_map11_tree(tree, maps, propagate)
+/* TOK822 *tree;
+/* MAPS *maps;
+/* int propagate;
+/* DESCRIPTION
+/* This module performs non-recursive one-to-one address mapping.
+/* An unmatched address extension is propagated when
+/* \fIpropagate\fR is non-zero.
+/*
+/* smtp_map11_internal() looks up the RFC 822 internal (unquoted)
+/* string form of an address in the maps specified via the
+/* \fImaps\fR argument.
+/*
+/* smtp_map11_external() is a wrapper around the smtp_map11_internal()
+/* routine that transforms from external (quoted) string form
+/* to internal form and back.
+/*
+/* smtp_map11_tree() is a wrapper around the smtp_map11_internal()
+/* routine that transforms from internal parse tree form to
+/* internal form and back.
+/* DIAGNOSTICS
+/* Table lookup errors are fatal.
+/* SEE ALSO
+/* 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 <argv.h>
+#include <tok822.h>
+
+/* Global library. */
+
+#include <mail_addr_map.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+
+/* smtp_map11_internal - one-to-one table lookups */
+
+int smtp_map11_internal(VSTRING *addr, MAPS *maps, int propagate)
+{
+ const char *myname = "smtp_map11_internal";
+ ARGV *new_addr;
+ const char *result;
+
+ if ((new_addr = mail_addr_map_internal(maps, STR(addr), propagate)) != 0) {
+ if (new_addr->argc > 1)
+ msg_warn("multi-valued %s result for %s", maps->title, STR(addr));
+ result = new_addr->argv[0];
+ if (msg_verbose)
+ msg_info("%s: %s -> %s", myname, STR(addr), result);
+ vstring_strcpy(addr, result);
+ argv_free(new_addr);
+ return (1);
+ } else {
+ if (maps->error != 0)
+ msg_fatal("%s map lookup problem for %s", maps->title, STR(addr));
+ if (msg_verbose)
+ msg_info("%s: %s not found", myname, STR(addr));
+ return (0);
+ }
+}
+
+/* smtp_map11_tree - rewrite address node */
+
+int smtp_map11_tree(TOK822 *tree, MAPS *maps, int propagate)
+{
+ VSTRING *int_buf = vstring_alloc(100);
+ VSTRING *ext_buf = vstring_alloc(100);
+ int ret;
+
+ tok822_internalize(int_buf, tree->head, TOK822_STR_DEFL);
+ ret = smtp_map11_internal(int_buf, maps, propagate);
+ tok822_free_tree(tree->head);
+ quote_822_local(ext_buf, STR(int_buf));
+ tree->head = tok822_scan(STR(ext_buf), &tree->tail);
+ vstring_free(int_buf);
+ vstring_free(ext_buf);
+ return (ret);
+}
+
+/* smtp_map11_external - rewrite address external form */
+
+int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int ret;
+
+ unquote_822_local(temp, STR(addr));
+ ret = smtp_map11_internal(temp, maps, propagate);
+ quote_822_local(addr, STR(temp));
+ vstring_free(temp);
+ return (ret);
+}
+
+#ifdef TEST
+#include <ctype.h>
+
+#include <msg_vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <vstring_vstream.h>
+
+#include <canon_addr.h>
+#include <mail_params.h>
+
+/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */
+
+VSTRING *canon_addr_external(VSTRING *result, const char *addr)
+{
+ char *at;
+
+ vstring_strcpy(result, addr);
+ if ((at = strrchr(addr, '@')) == 0
+ || (at + 1)[strcspn(at + 1, "\"\\")] != 0)
+ vstring_sprintf_append(result, "@%s", var_myorigin);
+ return (result);
+}
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s [-v]", progname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *read_buf = vstring_alloc(100);
+ MAPS *maps = 0;
+ int lineno;
+ int first_line;
+ char *bp;
+ char *cmd;
+ VSTRING *addr_buf = vstring_alloc(100);
+ char *addr_field;
+ char *res_field;
+ int ch;
+ int errs = 0;
+
+ /*
+ * Initialize.
+ */
+ msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind)
+ usage(argv[0]);
+
+ util_utf8_enable = 1;
+ mail_params_init();
+
+ /*
+ * TODO: Data-driven parameter settings.
+ */
+#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
+
+ UPDATE(var_myhostname, "localhost.localdomain");
+ UPDATE(var_mydomain, "localdomain");
+ UPDATE(var_myorigin, "localdomain");
+ UPDATE(var_mydest, "localhost.localdomain");
+ UPDATE(var_rcpt_delim, "+");
+
+ /*
+ * The read-eval-print loop.
+ */
+ while (readllines(read_buf, VSTREAM_IN, &lineno, &first_line)) {
+ bp = STR(read_buf);
+ if (msg_verbose)
+ msg_info("> %s", bp);
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#')
+ continue;
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == 0)
+ msg_fatal("missing parameter for command %s", cmd);
+
+ /*
+ * Open maps.
+ */
+ if (strcmp(cmd, "maps") == 0) {
+ if (maps)
+ maps_free(maps);
+ maps = maps_create(bp, bp, DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ vstream_printf("%s\n", bp);
+ continue;
+ }
+
+ /*
+ * Lookup and verify.
+ */
+ else if (maps != 0 && (strcmp(cmd, "external") == 0
+ || strcmp(cmd, "internal") == 0
+ || strcmp(cmd, "tree") == 0)) {
+ int have_result = 0;
+
+
+ /*
+ * Parse the input and expectations.
+ */
+ if ((addr_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("missing address field");
+ res_field = mystrtok(&bp, ":");
+ if (mystrtok(&bp, ":") != 0)
+ msg_fatal("garbage after result field");
+
+ /*
+ * Perform the mapping.
+ */
+ if (strcmp(cmd, "external") == 0) {
+ vstring_strcpy(addr_buf, addr_field);
+ have_result = smtp_map11_external(addr_buf, maps, 1);
+ } else if (maps && strcmp(cmd, "internal") == 0) {
+ vstring_strcpy(addr_buf, addr_field);
+ have_result = smtp_map11_internal(addr_buf, maps, 1);
+ } else if (maps && strcmp(cmd, "tree") == 0) {
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+
+ tree = tok822_parse(addr_field);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++)
+ have_result |= smtp_map11_tree(tpp[0], maps, 1);
+ myfree((void *) addr_list);
+ if (have_result)
+ tok822_externalize(addr_buf, tree, TOK822_STR_DEFL);
+ tok822_free_tree(tree);
+ }
+
+ /*
+ * Summarize.
+ */
+ vstream_printf("%s:%s -> %s\n",
+ cmd, addr_field, have_result ?
+ STR(addr_buf) : maps->error ?
+ "(error)" : "(no match)");
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Enforce expectations.
+ */
+ if (res_field && have_result) {
+ if (strcmp(res_field, STR(addr_buf)) != 0) {
+ msg_warn("expect result '%s' but got '%s'",
+ res_field, STR(addr_buf));
+ errs = 1;
+ }
+ } else if (res_field && !have_result) {
+ msg_warn("expect result '%s' but got none", res_field);
+ errs = 1;
+ } else if (!res_field && have_result) {
+ msg_warn("expected no result but got '%s'", STR(addr_buf));
+ errs = 1;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Unknown request.
+ */
+ else {
+ msg_fatal("bad request: %s", cmd);
+ }
+ }
+ vstring_free(addr_buf);
+ vstring_free(read_buf);
+ maps_free(maps);
+ return (errs != 0);
+}
+
+#endif
diff --git a/src/smtp/smtp_map11.in b/src/smtp/smtp_map11.in
new file mode 100644
index 0000000..242859f
--- /dev/null
+++ b/src/smtp/smtp_map11.in
@@ -0,0 +1,33 @@
+# Table with external-form mapping.
+maps inline:{
+ { foo@example.com = bar@com.example }
+ { bar@example.com = bar }
+ { baz@example.com = @com.example }
+ { splitme@example.com = "split me"@com.example } }
+
+# Tests for external form.
+external foo@example.com:bar@com.example
+external bar@example.com:bar@localdomain
+external baz@example.com:baz@com.example
+external foo@example.net
+external splitme@example.com:"split me"@com.example
+external splitme+ext@example.com:"split me+ext"@com.example
+external "baz+first last"@example.com:"baz+first last"@com.example
+
+# Same tests for tree form.
+tree foo@example.com:bar@com.example
+tree bar@example.com:bar@localdomain
+tree baz@example.com:baz@com.example
+tree foo@example.net
+tree splitme@example.com:"split me"@com.example
+tree splitme+ext@example.com:"split me+ext"@com.example
+tree "baz+first last"@example.com:"baz+first last"@com.example
+
+# Same tests for internal form.
+internal foo@example.com:bar@com.example
+internal bar@example.com:bar@localdomain
+internal baz@example.com:baz@com.example
+internal foo@example.net
+internal splitme@example.com:split me@com.example
+internal splitme+ext@example.com:split me+ext@com.example
+internal baz+first last@example.com:baz+first last@com.example
diff --git a/src/smtp/smtp_map11.ref b/src/smtp/smtp_map11.ref
new file mode 100644
index 0000000..7e6f2ed
--- /dev/null
+++ b/src/smtp/smtp_map11.ref
@@ -0,0 +1,22 @@
+inline:{ { foo@example.com = bar@com.example } { bar@example.com = bar } { baz@example.com = @com.example } { splitme@example.com = "split me"@com.example } }
+external:foo@example.com -> bar@com.example
+external:bar@example.com -> bar@localdomain
+external:baz@example.com -> baz@com.example
+external:foo@example.net -> (no match)
+external:splitme@example.com -> "split me"@com.example
+external:splitme+ext@example.com -> "split me+ext"@com.example
+external:"baz+first last"@example.com -> "baz+first last"@com.example
+tree:foo@example.com -> bar@com.example
+tree:bar@example.com -> bar@localdomain
+tree:baz@example.com -> baz@com.example
+tree:foo@example.net -> (no match)
+tree:splitme@example.com -> "split me"@com.example
+tree:splitme+ext@example.com -> "split me+ext"@com.example
+tree:"baz+first last"@example.com -> "baz+first last"@com.example
+internal:foo@example.com -> bar@com.example
+internal:bar@example.com -> bar@localdomain
+internal:baz@example.com -> baz@com.example
+internal:foo@example.net -> (no match)
+internal:splitme@example.com -> split me@com.example
+internal:splitme+ext@example.com -> split me+ext@com.example
+internal:baz+first last@example.com -> baz+first last@com.example
diff --git a/src/smtp/smtp_misc.c b/src/smtp/smtp_misc.c
new file mode 100644
index 0000000..43a176f
--- /dev/null
+++ b/src/smtp/smtp_misc.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* smtp_misc 3
+/* SUMMARY
+/* SMTP client address rewriting
+/* SYNOPSIS
+/* #include <smtp_addr.h>
+/*
+/* void smtp_rewrite_generic_internal(
+/* VSTRING *dst,
+/* const char *src);
+/*
+/* void smtp_quote_822_address_flags(
+/* VSTRING *dst,
+/* const char *src,
+/* int flags);
+/*
+/* void smtp_quote_821_address(
+/* VSTRING *dst,
+/* const char *src);
+/* DESCRIPTION
+/* smtp_rewrite_generic_internal() rewrites a non-empty address
+/* if generic mapping is enabled, otherwise copies it literally.
+/*
+/* smtp_quote_822_address_flags() is a wrapper around
+/* quote_822_local_flags(), except for the empty address which
+/* is copied literally.
+/*
+/* smtp_quote_821_address() is a wrapper around quote_821_local(),
+/* except for the empty address or with "smtp_quote_rfc821_envelope
+/* = no"; in those cases the address is copied literally.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <ext_prop.h>
+#include <mail_params.h>
+#include <quote_821_local.h>
+#include <quote_822_local.h>
+
+ /*
+ * Application-specific.
+ */
+#include <smtp.h>
+
+/* smtp_rewrite_generic_internal - generic non-empty address rewriting */
+
+void smtp_rewrite_generic_internal(VSTRING *dst, const char *src)
+{
+ vstring_strcpy(dst, src);
+ if (*src && smtp_generic_maps)
+ smtp_map11_internal(dst, smtp_generic_maps,
+ smtp_ext_prop_mask & EXT_PROP_GENERIC);
+}
+
+/* smtp_quote_822_address_flags - quote non-empty header address */
+
+void smtp_quote_822_address_flags(VSTRING *dst, const char *src, int flags)
+{
+ if (*src) {
+ quote_822_local_flags(dst, src, flags);
+ } else if (flags & QUOTE_FLAG_APPEND) {
+ vstring_strcat(dst, src);
+ } else {
+ vstring_strcpy(dst, src);
+ }
+}
+
+/* smtp_quote_821_address - quote non-empty envelope address */
+
+void smtp_quote_821_address(VSTRING *dst, const char *src)
+{
+ if (*src && var_smtp_quote_821_env) {
+ quote_821_local(dst, src);
+ } else {
+ vstring_strcpy(dst, src);
+ }
+}
diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c
new file mode 100644
index 0000000..cd54f8f
--- /dev/null
+++ b/src/smtp/smtp_params.c
@@ -0,0 +1,140 @@
+ static const CONFIG_STR_TABLE smtp_str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_SMTP_FALLBACK, DEF_SMTP_FALLBACK, &var_fallback_relay, 0, 0,
+ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
+ VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTP_SASL_TLS_OPTS, DEF_SMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0,
+ VAR_SMTP_SASL_TLSV_OPTS, DEF_SMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0,
+ VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0,
+ VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0,
+ VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0,
+ VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0,
+ VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0,
+ VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0,
+ VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0,
+ VAR_SMTP_TLS_MAND_CIPH, DEF_SMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0,
+ VAR_SMTP_TLS_EXCL_CIPH, DEF_SMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0,
+ VAR_SMTP_TLS_MAND_EXCL, DEF_SMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0,
+ VAR_SMTP_TLS_MAND_PROTO, DEF_SMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0,
+ VAR_SMTP_TLS_VFY_CMATCH, DEF_SMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0,
+ VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0,
+ VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0,
+ VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+ VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0,
+ VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0,
+ VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0,
+ VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
+ VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
+ VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_SMTP_TLS_SNI, DEF_SMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0,
+ VAR_SMTP_TLS_INSECURE_MX_POLICY, DEF_SMTP_TLS_INSECURE_MX_POLICY, &var_smtp_tls_insecure_mx_policy, 0, 0,
+#endif
+ VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
+ VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
+ VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0,
+ VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0,
+ VAR_SMTP_VRFY_TGT, DEF_SMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0,
+ VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0,
+ VAR_SMTP_HOST_LOOKUP, DEF_SMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0,
+ VAR_SMTP_DNS_SUPPORT, DEF_SMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0,
+ VAR_SMTP_CACHE_DEST, DEF_SMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0,
+ VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0,
+ VAR_SMTP_EHLO_DIS_WORDS, DEF_SMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0,
+ VAR_SMTP_EHLO_DIS_MAPS, DEF_SMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0,
+ VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0,
+ VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0,
+ VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_SMTP_GENERIC_MAPS, DEF_SMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0,
+ VAR_SMTP_TCP_PORT, DEF_SMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0,
+ VAR_SMTP_PIX_BUG_WORDS, DEF_SMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0,
+ VAR_SMTP_PIX_BUG_MAPS, DEF_SMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0,
+ VAR_SMTP_SASL_AUTH_CACHE_NAME, DEF_SMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_SMTP_HEAD_CHKS, DEF_SMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0,
+ VAR_SMTP_MIME_CHKS, DEF_SMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0,
+ VAR_SMTP_NEST_CHKS, DEF_SMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0,
+ VAR_SMTP_BODY_CHKS, DEF_SMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0,
+ VAR_SMTP_RESP_FILTER, DEF_SMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0,
+ VAR_SMTP_ADDR_PREF, DEF_SMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0,
+ VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0,
+ VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0,
+ VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE smtp_time_table[] = {
+ VAR_SMTP_CONN_TMOUT, DEF_SMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0,
+ VAR_SMTP_HELO_TMOUT, DEF_SMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0,
+ VAR_SMTP_XFWD_TMOUT, DEF_SMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0,
+ VAR_SMTP_MAIL_TMOUT, DEF_SMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0,
+ VAR_SMTP_RCPT_TMOUT, DEF_SMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0,
+ VAR_SMTP_DATA0_TMOUT, DEF_SMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0,
+ VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0,
+ VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0,
+ VAR_SMTP_RSET_TMOUT, DEF_SMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0,
+ VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0,
+ VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0,
+ VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0,
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_SMTP_CACHE_CONNT, DEF_SMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0,
+ VAR_SMTP_REUSE_TIME, DEF_SMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0,
+#ifdef USE_TLS
+ VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0,
+#endif
+ VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0,
+ VAR_SMTP_SASL_AUTH_CACHE_TIME, DEF_SMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE smtp_int_table[] = {
+ VAR_SMTP_LINE_LIMIT, DEF_SMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0,
+ VAR_SMTP_MXADDR_LIMIT, DEF_SMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0,
+ VAR_SMTP_MXSESS_LIMIT, DEF_SMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0,
+ VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0,
+#endif
+ VAR_SMTP_MIN_DATA_RATE, DEF_SMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE smtp_bool_table[] = {
+ VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting,
+ VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err,
+ VAR_SMTP_SKIP_QUIT_RESP, DEF_SMTP_SKIP_QUIT_RESP, &var_skip_quit_resp,
+ VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo,
+ VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo,
+ VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable,
+ VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr,
+ VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env,
+ VAR_SMTP_DEFER_MXADDR, DEF_SMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr,
+ VAR_SMTP_SEND_XFORWARD, DEF_SMTP_SEND_XFORWARD, &var_smtp_send_xforward,
+ VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand,
+ VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls,
+ VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
+ VAR_SMTP_TLS_CONN_REUSE, DEF_SMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse,
+#ifdef USE_TLS
+ VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername,
+ VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
+ VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
+ VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+#endif
+ VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
+ VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth,
+ VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr,
+ VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce,
+ VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final,
+ VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
+ VAR_SMTP_BALANCE_INET_PROTO, DEF_SMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto,
+ VAR_SMTP_BIND_ADDR_ENFORCE, DEF_SMTP_BIND_ADDR_ENFORCE, &var_smtp_bind_addr_enforce,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE smtp_nbool_table[] = {
+ VAR_SMTP_REQ_DEADLINE, DEF_SMTP_REQ_DEADLINE, &var_smtp_req_deadline,
+ 0,
+ };
diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c
new file mode 100644
index 0000000..2ceb0f3
--- /dev/null
+++ b/src/smtp/smtp_proto.c
@@ -0,0 +1,2515 @@
+/*++
+/* NAME
+/* smtp_proto 3
+/* SUMMARY
+/* client SMTP/LMTP protocol
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_helo(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_xfer(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_rset(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_quit(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* In the subsequent text, SMTP implies LMTP.
+/* This module implements the client side of the SMTP protocol.
+/*
+/* smtp_helo() performs the initial handshake with the SMTP server.
+/* When TLS is enabled, this includes STARTTLS negotiations.
+/*
+/* smtp_xfer() sends message envelope information followed by the
+/* message data, and finishes the SMTP conversation. These operations
+/* are combined in one function, in order to implement SMTP pipelining.
+/* Recipients are marked as "done" in the mail queue file when
+/* bounced or delivered. The message delivery status is updated
+/* accordingly.
+/*
+/* smtp_rset() sends a single RSET command and waits for the
+/* response. In case of a negative reply it sets the
+/* CANT_RSET_THIS_SESSION flag.
+/*
+/* smtp_quit() sends a single QUIT command and waits for the
+/* response if configured to do so. It always turns off connection
+/* caching.
+/* DIAGNOSTICS
+/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return
+/* 0 in case of success, -1 in case of failure. For smtp_xfer(),
+/* smtp_rset() and smtp_quit(), success means the ability to
+/* perform an SMTP conversation, not necessarily the ability
+/* to deliver mail, or the achievement of server happiness.
+/*
+/* In case of a rejected or failed connection, a connection
+/* is marked as "bad, do not cache". Otherwise, connection
+/* caching may be turned off (without being marked "bad") at
+/* the discretion of the code that implements the individual
+/* protocol steps.
+/*
+/* Warnings: corrupt message file. A corrupt message is marked
+/* as "corrupt" by changing its queue file permissions.
+/* BUGS
+/* Some SMTP servers will abort when the number of recipients
+/* for one message exceeds their capacity. This behavior violates
+/* the SMTP protocol.
+/* The only way around this is to limit the number of recipients
+/* per transaction to an artificially-low value.
+/* SEE ALSO
+/* smtp(3h) internal data structures
+/* smtp_chat(3) query/reply SMTP support
+/* smtp_trouble(3) error handlers
+/* 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
+/*
+/* Pipelining code in cooperation with:
+/* Jon Ribbens
+/* Oaktree Internet Solutions Ltd.,
+/* Internet House,
+/* Canal Basin,
+/* Coventry,
+/* CV1 4LY, United Kingdom.
+/*
+/* Connection caching in cooperation with:
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <sys/socket.h> /* shutdown(2) */
+#include <netinet/in.h> /* ntohs() */
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <time.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <name_mask.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <smtp_stream.h>
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <defer.h>
+#include <bounce.h>
+#include <record.h>
+#include <rec_type.h>
+#include <off_cvt.h>
+#include <mark_corrupt.h>
+#include <quote_822_local.h>
+#include <mail_proto.h>
+#include <mime_state.h>
+#include <ehlo_mask.h>
+#include <maps.h>
+#include <tok822.h>
+#include <mail_addr_map.h>
+#include <ext_prop.h>
+#include <namadr_list.h>
+#include <match_parent_style.h>
+#include <lex_822.h>
+#include <dsn_mask.h>
+#include <xtext.h>
+#include <uxtext.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Sender and receiver state. A session does not necessarily go through a
+ * linear progression, but states are guaranteed to not jump backwards.
+ * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states
+ * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST.
+ *
+ * When connection caching is enabled, the QUIT state is suppressed. Normal
+ * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions
+ * end with ABORT->LAST. The connection is left open for a limited time. An
+ * RSET probe should be sent before attempting to reuse an open connection
+ * for a new transaction.
+ *
+ * The code to send an RSET probe is a special case with its own initial state
+ * and with its own dedicated state transitions. The session proceeds as
+ * RSET->LAST. This code is kept inside the main protocol engine for
+ * consistent error handling and error reporting. It is not to be confused
+ * with the code that sends RSET to abort a mail transaction in progress.
+ *
+ * The code to send QUIT without message delivery transaction jumps into the
+ * main state machine. If this introduces complications, then we should
+ * introduce a second QUIT state with its own dedicated state transitions,
+ * just like we did for RSET probes.
+ *
+ * By default, the receiver skips the QUIT response. Some SMTP servers
+ * disconnect after responding to ".", and some SMTP servers wait before
+ * responding to QUIT.
+ *
+ * Client states that are associated with sending mail (up to and including
+ * SMTP_STATE_DOT) must have smaller numerical values than the non-sending
+ * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST).
+ */
+#define SMTP_STATE_XFORWARD_NAME_ADDR 0
+#define SMTP_STATE_XFORWARD_PROTO_HELO 1
+#define SMTP_STATE_MAIL 2
+#define SMTP_STATE_RCPT 3
+#define SMTP_STATE_DATA 4
+#define SMTP_STATE_DOT 5
+#define SMTP_STATE_ABORT 6
+#define SMTP_STATE_RSET 7
+#define SMTP_STATE_QUIT 8
+#define SMTP_STATE_LAST 9
+
+int *xfer_timeouts[SMTP_STATE_LAST] = {
+ &var_smtp_xfwd_tmout, /* name/addr */
+ &var_smtp_xfwd_tmout, /* helo/proto */
+ &var_smtp_mail_tmout,
+ &var_smtp_rcpt_tmout,
+ &var_smtp_data0_tmout,
+ &var_smtp_data2_tmout,
+ &var_smtp_rset_tmout,
+ &var_smtp_rset_tmout,
+ &var_smtp_quit_tmout,
+};
+
+char *xfer_states[SMTP_STATE_LAST] = {
+ "sending XFORWARD name/address",
+ "sending XFORWARD protocol/helo_name",
+ "sending MAIL FROM",
+ "sending RCPT TO",
+ "sending DATA command",
+ "sending end of data -- message may be sent more than once",
+ "sending final RSET",
+ "sending RSET probe",
+ "sending QUIT",
+};
+
+char *xfer_request[SMTP_STATE_LAST] = {
+ "XFORWARD name/address command",
+ "XFORWARD helo/protocol command",
+ "MAIL FROM command",
+ "RCPT TO command",
+ "DATA command",
+ "end of DATA command",
+ "final RSET command",
+ "RSET probe",
+ "QUIT command",
+};
+
+ /*
+ * Note: MIME downgrade never happens for mail that must be delivered with
+ * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request
+ * involves at least one UTF-8 envelope address or header value.
+ */
+#define SMTP_MIME_DOWNGRADE(session, request) \
+ (var_disable_mime_oconv == 0 \
+ && (session->features & SMTP_FEATURE_8BITMIME) == 0 \
+ && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0)
+
+#ifdef USE_TLS
+
+static int smtp_start_tls(SMTP_STATE *);
+
+#endif
+
+ /*
+ * Call-back information for header/body checks. We don't provide call-backs
+ * for actions that change the message delivery time or destination.
+ */
+static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *);
+static void smtp_text_out(void *, int, const char *, ssize_t, off_t);
+
+HBC_CALL_BACKS smtp_hbc_callbacks[1] = {
+ smtp_hbc_logger,
+ smtp_text_out,
+};
+
+static int smtp_vrfy_tgt;
+
+/* smtp_vrfy_init - initialize */
+
+void smtp_vrfy_init(void)
+{
+ static const NAME_CODE vrfy_init_table[] = {
+ SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT,
+ SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA,
+ 0,
+ };
+
+ if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE,
+ var_smtp_vrfy_tgt)) == 0)
+ msg_fatal("bad protocol stage: \"%s = %s\"",
+ VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt);
+}
+
+/* smtp_helo - perform initial handshake with SMTP server */
+
+int smtp_helo(SMTP_STATE *state)
+{
+ const char *myname = "smtp_helo";
+ SMTP_SESSION *session = state->session;
+ DELIVER_REQUEST *request = state->request;
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_RESP *resp;
+ SMTP_RESP fake;
+ int except;
+ char *lines;
+ char *words;
+ char *word;
+ int n;
+ static const NAME_CODE xforward_features[] = {
+ XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ const char *ehlo_words;
+ int discard_mask;
+ static const NAME_MASK pix_bug_table[] = {
+ PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP,
+ PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF,
+ 0,
+ };
+ const char *pix_bug_words;
+ const char *pix_bug_source;
+ int pix_bug_mask;
+
+#ifdef USE_TLS
+ int saved_features = session->features;
+ int tls_helo_status;
+
+#endif
+ const char *NOCLOBBER where;
+
+ /*
+ * Skip the plaintext SMTP handshake when connecting in SMTPS mode.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_wrappermode
+ && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+ /* XXX Mix-up of per-session and per-request flags. */
+ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
+ smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
+ var_smtp_req_deadline, 0);
+ tls_helo_status = smtp_start_tls(state);
+ state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
+ return (tls_helo_status);
+ }
+#endif
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(state->session->stream, var_smtp_helo_tmout,
+ var_smtp_req_deadline, 0);
+ if ((except = vstream_setjmp(state->session->stream)) != 0)
+ return (smtp_stream_except(state, except, where));
+
+ /*
+ * If not recursing after STARTTLS, examine the server greeting banner
+ * and decide if we are going to send EHLO as the next command.
+ */
+ if (var_smtp_tls_wrappermode
+ || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ where = "receiving the initial server greeting";
+ switch ((resp = smtp_chat_resp(session))->code / 100) {
+ case 2:
+ break;
+ case 5:
+ if (var_smtp_skip_5xx_greeting)
+ STR(resp->dsn_buf)[0] = '4';
+ /* FALLTHROUGH */
+ default:
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+
+ /*
+ * If the policy table specifies a bogus TLS security level, fail
+ * now.
+ */
+#ifdef USE_TLS
+ if (state->tls->level == TLS_LEV_INVALID)
+ /* Warning is already logged. */
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "client TLS configuration problem"));
+#endif
+
+ /*
+ * XXX Some PIX firewall versions require flush before ".<CR><LF>" so
+ * it does not span a packet boundary. This hurts performance so it
+ * is not on by default.
+ */
+ if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) {
+ /* Best effort only. Ignore errors. */
+ if (smtp_pix_bug_maps != 0
+ && (pix_bug_words =
+ maps_find(smtp_pix_bug_maps,
+ STR(iter->addr), 0)) != 0) {
+ pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS);
+ } else {
+ pix_bug_words = var_smtp_pix_bug_words;
+ pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS);
+ }
+ if (*pix_bug_words) {
+ pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table,
+ pix_bug_words,
+ NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
+ if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF)
+ && request->msg_stats.incoming_arrival.tv_sec
+ > vstream_ftime(state->session->stream) - var_smtp_pix_thresh)
+ pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF;
+ msg_info("%s: enabling PIX workarounds: %s for %s",
+ request->queue_id,
+ str_name_mask("pix workaround bitmask",
+ pix_bug_table, pix_bug_mask),
+ session->namaddrport);
+ session->features |= pix_bug_mask;
+ }
+ }
+
+ /*
+ * See if we are talking to ourself. This should not be possible with
+ * the way we implement DNS lookups. However, people are known to
+ * sometimes screw up the naming service. And, mailer loops are still
+ * possible when our own mailer routing tables are mis-configured.
+ */
+ words = resp->str;
+ (void) mystrtok(&words, "- \t\n");
+ for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) {
+ if (n == 0 && strcasecmp(word, var_myhostname) == 0) {
+ if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ msg_warn("host %s greeted me with my own hostname %s",
+ session->namaddrport, var_myhostname);
+ } else if (strcasecmp(word, "ESMTP") == 0)
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+ if (smtp_mode) {
+ if (var_smtp_always_ehlo
+ && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0)
+ session->features |= SMTP_FEATURE_ESMTP;
+ if (var_smtp_never_ehlo
+ || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0)
+ session->features &= ~SMTP_FEATURE_ESMTP;
+ } else {
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+ }
+
+ /*
+ * If recursing after STARTTLS, there is no server greeting banner.
+ * Always send EHLO as the next command.
+ */
+ else {
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+
+ /*
+ * Return the compliment. Fall back to SMTP if our ESMTP recognition
+ * heuristic failed.
+ */
+ if (smtp_mode) {
+ where = "performing the EHLO handshake";
+ if (session->features & SMTP_FEATURE_ESMTP) {
+ smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2) {
+ if (resp->code == 421)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ else
+ session->features &= ~SMTP_FEATURE_ESMTP;
+ }
+ }
+ if ((session->features & SMTP_FEATURE_ESMTP) == 0) {
+ where = "performing the HELO handshake";
+ smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+ } else {
+ where = "performing the LHLO handshake";
+ smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+
+ /*
+ * No early returns allowed, to ensure consistent handling of TLS and
+ * SASL policies.
+ */
+ if (session->features & SMTP_FEATURE_ESMTP) {
+
+ /*
+ * Determine what server EHLO keywords to ignore, typically to avoid
+ * inter-operability problems.
+ */
+ if (smtp_ehlo_dis_maps == 0
+ || (ehlo_words = maps_find(smtp_ehlo_dis_maps,
+ STR(iter->addr), 0)) == 0)
+ ehlo_words = var_smtp_ehlo_dis_words;
+ if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) {
+ msg_warn("%s: %s map lookup error for %s",
+ session->state->request->queue_id,
+ smtp_ehlo_dis_maps->title, STR(iter->addr));
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ }
+ discard_mask = ehlo_mask(ehlo_words);
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("discarding EHLO keywords: %s",
+ str_ehlo_mask(discard_mask));
+
+ /*
+ * Pick up some useful features offered by the SMTP server. XXX Until
+ * we have a portable routine to convert from string to off_t with
+ * proper overflow detection, ignore the message size limit
+ * advertised by the SMTP server. Otherwise, we might do the wrong
+ * thing when the server advertises a really huge message size limit.
+ *
+ * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...",
+ * because MicroSoft implemented AUTH based on an old draft.
+ */
+ lines = resp->str;
+ for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) {
+ if (mystrtok(&words, "- ")
+ && (word = mystrtok(&words, " \t=")) != 0) {
+ if (n == 0) {
+ if (session->helo != 0)
+ myfree(session->helo);
+
+ /*
+ * XXX: Keep the original case: we don't expect a single
+ * SMTP server to randomly change the case of its helo
+ * response. If different capitalization is detected, we
+ * should assume disjoint TLS caches.
+ */
+ session->helo = mystrdup(word);
+ if (strcasecmp(word, var_myhostname) == 0
+ && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) {
+ msg_warn("host %s replied to HELO/EHLO"
+ " with my own hostname %s",
+ session->namaddrport, var_myhostname);
+ if (session->features & SMTP_FEATURE_BEST_MX)
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.4.6"),
+ "mail for %s loops back to myself",
+ request->nexthop));
+ else
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.4.6"),
+ "mail for %s loops back to myself",
+ request->nexthop));
+ }
+ } else if (strcasecmp(word, "8BITMIME") == 0) {
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ session->features |= SMTP_FEATURE_8BITMIME;
+ } else if (strcasecmp(word, "PIPELINING") == 0) {
+ if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
+ session->features |= SMTP_FEATURE_PIPELINING;
+ } else if (strcasecmp(word, "XFORWARD") == 0) {
+ if ((discard_mask & EHLO_MASK_XFORWARD) == 0)
+ while ((word = mystrtok(&words, " \t")) != 0)
+ session->features |=
+ name_code(xforward_features,
+ NAME_CODE_FLAG_NONE, word);
+ } else if (strcasecmp(word, "SIZE") == 0) {
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ session->features |= SMTP_FEATURE_SIZE;
+ if ((word = mystrtok(&words, " \t")) != 0) {
+ if (!alldig(word))
+ msg_warn("bad EHLO SIZE limit \"%s\" from %s",
+ word, session->namaddrport);
+ else
+ session->size_limit = off_cvt_string(word);
+ }
+ }
+#ifdef USE_TLS
+ } else if (strcasecmp(word, "STARTTLS") == 0) {
+ /* Ignored later if we already sent STARTTLS. */
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
+ session->features |= SMTP_FEATURE_STARTTLS;
+#endif
+#ifdef USE_SASL_AUTH
+ } else if (var_smtp_sasl_enable
+ && strcasecmp(word, "AUTH") == 0) {
+ if ((discard_mask & EHLO_MASK_AUTH) == 0)
+ smtp_sasl_helo_auth(session, words);
+#endif
+ } else if (strcasecmp(word, "DSN") == 0) {
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ session->features |= SMTP_FEATURE_DSN;
+ } else if (strcasecmp(word, "SMTPUTF8") == 0) {
+ if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ session->features |= SMTP_FEATURE_SMTPUTF8;
+ }
+ n++;
+ }
+ }
+ }
+ if (msg_verbose)
+ msg_info("server features: 0x%x size %.0f",
+ session->features, (double) session->size_limit);
+
+ /*
+ * Decide if this delivery requires SMTPUTF8 server support.
+ *
+ * For now, we require that the remote SMTP server supports SMTPUTF8 when
+ * the sender requested SMTPUTF8 support.
+ *
+ * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the
+ * delivery request involves at least one UTF-8 envelope address or
+ * header value.
+ *
+ * If the sender requested SMTPUTF8 support but the delivery request
+ * involves no UTF-8 envelope address or header value, then we could
+ * still deliver such mail to a non-SMTPUTF8 server, except that we must
+ * either uxtext-encode ORCPT parameters or not send them. We cannot
+ * encode the ORCPT in xtext, because legacy SMTP requires that the
+ * unencoded address consist entirely of printable (graphic and white
+ * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A
+ * correct uxtext encoder will produce a result that an xtext decoder
+ * will pass through unchanged.
+ *
+ * XXX Should we try to encode headers with RFC 2047 when delivering to a
+ * non-SMTPUTF8 server? That could make life easier for mailing lists.
+ */
+#define DELIVERY_REQUIRES_SMTPUTF8 \
+ ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \
+ && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED))
+
+ /*
+ * Require that the server supports SMTPUTF8 when delivery requires
+ * SMTPUTF8.
+ *
+ * Fix 20140706: moved this before negotiating TLS, AUTH, and so on.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0
+ && DELIVERY_REQUIRES_SMTPUTF8)
+ return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.6.7"),
+ "SMTPUTF8 is required, "
+ "but was not offered by host %s",
+ session->namaddr));
+
+ /*
+ * Fix 20140706: don't do silly things when the remote server announces
+ * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver
+ * mail, not to force people into compliance.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
+ && (session->features & SMTP_FEATURE_8BITMIME) == 0) {
+ msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME",
+ session->namaddr);
+ session->features |= SMTP_FEATURE_8BITMIME;
+ }
+
+ /*
+ * We use SMTP command pipelining if the server said it supported it.
+ * Since we use blocking I/O, RFC 2197 says that we should inspect the
+ * TCP window size and not send more than this amount of information.
+ * Unfortunately this information is unavailable using the sockets
+ * interface. However, we *can* get the TCP send buffer size on the local
+ * TCP/IP stack. We should be able to fill this buffer without being
+ * blocked, and then the kernel will effectively do non-blocking I/O for
+ * us by automatically writing out the contents of its send buffer while
+ * we are reading in the responses. In addition to TCP buffering we have
+ * to be aware of application-level buffering by the vstream module,
+ * which is limited to a couple kbytes.
+ *
+ * XXX No need to do this before and after STARTTLS, but it's not a big deal
+ * if we do.
+ *
+ * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as
+ * TLS messages. Thus, the TCP-level payload will be larger than the
+ * SMTP-level payload. This has implications for the PIPELINING engine.
+ *
+ * To avoid deadlock, the PIPELINING engine needs to request a TCP send
+ * buffer size that can hold the unacknowledged commands plus the TLS
+ * encapsulation overhead.
+ *
+ * The PIPELINING engine keeps the unacknowledged command size <= the
+ * default VSTREAM buffer size (to avoid small-write performance issues
+ * when the VSTREAM buffer size is at its default size). With a default
+ * VSTREAM buffer size of 4096 there is no reason to increase the
+ * unacknowledged command size as the TCP MSS increases. It's safer to
+ * spread the remote SMTP server's recipient processing load over time,
+ * than dumping a very large recipient list all at once.
+ *
+ * For TLS encapsulation overhead we make a conservative guess: take the
+ * current protocol overhead of ~40 bytes, double the number for future
+ * proofing (~80 bytes), then round up the result to the nearest power of
+ * 2 (128 bytes). Plus, be prepared for worst-case compression that
+ * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS
+ * message becomes 15 kbytes.
+ */
+#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE
+#ifdef USE_TLS
+#define TLS_WORST_PAYLOAD 16384
+#define TLS_WORST_COMP_OVERHD 1024
+#define TLS_WORST_PROTO_OVERHD 128
+#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD)
+#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD)
+#endif
+
+ if (session->features & SMTP_FEATURE_PIPELINING) {
+ SOCKOPT_SIZE optlen;
+ int tcp_bufsize;
+ int enc_overhead = 0;
+
+ optlen = sizeof(tcp_bufsize);
+ if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET,
+ SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0)
+ msg_fatal("%s: getsockopt: %m", myname);
+#ifdef USE_TLS
+ if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS)
+ enc_overhead +=
+ (1 + (PIPELINING_BUFSIZE - 1)
+ / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD;
+#endif
+ if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) {
+ tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead;
+ if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET,
+ SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0)
+ msg_fatal("%s: setsockopt: %m", myname);
+ }
+ if (msg_verbose)
+ msg_info("Using %s PIPELINING, TCP send buffer size is %d, "
+ "PIPELINING buffer size is %d",
+ smtp_mode ? "ESMTP" : "LMTP",
+ tcp_bufsize, PIPELINING_BUFSIZE);
+ }
+#ifdef USE_TLS
+
+ /*
+ * Skip this part if we already sent STARTTLS.
+ */
+ if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+
+ /*
+ * Optionally log unused STARTTLS opportunities.
+ */
+ if ((session->features & SMTP_FEATURE_STARTTLS) &&
+ var_smtp_tls_note_starttls_offer &&
+ state->tls->level <= TLS_LEV_NONE)
+ msg_info("Host offered STARTTLS: [%s]", STR(iter->host));
+
+ /*
+ * Decide whether or not to send STARTTLS.
+ */
+ if ((session->features & SMTP_FEATURE_STARTTLS) != 0
+ && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) {
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
+ var_smtp_req_deadline, 0);
+ if ((except = vstream_setjmp(state->session->stream)) != 0)
+ return (smtp_stream_except(state, except,
+ "receiving the STARTTLS response"));
+
+ /*
+ * Send STARTTLS. Recurse when the server accepts STARTTLS, after
+ * resetting the SASL and EHLO features lists.
+ *
+ * Reset the SASL mechanism list to avoid spurious warnings.
+ *
+ * Use the smtp_sasl_tls_security_options feature to allow SASL
+ * mechanisms that may not be allowed with plain-text
+ * connections.
+ */
+ smtp_chat_cmd(session, "STARTTLS");
+ if ((resp = smtp_chat_resp(session))->code / 100 == 2) {
+#ifdef USE_SASL_AUTH
+ if (session->features & SMTP_FEATURE_AUTH)
+ smtp_sasl_cleanup(session);
+#endif
+ session->features = saved_features;
+ /* XXX Mix-up of per-session and per-request flags. */
+ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
+ tls_helo_status = smtp_start_tls(state);
+ state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
+ return (tls_helo_status);
+ }
+
+ /*
+ * Give up if we must use TLS but the server rejects STARTTLS
+ * although support for it was announced in the EHLO response.
+ */
+ session->features &= ~SMTP_FEATURE_STARTTLS;
+ if (TLS_REQUIRED(state->tls->level))
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "TLS is required, but host %s refused to start TLS: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ /* Else try to continue in plain-text mode. */
+ }
+
+ /*
+ * Give up if we must use TLS but can't for various reasons.
+ *
+ * 200412 Be sure to provide the default clause at the bottom of this
+ * block. When TLS is required we must never, ever, end up in
+ * plain-text mode.
+ */
+ if (TLS_REQUIRED(state->tls->level)) {
+ if (!(session->features & SMTP_FEATURE_STARTTLS)) {
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.4"),
+ "TLS is required, but was not offered by host %s",
+ session->namaddr));
+ } else if (smtp_tls_ctx == 0) {
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "TLS is required, but our TLS engine is unavailable"));
+ } else {
+ msg_warn("%s: TLS is required but unavailable, don't know why",
+ myname);
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "TLS is required, but unavailable"));
+ }
+ }
+ }
+#endif
+#ifdef USE_SASL_AUTH
+ if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH))
+ return (smtp_sasl_helo_login(state));
+#endif
+
+ return (0);
+}
+
+#ifdef USE_TLS
+
+/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */
+
+static int smtp_start_tls(SMTP_STATE *state)
+{
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ TLS_CLIENT_START_PROPS start_props;
+ VSTRING *serverid;
+ SMTP_RESP fake;
+ TLS_CLIENT_INIT_PROPS init_props;
+ VSTREAM *tlsproxy;
+ VSTRING *port_buf;
+
+ /*
+ * When the TLS handshake succeeds, we can reuse a connection only if TLS
+ * remains turned on for the lifetime of that connection. This requires
+ * that the TLS library state is maintained in some proxy process, for
+ * example, in tlsproxy(8). We then store the proxy file handle in the
+ * connection cache, and reuse that file handle.
+ *
+ * Otherwise, we must turn off connection caching. We can't turn off TLS in
+ * one SMTP client process, save the open connection to a cache which is
+ * shared with all SMTP clients, migrate the connection to another SMTP
+ * client, and resume TLS there. When the TLS handshake fails, we can't
+ * reuse the SMTP connection either, because the conversation is in an
+ * unknown state.
+ */
+ if (state->tls->conn_reuse == 0)
+ DONT_CACHE_THIS_SESSION;
+
+ /*
+ * The following assumes sites that use TLS in a perverse configuration:
+ * multiple hosts per hostname, or even multiple hosts per IP address.
+ * All this without a shared TLS session cache, and they still want to
+ * use TLS session caching???
+ *
+ * The TLS session cache records the trust chain verification status of
+ * cached sessions. Different transports may have different CAfile or
+ * CApath settings, perhaps to allow authenticated connections to sites
+ * with private CA certs without trusting said private certs for other
+ * sites. So we cannot assume that a trust chain valid for one transport
+ * is valid for another. Therefore the client session id must include
+ * either the transport name or the values of CAfile and CApath. We use
+ * the transport name.
+ *
+ * XXX: We store only one session per lookup key. Ideally the key maps
+ * 1-to-1 to a server TLS session cache. We use the IP address, port and
+ * ehlo response name to build a lookup key that works for split caches
+ * (that announce distinct names) behind a load balancer.
+ *
+ * XXX: The TLS library will salt the serverid with further details of the
+ * protocol and cipher requirements including the server ehlo response.
+ * Deferring the helo to the digested suffix results in more predictable
+ * SSL session lookup key lengths.
+ */
+ serverid = vstring_alloc(10);
+ smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE
+ | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */
+ | SMTP_KEY_FLAG_HOSTNAME
+ | SMTP_KEY_FLAG_ADDR);
+
+ if (state->tls->conn_reuse) {
+ TLS_CLIENT_PARAMS tls_params;
+
+ /*
+ * Send all our wishes in one big request.
+ */
+ TLS_PROXY_CLIENT_INIT_PROPS(&init_props,
+ log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL),
+ log_level = var_smtp_tls_loglevel,
+ verifydepth = var_smtp_tls_scert_vd,
+ cache_type
+ = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE),
+ chain_files = var_smtp_tls_chain_files,
+ cert_file = var_smtp_tls_cert_file,
+ key_file = var_smtp_tls_key_file,
+ dcert_file = var_smtp_tls_dcert_file,
+ dkey_file = var_smtp_tls_dkey_file,
+ eccert_file = var_smtp_tls_eccert_file,
+ eckey_file = var_smtp_tls_eckey_file,
+ CAfile = var_smtp_tls_CAfile,
+ CApath = var_smtp_tls_CApath,
+ mdalg = var_smtp_tls_fpt_dgst);
+ TLS_PROXY_CLIENT_START_PROPS(&start_props,
+ timeout = var_smtp_starttls_tmout,
+ tls_level = state->tls->level,
+ nexthop = session->tls_nexthop,
+ host = STR(iter->host),
+ namaddr = session->namaddrport,
+ sni = state->tls->sni,
+ serverid = vstring_str(serverid),
+ helo = session->helo,
+ protocols = state->tls->protocols,
+ cipher_grade = state->tls->grade,
+ cipher_exclusions
+ = vstring_str(state->tls->exclusions),
+ matchargv = state->tls->matchargv,
+ mdalg = var_smtp_tls_fpt_dgst,
+ dane = state->tls->dane);
+
+ /*
+ * The tlsproxy(8) server enforces timeouts that are larger than
+ * those specified by the tlsproxy(8) client. These timeouts are a
+ * safety net for the case that the tlsproxy(8) client fails to
+ * enforce time limits. Normally, the tlsproxy(8) client would time
+ * out and trigger a plaintext event in the tlsproxy(8) server, and
+ * cause it to tear down the session.
+ *
+ * However, the tlsproxy(8) server has no insight into the SMTP
+ * protocol, and therefore it cannot by itself support different
+ * timeouts at different SMTP protocol stages. Instead, we specify
+ * the largest timeout (end-of-data) and rely on the SMTP client to
+ * time out first, which normally results in a plaintext event in the
+ * tlsproxy(8) server. Unfortunately, we cannot permit plaintext
+ * events during the TLS handshake, so we specify a separate timeout
+ * for that stage (the end-of-data timeout would be unreasonably
+ * large anyway).
+ */
+#define PROXY_OPEN_FLAGS \
+ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)
+
+ port_buf = vstring_alloc(100); /* minimize fragmentation */
+ vstring_sprintf(port_buf, "%d", ntohs(iter->port));
+ tlsproxy =
+ tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS,
+ session->stream, STR(iter->addr),
+ STR(port_buf), var_smtp_starttls_tmout,
+ var_smtp_data2_tmout, state->service,
+ tls_proxy_client_param_from_config(&tls_params),
+ &init_props, &start_props);
+ vstring_free(port_buf);
+
+ /*
+ * To insert tlsproxy(8) between this process and the remote SMTP
+ * server, we swap the file descriptors between the tlsproxy and
+ * session->stream VSTREAMS, so that we don't lose all the
+ * user-configurable session->stream attributes (such as longjump
+ * buffers or timeouts).
+ *
+ * TODO: the tlsproxy RPCs should return more error detail than a "NO"
+ * result. OTOH, the in-process TLS engine does not return such info
+ * either.
+ *
+ * If the tlsproxy request fails we do not fall back to the in-process
+ * TLS stack. Reason: the admin enabled connection reuse to respect
+ * receiver policy; silently violating such policy would not be
+ * useful.
+ *
+ * We also don't fall back to the in-process TLS stack under low-traffic
+ * conditions, to avoid frustrating attempts to debug a problem with
+ * using the tlsproxy(8) service.
+ */
+ if (tlsproxy == 0) {
+ session->tls_context = 0;
+ } else {
+ vstream_control(tlsproxy,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_END);
+ vstream_control(session->stream,
+ CA_VSTREAM_CTL_SWAP_FD(tlsproxy),
+ CA_VSTREAM_CTL_END);
+ (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */
+
+ /*
+ * There must not be any pending data in the stream buffers
+ * before we read the TLS context attributes.
+ */
+ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+
+ /*
+ * After plumbing the plaintext stream, receive the TLS context
+ * object. For this we use the same VSTREAM buffer that we also
+ * use to receive subsequent SMTP commands, therefore we must be
+ * prepared for the possibility that the remote SMTP server
+ * starts talking immediately. The tlsproxy implementation sends
+ * the TLS context before remote content. The attribute protocol
+ * is robust enough that an adversary cannot insert their own TLS
+ * context attributes.
+ */
+ session->tls_context = tls_proxy_context_receive(session->stream);
+ if (session->tls_context) {
+ session->features |= SMTP_FEATURE_FROM_PROXY;
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
+ session->tls_context);
+ }
+ }
+ } else { /* state->tls->conn_reuse */
+
+ /*
+ * As of Postfix 2.5, tls_client_start() tries hard to always
+ * complete the TLS handshake. It records the verification and match
+ * status in the resulting TLScontext. It is now up to the
+ * application to abort the TLS connection if it chooses.
+ *
+ * XXX When tls_client_start() fails then we don't know what state the
+ * SMTP connection is in, so we give up on this connection even if we
+ * are not required to use TLS.
+ *
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ */
+ session->tls_context =
+ TLS_CLIENT_START(&start_props,
+ ctx = smtp_tls_ctx,
+ stream = session->stream,
+ fd = -1,
+ timeout = var_smtp_starttls_tmout,
+ tls_level = state->tls->level,
+ nexthop = session->tls_nexthop,
+ host = STR(iter->host),
+ namaddr = session->namaddrport,
+ sni = state->tls->sni,
+ serverid = vstring_str(serverid),
+ helo = session->helo,
+ protocols = state->tls->protocols,
+ cipher_grade = state->tls->grade,
+ cipher_exclusions
+ = vstring_str(state->tls->exclusions),
+ matchargv = state->tls->matchargv,
+ mdalg = var_smtp_tls_fpt_dgst,
+ dane = state->tls->dane);
+
+ /*
+ * At this point there must not be any pending data in the stream
+ * buffers.
+ */
+ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+ } /* state->tls->conn_reuse */
+
+ vstring_free(serverid);
+
+ if (session->tls_context == 0) {
+
+ /*
+ * We must avoid further I/O, the peer is in an undefined state.
+ */
+ DONT_USE_FORBIDDEN_SESSION;
+
+ /*
+ * If TLS is optional, try delivery to the same server over a
+ * plaintext connection. Otherwise we would defer mail forever with
+ * destinations that have no alternate MX host.
+ *
+ * Don't fall back to plaintext if we were willing to use SASL-over-TLS
+ * authentication. If the server doesn't announce SASL support over
+ * plaintext connections, then we don't want delivery to fail with
+ * "relay access denied".
+ *
+ * If TLS is opportunistic, don't throttle the destination, otherwise if
+ * the mail is volume is high enough we may have difficulty ever
+ * draining even the deferred mail, as new mail provides a constant
+ * stream of negative feedback.
+ */
+ if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE)
+ RETRY_AS_PLAINTEXT;
+ return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ?
+ SMTP_NOTHROTTLE : SMTP_THROTTLE,
+ DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "Cannot start TLS: handshake failure"));
+ }
+
+ /*
+ * If we are verifying the server certificate and are not happy with the
+ * result, abort the delivery here. We have a usable TLS session with the
+ * server, so no need to disable I/O, ... we can even be polite and send
+ * "QUIT".
+ *
+ * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require
+ * matching.
+ *
+ * NOTE: We use "IS_MATCHED" to satisfy policy, but "IS_SECURED" to log
+ * effective security. Thus "half-dane" is never "Verified" only
+ * "Trusted", but matching is enforced here.
+ *
+ * NOTE: When none of the TLSA records were usable, "dane" and "half-dane"
+ * fall back to "encrypt", updating the tls_context level accordingly, so
+ * we must check that here, and not state->tls->level.
+ */
+ if (TLS_MUST_MATCH(session->tls_context->level))
+ if (!TLS_CERT_IS_MATCHED(session->tls_context))
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "Server certificate not verified"));
+
+ /*
+ * At this point we have to re-negotiate the "EHLO" to reget the
+ * feature-list.
+ */
+ return (smtp_helo(state));
+}
+
+#endif
+
+/* smtp_hbc_logger - logging call-back for header/body checks */
+
+static void smtp_hbc_logger(void *context, const char *action,
+ const char *where, const char *content,
+ const char *text)
+{
+ const SMTP_STATE *state = (SMTP_STATE *) context;
+
+ if (*text) {
+ msg_info("%s: %s: %s %.60s: %s",
+ state->request->queue_id, action, where, content, text);
+ } else {
+ msg_info("%s: %s: %s %.60s",
+ state->request->queue_id, action, where, content);
+ }
+}
+
+/* smtp_text_out - output one header/body record */
+
+static void smtp_text_out(void *context, int rec_type,
+ const char *text, ssize_t len,
+ off_t unused_offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ SMTP_SESSION *session = state->session;
+ ssize_t data_left;
+ const char *data_start;
+
+ /*
+ * Deal with an impedance mismatch between Postfix queue files (record
+ * length <= $message_line_length_limit) and SMTP (DATA record length <=
+ * $smtp_line_length_limit). The code below does a little too much work
+ * when the SMTP line length limit is disabled, but it avoids code
+ * duplication, and thus, it avoids testing and maintenance problems.
+ */
+ data_left = len;
+ data_start = text;
+ do {
+ if (state->space_left == var_smtp_line_limit
+ && data_left > 0 && *data_start == '.')
+ smtp_fputc('.', session->stream);
+ if (ENFORCING_SIZE_LIMIT(var_smtp_line_limit)
+ && data_left >= state->space_left) {
+ smtp_fputs(data_start, state->space_left, session->stream);
+ data_start += state->space_left;
+ data_left -= state->space_left;
+ state->space_left = var_smtp_line_limit;
+ if (data_left > 0 || rec_type == REC_TYPE_CONT) {
+ smtp_fputc(' ', session->stream);
+ state->space_left -= 1;
+
+ /*
+ * XXX This can insert a line break into the middle of a
+ * multi-byte character (not necessarily UTF-8). Note that
+ * multibyte characters can span queue file records, for
+ * example if line_length_limit == smtp_line_length_limit.
+ */
+ if (state->logged_line_length_limit == 0) {
+ msg_info("%s: breaking line > %d bytes with <CR><LF>SPACE",
+ state->request->queue_id, var_smtp_line_limit);
+ state->logged_line_length_limit = 1;
+ }
+ }
+ } else {
+ if (rec_type == REC_TYPE_CONT) {
+ smtp_fwrite(data_start, data_left, session->stream);
+ state->space_left -= data_left;
+ } else {
+ smtp_fputs(data_start, data_left, session->stream);
+ state->space_left = var_smtp_line_limit;
+ }
+ break;
+ }
+ } while (data_left > 0);
+}
+
+/* smtp_format_out - output one header/body record */
+
+static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...);
+
+static void smtp_format_out(void *context, int rec_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);
+ smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0);
+}
+
+/* smtp_header_out - output one message header */
+
+static void smtp_header_out(void *context, int unused_header_class,
+ const HEADER_OPTS *unused_info,
+ VSTRING *buf, off_t offset)
+{
+ char *start = vstring_str(buf);
+ char *line;
+ char *next_line;
+
+ /*
+ * This code destroys the header. We could try to avoid clobbering it,
+ * but we're not going to use the data any further.
+ */
+ for (line = start; line; line = next_line) {
+ next_line = split_at(line, '\n');
+ smtp_text_out(context, REC_TYPE_NORM, line, next_line ?
+ next_line - line - 1 : strlen(line), offset);
+ }
+}
+
+/* smtp_header_rewrite - rewrite message header before output */
+
+static void smtp_header_rewrite(void *context, int header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ int did_rewrite = 0;
+ char *line;
+ char *start;
+ char *next_line;
+ char *end_line;
+ char *result;
+
+ /*
+ * Apply optional header filtering.
+ */
+ if (smtp_header_checks) {
+ result = hbc_header_checks(context, smtp_header_checks, header_class,
+ header_info, buf, offset);
+ if (result == 0)
+ return;
+ if (result == HBC_CHECKS_STAT_ERROR) {
+ msg_warn("%s: smtp header checks lookup error",
+ state->request->queue_id);
+ vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
+ }
+ if (result != STR(buf)) {
+ vstring_strcpy(buf, result);
+ myfree(result);
+ }
+ }
+
+ /*
+ * Rewrite primary header addresses that match the smtp_generic_maps. The
+ * cleanup server already enforces that all headers have proper lengths
+ * and that all addresses are in proper form, so we don't have to repeat
+ * that.
+ */
+ if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY
+ && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) {
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+
+ tree = tok822_parse(vstring_str(buf)
+ + strlen(header_info->name) + 1);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++)
+ did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps,
+ smtp_ext_prop_mask & EXT_PROP_GENERIC);
+ if (did_rewrite) {
+ vstring_truncate(buf, strlen(header_info->name));
+ vstring_strcat(buf, ": ");
+ tok822_externalize(buf, tree, TOK822_STR_HEAD);
+ }
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ }
+
+ /*
+ * Pass through unmodified headers without reconstruction.
+ */
+ if (did_rewrite == 0) {
+ smtp_header_out(context, header_class, header_info, buf, offset);
+ return;
+ }
+
+ /*
+ * 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.
+ *
+ * Code derived from cleanup_fold_header().
+ */
+ for (line = start = vstring_str(buf); line != 0; line = next_line) {
+ end_line = line + strcspn(line, "\n");
+ if (line > start) {
+ if (end_line - start < 70) { /* TAB counts as one */
+ line[-1] = ' ';
+ } else {
+ start = line;
+ }
+ }
+ next_line = *end_line ? end_line + 1 : 0;
+ }
+
+ /*
+ * Prepend a tab to continued header lines that went through the address
+ * rewriting machinery. Just like smtp_header_out(), this code destroys
+ * the header. We could try to avoid clobbering it, but we're not going
+ * to use the data any further.
+ *
+ * Code derived from cleanup_out_header().
+ */
+ for (line = start = vstring_str(buf); line != 0; line = next_line) {
+ next_line = split_at(line, '\n');
+ if (line == start || IS_SPACE_TAB(*line)) {
+ smtp_text_out(state, REC_TYPE_NORM, line, next_line ?
+ next_line - line - 1 : strlen(line), offset);
+ } else {
+ smtp_format_out(state, REC_TYPE_NORM, "\t%s", line);
+ }
+ }
+}
+
+/* smtp_body_rewrite - rewrite message body before output */
+
+static void smtp_body_rewrite(void *context, int type,
+ const char *buf, ssize_t len,
+ off_t offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ char *result;
+
+ /*
+ * Apply optional body filtering.
+ */
+ if (smtp_body_checks) {
+ result = hbc_body_checks(context, smtp_body_checks, buf, len, offset);
+ if (result == buf) {
+ smtp_text_out(state, type, buf, len, offset);
+ } else if (result == HBC_CHECKS_STAT_ERROR) {
+ msg_warn("%s: smtp body checks lookup error",
+ state->request->queue_id);
+ vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
+ } else if (result != 0) {
+ smtp_text_out(state, type, result, strlen(result), offset);
+ myfree(result);
+ }
+ }
+}
+
+/* smtp_mime_fail - MIME problem */
+
+static void smtp_mime_fail(SMTP_STATE *state, int mime_errs)
+{
+ const MIME_STATE_DETAIL *detail;
+ SMTP_RESP fake;
+
+ detail = mime_state_detail(mime_errs);
+ smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, detail->dsn),
+ "%s", detail->text);
+}
+
+/* smtp_out_raw_or_mime - output buffer, raw output or MIME-aware */
+
+static int smtp_out_raw_or_mime(SMTP_STATE *state, int rec_type, VSTRING *buf)
+{
+ SMTP_SESSION *session = state->session;
+ int mime_errs;
+
+ if (session->mime_state == 0) {
+ smtp_text_out((void *) state, rec_type, vstring_str(buf),
+ VSTRING_LEN(buf), (off_t) 0);
+ } else {
+ mime_errs =
+ mime_state_update(session->mime_state, rec_type,
+ vstring_str(buf), VSTRING_LEN(buf));
+ if (mime_errs) {
+ smtp_mime_fail(state, mime_errs);
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* smtp_out_add_header - format address header, uses session->scratch* */
+
+static int smtp_out_add_header(SMTP_STATE *state, const char *label,
+ const char *lt, const char *addr,
+ const char *gt)
+{
+ SMTP_SESSION *session = state->session;
+
+ smtp_rewrite_generic_internal(session->scratch2, addr);
+ vstring_sprintf(session->scratch, "%s: %s", label, lt);
+ smtp_quote_822_address_flags(session->scratch,
+ vstring_str(session->scratch2),
+ QUOTE_FLAG_DEFAULT | QUOTE_FLAG_APPEND);
+ vstring_strcat(session->scratch, gt);
+ return (smtp_out_raw_or_mime(state, REC_TYPE_NORM, session->scratch));
+}
+
+/* smtp_out_add_headers - output additional headers, uses session->scratch* */
+
+static int smtp_out_add_headers(SMTP_STATE *state)
+{
+ /* Prepend headers in the same order as mail_copy.c. */
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_RETURN_PATH)
+ if (smtp_out_add_header(state, "Return-Path", "<",
+ state->request->sender, ">") < 0)
+ return (-1);
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT)
+ if (smtp_out_add_header(state, "X-Original-To", "",
+ state->request->rcpt_list.info->orig_addr, "") < 0)
+ return (-1);
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO)
+ if (smtp_out_add_header(state, "Delivered-To", "",
+ state->request->rcpt_list.info->address, "") < 0)
+ return (-1);
+ return (0);
+}
+
+/* smtp_loop - exercise the SMTP protocol engine */
+
+static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
+ NOCLOBBER int recv_state)
+{
+ const char *myname = "smtp_loop";
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_RESP *resp;
+ RECIPIENT *rcpt;
+ VSTRING *next_command = vstring_alloc(100);
+ int *NOCLOBBER survivors = 0;
+ NOCLOBBER int next_state;
+ NOCLOBBER int next_rcpt;
+ NOCLOBBER int send_rcpt;
+ NOCLOBBER int recv_rcpt;
+ NOCLOBBER int nrcpt;
+ NOCLOBBER int recv_done;
+ int except;
+ int rec_type;
+ NOCLOBBER int prev_type = 0;
+ NOCLOBBER int mail_from_rejected;
+ NOCLOBBER int downgrading;
+ int mime_errs;
+ SMTP_RESP fake;
+ int fail_status;
+
+ /* Caution: changes to RETURN() also affect code outside the main loop. */
+
+#define RETURN(x) do { \
+ if (recv_state != SMTP_STATE_LAST) \
+ DONT_CACHE_THIS_SESSION; \
+ vstring_free(next_command); \
+ if (survivors) \
+ myfree((void *) survivors); \
+ if (session->mime_state) \
+ session->mime_state = mime_state_free(session->mime_state); \
+ return (x); \
+ } while (0)
+
+#define SENDER_IS_AHEAD \
+ (recv_state < send_state || recv_rcpt != send_rcpt)
+
+#define SENDER_IN_WAIT_STATE \
+ (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST)
+
+#define SENDING_MAIL \
+ (recv_state <= SMTP_STATE_DOT)
+
+#define CANT_RSET_THIS_SESSION \
+ (session->features |= SMTP_FEATURE_RSET_REJECTED)
+
+ /*
+ * Pipelining support requires two loops: one loop for sending and one
+ * for receiving. Each loop has its own independent state. Most of the
+ * time the sender can run ahead of the receiver by as much as the TCP
+ * send buffer permits. There are only two places where the sender must
+ * wait for status information from the receiver: once after sending DATA
+ * and once after sending QUIT.
+ *
+ * The sender state advances until the TCP send buffer would overflow, or
+ * until the sender needs status information from the receiver. At that
+ * point the receiver starts processing responses. Once the receiver has
+ * caught up with the sender, the sender resumes sending commands. If the
+ * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO
+ * commands rejected, DATA rejected) it forces the sender to abort the
+ * SMTP dialog with RSET and QUIT.
+ */
+ nrcpt = 0;
+ next_rcpt = send_rcpt = recv_rcpt = recv_done = 0;
+ mail_from_rejected = 0;
+
+ /*
+ * Prepare for disaster. This should not be needed because the design
+ * guarantees that no output is flushed before smtp_chat_resp() is
+ * called.
+ *
+ * 1) Every SMTP command fits entirely in a VSTREAM output buffer.
+ *
+ * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that
+ * there is sufficient space for the command in the output buffer.
+ *
+ * 3) smtp_loop() flushes the output buffer to avoid server timeouts.
+ *
+ * Changing any of these would violate the design, and would likely break
+ * SMTP pipelining.
+ *
+ * We set up the error handler anyway (only upon entry to avoid wasting
+ * resources) because 1) there is code below that expects that VSTREAM
+ * timeouts are enabled, and 2) this allows us to detect if someone broke
+ * Postfix by introducing spurious flush before read operations.
+ */
+ if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR
+ || send_state > SMTP_STATE_QUIT)
+ msg_panic("%s: bad sender state %d (receiver state %d)",
+ myname, send_state, recv_state);
+ smtp_stream_setup(session->stream, *xfer_timeouts[send_state],
+ var_smtp_req_deadline, 0);
+ if ((except = vstream_setjmp(session->stream)) != 0) {
+ msg_warn("smtp_proto: spurious flush before read in send state %d",
+ send_state);
+ RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
+ xfer_states[send_state]) : -1);
+ }
+
+ /*
+ * The main protocol loop.
+ */
+ do {
+
+ /*
+ * Build the next command.
+ */
+ switch (send_state) {
+
+ /*
+ * Sanity check.
+ */
+ default:
+ msg_panic("%s: bad sender state %d", myname, send_state);
+
+ /*
+ * Build the XFORWARD command. With properly sanitized
+ * information, the command length stays within the 512 byte
+ * command line length limit.
+ *
+ * XXX smtpd_xforward_preset() initializes some fields as "unknown"
+ * and some as null; historically, pickup(8) does not send any of
+ * these, and the queue manager presets absent fields to "not
+ * available" except for the rewrite context which is preset to
+ * local by way of migration aid. These definitions need to be
+ * centralized for maintainability.
+ */
+#ifndef CAN_FORWARD_CLIENT_NAME
+#define _ATTR_AVAIL_AND_KNOWN_(val) \
+ (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown"))
+#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL
+#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL
+#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL
+#endif
+
+ case SMTP_STATE_XFORWARD_NAME_ADDR:
+ vstring_strcpy(next_command, XFORWARD_CMD);
+ if ((session->features & SMTP_FEATURE_XFORWARD_NAME)
+ && CAN_FORWARD_CLIENT_NAME(request->client_name)) {
+ vstring_strcat(next_command, " " XFORWARD_NAME "=");
+ xtext_quote_append(next_command, request->client_name, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
+ && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) {
+ vstring_strcat(next_command, " " XFORWARD_ADDR "=");
+ xtext_quote_append(next_command, request->client_addr, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_PORT)
+ && CAN_FORWARD_CLIENT_PORT(request->client_port)) {
+ vstring_strcat(next_command, " " XFORWARD_PORT "=");
+ xtext_quote_append(next_command, request->client_port, "");
+ }
+ if (session->send_proto_helo)
+ next_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ next_state = SMTP_STATE_MAIL;
+ break;
+
+ case SMTP_STATE_XFORWARD_PROTO_HELO:
+ vstring_strcpy(next_command, XFORWARD_CMD);
+ if ((session->features & SMTP_FEATURE_XFORWARD_PROTO)
+ && CAN_FORWARD_PROTO_NAME(request->client_proto)) {
+ vstring_strcat(next_command, " " XFORWARD_PROTO "=");
+ xtext_quote_append(next_command, request->client_proto, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_HELO)
+ && CAN_FORWARD_HELO_NAME(request->client_helo)) {
+ vstring_strcat(next_command, " " XFORWARD_HELO "=");
+ xtext_quote_append(next_command, request->client_helo, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
+ && CAN_FORWARD_IDENT_NAME(request->log_ident)) {
+ vstring_strcat(next_command, " " XFORWARD_IDENT "=");
+ xtext_quote_append(next_command, request->log_ident, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
+ && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) {
+ vstring_strcat(next_command, " " XFORWARD_DOMAIN "=");
+ xtext_quote_append(next_command,
+ strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ?
+ XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, "");
+ }
+ next_state = SMTP_STATE_MAIL;
+ break;
+
+ /*
+ * Build the MAIL FROM command.
+ */
+ case SMTP_STATE_MAIL:
+ request->msg_stats.reuse_count = session->reuse_count;
+ GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
+ smtp_rewrite_generic_internal(session->scratch2, request->sender);
+ smtp_quote_821_address(session->scratch,
+ vstring_str(session->scratch2));
+ vstring_sprintf(next_command, "MAIL FROM:<%s>",
+ vstring_str(session->scratch));
+ /* XXX Don't announce SIZE if we're going to MIME downgrade. */
+ if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */
+ && !SMTP_MIME_DOWNGRADE(session, request))
+ vstring_sprintf_append(next_command, " SIZE=%lu",
+ request->data_size);
+ if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */
+ if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0)
+ vstring_strcat(next_command, " BODY=8BITMIME");
+ else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0)
+ vstring_strcat(next_command, " BODY=7BIT");
+ else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0)
+ msg_warn("%s: unknown content encoding: %s",
+ request->queue_id, request->encoding);
+ }
+ if (session->features & SMTP_FEATURE_DSN) {
+ if (request->dsn_envid[0]) {
+ vstring_sprintf_append(next_command, " ENVID=");
+ xtext_quote_append(next_command, request->dsn_envid, "+=");
+ }
+ if (request->dsn_ret)
+ vstring_sprintf_append(next_command, " RET=%s",
+ dsn_ret_str(request->dsn_ret));
+ }
+
+ /*
+ * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8
+ * and the sender requested SMTPUTF8 support.
+ *
+ * If the sender requested SMTPUTF8 but the remote SMTP server does
+ * not support SMTPUTF8, then we have already determined earlier
+ * that delivering this message without SMTPUTF8 will not break
+ * the SMTPUTF8 promise that was made to the sender.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
+ && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0)
+ vstring_strcat(next_command, " SMTPUTF8");
+
+ /*
+ * We authenticate the local MTA only, but not the sender.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtp_sasl_enable
+ && var_smtp_dummy_mail_auth
+ && (session->features & SMTP_FEATURE_AUTH))
+ vstring_strcat(next_command, " AUTH=<>");
+#endif
+
+ /*
+ * CVE-2009-3555 (TLS renegotiation). Try to detect a mail
+ * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA
+ * commands to our TLS session.
+ *
+ * For the attack to succeed, the remote SMTP server must reply to
+ * the malicious EHLO/MAIL/RCPT/DATA commands after completing
+ * TLS (re)negotiation, so that the replies arrive in our TLS
+ * session (otherwise the Postfix SMTP client would time out
+ * waiting for an answer). With some luck we can detect this
+ * specific attack as a server MAIL reply that arrives before we
+ * send our own MAIL command.
+ *
+ * We don't apply this test to the HELO command because the result
+ * would be very timing sensitive, and we don't apply this test
+ * to RCPT and DATA replies because these may be pipelined for
+ * legitimate reasons.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_blk_early_mail_reply
+ && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0
+ && (vstream_peek(session->stream) > 0
+ || peekfd(vstream_fileno(session->stream)) > 0))
+ session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY;
+#endif
+
+ /*
+ * We now return to our regular broadcast.
+ */
+ next_state = SMTP_STATE_RCPT;
+ break;
+
+ /*
+ * Build one RCPT TO command before we have seen the MAIL FROM
+ * response.
+ */
+ case SMTP_STATE_RCPT:
+ rcpt = request->rcpt_list.info + send_rcpt;
+ smtp_rewrite_generic_internal(session->scratch2, rcpt->address);
+ smtp_quote_821_address(session->scratch,
+ vstring_str(session->scratch2));
+ vstring_sprintf(next_command, "RCPT TO:<%s>",
+ vstring_str(session->scratch));
+ if (session->features & SMTP_FEATURE_DSN) {
+ /* XXX DSN xtext encode address value not type. */
+ const char *orcpt_type_addr = rcpt->dsn_orcpt;
+
+ /* Fix 20140706: don't use empty rcpt->orig_addr. */
+ if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) {
+ quote_822_local(session->scratch, rcpt->orig_addr);
+ vstring_sprintf(session->scratch2, "%s;%s",
+ /* Fix 20140707: sender must request SMTPUTF8. */
+ (request->smtputf8 != 0
+ && !allascii(vstring_str(session->scratch))) ?
+ "utf-8" : "rfc822",
+ vstring_str(session->scratch));
+ orcpt_type_addr = vstring_str(session->scratch2);
+ }
+ if (orcpt_type_addr[0] != 0) {
+ /* Fix 20140706: don't send unquoted ORCPT. */
+ /* Fix 20140707: quoting method must match orcpt type. */
+ /* Fix 20140707: handle uxtext encoder errors. */
+ if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) {
+ if (uxtext_quote(session->scratch,
+ orcpt_type_addr, "+=") != 0)
+ vstring_sprintf_append(next_command, " ORCPT=%s",
+ vstring_str(session->scratch));
+ } else {
+ xtext_quote(session->scratch, orcpt_type_addr, "=");
+ vstring_sprintf_append(next_command, " ORCPT=%s",
+ vstring_str(session->scratch));
+ }
+ }
+ if (rcpt->dsn_notify)
+ vstring_sprintf_append(next_command, " NOTIFY=%s",
+ dsn_notify_str(rcpt->dsn_notify));
+ }
+ if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state))
+ next_state = (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
+ SMTP_STATE_ABORT : SMTP_STATE_DATA;
+ break;
+
+ /*
+ * Build the DATA command before we have seen all the RCPT TO
+ * responses.
+ */
+ case SMTP_STATE_DATA:
+ vstring_strcpy(next_command, "DATA");
+ next_state = SMTP_STATE_DOT;
+ break;
+
+ /*
+ * Build the "." command after we have seen the DATA response
+ * (DATA is a protocol synchronization point).
+ *
+ * Changing the connection caching state here is safe because it
+ * affects none of the not-yet processed replies to
+ * already-generated commands.
+ */
+ case SMTP_STATE_DOT:
+ vstring_strcpy(next_command, ".");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * The SMTP_STATE_ABORT sender state is entered by the sender
+ * when it has verified all recipients; or it is entered by the
+ * receiver when all recipients are verified or rejected, and is
+ * then left before the bottom of the main loop.
+ *
+ * Changing the connection caching state here is safe because there
+ * are no not-yet processed replies to already-generated
+ * commands.
+ */
+ case SMTP_STATE_ABORT:
+ vstring_strcpy(next_command, "RSET");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * Build the RSET command. This is entered as initial state from
+ * smtp_rset() and has its own dedicated state transitions. It is
+ * used to find out the status of a cached session before
+ * attempting mail delivery.
+ */
+ case SMTP_STATE_RSET:
+ vstring_strcpy(next_command, "RSET");
+ next_state = SMTP_STATE_LAST;
+ break;
+
+ /*
+ * Build the QUIT command before we have seen the "." or RSET
+ * response. This is entered as initial state from smtp_quit(),
+ * or is reached near the end of any non-cached session.
+ *
+ * Changing the connection caching state here is safe. If this
+ * command is pipelined together with a preceding command, then
+ * connection caching was already turned off. Do not clobber the
+ * "bad connection" flag.
+ */
+ case SMTP_STATE_QUIT:
+ vstring_strcpy(next_command, "QUIT");
+ next_state = SMTP_STATE_LAST;
+ if (THIS_SESSION_IS_CACHED)
+ DONT_CACHE_THIS_SESSION;
+ break;
+
+ /*
+ * The final sender state has no action associated with it.
+ */
+ case SMTP_STATE_LAST:
+ VSTRING_RESET(next_command);
+ break;
+ }
+ VSTRING_TERMINATE(next_command);
+
+ /*
+ * Process responses until the receiver has caught up. Vstreams
+ * automatically flush buffered output when reading new data.
+ *
+ * Flush unsent output if command pipelining is off or if no I/O
+ * happened for a while. This limits the accumulation of client-side
+ * delays in pipelined sessions.
+ *
+ * The PIPELINING engine will flush the VSTREAM buffer if the sender
+ * could otherwise produce more output than fits the PIPELINING
+ * buffer. This generally works because we know exactly how much
+ * output we produced since the last time that the sender and
+ * receiver synchronized the SMTP state. However this logic is not
+ * applicable after the sender enters the DATA phase, where it does
+ * not synchronize with the receiver until the <CR><LF>.<CR><LF>.
+ * Thus, the PIPELINING engine no longer knows how much data is
+ * pending in the TCP send buffer. For this reason, if PIPELINING is
+ * enabled, we always pipeline QUIT after <CR><LF>.<CR><LF>. This is
+ * safe because once the receiver reads <CR><LF>.<CR><LF>, its TCP
+ * stack either has already received the QUIT<CR><LF>, or else it
+ * acknowledges all bytes up to and including <CR><LF>.<CR><LF>,
+ * making room in the sender's TCP stack for QUIT<CR><LF>.
+ */
+#define CHECK_PIPELINING_BUFSIZE \
+ (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT)
+
+ if (SENDER_IN_WAIT_STATE
+ || (SENDER_IS_AHEAD
+ && ((session->features & SMTP_FEATURE_PIPELINING) == 0
+ || (CHECK_PIPELINING_BUFSIZE
+ && (VSTRING_LEN(next_command) + 2
+ + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND)
+ > PIPELINING_BUFSIZE))
+ || time((time_t *) 0)
+ - vstream_ftime(session->stream) > 10))) {
+ while (SENDER_IS_AHEAD) {
+
+ /*
+ * Sanity check.
+ */
+ if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR
+ || recv_state > SMTP_STATE_QUIT)
+ msg_panic("%s: bad receiver state %d (sender state %d)",
+ myname, recv_state, send_state);
+
+ /*
+ * Receive the next server response. Use the proper timeout,
+ * and log the proper client state in case of trouble.
+ *
+ * XXX If we lose the connection before sending end-of-data,
+ * find out if the server sent a premature end-of-data reply.
+ * If this read attempt fails, report "lost connection while
+ * sending message body", not "lost connection while sending
+ * end-of-data".
+ *
+ * "except" becomes zero just above the protocol loop, and stays
+ * zero or triggers an early return from the loop. In just
+ * one case: loss of the connection when sending the message
+ * body, we record the exception, and keep processing in the
+ * hope of detecting a premature 5XX. We must be careful to
+ * not clobber this non-zero value once it is set. The
+ * variable need not survive longjmp() calls, since the only
+ * setjmp() which does not return early is the one sets this
+ * condition, subsequent failures always return early.
+ */
+#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF)
+
+ smtp_stream_setup(session->stream, *xfer_timeouts[recv_state],
+ var_smtp_req_deadline, 0);
+ if (LOST_CONNECTION_INSIDE_DATA) {
+ if (vstream_setjmp(session->stream) != 0)
+ RETURN(smtp_stream_except(state, SMTP_ERR_EOF,
+ "sending message body"));
+ } else {
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
+ xfer_states[recv_state]) : -1);
+ }
+ resp = smtp_chat_resp(session);
+
+ /*
+ * Process the response.
+ */
+ switch (recv_state) {
+
+ /*
+ * Process the XFORWARD response.
+ */
+ case SMTP_STATE_XFORWARD_NAME_ADDR:
+ if (resp->code / 100 != 2)
+ msg_warn("host %s said: %s (in reply to %s)",
+ session->namaddrport,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]);
+ if (session->send_proto_helo)
+ recv_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ recv_state = SMTP_STATE_MAIL;
+ break;
+
+ case SMTP_STATE_XFORWARD_PROTO_HELO:
+ if (resp->code / 100 != 2)
+ msg_warn("host %s said: %s (in reply to %s)",
+ session->namaddrport,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]);
+ recv_state = SMTP_STATE_MAIL;
+ break;
+
+ /*
+ * Process the MAIL FROM response. When the server
+ * rejects the sender, set the mail_from_rejected flag so
+ * that the receiver may apply a course correction.
+ */
+ case SMTP_STATE_MAIL:
+ if (resp->code / 100 != 2) {
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_MAIL]);
+ mail_from_rejected = 1;
+ }
+
+ /*
+ * CVE-2009-3555 (TLS renegotiation). Whatever it was
+ * that arrived before we sent our MAIL FROM command, it
+ * was not a fatal-level TLS alert message. It could be a
+ * warning-level TLS alert message, or a ChangeCipherSpec
+ * message, but such messages are not normally sent in
+ * the middle of a TLS session. We disconnect and try
+ * again later.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_blk_early_mail_reply
+ && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) {
+ smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "unexpected server message");
+ msg_warn("server %s violates %s policy",
+ session->namaddr,
+ VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY));
+ mail_from_rejected = 1;
+ }
+#endif
+
+ /*
+ * We now return to our regular broadcast.
+ */
+ recv_state = SMTP_STATE_RCPT;
+ break;
+
+ /*
+ * Process one RCPT TO response. If MAIL FROM was
+ * rejected, ignore RCPT TO responses: all recipients are
+ * dead already. When all recipients are rejected the
+ * receiver may apply a course correction.
+ *
+ * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply
+ * must be treated as if the server replied with 452.
+ * However, this causes "too much mail data" to be
+ * treated as a recoverable error, which is wrong. I'll
+ * stick with RFC 821.
+ */
+ case SMTP_STATE_RCPT:
+ if (!mail_from_rejected) {
+#ifdef notdef
+ if (resp->code == 552) {
+ resp->code = 452;
+ resp->dsn[0] = '4';
+ }
+#endif
+ rcpt = request->rcpt_list.info + recv_rcpt;
+ if (resp->code / 100 == 2) {
+ if (!smtp_mode) {
+ if (survivors == 0)
+ survivors = (int *)
+ mymalloc(request->rcpt_list.len
+ * sizeof(int));
+ survivors[nrcpt] = recv_rcpt;
+ }
+ ++nrcpt;
+ /* If trace-only, mark the recipient done. */
+ if (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ } else {
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_RCPT]);
+ }
+ }
+ /* If trace-only, send RSET instead of DATA. */
+ if (++recv_rcpt == SMTP_RCPT_LEFT(state))
+ recv_state = (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
+ SMTP_STATE_ABORT : SMTP_STATE_DATA;
+ /* XXX Also: record if non-delivering session. */
+ break;
+
+ /*
+ * Process the DATA response. When the server rejects
+ * DATA, set nrcpt to a negative value so that the
+ * receiver can apply a course correction.
+ */
+ case SMTP_STATE_DATA:
+ recv_state = SMTP_STATE_DOT;
+ if (resp->code / 100 != 3) {
+ if (nrcpt > 0)
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DATA]);
+ nrcpt = -1;
+ }
+
+ /*
+ * In the case of a successful address probe with target
+ * equal to DATA, the remote server is now in the DATA
+ * state, and therefore we must not make any further
+ * attempt to send or receive on this connection. This
+ * means that we cannot not reuse the general-purpose
+ * course-correction logic below which sends RSET (and
+ * perhaps QUIT). Instead we "jump" straight to the exit
+ * and force an unceremonious disconnect.
+ */
+ else if (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_DATA) {
+ for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (!SMTP_RCPT_ISMARKED(rcpt)) {
+ translit(resp->str, "\n", " ");
+ SMTP_RESP_SET_DSN(resp, "2.0.0");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ DONT_CACHE_THIS_SESSION;
+ send_state = recv_state = SMTP_STATE_LAST;
+ }
+ break;
+
+ /*
+ * Process the end of message response. Ignore the
+ * response when no recipient was accepted: all
+ * recipients are dead already, and the next receiver
+ * state is SMTP_STATE_LAST/QUIT regardless. Otherwise,
+ * if the message transfer fails, bounce all remaining
+ * recipients, else cross off the recipients that were
+ * delivered.
+ */
+ case SMTP_STATE_DOT:
+ GETTIMEOFDAY(&request->msg_stats.deliver_done);
+ if (smtp_mode) {
+ if (nrcpt > 0) {
+ if (resp->code / 100 != 2) {
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DOT]);
+ } else {
+ for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (!SMTP_RCPT_ISMARKED(rcpt)) {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * With LMTP we have one response per accepted RCPT TO
+ * command. Stay in the SMTP_STATE_DOT state until we
+ * have collected all responses.
+ */
+ else {
+ if (nrcpt > 0) {
+ rcpt = request->rcpt_list.info
+ + survivors[recv_done++];
+ if (resp->code / 100 != 2) {
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DOT]);
+ } else {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: got %d of %d end-of-data replies",
+ myname, recv_done, nrcpt);
+ if (recv_done < nrcpt)
+ break;
+ }
+
+ /*
+ * XXX Do not change the connection caching state here,
+ * even if the connection caching timer expired between
+ * generating the command and processing the reply,
+ * otherwise the sender and receiver loops get out of
+ * sync. The caller will call smtp_quit() if appropriate.
+ */
+ if (var_skip_quit_resp || THIS_SESSION_IS_CACHED
+ || LOST_CONNECTION_INSIDE_DATA)
+ recv_state = SMTP_STATE_LAST;
+ else
+ recv_state = SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * Receive the RSET response.
+ *
+ * The SMTP_STATE_ABORT sender state is entered by the
+ * sender when it has verified all recipients; or it is
+ * entered by the receiver when all recipients are
+ * verified or rejected, and is then left before the
+ * bottom of the main loop.
+ *
+ * XXX Do not change the connection caching state here, even
+ * if the server rejected RSET or if the connection
+ * caching timer expired between generating the command
+ * and processing the reply, otherwise the sender and
+ * receiver loops get out of sync. The caller will call
+ * smtp_quit() if appropriate.
+ */
+ case SMTP_STATE_ABORT:
+ recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT);
+ break;
+
+ /*
+ * This is the initial receiver state from smtp_rset().
+ * It is used to find out the status of a cached session
+ * before attempting mail delivery.
+ */
+ case SMTP_STATE_RSET:
+ if (resp->code / 100 != 2)
+ CANT_RSET_THIS_SESSION;
+ recv_state = SMTP_STATE_LAST;
+ break;
+
+ /*
+ * Receive, but otherwise ignore, the QUIT response.
+ */
+ case SMTP_STATE_QUIT:
+ recv_state = SMTP_STATE_LAST;
+ break;
+ }
+ }
+
+ /*
+ * At this point, the sender and receiver are fully synchronized.
+ */
+
+ /*
+ * We know the server response to every command that was sent.
+ * Apply a course correction if necessary: the sender wants to
+ * send RCPT TO but MAIL FROM was rejected; the sender wants to
+ * send DATA but all recipients were rejected; the sender wants
+ * to deliver the message but DATA was rejected.
+ */
+ if ((send_state == SMTP_STATE_RCPT && mail_from_rejected)
+ || (send_state == SMTP_STATE_DATA && nrcpt == 0)
+ || (send_state == SMTP_STATE_DOT && nrcpt < 0)) {
+ send_state = recv_state = SMTP_STATE_ABORT;
+ send_rcpt = recv_rcpt = 0;
+ vstring_strcpy(next_command, "RSET");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ /* XXX Also: record if non-delivering session. */
+ next_rcpt = 0;
+ }
+ }
+
+ /*
+ * Make the next sender state the current sender state.
+ */
+ if (send_state == SMTP_STATE_LAST)
+ continue;
+
+ /*
+ * Special case if the server accepted the DATA command. If the
+ * server accepted at least one recipient send the entire message.
+ * Otherwise, just send "." as per RFC 2197.
+ *
+ * XXX If there is a hard MIME error while downgrading to 7-bit mail,
+ * disconnect ungracefully, because there is no other way to cancel a
+ * transaction in progress.
+ */
+ if (send_state == SMTP_STATE_DOT && nrcpt > 0) {
+
+ smtp_stream_setup(session->stream, var_smtp_data1_tmout,
+ var_smtp_req_deadline, var_smtp_min_data_rate);
+
+ if ((except = vstream_setjmp(session->stream)) == 0) {
+
+ if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0)
+ msg_fatal("seek queue file: %m");
+
+ downgrading = SMTP_MIME_DOWNGRADE(session, request);
+
+ /*
+ * XXX Don't downgrade just because generic_maps is turned
+ * on.
+ */
+#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks)
+
+ if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS)
+ session->mime_state = mime_state_alloc(downgrading ?
+ MIME_OPT_DOWNGRADE
+ | MIME_OPT_REPORT_NESTING :
+ SMTP_ANY_CHECKS == 0 ?
+ MIME_OPT_DISABLE_MIME :
+ 0,
+ smtp_generic_maps
+ || smtp_header_checks ?
+ smtp_header_rewrite :
+ smtp_header_out,
+ (MIME_STATE_ANY_END) 0,
+ smtp_body_checks ?
+ smtp_body_rewrite :
+ smtp_text_out,
+ (MIME_STATE_ANY_END) 0,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) state);
+ state->space_left = var_smtp_line_limit;
+
+ if ((smtp_cli_attr.flags & SMTP_CLI_MASK_ADD_HEADERS) != 0
+ && smtp_out_add_headers(state) < 0)
+ RETURN(0);
+
+ while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (smtp_out_raw_or_mime(state, rec_type,
+ session->scratch) < 0)
+ RETURN(0);
+ prev_type = rec_type;
+ }
+
+ if (session->mime_state) {
+
+ /*
+ * The cleanup server normally ends MIME content with a
+ * normal text record. The following code is needed to
+ * flush an internal buffer when someone submits 8-bit
+ * mail not ending in newline via /usr/sbin/sendmail
+ * while MIME input processing is turned off, and MIME
+ * 8bit->7bit conversion is requested upon delivery.
+ *
+ * Or some error while doing generic address mapping.
+ */
+ mime_errs =
+ mime_state_update(session->mime_state, rec_type, "", 0);
+ if (mime_errs) {
+ smtp_mime_fail(state, mime_errs);
+ RETURN(0);
+ }
+ } else if (prev_type == REC_TYPE_CONT) /* missing newline */
+ smtp_fputs("", 0, session->stream);
+ if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) {
+ smtp_flush(session->stream);/* hurts performance */
+ sleep(var_smtp_pix_delay); /* not to mention this */
+ }
+ if (vstream_ferror(state->src))
+ msg_fatal("queue file read error");
+ if (rec_type != REC_TYPE_XTRA) {
+ msg_warn("%s: bad record type: %d in message content",
+ request->queue_id, rec_type);
+ fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.3.0"),
+ "unreadable mail queue entry");
+ /* Bailing out, abort stream with prejudice */
+ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+ DONT_USE_FORBIDDEN_SESSION;
+ /* If bounce_append() succeeded, status is still 0 */
+ if (state->status == 0)
+ (void) mark_corrupt(state->src);
+ /* Don't override smtp_mesg_fail() here. */
+ RETURN(fail_status);
+ }
+ } else {
+ if (!LOST_CONNECTION_INSIDE_DATA)
+ RETURN(smtp_stream_except(state, except,
+ "sending message body"));
+
+ /*
+ * We will clear the stream error flag to try and read a
+ * premature 5XX response, so it is important to flush any
+ * unwritten data. Otherwise, we will try to flush it again
+ * before reading, which may incur an unnecessary delay and
+ * will prevent the reading of any response that is not
+ * already buffered (bundled with the DATA 354 response).
+ *
+ * Not much point in sending QUIT at this point, skip right to
+ * SMTP_STATE_LAST. The read engine above will likewise avoid
+ * looking for a QUIT response.
+ */
+ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE);
+ next_state = SMTP_STATE_LAST;
+ }
+ }
+
+ /*
+ * Copy the next command to the buffer and update the sender state.
+ */
+ if (except == 0) {
+ smtp_chat_cmd(session, "%s", vstring_str(next_command));
+ } else {
+ DONT_CACHE_THIS_SESSION;
+ }
+ send_state = next_state;
+ send_rcpt = next_rcpt;
+ } while (recv_state != SMTP_STATE_LAST);
+ RETURN(0);
+}
+
+/* smtp_xfer - send a batch of envelope information and the message data */
+
+int smtp_xfer(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_RESP fake;
+ int send_state;
+ int recv_state;
+ int send_name_addr;
+ int result;
+
+ /*
+ * Sanity check. Recipients should be unmarked at this point.
+ */
+ if (SMTP_RCPT_LEFT(state) <= 0)
+ msg_panic("smtp_xfer: bad recipient count: %d",
+ SMTP_RCPT_LEFT(state));
+ if (SMTP_RCPT_ISMARKED(request->rcpt_list.info))
+ msg_panic("smtp_xfer: bad recipient status: %d",
+ request->rcpt_list.info->u.status);
+
+ /*
+ * See if we should even try to send this message at all. This code sits
+ * here rather than in the EHLO processing code, because of SMTP
+ * connection caching.
+ */
+ if (session->size_limit > 0 && session->size_limit < request->data_size) {
+ smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.3.4"),
+ "message size %lu exceeds size limit %.0f of server %s",
+ request->data_size, (double) session->size_limit,
+ session->namaddr);
+ /* Redundant. We abort this delivery attempt. */
+ state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;
+ return (0);
+ }
+
+ /*
+ * Use XFORWARD to forward the origin of this email message across an
+ * SMTP-based content filter. Send client attribute information only if
+ * it exists (i.e. remote submission). Local submissions have no client
+ * attributes; the mail will appear to originate from the content filter
+ * which is acceptable.
+ */
+ send_name_addr =
+ var_smtp_send_xforward
+ && (((session->features & SMTP_FEATURE_XFORWARD_NAME)
+ && CAN_FORWARD_CLIENT_NAME(request->client_name))
+ || ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
+ && CAN_FORWARD_CLIENT_ADDR(request->client_addr))
+ || ((session->features & SMTP_FEATURE_XFORWARD_PORT)
+ && CAN_FORWARD_CLIENT_PORT(request->client_port)));
+ session->send_proto_helo =
+ var_smtp_send_xforward
+ && (((session->features & SMTP_FEATURE_XFORWARD_PROTO)
+ && CAN_FORWARD_PROTO_NAME(request->client_proto))
+ || ((session->features & SMTP_FEATURE_XFORWARD_HELO)
+ && CAN_FORWARD_HELO_NAME(request->client_helo))
+ || ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
+ && CAN_FORWARD_IDENT_NAME(request->log_ident))
+ || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
+ && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)));
+ if (send_name_addr)
+ recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR;
+ else if (session->send_proto_helo)
+ recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ recv_state = send_state = SMTP_STATE_MAIL;
+
+ /*
+ * Remember this session's "normal completion", even if the server 4xx-ed
+ * some or all recipients. Connection or handshake errors with a later MX
+ * host should not cause this destination be marked as unreachable.
+ */
+ result = smtp_loop(state, send_state, recv_state);
+
+ if (result == 0
+ /* Just in case */
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;
+
+ return (result);
+}
+
+/* smtp_rset - send a lone RSET command */
+
+int smtp_rset(SMTP_STATE *state)
+{
+
+ /*
+ * This works because SMTP_STATE_RSET is a dedicated sender/recipient
+ * entry state, with SMTP_STATE_LAST as next sender/recipient state.
+ */
+ return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET));
+}
+
+/* smtp_quit - send a lone QUIT command */
+
+int smtp_quit(SMTP_STATE *state)
+{
+
+ /*
+ * This works because SMTP_STATE_QUIT is the last state with a sender
+ * action, with SMTP_STATE_LAST as the next sender/recipient state.
+ */
+ return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT));
+}
diff --git a/src/smtp/smtp_rcpt.c b/src/smtp/smtp_rcpt.c
new file mode 100644
index 0000000..6608ea8
--- /dev/null
+++ b/src/smtp/smtp_rcpt.c
@@ -0,0 +1,226 @@
+/*++
+/* NAME
+/* smtp_rcpt 3
+/* SUMMARY
+/* application-specific recipient list operations
+/* SYNOPSIS
+/* #include <smtp.h>
+/*
+/* SMTP_RCPT_INIT(state)
+/* SMTP_STATE *state;
+/*
+/* SMTP_RCPT_DROP(state, rcpt)
+/* SMTP_STATE *state;
+/* RECIPIENT *rcpt;
+/*
+/* SMTP_RCPT_KEEP(state, rcpt)
+/* SMTP_STATE *state;
+/* RECIPIENT *rcpt;
+/*
+/* SMTP_RCPT_ISMARKED(rcpt)
+/* RECIPIENT *rcpt;
+/*
+/* void smtp_rcpt_cleanup(SMTP_STATE *state)
+/* SMTP_STATE *state;
+/*
+/* int SMTP_RCPT_LEFT(state)
+/* SMTP_STATE *state;
+/*
+/* int SMTP_RCPT_MARK_COUNT(state)
+/* SMTP_STATE *state;
+/*
+/* void smtp_rcpt_done(state, resp, rcpt)
+/* SMTP_STATE *state;
+/* SMTP_RESP *resp;
+/* RECIPIENT *rcpt;
+/* DESCRIPTION
+/* This module implements application-specific mark and sweep
+/* operations on recipient lists. Operation is as follows:
+/* .IP \(bu
+/* In the course of a delivery attempt each recipient is
+/* marked either as DROP (remove from recipient list) or KEEP
+/* (deliver to alternate mail server).
+/* .IP \(bu
+/* After a delivery attempt any recipients marked DROP are deleted
+/* from the request, and the left-over recipients are unmarked.
+/* .PP
+/* The mark/sweep algorithm is implemented in a redundant manner,
+/* and ensures that all recipients are explicitly accounted for.
+/*
+/* Operations with upper case names are implemented by macros
+/* whose arguments may be evaluated more than once.
+/*
+/* SMTP_RCPT_INIT() initializes application-specific recipient
+/* information and must be called before the first delivery attempt.
+/*
+/* SMTP_RCPT_DROP() marks the specified recipient as DROP (remove
+/* from recipient list). It is an error to mark an already marked
+/* recipient.
+/*
+/* SMTP_RCPT_KEEP() marks the specified recipient as KEEP (deliver
+/* to alternate mail server). It is an error to mark an already
+/* marked recipient.
+/*
+/* SMTP_RCPT_ISMARKED() returns non-zero when the specified
+/* recipient is marked.
+/*
+/* SMTP_RCPT_LEFT() returns the number of left_over recipients
+/* (the total number of marked and non-marked recipients).
+/*
+/* SMTP_RCPT_MARK_COUNT() returns the number of left_over
+/* recipients that are marked.
+/*
+/* smtp_rcpt_cleanup() cleans up the in-memory recipient list.
+/* It removes the recipients marked DROP from the left-over
+/* recipients, unmarks the left-over recipients, and enforces
+/* the requirement that all recipients are marked upon entry.
+/*
+/* smtp_rcpt_done() logs that a recipient is completed and upon
+/* success it marks the recipient as done in the queue file.
+/* Finally, it marks the in-memory recipient as DROP.
+/*
+/* Note: smtp_rcpt_done() may change the order of the recipient
+/* list.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* When a recipient can't be logged as completed, the recipient is
+/* logged as deferred instead.
+/* BUGS
+/* The single recipient list abstraction dates from the time
+/* that the SMTP client would give up after one SMTP session,
+/* so that each recipient was either bounced, delivered or
+/* deferred. Implicitly, all recipients were marked as DROP.
+/*
+/* This abstraction is less convenient when an SMTP client
+/* must be able to deliver left-over recipients to a backup
+/* host. It might be more natural to have an input list with
+/* recipients to deliver, and an output list with left-over
+/* recipients.
+/* 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 <stdlib.h> /* smtp_rcpt_cleanup */
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <deliver_request.h> /* smtp_rcpt_done */
+#include <deliver_completed.h> /* smtp_rcpt_done */
+#include <sent.h> /* smtp_rcpt_done */
+#include <dsn_mask.h> /* smtp_rcpt_done */
+
+/* Application-specific. */
+
+#include <smtp.h>
+
+/* smtp_rcpt_done - mark recipient as done or else */
+
+void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ DSN_BUF *why = state->why;
+ const char *dsn_action = "relayed";
+ int status;
+
+ /*
+ * Assume this was intermediate delivery when the server announced DSN
+ * support, and don't send a DSN "SUCCESS" notification.
+ */
+ if (session->features & SMTP_FEATURE_DSN)
+ rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS;
+
+ /*
+ * Assume this was final delivery when the LMTP server announced no DSN
+ * support. In backwards compatibility mode, send a "relayed" instead of
+ * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify"
+ * the expression. The redundancy is for clarity. It is trivially
+ * eliminated by the compiler. There is no need to sacrifice clarity for
+ * the sake of "performance".
+ */
+ if ((session->features & SMTP_FEATURE_DSN) == 0
+ && !smtp_mode
+ && (smtp_cli_attr.flags & SMTP_CLI_FLAG_FINAL_DELIVERY) != 0)
+ dsn_action = "delivered";
+
+ /*
+ * Report success and delete the recipient from the delivery request.
+ * Defer if the success can't be reported.
+ *
+ * Note: the DSN action is ignored in case of address probes.
+ */
+ dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host),
+ DSB_DTYPE_SMTP, resp->str, "%s", resp->str);
+
+ status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats, rcpt,
+ session->namaddrport, DSN_FROM_DSN_BUF(why));
+ if (status == 0)
+ if (request->flags & DEL_REQ_FLAG_SUCCESS)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ state->status |= status;
+}
+
+/* smtp_rcpt_cleanup_callback - qsort callback */
+
+static int smtp_rcpt_cleanup_callback(const void *a, const void *b)
+{
+ return (((RECIPIENT *) a)->u.status - ((RECIPIENT *) b)->u.status);
+}
+
+/* smtp_rcpt_cleanup - purge completed recipients from request */
+
+void smtp_rcpt_cleanup(SMTP_STATE *state)
+{
+ RECIPIENT_LIST *rcpt_list = &state->request->rcpt_list;
+ RECIPIENT *rcpt;
+
+ /*
+ * Sanity checks.
+ */
+ if (state->rcpt_drop + state->rcpt_keep != state->rcpt_left)
+ msg_panic("smtp_rcpt_cleanup: recipient count mismatch: %d+%d!=%d",
+ state->rcpt_drop, state->rcpt_keep, state->rcpt_left);
+
+ /*
+ * Recipients marked KEEP sort before recipients marked DROP. Skip the
+ * sorting in the common case that all recipients are marked the same.
+ */
+ if (state->rcpt_drop > 0 && state->rcpt_keep > 0)
+ qsort((void *) rcpt_list->info, state->rcpt_left,
+ sizeof(rcpt_list->info[0]), smtp_rcpt_cleanup_callback);
+
+ /*
+ * Truncate the recipient list and unmark the left-over recipients.
+ */
+ state->rcpt_left = state->rcpt_keep;
+ for (rcpt = rcpt_list->info; rcpt < rcpt_list->info + state->rcpt_left; rcpt++)
+ rcpt->u.status = 0;
+ state->rcpt_drop = state->rcpt_keep = 0;
+}
diff --git a/src/smtp/smtp_reuse.c b/src/smtp/smtp_reuse.c
new file mode 100644
index 0000000..f93ba29
--- /dev/null
+++ b/src/smtp/smtp_reuse.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* smtp_reuse 3
+/* SUMMARY
+/* SMTP session cache glue
+/* SYNOPSIS
+/* #include <smtp.h>
+/* #include <smtp_reuse.h>
+/*
+/* void smtp_save_session(state, name_key_flags, endp_key_flags)
+/* SMTP_STATE *state;
+/* int name_key_flags;
+/* int endp_key_flags;
+/*
+/* SMTP_SESSION *smtp_reuse_nexthop(state, name_key_flags)
+/* SMTP_STATE *state;
+/* int name_key_flags;
+/*
+/* SMTP_SESSION *smtp_reuse_addr(state, endp_key_flags)
+/* SMTP_STATE *state;
+/* int endp_key_flags;
+/* DESCRIPTION
+/* This module implements the SMTP client specific interface to
+/* the generic session cache infrastructure.
+/*
+/* The caller needs to include additional state in _key_flags
+/* to avoid false sharing of SASL-authenticated or TLS-authenticated
+/* sessions.
+/*
+/* smtp_save_session() stores the current session under the
+/* delivery request next-hop logical destination (if applicable)
+/* and under the remote server address. The SMTP_SESSION object
+/* is destroyed.
+/*
+/* smtp_reuse_nexthop() looks up a cached session by its
+/* delivery request next-hop destination, and verifies that
+/* the session is still alive. The restored session information
+/* includes the "best MX" bit and overrides the iterator dest,
+/* host and addr fields. The result is null in case of failure.
+/*
+/* smtp_reuse_addr() looks up a cached session by its server
+/* address, and verifies that the session is still alive.
+/* The restored session information does not include the "best
+/* MX" bit, and does not override the iterator dest, host and
+/* addr fields. The result is null in case of failure.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP client state, including the current session, the original
+/* next-hop domain, etc.
+/* .IP name_key_flags
+/* Explicit declaration of context that should be used to look
+/* up a cached connection by its logical destination.
+/* See smtp_key(3) for details.
+/* .IP endp_key_flags
+/* Explicit declaration of context that should be used to look
+/* up a cached connection by its server address.
+/* See smtp_key(3) for details.
+/* 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <scache.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+#include <smtp_reuse.h>
+
+ /*
+ * Key field delimiter, and place holder field value for
+ * unavailable/inapplicable information.
+ */
+#define SMTP_REUSE_KEY_DELIM_NA "\n*"
+
+/* smtp_save_session - save session under next-hop name and server address */
+
+void smtp_save_session(SMTP_STATE *state, int name_key_flags,
+ int endp_key_flags)
+{
+ SMTP_SESSION *session = state->session;
+ int fd;
+
+ /*
+ * Encode the delivery request next-hop destination, if applicable. Reuse
+ * storage that is also used for cache lookup queries.
+ *
+ * HAVE_SCACHE_REQUEST_NEXTHOP() controls whether or not to reuse or cache a
+ * connection by its delivery request next-hop destination. The idea is
+ * 1) to allow a reuse request to skip over bad hosts, and 2) to avoid
+ * caching a less-preferred connection when a more-preferred connection
+ * was possible.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, name_key_flags);
+
+ /*
+ * Encode the physical endpoint name. Reuse storage that is also used for
+ * cache lookup queries.
+ */
+ smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, endp_key_flags);
+
+ /*
+ * Passivate the SMTP_SESSION object, destroying the object in the
+ * process. Reuse storage that is also used for cache lookup results.
+ */
+ fd = smtp_session_passivate(session, state->dest_prop, state->endp_prop);
+ state->session = 0;
+
+ /*
+ * Save the session under the delivery request next-hop name, if
+ * applicable.
+ *
+ * XXX The logical to physical binding can be kept for as long as the DNS
+ * allows us to (but that could result in the caching of lots of unused
+ * bindings). The session should be idle for no more than 30 seconds or
+ * so.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ scache_save_dest(smtp_scache, var_smtp_cache_conn,
+ STR(state->dest_label), STR(state->dest_prop),
+ STR(state->endp_label));
+
+ /*
+ * Save every good session under its physical endpoint address.
+ */
+ scache_save_endp(smtp_scache, var_smtp_cache_conn, STR(state->endp_label),
+ STR(state->endp_prop), fd);
+}
+
+/* smtp_reuse_common - common session reuse code */
+
+static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
+ const char *label)
+{
+ const char *myname = "smtp_reuse_common";
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_SESSION *session;
+
+ /*
+ * Re-activate the SMTP_SESSION object.
+ */
+ session = smtp_session_activate(fd, state->iterator, state->dest_prop,
+ state->endp_prop);
+ if (session == 0) {
+ msg_warn("%s: bad cached session attribute for %s", myname, label);
+ (void) close(fd);
+ return (0);
+ }
+ state->session = session;
+ session->state = state;
+
+ /*
+ * Send an RSET probe to verify that the session is still good.
+ */
+ if (smtp_rset(state) < 0
+ || (session->features & SMTP_FEATURE_RSET_REJECTED) != 0) {
+ smtp_session_free(session);
+ return (state->session = 0);
+ }
+
+ /*
+ * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+ */
+ vstream_tweak_sock(session->stream);
+
+ /*
+ * Update the list of used cached addresses.
+ */
+ htable_enter(state->cache_used, STR(iter->addr), (void *) 0);
+
+ return (session);
+}
+
+/* smtp_reuse_nexthop - reuse session cached under nexthop name */
+
+SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags)
+{
+ SMTP_SESSION *session;
+ int fd;
+
+ /*
+ * Look up the session by its logical name.
+ */
+ smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, name_key_flags);
+ if ((fd = scache_find_dest(smtp_scache, STR(state->dest_label),
+ state->dest_prop, state->endp_prop)) < 0)
+ return (0);
+
+ /*
+ * Re-activate the SMTP_SESSION object, and verify that the session is
+ * still good.
+ */
+ session = smtp_reuse_common(state, fd, STR(state->dest_label));
+ return (session);
+}
+
+/* smtp_reuse_addr - reuse session cached under numerical address */
+
+SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags)
+{
+ SMTP_SESSION *session;
+ int fd;
+
+ /*
+ * Address-based reuse is safe for security levels that require TLS
+ * certificate checks, as long as the current nexhop is included in the
+ * cache lookup key (COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP). This is
+ * sufficient to prevent the reuse of a TLS-authenticated connection to
+ * the same MX hostname, IP address, and port, but for a different
+ * current nexthop destination with a different TLS policy.
+ */
+
+ /*
+ * Look up the session by its IP address. This means that we have no
+ * destination-to-address binding properties.
+ */
+ smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, endp_key_flags);
+ if ((fd = scache_find_endp(smtp_scache, STR(state->endp_label),
+ state->endp_prop)) < 0)
+ return (0);
+ VSTRING_RESET(state->dest_prop);
+ VSTRING_TERMINATE(state->dest_prop);
+
+ /*
+ * Re-activate the SMTP_SESSION object, and verify that the session is
+ * still good.
+ */
+ session = smtp_reuse_common(state, fd, STR(state->endp_label));
+
+ return (session);
+}
diff --git a/src/smtp/smtp_reuse.h b/src/smtp/smtp_reuse.h
new file mode 100644
index 0000000..8c6cb48
--- /dev/null
+++ b/src/smtp/smtp_reuse.h
@@ -0,0 +1,27 @@
+/*++
+/* NAME
+/* smtp_reuse 3h
+/* SUMMARY
+/* SMTP session cache glue
+/* SYNOPSIS
+/* #include <smtp_reuse.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Internal interfaces.
+ */
+extern void smtp_save_session(SMTP_STATE *, int, int);
+extern SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *, int);
+extern SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *, int);
+
+/* 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
+/*--*/
diff --git a/src/smtp/smtp_sasl.h b/src/smtp/smtp_sasl.h
new file mode 100644
index 0000000..756d13d
--- /dev/null
+++ b/src/smtp/smtp_sasl.h
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* smtp_sasl 3h
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include "smtp_sasl.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SASL protocol functions
+ */
+extern void smtp_sasl_initialize(void);
+extern void smtp_sasl_connect(SMTP_SESSION *);
+extern int smtp_sasl_passwd_lookup(SMTP_SESSION *);
+extern void smtp_sasl_start(SMTP_SESSION *, const char *, const char *);
+extern int smtp_sasl_authenticate(SMTP_SESSION *, DSN_BUF *);
+extern void smtp_sasl_cleanup(SMTP_SESSION *);
+
+extern void smtp_sasl_helo_auth(SMTP_SESSION *, const char *);
+extern int smtp_sasl_helo_login(SMTP_STATE *);
+
+extern void smtp_sasl_passivate(SMTP_SESSION *, VSTRING *);
+extern int smtp_sasl_activate(SMTP_SESSION *, char *);
+extern STRING_LIST *smtp_sasl_mechs;
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtp/smtp_sasl_auth_cache.c b/src/smtp/smtp_sasl_auth_cache.c
new file mode 100644
index 0000000..f3104ca
--- /dev/null
+++ b/src/smtp/smtp_sasl_auth_cache.c
@@ -0,0 +1,272 @@
+/*++
+/* NAME
+/* smtp_sasl_auth_cache 3
+/* SUMMARY
+/* Postfix SASL authentication reply cache
+/* SYNOPSIS
+/* #include "smtp.h"
+/* #include "smtp_sasl_auth_cache.h"
+/*
+/* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl)
+/* const char *map
+/* int ttl;
+/*
+/* void smtp_sasl_auth_cache_store(auth_cache, session, resp)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* const SMTP_SESSION *session;
+/* const SMTP_RESP *resp;
+/*
+/* int smtp_sasl_auth_cache_find(auth_cache, session)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* const SMTP_SESSION *session;
+/*
+/* char *smtp_sasl_auth_cache_dsn(auth_cache)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/*
+/* char *smtp_sasl_auth_cache_text(auth_cache)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* DESCRIPTION
+/* This module maintains a cache of SASL authentication server replies.
+/* This can be used to avoid repeated login failure errors.
+/*
+/* smtp_sasl_auth_cache_init() opens or creates the named cache.
+/*
+/* smtp_sasl_auth_cache_store() stores information about a
+/* SASL login attempt together with the server status and
+/* complete response.
+/*
+/* smtp_sasl_auth_cache_find() returns non-zero when a cache
+/* entry exists for the given host, username and password.
+/*
+/* smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text()
+/* return the status and complete server response as found
+/* with smtp_sasl_auth_cache_find().
+/*
+/* Arguments:
+/* .IP map
+/* Lookup table name. The name must be singular and must start
+/* with "proxy:".
+/* .IP ttl
+/* The time after which a cache entry is considered expired.
+/* .IP session
+/* Session context.
+/* .IP resp
+/* Remote SMTP server response, to be stored into the cache.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Keean Schupke
+/* Fry-IT Ltd.
+/*
+/* Updated by:
+/* 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 <mymalloc.h>
+#include <stringops.h>
+#include <base64_code.h>
+#include <dict.h>
+
+ /*
+ * Global library
+ */
+#include <dsn_util.h>
+#include <dict_proxy.h>
+
+ /*
+ * Application-specific
+ */
+#include "smtp.h"
+#include "smtp_sasl_auth_cache.h"
+
+ /*
+ * XXX This feature stores passwords, so we must mask them with a strong
+ * cryptographic hash. This requires OpenSSL support.
+ *
+ * XXX It would be even better if the stored hash were salted.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+
+/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */
+
+SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl)
+{
+ const char *myname = "smtp_sasl_auth_cache_init";
+ SMTP_SASL_AUTH_CACHE *auth_cache;
+
+ /*
+ * Sanity checks.
+ */
+#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), CHARS_COMMA_SP)] != 0)
+
+ if (*map == 0)
+ msg_panic("%s: empty SASL authentication cache name", myname);
+ if (ttl < 0)
+ msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl);
+ if (HAS_MULTIPLE_VALUES(map))
+ msg_fatal("SASL authentication cache name \"%s\" "
+ "contains multiple values", map);
+
+ /*
+ * XXX To avoid multiple writers the map needs to be maintained by the
+ * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag
+ * so that the library can enforce this, but that requires moving the
+ * dict_proxy module one level down in the build dependency hierarchy.
+ */
+#define CACHE_DICT_OPEN_FLAGS \
+ (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST)
+#define PROXY_COLON DICT_TYPE_PROXY ":"
+#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1)
+
+ if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0)
+ msg_fatal("SASL authentication cache name \"%s\" must start with \""
+ PROXY_COLON, map);
+
+ auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache));
+ auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS);
+ auth_cache->ttl = ttl;
+ auth_cache->dsn = mystrdup("");
+ auth_cache->text = mystrdup("");
+ return (auth_cache);
+}
+
+ /*
+ * Each cache lookup key contains a server host name and user name. Each
+ * cache value contains a time stamp, a hashed password, and the server
+ * response. With this organization, we don't have to worry about cache
+ * pollution, because we can detect if a cache entry has expired, or if the
+ * password has changed.
+ */
+
+/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */
+
+static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ vstring_sprintf(buf, "%s;%s", host, user);
+ return (vstring_export(buf));
+}
+
+/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */
+
+static char *smtp_sasl_auth_cache_make_pass(const char *password)
+{
+ VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH);
+
+ base64_encode(buf, (const char *) SHA1((const unsigned char *) password,
+ strlen(password), 0),
+ SHA_DIGEST_LENGTH);
+ return (vstring_export(buf));
+}
+
+/* smtp_sasl_auth_cache_make_value - format auth failure cache value */
+
+static char *smtp_sasl_auth_cache_make_value(const char *password,
+ const char *dsn,
+ const char *rep_str)
+{
+ VSTRING *val_buf = vstring_alloc(100);
+ char *pwd_hash;
+ unsigned long now = (unsigned long) time((time_t *) 0);
+
+ pwd_hash = smtp_sasl_auth_cache_make_pass(password);
+ vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str);
+ myfree(pwd_hash);
+ return (vstring_export(val_buf));
+}
+
+/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */
+
+static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const char *entry,
+ const char *password)
+{
+ ssize_t len = strlen(entry);
+ char *cache_hash = mymalloc(len);
+ char *curr_hash;
+ unsigned long now = (unsigned long) time((time_t *) 0);
+ unsigned long time_stamp;
+ int valid;
+
+ auth_cache->dsn = myrealloc(auth_cache->dsn, len);
+ auth_cache->text = myrealloc(auth_cache->text, len);
+
+ if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash,
+ auth_cache->dsn, auth_cache->text) != 4
+ || !dsn_valid(auth_cache->dsn)) {
+ msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry);
+ valid = 0;
+ } else if (time_stamp + auth_cache->ttl < now) {
+ valid = 0;
+ } else {
+ curr_hash = smtp_sasl_auth_cache_make_pass(password);
+ valid = (strcmp(cache_hash, curr_hash) == 0);
+ myfree(curr_hash);
+ }
+ myfree(cache_hash);
+ return (valid);
+}
+
+/* smtp_sasl_auth_cache_find - search auth failure cache */
+
+int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const SMTP_SESSION *session)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ char *key;
+ const char *entry;
+ int valid = 0;
+
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
+ if ((entry = dict_get(auth_cache->dict, key)) != 0)
+ if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry,
+ session->sasl_passwd)) == 0)
+ /* Remove expired, password changed, or malformed cache entry. */
+ if (dict_del(auth_cache->dict, key) != 0)
+ msg_warn("SASL auth failure map %s: entry not deleted: %s",
+ auth_cache->dict->name, key);
+ if (auth_cache->dict->error)
+ msg_warn("SASL auth failure map %s: lookup failed for %s",
+ auth_cache->dict->name, key);
+ myfree(key);
+ return (valid);
+}
+
+/* smtp_sasl_auth_cache_store - update auth failure cache */
+
+void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const SMTP_SESSION *session,
+ const SMTP_RESP *resp)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ char *key;
+ char *value;
+
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
+ value = smtp_sasl_auth_cache_make_value(session->sasl_passwd,
+ resp->dsn, resp->str);
+ dict_put(auth_cache->dict, key, value);
+
+ myfree(value);
+ myfree(key);
+}
+
+#endif
diff --git a/src/smtp/smtp_sasl_auth_cache.h b/src/smtp/smtp_sasl_auth_cache.h
new file mode 100644
index 0000000..cbbdb0d
--- /dev/null
+++ b/src/smtp/smtp_sasl_auth_cache.h
@@ -0,0 +1,62 @@
+#ifndef _SMTP_SASL_AUTH_CACHE_H_INCLUDED_
+#define _SMTP_SASL_AUTH_CACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* smtp_sasl_auth_cache 3h
+/* SUMMARY
+/* Postfix SASL authentication failure cache
+/* SYNOPSIS
+/* #include "smtp.h"
+/* #include "smtp_sasl_auth_cache.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * This code stores hashed passwords which requires OpenSSL.
+ */
+#if defined(USE_TLS) && defined(USE_SASL_AUTH)
+#define HAVE_SASL_AUTH_CACHE
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ DICT *dict;
+ int ttl;
+ char *dsn;
+ char *text;
+} SMTP_SASL_AUTH_CACHE;
+
+extern SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *, int);
+extern void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *, const SMTP_RESP *);
+extern int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *);
+
+#define smtp_sasl_auth_cache_dsn(cp) ((cp)->dsn)
+#define smtp_sasl_auth_cache_text(cp) ((cp)->text)
+
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c
new file mode 100644
index 0000000..ef8e8c4
--- /dev/null
+++ b/src/smtp/smtp_sasl_glue.c
@@ -0,0 +1,512 @@
+/*++
+/* NAME
+/* smtp_sasl_glue 3
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include smtp_sasl.h
+/*
+/* void smtp_sasl_initialize()
+/*
+/* void smtp_sasl_connect(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_passwd_lookup(session)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_authenticate(session, why)
+/* SMTP_SESSION *session;
+/* DSN_BUF *why;
+/*
+/* void smtp_sasl_cleanup(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_passivate(session, buf)
+/* SMTP_SESSION *session;
+/* VSTRING *buf;
+/*
+/* int smtp_sasl_activate(session, buf)
+/* SMTP_SESSION *session;
+/* char *buf;
+/* DESCRIPTION
+/* smtp_sasl_initialize() initializes the SASL library. This
+/* routine must be called once at process startup, before any
+/* chroot operations.
+/*
+/* smtp_sasl_connect() performs per-session initialization. This
+/* routine must be called once at the start of each connection.
+/*
+/* smtp_sasl_start() performs per-session initialization. This
+/* routine must be called once per session before doing any SASL
+/* authentication. The sasl_opts_name and sasl_opts_val parameters are
+/* the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
+/*
+/* smtp_sasl_passwd_lookup() looks up the username/password
+/* for the current SMTP server. The result is zero in case
+/* of failure, a long jump in case of error.
+/*
+/* smtp_sasl_authenticate() implements the SASL authentication
+/* dialog. The result is < 0 in case of protocol failure, zero in
+/* case of unsuccessful authentication, > 0 in case of success.
+/* The why argument is updated with a reason for failure.
+/* This routine must be called only when smtp_sasl_passwd_lookup()
+/* succeeds.
+/*
+/* smtp_sasl_cleanup() cleans up. It must be called at the
+/* end of every SMTP session that uses SASL authentication.
+/* This routine is a noop for non-SASL sessions.
+/*
+/* smtp_sasl_passivate() appends flattened SASL attributes to the
+/* specified buffer. The SASL attributes are not destroyed.
+/*
+/* smtp_sasl_activate() restores SASL attributes from the
+/* specified buffer. The buffer is modified. A result < 0
+/* means there was an error.
+/*
+/* Arguments:
+/* .IP session
+/* Session context.
+/* .IP mech_list
+/* String of SASL mechanisms (separated by blanks)
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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 <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <split_at.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+#include <string_list.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <smtp_stream.h>
+
+ /*
+ * XSASL library.
+ */
+#include <xsasl.h>
+
+ /*
+ * Application-specific
+ */
+#include "smtp.h"
+#include "smtp_sasl.h"
+#include "smtp_sasl_auth_cache.h"
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * Per-host login/password information.
+ */
+static MAPS *smtp_sasl_passwd_map;
+
+ /*
+ * Supported SASL mechanisms.
+ */
+STRING_LIST *smtp_sasl_mechs;
+
+ /*
+ * SASL implementation handle.
+ */
+static XSASL_CLIENT_IMPL *smtp_sasl_impl;
+
+ /*
+ * The 535 SASL authentication failure cache.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
+
+#endif
+
+/* smtp_sasl_passwd_lookup - password lookup routine */
+
+int smtp_sasl_passwd_lookup(SMTP_SESSION *session)
+{
+ const char *myname = "smtp_sasl_passwd_lookup";
+ SMTP_STATE *state = session->state;
+ SMTP_ITERATOR *iter = session->iterator;
+ const char *value;
+ char *passwd;
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map == 0)
+ msg_panic("%s: passwd map not initialized", myname);
+
+ /*
+ * Look up the per-server password information. Try the hostname first,
+ * then try the destination.
+ *
+ * XXX Instead of using nexthop (the intended destination) we use dest
+ * (either the intended destination, or a fall-back destination).
+ *
+ * XXX SASL authentication currently depends on the host/domain but not on
+ * the TCP port. If the port is not :25, we should append it to the table
+ * lookup key. Code for this was briefly introduced into 2.2 snapshots,
+ * but didn't canonicalize the TCP port, and did not append the port to
+ * the MX hostname.
+ */
+ smtp_sasl_passwd_map->error = 0;
+ if ((smtp_mode
+ && var_smtp_sender_auth && state->request->sender[0]
+ && (value = mail_addr_find(smtp_sasl_passwd_map,
+ state->request->sender, (char **) 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->host), 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->dest), 0)) != 0)) {
+ if (session->sasl_username)
+ myfree(session->sasl_username);
+ session->sasl_username = mystrdup(value);
+ passwd = split_at(session->sasl_username, ':');
+ if (session->sasl_passwd)
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = mystrdup(passwd ? passwd : "");
+ if (msg_verbose)
+ msg_info("%s: host `%s' user `%s' pass `%s'",
+ myname, STR(iter->host),
+ session->sasl_username, session->sasl_passwd);
+ return (1);
+ } else if (smtp_sasl_passwd_map->error) {
+ msg_warn("%s: %s lookup error",
+ state->request->queue_id, smtp_sasl_passwd_map->title);
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: no auth info found (sender=`%s', host=`%s')",
+ myname, state->request->sender, STR(iter->host));
+ return (0);
+ }
+}
+
+/* smtp_sasl_initialize - per-process initialization (pre jail) */
+
+void smtp_sasl_initialize(void)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map || smtp_sasl_impl)
+ msg_panic("smtp_sasl_initialize: repeated call");
+ if (*var_smtp_sasl_passwd == 0)
+ msg_fatal("specify a password table via the `%s' configuration parameter",
+ VAR_LMTP_SMTP(SASL_PASSWD));
+
+ /*
+ * Open the per-host password table and initialize the SASL library. Use
+ * shared locks for reading, just in case someone updates the table.
+ */
+ smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
+ var_smtp_sasl_passwd,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
+ var_smtp_sasl_path)) == 0)
+ msg_fatal("SASL library initialization");
+
+ /*
+ * Initialize optional supported mechanism matchlist
+ */
+ if (*var_smtp_sasl_mechs)
+ smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
+ MATCH_FLAG_NONE,
+ var_smtp_sasl_mechs);
+
+ /*
+ * Initialize the 535 SASL authentication failure cache.
+ */
+ if (*var_smtp_sasl_auth_cache_name) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ smtp_sasl_auth_cache =
+ smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
+ var_smtp_sasl_auth_cache_time);
+#else
+ msg_warn("not compiled with TLS support -- "
+ "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
+#endif
+ }
+}
+
+/* smtp_sasl_connect - per-session client initialization */
+
+void smtp_sasl_connect(SMTP_SESSION *session)
+{
+
+ /*
+ * This initialization happens whenever we instantiate an SMTP session
+ * object. We don't instantiate a SASL client until we actually need one.
+ */
+ session->sasl_mechanism_list = 0;
+ session->sasl_username = 0;
+ session->sasl_passwd = 0;
+ session->sasl_client = 0;
+ session->sasl_reply = 0;
+}
+
+/* smtp_sasl_start - per-session SASL initialization */
+
+void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
+ const char *sasl_opts_val)
+{
+ XSASL_CLIENT_CREATE_ARGS create_args;
+ SMTP_ITERATOR *iter = session->iterator;
+
+ if (msg_verbose)
+ msg_info("starting new SASL client");
+ if ((session->sasl_client =
+ XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
+ stream = session->stream,
+ service = var_procname,
+ server_name = STR(iter->host),
+ security_options = sasl_opts_val)) == 0)
+ msg_fatal("SASL per-connection initialization failed");
+ session->sasl_reply = vstring_alloc(20);
+}
+
+/* smtp_sasl_authenticate - run authentication protocol */
+
+int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
+{
+ const char *myname = "smtp_sasl_authenticate";
+ SMTP_ITERATOR *iter = session->iterator;
+ SMTP_RESP *resp;
+ const char *mechanism;
+ int result;
+ char *line;
+ int steps = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (session->sasl_mechanism_list == 0)
+ msg_panic("%s: no mechanism list", myname);
+
+ if (msg_verbose)
+ msg_info("%s: %s: SASL mechanisms %s",
+ myname, session->namaddrport, session->sasl_mechanism_list);
+
+ /*
+ * Avoid repeated login failures after a recent 535 error.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+ if (smtp_sasl_auth_cache
+ && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
+ char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
+ char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);
+
+ if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
+ resp_dsn[0] = '4';
+ dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
+ STR(iter->host), var_procname, resp_str,
+ "SASL [CACHED] authentication failed; server %s said: %s",
+ STR(iter->host), resp_str);
+ return (0);
+ }
+#endif
+
+ /*
+ * Start the client side authentication protocol.
+ */
+ result = xsasl_client_first(session->sasl_client,
+ session->sasl_mechanism_list,
+ session->sasl_username,
+ session->sasl_passwd,
+ &mechanism, session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
+ DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ /*-
+ * Send the AUTH command and the optional initial client response.
+ *
+ * https://tools.ietf.org/html/rfc4954#page-4
+ * Note that the AUTH command is still subject to the line length
+ * limitations defined in [SMTP]. If use of the initial response argument
+ * would cause the AUTH command to exceed this length, the client MUST NOT
+ * use the initial response parameter...
+ *
+ * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
+ * The maximum total length of a command line including the command word
+ * and the <CRLF> is 512 octets.
+ *
+ * Defer the initial response if the resulting command exceeds the limit.
+ */
+ if (LEN(session->sasl_reply) > 0
+ && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) {
+ smtp_chat_cmd(session, "AUTH %s %s", mechanism,
+ STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */
+ } else {
+ smtp_chat_cmd(session, "AUTH %s", mechanism);
+ }
+
+ /*
+ * Step through the authentication protocol until the server tells us
+ * that we are done. If session->sasl_reply is non-empty we have a
+ * deferred initial reply and expect an empty initial challenge from the
+ * server. If the server's initial challenge is non-empty we have a SASL
+ * protocol violation with both sides wanting to go first.
+ */
+ while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
+
+ /*
+ * Sanity check.
+ */
+ if (++steps > 100) {
+ dsb_simple(why, "4.3.0", "SASL authentication failed; "
+ "authentication protocol loop with server %s",
+ session->namaddr);
+ return (-1);
+ }
+
+ /*
+ * Process a server challenge.
+ */
+ line = resp->str;
+ (void) mystrtok(&line, "- \t\n"); /* skip over result code */
+
+ if (LEN(session->sasl_reply) > 0) {
+
+ /*
+ * Deferred initial response, the server challenge must be empty.
+ * Cleared after actual transmission to the server.
+ */
+ if (*line) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION,
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
+ "SASL authentication failed; non-empty initial "
+ "%s challenge from server %s: %s", mechanism,
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ } else {
+ result = xsasl_client_next(session->sasl_client, line,
+ session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1); /* Fix 200512 */
+ }
+ }
+
+ /*
+ * Send a client response.
+ */
+ smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* clear initial reply */
+ }
+
+ /*
+ * We completed the authentication protocol.
+ */
+ if (resp->code / 100 != 2) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ /* Update the 535 authentication failure cache. */
+ if (smtp_sasl_auth_cache && resp->code == 535)
+ smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
+#endif
+ if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
+ STR(resp->dsn_buf)[0] = '4';
+ dsb_update(why, resp->dsn, DSB_DEF_ACTION,
+ DSB_MTYPE_DNS, STR(iter->host),
+ var_procname, resp->str,
+ "SASL authentication failed; server %s said: %s",
+ session->namaddr, resp->str);
+ return (0);
+ }
+ return (1);
+}
+
+/* smtp_sasl_cleanup - per-session cleanup */
+
+void smtp_sasl_cleanup(SMTP_SESSION *session)
+{
+ if (session->sasl_username) {
+ myfree(session->sasl_username);
+ session->sasl_username = 0;
+ }
+ if (session->sasl_passwd) {
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = 0;
+ }
+ if (session->sasl_mechanism_list) {
+ /* allocated in smtp_sasl_helo_auth */
+ myfree(session->sasl_mechanism_list);
+ session->sasl_mechanism_list = 0;
+ }
+ if (session->sasl_client) {
+ if (msg_verbose)
+ msg_info("disposing SASL state information");
+ xsasl_client_free(session->sasl_client);
+ session->sasl_client = 0;
+ }
+ if (session->sasl_reply) {
+ vstring_free(session->sasl_reply);
+ session->sasl_reply = 0;
+ }
+}
+
+/* smtp_sasl_passivate - append serialized SASL attributes */
+
+void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
+{
+}
+
+/* smtp_sasl_activate - de-serialize SASL attributes */
+
+int smtp_sasl_activate(SMTP_SESSION *session, char *buf)
+{
+ return (0);
+}
+
+#endif
diff --git a/src/smtp/smtp_sasl_proto.c b/src/smtp/smtp_sasl_proto.c
new file mode 100644
index 0000000..30ae9ec
--- /dev/null
+++ b/src/smtp/smtp_sasl_proto.c
@@ -0,0 +1,171 @@
+/*++
+/* NAME
+/* smtp_sasl_proto 3
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include smtp_sasl.h
+/*
+/* void smtp_sasl_helo_auth(state, words)
+/* SMTP_STATE *state;
+/* const char *words;
+/*
+/* int smtp_sasl_helo_login(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* This module contains random chunks of code that implement
+/* the SMTP protocol interface for SASL negotiation. The goal
+/* is to reduce clutter in the main SMTP client source code.
+/*
+/* smtp_sasl_helo_auth() processes the AUTH option in the
+/* SMTP server's EHLO response.
+/*
+/* smtp_sasl_helo_login() authenticates the SMTP client to the
+/* SMTP server, using the authentication mechanism information
+/* given by the server. The result is a Postfix delivery status
+/* code in case of trouble.
+/*
+/* Arguments:
+/* .IP state
+/* Session context.
+/* .IP words
+/* List of SASL authentication mechanisms (separated by blanks)
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <sasl_mech_filter.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+#ifdef USE_SASL_AUTH
+
+/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */
+
+void smtp_sasl_helo_auth(SMTP_SESSION *session, const char *words)
+{
+ const char *mech_list = sasl_mech_filter(smtp_sasl_mechs, words);
+ char *junk;
+
+ /*
+ * XXX If the server offers no compatible authentication mechanisms, then
+ * pretend that the server doesn't support SASL authentication.
+ *
+ * XXX If the server offers multiple different lists, concatenate them. Let
+ * the SASL library worry about duplicates.
+ */
+ if (session->sasl_mechanism_list) {
+ if (strcasecmp(session->sasl_mechanism_list, mech_list) != 0
+ && strlen(mech_list) > 0
+ && strlen(session->sasl_mechanism_list) < var_line_limit) {
+ junk = concatenate(session->sasl_mechanism_list, " ", mech_list,
+ (char *) 0);
+ myfree(session->sasl_mechanism_list);
+ session->sasl_mechanism_list = junk;
+ }
+ return;
+ }
+ if (strlen(mech_list) > 0) {
+ session->sasl_mechanism_list = mystrdup(mech_list);
+ } else {
+ msg_warn(*words ? "%s offered no supported AUTH mechanisms: '%s'" :
+ "%s offered null AUTH mechanism list%s",
+ session->namaddrport, words);
+ }
+ session->features |= SMTP_FEATURE_AUTH;
+}
+
+/* smtp_sasl_helo_login - perform SASL login */
+
+int smtp_sasl_helo_login(SMTP_STATE *state)
+{
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ int ret;
+
+ /*
+ * Skip authentication when no authentication info exists for this
+ * server, so that we talk to each other like strangers.
+ */
+ if (smtp_sasl_passwd_lookup(session) == 0) {
+ session->features &= ~SMTP_FEATURE_AUTH;
+ return 0;
+ }
+
+ /*
+ * Otherwise, if authentication information exists, assume that
+ * authentication is required, and assume that an authentication error is
+ * recoverable from the message delivery point of view. An authentication
+ * error is unrecoverable from a session point of view - the session will
+ * not be reused.
+ */
+ ret = 0;
+ if (session->sasl_mechanism_list == 0) {
+ dsb_simple(why, "4.7.0", "SASL authentication failed: "
+ "server %s offered no compatible authentication mechanisms for this type of connection security",
+ session->namaddr);
+ ret = smtp_sess_fail(state);
+ /* Session reuse is disabled. */
+ } else {
+#ifndef USE_TLS
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), var_smtp_sasl_opts);
+#else
+ if (session->tls_context == 0)
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS),
+ var_smtp_sasl_opts);
+ else if (TLS_CERT_IS_MATCHED(session->tls_context))
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLSV_OPTS),
+ var_smtp_sasl_tlsv_opts);
+ else
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLS_OPTS),
+ var_smtp_sasl_tls_opts);
+#endif
+ if (smtp_sasl_authenticate(session, why) <= 0) {
+ ret = smtp_sess_fail(state);
+ /* Session reuse is disabled. */
+ }
+ }
+ return (ret);
+}
+
+#endif
diff --git a/src/smtp/smtp_session.c b/src/smtp/smtp_session.c
new file mode 100644
index 0000000..9f13978
--- /dev/null
+++ b/src/smtp/smtp_session.c
@@ -0,0 +1,447 @@
+/*++
+/* NAME
+/* smtp_session 3
+/* SUMMARY
+/* SMTP_SESSION structure management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags)
+/* VSTREAM *stream;
+/* SMTP_ITERATOR *iter;
+/* time_t start;
+/* int flags;
+/*
+/* void smtp_session_free(session)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_session_passivate(session, dest_prop, endp_prop)
+/* SMTP_SESSION *session;
+/* VSTRING *dest_prop;
+/* VSTRING *endp_prop;
+/*
+/* SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop)
+/* int fd;
+/* SMTP_ITERATOR *iter;
+/* VSTRING *dest_prop;
+/* VSTRING *endp_prop;
+/* DESCRIPTION
+/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure
+/* and initializes it with the given stream and destination, host name
+/* and address information. The host name and address strings are
+/* copied. The port is in network byte order.
+/*
+/* smtp_session_free() destroys an SMTP_SESSION structure and its
+/* members, making memory available for reuse. It will handle the
+/* case of a null stream and will assume it was given a different
+/* purpose.
+/*
+/* smtp_session_passivate() flattens an SMTP session (including
+/* TLS context) so that it can be cached. The SMTP_SESSION
+/* structure is destroyed.
+/*
+/* smtp_session_activate() inflates a flattened SMTP session
+/* so that it can be used. The input property arguments are
+/* modified.
+/*
+/* Arguments:
+/* .IP stream
+/* A full-duplex stream.
+/* .IP iter
+/* The literal next-hop or fall-back destination including
+/* the optional [] and including the :port or :service;
+/* the name of the remote host;
+/* the printable address of the remote host;
+/* the remote port in network byte order.
+/* .IP start
+/* The time when this connection was opened.
+/* .IP flags
+/* Zero or more of the following:
+/* .RS
+/* .IP SMTP_MISC_FLAG_CONN_LOAD
+/* Enable re-use of cached SMTP or LMTP connections.
+/* .IP SMTP_MISC_FLAG_CONN_STORE
+/* Enable saving of cached SMTP or LMTP connections.
+/* .RE
+/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE.
+/* .IP dest_prop
+/* Destination specific session properties: the server is the
+/* best MX host for the current logical destination, the dest,
+/* host, and addr properties. When dest_prop is non-empty, it
+/* overrides the iterator dest, host, and addr properties. It
+/* is the caller's responsibility to save the current nexthop
+/* with SMTP_ITER_SAVE_DEST() and to restore it afterwards
+/* with SMTP_ITER_RESTORE_DEST() before trying alternatives.
+/* .IP endp_prop
+/* Endpoint specific session properties: all the features
+/* advertised by the remote server.
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mime_state.h>
+#include <debug_peer.h>
+#include <mail_params.h>
+
+/* TLS Library. */
+
+#include <tls_proxy.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Local, because these are meaningful only for code in this file.
+ */
+#define SESS_ATTR_DEST "destination"
+#define SESS_ATTR_HOST "host_name"
+#define SESS_ATTR_ADDR "host_addr"
+#define SESS_ATTR_DEST_FEATURES "destination_features"
+
+#define SESS_ATTR_TLS_LEVEL "tls_level"
+#define SESS_ATTR_REUSE_COUNT "reuse_count"
+#define SESS_ATTR_ENDP_FEATURES "endpoint_features"
+#define SESS_ATTR_EXPIRE_TIME "expire_time"
+
+/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
+
+SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter,
+ time_t start, int flags)
+{
+ SMTP_SESSION *session;
+ const char *host = STR(iter->host);
+ const char *addr = STR(iter->addr);
+ unsigned port = iter->port;
+
+ session = (SMTP_SESSION *) mymalloc(sizeof(*session));
+ session->stream = stream;
+ session->iterator = iter;
+ session->namaddr = concatenate(host, "[", addr, "]", (char *) 0);
+ session->helo = 0;
+ session->port = port;
+ session->features = 0;
+
+ session->size_limit = 0;
+ session->error_mask = 0;
+ session->buffer = vstring_alloc(100);
+ session->scratch = vstring_alloc(100);
+ session->scratch2 = vstring_alloc(100);
+ smtp_chat_init(session);
+ session->mime_state = 0;
+
+ if (session->port) {
+ vstring_sprintf(session->buffer, "%s:%d",
+ session->namaddr, ntohs(session->port));
+ session->namaddrport = mystrdup(STR(session->buffer));
+ } else
+ session->namaddrport = mystrdup(session->namaddr);
+
+ session->send_proto_helo = 0;
+
+ if (flags & SMTP_MISC_FLAG_CONN_STORE)
+ CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
+ else
+ DONT_CACHE_THIS_SESSION;
+ session->reuse_count = 0;
+ USE_NEWBORN_SESSION; /* He's not dead Jim! */
+
+#ifdef USE_SASL_AUTH
+ smtp_sasl_connect(session);
+#endif
+
+#ifdef USE_TLS
+ session->tls_context = 0;
+ session->tls_retry_plain = 0;
+ session->tls_nexthop = 0;
+#endif
+ session->state = 0;
+ debug_peer_check(host, addr);
+ return (session);
+}
+
+/* smtp_session_free - destroy SMTP_SESSION structure and contents */
+
+void smtp_session_free(SMTP_SESSION *session)
+{
+#ifdef USE_TLS
+ if (session->stream) {
+ vstream_fflush(session->stream);
+ }
+ if (session->tls_context) {
+ if (session->features &
+ (SMTP_FEATURE_FROM_CACHE | SMTP_FEATURE_FROM_PROXY))
+ tls_proxy_context_free(session->tls_context);
+ else
+ tls_client_stop(smtp_tls_ctx, session->stream,
+ var_smtp_starttls_tmout, 0, session->tls_context);
+ }
+#endif
+ if (session->stream)
+ vstream_fclose(session->stream);
+ myfree(session->namaddr);
+ myfree(session->namaddrport);
+ if (session->helo)
+ myfree(session->helo);
+
+ vstring_free(session->buffer);
+ vstring_free(session->scratch);
+ vstring_free(session->scratch2);
+
+ if (session->history)
+ smtp_chat_reset(session);
+ if (session->mime_state)
+ mime_state_free(session->mime_state);
+
+#ifdef USE_SASL_AUTH
+ smtp_sasl_cleanup(session);
+#endif
+
+ if (session->state->debug_peer_per_nexthop == 0)
+ debug_peer_restore();
+ myfree((void *) session);
+}
+
+/* smtp_session_passivate - passivate an SMTP_SESSION object */
+
+int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ VSTREAM *mp;
+ int fd;
+
+ /*
+ * Encode the delivery request next-hop to endpoint binding properties:
+ * whether or not this server is best MX host for the delivery request
+ * next-hop or fall-back logical destination (this information is needed
+ * for loop handling in smtp_proto()).
+ *
+ * TODO: save SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ *
+ * These memory writes should never fail.
+ */
+ if ((mp = vstream_memopen(dest_prop, O_WRONLY)) == 0
+ || attr_print_plain(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(SESS_ATTR_DEST, STR(iter->dest)),
+ SEND_ATTR_STR(SESS_ATTR_HOST, STR(iter->host)),
+ SEND_ATTR_STR(SESS_ATTR_ADDR, STR(iter->addr)),
+ SEND_ATTR_INT(SESS_ATTR_DEST_FEATURES,
+ session->features & SMTP_FEATURE_DESTINATION_MASK),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("smtp_session_passivate: can't save dest properties: %m");
+
+ /*
+ * Encode the physical endpoint properties: all the session properties
+ * except for "session from cache", "best MX", or "RSET failure". Plus
+ * the TLS level, reuse count, and connection expiration time.
+ *
+ * XXX Should also record how many non-delivering mail transactions there
+ * were during this session, and perhaps other statistics, so that we
+ * don't reuse a session too much.
+ *
+ * TODO: passivate SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ *
+ * These memory writes should never fail.
+ */
+ if ((mp = vstream_memopen(endp_prop, O_WRONLY)) == 0
+ || attr_print_plain(mp, ATTR_FLAG_NONE,
+#ifdef USE_TLS
+ SEND_ATTR_INT(SESS_ATTR_TLS_LEVEL,
+ session->state->tls->level),
+#endif
+ SEND_ATTR_INT(SESS_ATTR_REUSE_COUNT,
+ session->reuse_count),
+ SEND_ATTR_INT(SESS_ATTR_ENDP_FEATURES,
+ session->features & SMTP_FEATURE_ENDPOINT_MASK),
+ SEND_ATTR_LONG(SESS_ATTR_EXPIRE_TIME,
+ (long) session->expire_time),
+ ATTR_TYPE_END) != 0
+
+ /*
+ * Append the passivated TLS context. These memory writes should never
+ * fail.
+ */
+#ifdef USE_TLS
+ || (session->tls_context
+ && attr_print_plain(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_context_print,
+ (void *) session->tls_context),
+ ATTR_TYPE_END) != 0)
+#endif
+ || vstream_fclose(mp) != 0)
+ msg_fatal("smtp_session_passivate: cannot save TLS context: %m");
+
+ /*
+ * Salvage the underlying file descriptor, and destroy the session
+ * object.
+ */
+ fd = vstream_fileno(session->stream);
+ vstream_fdclose(session->stream);
+ session->stream = 0;
+ smtp_session_free(session);
+
+ return (fd);
+}
+
+/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */
+
+SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter,
+ VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ const char *myname = "smtp_session_activate";
+ VSTREAM *mp;
+ SMTP_SESSION *session;
+ int endp_features; /* server features */
+ int dest_features; /* server features */
+ long expire_time; /* session re-use expiration time */
+ int reuse_count; /* # times reused */
+
+#ifdef USE_TLS
+ TLS_SESS_STATE *tls_context = 0;
+ SMTP_TLS_POLICY *tls = iter->parent->tls;
+
+#define TLS_PROXY_CONTEXT_FREE() do { \
+ if (tls_context) \
+ tls_proxy_context_free(tls_context); \
+ } while (0)
+#else
+#define TLS_PROXY_CONTEXT_FREE() /* nothing */
+#endif
+
+#define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \
+ TLS_PROXY_CONTEXT_FREE(); \
+ return (0); \
+ } while (0)
+
+ /*
+ * Sanity check: if TLS is required, the cached properties must contain a
+ * TLS context.
+ */
+ if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0
+ || attr_scan_plain(mp, ATTR_FLAG_NONE,
+#ifdef USE_TLS
+ RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL,
+ &tls->level),
+#endif
+ RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT,
+ &reuse_count),
+ RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES,
+ &endp_features),
+ RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME,
+ &expire_time),
+ ATTR_TYPE_END) != 4
+#ifdef USE_TLS
+ || ((tls->level > TLS_LEV_MAY
+ || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0))
+ && attr_scan_plain(mp, ATTR_FLAG_NONE,
+ RECV_ATTR_FUNC(tls_proxy_context_scan,
+ (void *) &tls_context),
+ ATTR_TYPE_END) != 1)
+#endif
+ || vstream_fclose(mp) != 0) {
+ msg_warn("smtp_session_activate: bad cached endp properties");
+ SMTP_SESSION_ACTIVATE_ERR_RETURN();
+ }
+
+ /*
+ * Clobber the iterator's current nexthop, host and address fields with
+ * cached-connection information. This is done when a session is looked
+ * up by delivery request nexthop instead of address and port. It is the
+ * caller's responsibility to save and restore the delivery request
+ * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST().
+ *
+ * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION.
+ *
+ * TODO: restore SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ */
+ if (dest_prop && VSTRING_LEN(dest_prop)) {
+ if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0
+ || attr_scan_plain(mp, ATTR_FLAG_NONE,
+ RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest),
+ RECV_ATTR_STR(SESS_ATTR_HOST, iter->host),
+ RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr),
+ RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES,
+ &dest_features),
+ ATTR_TYPE_END) != 4
+ || vstream_fclose(mp) != 0) {
+ msg_warn("smtp_session_passivate: bad cached dest properties");
+ SMTP_SESSION_ACTIVATE_ERR_RETURN();
+ }
+ } else {
+ dest_features = 0;
+ }
+#ifdef USE_TLS
+ if (msg_verbose)
+ msg_info("%s: tls_level=%d", myname, tls->level);
+#endif
+
+ /*
+ * Allright, bundle up what we have sofar.
+ */
+#define NO_FLAGS 0
+
+ session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter,
+ (time_t) 0, NO_FLAGS);
+ session->features =
+ (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE);
+#ifdef USE_TLS
+ session->tls_context = tls_context;
+#endif
+ CACHE_THIS_SESSION_UNTIL(expire_time);
+ session->reuse_count = ++reuse_count;
+
+ if (msg_verbose)
+ msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, "
+ "ttl=%ld, reuse=%d",
+ myname, STR(iter->dest), STR(iter->host),
+ STR(iter->addr), ntohs(iter->port),
+ endp_features | dest_features,
+ (long) (expire_time - time((time_t *) 0)),
+ reuse_count);
+
+#if USE_TLS
+ if (tls_context)
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED,
+ session->tls_context);
+#endif
+
+ return (session);
+}
diff --git a/src/smtp/smtp_state.c b/src/smtp/smtp_state.c
new file mode 100644
index 0000000..6b81fa4
--- /dev/null
+++ b/src/smtp/smtp_state.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* smtp_state 3
+/* SUMMARY
+/* initialize/cleanup shared state
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* SMTP_STATE *smtp_state_alloc()
+/*
+/* void smtp_state_free(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* smtp_state_init() initializes the shared state, and allocates
+/* memory for buffers etc.
+/*
+/* smtp_cleanup() destroys memory allocated by smtp_state_init().
+/* STANDARDS
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* 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 <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <debug_peer.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+/* smtp_state_alloc - initialize */
+
+SMTP_STATE *smtp_state_alloc(void)
+{
+ SMTP_STATE *state = (SMTP_STATE *) mymalloc(sizeof(*state));
+
+ state->misc_flags = 0;
+ state->src = 0;
+ state->service = 0;
+ state->request = 0;
+ state->session = 0;
+ state->status = 0;
+ state->space_left = 0;
+ state->iterator->request_nexthop = vstring_alloc(100);
+ state->iterator->dest = vstring_alloc(100);
+ state->iterator->host = vstring_alloc(100);
+ state->iterator->addr = vstring_alloc(100);
+ state->iterator->saved_dest = vstring_alloc(100);
+ if (var_smtp_cache_conn) {
+ state->dest_label = vstring_alloc(10);
+ state->dest_prop = vstring_alloc(10);
+ state->endp_label = vstring_alloc(10);
+ state->endp_prop = vstring_alloc(10);
+ state->cache_used = htable_create(1);
+ } else {
+ state->dest_label = 0;
+ state->dest_prop = 0;
+ state->endp_label = 0;
+ state->endp_prop = 0;
+ state->cache_used = 0;
+ }
+ state->why = dsb_create();
+ state->debug_peer_per_nexthop = 0;
+ state->logged_line_length_limit = 0;
+ return (state);
+}
+
+/* smtp_state_free - destroy state */
+
+void smtp_state_free(SMTP_STATE *state)
+{
+#ifdef USE_TLS
+ /* The TLS policy cache lifetime is one delivery. */
+ smtp_tls_policy_cache_flush();
+#endif
+ vstring_free(state->iterator->request_nexthop);
+ vstring_free(state->iterator->dest);
+ vstring_free(state->iterator->host);
+ vstring_free(state->iterator->addr);
+ vstring_free(state->iterator->saved_dest);
+ if (state->dest_label)
+ vstring_free(state->dest_label);
+ if (state->dest_prop)
+ vstring_free(state->dest_prop);
+ if (state->endp_label)
+ vstring_free(state->endp_label);
+ if (state->endp_prop)
+ vstring_free(state->endp_prop);
+ if (state->cache_used)
+ htable_free(state->cache_used, (void (*) (void *)) 0);
+ if (state->why)
+ dsb_free(state->why);
+ if (state->debug_peer_per_nexthop)
+ debug_peer_restore();
+
+ myfree((void *) state);
+}
diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c
new file mode 100644
index 0000000..92a231d
--- /dev/null
+++ b/src/smtp/smtp_tls_policy.c
@@ -0,0 +1,927 @@
+/*++
+/* NAME
+/* smtp_tls_policy 3
+/* SUMMARY
+/* SMTP_TLS_POLICY structure management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* void smtp_tls_list_init()
+/*
+/* int smtp_tls_policy_cache_query(why, tls, iter)
+/* DSN_BUF *why;
+/* SMTP_TLS_POLICY *tls;
+/* SMTP_ITERATOR *iter;
+/*
+/* void smtp_tls_policy_dummy(tls)
+/* SMTP_TLS_POLICY *tls;
+/*
+/* void smtp_tls_policy_cache_flush()
+/* DESCRIPTION
+/* smtp_tls_list_init() initializes lookup tables used by the TLS
+/* policy engine.
+/*
+/* smtp_tls_policy_cache_query() returns a shallow copy of the
+/* cached SMTP_TLS_POLICY structure for the iterator's
+/* destination, host, port and DNSSEC validation status.
+/* This copy is guaranteed to be valid until the next
+/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush()
+/* call. The caller can override the TLS security level without
+/* corrupting the policy cache.
+/* When any required table or DNS lookups fail, the TLS level
+/* is set to TLS_LEV_INVALID, the "why" argument is updated
+/* with the error reason and the result value is zero (false).
+/*
+/* smtp_tls_policy_dummy() initializes a trivial, non-cached,
+/* policy with TLS disabled.
+/*
+/* smtp_tls_policy_cache_flush() destroys the TLS policy cache
+/* and contents.
+/*
+/* Arguments:
+/* .IP why
+/* A pointer to a DSN_BUF which holds error status information when
+/* the TLS policy lookup fails.
+/* .IP tls
+/* Pointer to TLS policy storage.
+/* .IP iter
+/* The literal next-hop or fall-back destination including
+/* the optional [] and including the :port or :service;
+/* the name of the remote host after MX and CNAME expansions
+/* (see smtp_cname_overrides_servername for the handling
+/* of hostnames that resolve to a CNAME record);
+/* the printable address of the remote host;
+/* the remote port in network byte order;
+/* the DNSSEC validation status of the host name lookup after
+/* MX and CNAME expansions.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <netinet/in.h> /* ntohs() for Solaris or BSD */
+#include <arpa/inet.h> /* ntohs() for Linux or BSD */
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <valid_utf8_hostname.h>
+#include <ctable.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <maps.h>
+#include <dsn_buf.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */
+#define CACHE_SIZE 20
+static CTABLE *policy_cache;
+
+static int global_tls_level(void);
+static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *);
+
+static MAPS *tls_policy; /* lookup table(s) */
+static MAPS *tls_per_site; /* lookup table(s) */
+
+/* smtp_tls_list_init - initialize per-site policy lists */
+
+void smtp_tls_list_init(void)
+{
+ if (*var_smtp_tls_policy) {
+ tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY),
+ var_smtp_tls_policy,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_smtp_tls_per_site)
+ msg_warn("%s ignored when %s is not empty.",
+ VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY));
+ return;
+ }
+ if (*var_smtp_tls_per_site) {
+ tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE),
+ var_smtp_tls_per_site,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ }
+}
+
+/* policy_name - printable tls policy level */
+
+static const char *policy_name(int tls_level)
+{
+ const char *name = str_tls_level(tls_level);
+
+ if (name == 0)
+ name = "unknown";
+ return name;
+}
+
+#define MARK_INVALID(why, levelp) do { \
+ dsb_simple((why), "4.7.5", "client TLS configuration problem"); \
+ *(levelp) = TLS_LEV_INVALID; } while (0)
+
+/* tls_site_lookup - look up per-site TLS security level */
+
+static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name, const char *site_class)
+{
+ const char *lookup;
+
+ /*
+ * Look up a non-default policy. In case of multiple lookup results, the
+ * precedence order is a permutation of the TLS enforcement level order:
+ * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
+ * specific policy including NONE, otherwise we choose the stronger
+ * enforcement level.
+ */
+ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
+ if (!strcasecmp(lookup, "NONE")) {
+ /* NONE overrides MAY or NOTFOUND. */
+ if (*site_level <= TLS_LEV_MAY)
+ *site_level = TLS_LEV_NONE;
+ } else if (!strcasecmp(lookup, "MAY")) {
+ /* MAY overrides NOTFOUND but not NONE. */
+ if (*site_level < TLS_LEV_NONE)
+ *site_level = TLS_LEV_MAY;
+ } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
+ if (*site_level < TLS_LEV_ENCRYPT)
+ *site_level = TLS_LEV_ENCRYPT;
+ } else if (!strcasecmp(lookup, "MUST")) {
+ if (*site_level < TLS_LEV_VERIFY)
+ *site_level = TLS_LEV_VERIFY;
+ } else {
+ msg_warn("%s: unknown TLS policy '%s' for %s %s",
+ tls_per_site->title, lookup, site_class, site_name);
+ MARK_INVALID(tls->why, site_level);
+ return;
+ }
+ } else if (tls_per_site->error) {
+ msg_warn("%s: %s \"%s\": per-site table lookup error",
+ tls_per_site->title, site_class, site_name);
+ dsb_simple(tls->why, "4.3.0", "Temporary lookup error");
+ *site_level = TLS_LEV_INVALID;
+ return;
+ }
+ return;
+}
+
+/* tls_policy_lookup_one - look up destination TLS policy */
+
+static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+ const char *lookup;
+ char *policy;
+ char *saved_policy;
+ char *tok;
+ const char *err;
+ char *name;
+ char *val;
+ static VSTRING *cbuf;
+
+#undef FREE_RETURN
+#define FREE_RETURN do { myfree(saved_policy); return; } while (0)
+
+#define INVALID_RETURN(why, levelp) do { \
+ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0)
+
+#define WHERE \
+ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \
+ tls_policy->title, site_class, site_name))
+
+ if (cbuf == 0)
+ cbuf = vstring_alloc(10);
+
+ if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
+ if (tls_policy->error) {
+ msg_warn("%s: policy table lookup error", WHERE);
+ MARK_INVALID(tls->why, site_level);
+ }
+ return;
+ }
+ saved_policy = policy = mystrdup(lookup);
+
+ if ((tok = mystrtok(&policy, CHARS_COMMA_SP)) == 0) {
+ msg_warn("%s: invalid empty policy", WHERE);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ *site_level = tls_level_lookup(tok);
+ if (*site_level == TLS_LEV_INVALID) {
+ /* tls_level_lookup() logs no warning. */
+ msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
+ INVALID_RETURN(tls->why, site_level);
+ }
+
+ /*
+ * Warn about ignored attributes when TLS is disabled.
+ */
+ if (*site_level < TLS_LEV_MAY) {
+ while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0)
+ msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
+ WHERE, tok);
+ FREE_RETURN;
+ }
+
+ /*
+ * Errors in attributes may have security consequences, don't ignore
+ * errors that can degrade security.
+ */
+ while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) {
+ if ((err = split_nameval(tok, &name, &val)) != 0) {
+ msg_warn("%s: malformed attribute/value pair \"%s\": %s",
+ WHERE, tok, err);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "ciphers")) {
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (tls->grade) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->grade = mystrdup(val);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "protocols")) {
+ if (tls->protocols) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->protocols = mystrdup(val);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "servername")) {
+ if (tls->sni) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (valid_hostname(val, DONT_GRIPE))
+ tls->sni = mystrdup(val);
+ else {
+ msg_warn("%s: \"%s=%s\" specifies an invalid hostname",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ /* Multiple instances per policy. */
+ if (!strcasecmp(name, "match")) {
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ switch (*site_level) {
+ default:
+ msg_warn("%s: attribute \"%s\" invalid at security level "
+ "\"%s\"", WHERE, name, policy_name(*site_level));
+ INVALID_RETURN(tls->why, site_level);
+ break;
+ case TLS_LEV_FPRINT:
+ if (!tls->dane)
+ tls->dane = tls_dane_alloc();
+ tls_dane_add_fpt_digests(tls->dane, val, "|", smtp_mode);
+ break;
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->matchargv == 0)
+ tls->matchargv = argv_split(val, ":");
+ else
+ argv_split_append(tls->matchargv, val, ":");
+ break;
+ }
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "exclude")) {
+ if (tls->exclusions) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
+ continue;
+ }
+ /* Multiple instances per policy. */
+ if (!strcasecmp(name, "tafile")) {
+ /* Only makes sense if we're using CA-based trust */
+ if (!TLS_MUST_PKIX(*site_level)) {
+ msg_warn("%s: attribute \"%s\" invalid at security level"
+ " \"%s\"", WHERE, name, policy_name(*site_level));
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (!tls->dane)
+ tls->dane = tls_dane_alloc();
+ if (!tls_dane_load_trustfile(tls->dane, val)) {
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ /* Last one wins. */
+ if (!strcasecmp(name, "connection_reuse")) {
+ if (strcasecmp(val, "yes") == 0) {
+ tls->conn_reuse = 1;
+ } else if (strcasecmp(val, "no") == 0) {
+ tls->conn_reuse = 0;
+ } else {
+ msg_warn("%s: attribute \"%s\" has bad value: \"%s\"",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+
+ FREE_RETURN;
+}
+
+/* tls_policy_lookup - look up destination TLS policy */
+
+static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+
+ /*
+ * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
+ * are never the domain part of localpart@domain, rather they are
+ * explicit nexthops from transport:nexthop, and match only the
+ * corresponding policy. Parent domain matching (below) applies only to
+ * sub-domains of the recipient domain.
+ *
+ * XXX UNIX-domain connections query with the pathname as destination.
+ */
+ if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ return;
+ }
+ do {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ } while (*site_level == TLS_LEV_NOTFOUND
+ && (site_name = strchr(site_name + 1, '.')) != 0);
+}
+
+/* load_tas - load one or more ta files */
+
+static int load_tas(TLS_DANE *dane, const char *files)
+{
+ int ret = 0;
+ char *save = mystrdup(files);
+ char *buf = save;
+ char *file;
+
+ do {
+ if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0)
+ ret = tls_dane_load_trustfile(dane, file);
+ } while (file && ret);
+
+ myfree(save);
+ return (ret);
+}
+
+/* set_cipher_grade - Set cipher grade and exclusions */
+
+static void set_cipher_grade(SMTP_TLS_POLICY *tls)
+{
+ const char *mand_exclude = "";
+ const char *also_exclude = "";
+
+ /*
+ * Use main.cf cipher level if no per-destination value specified. With
+ * mandatory encryption at least encrypt, and with mandatory verification
+ * at least authenticate!
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ return;
+
+ case TLS_LEV_MAY:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_ciph);
+ break;
+
+ case TLS_LEV_ENCRYPT:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "eNULL";
+ break;
+
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "aNULL";
+ break;
+ }
+
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ /*
+ * The "exclude" policy table attribute overrides main.cf exclusion
+ * lists.
+ */
+ if (tls->exclusions == 0) {
+ tls->exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph);
+ ADD_EXCLUDE(tls->exclusions, mand_exclude);
+ }
+ ADD_EXCLUDE(tls->exclusions, also_exclude);
+}
+
+/* policy_create - create SMTP TLS policy cache object (ctable call-back) */
+
+static void *policy_create(const char *unused_key, void *context)
+{
+ SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context;
+ int site_level;
+ const char *dest = STR(iter->dest);
+ const char *host = STR(iter->host);
+
+ /*
+ * Prepare a pristine policy object.
+ */
+ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls));
+
+ smtp_tls_policy_init(tls, dsb_create());
+ tls->conn_reuse = var_smtp_tls_conn_reuse;
+
+ /*
+ * Compute the per-site TLS enforcement level. For compatibility with the
+ * original TLS patch, this algorithm is gives equal precedence to host
+ * and next-hop policies.
+ */
+ tls->level = global_tls_level();
+ site_level = TLS_LEV_NOTFOUND;
+
+ if (tls_policy) {
+ tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+ } else if (tls_per_site) {
+ tls_site_lookup(tls, &site_level, dest, "next-hop destination");
+ if (site_level != TLS_LEV_INVALID
+ && strcasecmp_utf8(dest, host) != 0)
+ tls_site_lookup(tls, &site_level, host, "server hostname");
+
+ /*
+ * Override a wild-card per-site policy with a more specific global
+ * policy.
+ *
+ * With the original TLS patch, 1) a per-site ENCRYPT could not override
+ * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
+ * produced inconsistent results: it changed a global VERIFY into
+ * NONE, while producing MAY with all weaker global policy settings.
+ *
+ * With the current implementation, a combined per-site (NONE+MAY)
+ * consistently overrides global policy with NONE, and global policy
+ * can override only a per-site MAY wildcard. That is, specific
+ * policies consistently override wildcard policies, and
+ * (non-wildcard) per-site policies consistently override global
+ * policies.
+ */
+ if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY)
+ site_level = tls->level;
+ }
+ switch (site_level) {
+ default:
+ tls->level = site_level;
+ /* FALLTHROUGH */
+ case TLS_LEV_NOTFOUND:
+ break;
+ case TLS_LEV_INVALID:
+ tls->level = site_level;
+ return ((void *) tls);
+ }
+
+ /*
+ * DANE initialization may change the security level to something else,
+ * so do this early, so that we use the right level below. Note that
+ * "dane-only" changes to "dane" once we obtain the requisite TLSA
+ * records.
+ */
+ if (TLS_DANE_BASED(tls->level))
+ dane_init(tls, iter);
+ if (tls->level == TLS_LEV_INVALID)
+ return ((void *) tls);
+
+ /*
+ * Use main.cf protocols and SNI settings if not set in per-destination
+ * table.
+ */
+ if (tls->level > TLS_LEV_NONE && tls->protocols == 0)
+ tls->protocols =
+ mystrdup((tls->level == TLS_LEV_MAY) ?
+ var_smtp_tls_proto : var_smtp_tls_mand_proto);
+ if (tls->level > TLS_LEV_NONE && tls->sni == 0) {
+ if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE))
+ tls->sni = mystrdup(var_smtp_tls_sni);
+ else {
+ msg_warn("\"%s = %s\" specifies an invalid hostname",
+ VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+
+ /*
+ * Compute cipher grade (if set in per-destination table, else
+ * set_cipher() uses main.cf settings) and security level dependent
+ * cipher exclusion list.
+ */
+ set_cipher_grade(tls);
+
+ /*
+ * Use main.cf cert_match setting if not set in per-destination table.
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+ break;
+ case TLS_LEV_FPRINT:
+ if (tls->dane == 0)
+ tls->dane = tls_dane_alloc();
+ if (tls->dane->tlsa == 0) {
+ tls_dane_add_fpt_digests(tls->dane, var_smtp_tls_fpt_cmatch,
+ CHARS_COMMA_SP, smtp_mode);
+ if (tls->dane->tlsa == 0) {
+ msg_warn("nexthop domain %s: configured at fingerprint "
+ "security level, but with no fingerprints to match.",
+ dest);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+ break;
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->matchargv == 0)
+ tls->matchargv =
+ argv_split(tls->level == TLS_LEV_VERIFY ?
+ var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch,
+ CHARS_COMMA_SP ":");
+ if (*var_smtp_tls_tafile) {
+ if (tls->dane == 0)
+ tls->dane = tls_dane_alloc();
+ if (tls->dane->tlsa == 0
+ && !load_tas(tls->dane, var_smtp_tls_tafile)) {
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+ break;
+ default:
+ msg_panic("unexpected TLS security level: %d", tls->level);
+ }
+
+ if (msg_verbose && tls->level != global_tls_level())
+ msg_info("%s TLS level: %s", "effective", policy_name(tls->level));
+
+ return ((void *) tls);
+}
+
+/* policy_delete - free no longer cached policy (ctable call-back) */
+
+static void policy_delete(void *item, void *unused_context)
+{
+ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item;
+
+ if (tls->protocols)
+ myfree(tls->protocols);
+ if (tls->sni)
+ myfree(tls->sni);
+ if (tls->grade)
+ myfree(tls->grade);
+ if (tls->exclusions)
+ vstring_free(tls->exclusions);
+ if (tls->matchargv)
+ argv_free(tls->matchargv);
+ if (tls->dane)
+ tls_dane_free(tls->dane);
+ dsb_free(tls->why);
+
+ myfree((void *) tls);
+}
+
+/* smtp_tls_policy_cache_query - cached lookup of TLS policy */
+
+int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls,
+ SMTP_ITERATOR *iter)
+{
+ VSTRING *key;
+
+ /*
+ * Create an empty TLS Policy cache on the fly.
+ */
+ if (policy_cache == 0)
+ policy_cache =
+ ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0);
+
+ /*
+ * Query the TLS Policy cache, with a search key that reflects our shared
+ * values that also appear in other cache and table search keys.
+ */
+ key = vstring_alloc(100);
+ smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_CUR_NEXTHOP
+ | SMTP_KEY_FLAG_HOSTNAME
+ | SMTP_KEY_FLAG_PORT);
+ ctable_newcontext(policy_cache, (void *) iter);
+ *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
+ vstring_free(key);
+
+ /*
+ * Report errors. Both error and non-error results are cached. We must
+ * therefore copy the cached DSN buffer content to the caller's buffer.
+ */
+ if (tls->level == TLS_LEV_INVALID) {
+ /* XXX Simplify this by implementing a "copy" primitive. */
+ dsb_update(why,
+ STR(tls->why->status), STR(tls->why->action),
+ STR(tls->why->mtype), STR(tls->why->mname),
+ STR(tls->why->dtype), STR(tls->why->dtext),
+ "%s", STR(tls->why->reason));
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+/* smtp_tls_policy_cache_flush - flush TLS policy cache */
+
+void smtp_tls_policy_cache_flush(void)
+{
+ if (policy_cache != 0) {
+ ctable_free(policy_cache);
+ policy_cache = 0;
+ }
+}
+
+/* global_tls_level - parse and cache var_smtp_tls_level */
+
+static int global_tls_level(void)
+{
+ static int l = TLS_LEV_NOTFOUND;
+
+ if (l != TLS_LEV_NOTFOUND)
+ return l;
+
+ /*
+ * Compute the global TLS policy. This is the default policy level when
+ * no per-site policy exists. It also is used to override a wild-card
+ * per-site policy.
+ *
+ * We require that the global level is valid on startup.
+ */
+ if (*var_smtp_tls_level) {
+ if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID)
+ msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level);
+ } else if (var_smtp_enforce_tls)
+ l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
+ else
+ l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE;
+
+ if (msg_verbose)
+ msg_info("%s TLS level: %s", "global", policy_name(l));
+
+ return l;
+}
+
+#define NONDANE_CONFIG 0 /* Administrator's fault */
+#define NONDANE_DEST 1 /* Remote server's fault */
+#define DANE_CANTAUTH 2 /* Remote server's fault */
+
+static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls,
+ SMTP_ITERATOR *iter,
+ int errtype,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (tls->level == TLS_LEV_DANE) {
+ tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY;
+ if (errtype == NONDANE_CONFIG)
+ vmsg_warn(fmt, ap);
+ else if (msg_verbose)
+ vmsg_info(fmt, ap);
+ } else { /* dane-only */
+ if (errtype == NONDANE_CONFIG) {
+ vmsg_warn(fmt, ap);
+ MARK_INVALID(tls->why, &tls->level);
+ } else {
+ tls->level = TLS_LEV_INVALID;
+ vdsb_simple(tls->why, "4.7.5", fmt, ap);
+ }
+ }
+ va_end(ap);
+}
+
+/* dane_init - special initialization for "dane" security level */
+
+static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
+{
+ TLS_DANE *dane;
+
+ if (!iter->port) {
+ msg_warn("%s: the \"dane\" security level is invalid for delivery via"
+ " unix-domain sockets", STR(iter->dest));
+ MARK_INVALID(tls->why, &tls->level);
+ return;
+ }
+ if (!tls_dane_avail()) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured, but no requisite library support",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+ if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS)
+ || smtp_dns_support != SMTP_DNS_DNSSEC) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured with dnssec lookups disabled",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * If we ignore MX lookup errors, we also ignore DNSSEC security problems
+ * and thus avoid any reasonable expectation that we get the right DANE
+ * key material.
+ */
+ if (smtp_mode && var_ign_mx_lookup_err) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured with MX lookup errors ignored",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * This is not optional, code in tls_dane.c assumes that the nexthop
+ * qname is already an fqdn. If we're using these flags to go from qname
+ * to rname, the assumption is invalid. Likewise we cannot add the qname
+ * to certificate name checks, ...
+ */
+ if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: dns resolver options incompatible with %s TLS",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * When the MX name is present and insecure, DANE may not apply, we then
+ * either fail if DANE is mandatory or use regular opportunistic TLS if
+ * the insecure MX level is "may".
+ */
+ if (iter->mx && !iter->mx->dnssec_valid
+ && (tls->level == TLS_LEV_DANE_ONLY ||
+ smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) {
+ dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination");
+ return;
+ }
+ /* When TLSA lookups fail, we defer the message */
+ if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr,
+ var_smtp_tls_force_tlsa)) == 0) {
+ tls->level = TLS_LEV_INVALID;
+ dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u",
+ STR(iter->host), ntohs(iter->port));
+ return;
+ }
+ if (tls_dane_notfound(dane)) {
+ dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found");
+ tls_dane_free(dane);
+ return;
+ }
+
+ /*
+ * Some TLSA records found, but none usable, per
+ *
+ * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4
+ *
+ * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter
+ * would be unwise for SMTP: no human present to "click ok" and risk of
+ * non-delivery in most cases exceeds risk of interception.
+ *
+ * We also have a form of Goedel's incompleteness theorem in play: any list
+ * of public root CA certs is either incomplete or inconsistent (for any
+ * given verifier some of the CAs are surely not trustworthy).
+ */
+ if (tls_dane_unusable(dane)) {
+ dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable");
+ tls_dane_free(dane);
+ return;
+ }
+
+ /*
+ * Perhaps downgrade to "encrypt" if MX is insecure.
+ */
+ if (iter->mx && !iter->mx->dnssec_valid) {
+ if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) {
+ dane_incompat(tls, iter, DANE_CANTAUTH,
+ "Verification not possible, MX RRset is insecure");
+ tls_dane_free(dane);
+ return;
+ }
+ if (tls->level != TLS_LEV_DANE
+ || smtp_tls_insecure_mx_policy != TLS_LEV_DANE)
+ msg_panic("wrong state for insecure MX host DANE policy");
+
+ /* For correct logging in tls_client_start() */
+ tls->level = TLS_LEV_HALF_DANE;
+ }
+
+ /*
+ * With DANE trust anchors, peername matching is not configurable.
+ */
+ if (dane->tlsa != 0) {
+ tls->matchargv = argv_alloc(2);
+ argv_add(tls->matchargv, dane->base_domain, ARGV_END);
+ if (iter->mx) {
+ if (strcmp(iter->mx->qname, iter->mx->rname) == 0)
+ argv_add(tls->matchargv, iter->mx->qname, ARGV_END);
+ else
+ argv_add(tls->matchargv, iter->mx->rname,
+ iter->mx->qname, ARGV_END);
+ }
+ } else
+ msg_panic("empty DANE match list");
+ tls->dane = dane;
+ return;
+}
+
+#endif
diff --git a/src/smtp/smtp_trouble.c b/src/smtp/smtp_trouble.c
new file mode 100644
index 0000000..60880df
--- /dev/null
+++ b/src/smtp/smtp_trouble.c
@@ -0,0 +1,476 @@
+/*++
+/* NAME
+/* smtp_trouble 3
+/* SUMMARY
+/* error handler policies
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_sess_fail(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_site_fail(state, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* int smtp_mesg_fail(state, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* void smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* RECIPIENT *recipient;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* int smtp_stream_except(state, exception, description)
+/* SMTP_STATE *state;
+/* int exception;
+/* const char *description;
+/* AUXILIARY FUNCTIONS
+/* int smtp_misc_fail(state, throttle, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* int throttle;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/* DESCRIPTION
+/* This module handles all non-fatal errors that can happen while
+/* attempting to deliver mail via SMTP, and implements the policy
+/* of how to deal with the error. Depending on the nature of
+/* the problem, delivery of a single message is deferred, delivery
+/* of all messages to the same domain is deferred, or one or more
+/* recipients are given up as non-deliverable and a bounce log is
+/* updated. In any case, the recipient is marked as either KEEP
+/* (try again with a backup host) or DROP (delete recipient from
+/* delivery request).
+/*
+/* In addition, when an unexpected response code is seen such
+/* as 3xx where only 4xx or 5xx are expected, or any error code
+/* that suggests a syntax error or something similar, the
+/* protocol error flag is set so that the postmaster receives
+/* a transcript of the session. No notification is generated for
+/* what appear to be configuration errors - very likely, they
+/* would suffer the same problem and just cause more trouble.
+/*
+/* In case of a soft error, action depends on whether the error
+/* qualifies for trying the request with other mail servers (log
+/* an informational record only and try a backup server) or
+/* whether this is the final server (log recipient delivery status
+/* records and delete the recipient from the request).
+/*
+/* smtp_sess_fail() takes a pre-formatted error report after
+/* failure to complete some protocol handshake. The policy is
+/* as with smtp_site_fail().
+/*
+/* smtp_site_fail() handles the case where the program fails to
+/* complete the initial handshake: the server is not reachable,
+/* is not running, does not want talk to us, or we talk to ourselves.
+/* The \fIcode\fR gives an error status code; the \fIformat\fR
+/* argument gives a textual description.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the host is being skipped; soft error, final server:
+/* defer delivery of all remaining recipients and mark the destination
+/* as problematic; hard error: bounce all remaining recipients.
+/* The session is marked as "do not cache".
+/* The result is non-zero.
+/*
+/* smtp_mesg_fail() handles the case where the smtp server
+/* does not accept the sender address or the message data,
+/* or when the local MTA is unable to convert the message data.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the host is being skipped; soft error, final server:
+/* defer delivery of all remaining recipients; hard error: bounce all
+/* remaining recipients.
+/* The result is non-zero.
+/*
+/* smtp_misc_fail() provides a more detailed interface than
+/* smtp_site_fail() and smtp_mesg_fail(), which are convenience
+/* wrappers around smtp_misc_fail(). The throttle argument
+/* is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only
+/* in the "soft error, final server" policy, and determines
+/* whether a destination will be marked as problematic.
+/*
+/* smtp_rcpt_fail() handles the case where a recipient is not
+/* accepted by the server for reasons other than that the server
+/* recipient limit is reached.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the recipient is being skipped; soft error, final server:
+/* defer delivery of this recipient; hard error: bounce this
+/* recipient.
+/*
+/* smtp_stream_except() handles the exceptions generated by
+/* the smtp_stream(3) module (i.e. timeouts and I/O errors).
+/* The \fIexception\fR argument specifies the type of problem.
+/* The \fIdescription\fR argument describes at what stage of
+/* the SMTP dialog the problem happened.
+/* The policy is: non-final server: log an informational record
+/* with the reason why the host is being skipped; final server:
+/* defer delivery of all remaining recipients.
+/* Retry plaintext delivery after TLS post-handshake session
+/* failure, provided that at least one recipient was not
+/* deferred or rejected during the TLS phase, and that global
+/* preconditions for plaintext fallback are met.
+/* The session is marked as "do not cache".
+/* The result is non-zero.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP client state per delivery request.
+/* .IP resp
+/* Server response including reply code and text.
+/* .IP recipient
+/* Undeliverable recipient address information.
+/* .IP format
+/* Human-readable description of why mail is not deliverable.
+/* DIAGNOSTICS
+/* Panic: unknown exception code.
+/* SEE ALSO
+/* smtp_proto(3) smtp high-level protocol
+/* smtp_stream(3) smtp low-level protocol
+/* defer(3) basic message defer interface
+/* bounce(3) basic message bounce interface
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <deliver_request.h>
+#include <deliver_completed.h>
+#include <bounce.h>
+#include <defer.h>
+#include <mail_error.h>
+#include <dsn_buf.h>
+#include <dsn.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+/* smtp_check_code - check response code */
+
+static void smtp_check_code(SMTP_SESSION *session, int code)
+{
+
+ /*
+ * The intention of this code is to alert the postmaster when the local
+ * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z
+ * replies "refer to syntax errors, syntactically correct commands that
+ * don't fit any functional category, and unimplemented or superfluous
+ * commands". Unfortunately, this also triggers postmaster notices when
+ * remote servers screw up, protocol wise. This is becoming a common
+ * problem now that response codes are configured manually as part of
+ * anti-UCE systems, by people who aren't aware of RFC details.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
+ * The protocol may be in a bad state. Disable caching here so that the
+ * protocol engine will send QUIT.
+ */
+ if (code < 400 || code > 599
+ || code == 555 /* RFC 1869, section 6.1. */
+ || (code >= 500 && code < 510)) {
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ }
+}
+
+/* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */
+
+static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ RECIPIENT *rcpt;
+ int status;
+ int aggregate_status;
+ int soft_error = (STR(why->status)[0] == '4');
+ int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
+ int nrcpt;
+
+ /*
+ * Don't defer the recipients just yet when this error qualifies them for
+ * delivery to a backup server. Just log something informative to show
+ * why we're skipping this host.
+ */
+ if ((soft_error || soft_bounce_error)
+ && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
+ msg_info("%s: %s", request->queue_id, STR(why->reason));
+ for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ continue;
+ SMTP_RCPT_KEEP(state, rcpt);
+ }
+ }
+
+ /*
+ * Defer or bounce all the remaining recipients, and delete them from the
+ * delivery request. If a bounce fails, defer instead and do not qualify
+ * the recipient for delivery to a backup server.
+ */
+ else {
+
+ /*
+ * If we are still in the connection set-up phase, update the set-up
+ * completion time here, otherwise the time spent in set-up latency
+ * will be attributed as message transfer latency.
+ *
+ * All remaining recipients have failed at this point, so we update the
+ * delivery completion time stamp so that multiple recipient status
+ * records show the same delay values.
+ */
+ if (request->msg_stats.conn_setup_done.tv_sec == 0) {
+ GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
+ request->msg_stats.deliver_done =
+ request->msg_stats.conn_setup_done;
+ } else
+ GETTIMEOFDAY(&request->msg_stats.deliver_done);
+
+ (void) DSN_FROM_DSN_BUF(why);
+ aggregate_status = 0;
+ for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ continue;
+ status = (soft_error ? defer_append : bounce_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
+ &request->msg_stats, rcpt,
+ session ? session->namaddrport : "none", &why->dsn);
+ if (status == 0)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ aggregate_status |= status;
+ }
+ state->status |= aggregate_status;
+ if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0
+ && throttle_queue && aggregate_status
+ && request->hop_status == 0)
+ request->hop_status = DSN_COPY(&why->dsn);
+ }
+
+ /*
+ * Don't cache this session. We can't talk to this server.
+ */
+ if (throttle_queue && session)
+ DONT_CACHE_THROTTLED_SESSION;
+
+ return (-1);
+}
+
+/* smtp_sess_fail - skip site, defer or bounce all recipients */
+
+int smtp_sess_fail(SMTP_STATE *state)
+{
+
+ /*
+ * We can't avoid copying copying lots of strings into VSTRING buffers,
+ * because this error information is collected by a routine that
+ * terminates BEFORE the error is reported.
+ */
+ return (smtp_bulk_fail(state, SMTP_THROTTLE));
+}
+
+/* vsmtp_fill_dsn - fill in temporary DSN structure */
+
+static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name,
+ const char *status, const char *reply,
+ const char *format, va_list ap)
+{
+ DSN_BUF *why = state->why;
+
+ /*
+ * We could avoid copying lots of strings into VSTRING buffers, because
+ * this error information is given to us by a routine that terminates
+ * AFTER the error is reported. However, this results in ugly kludges
+ * when informal text needs to be formatted. So we maintain consistency
+ * with other error reporting in the SMTP client even if we waste a few
+ * cycles.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
+ * The protocol may be in a bad state. Disable caching here so that the
+ * protocol engine will send QUIT.
+ */
+ VSTRING_RESET(why->reason);
+ if (mta_name && status && status[0] != '4' && status[0] != '5') {
+ SMTP_SESSION *session = state->session;
+
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ vstring_strcpy(why->reason, "Protocol error: ");
+ status = "5.5.0";
+ }
+ vstring_vsprintf_append(why->reason, format, ap);
+ dsb_formal(why, status, DSB_DEF_ACTION,
+ mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name,
+ reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply);
+}
+
+/* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */
+
+int smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name,
+ SMTP_RESP *resp, const char *format,...)
+{
+ va_list ap;
+
+ /*
+ * Initialize.
+ */
+ va_start(ap, format);
+ vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
+ va_end(ap);
+
+ if (state->session && mta_name)
+ smtp_check_code(state->session, resp->code);
+
+ /*
+ * Skip, defer or bounce recipients, and throttle this queue.
+ */
+ return (smtp_bulk_fail(state, throttle));
+}
+
+/* smtp_rcpt_fail - skip, defer, or bounce recipient */
+
+void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name,
+ SMTP_RESP *resp, const char *format,...)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ int status;
+ int soft_error;
+ int soft_bounce_error;
+ va_list ap;
+
+ /*
+ * Sanity check.
+ */
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address);
+
+ /*
+ * Initialize.
+ */
+ va_start(ap, format);
+ vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
+ va_end(ap);
+ soft_error = STR(why->status)[0] == '4';
+ soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
+
+ if (state->session && mta_name)
+ smtp_check_code(state->session, resp->code);
+
+ /*
+ * Don't defer this recipient record just yet when this error qualifies
+ * for trying other mail servers. Just log something informative to show
+ * why we're skipping this recipient now.
+ */
+ if ((soft_error || soft_bounce_error)
+ && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
+ msg_info("%s: %s", request->queue_id, STR(why->reason));
+ SMTP_RCPT_KEEP(state, rcpt);
+ }
+
+ /*
+ * Defer or bounce this recipient, and delete from the delivery request.
+ * If the bounce fails, defer instead and do not qualify the recipient
+ * for delivery to a backup server.
+ *
+ * Note: we may still make an SMTP connection to deliver other recipients
+ * that did qualify for delivery to a backup server.
+ */
+ else {
+ (void) DSN_FROM_DSN_BUF(state->why);
+ status = (soft_error ? defer_append : bounce_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
+ &request->msg_stats, rcpt,
+ session ? session->namaddrport : "none", &why->dsn);
+ if (status == 0)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ state->status |= status;
+ }
+}
+
+/* smtp_stream_except - defer domain after I/O problem */
+
+int smtp_stream_except(SMTP_STATE *state, int code, const char *description)
+{
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+
+ /*
+ * Sanity check.
+ */
+ if (session == 0)
+ msg_panic("smtp_stream_except: no session");
+
+ /*
+ * Initialize.
+ */
+ switch (code) {
+ default:
+ msg_panic("smtp_stream_except: unknown exception %d", code);
+ case SMTP_ERR_EOF:
+ dsb_simple(why, "4.4.2", "lost connection with %s while %s",
+ session->namaddr, description);
+#ifdef USE_TLS
+ if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
+ RETRY_AS_PLAINTEXT;
+#endif
+ break;
+ case SMTP_ERR_TIME:
+ dsb_simple(why, "4.4.2", "conversation with %s timed out while %s",
+ session->namaddr, description);
+#ifdef USE_TLS
+ if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
+ RETRY_AS_PLAINTEXT;
+#endif
+ break;
+ case SMTP_ERR_DATA:
+ session->error_mask |= MAIL_ERROR_DATA;
+ dsb_simple(why, "4.3.0", "local data error while talking to %s",
+ session->namaddr);
+ }
+
+ /*
+ * The smtp_bulk_fail() call below will not throttle the destination when
+ * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the
+ * FINAL_SERVER flag.
+ */
+ return (smtp_bulk_fail(state, SMTP_THROTTLE));
+}
diff --git a/src/smtp/smtp_unalias.c b/src/smtp/smtp_unalias.c
new file mode 100644
index 0000000..1c4d34d
--- /dev/null
+++ b/src/smtp/smtp_unalias.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* smtp_unalias 3
+/* SUMMARY
+/* replace host/domain alias by official name
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* const char *smtp_unalias_name(name)
+/* const char *name;
+/*
+/* VSTRING *smtp_unalias_addr(result, addr)
+/* VSTRING *result;
+/* const char *addr;
+/* DESCRIPTION
+/* smtp_unalias_name() looks up A or MX records for the specified
+/* name and returns the official name provided in the reply. When
+/* no A or MX record is found, the result is a copy of the input.
+/* In order to improve performance, smtp_unalias_name() remembers
+/* results in a private cache, so a name is looked up only once.
+/*
+/* smtp_unalias_addr() returns the input address, which is in
+/* RFC 821 quoted form, after replacing the domain part by the
+/* official name as found by smtp_unalias_name(). When the input
+/* contains no domain part, the result is a copy of the input.
+/* 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/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <htable.h>
+#include <vstring.h>
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+static int smtp_unalias_flags;
+
+/* smtp_unalias_name - look up the official host or domain name. */
+
+const char *smtp_unalias_name(const char *name)
+{
+ static HTABLE *cache;
+ VSTRING *fqdn;
+ char *result;
+
+ if (*name == '[')
+ return (name);
+
+ /*
+ * Initialize the cache on the fly. The smtp client is designed to exit
+ * after servicing a limited number of requests, so there is no need to
+ * prevent the cache from growing too large, or to expire old entries.
+ */
+ if (cache == 0)
+ cache = htable_create(10);
+
+ /*
+ * Look up the fqdn. If none is found use the query name instead, so that
+ * we won't lose time looking up the same bad name again.
+ */
+ if ((result = htable_find(cache, name)) == 0) {
+ fqdn = vstring_alloc(10);
+ if (dns_lookup_l(name, smtp_unalias_flags, (DNS_RR **) 0, fqdn,
+ (VSTRING *) 0, DNS_REQ_FLAG_NONE, T_MX, T_A,
+#ifdef HAS_IPV6
+ T_AAAA,
+#endif
+ 0) != DNS_OK)
+ vstring_strcpy(fqdn, name);
+ htable_enter(cache, name, result = vstring_export(fqdn));
+ }
+ return (result);
+}
+
+/* smtp_unalias_addr - rewrite aliases in domain part of address */
+
+VSTRING *smtp_unalias_addr(VSTRING *result, const char *addr)
+{
+ char *at;
+ const char *fqdn;
+
+ if ((at = strrchr(addr, '@')) == 0 || at[1] == 0) {
+ vstring_strcpy(result, addr);
+ } else {
+ fqdn = smtp_unalias_name(at + 1);
+ vstring_strncpy(result, addr, at - addr + 1);
+ vstring_strcat(result, fqdn);
+ }
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - read address from stdin, print result on stdout.
+ */
+
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *addr = vstring_alloc(10);
+ VSTRING *result = vstring_alloc(10);
+
+ smtp_unalias_flags |= RES_DEBUG;
+
+ while (vstring_fgets_nonl(addr, VSTREAM_IN)) {
+ smtp_unalias_addr(result, vstring_str(addr));
+ vstream_printf("%s -> %s\n", vstring_str(addr), vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(addr);
+ vstring_free(result);
+ return (0);
+}
+
+#endif
diff --git a/src/smtp/tls_policy.in b/src/smtp/tls_policy.in
new file mode 100644
index 0000000..95c295b
--- /dev/null
+++ b/src/smtp/tls_policy.in
@@ -0,0 +1,64 @@
+- - -
+- - may
+- - must_nopeermatch
+- - must
+- none -
+- none may
+- none must_nopeermatch
+- none must
+- may -
+- may may
+- may must_nopeermatch
+- may must
+- must_nopeermatch -
+- must_nopeermatch may
+- must_nopeermatch must_nopeermatch
+- must_nopeermatch must
+- must -
+- must may
+- must must_nopeermatch
+- must must
+
+none none -
+none none may
+none none must_nopeermatch
+none none must
+none may -
+none may may
+none may must_nopeermatch
+none may must
+none must_nopeermatch -
+none must_nopeermatch may
+none must_nopeermatch must_nopeermatch
+none must_nopeermatch must
+none must -
+none must may
+none must must_nopeermatch
+none must must
+
+may may -
+may may may
+may may must_nopeermatch
+may may must
+may must_nopeermatch -
+may must_nopeermatch may
+may must_nopeermatch must_nopeermatch
+may must_nopeermatch must
+may must -
+may must may
+may must must_nopeermatch
+may must must
+
+must_nopeermatch must_nopeermatch -
+must_nopeermatch must_nopeermatch may
+must_nopeermatch must_nopeermatch must_nopeermatch
+must_nopeermatch must_nopeermatch must
+must_nopeermatch must -
+must_nopeermatch must may
+must_nopeermatch must must_nopeermatch
+must_nopeermatch must must
+
+must must -
+must must may
+must must must_nopeermatch
+must must must
diff --git a/src/smtp/tls_policy.ref b/src/smtp/tls_policy.ref
new file mode 100644
index 0000000..5b9f187
--- /dev/null
+++ b/src/smtp/tls_policy.ref
@@ -0,0 +1,65 @@
+host dest global result
+- - - none
+- - may may
+- - must_nopeermatch must_nopeermatch
+- - must must
+- none - none
+- none may none
+- none must_nopeermatch none
+- none must none
+- may - may
+- may may may
+- may must_nopeermatch must_nopeermatch
+- may must must
+- must_nopeermatch - must_nopeermatch
+- must_nopeermatch may must_nopeermatch
+- must_nopeermatch must_nopeermatch must_nopeermatch
+- must_nopeermatch must must_nopeermatch
+- must - must
+- must may must
+- must must_nopeermatch must
+- must must must
+
+none none - none
+none none may none
+none none must_nopeermatch none
+none none must none
+none may - none
+none may may none
+none may must_nopeermatch none
+none may must none
+none must_nopeermatch - must_nopeermatch
+none must_nopeermatch may must_nopeermatch
+none must_nopeermatch must_nopeermatch must_nopeermatch
+none must_nopeermatch must must_nopeermatch
+none must - must
+none must may must
+none must must_nopeermatch must
+none must must must
+
+may may - may
+may may may may
+may may must_nopeermatch must_nopeermatch
+may may must must
+may must_nopeermatch - must_nopeermatch
+may must_nopeermatch may must_nopeermatch
+may must_nopeermatch must_nopeermatch must_nopeermatch
+may must_nopeermatch must must_nopeermatch
+may must - must
+may must may must
+may must must_nopeermatch must
+may must must must
+
+must_nopeermatch must_nopeermatch - must_nopeermatch
+must_nopeermatch must_nopeermatch may must_nopeermatch
+must_nopeermatch must_nopeermatch must_nopeermatch must_nopeermatch
+must_nopeermatch must_nopeermatch must must_nopeermatch
+must_nopeermatch must - must
+must_nopeermatch must may must
+must_nopeermatch must must_nopeermatch must
+must_nopeermatch must must must
+
+must must - must
+must must may must
+must must must_nopeermatch must
+must must must must
diff --git a/src/smtpd/.indent.pro b/src/smtpd/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtpd/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtpd/.printfck b/src/smtpd/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtpd/.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/smtpd/Makefile.in b/src/smtpd/Makefile.in
new file mode 100644
index 0000000..8c4132a
--- /dev/null
+++ b/src/smtpd/Makefile.in
@@ -0,0 +1,679 @@
+SHELL = /bin/sh
+SRCS = smtpd.c smtpd_token.c smtpd_check.c smtpd_chat.c smtpd_state.c \
+ smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c \
+ smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c \
+ smtpd_expand.c smtpd_haproxy.c
+OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \
+ smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o smtpd_proxy.o \
+ smtpd_xforward.o smtpd_dsn_fix.o smtpd_milter.o smtpd_resolve.o \
+ smtpd_expand.o smtpd_haproxy.o
+HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \
+ smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \
+ smtpd_resolve.h smtpd_expand.h
+TESTSRC = smtpd_token_test.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= smtpd_token smtpd_check
+PROG = smtpd
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/libxsasl.a \
+ ../../lib/libmilter.a \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../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
+
+SMTPD_CHECK_OBJ = smtpd_state.o smtpd_peer.o smtpd_xforward.o smtpd_dsn_fix.o \
+ smtpd_resolve.o smtpd_expand.o smtpd_proxy.o smtpd_haproxy.o
+
+smtpd_token: smtpd_token.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtpd_check: smtpd_check.o smtpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ smtpd_check.c $(SMTPD_CHECK_OBJ) \
+ $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+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 *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+broken-tests: smtpd_check_test smtpd_check_test2
+
+tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \
+ smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \
+ smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \
+ smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test
+
+root_tests:
+
+# This requires that the DNS server can query porcupine.org.
+
+smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+# This requires that the DNS server can query porcupine.org.
+
+smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in2 >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref2 smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_check_test4: smtpd_check smtpd_check.in4 smtpd_check.ref4 smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in4 >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref4 smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_acl.in >smtpd_check.tmp 2>&1
+ diff smtpd_acl.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_addr_valid.in >smtpd_check.tmp 2>&1
+ diff smtpd_addr_valid.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
+# This requires that the DNS server can query porcupine.org.
+
+ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/'
+
+smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_exp.in >smtpd_exp.tmp 2>&1
+ diff smtpd_exp.ref smtpd_exp.tmp
+ rm -f smtpd_exp.tmp smtpd_check_access.*
+
+smtpd_server_test: smtpd_check smtpd_server.in smtpd_server.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_server.in >smtpd_server.tmp 2>&1
+ $(ADDRINFO_FIX) smtpd_server.tmp | diff smtpd_server.ref -
+ rm -f smtpd_server.tmp smtpd_check_access.*
+
+smtpd_nullmx_test: smtpd_check smtpd_nullmx.in smtpd_nullmx.ref
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_nullmx.in >smtpd_nullmx.tmp 2>&1
+ $(ADDRINFO_FIX) smtpd_nullmx.tmp | diff smtpd_nullmx.ref -
+ rm -f smtpd_nullmx.tmp smtpd_check_access.*
+
+smtpd_dns_filter_test: smtpd_check smtpd_dns_filter.in smtpd_dns_filter.ref \
+ ../dns/no-mx.reg ../dns/no-a.reg ../dns/error.reg
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dns_filter.in 2>&1 | \
+ sed 's/\. [0-9]* IN/. TTL IN/' >smtpd_dns_filter.tmp
+ diff smtpd_dns_filter.ref smtpd_dns_filter.tmp
+ rm -f smtpd_dns_filter.tmp
+
+smtpd_check_dsn_test: smtpd_check smtpd_check_dsn.in smtpd_check_dsn.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_dsn.in >smtpd_check.tmp 2>&1
+ diff smtpd_check_dsn.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+# This requires that 168.100.3.7 is a local or virtual interface.
+
+smtpd_check_backup_test: smtpd_check smtpd_check_backup.in smtpd_check_backup.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_backup.in >smtpd_check.tmp 2>&1
+ diff smtpd_check_backup.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
+smtpd_token_test: smtpd_token smtpd_token.in smtpd_token.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_token <smtpd_token.in >smtpd_token.tmp 2>&1
+ diff smtpd_token.ref smtpd_token.tmp
+ rm -f smtpd_token.tmp
+
+# This requires that the DNS server can query porcupine.org and rfc-ignorant.org
+
+smtpd_dnswl_test: smtpd_check smtpd_dnswl.in smtpd_dnswl.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dnswl.in >smtpd_dnswl.tmp 2>&1
+ diff smtpd_dnswl.ref smtpd_dnswl.tmp
+ rm -f smtpd_dnswl.tmp
+
+smtpd_error_test: smtpd_check smtpd_error.in smtpd_error.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_error.in >smtpd_check.tmp 2>&1
+ diff smtpd_error.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
+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'
+smtpd.o: ../../include/anvil_clnt.h
+smtpd.o: ../../include/argv.h
+smtpd.o: ../../include/attr.h
+smtpd.o: ../../include/attr_clnt.h
+smtpd.o: ../../include/check_arg.h
+smtpd.o: ../../include/cleanup_user.h
+smtpd.o: ../../include/debug_peer.h
+smtpd.o: ../../include/dict.h
+smtpd.o: ../../include/dns.h
+smtpd.o: ../../include/dsn_mask.h
+smtpd.o: ../../include/ehlo_mask.h
+smtpd.o: ../../include/events.h
+smtpd.o: ../../include/flush_clnt.h
+smtpd.o: ../../include/hfrom_format.h
+smtpd.o: ../../include/htable.h
+smtpd.o: ../../include/inet_proto.h
+smtpd.o: ../../include/info_log_addr_form.h
+smtpd.o: ../../include/input_transp.h
+smtpd.o: ../../include/iostuff.h
+smtpd.o: ../../include/is_header.h
+smtpd.o: ../../include/lex_822.h
+smtpd.o: ../../include/mac_expand.h
+smtpd.o: ../../include/mac_parse.h
+smtpd.o: ../../include/mail_conf.h
+smtpd.o: ../../include/mail_date.h
+smtpd.o: ../../include/mail_error.h
+smtpd.o: ../../include/mail_params.h
+smtpd.o: ../../include/mail_proto.h
+smtpd.o: ../../include/mail_queue.h
+smtpd.o: ../../include/mail_server.h
+smtpd.o: ../../include/mail_stream.h
+smtpd.o: ../../include/mail_version.h
+smtpd.o: ../../include/maps.h
+smtpd.o: ../../include/match_list.h
+smtpd.o: ../../include/match_parent_style.h
+smtpd.o: ../../include/milter.h
+smtpd.o: ../../include/msg.h
+smtpd.o: ../../include/myaddrinfo.h
+smtpd.o: ../../include/myflock.h
+smtpd.o: ../../include/mymalloc.h
+smtpd.o: ../../include/namadr_list.h
+smtpd.o: ../../include/name_code.h
+smtpd.o: ../../include/name_mask.h
+smtpd.o: ../../include/normalize_mailhost_addr.h
+smtpd.o: ../../include/nvtable.h
+smtpd.o: ../../include/off_cvt.h
+smtpd.o: ../../include/quote_822_local.h
+smtpd.o: ../../include/quote_flags.h
+smtpd.o: ../../include/rec_type.h
+smtpd.o: ../../include/recipient_list.h
+smtpd.o: ../../include/record.h
+smtpd.o: ../../include/resolve_clnt.h
+smtpd.o: ../../include/smtp_stream.h
+smtpd.o: ../../include/smtputf8.h
+smtpd.o: ../../include/sock_addr.h
+smtpd.o: ../../include/split_at.h
+smtpd.o: ../../include/string_list.h
+smtpd.o: ../../include/stringops.h
+smtpd.o: ../../include/sys_defs.h
+smtpd.o: ../../include/tls.h
+smtpd.o: ../../include/tls_proxy.h
+smtpd.o: ../../include/tok822.h
+smtpd.o: ../../include/uxtext.h
+smtpd.o: ../../include/valid_hostname.h
+smtpd.o: ../../include/valid_mailhost_addr.h
+smtpd.o: ../../include/vbuf.h
+smtpd.o: ../../include/verify_sender_addr.h
+smtpd.o: ../../include/verp_sender.h
+smtpd.o: ../../include/vstream.h
+smtpd.o: ../../include/vstring.h
+smtpd.o: ../../include/vstring_vstream.h
+smtpd.o: ../../include/watchdog.h
+smtpd.o: ../../include/xtext.h
+smtpd.o: smtpd.c
+smtpd.o: smtpd.h
+smtpd.o: smtpd_chat.h
+smtpd.o: smtpd_check.h
+smtpd.o: smtpd_expand.h
+smtpd.o: smtpd_milter.h
+smtpd.o: smtpd_proxy.h
+smtpd.o: smtpd_sasl_glue.h
+smtpd.o: smtpd_sasl_proto.h
+smtpd.o: smtpd_token.h
+smtpd_chat.o: ../../include/argv.h
+smtpd_chat.o: ../../include/attr.h
+smtpd_chat.o: ../../include/check_arg.h
+smtpd_chat.o: ../../include/cleanup_user.h
+smtpd_chat.o: ../../include/dict.h
+smtpd_chat.o: ../../include/dns.h
+smtpd_chat.o: ../../include/hfrom_format.h
+smtpd_chat.o: ../../include/htable.h
+smtpd_chat.o: ../../include/int_filt.h
+smtpd_chat.o: ../../include/iostuff.h
+smtpd_chat.o: ../../include/line_wrap.h
+smtpd_chat.o: ../../include/mac_expand.h
+smtpd_chat.o: ../../include/mac_parse.h
+smtpd_chat.o: ../../include/mail_addr.h
+smtpd_chat.o: ../../include/mail_error.h
+smtpd_chat.o: ../../include/mail_params.h
+smtpd_chat.o: ../../include/mail_proto.h
+smtpd_chat.o: ../../include/mail_stream.h
+smtpd_chat.o: ../../include/maps.h
+smtpd_chat.o: ../../include/milter.h
+smtpd_chat.o: ../../include/msg.h
+smtpd_chat.o: ../../include/myaddrinfo.h
+smtpd_chat.o: ../../include/myflock.h
+smtpd_chat.o: ../../include/mymalloc.h
+smtpd_chat.o: ../../include/name_code.h
+smtpd_chat.o: ../../include/name_mask.h
+smtpd_chat.o: ../../include/nvtable.h
+smtpd_chat.o: ../../include/post_mail.h
+smtpd_chat.o: ../../include/rec_type.h
+smtpd_chat.o: ../../include/record.h
+smtpd_chat.o: ../../include/smtp_reply_footer.h
+smtpd_chat.o: ../../include/smtp_stream.h
+smtpd_chat.o: ../../include/smtputf8.h
+smtpd_chat.o: ../../include/sock_addr.h
+smtpd_chat.o: ../../include/stringops.h
+smtpd_chat.o: ../../include/sys_defs.h
+smtpd_chat.o: ../../include/tls.h
+smtpd_chat.o: ../../include/vbuf.h
+smtpd_chat.o: ../../include/vstream.h
+smtpd_chat.o: ../../include/vstring.h
+smtpd_chat.o: smtpd.h
+smtpd_chat.o: smtpd_chat.c
+smtpd_chat.o: smtpd_chat.h
+smtpd_chat.o: smtpd_expand.h
+smtpd_check.o: ../../include/argv.h
+smtpd_check.o: ../../include/attr.h
+smtpd_check.o: ../../include/attr_clnt.h
+smtpd_check.o: ../../include/attr_override.h
+smtpd_check.o: ../../include/check_arg.h
+smtpd_check.o: ../../include/cleanup_user.h
+smtpd_check.o: ../../include/conv_time.h
+smtpd_check.o: ../../include/ctable.h
+smtpd_check.o: ../../include/deliver_request.h
+smtpd_check.o: ../../include/dict.h
+smtpd_check.o: ../../include/dns.h
+smtpd_check.o: ../../include/domain_list.h
+smtpd_check.o: ../../include/dsn.h
+smtpd_check.o: ../../include/dsn_util.h
+smtpd_check.o: ../../include/fsspace.h
+smtpd_check.o: ../../include/htable.h
+smtpd_check.o: ../../include/inet_addr_list.h
+smtpd_check.o: ../../include/inet_proto.h
+smtpd_check.o: ../../include/info_log_addr_form.h
+smtpd_check.o: ../../include/input_transp.h
+smtpd_check.o: ../../include/iostuff.h
+smtpd_check.o: ../../include/ip_match.h
+smtpd_check.o: ../../include/is_header.h
+smtpd_check.o: ../../include/mac_expand.h
+smtpd_check.o: ../../include/mac_parse.h
+smtpd_check.o: ../../include/mail_addr.h
+smtpd_check.o: ../../include/mail_addr_find.h
+smtpd_check.o: ../../include/mail_addr_form.h
+smtpd_check.o: ../../include/mail_conf.h
+smtpd_check.o: ../../include/mail_error.h
+smtpd_check.o: ../../include/mail_params.h
+smtpd_check.o: ../../include/mail_proto.h
+smtpd_check.o: ../../include/mail_stream.h
+smtpd_check.o: ../../include/map_search.h
+smtpd_check.o: ../../include/maps.h
+smtpd_check.o: ../../include/match_list.h
+smtpd_check.o: ../../include/match_parent_style.h
+smtpd_check.o: ../../include/midna_domain.h
+smtpd_check.o: ../../include/milter.h
+smtpd_check.o: ../../include/msg.h
+smtpd_check.o: ../../include/msg_stats.h
+smtpd_check.o: ../../include/myaddrinfo.h
+smtpd_check.o: ../../include/myflock.h
+smtpd_check.o: ../../include/mymalloc.h
+smtpd_check.o: ../../include/mynetworks.h
+smtpd_check.o: ../../include/namadr_list.h
+smtpd_check.o: ../../include/name_code.h
+smtpd_check.o: ../../include/name_mask.h
+smtpd_check.o: ../../include/nvtable.h
+smtpd_check.o: ../../include/own_inet_addr.h
+smtpd_check.o: ../../include/rec_type.h
+smtpd_check.o: ../../include/recipient_list.h
+smtpd_check.o: ../../include/record.h
+smtpd_check.o: ../../include/resolve_clnt.h
+smtpd_check.o: ../../include/resolve_local.h
+smtpd_check.o: ../../include/smtp_stream.h
+smtpd_check.o: ../../include/sock_addr.h
+smtpd_check.o: ../../include/split_at.h
+smtpd_check.o: ../../include/string_list.h
+smtpd_check.o: ../../include/stringops.h
+smtpd_check.o: ../../include/strip_addr.h
+smtpd_check.o: ../../include/sys_defs.h
+smtpd_check.o: ../../include/tls.h
+smtpd_check.o: ../../include/valid_hostname.h
+smtpd_check.o: ../../include/valid_mailhost_addr.h
+smtpd_check.o: ../../include/valid_utf8_hostname.h
+smtpd_check.o: ../../include/vbuf.h
+smtpd_check.o: ../../include/verify_clnt.h
+smtpd_check.o: ../../include/vstream.h
+smtpd_check.o: ../../include/vstring.h
+smtpd_check.o: ../../include/xtext.h
+smtpd_check.o: smtpd.h
+smtpd_check.o: smtpd_check.c
+smtpd_check.o: smtpd_check.h
+smtpd_check.o: smtpd_dsn_fix.h
+smtpd_check.o: smtpd_expand.h
+smtpd_check.o: smtpd_resolve.h
+smtpd_check.o: smtpd_sasl_glue.h
+smtpd_dsn_fix.o: ../../include/msg.h
+smtpd_dsn_fix.o: ../../include/sys_defs.h
+smtpd_dsn_fix.o: smtpd_dsn_fix.c
+smtpd_dsn_fix.o: smtpd_dsn_fix.h
+smtpd_expand.o: ../../include/argv.h
+smtpd_expand.o: ../../include/attr.h
+smtpd_expand.o: ../../include/check_arg.h
+smtpd_expand.o: ../../include/dns.h
+smtpd_expand.o: ../../include/htable.h
+smtpd_expand.o: ../../include/iostuff.h
+smtpd_expand.o: ../../include/mac_expand.h
+smtpd_expand.o: ../../include/mac_parse.h
+smtpd_expand.o: ../../include/mail_params.h
+smtpd_expand.o: ../../include/mail_proto.h
+smtpd_expand.o: ../../include/mail_stream.h
+smtpd_expand.o: ../../include/milter.h
+smtpd_expand.o: ../../include/msg.h
+smtpd_expand.o: ../../include/myaddrinfo.h
+smtpd_expand.o: ../../include/mymalloc.h
+smtpd_expand.o: ../../include/name_code.h
+smtpd_expand.o: ../../include/name_mask.h
+smtpd_expand.o: ../../include/nvtable.h
+smtpd_expand.o: ../../include/sock_addr.h
+smtpd_expand.o: ../../include/stringops.h
+smtpd_expand.o: ../../include/sys_defs.h
+smtpd_expand.o: ../../include/tls.h
+smtpd_expand.o: ../../include/vbuf.h
+smtpd_expand.o: ../../include/vstream.h
+smtpd_expand.o: ../../include/vstring.h
+smtpd_expand.o: smtpd.h
+smtpd_expand.o: smtpd_expand.c
+smtpd_expand.o: smtpd_expand.h
+smtpd_haproxy.o: ../../include/argv.h
+smtpd_haproxy.o: ../../include/attr.h
+smtpd_haproxy.o: ../../include/check_arg.h
+smtpd_haproxy.o: ../../include/dns.h
+smtpd_haproxy.o: ../../include/haproxy_srvr.h
+smtpd_haproxy.o: ../../include/htable.h
+smtpd_haproxy.o: ../../include/iostuff.h
+smtpd_haproxy.o: ../../include/mail_params.h
+smtpd_haproxy.o: ../../include/mail_stream.h
+smtpd_haproxy.o: ../../include/milter.h
+smtpd_haproxy.o: ../../include/msg.h
+smtpd_haproxy.o: ../../include/myaddrinfo.h
+smtpd_haproxy.o: ../../include/mymalloc.h
+smtpd_haproxy.o: ../../include/name_code.h
+smtpd_haproxy.o: ../../include/name_mask.h
+smtpd_haproxy.o: ../../include/nvtable.h
+smtpd_haproxy.o: ../../include/smtp_stream.h
+smtpd_haproxy.o: ../../include/sock_addr.h
+smtpd_haproxy.o: ../../include/stringops.h
+smtpd_haproxy.o: ../../include/sys_defs.h
+smtpd_haproxy.o: ../../include/tls.h
+smtpd_haproxy.o: ../../include/valid_hostname.h
+smtpd_haproxy.o: ../../include/valid_mailhost_addr.h
+smtpd_haproxy.o: ../../include/vbuf.h
+smtpd_haproxy.o: ../../include/vstream.h
+smtpd_haproxy.o: ../../include/vstring.h
+smtpd_haproxy.o: smtpd.h
+smtpd_haproxy.o: smtpd_haproxy.c
+smtpd_milter.o: ../../include/argv.h
+smtpd_milter.o: ../../include/attr.h
+smtpd_milter.o: ../../include/check_arg.h
+smtpd_milter.o: ../../include/dns.h
+smtpd_milter.o: ../../include/htable.h
+smtpd_milter.o: ../../include/mail_params.h
+smtpd_milter.o: ../../include/mail_stream.h
+smtpd_milter.o: ../../include/milter.h
+smtpd_milter.o: ../../include/myaddrinfo.h
+smtpd_milter.o: ../../include/mymalloc.h
+smtpd_milter.o: ../../include/name_code.h
+smtpd_milter.o: ../../include/name_mask.h
+smtpd_milter.o: ../../include/nvtable.h
+smtpd_milter.o: ../../include/quote_821_local.h
+smtpd_milter.o: ../../include/quote_flags.h
+smtpd_milter.o: ../../include/resolve_clnt.h
+smtpd_milter.o: ../../include/sock_addr.h
+smtpd_milter.o: ../../include/split_at.h
+smtpd_milter.o: ../../include/stringops.h
+smtpd_milter.o: ../../include/sys_defs.h
+smtpd_milter.o: ../../include/tls.h
+smtpd_milter.o: ../../include/vbuf.h
+smtpd_milter.o: ../../include/vstream.h
+smtpd_milter.o: ../../include/vstring.h
+smtpd_milter.o: smtpd.h
+smtpd_milter.o: smtpd_milter.c
+smtpd_milter.o: smtpd_milter.h
+smtpd_milter.o: smtpd_resolve.h
+smtpd_milter.o: smtpd_sasl_glue.h
+smtpd_peer.o: ../../include/argv.h
+smtpd_peer.o: ../../include/attr.h
+smtpd_peer.o: ../../include/check_arg.h
+smtpd_peer.o: ../../include/dns.h
+smtpd_peer.o: ../../include/haproxy_srvr.h
+smtpd_peer.o: ../../include/htable.h
+smtpd_peer.o: ../../include/inet_proto.h
+smtpd_peer.o: ../../include/iostuff.h
+smtpd_peer.o: ../../include/mail_params.h
+smtpd_peer.o: ../../include/mail_proto.h
+smtpd_peer.o: ../../include/mail_stream.h
+smtpd_peer.o: ../../include/milter.h
+smtpd_peer.o: ../../include/msg.h
+smtpd_peer.o: ../../include/myaddrinfo.h
+smtpd_peer.o: ../../include/mymalloc.h
+smtpd_peer.o: ../../include/name_code.h
+smtpd_peer.o: ../../include/name_mask.h
+smtpd_peer.o: ../../include/nvtable.h
+smtpd_peer.o: ../../include/sock_addr.h
+smtpd_peer.o: ../../include/split_at.h
+smtpd_peer.o: ../../include/stringops.h
+smtpd_peer.o: ../../include/sys_defs.h
+smtpd_peer.o: ../../include/tls.h
+smtpd_peer.o: ../../include/valid_hostname.h
+smtpd_peer.o: ../../include/valid_mailhost_addr.h
+smtpd_peer.o: ../../include/vbuf.h
+smtpd_peer.o: ../../include/vstream.h
+smtpd_peer.o: ../../include/vstring.h
+smtpd_peer.o: smtpd.h
+smtpd_peer.o: smtpd_peer.c
+smtpd_proxy.o: ../../include/argv.h
+smtpd_proxy.o: ../../include/attr.h
+smtpd_proxy.o: ../../include/check_arg.h
+smtpd_proxy.o: ../../include/cleanup_user.h
+smtpd_proxy.o: ../../include/connect.h
+smtpd_proxy.o: ../../include/dns.h
+smtpd_proxy.o: ../../include/htable.h
+smtpd_proxy.o: ../../include/iostuff.h
+smtpd_proxy.o: ../../include/mail_error.h
+smtpd_proxy.o: ../../include/mail_params.h
+smtpd_proxy.o: ../../include/mail_proto.h
+smtpd_proxy.o: ../../include/mail_queue.h
+smtpd_proxy.o: ../../include/mail_stream.h
+smtpd_proxy.o: ../../include/milter.h
+smtpd_proxy.o: ../../include/msg.h
+smtpd_proxy.o: ../../include/myaddrinfo.h
+smtpd_proxy.o: ../../include/mymalloc.h
+smtpd_proxy.o: ../../include/name_code.h
+smtpd_proxy.o: ../../include/name_mask.h
+smtpd_proxy.o: ../../include/nvtable.h
+smtpd_proxy.o: ../../include/rec_type.h
+smtpd_proxy.o: ../../include/record.h
+smtpd_proxy.o: ../../include/smtp_stream.h
+smtpd_proxy.o: ../../include/sock_addr.h
+smtpd_proxy.o: ../../include/stringops.h
+smtpd_proxy.o: ../../include/sys_defs.h
+smtpd_proxy.o: ../../include/tls.h
+smtpd_proxy.o: ../../include/vbuf.h
+smtpd_proxy.o: ../../include/vstream.h
+smtpd_proxy.o: ../../include/vstring.h
+smtpd_proxy.o: ../../include/xtext.h
+smtpd_proxy.o: smtpd.h
+smtpd_proxy.o: smtpd_proxy.c
+smtpd_proxy.o: smtpd_proxy.h
+smtpd_resolve.o: ../../include/attr.h
+smtpd_resolve.o: ../../include/check_arg.h
+smtpd_resolve.o: ../../include/ctable.h
+smtpd_resolve.o: ../../include/htable.h
+smtpd_resolve.o: ../../include/iostuff.h
+smtpd_resolve.o: ../../include/mail_proto.h
+smtpd_resolve.o: ../../include/msg.h
+smtpd_resolve.o: ../../include/mymalloc.h
+smtpd_resolve.o: ../../include/nvtable.h
+smtpd_resolve.o: ../../include/resolve_clnt.h
+smtpd_resolve.o: ../../include/rewrite_clnt.h
+smtpd_resolve.o: ../../include/split_at.h
+smtpd_resolve.o: ../../include/stringops.h
+smtpd_resolve.o: ../../include/sys_defs.h
+smtpd_resolve.o: ../../include/vbuf.h
+smtpd_resolve.o: ../../include/vstream.h
+smtpd_resolve.o: ../../include/vstring.h
+smtpd_resolve.o: smtpd_resolve.c
+smtpd_resolve.o: smtpd_resolve.h
+smtpd_sasl_glue.o: ../../include/argv.h
+smtpd_sasl_glue.o: ../../include/attr.h
+smtpd_sasl_glue.o: ../../include/check_arg.h
+smtpd_sasl_glue.o: ../../include/dns.h
+smtpd_sasl_glue.o: ../../include/htable.h
+smtpd_sasl_glue.o: ../../include/mail_params.h
+smtpd_sasl_glue.o: ../../include/mail_stream.h
+smtpd_sasl_glue.o: ../../include/match_list.h
+smtpd_sasl_glue.o: ../../include/milter.h
+smtpd_sasl_glue.o: ../../include/msg.h
+smtpd_sasl_glue.o: ../../include/myaddrinfo.h
+smtpd_sasl_glue.o: ../../include/mymalloc.h
+smtpd_sasl_glue.o: ../../include/name_code.h
+smtpd_sasl_glue.o: ../../include/name_mask.h
+smtpd_sasl_glue.o: ../../include/nvtable.h
+smtpd_sasl_glue.o: ../../include/sasl_mech_filter.h
+smtpd_sasl_glue.o: ../../include/sock_addr.h
+smtpd_sasl_glue.o: ../../include/string_list.h
+smtpd_sasl_glue.o: ../../include/stringops.h
+smtpd_sasl_glue.o: ../../include/sys_defs.h
+smtpd_sasl_glue.o: ../../include/tls.h
+smtpd_sasl_glue.o: ../../include/vbuf.h
+smtpd_sasl_glue.o: ../../include/vstream.h
+smtpd_sasl_glue.o: ../../include/vstring.h
+smtpd_sasl_glue.o: ../../include/xsasl.h
+smtpd_sasl_glue.o: smtpd.h
+smtpd_sasl_glue.o: smtpd_chat.h
+smtpd_sasl_glue.o: smtpd_sasl_glue.c
+smtpd_sasl_glue.o: smtpd_sasl_glue.h
+smtpd_sasl_proto.o: ../../include/argv.h
+smtpd_sasl_proto.o: ../../include/attr.h
+smtpd_sasl_proto.o: ../../include/check_arg.h
+smtpd_sasl_proto.o: ../../include/dns.h
+smtpd_sasl_proto.o: ../../include/ehlo_mask.h
+smtpd_sasl_proto.o: ../../include/htable.h
+smtpd_sasl_proto.o: ../../include/iostuff.h
+smtpd_sasl_proto.o: ../../include/mail_error.h
+smtpd_sasl_proto.o: ../../include/mail_params.h
+smtpd_sasl_proto.o: ../../include/mail_proto.h
+smtpd_sasl_proto.o: ../../include/mail_stream.h
+smtpd_sasl_proto.o: ../../include/milter.h
+smtpd_sasl_proto.o: ../../include/msg.h
+smtpd_sasl_proto.o: ../../include/myaddrinfo.h
+smtpd_sasl_proto.o: ../../include/mymalloc.h
+smtpd_sasl_proto.o: ../../include/name_code.h
+smtpd_sasl_proto.o: ../../include/name_mask.h
+smtpd_sasl_proto.o: ../../include/nvtable.h
+smtpd_sasl_proto.o: ../../include/sock_addr.h
+smtpd_sasl_proto.o: ../../include/stringops.h
+smtpd_sasl_proto.o: ../../include/sys_defs.h
+smtpd_sasl_proto.o: ../../include/tls.h
+smtpd_sasl_proto.o: ../../include/vbuf.h
+smtpd_sasl_proto.o: ../../include/vstream.h
+smtpd_sasl_proto.o: ../../include/vstring.h
+smtpd_sasl_proto.o: smtpd.h
+smtpd_sasl_proto.o: smtpd_chat.h
+smtpd_sasl_proto.o: smtpd_sasl_glue.h
+smtpd_sasl_proto.o: smtpd_sasl_proto.c
+smtpd_sasl_proto.o: smtpd_sasl_proto.h
+smtpd_sasl_proto.o: smtpd_token.h
+smtpd_state.o: ../../include/argv.h
+smtpd_state.o: ../../include/attr.h
+smtpd_state.o: ../../include/check_arg.h
+smtpd_state.o: ../../include/cleanup_user.h
+smtpd_state.o: ../../include/dns.h
+smtpd_state.o: ../../include/events.h
+smtpd_state.o: ../../include/htable.h
+smtpd_state.o: ../../include/iostuff.h
+smtpd_state.o: ../../include/mail_error.h
+smtpd_state.o: ../../include/mail_params.h
+smtpd_state.o: ../../include/mail_proto.h
+smtpd_state.o: ../../include/mail_stream.h
+smtpd_state.o: ../../include/milter.h
+smtpd_state.o: ../../include/msg.h
+smtpd_state.o: ../../include/myaddrinfo.h
+smtpd_state.o: ../../include/mymalloc.h
+smtpd_state.o: ../../include/name_code.h
+smtpd_state.o: ../../include/name_mask.h
+smtpd_state.o: ../../include/nvtable.h
+smtpd_state.o: ../../include/sock_addr.h
+smtpd_state.o: ../../include/sys_defs.h
+smtpd_state.o: ../../include/tls.h
+smtpd_state.o: ../../include/vbuf.h
+smtpd_state.o: ../../include/vstream.h
+smtpd_state.o: ../../include/vstring.h
+smtpd_state.o: smtpd.h
+smtpd_state.o: smtpd_chat.h
+smtpd_state.o: smtpd_sasl_glue.h
+smtpd_state.o: smtpd_state.c
+smtpd_token.o: ../../include/check_arg.h
+smtpd_token.o: ../../include/mvect.h
+smtpd_token.o: ../../include/mymalloc.h
+smtpd_token.o: ../../include/sys_defs.h
+smtpd_token.o: ../../include/vbuf.h
+smtpd_token.o: ../../include/vstring.h
+smtpd_token.o: smtpd_token.c
+smtpd_token.o: smtpd_token.h
+smtpd_xforward.o: ../../include/argv.h
+smtpd_xforward.o: ../../include/attr.h
+smtpd_xforward.o: ../../include/check_arg.h
+smtpd_xforward.o: ../../include/dns.h
+smtpd_xforward.o: ../../include/htable.h
+smtpd_xforward.o: ../../include/iostuff.h
+smtpd_xforward.o: ../../include/mail_proto.h
+smtpd_xforward.o: ../../include/mail_stream.h
+smtpd_xforward.o: ../../include/milter.h
+smtpd_xforward.o: ../../include/msg.h
+smtpd_xforward.o: ../../include/myaddrinfo.h
+smtpd_xforward.o: ../../include/mymalloc.h
+smtpd_xforward.o: ../../include/name_code.h
+smtpd_xforward.o: ../../include/name_mask.h
+smtpd_xforward.o: ../../include/nvtable.h
+smtpd_xforward.o: ../../include/sock_addr.h
+smtpd_xforward.o: ../../include/sys_defs.h
+smtpd_xforward.o: ../../include/tls.h
+smtpd_xforward.o: ../../include/vbuf.h
+smtpd_xforward.o: ../../include/vstream.h
+smtpd_xforward.o: ../../include/vstring.h
+smtpd_xforward.o: smtpd.h
+smtpd_xforward.o: smtpd_xforward.c
diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c
new file mode 100644
index 0000000..4f4aedd
--- /dev/null
+++ b/src/smtpd/smtpd.c
@@ -0,0 +1,6776 @@
+/*++
+/* NAME
+/* smtpd 8
+/* SUMMARY
+/* Postfix SMTP server
+/* SYNOPSIS
+/* \fBsmtpd\fR [generic Postfix daemon options]
+/*
+/* \fBsendmail -bs\fR
+/* DESCRIPTION
+/* The SMTP server accepts network connection requests
+/* and performs zero or more SMTP transactions per connection.
+/* Each received message is piped through the \fBcleanup\fR(8)
+/* daemon, and is placed into the \fBincoming\fR queue as one
+/* single queue file. For this mode of operation, the program
+/* expects to be run from the \fBmaster\fR(8) process manager.
+/*
+/* Alternatively, the SMTP server be can run in stand-alone
+/* mode; this is traditionally obtained with "\fBsendmail
+/* -bs\fR". When the SMTP server runs stand-alone with non
+/* $\fBmail_owner\fR privileges, it receives mail even while
+/* the mail system is not running, deposits messages directly
+/* into the \fBmaildrop\fR queue, and disables the SMTP server's
+/* access policies. As of Postfix version 2.3, the SMTP server
+/* refuses to receive mail from the network when it runs with
+/* non $\fBmail_owner\fR privileges.
+/*
+/* The SMTP server implements a variety of policies for connection
+/* requests, and for parameters given to \fBHELO, ETRN, MAIL FROM, VRFY\fR
+/* and \fBRCPT TO\fR commands. They are detailed below and in the
+/* \fBmain.cf\fR configuration file.
+/* SECURITY
+/* .ad
+/* .fi
+/* The SMTP server is moderately security-sensitive. It talks to SMTP
+/* clients and to DNS servers on the network. The SMTP server can be
+/* run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 1123 (Host requirements)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1869 (SMTP service extensions)
+/* RFC 1870 (Message size declaration)
+/* RFC 1985 (ETRN command)
+/* RFC 2034 (SMTP enhanced status codes)
+/* RFC 2554 (AUTH command)
+/* RFC 2821 (SMTP protocol)
+/* RFC 2920 (SMTP pipelining)
+/* RFC 3030 (CHUNKING without BINARYMIME)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN extension)
+/* RFC 3463 (Enhanced status codes)
+/* RFC 3848 (ESMTP transmission types)
+/* RFC 4409 (Message submission)
+/* RFC 4954 (AUTH command)
+/* RFC 5321 (SMTP protocol)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* RFC 7505 ("Null MX" No Service Resource Record)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces, protocol problems,
+/* policy violations, and of other trouble.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtpd\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
+/* The following parameters work around implementation errors in other
+/* software, and/or allow you to override standards in order to prevent
+/* undesirable use.
+/* .ad
+/* .fi
+/* .IP "\fBbroken_sasl_auth_clients (no)\fR"
+/* Enable interoperability with remote SMTP clients that implement an obsolete
+/* version of the AUTH command (RFC 4954).
+/* .IP "\fBdisable_vrfy_command (no)\fR"
+/* Disable the SMTP VRFY command.
+/* .IP "\fBsmtpd_noop_commands (empty)\fR"
+/* List of commands that the Postfix SMTP server replies to with "250
+/* Ok", without doing any syntax checks and without changing state.
+/* .IP "\fBstrict_rfc821_envelopes (no)\fR"
+/* Require that addresses received in SMTP MAIL FROM and RCPT TO
+/* commands are enclosed with <>, and that those addresses do
+/* not contain RFC 822 style comments or phrases.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
+/* Request that the Postfix SMTP server rejects mail from unknown
+/* sender addresses, even when no explicit reject_unlisted_sender
+/* access restriction is specified.
+/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR"
+/* What remote SMTP clients the Postfix SMTP server will not offer
+/* AUTH support to.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtpd_discard_ehlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP client address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the Postfix SMTP server will not send in the EHLO response
+/* to a
+/* remote SMTP client.
+/* .IP "\fBsmtpd_discard_ehlo_keywords (empty)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix SMTP server will not send in the EHLO
+/* response
+/* to a remote SMTP client.
+/* .IP "\fBsmtpd_delay_open_until_valid_rcpt (yes)\fR"
+/* Postpone the start of an SMTP mail transaction until a valid
+/* RCPT TO command is received.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR"
+/* Force the Postfix SMTP server to issue a TLS session id, even
+/* when TLS session caching is turned off (smtpd_tls_session_cache_database
+/* is empty).
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBtcp_windowsize (0)\fR"
+/* An optional workaround for routers that break TCP window scaling.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBsmtpd_command_filter (empty)\fR"
+/* A mechanism to transform commands from remote SMTP clients.
+/* .PP
+/* Available in Postfix version 2.9 - 3.6:
+/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtpd_dns_reply_filter (empty)\fR"
+/* Optional filter for Postfix SMTP server DNS lookup results.
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBsmtpd_relay_before_recipient_restrictions (see 'postconf -d' output)\fR"
+/* Evaluate smtpd_relay_restrictions before smtpd_recipient_restrictions.
+/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR"
+/* Optional setting that avoids lookups in the \fBservices\fR(5) database.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a time limit per plaintext or TLS read or write
+/* call, to a combined time limit for receiving a complete SMTP request
+/* and for sending a complete SMTP response.
+/* .IP "\fBsmtpd_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA and BDAT requests, when deadlines are enabled with
+/* smtpd_per_request_deadline.
+/* ADDRESS REWRITING CONTROLS
+/* .ad
+/* .fi
+/* See the ADDRESS_REWRITING_README document for a detailed
+/* discussion of Postfix address rewriting.
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR"
+/* Rewrite message header addresses in mail from these clients and
+/* update incomplete addresses with the domain name in $myorigin or
+/* $mydomain; either don't rewrite message headers from other clients
+/* at all, or rewrite message headers and update incomplete addresses
+/* with the domain specified in the remote_header_rewrite_domain
+/* parameter.
+/* BEFORE-SMTPD PROXY AGENT
+/* .ad
+/* .fi
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_upstream_proxy_protocol (empty)\fR"
+/* The name of the proxy protocol used by an optional before-smtpd
+/* proxy agent.
+/* .IP "\fBsmtpd_upstream_proxy_timeout (5s)\fR"
+/* The time limit for the proxy protocol specified with the
+/* smtpd_upstream_proxy_protocol parameter.
+/* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* As of version 1.0, Postfix can be configured to send new mail to
+/* an external content filter AFTER the mail is queued. This content
+/* filter is expected to inject mail back into a (Postfix or other)
+/* MTA for further delivery. See the FILTER_README document for details.
+/* .IP "\fBcontent_filter (empty)\fR"
+/* After the message is queued, send the entire message to the
+/* specified \fItransport:destination\fR.
+/* BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.1, the Postfix SMTP server can be configured
+/* to send incoming mail to a real-time SMTP-based content filter
+/* BEFORE mail is queued. This content filter is expected to inject
+/* mail back into Postfix. See the SMTPD_PROXY_README document for
+/* details on how to configure and operate this feature.
+/* .IP "\fBsmtpd_proxy_filter (empty)\fR"
+/* The hostname and TCP port of the mail filtering proxy server.
+/* .IP "\fBsmtpd_proxy_ehlo ($myhostname)\fR"
+/* How the Postfix SMTP server announces itself to the proxy filter.
+/* .IP "\fBsmtpd_proxy_options (empty)\fR"
+/* List of options that control how the Postfix SMTP server
+/* communicates with a before-queue content filter.
+/* .IP "\fBsmtpd_proxy_timeout (100s)\fR"
+/* The time limit for connecting to a proxy filter and for sending or
+/* receiving information.
+/* BEFORE QUEUE MILTER CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.3, Postfix supports the Sendmail version 8
+/* Milter (mail filter) protocol. These content filters run
+/* outside Postfix. They can inspect the SMTP command stream
+/* and the message content, and can request modifications before
+/* mail is queued. For details see the MILTER_README document.
+/* .IP "\fBsmtpd_milters (empty)\fR"
+/* A list of Milter (mail filter) applications for new mail that
+/* arrives 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) response is
+/* unavailable (for example, bad Postfix configuration or Milter
+/* failure).
+/* .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_header_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the end of the message header.
+/* .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 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.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBsmtpd_milter_maps (empty)\fR"
+/* Lookup tables with Milter settings per remote SMTP client IP
+/* address.
+/* GENERAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* The following parameters are applicable for both built-in
+/* and external content filters.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* The following parameters are applicable for both before-queue
+/* and after-queue content filtering.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_xforward_hosts (empty)\fR"
+/* What remote SMTP clients are allowed to use the XFORWARD feature.
+/* SASL AUTHENTICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix SASL support (RFC 4954) can be used to authenticate remote
+/* SMTP clients to the Postfix SMTP server, and to authenticate the
+/* Postfix SMTP client to a remote SMTP server.
+/* See the SASL_README document for details.
+/* .IP "\fBbroken_sasl_auth_clients (no)\fR"
+/* Enable interoperability with remote SMTP clients that implement an obsolete
+/* version of the AUTH command (RFC 4954).
+/* .IP "\fBsmtpd_sasl_auth_enable (no)\fR"
+/* Enable SASL authentication in the Postfix SMTP server.
+/* .IP "\fBsmtpd_sasl_local_domain (empty)\fR"
+/* The name of the Postfix SMTP server's local SASL authentication
+/* realm.
+/* .IP "\fBsmtpd_sasl_security_options (noanonymous)\fR"
+/* Postfix SMTP server SASL security options; as of Postfix 2.3
+/* the list of available
+/* features depends on the SASL server implementation that is selected
+/* with \fBsmtpd_sasl_type\fR.
+/* .IP "\fBsmtpd_sender_login_maps (empty)\fR"
+/* Optional lookup table with the SASL login names that own the sender
+/* (MAIL FROM) addresses.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR"
+/* What remote SMTP clients the Postfix SMTP server will not offer
+/* AUTH support to.
+/* .PP
+/* Available in Postfix version 2.1 and 2.2:
+/* .IP "\fBsmtpd_sasl_application_name (smtpd)\fR"
+/* The application name that the Postfix SMTP server uses for SASL
+/* server initialization.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_sasl_authenticated_header (no)\fR"
+/* Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received
+/* message header.
+/* .IP "\fBsmtpd_sasl_path (smtpd)\fR"
+/* Implementation-specific information that the Postfix SMTP server
+/* passes through to
+/* the SASL plug-in implementation that is selected with
+/* \fBsmtpd_sasl_type\fR.
+/* .IP "\fBsmtpd_sasl_type (cyrus)\fR"
+/* The SASL plug-in type that the Postfix SMTP server should use
+/* for authentication.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBcyrus_sasl_config_path (empty)\fR"
+/* Search path for Cyrus SASL application configuration files,
+/* currently used only to locate the $smtpd_sasl_path.conf file.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtpd_sasl_service (smtp)\fR"
+/* The service name that is passed to the SASL plug-in that is
+/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR"
+/* The maximum length of a SASL client's response to a server challenge.
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBsmtpd_sasl_mechanism_filter (!external, static:rest)\fR"
+/* If non-empty, a filter for the SASL mechanism names that the
+/* Postfix SMTP server will announce in the EHLO response.
+/* STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* Detailed information about STARTTLS configuration may be
+/* found in the TLS_README document.
+/* .IP "\fBsmtpd_tls_security_level (empty)\fR"
+/* The SMTP TLS security level for the Postfix SMTP server; when
+/* a non-empty value is specified, this overrides the obsolete parameters
+/* smtpd_use_tls and smtpd_enforce_tls.
+/* .IP "\fBsmtpd_sasl_tls_security_options ($smtpd_sasl_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* server uses for TLS encrypted SMTP sessions.
+/* .IP "\fBsmtpd_starttls_timeout (see 'postconf -d' output)\fR"
+/* The time limit for Postfix SMTP server write and read operations
+/* during TLS startup and shutdown handshake procedures.
+/* .IP "\fBsmtpd_tls_CAfile (empty)\fR"
+/* A file containing (PEM format) CA certificates of root CAs trusted
+/* to sign either remote SMTP client certificates or intermediate CA
+/* certificates.
+/* .IP "\fBsmtpd_tls_CApath (empty)\fR"
+/* A directory containing (PEM format) CA certificates of root CAs
+/* trusted to sign either remote SMTP client certificates or intermediate CA
+/* certificates.
+/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR"
+/* Force the Postfix SMTP server to issue a TLS session id, even
+/* when TLS session caching is turned off (smtpd_tls_session_cache_database
+/* is empty).
+/* .IP "\fBsmtpd_tls_ask_ccert (no)\fR"
+/* Ask a remote SMTP client for a client certificate.
+/* .IP "\fBsmtpd_tls_auth_only (no)\fR"
+/* When TLS encryption is optional in the Postfix SMTP server, do
+/* not announce or accept SASL authentication over unencrypted
+/* connections.
+/* .IP "\fBsmtpd_tls_ccert_verifydepth (9)\fR"
+/* The verification depth for remote SMTP client certificates.
+/* .IP "\fBsmtpd_tls_cert_file (empty)\fR"
+/* File with the Postfix SMTP server RSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_exclude_ciphers (empty)\fR"
+/* List of ciphers or cipher types to exclude from the SMTP server
+/* cipher list at all TLS security levels.
+/* .IP "\fBsmtpd_tls_dcert_file (empty)\fR"
+/* File with the Postfix SMTP server DSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_dh1024_param_file (empty)\fR"
+/* File with DH parameters that the Postfix SMTP server should
+/* use with non-export EDH ciphers.
+/* .IP "\fBsmtpd_tls_dh512_param_file (empty)\fR"
+/* File with DH parameters that the Postfix SMTP server should
+/* use with export-grade EDH ciphers.
+/* .IP "\fBsmtpd_tls_dkey_file ($smtpd_tls_dcert_file)\fR"
+/* File with the Postfix SMTP server DSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR"
+/* File with the Postfix SMTP server RSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP server logging of TLS activity.
+/* .IP "\fBsmtpd_tls_mandatory_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP server will
+/* use with mandatory TLS encryption.
+/* .IP "\fBsmtpd_tls_mandatory_exclude_ciphers (empty)\fR"
+/* Additional list of ciphers or cipher types to exclude from the
+/* Postfix SMTP server cipher list at mandatory TLS security levels.
+/* .IP "\fBsmtpd_tls_mandatory_protocols (see 'postconf -d' output)\fR"
+/* TLS protocols accepted by the Postfix SMTP server with mandatory TLS
+/* encryption.
+/* .IP "\fBsmtpd_tls_received_header (no)\fR"
+/* Request that the Postfix SMTP server produces Received: message
+/* headers that include information about the protocol and cipher used,
+/* as well as the remote SMTP client CommonName and client certificate issuer
+/* CommonName.
+/* .IP "\fBsmtpd_tls_req_ccert (no)\fR"
+/* With mandatory TLS encryption, require a trusted remote SMTP client
+/* certificate in order to allow TLS connections to proceed.
+/* .IP "\fBsmtpd_tls_wrappermode (no)\fR"
+/* Run the Postfix SMTP server in the non-standard "wrapper" mode,
+/* instead of using the STARTTLS command.
+/* .IP "\fBtls_daemon_random_bytes (32)\fR"
+/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8)
+/* process requests from the \fBtlsmgr\fR(8) server in order to seed its
+/* internal pseudo random number generator (PRNG).
+/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "high" grade ciphers.
+/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "medium" or higher grade ciphers.
+/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "low" or higher grade ciphers.
+/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "export" or higher grade ciphers.
+/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR"
+/* The OpenSSL cipherlist for "NULL" grade ciphers that provide
+/* authentication without encryption.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtpd_tls_fingerprint_digest (see 'postconf -d' output)\fR"
+/* The message digest algorithm to construct remote SMTP client-certificate
+/* fingerprints or public key fingerprints (Postfix 2.9 and later) for
+/* \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBsmtpd_tls_protocols (see postconf -d output)\fR"
+/* TLS protocols accepted by the Postfix SMTP server with opportunistic
+/* TLS encryption.
+/* .IP "\fBsmtpd_tls_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP server
+/* will use with opportunistic TLS encryption.
+/* .IP "\fBsmtpd_tls_eccert_file (empty)\fR"
+/* File with the Postfix SMTP server ECDSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR"
+/* File with the Postfix SMTP server ECDSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_eecdh_grade (see 'postconf -d' output)\fR"
+/* The Postfix SMTP server security grade for ephemeral elliptic-curve
+/* Diffie-Hellman (EECDH) key exchange.
+/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for sensibly
+/* strong
+/* ephemeral ECDH key exchange.
+/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for maximally
+/* strong
+/* ephemeral ECDH key exchange.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBtls_preempt_cipherlist (no)\fR"
+/* With SSLv3 and later, use the Postfix SMTP server's cipher
+/* preference order instead of the remote client's cipher preference
+/* order.
+/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR"
+/* List or bit-mask of OpenSSL bug work-arounds to disable.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR"
+/* The name of the \fBtlsmgr\fR(8) service entry in master.cf.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR"
+/* Algorithm used to encrypt RFC5077 TLS session tickets.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR"
+/* The prioritized list of elliptic curves supported by the Postfix
+/* SMTP client and server.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtpd_tls_chain_files (empty)\fR"
+/* List of one or more PEM files, each holding one or more private keys
+/* directly followed by a corresponding certificate chain.
+/* .IP "\fBtls_server_sni_maps (empty)\fR"
+/* Optional lookup tables that map names received from remote SMTP
+/* clients via the TLS Server Name Indication (SNI) extension to the
+/* appropriate keys and certificate chains.
+/* .PP
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .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.).
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* OBSOLETE STARTTLS CONTROLS
+/* .ad
+/* .fi
+/* The following configuration parameters exist for compatibility
+/* with Postfix versions before 2.3. Support for these will
+/* be removed in a future release.
+/* .IP "\fBsmtpd_use_tls (no)\fR"
+/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+/* but do not require that clients use TLS encryption.
+/* .IP "\fBsmtpd_enforce_tls (no)\fR"
+/* Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+/* and require that clients use TLS encryption.
+/* .IP "\fBsmtpd_tls_cipherlist (empty)\fR"
+/* Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS
+/* cipher list.
+/* 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 "\fBstrict_smtputf8 (no)\fR"
+/* Enable stricter enforcement of the SMTPUTF8 protocol.
+/* .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.
+/* VERP SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* With VERP style delivery, each recipient of a message receives a
+/* customized copy of the message with his/her own recipient address
+/* encoded in the envelope sender address. The VERP_README file
+/* describes configuration and operation details of Postfix support
+/* for variable envelope return path addresses. VERP style delivery
+/* is requested with the SMTP XVERP command or with the "sendmail
+/* -V" command-line option and is available in Postfix version 1.1
+/* and later.
+/* .IP "\fBdefault_verp_delimiters (+=)\fR"
+/* The two default VERP delimiter characters.
+/* .IP "\fBverp_delimiter_filter (-=+)\fR"
+/* The characters Postfix accepts as VERP delimiter characters on the
+/* Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+/* .PP
+/* Available in Postfix version 1.1 and 2.0:
+/* .IP "\fBauthorized_verp_clients ($mynetworks)\fR"
+/* What remote SMTP clients are allowed to specify the XVERP command.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_verp_clients ($authorized_verp_clients)\fR"
+/* What remote SMTP clients are allowed to specify the XVERP command.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* The DEBUG_README document describes how to debug parts of the
+/* Postfix mail system. The methods vary from making the software log
+/* a lot of detail, to running some daemon processes under control of
+/* a call tracer or debugger.
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, cause the verbose
+/* logging level to increase by the amount specified in $debug_peer_level.
+/* .IP "\fBerror_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications about mail delivery
+/* problems that are caused by policy, resource, software or protocol
+/* errors.
+/* .IP "\fBinternal_mail_filter_classes (empty)\fR"
+/* What categories of Postfix-generated mail are subject to
+/* before-queue content inspection by non_smtpd_milters, header_checks
+/* and body_checks.
+/* .IP "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* .IP "\fBsmtpd_reject_footer (empty)\fR"
+/* Optional information that is appended after each Postfix SMTP
+/* server
+/* 4XX or 5XX response.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_xclient_hosts (empty)\fR"
+/* What remote SMTP clients are allowed to use the XCLIENT feature.
+/* .PP
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_log_access_permit_actions (empty)\fR"
+/* Enable logging of the named "permit" actions in SMTP server
+/* access lists (by default, the SMTP server logs "reject" actions but
+/* not "permit" actions).
+/* KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS
+/* .ad
+/* .fi
+/* As of Postfix version 2.0, the SMTP server rejects mail for
+/* unknown recipients. This prevents the mail queue from clogging up
+/* with undeliverable MAILER-DAEMON messages. Additional information
+/* on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README
+/* documents.
+/* .IP "\fBshow_user_unknown_table_name (yes)\fR"
+/* Display the name of the recipient table in the "User unknown"
+/* responses.
+/* .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.
+/* .PP
+/* Parameters concerning known/unknown local recipients:
+/* .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+/* The list of domains that are delivered via the $local_transport
+/* mail delivery transport.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .IP "\fBproxy_interfaces (empty)\fR"
+/* The network interface addresses that this mail system receives mail
+/* on by way of a proxy or network address translation unit.
+/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR"
+/* Lookup tables with all names or addresses of local recipients:
+/* a recipient address is local when its domain matches $mydestination,
+/* $inet_interfaces or $proxy_interfaces.
+/* .IP "\fBunknown_local_recipient_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when a recipient
+/* address is local, and $local_recipient_maps specifies a list of
+/* lookup tables that does not match the recipient.
+/* .PP
+/* Parameters concerning known/unknown recipients of relay destinations:
+/* .IP "\fBrelay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)\fR"
+/* What destination domains (and subdomains thereof) this system
+/* will relay mail to.
+/* .IP "\fBrelay_recipient_maps (empty)\fR"
+/* Optional lookup tables with all valid addresses in the domains
+/* that match $relay_domains.
+/* .IP "\fBunknown_relay_recipient_reject_code (550)\fR"
+/* The numerical Postfix SMTP server reply code when a recipient
+/* address matches $relay_domains, and relay_recipient_maps specifies
+/* a list of lookup tables that does not match the recipient address.
+/* .PP
+/* Parameters concerning known/unknown recipients in virtual alias
+/* domains:
+/* .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR"
+/* Postfix is final destination for the specified list of virtual
+/* alias domains, that is, domains for which all addresses are aliased
+/* to addresses in other local or remote domains.
+/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
+/* Optional lookup tables that alias specific mail addresses or domains
+/* to other local or remote address.
+/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR"
+/* The Postfix SMTP server reply code when a recipient address matches
+/* $virtual_alias_domains, and $virtual_alias_maps specifies a list
+/* of lookup tables that does not match the recipient address.
+/* .PP
+/* Parameters concerning known/unknown recipients in virtual mailbox
+/* domains:
+/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR"
+/* Postfix is final destination for the specified list of domains;
+/* mail is delivered via the $virtual_transport mail delivery transport.
+/* .IP "\fBvirtual_mailbox_maps (empty)\fR"
+/* Optional lookup tables with all valid addresses in the domains that
+/* match $virtual_mailbox_domains.
+/* .IP "\fBunknown_virtual_mailbox_reject_code (550)\fR"
+/* The Postfix SMTP server reply code when a recipient address matches
+/* $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list
+/* of lookup tables that does not match the recipient address.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* The following parameters limit resource usage by the SMTP
+/* server and/or control client request rates.
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBqueue_minfree (0)\fR"
+/* The minimal amount of free space in bytes in the queue file system
+/* that is needed to receive mail.
+/* .IP "\fBmessage_size_limit (10240000)\fR"
+/* The maximal size in bytes of a message, including envelope information.
+/* .IP "\fBsmtpd_recipient_limit (1000)\fR"
+/* The maximal number of recipients that the Postfix SMTP server
+/* accepts per message delivery request.
+/* .IP "\fBsmtpd_timeout (normal: 300s, overload: 10s)\fR"
+/* When the Postfix SMTP server wants to send an SMTP server
+/* response, how long the Postfix SMTP server will wait for an underlying
+/* network write operation to complete; and when the Postfix SMTP
+/* server Postfix wants to receive an SMTP client request, how long
+/* the Postfix SMTP server will wait for an underlying network read
+/* operation to complete.
+/* .IP "\fBsmtpd_history_flush_threshold (100)\fR"
+/* The maximal number of lines in the Postfix SMTP server command history
+/* before it is flushed upon receipt of EHLO, RSET, or end of DATA.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_peername_lookup (yes)\fR"
+/* Attempt to look up the remote SMTP client hostname, and verify that
+/* the name matches the client IP address.
+/* .PP
+/* The per SMTP client connection count and request rate limits are
+/* implemented in co-operation with the \fBanvil\fR(8) service, and
+/* are available in Postfix version 2.2 and later.
+/* .IP "\fBsmtpd_client_connection_count_limit (50)\fR"
+/* How many simultaneous connections any client is allowed to
+/* make to this service.
+/* .IP "\fBsmtpd_client_connection_rate_limit (0)\fR"
+/* The maximal number of connection attempts any client is allowed to
+/* make to this service per time unit.
+/* .IP "\fBsmtpd_client_message_rate_limit (0)\fR"
+/* The maximal number of message delivery requests that any client is
+/* allowed to make to this service per time unit, regardless of whether
+/* or not Postfix actually accepts those messages.
+/* .IP "\fBsmtpd_client_recipient_rate_limit (0)\fR"
+/* The maximal number of recipient addresses that any client is allowed
+/* to send to this service per time unit, regardless of whether or not
+/* Postfix actually accepts those recipients.
+/* .IP "\fBsmtpd_client_event_limit_exceptions ($mynetworks)\fR"
+/* Clients that are excluded from smtpd_client_*_count/rate_limit
+/* restrictions.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_client_new_tls_session_rate_limit (0)\fR"
+/* The maximal number of new (i.e., uncached) TLS sessions that a
+/* remote SMTP client is allowed to negotiate with this service per
+/* time unit.
+/* .PP
+/* Available in Postfix version 2.9 - 3.6:
+/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR"
+/* The maximal number of AUTH commands that any client is allowed to
+/* send to this service per time unit, regardless of whether or not
+/* Postfix actually accepts those commands.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a time limit per plaintext or TLS read or write
+/* call, to a combined time limit for receiving a complete SMTP request
+/* and for sending a complete SMTP response.
+/* .IP "\fBsmtpd_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA and BDAT requests, when deadlines are enabled with
+/* smtpd_per_request_deadline.
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBsmtpd_forbid_unauth_pipelining (Postfix >= 3.9: yes)\fR"
+/* Disconnect remote SMTP clients that violate RFC 2920 (or 5321)
+/* command pipelining constraints.
+/* .PP
+/* Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
+/* .IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR"
+/* Reject or restrict input lines from an SMTP client that end in
+/* <LF> instead of the standard <CR><LF>.
+/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
+/* Exclude the specified clients from smtpd_forbid_bare_newline
+/* enforcement.
+/* .PP
+/* Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and
+/* later:
+/* .IP "\fBsmtpd_forbid_bare_newline_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when rejecting a
+/* request with "smtpd_forbid_bare_newline = reject".
+/* TARPIT CONTROLS
+/* .ad
+/* .fi
+/* When a remote SMTP client makes errors, the Postfix SMTP server
+/* can insert delays before responding. This can help to slow down
+/* run-away software. The behavior is controlled by an error counter
+/* that counts the number of errors within an SMTP session that a
+/* client makes without delivering mail.
+/* .IP "\fBsmtpd_error_sleep_time (1s)\fR"
+/* With Postfix version 2.1 and later: the SMTP server response delay after
+/* a client has made more than $smtpd_soft_error_limit errors, and
+/* fewer than $smtpd_hard_error_limit errors, without delivering mail.
+/* .IP "\fBsmtpd_soft_error_limit (10)\fR"
+/* The number of errors a remote SMTP client is allowed to make without
+/* delivering mail before the Postfix SMTP server slows down all its
+/* responses.
+/* .IP "\fBsmtpd_hard_error_limit (normal: 20, overload: 1)\fR"
+/* The maximal number of errors a remote SMTP client is allowed to
+/* make without delivering mail.
+/* .IP "\fBsmtpd_junk_command_limit (normal: 100, overload: 1)\fR"
+/* The number of junk commands (NOOP, VRFY, ETRN or RSET) that a remote
+/* SMTP client can send before the Postfix SMTP server starts to
+/* increment the error counter with each junk command.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_recipient_overshoot_limit (1000)\fR"
+/* The number of recipients that a remote SMTP client can send in
+/* excess of the limit specified with $smtpd_recipient_limit, before
+/* the Postfix SMTP server increments the per-session error count
+/* for each excess recipient.
+/* ACCESS POLICY DELEGATION CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.1, Postfix can be configured to delegate access
+/* policy decisions to an external server that runs outside Postfix.
+/* See the file SMTPD_POLICY_README for more information.
+/* .IP "\fBsmtpd_policy_service_max_idle (300s)\fR"
+/* The time after which an idle SMTPD policy service connection is
+/* closed.
+/* .IP "\fBsmtpd_policy_service_max_ttl (1000s)\fR"
+/* The time after which an active SMTPD policy service connection is
+/* closed.
+/* .IP "\fBsmtpd_policy_service_timeout (100s)\fR"
+/* The time limit for connecting to, writing to, or receiving from a
+/* delegated SMTPD policy server.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtpd_policy_service_default_action (451 4.3.5 Server configuration problem)\fR"
+/* The default action when an SMTPD policy service request fails.
+/* .IP "\fBsmtpd_policy_service_request_limit (0)\fR"
+/* The maximal number of requests per SMTPD policy service connection,
+/* or zero (no limit).
+/* .IP "\fBsmtpd_policy_service_try_limit (2)\fR"
+/* The maximal number of attempts to send an SMTPD policy service
+/* request before giving up.
+/* .IP "\fBsmtpd_policy_service_retry_delay (1s)\fR"
+/* The delay between attempts to resend a failed SMTPD policy
+/* service request.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBsmtpd_policy_service_policy_context (empty)\fR"
+/* Optional information that the Postfix SMTP server specifies in
+/* the "policy_context" attribute of a policy service request (originally,
+/* to share the same service endpoint among multiple check_policy_service
+/* clients).
+/* ACCESS CONTROLS
+/* .ad
+/* .fi
+/* The SMTPD_ACCESS_README document gives an introduction to all the
+/* SMTP server access control features.
+/* .IP "\fBsmtpd_delay_reject (yes)\fR"
+/* Wait until the RCPT TO command before evaluating
+/* $smtpd_client_restrictions, $smtpd_helo_restrictions and
+/* $smtpd_sender_restrictions, or wait until the ETRN command before
+/* evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions.
+/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
+/* A list of Postfix features where the pattern "example.com" also
+/* matches subdomains of example.com,
+/* instead of requiring an explicit ".example.com" pattern.
+/* .IP "\fBsmtpd_client_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client connection request.
+/* .IP "\fBsmtpd_helo_required (no)\fR"
+/* Require that a remote SMTP client introduces itself with the HELO
+/* or EHLO command before sending the MAIL command or other commands
+/* that require EHLO negotiation.
+/* .IP "\fBsmtpd_helo_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client HELO command.
+/* .IP "\fBsmtpd_sender_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client MAIL FROM command.
+/* .IP "\fBsmtpd_recipient_restrictions (see 'postconf -d' output)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client RCPT TO command, after smtpd_relay_restrictions.
+/* .IP "\fBsmtpd_etrn_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client ETRN command.
+/* .IP "\fBallow_untrusted_routing (no)\fR"
+/* Forward mail with sender-specified routing (user[@%!]remote[@%!]site)
+/* from untrusted clients to destinations matching $relay_domains.
+/* .IP "\fBsmtpd_restriction_classes (empty)\fR"
+/* User-defined aliases for groups of access restrictions.
+/* .IP "\fBsmtpd_null_access_lookup_key (<>)\fR"
+/* The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the
+/* null sender address.
+/* .IP "\fBpermit_mx_backup_networks (empty)\fR"
+/* Restrict the use of the permit_mx_backup SMTP access feature to
+/* only domains whose primary MX hosts match the listed networks.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBsmtpd_data_restrictions (empty)\fR"
+/* Optional access restrictions that the Postfix SMTP server applies
+/* in the context of the SMTP DATA command.
+/* .IP "\fBsmtpd_expansion_filter (see 'postconf -d' output)\fR"
+/* What characters are allowed in $name expansions of RBL reply
+/* templates.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
+/* Request that the Postfix SMTP server rejects mail from unknown
+/* sender addresses, even when no explicit reject_unlisted_sender
+/* access restriction is specified.
+/* .IP "\fBsmtpd_reject_unlisted_recipient (yes)\fR"
+/* Request that the Postfix SMTP server rejects mail for unknown
+/* recipient addresses, even when no explicit reject_unlisted_recipient
+/* access restriction is specified.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtpd_end_of_data_restrictions (empty)\fR"
+/* Optional access restrictions that the Postfix SMTP server
+/* applies in the context of the SMTP END-OF-DATA command.
+/* .PP
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)\fR"
+/* Access restrictions for mail relay control that the Postfix
+/* SMTP server applies in the context of the RCPT TO command, before
+/* smtpd_recipient_restrictions.
+/* SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix version 2.1 introduces sender and recipient address verification.
+/* This feature is implemented by sending probe email messages that
+/* are not actually delivered.
+/* This feature is requested via the reject_unverified_sender and
+/* reject_unverified_recipient access restrictions. The status of
+/* verification probes is maintained by the \fBverify\fR(8) server.
+/* See the file ADDRESS_VERIFICATION_README for information
+/* about how to configure and operate the Postfix sender/recipient
+/* address verification service.
+/* .IP "\fBaddress_verify_poll_count (normal: 3, overload: 1)\fR"
+/* How many times to query the \fBverify\fR(8) service for the completion
+/* of an address verification request in progress.
+/* .IP "\fBaddress_verify_poll_delay (3s)\fR"
+/* The delay between queries for the completion of an address
+/* verification request in progress.
+/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR"
+/* The sender address to use in address verification probes; prior
+/* to Postfix 2.5 the default was "postmaster".
+/* .IP "\fBunverified_sender_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a recipient
+/* address is rejected by the reject_unverified_sender restriction.
+/* .IP "\fBunverified_recipient_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response when a recipient address
+/* is rejected by the reject_unverified_recipient restriction.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBunverified_sender_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a sender address
+/* probe fails due to a temporary error condition.
+/* .IP "\fBunverified_recipient_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response when a recipient address
+/* probe fails due to a temporary error condition.
+/* .IP "\fBunverified_sender_reject_reason (empty)\fR"
+/* The Postfix SMTP server's reply when rejecting mail with
+/* reject_unverified_sender.
+/* .IP "\fBunverified_recipient_reject_reason (empty)\fR"
+/* The Postfix SMTP server's reply when rejecting mail with
+/* reject_unverified_recipient.
+/* .IP "\fBunverified_sender_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unverified_sender
+/* fails due to a temporary error condition.
+/* .IP "\fBunverified_recipient_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unverified_recipient
+/* fails due to a temporary error condition.
+/* .PP
+/* Available with Postfix 2.9 and later:
+/* .IP "\fBaddress_verify_sender_ttl (0s)\fR"
+/* The time between changes in the time-dependent portion of address
+/* verification probe sender addresses.
+/* ACCESS CONTROL RESPONSES
+/* .ad
+/* .fi
+/* The following parameters control numerical SMTP reply codes
+/* and/or text responses.
+/* .IP "\fBaccess_map_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code for
+/* an \fBaccess\fR(5) map "reject" action.
+/* .IP "\fBdefer_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is rejected by the "defer" restriction.
+/* .IP "\fBinvalid_hostname_reject_code (501)\fR"
+/* The numerical Postfix SMTP server response code when the client
+/* HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname
+/* restriction.
+/* .IP "\fBmaps_rbl_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is blocked by the reject_rbl_client, reject_rhsbl_client,
+/* reject_rhsbl_reverse_client, reject_rhsbl_sender or
+/* reject_rhsbl_recipient restriction.
+/* .IP "\fBnon_fqdn_reject_code (504)\fR"
+/* The numerical Postfix SMTP server reply code when a client request
+/* is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender
+/* or reject_non_fqdn_recipient restriction.
+/* .IP "\fBplaintext_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a request
+/* is rejected by the \fBreject_plaintext_session\fR restriction.
+/* .IP "\fBreject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is rejected by the "reject" restriction.
+/* .IP "\fBrelay_domains_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a client
+/* request is rejected by the reject_unauth_destination recipient
+/* restriction.
+/* .IP "\fBunknown_address_reject_code (450)\fR"
+/* The numerical response code when the Postfix SMTP server rejects a
+/* sender or recipient address because its domain is unknown.
+/* .IP "\fBunknown_client_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a client
+/* without valid address <=> name mapping is rejected by the
+/* reject_unknown_client_hostname restriction.
+/* .IP "\fBunknown_hostname_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when the hostname
+/* specified with the HELO or EHLO command is rejected by the
+/* reject_unknown_helo_hostname restriction.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBdefault_rbl_reply (see 'postconf -d' output)\fR"
+/* The default Postfix SMTP server response template for a request that is
+/* rejected by an RBL-based restriction.
+/* .IP "\fBmulti_recipient_bounce_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is blocked by the reject_multi_recipient_bounce
+/* restriction.
+/* .IP "\fBrbl_reply_maps (empty)\fR"
+/* Optional lookup tables with RBL response templates.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBaccess_map_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response code for
+/* an \fBaccess\fR(5) map "defer" action, including "defer_if_permit"
+/* or "defer_if_reject".
+/* .IP "\fBreject_tempfail_action (defer_if_permit)\fR"
+/* The Postfix SMTP server's action when a reject-type restriction
+/* fails due to a temporary error condition.
+/* .IP "\fBunknown_helo_hostname_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unknown_helo_hostname
+/* fails due to a temporary error condition.
+/* .IP "\fBunknown_address_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unknown_sender_domain
+/* or reject_unknown_recipient_domain fail due to a temporary error
+/* condition.
+/* 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 "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .IP "\fBdouble_bounce_sender (double-bounce)\fR"
+/* The sender address of postmaster notifications that are generated
+/* by the mail system.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmail_name (Postfix)\fR"
+/* The mail system name that is displayed in Received: headers, in
+/* the SMTP greeting banner, and in bounced mail.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .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 "\fBmynetworks (see 'postconf -d' output)\fR"
+/* The list of "trusted" remote SMTP clients that have more privileges than
+/* "strangers".
+/* .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 "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
+/* The text that follows the 220 status code in the SMTP greeting
+/* banner.
+/* .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.2 and later:
+/* .IP "\fBsmtpd_forbidden_commands (CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}})\fR"
+/* List of commands that cause the Postfix SMTP server to immediately
+/* terminate the session with a 221 code.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtpd_client_port_logging (no)\fR"
+/* Enable logging of the remote SMTP client port in addition to
+/* the hostname and IP address.
+/* .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.4 and later:
+/* .IP "\fBsmtpd_reject_footer_maps (empty)\fR"
+/* Lookup tables, indexed by the complete Postfix SMTP server 4xx or
+/* 5xx response, with reject footer templates.
+/* SEE ALSO
+/* anvil(8), connection/rate limiting
+/* cleanup(8), message canonicalization
+/* tlsmgr(8), TLS session and PRNG management
+/* trivial-rewrite(8), address resolver
+/* verify(8), address verification service
+/* 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_CLASS_README, blocking unknown hosted or relay recipients
+/* ADDRESS_REWRITING_README, Postfix address manipulation
+/* BDAT_README, Postfix CHUNKING support
+/* FILTER_README, external after-queue content filter
+/* LOCAL_RECIPIENT_README, blocking unknown local recipients
+/* MILTER_README, before-queue mail filter applications
+/* SMTPD_ACCESS_README, built-in access policies
+/* SMTPD_POLICY_README, external policy server
+/* SMTPD_PROXY_README, external before-queue content filter
+/* SASL_README, Postfix SASL howto
+/* TLS_README, Postfix STARTTLS howto
+/* VERP_README, Postfix XVERP extension
+/* XCLIENT_README, Postfix XCLIENT extension
+/* XFORWARD_README, Postfix XFORWARD extension
+/* 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
+/*
+/* SASL support originally by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Revised TLS support by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h> /* remove() */
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <signal.h>
+#include <stddef.h> /* offsetof() */
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <events.h>
+#include <smtp_stream.h>
+#include <valid_hostname.h>
+#include <dict.h>
+#include <watchdog.h>
+#include <iostuff.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h> /* milter_macro_v */
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_conf.h>
+#include <off_cvt.h>
+#include <debug_peer.h>
+#include <mail_error.h>
+#include <flush_clnt.h>
+#include <mail_stream.h>
+#include <mail_queue.h>
+#include <tok822.h>
+#include <verp_sender.h>
+#include <string_list.h>
+#include <quote_822_local.h>
+#include <lex_822.h>
+#include <namadr_list.h>
+#include <input_transp.h>
+#include <is_header.h>
+#include <anvil_clnt.h>
+#include <flush_clnt.h>
+#include <ehlo_mask.h> /* ehlo filter */
+#include <maps.h> /* ehlo filter */
+#include <valid_mailhost_addr.h>
+#include <dsn_mask.h>
+#include <xtext.h>
+#include <uxtext.h>
+#include <tls_proxy.h>
+#include <verify_sender_addr.h>
+#include <smtputf8.h>
+#include <match_parent_style.h>
+#include <normalize_mailhost_addr.h>
+#include <info_log_addr_form.h>
+#include <hfrom_format.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Mail filter library. */
+
+#include <milter.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific */
+
+#include <smtpd_token.h>
+#include <smtpd.h>
+#include <smtpd_check.h>
+#include <smtpd_chat.h>
+#include <smtpd_sasl_proto.h>
+#include <smtpd_sasl_glue.h>
+#include <smtpd_proxy.h>
+#include <smtpd_milter.h>
+#include <smtpd_expand.h>
+
+ /*
+ * Tunable parameters. Make sure that there is some bound on the length of
+ * an SMTP command, so that the mail system stays in control even when a
+ * malicious client sends commands of unreasonable length (qmail-dos-1).
+ * Make sure there is some bound on the number of recipients, so that the
+ * mail system stays in control even when a malicious client sends an
+ * unreasonable number of recipients (qmail-dos-2).
+ */
+int var_smtpd_rcpt_limit;
+int var_smtpd_tmout;
+int var_smtpd_soft_erlim;
+int var_smtpd_hard_erlim;
+long var_queue_minfree; /* XXX use off_t */
+char *var_smtpd_banner;
+char *var_notify_classes;
+char *var_client_checks;
+char *var_helo_checks;
+char *var_mail_checks;
+char *var_relay_checks;
+char *var_rcpt_checks;
+char *var_etrn_checks;
+char *var_data_checks;
+char *var_eod_checks;
+int var_unk_client_code;
+int var_bad_name_code;
+int var_unk_name_code;
+int var_unk_addr_code;
+int var_relay_code;
+int var_maps_rbl_code;
+int var_map_reject_code;
+int var_map_defer_code;
+char *var_maps_rbl_domains;
+char *var_rbl_reply_maps;
+int var_helo_required;
+int var_reject_code;
+int var_defer_code;
+int var_smtpd_err_sleep;
+int var_non_fqdn_code;
+char *var_bounce_rcpt;
+char *var_error_rcpt;
+int var_smtpd_delay_reject;
+char *var_rest_classes;
+int var_strict_rfc821_env;
+bool var_disable_vrfy_cmd;
+char *var_canonical_maps;
+char *var_send_canon_maps;
+char *var_rcpt_canon_maps;
+char *var_virt_alias_maps;
+char *var_virt_mailbox_maps;
+char *var_alias_maps;
+char *var_local_rcpt_maps;
+bool var_allow_untrust_route;
+int var_smtpd_junk_cmd_limit;
+int var_smtpd_rcpt_overlim;
+bool var_smtpd_sasl_enable;
+bool var_smtpd_sasl_auth_hdr;
+char *var_smtpd_sasl_opts;
+char *var_smtpd_sasl_path;
+char *var_smtpd_sasl_service;
+char *var_cyrus_conf_path;
+char *var_smtpd_sasl_realm;
+int var_smtpd_sasl_resp_limit;
+char *var_smtpd_sasl_exceptions_networks;
+char *var_smtpd_sasl_type;
+char *var_smtpd_sasl_mech_filter;
+char *var_filter_xport;
+bool var_broken_auth_clients;
+char *var_perm_mx_networks;
+char *var_smtpd_snd_auth_maps;
+char *var_smtpd_noop_cmds;
+char *var_smtpd_null_key;
+int var_smtpd_hist_thrsh;
+char *var_smtpd_exp_filter;
+char *var_def_rbl_reply;
+int var_unv_from_rcode;
+int var_unv_rcpt_rcode;
+int var_unv_from_dcode;
+int var_unv_rcpt_dcode;
+char *var_unv_from_why;
+char *var_unv_rcpt_why;
+int var_mul_rcpt_code;
+char *var_relay_rcpt_maps;
+int var_local_rcpt_code;
+int var_virt_alias_code;
+int var_virt_mailbox_code;
+int var_relay_rcpt_code;
+char *var_verp_clients;
+int var_show_unk_rcpt_table;
+int var_verify_poll_count;
+int var_verify_poll_delay;
+char *var_smtpd_proxy_filt;
+int var_smtpd_proxy_tmout;
+char *var_smtpd_proxy_ehlo;
+char *var_smtpd_proxy_opts;
+char *var_input_transp;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_req_limit;
+int var_smtpd_policy_try_limit;
+int var_smtpd_policy_try_delay;
+char *var_smtpd_policy_def_action;
+char *var_smtpd_policy_context;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
+char *var_xclient_hosts;
+char *var_xforward_hosts;
+bool var_smtpd_rej_unl_from;
+bool var_smtpd_rej_unl_rcpt;
+char *var_smtpd_forbid_cmds;
+int var_smtpd_crate_limit;
+int var_smtpd_cconn_limit;
+int var_smtpd_cmail_limit;
+int var_smtpd_crcpt_limit;
+int var_smtpd_cntls_limit;
+int var_smtpd_cauth_limit;
+char *var_smtpd_hoggers;
+char *var_local_rwr_clients;
+char *var_smtpd_ehlo_dis_words;
+char *var_smtpd_ehlo_dis_maps;
+
+char *var_smtpd_tls_level;
+bool var_smtpd_use_tls;
+bool var_smtpd_enforce_tls;
+bool var_smtpd_tls_wrappermode;
+bool var_smtpd_tls_auth_only;
+char *var_smtpd_cmd_filter;
+char *var_smtpd_rej_footer;
+char *var_smtpd_rej_ftr_maps;
+char *var_smtpd_acl_perm_log;
+char *var_smtpd_dns_re_filter;
+
+#ifdef USE_TLS
+char *var_smtpd_relay_ccerts;
+char *var_smtpd_sasl_tls_opts;
+int var_smtpd_starttls_tmout;
+char *var_smtpd_tls_CAfile;
+char *var_smtpd_tls_CApath;
+bool var_smtpd_tls_ask_ccert;
+int var_smtpd_tls_ccert_vd;
+char *var_smtpd_tls_cert_file;
+char *var_smtpd_tls_mand_ciph;
+char *var_smtpd_tls_excl_ciph;
+char *var_smtpd_tls_mand_excl;
+char *var_smtpd_tls_dcert_file;
+char *var_smtpd_tls_dh1024_param_file;
+char *var_smtpd_tls_dh512_param_file;
+char *var_smtpd_tls_dkey_file;
+char *var_smtpd_tls_key_file;
+char *var_smtpd_tls_loglevel;
+char *var_smtpd_tls_mand_proto;
+bool var_smtpd_tls_received_header;
+bool var_smtpd_tls_req_ccert;
+bool var_smtpd_tls_set_sessid;
+char *var_smtpd_tls_fpt_dgst;
+char *var_smtpd_tls_ciph;
+char *var_smtpd_tls_proto;
+char *var_smtpd_tls_eecdh;
+char *var_smtpd_tls_eccert_file;
+char *var_smtpd_tls_eckey_file;
+char *var_smtpd_tls_chain_files;
+
+#endif
+
+bool var_smtpd_peername_lookup;
+int var_plaintext_code;
+bool var_smtpd_delay_open;
+char *var_smtpd_milters;
+char *var_smtpd_milter_maps;
+int var_milt_conn_time;
+int var_milt_cmd_time;
+int var_milt_msg_time;
+char *var_milt_protocol;
+char *var_milt_def_action;
+char *var_milt_daemon_name;
+char *var_milt_v;
+char *var_milt_conn_macros;
+char *var_milt_helo_macros;
+char *var_milt_mail_macros;
+char *var_milt_rcpt_macros;
+char *var_milt_data_macros;
+char *var_milt_eoh_macros;
+char *var_milt_eod_macros;
+char *var_milt_unk_macros;
+char *var_milt_macro_deflts;
+bool var_smtpd_client_port_log;
+bool var_smtpd_forbid_unauth_pipe;
+char *var_stress;
+
+char *var_reject_tmpf_act;
+char *var_unk_name_tf_act;
+char *var_unk_addr_tf_act;
+char *var_unv_rcpt_tf_act;
+char *var_unv_from_tf_act;
+
+int smtpd_proxy_opts;
+
+#ifdef USE_TLSPROXY
+char *var_tlsproxy_service;
+
+#endif
+
+char *var_smtpd_uproxy_proto;
+int var_smtpd_uproxy_tmout;
+bool var_relay_before_rcpt_checks;
+bool var_smtpd_req_deadline;
+int var_smtpd_min_data_rate;
+char *var_hfrom_format;
+char *var_smtpd_forbid_bare_lf;
+char *var_smtpd_forbid_bare_lf_excl;
+int var_smtpd_forbid_bare_lf_code;
+static int bare_lf_mask;
+static NAMADR_LIST *bare_lf_excl;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * EHLO keyword filter
+ */
+static MAPS *ehlo_discard_maps;
+
+ /*
+ * Per-client Milter support.
+ */
+static MAPS *smtpd_milter_maps;
+static void setup_milters(SMTPD_STATE *);
+static void teardown_milters(SMTPD_STATE *);
+
+ /*
+ * VERP command name.
+ */
+#define VERP_CMD "XVERP"
+#define VERP_CMD_LEN 5
+
+static NAMADR_LIST *verp_clients;
+
+ /*
+ * XCLIENT command. Access control is cached, so that XCLIENT can't override
+ * its own access control.
+ */
+static NAMADR_LIST *xclient_hosts;
+static int xclient_allowed; /* XXX should be SMTPD_STATE member */
+
+ /*
+ * XFORWARD command. Access control is cached.
+ */
+static NAMADR_LIST *xforward_hosts;
+static int xforward_allowed; /* XXX should be SMTPD_STATE member */
+
+ /*
+ * Client connection and rate limiting.
+ */
+ANVIL_CLNT *anvil_clnt;
+static NAMADR_LIST *hogger_list;
+
+ /*
+ * Other application-specific globals.
+ */
+int smtpd_input_transp_mask;
+
+ /*
+ * Forward declarations.
+ */
+static void helo_reset(SMTPD_STATE *);
+static void mail_reset(SMTPD_STATE *);
+static void rcpt_reset(SMTPD_STATE *);
+static void chat_reset(SMTPD_STATE *, int);
+
+#ifdef USE_TLS
+static void tls_reset(SMTPD_STATE *);
+
+#endif
+
+ /*
+ * This filter is applied after printable().
+ */
+#define NEUTER_CHARACTERS " <>()\\\";@"
+
+ /*
+ * Reasons for losing the client.
+ */
+#define REASON_TIMEOUT "timeout"
+#define REASON_LOST_CONNECTION "lost connection"
+#define REASON_ERROR_LIMIT "too many errors"
+
+#ifdef USE_TLS
+
+ /*
+ * TLS initialization status.
+ */
+#ifndef USE_TLSPROXY
+static TLS_APPL_STATE *smtpd_tls_ctx;
+static int ask_client_cert;
+
+#endif /* USE_TLSPROXY */
+#endif
+
+ /*
+ * SMTP command mapping for broken clients.
+ */
+static DICT *smtpd_cmd_filter;
+
+ /*
+ * Parsed header_from_format setting.
+ */
+int smtpd_hfrom_format;
+
+ /*
+ * Bare LF and End-of-DATA controls (bare CR is handled elsewhere).
+ *
+ * At the smtp_get*() line reader level, setting any of these flags in the
+ * smtp_detect_bare_lf variable enables the detection of bare newlines. The
+ * line reader will set the same flags in the smtp_got_bare_lf variable
+ * after it detects a bare newline, otherwise it clears smtp_got_bare_lf.
+ *
+ * At the SMTP command level, the flags in smtp_got_bare_lf control whether
+ * commands ending in a bare newline are rejected.
+ *
+ * At the DATA and BDAT content level, the flags in smtp_got_bare_lf control
+ * whether the standard End-of-DATA sequence CRLF.CRLF is required, and
+ * whether lines ending in bare newlines are rejected.
+ *
+ * Postfix implements "delayed reject" after detecting a bare newline in BDAT
+ * or DATA content. The SMTP server delays a REJECT response until the
+ * command is finished, instead of replying and hanging up immediately. The
+ * End-of-DATA detection is secured with BARE_LF_FLAG_WANT_STD_EOD.
+ */
+#define BARE_LF_FLAG_WANT_STD_EOD (1<<0) /* Require CRLF.CRLF */
+#define BARE_LF_FLAG_REPLY_REJECT (1<<1) /* Reject bare newline */
+
+#define IS_BARE_LF_WANT_STD_EOD(m) ((m) & BARE_LF_FLAG_WANT_STD_EOD)
+#define IS_BARE_LF_REPLY_REJECT(m) ((m) & BARE_LF_FLAG_REPLY_REJECT)
+
+static const NAME_CODE bare_lf_mask_table[] = {
+ "normalize", BARE_LF_FLAG_WANT_STD_EOD, /* Default */
+ "yes", BARE_LF_FLAG_WANT_STD_EOD, /* Migration aid */
+ "reject", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_REPLY_REJECT,
+ "no", 0,
+ 0, -1, /* error */
+};
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * SASL exceptions.
+ */
+static NAMADR_LIST *sasl_exceptions_networks;
+
+/* sasl_client_exception - can we offer AUTH for this client */
+
+static int sasl_client_exception(SMTPD_STATE *state)
+{
+ int match;
+
+ /*
+ * This is to work around a Netscape mail client bug where it tries to
+ * use AUTH if available, even if user has not configured it. Returns
+ * TRUE if AUTH should be offered in the EHLO.
+ */
+ if (sasl_exceptions_networks == 0)
+ return (0);
+
+ if ((match = namadr_list_match(sasl_exceptions_networks,
+ state->name, state->addr)) == 0)
+ match = sasl_exceptions_networks->error;
+
+ if (msg_verbose)
+ msg_info("sasl_exceptions: %s, match=%d",
+ state->namaddr, match);
+
+ return (match);
+}
+
+#endif
+
+/* smtpd_whatsup - gather available evidence for logging */
+
+static const char *smtpd_whatsup(SMTPD_STATE *state)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ else
+ VSTRING_RESET(buf);
+ if (state->sender)
+ vstring_sprintf_append(buf, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recipient)
+ vstring_sprintf_append(buf, " to=<%s>",
+ info_log_addr_form_recipient(state->recipient));
+ if (state->protocol)
+ vstring_sprintf_append(buf, " proto=%s", state->protocol);
+ if (state->helo_name)
+ vstring_sprintf_append(buf, " helo=<%s>", state->helo_name);
+#ifdef USE_SASL_AUTH
+ if (state->sasl_username)
+ vstring_sprintf_append(buf, " sasl_username=<%s>",
+ state->sasl_username);
+#endif
+ return (STR(buf));
+}
+
+/* collapse_args - put arguments together again */
+
+static void collapse_args(int argc, SMTPD_TOKEN *argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ vstring_strcat(argv[0].vstrval, " ");
+ vstring_strcat(argv[0].vstrval, argv[i].strval);
+ }
+ argv[0].strval = STR(argv[0].vstrval);
+}
+
+/* check_milter_reply - process reply from Milter */
+
+static const char *check_milter_reply(SMTPD_STATE *state, const char *reply)
+{
+ const char *queue_id = state->queue_id ? state->queue_id : "NOQUEUE";
+ const char *action;
+ const char *text;
+
+ /*
+ * The syntax of user-specified SMTP replies is checked by the Milter
+ * module, because the replies are also used in the cleanup server.
+ * Automatically disconnect after 421 (shutdown) reply. The Sendmail 8
+ * Milter quarantine action is not final, so it is not included in
+ * MILTER_SKIP_FLAGS.
+ */
+#define MILTER_SKIP_FLAGS (CLEANUP_FLAG_DISCARD)
+
+ switch (reply[0]) {
+ case 'H':
+ state->saved_flags |= CLEANUP_FLAG_HOLD;
+ action = "milter-hold";
+ reply = 0;
+ text = "milter triggers HOLD action";
+ break;
+ case 'D':
+ state->saved_flags |= CLEANUP_FLAG_DISCARD;
+ action = "milter-discard";
+ reply = 0;
+ text = "milter triggers DISCARD action";
+ break;
+ case 'S':
+ state->error_mask |= MAIL_ERROR_POLICY;
+ action = "milter-reject";
+ reply = "421 4.7.0 Server closing connection";
+ text = 0;
+ break;
+ case '4':
+ case '5':
+ state->error_mask |= MAIL_ERROR_POLICY;
+ action = "milter-reject";
+ text = 0;
+ break;
+ default:
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ action = "reject";
+ reply = "421 4.3.5 Server configuration error";
+ text = 0;
+ break;
+ }
+ msg_info("%s: %s: %s from %s: %s;%s", queue_id, action, state->where,
+ state->namaddr, reply ? reply : text, smtpd_whatsup(state));
+ return (reply);
+}
+
+/* helo_cmd - process HELO command */
+
+static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+
+ /*
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other
+ * than the initial greeting and any response to HELO or EHLO are
+ * prefaced with a status code as defined in RFC 3463.
+ */
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Syntax: HELO hostname");
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+
+ /*
+ * XXX Sendmail compatibility: if a Milter rejects CONNECT, EHLO, or
+ * HELO, reply with 250 except in case of 421 (disconnect). The reply
+ * persists so it will apply to MAIL FROM and to other commands such as
+ * AUTH, STARTTLS, and VRFY.
+ */
+#define PUSH_STRING(old, curr, new) { char *old = (curr); (curr) = (new);
+#define POP_STRING(old, curr) (curr) = old; }
+
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_helo_event(state->milters, argv[1].strval, 0)) != 0) {
+ /* Log reject etc. with correct HELO information. */
+ PUSH_STRING(saved_helo, state->helo_name, argv[1].strval);
+ err = check_milter_reply(state, err);
+ POP_STRING(saved_helo, state->helo_name);
+ if (err != 0 && strncmp(err, "421", 3) == 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (state->helo_name != 0)
+ helo_reset(state);
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ state->helo_name = mystrdup(printable(argv[1].strval, '?'));
+ neuter(state->helo_name, NEUTER_CHARACTERS, '?');
+ /* Downgrading the protocol name breaks the unauthorized pipelining test. */
+ if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0
+ && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ }
+ smtpd_chat_reply(state, "250 %s", var_myhostname);
+ return (0);
+}
+
+/* cant_announce_feature - explain and terminate this session */
+
+static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature)
+{
+ msg_warn("don't know if EHLO feature %s should be announced to %s",
+ feature, state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+}
+
+/* cant_permit_command - explain and terminate this session */
+
+static NORETURN cant_permit_command(SMTPD_STATE *state, const char *command)
+{
+ msg_warn("don't know if command %s should be allowed from %s",
+ command, state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+}
+
+/* ehlo_cmd - process EHLO command */
+
+static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+ int discard_mask;
+ char **cpp;
+
+ /*
+ * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear
+ * all buffers and reset the state exactly as if a RSET command had been
+ * issued.
+ *
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other
+ * than the initial greeting and any response to HELO or EHLO are
+ * prefaced with a status code as defined in RFC 3463.
+ */
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Syntax: EHLO hostname");
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+
+ /*
+ * XXX Sendmail compatibility: if a Milter 5xx rejects CONNECT, EHLO, or
+ * HELO, reply with ENHANCEDSTATUSCODES except in case of immediate
+ * disconnect. The reply persists so it will apply to MAIL FROM and to
+ * other commands such as AUTH, STARTTLS, and VRFY.
+ */
+ err = 0;
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_helo_event(state->milters, argv[1].strval, 1)) != 0) {
+ /* Log reject etc. with correct HELO information. */
+ PUSH_STRING(saved_helo, state->helo_name, argv[1].strval);
+ err = check_milter_reply(state, err);
+ POP_STRING(saved_helo, state->helo_name);
+ if (err != 0 && strncmp(err, "421", 3) == 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (state->helo_name != 0)
+ helo_reset(state);
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ state->helo_name = mystrdup(printable(argv[1].strval, '?'));
+ neuter(state->helo_name, NEUTER_CHARACTERS, '?');
+
+ /*
+ * XXX reject_unauth_pipelining depends on the following. If the user
+ * sends EHLO then we announce PIPELINING and we can't accuse them of
+ * using pipelining in places where it is allowed.
+ *
+ * XXX The reject_unauth_pipelining test needs to change and also account
+ * for mechanisms that disable PIPELINING selectively.
+ */
+ if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_ESMTP);
+ }
+
+ /*
+ * Build the EHLO response, producing no output until we know what to
+ * send - this simplifies exception handling. The CRLF record boundaries
+ * don't exist at this level in the code, so we represent multi-line
+ * output as an array of single-line responses.
+ */
+#define EHLO_APPEND(state, cmd) \
+ do { \
+ vstring_sprintf((state)->ehlo_buf, (cmd)); \
+ argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \
+ } while (0)
+
+#define EHLO_APPEND1(state, cmd, arg) \
+ do { \
+ vstring_sprintf((state)->ehlo_buf, (cmd), (arg)); \
+ argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \
+ } while (0)
+
+ /*
+ * XXX Sendmail compatibility: if a Milter 5XX rejects CONNECT, EHLO, or
+ * HELO, reply with ENHANCEDSTATUSCODES only. The reply persists so it
+ * will apply to MAIL FROM, but we currently don't have a proper
+ * mechanism to apply Milter rejects to AUTH, STARTTLS, VRFY, and other
+ * commands while still allowing HELO/EHLO.
+ */
+ discard_mask = state->ehlo_discard_mask;
+ if (err != 0 && err[0] == '5')
+ discard_mask |= ~EHLO_MASK_ENHANCEDSTATUSCODES;
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask));
+ if (ehlo_discard_maps && ehlo_discard_maps->error) {
+ msg_warn("don't know what EHLO features to announce to %s",
+ state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+ }
+
+ /*
+ * These may still exist after a prior exception.
+ */
+ if (state->ehlo_argv == 0) {
+ state->ehlo_argv = argv_alloc(10);
+ state->ehlo_buf = vstring_alloc(10);
+ } else
+ argv_truncate(state->ehlo_argv, 0);
+
+ EHLO_APPEND1(state, "%s", var_myhostname);
+ if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
+ EHLO_APPEND(state, "PIPELINING");
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit))
+ EHLO_APPEND1(state, "SIZE %lu",
+ (unsigned long) var_message_limit); /* XXX */
+ else
+ EHLO_APPEND(state, "SIZE");
+ }
+ if ((discard_mask & EHLO_MASK_VRFY) == 0)
+ if (var_disable_vrfy_cmd == 0)
+ EHLO_APPEND(state, SMTPD_CMD_VRFY);
+ if ((discard_mask & EHLO_MASK_ETRN) == 0)
+ EHLO_APPEND(state, SMTPD_CMD_ETRN);
+#ifdef USE_TLS
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
+ if (var_smtpd_use_tls && (!state->tls_context))
+ EHLO_APPEND(state, SMTPD_CMD_STARTTLS);
+#endif
+#ifdef USE_SASL_AUTH
+#ifndef AUTH_CMD
+#define AUTH_CMD "AUTH"
+#endif
+ if ((discard_mask & EHLO_MASK_AUTH) == 0) {
+ if (smtpd_sasl_is_active(state) && !sasl_client_exception(state)) {
+ EHLO_APPEND1(state, "AUTH %s", state->sasl_mechanism_list);
+ if (var_broken_auth_clients)
+ EHLO_APPEND1(state, "AUTH=%s", state->sasl_mechanism_list);
+ } else if (sasl_exceptions_networks && sasl_exceptions_networks->error)
+ cant_announce_feature(state, AUTH_CMD);
+ }
+#define XCLIENT_LOGIN_KLUDGE " " XCLIENT_LOGIN
+#else
+#define XCLIENT_LOGIN_KLUDGE ""
+#endif
+ if ((discard_mask & EHLO_MASK_VERP) == 0) {
+ if (namadr_list_match(verp_clients, state->name, state->addr))
+ EHLO_APPEND(state, VERP_CMD);
+ else if (verp_clients && verp_clients->error)
+ cant_announce_feature(state, VERP_CMD);
+ }
+ /* XCLIENT must not override its own access control. */
+ if ((discard_mask & EHLO_MASK_XCLIENT) == 0) {
+ if (xclient_allowed)
+ EHLO_APPEND(state, XCLIENT_CMD
+ " " XCLIENT_NAME " " XCLIENT_ADDR
+ " " XCLIENT_PROTO " " XCLIENT_HELO
+ " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT
+ XCLIENT_LOGIN_KLUDGE
+ " " XCLIENT_DESTADDR
+ " " XCLIENT_DESTPORT);
+ else if (xclient_hosts && xclient_hosts->error)
+ cant_announce_feature(state, XCLIENT_CMD);
+ }
+ if ((discard_mask & EHLO_MASK_XFORWARD) == 0) {
+ if (xforward_allowed)
+ EHLO_APPEND(state, XFORWARD_CMD
+ " " XFORWARD_NAME " " XFORWARD_ADDR
+ " " XFORWARD_PROTO " " XFORWARD_HELO
+ " " XFORWARD_DOMAIN " " XFORWARD_PORT
+ " " XFORWARD_IDENT);
+ else if (xforward_hosts && xforward_hosts->error)
+ cant_announce_feature(state, XFORWARD_CMD);
+ }
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ EHLO_APPEND(state, "ENHANCEDSTATUSCODES");
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ EHLO_APPEND(state, "8BITMIME");
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ EHLO_APPEND(state, "DSN");
+ if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ EHLO_APPEND(state, "SMTPUTF8");
+ if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+ EHLO_APPEND(state, "CHUNKING");
+
+ /*
+ * Send the reply.
+ */
+ for (cpp = state->ehlo_argv->argv; *cpp; cpp++)
+ smtpd_chat_reply(state, "250%c%s", cpp[1] ? '-' : ' ', *cpp);
+
+ /*
+ * Clean up.
+ */
+ argv_free(state->ehlo_argv);
+ state->ehlo_argv = 0;
+ vstring_free(state->ehlo_buf);
+ state->ehlo_buf = 0;
+
+ return (0);
+}
+
+/* helo_reset - reset HELO/EHLO command stuff */
+
+static void helo_reset(SMTPD_STATE *state)
+{
+ if (state->helo_name) {
+ myfree(state->helo_name);
+ state->helo_name = 0;
+ if (state->milters != 0)
+ milter_abort(state->milters);
+ }
+ if (state->ehlo_argv) {
+ argv_free(state->ehlo_argv);
+ state->ehlo_argv = 0;
+ }
+ if (state->ehlo_buf) {
+ vstring_free(state->ehlo_buf);
+ state->ehlo_buf = 0;
+ }
+}
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_auth_cmd_wrapper - smtpd_sasl_auth_cmd front-end */
+
+static int smtpd_sasl_auth_cmd_wrapper(SMTPD_STATE *state, int argc,
+ SMTPD_TOKEN *argv)
+{
+ int rate;
+
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_cauth_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_auth(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cauth_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("AUTH command rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state,
+ "450 4.7.1 Error: too many AUTH commands from %s",
+ state->addr);
+ return (-1);
+ }
+ return (smtpd_sasl_auth_cmd(state, argc, argv));
+}
+
+#endif
+
+/* mail_open_stream - open mail queue file or IPC stream */
+
+static int mail_open_stream(SMTPD_STATE *state)
+{
+
+ /*
+ * Connect to the before-queue filter when one is configured. The MAIL
+ * FROM and RCPT TO commands are forwarded as received (including DSN
+ * attributes), with the exception that the before-filter smtpd process
+ * handles all authentication, encryption, access control and relay
+ * control, and that the before-filter smtpd process does not forward
+ * blocked commands. If the after-filter smtp server does not support
+ * some of Postfix's ESMTP features, then they must be turned off in the
+ * before-filter smtpd process with the smtpd_discard_ehlo_keywords
+ * feature.
+ */
+ if (state->proxy_mail) {
+ if (smtpd_proxy_create(state, smtpd_proxy_opts, var_smtpd_proxy_filt,
+ var_smtpd_proxy_tmout, var_smtpd_proxy_ehlo,
+ state->proxy_mail) != 0) {
+ smtpd_chat_reply(state, "%s", STR(state->proxy->reply));
+ smtpd_proxy_free(state);
+ return (-1);
+ }
+ }
+
+ /*
+ * If running from the master or from inetd, connect to the cleanup
+ * service.
+ *
+ * XXX 2821: An SMTP server is not allowed to "clean up" mail except in the
+ * case of original submissions.
+ *
+ * We implement this by distinguishing between mail that we are willing to
+ * rewrite (the local rewrite context) and mail from elsewhere.
+ */
+ else if (SMTPD_STAND_ALONE(state) == 0) {
+ int cleanup_flags;
+
+ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL,
+ smtpd_input_transp_mask)
+ | CLEANUP_FLAG_SMTP_REPLY;
+ if (state->flags & SMTPD_FLAG_SMTPUTF8)
+ cleanup_flags |= CLEANUP_FLAG_SMTPUTF8;
+ else
+ cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SMTPD);
+ state->dest = mail_stream_service(MAIL_CLASS_PUBLIC,
+ var_cleanup_service);
+ if (state->dest == 0
+ || attr_print(state->dest->stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to connect to the %s %s service",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ }
+
+ /*
+ * Otherwise, pipe the message through the privileged postdrop helper.
+ * XXX Make postdrop a manifest constant.
+ */
+ else {
+ char *postdrop_command;
+
+ postdrop_command = concatenate(var_command_dir, "/postdrop",
+ msg_verbose ? " -v" : (char *) 0, (char *) 0);
+ state->dest = mail_stream_command(postdrop_command);
+ if (state->dest == 0)
+ msg_fatal("unable to execute %s", postdrop_command);
+ myfree(postdrop_command);
+ }
+
+ /*
+ * Record the time of arrival, the SASL-related stuff if applicable, the
+ * sender envelope address, some session information, and some additional
+ * attributes.
+ *
+ * XXX Send Milter information first, because this will hang when cleanup
+ * goes into "throw away" mode. Also, cleanup needs to know early on
+ * whether or not it has to do its own SMTP event emulation.
+ *
+ * XXX At this point we send only dummy information to keep the cleanup
+ * server from using its non_smtpd_milters settings. We have to send
+ * up-to-date Milter information after DATA so that the cleanup server
+ * knows the actual Milter state.
+ */
+ if (state->dest) {
+ state->cleanup = state->dest->stream;
+ state->queue_id = mystrdup(state->dest->id);
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0)
+ /* Send place-holder smtpd_milters list. */
+ (void) milter_dummy(state->milters, state->cleanup);
+ rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(state->arrival_time));
+ if (*var_filter_xport)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+ if (FORWARD_IDENT(state))
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_IDENT, FORWARD_IDENT(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_RWR_CONTEXT, FORWARD_DOMAIN(state));
+#ifdef USE_SASL_AUTH
+ /* Make external authentication painless (e.g., XCLIENT). */
+ if (state->sasl_method)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_METHOD, state->sasl_method);
+ if (state->sasl_username)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_USERNAME, state->sasl_username);
+ if (state->sasl_sender)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_SENDER, state->sasl_sender);
+#endif
+
+ /*
+ * Record DSN related information that was received with the MAIL
+ * FROM command.
+ *
+ * RFC 3461 Section 5.2.1. If no ENVID parameter was included in the
+ * MAIL command when the message was received, the ENVID
+ * parameter MUST NOT be supplied when the message is relayed.
+ * Ditto for the RET parameter.
+ *
+ * In other words, we can't simply make up our default ENVID or RET
+ * values. We have to remember whether the client sent any.
+ *
+ * We store DSN information as named attribute records 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 (state->dsn_envid)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, state->dsn_envid);
+ if (state->dsn_ret)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, state->dsn_ret);
+ }
+ rec_fputs(state->cleanup, REC_TYPE_FROM, state->sender);
+ if (state->encoding != 0)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ENCODING, state->encoding);
+
+ /*
+ * Store client attributes.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0) {
+
+ /*
+ * Attributes for logging, also used for XFORWARD.
+ *
+ * We store all client attributes, including ones with unknown
+ * values. Otherwise, an unknown client hostname would be treated
+ * as a non-existent hostname (i.e. local submission).
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_NAME, FORWARD_NAME(state));
+ /* XXX Note: state->rfc_addr, not state->addr. */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_ADDR, FORWARD_ADDR(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_PORT, FORWARD_PORT(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, FORWARD_NAMADDR(state));
+ if (FORWARD_HELO(state))
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_HELO_NAME, FORWARD_HELO(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_PROTO_NAME, FORWARD_PROTO(state));
+
+ /*
+ * Attributes with actual client information. These are used by
+ * the smtpd Milter client for policy decisions. Mail that is
+ * requeued with "postsuper -r" is not subject to processing by
+ * the cleanup Milter client, because a) it has already been
+ * filtered, and b) we don't have sufficient information to
+ * reproduce the exact same SMTP events and Sendmail macros that
+ * the smtpd Milter client received when the message originally
+ * arrived in Postfix.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, state->reverse_name);
+ /* XXX Note: state->addr, not state->rfc_addr. */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_ADDR, state->addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_SERVER_ADDR, state->dest_addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_SERVER_PORT, state->dest_port);
+ if (state->helo_name)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_HELO_NAME, state->helo_name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_PROTO_NAME, state->protocol);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u",
+ MAIL_ATTR_ACT_CLIENT_AF, state->addr_family);
+
+ /*
+ * Don't send client certificate down the pipeline unless it is
+ * a) verified or b) just a fingerprint.
+ */
+ }
+ if (state->verp_delims)
+ rec_fputs(state->cleanup, REC_TYPE_VERP, state->verp_delims);
+ }
+
+ /*
+ * Log the queue ID with the message origin.
+ */
+#define PRINT_OR_NULL(cond, str) \
+ ((cond) ? (str) : "")
+#define PRINT2_OR_NULL(cond, name, value) \
+ PRINT_OR_NULL((cond), (name)), PRINT_OR_NULL((cond), (value))
+
+ msg_info("%s: client=%s%s%s%s%s%s%s%s%s%s%s",
+ (state->queue_id ? state->queue_id : "NOQUEUE"),
+ state->namaddr,
+#ifdef USE_SASL_AUTH
+ PRINT2_OR_NULL(state->sasl_method,
+ ", sasl_method=", state->sasl_method),
+ PRINT2_OR_NULL(state->sasl_username,
+ ", sasl_username=", state->sasl_username),
+ PRINT2_OR_NULL(state->sasl_sender,
+ ", sasl_sender=", state->sasl_sender),
+#else
+ "", "", "", "", "", "",
+#endif
+ /* Insert transaction TLS status here. */
+ PRINT2_OR_NULL(HAVE_FORWARDED_IDENT(state),
+ ", orig_queue_id=", FORWARD_IDENT(state)),
+ PRINT2_OR_NULL(HAVE_FORWARDED_CLIENT_ATTR(state),
+ ", orig_client=", FORWARD_NAMADDR(state)));
+ return (0);
+}
+
+/* extract_addr - extract address from rubble */
+
+static int extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
+ int allow_empty_addr, int strict_rfc821,
+ int smtputf8)
+{
+ const char *myname = "extract_addr";
+ TOK822 *tree;
+ TOK822 *tp;
+ TOK822 *addr = 0;
+ int naddr;
+ int non_addr;
+ int err = 0;
+ char *junk = 0;
+ char *text;
+ char *colon;
+
+ /*
+ * Special case.
+ */
+#define PERMIT_EMPTY_ADDR 1
+#define REJECT_EMPTY_ADDR 0
+
+ /*
+ * Some mailers send RFC822-style address forms (with comments and such)
+ * in SMTP envelopes. We cannot blame users for this: the blame is with
+ * programmers violating the RFC, and with sendmail for being permissive.
+ *
+ * XXX The SMTP command tokenizer must leave the address in externalized
+ * (quoted) form, so that the address parser can correctly extract the
+ * address from surrounding junk.
+ *
+ * XXX We have only one address parser, written according to the rules of
+ * RFC 822. That standard differs subtly from RFC 821.
+ */
+ if (msg_verbose)
+ msg_info("%s: input: %s", myname, STR(arg->vstrval));
+ if (STR(arg->vstrval)[0] == '<'
+ && STR(arg->vstrval)[LEN(arg->vstrval) - 1] == '>') {
+ junk = text = mystrndup(STR(arg->vstrval) + 1, LEN(arg->vstrval) - 2);
+ } else
+ text = STR(arg->vstrval);
+
+ /*
+ * Truncate deprecated route address form.
+ */
+ if (*text == '@' && (colon = strchr(text, ':')) != 0)
+ text = colon + 1;
+ tree = tok822_parse(text);
+
+ if (junk)
+ myfree(junk);
+
+ /*
+ * Find trouble.
+ */
+ for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ addr = tp;
+ naddr += 1; /* count address forms */
+ } else if (tp->type == '<' || tp->type == '>') {
+ /* void */ ; /* ignore brackets */
+ } else {
+ non_addr += 1; /* count non-address forms */
+ }
+ }
+
+ /*
+ * Report trouble. XXX Should log a warning only if we are going to
+ * sleep+reject so that attackers can't flood our logfiles.
+ *
+ * XXX Unfortunately, the sleep-before-reject feature had to be abandoned
+ * (at least for small error counts) because servers were DOS-ing
+ * themselves when flooded by backscatter traffic.
+ */
+ if (naddr > 1
+ || (strict_rfc821 && (non_addr || *STR(arg->vstrval) != '<'))) {
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where,
+ printable(STR(arg->vstrval), '?'));
+ err = 1;
+ }
+
+ /*
+ * Don't overwrite the input with the extracted address. We need the
+ * original (external) form in case the client does not send ORCPT
+ * information; and error messages are more accurate if we log the
+ * unmodified form. We need the internal form for all other purposes.
+ */
+ if (addr)
+ tok822_internalize(state->addr_buf, addr->head, TOK822_STR_DEFL);
+ else
+ vstring_strcpy(state->addr_buf, "");
+
+ /*
+ * Report trouble. XXX Should log a warning only if we are going to
+ * sleep+reject so that attackers can't flood our logfiles. Log the
+ * original address.
+ */
+ if (err == 0)
+ if ((STR(state->addr_buf)[0] == 0 && !allow_empty_addr)
+ || (strict_rfc821 && STR(state->addr_buf)[0] == '@')
+ || (SMTPD_STAND_ALONE(state) == 0
+ && smtpd_check_addr(strcmp(state->where, SMTPD_CMD_MAIL) == 0 ?
+ state->recipient : state->sender,
+ STR(state->addr_buf), smtputf8) != 0)) {
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where,
+ printable(STR(arg->vstrval), '?'));
+ err = 1;
+ }
+
+ /*
+ * Cleanup.
+ */
+ tok822_free_tree(tree);
+ if (msg_verbose)
+ msg_info("%s: in: %s, result: %s",
+ myname, STR(arg->vstrval), STR(state->addr_buf));
+ return (err);
+}
+
+/* milter_argv - impedance adapter */
+
+static const char **milter_argv(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ int n;
+ ssize_t len = argc + 1;
+
+ if (state->milter_argc < len) {
+ if (state->milter_argc > 0)
+ state->milter_argv = (const char **)
+ myrealloc((void *) state->milter_argv,
+ sizeof(const char *) * len);
+ else
+ state->milter_argv = (const char **)
+ mymalloc(sizeof(const char *) * len);
+ state->milter_argc = len;
+ }
+ for (n = 0; n < argc; n++)
+ state->milter_argv[n] = argv[n].strval;
+ state->milter_argv[n] = 0;
+ return (state->milter_argv);
+}
+
+/* mail_cmd - process MAIL command */
+
+static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+ int narg;
+ char *arg;
+ char *verp_delims = 0;
+ int rate;
+ int dsn_envid = 0;
+
+ state->flags &= ~SMTPD_FLAG_SMTPUTF8;
+ state->encoding = 0;
+ state->dsn_ret = 0;
+
+ /*
+ * Sanity checks.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. Postfix attempts to be 8-bit clean.
+ */
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command");
+ return (-1);
+ }
+ /* Don't accept MAIL after out-of-order BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT");
+ return (-1);
+ }
+ if (argc < 3
+ || strcasecmp(argv[1].strval, "from:") != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: MAIL FROM:<address>");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_cmail_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_mail(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cmail_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "450 4.7.1 Error: too much mail from %s",
+ state->addr);
+ msg_warn("Message delivery request rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ return (-1);
+ }
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax");
+ return (-1);
+ }
+
+ /*
+ * XXX The sender address comes first, but the optional SMTPUTF8
+ * parameter determines what address syntax is permitted. We must process
+ * this parameter early.
+ */
+ if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0) {
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */
+ /* Fix 20161206: allow UTF8 in smtpd_sender_restrictions. */
+ state->flags |= SMTPD_FLAG_SMTPUTF8;
+ break;
+ }
+ }
+ }
+ if (extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR,
+ var_strict_rfc821_env,
+ state->flags & SMTPD_FLAG_SMTPUTF8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax");
+ return (-1);
+ }
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strcasecmp(arg, "BODY=8BITMIME") == 0) { /* RFC 1652 */
+ state->encoding = MAIL_ATTR_ENC_8BIT;
+ } else if (strcasecmp(arg, "BODY=7BIT") == 0) { /* RFC 1652 */
+ state->encoding = MAIL_ATTR_ENC_7BIT;
+ } else if (strncasecmp(arg, "SIZE=", 5) == 0) { /* RFC 1870 */
+ /* Reject non-numeric size. */
+ if (!alldig(arg + 5)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad message size syntax");
+ return (-1);
+ }
+ /* Reject size overflow. */
+ if ((state->msg_size = off_cvt_string(arg + 5)) < 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "552 5.3.4 Message size exceeds file system imposed limit");
+ return (-1);
+ }
+ } else if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0
+ && strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */
+ /* Already processed early. */ ;
+#ifdef USE_SASL_AUTH
+ } else if (strncasecmp(arg, "AUTH=", 5) == 0) {
+ if ((err = smtpd_sasl_mail_opt(state, arg + 5)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+#endif
+ } else if (namadr_list_match(verp_clients, state->name, state->addr)
+ && strncasecmp(arg, VERP_CMD, VERP_CMD_LEN) == 0
+ && (arg[VERP_CMD_LEN] == '=' || arg[VERP_CMD_LEN] == 0)) {
+ if (arg[VERP_CMD_LEN] == 0) {
+ verp_delims = var_verp_delims;
+ } else {
+ verp_delims = arg + VERP_CMD_LEN + 1;
+ if (verp_delims_verify(verp_delims) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: %s needs two characters from %s",
+ VERP_CMD, var_verp_filter);
+ return (-1);
+ }
+ }
+ } else if (strncasecmp(arg, "RET=", 4) == 0) { /* RFC 3461 */
+ /* Sanitized on input. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (state->dsn_ret
+ || (state->dsn_ret = dsn_ret_code(arg + 4)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Bad RET parameter syntax");
+ return (-1);
+ }
+ } else if (strncasecmp(arg, "ENVID=", 6) == 0) { /* RFC 3461 */
+ /* Sanitized by bounce server. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (dsn_envid
+ || xtext_unquote(state->dsn_buf, arg + 6) == 0
+ || !allprint(STR(state->dsn_buf))) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad ENVID parameter syntax");
+ return (-1);
+ }
+ dsn_envid = 1;
+ } else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg);
+ return (-1);
+ }
+ }
+ /* Fix 20161205: show the envelope sender in reject logging. */
+ PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf));
+ err = smtpd_check_size(state, state->msg_size);
+ POP_STRING(saved_sender, state->sender);
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (verp_delims && STR(state->addr_buf)[0] == 0) {
+ smtpd_chat_reply(state, "503 5.5.4 Error: %s requires non-null sender",
+ VERP_CMD);
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ const char *verify_sender;
+
+ /*
+ * XXX Don't reject the address when we're probed with our own
+ * address verification sender address. Otherwise, some timeout or
+ * some UCE block may result in mutual negative caching, making it
+ * painful to get the mail through. Unfortunately we still have to
+ * send the address to the Milters otherwise they may bail out with a
+ * "missing recipient" protocol error.
+ */
+ verify_sender = valid_verify_sender_addr(STR(state->addr_buf));
+ if (verify_sender != 0)
+ vstring_strcpy(state->addr_buf, verify_sender);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_mail(state, STR(state->addr_buf))) != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) {
+ state->flags |= SMTPD_FLAG_NEED_MILTER_ABORT;
+ PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf));
+ err = milter_mail_event(state->milters,
+ milter_argv(state, argc - 2, argv + 2));
+ if (err != 0) {
+ /* Log reject etc. with correct sender information. */
+ err = check_milter_reply(state, err);
+ }
+ POP_STRING(saved_sender, state->sender);
+ if (err != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ err = smtpd_check_rewrite(state);
+ if (err != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * Historically, Postfix does not forbid 8-bit envelope localparts.
+ * Changing this would be a compatibility break. That can't happen in the
+ * foreseeable future.
+ */
+ if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable)
+ && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0
+ && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ if (var_strict_smtputf8) {
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to "
+ "send unicode address");
+ return (-1);
+ }
+
+ /*
+ * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a
+ * warning message is logged, so that they don't suddenly start to
+ * lose mail after Postfix is built with EAI support.
+ */
+ if (warn_compat_break_smtputf8_enable)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII sender "
+ "address \"%s\" from %s", STR(state->addr_buf),
+ state->namaddr);
+ }
+
+ /*
+ * Check the queue file space, if applicable. The optional before-filter
+ * speed-adjust buffers use disk space. However, we don't know if they
+ * compete for storage space with the after-filter queue, so we can't
+ * simply bump up the free space requirement to 2.5 * message_size_limit.
+ */
+ if (!USE_SMTPD_PROXY(state)
+ || (smtpd_proxy_opts & SMTPD_PROXY_FLAG_SPEED_ADJUST)) {
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_queue(state)) != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * No more early returns. The mail transaction is in progress.
+ */
+ GETTIMEOFDAY(&state->arrival_time);
+ state->sender = mystrdup(STR(state->addr_buf));
+ vstring_sprintf(state->instance, "%x.%lx.%lx.%x",
+ var_pid, (unsigned long) state->arrival_time.tv_sec,
+ (unsigned long) state->arrival_time.tv_usec, state->seqno++);
+ if (verp_delims)
+ state->verp_delims = mystrdup(verp_delims);
+ if (dsn_envid)
+ state->dsn_envid = mystrdup(STR(state->dsn_buf));
+ if (USE_SMTPD_PROXY(state))
+ state->proxy_mail = mystrdup(STR(state->buffer));
+ if (var_smtpd_delay_open == 0 && mail_open_stream(state) < 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ return (-1);
+ }
+ smtpd_chat_reply(state, "250 2.1.0 Ok");
+ return (0);
+}
+
+/* mail_reset - reset MAIL command stuff */
+
+static void mail_reset(SMTPD_STATE *state)
+{
+ state->msg_size = 0;
+ state->act_size = 0;
+ state->flags &= SMTPD_MASK_MAIL_KEEP;
+
+ /*
+ * Unceremoniously close the pipe to the cleanup service. The cleanup
+ * service will delete the queue file when it detects a premature
+ * end-of-file condition on input.
+ */
+ if (state->cleanup != 0) {
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+ state->err = 0;
+ if (state->queue_id != 0) {
+ myfree(state->queue_id);
+ state->queue_id = 0;
+ }
+ if (state->sender) {
+ myfree(state->sender);
+ state->sender = 0;
+ }
+ /* WeiYu Wu: need to undo milter_mail_event() state change. */
+ if (state->flags & SMTPD_FLAG_NEED_MILTER_ABORT) {
+ milter_abort(state->milters);
+ state->flags &= ~SMTPD_FLAG_NEED_MILTER_ABORT;
+ }
+ if (state->verp_delims) {
+ myfree(state->verp_delims);
+ state->verp_delims = 0;
+ }
+ if (state->proxy_mail) {
+ myfree(state->proxy_mail);
+ state->proxy_mail = 0;
+ }
+ if (state->saved_filter) {
+ myfree(state->saved_filter);
+ state->saved_filter = 0;
+ }
+ if (state->saved_redirect) {
+ myfree(state->saved_redirect);
+ state->saved_redirect = 0;
+ }
+ if (state->saved_bcc) {
+ argv_free(state->saved_bcc);
+ state->saved_bcc = 0;
+ }
+ state->saved_flags = 0;
+#ifdef DELAY_ACTION
+ state->saved_delay = 0;
+#endif
+#ifdef USE_SASL_AUTH
+ if (state->sasl_sender)
+ smtpd_sasl_mail_reset(state);
+#endif
+ state->discard = 0;
+ VSTRING_RESET(state->instance);
+ VSTRING_TERMINATE(state->instance);
+
+ if (state->proxy)
+ smtpd_proxy_free(state);
+ if (state->xforward.flags)
+ smtpd_xforward_reset(state);
+ if (state->prepend)
+ state->prepend = argv_free(state->prepend);
+ if (state->dsn_envid) {
+ myfree(state->dsn_envid);
+ state->dsn_envid = 0;
+ }
+ if (state->milter_argv) {
+ myfree((void *) state->milter_argv);
+ state->milter_argv = 0;
+ state->milter_argc = 0;
+ }
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_NONE;
+ if (state->bdat_get_stream) {
+ (void) vstream_fclose(state->bdat_get_stream);
+ state->bdat_get_stream = 0;
+ }
+ if (state->bdat_get_buffer)
+ VSTRING_RESET(state->bdat_get_buffer);
+}
+
+/* rcpt_cmd - process RCPT TO command */
+
+static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ int narg;
+ char *arg;
+ int rate;
+ const char *dsn_orcpt_addr = 0;
+ ssize_t dsn_orcpt_addr_len = 0;
+ const char *dsn_orcpt_type = 0;
+ int dsn_notify = 0;
+ const char *coded_addr;
+ const char *milter_err;
+
+ /*
+ * Sanity checks.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. So much for the principle of "be liberal
+ * in what you accept, be strict in what you send".
+ */
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command");
+ return (-1);
+ }
+ /* Don't accept RCPT after BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT");
+ return (-1);
+ }
+ if (argc < 3
+ || strcasecmp(argv[1].strval, "to:") != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: RCPT TO:<address>");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_crcpt_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_crcpt_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Recipient address rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s",
+ state->addr);
+ return (-1);
+ }
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ if (extract_addr(state, argv + 2, REJECT_EMPTY_ADDR, var_strict_rfc821_env,
+ state->flags & SMTPD_FLAG_SMTPUTF8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */
+ /* Sanitized on input. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: Bad NOTIFY parameter syntax");
+ return (-1);
+ }
+ } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */
+ /* Sanitized by bounce server. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ vstring_strcpy(state->dsn_orcpt_buf, arg + 6);
+ if (dsn_orcpt_addr
+ || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0
+ || *(dsn_orcpt_type = STR(state->dsn_orcpt_buf)) == 0
+ || (strcasecmp(dsn_orcpt_type, "utf-8") == 0 ?
+ uxtext_unquote(state->dsn_buf, coded_addr) == 0 :
+ xtext_unquote(state->dsn_buf, coded_addr) == 0)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: Bad ORCPT parameter syntax");
+ return (-1);
+ }
+ dsn_orcpt_addr = STR(state->dsn_buf);
+ dsn_orcpt_addr_len = LEN(state->dsn_buf);
+ } else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg);
+ return (-1);
+ }
+ }
+ if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) {
+ smtpd_chat_reply(state, "452 4.5.3 Error: too many recipients");
+ if (state->rcpt_overshoot++ < var_smtpd_rcpt_overlim)
+ return (0);
+ state->error_mask |= MAIL_ERROR_POLICY;
+ return (-1);
+ }
+
+ /*
+ * Historically, Postfix does not forbid 8-bit envelope localparts.
+ * Changing this would be a compatibility break. That can't happen in the
+ * foreseeable future.
+ */
+ if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable)
+ && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0
+ && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ if (var_strict_smtputf8) {
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to "
+ "send unicode address");
+ return (-1);
+ }
+
+ /*
+ * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a
+ * warning message is logged, so that they don't suddenly start to
+ * lose mail after Postfix is built with EAI support.
+ */
+ if (warn_compat_break_smtputf8_enable)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII recipient "
+ "address \"%s\" from %s", STR(state->addr_buf),
+ state->namaddr);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ const char *verify_sender;
+
+ /*
+ * XXX Don't reject the address when we're probed with our own
+ * address verification sender address. Otherwise, some timeout or
+ * some UCE block may result in mutual negative caching, making it
+ * painful to get the mail through. Unfortunately we still have to
+ * send the address to the Milters otherwise they may bail out with a
+ * "missing recipient" protocol error.
+ */
+ verify_sender = valid_verify_sender_addr(STR(state->addr_buf));
+ if (verify_sender != 0) {
+ vstring_strcpy(state->addr_buf, verify_sender);
+ err = 0;
+ } else {
+ err = smtpd_check_rcpt(state, STR(state->addr_buf));
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) {
+ PUSH_STRING(saved_rcpt, state->recipient, STR(state->addr_buf));
+ state->milter_reject_text = err;
+ milter_err = milter_rcpt_event(state->milters,
+ err == 0 ? MILTER_FLAG_NONE :
+ MILTER_FLAG_WANT_RCPT_REJ,
+ milter_argv(state, argc - 2, argv + 2));
+ if (err == 0 && milter_err != 0) {
+ /* Log reject etc. with correct recipient information. */
+ err = check_milter_reply(state, milter_err);
+ }
+ POP_STRING(saved_rcpt, state->recipient);
+ }
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * Don't access the proxy, queue file, or queue file writer process until
+ * we have a valid recipient address.
+ */
+ if (state->proxy == 0 && state->cleanup == 0 && mail_open_stream(state) < 0)
+ return (-1);
+
+ /*
+ * Proxy the recipient. OK, so we lied. If the real-time proxy rejects
+ * the recipient then we can have a proxy connection without having
+ * accepted a recipient.
+ */
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_OK,
+ "%s", STR(state->buffer)) != 0) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ return (-1);
+ }
+
+ /*
+ * Store the recipient. Remember the first one.
+ *
+ * Flush recipients to maintain a stiffer coupling with the next stage and
+ * to better utilize parallelism.
+ *
+ * RFC 3461 Section 5.2.1: If the NOTIFY parameter was not supplied for a
+ * recipient when the message was received, the NOTIFY parameter MUST NOT
+ * be supplied for that recipient when the message is relayed.
+ *
+ * In other words, we can't simply make up our default NOTIFY value. We have
+ * to remember whether the client sent any.
+ *
+ * RFC 3461 Section 5.2.1: If no ORCPT parameter was present when the
+ * message was received, an ORCPT parameter MAY be added to the RCPT
+ * command when the message is relayed. If an ORCPT parameter is added
+ * by the relaying MTA, it MUST contain the recipient address from the
+ * RCPT command used when the message was received by that MTA.
+ *
+ * In other words, it is OK to make up our own DSN original recipient when
+ * the client didn't send one. Although the RFC mentions mail relaying
+ * only, we also make up our own original recipient for the purpose of
+ * final delivery. For now, we do this here, rather than on the fly.
+ *
+ * XXX We use REC_TYPE_ATTR for DSN-related recipient attributes even though
+ * 1) REC_TYPE_ATTR is not meant for multiple instances of the same named
+ * attribute, and 2) mixing REC_TYPE_ATTR with REC_TYPE_(not attr)
+ * requires that we map attributes with rec_attr_map() in order to
+ * simplify the recipient record processing loops in the cleanup and qmgr
+ * servers.
+ *
+ * Another possibility, yet to be explored, is to leave the additional
+ * recipient information in the queue file and just pass queue file
+ * offsets along with the delivery request. This is a trade off between
+ * memory allocation versus numeric conversion overhead.
+ *
+ * Since we have no record grouping mechanism, all recipient-specific
+ * parameters must be sent to the cleanup server before the actual
+ * recipient address.
+ */
+ state->rcpt_count++;
+ if (state->recipient == 0)
+ state->recipient = mystrdup(STR(state->addr_buf));
+ if (state->cleanup) {
+ /* Note: RFC(2)821 externalized address! */
+ if (dsn_orcpt_addr == 0) {
+ dsn_orcpt_type = "rfc822";
+ dsn_orcpt_addr = argv[2].strval;
+ dsn_orcpt_addr_len = strlen(argv[2].strval);
+ if (dsn_orcpt_addr[0] == '<'
+ && dsn_orcpt_addr[dsn_orcpt_addr_len - 1] == '>') {
+ dsn_orcpt_addr += 1;
+ dsn_orcpt_addr_len -= 2;
+ }
+ }
+ if (dsn_notify)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s;%.*s",
+ MAIL_ATTR_DSN_ORCPT, dsn_orcpt_type,
+ (int) dsn_orcpt_addr_len, dsn_orcpt_addr);
+ rec_fputs(state->cleanup, REC_TYPE_RCPT, STR(state->addr_buf));
+ vstream_fflush(state->cleanup);
+ }
+ smtpd_chat_reply(state, "250 2.1.5 Ok");
+ return (0);
+}
+
+/* rcpt_reset - reset RCPT stuff */
+
+static void rcpt_reset(SMTPD_STATE *state)
+{
+ if (state->recipient) {
+ myfree(state->recipient);
+ state->recipient = 0;
+ }
+ state->rcpt_count = 0;
+ /* XXX Must flush the command history. */
+ state->rcpt_overshoot = 0;
+}
+
+#if 0
+
+/* rfc2047_comment_encode - encode comment string */
+
+static VSTRING *rfc2047_comment_encode(const char *str, const char *charset)
+{
+ VSTRING *buf = vstring_alloc(30);
+ const unsigned char *cp;
+ int ch;
+
+ /*
+ * XXX This is problematic code.
+ *
+ * XXX Most of the RFC 2047 "especials" are not special in RFC*822 comments,
+ * but we encode them anyway to avoid complaints.
+ *
+ * XXX In Received: header comments we enclose peer and issuer common names
+ * with "" quotes (inherited from the Lutz Jaenicke patch). This is the
+ * cause of several quirks.
+ *
+ * 1) We encode text that contains the " character, even though that
+ * character is not special for RFC*822 comments.
+ *
+ * 2) We ignore the recommended limit of 75 characters per encoded word,
+ * because long comments look ugly when folded in-between quotes.
+ *
+ * 3) We encode the enclosing quotes, to avoid producing invalid encoded
+ * words. Microsoft abuses RFC 2047 encoding with attachment names, but
+ * we have no information on what decoders do with malformed encoding in
+ * comments. This means the comments are Jaenicke-compatible only after
+ * decoding.
+ */
+#define ESPECIALS "()<>@,;:\"/[]?.=" /* Special in RFC 2047 */
+#define QSPECIALS "_" ESPECIALS /* Special in RFC 2047 'Q' */
+#define CSPECIALS "\\\"()" /* Special in our comments */
+
+ /* Don't encode if not needed. */
+ for (cp = (unsigned char *) str; /* see below */ ; ++cp) {
+ if ((ch = *cp) == 0) {
+ vstring_sprintf(buf, "\"%s\"", str);
+ return (buf);
+ }
+ if (!ISPRINT(ch) || strchr(CSPECIALS, ch))
+ break;
+ }
+
+ /*
+ * Use quoted-printable (like) encoding with spaces mapped to underscore.
+ */
+ vstring_sprintf(buf, "=?%s?Q?=%02X", charset, '"');
+ for (cp = (unsigned char *) str; (ch = *cp) != 0; ++cp) {
+ if (!ISPRINT(ch) || strchr(QSPECIALS CSPECIALS, ch)) {
+ vstring_sprintf_append(buf, "=%02X", ch);
+ } else if (ch == ' ') {
+ VSTRING_ADDCH(buf, '_');
+ } else {
+ VSTRING_ADDCH(buf, ch);
+ }
+ }
+ vstring_sprintf_append(buf, "=%02X?=", '"');
+ return (buf);
+}
+
+#endif
+
+/* comment_sanitize - clean up comment string */
+
+static void comment_sanitize(VSTRING *comment_string)
+{
+ unsigned char *cp;
+ int ch;
+ int pc;
+
+ /*
+ * Postfix Received: headers can be configured to include a comment with
+ * the CN (CommonName) of the peer and its issuer, or the login name of a
+ * SASL authenticated user. To avoid problems with RFC 822 etc. syntax,
+ * we limit this information to printable ASCII text, and neutralize
+ * characters that affect comment parsing: the backslash and unbalanced
+ * parentheses.
+ */
+ for (pc = 0, cp = (unsigned char *) STR(comment_string); (ch = *cp) != 0; cp++) {
+ if (!ISASCII(ch) || !ISPRINT(ch) || ch == '\\') {
+ *cp = '?';
+ } else if (ch == '(') {
+ pc++;
+ } else if (ch == ')') {
+ if (pc > 0)
+ pc--;
+ else
+ *cp = '?';
+ }
+ }
+ while (pc-- > 0)
+ VSTRING_ADDCH(comment_string, ')');
+ VSTRING_TERMINATE(comment_string);
+}
+
+static void common_pre_message_handling(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream, int out_error);
+static void receive_data_message(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream, int out_error);
+static int common_post_message_handling(SMTPD_STATE *state);
+
+/* data_cmd - process DATA command */
+
+static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t);
+ int (*out_fprintf) (VSTREAM *, int, const char *,...);
+ VSTREAM *out_stream;
+ int out_error;
+
+ /*
+ * Sanity checks. With ESMTP command pipelining the client can send DATA
+ * before all recipients are rejected, so don't report that as a protocol
+ * error.
+ */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT");
+ return (-1);
+ }
+ if (state->rcpt_count == 0) {
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: need RCPT command");
+ } else {
+ smtpd_chat_reply(state, "554 5.5.1 Error: no valid recipients");
+ }
+ return (-1);
+ }
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: DATA");
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_data(state)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_data_event(state->milters)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE,
+ "%s", STR(state->buffer)) != 0) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ return (-1);
+ }
+
+ /*
+ * One level of indirection to choose between normal or proxied
+ * operation. We want to avoid massive code duplication within tons of
+ * if-else clauses.
+ */
+ if (proxy) {
+ out_stream = proxy->stream;
+ out_record = proxy->rec_put;
+ out_fprintf = proxy->rec_fprintf;
+ out_error = CLEANUP_STAT_PROXY;
+ } else {
+ out_stream = state->cleanup;
+ out_record = rec_put;
+ out_fprintf = rec_fprintf;
+ out_error = CLEANUP_STAT_WRITE;
+ }
+ common_pre_message_handling(state, out_record, out_fprintf,
+ out_stream, out_error);
+ smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
+ state->where = SMTPD_AFTER_DATA;
+ receive_data_message(state, out_record, out_fprintf, out_stream, out_error);
+ return common_post_message_handling(state);
+}
+
+/* common_pre_message_handling - finish envelope and open message segment */
+
+static void common_pre_message_handling(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream,
+ int out_error)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ char **cpp;
+ const char *rfc3848_sess;
+ const char *rfc3848_auth;
+ const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ?
+ "UTF8SMTP" : state->protocol;
+
+#ifdef USE_TLS
+ VSTRING *peer_CN;
+ VSTRING *issuer_CN;
+
+#endif
+#ifdef USE_SASL_AUTH
+ VSTRING *username;
+
+#endif
+
+ /*
+ * Flush out a first batch of access table actions that are delegated to
+ * the cleanup server, and that may trigger before we accept the first
+ * valid recipient. There will be more after end-of-data.
+ *
+ * Terminate the message envelope segment. Start the message content
+ * segment, and prepend our own Received: header. If there is only one
+ * recipient, list the recipient address.
+ */
+ if (state->cleanup) {
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0)
+ /* Send actual smtpd_milters list. */
+ (void) milter_send(state->milters, state->cleanup);
+ if (state->saved_flags)
+ rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d",
+ state->saved_flags);
+ }
+ rec_fputs(state->cleanup, REC_TYPE_MESG, "");
+ }
+
+ /*
+ * PREPEND message headers above our own Received: header.
+ */
+ if (state->prepend)
+ for (cpp = state->prepend->argv; *cpp; cpp++)
+ out_fprintf(out_stream, REC_TYPE_NORM, "%s", *cpp);
+
+ /*
+ * Suppress our own Received: header in the unlikely case that we are an
+ * intermediate proxy.
+ */
+ if (!proxy || state->xforward.flags == 0) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "Received: from %s (%s [%s])",
+ state->helo_name ? state->helo_name : state->name,
+ state->name, state->rfc_addr);
+
+#define VSTRING_STRDUP(s) vstring_strcpy(vstring_alloc(strlen(s) + 1), (s))
+
+#ifdef USE_TLS
+ if (var_smtpd_tls_received_header && state->tls_context) {
+ int cont = 0;
+
+ vstring_sprintf(state->buffer,
+ "\t(using %s with cipher %s (%d/%d bits)",
+ state->tls_context->protocol,
+ state->tls_context->cipher_name,
+ state->tls_context->cipher_usebits,
+ state->tls_context->cipher_algbits);
+ if (state->tls_context->kex_name && *state->tls_context->kex_name) {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t key-exchange %s",
+ state->tls_context->kex_name);
+ if (state->tls_context->kex_curve
+ && *state->tls_context->kex_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->kex_curve);
+ else if (state->tls_context->kex_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->kex_bits);
+ cont = 1;
+ }
+ if (state->tls_context->srvr_sig_name
+ && *state->tls_context->srvr_sig_name) {
+ if (cont) {
+ vstring_sprintf_append(state->buffer, " server-signature %s",
+ state->tls_context->srvr_sig_name);
+ } else {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t server-signature %s",
+ state->tls_context->srvr_sig_name);
+ }
+ if (state->tls_context->srvr_sig_curve
+ && *state->tls_context->srvr_sig_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->srvr_sig_curve);
+ else if (state->tls_context->srvr_sig_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->srvr_sig_bits);
+ if (state->tls_context->srvr_sig_dgst
+ && *state->tls_context->srvr_sig_dgst)
+ vstring_sprintf_append(state->buffer, " server-digest %s",
+ state->tls_context->srvr_sig_dgst);
+ }
+ if (state->tls_context->clnt_sig_name
+ && *state->tls_context->clnt_sig_name) {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t client-signature %s",
+ state->tls_context->clnt_sig_name);
+ if (state->tls_context->clnt_sig_curve
+ && *state->tls_context->clnt_sig_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->clnt_sig_curve);
+ else if (state->tls_context->clnt_sig_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->clnt_sig_bits);
+ if (state->tls_context->clnt_sig_dgst
+ && *state->tls_context->clnt_sig_dgst)
+ vstring_sprintf_append(state->buffer, " client-digest %s",
+ state->tls_context->clnt_sig_dgst);
+ }
+ out_fprintf(out_stream, REC_TYPE_NORM, "%s)", STR(state->buffer));
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ peer_CN = VSTRING_STRDUP(state->tls_context->peer_CN);
+ comment_sanitize(peer_CN);
+ issuer_CN = VSTRING_STRDUP(state->tls_context->issuer_CN ?
+ state->tls_context->issuer_CN : "");
+ comment_sanitize(issuer_CN);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client CN \"%s\", Issuer \"%s\" (%s))",
+ STR(peer_CN), STR(issuer_CN),
+ TLS_CERT_IS_TRUSTED(state->tls_context) ?
+ "verified OK" : "not verified");
+ vstring_free(issuer_CN);
+ vstring_free(peer_CN);
+ } else if (var_smtpd_tls_ask_ccert)
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client did not present a certificate)");
+ else
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(No client certificate requested)");
+ }
+ /* RFC 3848 is defined for ESMTP only. */
+ if (state->tls_context != 0
+ && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0)
+ rfc3848_sess = "S";
+ else
+#endif
+ rfc3848_sess = "";
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_auth_hdr && state->sasl_username) {
+ username = VSTRING_STRDUP(state->sasl_username);
+ comment_sanitize(username);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Authenticated sender: %s)", STR(username));
+ vstring_free(username);
+ }
+ /* RFC 3848 is defined for ESMTP only. */
+ if (state->sasl_username
+ && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0)
+ rfc3848_auth = "A";
+ else
+#endif
+ rfc3848_auth = "";
+ if (state->rcpt_count == 1 && state->recipient) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ state->cleanup ? "\tby %s (%s) with %s%s%s id %s" :
+ "\tby %s (%s) with %s%s%s",
+ var_myhostname, var_mail_name,
+ with_protocol, rfc3848_sess,
+ rfc3848_auth, state->queue_id);
+ quote_822_local(state->buffer, state->recipient);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\tfor <%s>; %s", STR(state->buffer),
+ mail_date(state->arrival_time.tv_sec));
+ } else {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ state->cleanup ? "\tby %s (%s) with %s%s%s id %s;" :
+ "\tby %s (%s) with %s%s%s;",
+ var_myhostname, var_mail_name,
+ with_protocol, rfc3848_sess,
+ rfc3848_auth, state->queue_id);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t%s", mail_date(state->arrival_time.tv_sec));
+ }
+#ifdef RECEIVED_ENVELOPE_FROM
+ quote_822_local(state->buffer, state->sender);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(envelope-from %s)", STR(state->buffer));
+#endif
+ }
+}
+
+/* receive_data_message - finish envelope and open message segment */
+
+static void receive_data_message(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream,
+ int out_error)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ char *start;
+ int len;
+ int curr_rec_type;
+ int prev_rec_type;
+ int first = 1;
+ int prev_got_bare_lf = 0;
+
+ /*
+ * If deadlines are enabled, increase the time budget as message content
+ * arrives.
+ */
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline,
+ var_smtpd_min_data_rate);
+
+ /*
+ * Copy the message content. If the cleanup process has a problem, keep
+ * reading until the remote stops sending, then complain. Produce typed
+ * records from the SMTP stream so we can handle data that spans buffers.
+ *
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our own
+ * Received: header. What an ugly Kluge.
+ *
+ * XXX Deal with UNIX-style From_ lines at the start of message content
+ * because sendmail permits it.
+ */
+ for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type,
+ prev_got_bare_lf = smtp_got_bare_lf) {
+ if (smtp_get(state->buffer, state->client, var_line_limit,
+ SMTP_GET_FLAG_NONE) == '\n')
+ curr_rec_type = REC_TYPE_NORM;
+ else
+ curr_rec_type = REC_TYPE_CONT;
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
+ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->buffer);
+ len = VSTRING_LEN(state->buffer);
+ if (first) {
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
+ out_fprintf(out_stream, curr_rec_type,
+ "X-Mailbox-Line: %s", start);
+ continue;
+ }
+ first = 0;
+ if (len > 0 && IS_SPACE_TAB(start[0]))
+ out_record(out_stream, REC_TYPE_NORM, "", 0);
+ }
+ if (prev_rec_type != REC_TYPE_CONT && *start == '.') {
+ if (len == 1 && IS_BARE_LF_WANT_STD_EOD(smtp_detect_bare_lf)
+ && (smtp_got_bare_lf || prev_got_bare_lf))
+ /* Do not store or send to proxy filter. */
+ continue;
+ if (proxy == 0 ? (++start, --len) == 0 : len == 1)
+ break;
+ }
+ if (state->err == CLEANUP_STAT_OK) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_message_limit - state->act_size < len + 2) {
+ state->err = CLEANUP_STAT_SIZE;
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id ? state->queue_id : "NOQUEUE");
+ } else {
+ state->act_size += len + 2;
+ if (out_record(out_stream, curr_rec_type, start, len) < 0)
+ state->err = out_error;
+ }
+ }
+ }
+ state->where = SMTPD_AFTER_EOM;
+}
+
+/* common_post_message_handling - commit message or report error */
+
+static int common_post_message_handling(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ const char *err;
+ VSTRING *why = 0;
+ int saved_err;
+ const CLEANUP_STAT_DETAIL *detail;
+
+#define IS_SMTP_REJECT(s) \
+ (((s)[0] == '4' || (s)[0] == '5') \
+ && ISDIGIT((s)[1]) && ISDIGIT((s)[2]) \
+ && ((s)[3] == '\0' || (s)[3] == ' ' || (s)[3] == '-'))
+
+ if (state->err == CLEANUP_STAT_OK
+ && SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_eod(state)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ if (proxy) {
+ smtpd_proxy_close(state);
+ } else {
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+ return (-1);
+ }
+
+ /*
+ * Send the end of DATA and finish the proxy connection. Set the
+ * CLEANUP_STAT_PROXY error flag in case of trouble.
+ */
+ if (proxy) {
+ if (state->err == CLEANUP_STAT_OK) {
+ (void) proxy->cmd(state, SMTPD_PROX_WANT_ANY, ".");
+ if (state->err == CLEANUP_STAT_OK &&
+ *STR(proxy->reply) != '2')
+ state->err = CLEANUP_STAT_CONT;
+ }
+ }
+
+ /*
+ * Flush out access table actions that are delegated to the cleanup
+ * server. There is similar code at the beginning of the DATA command.
+ *
+ * Send the end-of-segment markers and finish the queue file record stream.
+ */
+ else {
+ if (state->err == CLEANUP_STAT_OK) {
+ rec_fputs(state->cleanup, REC_TYPE_XTRA, "");
+ if (state->saved_filter)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s",
+ state->saved_filter);
+ if (state->saved_redirect)
+ rec_fprintf(state->cleanup, REC_TYPE_RDR, "%s",
+ state->saved_redirect);
+ if (state->saved_bcc) {
+ char **cpp;
+
+ for (cpp = state->saved_bcc->argv; *cpp; cpp++) {
+ rec_fprintf(state->cleanup, REC_TYPE_RCPT, "%s",
+ *cpp);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, DSN_NOTIFY_NEVER);
+ }
+ }
+ if (state->saved_flags)
+ rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d",
+ state->saved_flags);
+#ifdef DELAY_ACTION
+ if (state->saved_delay)
+ rec_fprintf(state->cleanup, REC_TYPE_DELAY, "%d",
+ state->saved_delay);
+#endif
+ if (vstream_ferror(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+ }
+ if (state->err == CLEANUP_STAT_OK)
+ if (rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
+ || vstream_fflush(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+ if (state->err == 0) {
+ why = vstring_alloc(10);
+ state->err = mail_stream_finish(state->dest, why);
+ if (IS_SMTP_REJECT(STR(why)))
+ printable_except(STR(why), ' ', "\r\n");
+ else
+ printable(STR(why), ' ');
+ } else
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+
+ /*
+ * XXX If we lose the cleanup server while it is editing a queue file,
+ * the Postfix SMTP server will be out of sync with Milter applications.
+ * Sending an ABORT to the Milters is not sufficient to restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Destroying and recreating the Milters (and faking
+ * the connect and ehlo events) is too much trouble for testing and
+ * maintenance. Workaround: force the Postfix SMTP server to hang up with
+ * a 421 response in the rare case that the cleanup server breaks AND
+ * that the remote SMTP client continues the session after end-of-data.
+ *
+ * XXX Should use something other than CLEANUP_STAT_WRITE when we lose
+ * contact with the cleanup server. This requires changes to the
+ * mail_stream module and its users (smtpd, qmqpd, perhaps sendmail).
+ *
+ * XXX See exception below in code that overrides state->access_denied for
+ * compliance with RFC 2821 Sec 3.1.
+ */
+ if (state->milters != 0 && (state->err & CLEANUP_STAT_WRITE) != 0)
+ state->access_denied = mystrdup("421 4.3.0 Mail system error");
+
+ /*
+ * Handle any errors. One message may suffer from multiple errors, so
+ * complain only about the most severe error. Forgive any previous client
+ * errors when a message was received successfully.
+ *
+ * See also: qmqpd.c
+ */
+ if (state->err == CLEANUP_STAT_OK) {
+ state->error_count = 0;
+ state->error_mask = 0;
+ state->junk_cmds = 0;
+ if (proxy)
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ else if (SMTPD_PROCESSING_BDAT(state))
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: %ld bytes queued as %s",
+ (long) state->act_size, state->queue_id);
+ else
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_BARE_LF) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ log_whatsup(state, "reject", "bare <LF> received");
+ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
+ var_smtpd_forbid_bare_lf_code, var_myhostname);
+ } else if (why && IS_SMTP_REJECT(STR(why))) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", STR(why));
+ } else if ((state->err & CLEANUP_STAT_DEFER) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ detail = cleanup_stat_detail(CLEANUP_STAT_DEFER);
+ if (why && LEN(why) > 0) {
+ /* Allow address-specific DSN status in header/body_checks. */
+ smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why));
+ } else {
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ }
+ } else if ((state->err & CLEANUP_STAT_BAD) != 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_BAD);
+ smtpd_chat_reply(state, "%d %s Error: internal error %d",
+ detail->smtp, detail->dsn, state->err);
+ } else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
+ state->error_mask |= MAIL_ERROR_BOUNCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_SIZE);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
+ state->error_mask |= MAIL_ERROR_BOUNCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_HOPS);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_CONT) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
+ if (proxy) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ } else if (why && LEN(why) > 0) {
+ /* Allow address-specific DSN status in header/body_checks. */
+ smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why));
+ } else {
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ }
+ } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_WRITE);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_PROXY) != 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ } else {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_BAD);
+ smtpd_chat_reply(state, "%d %s Error: internal error %d",
+ detail->smtp, detail->dsn, state->err);
+ }
+
+ /*
+ * By popular command: the proxy's end-of-data reply.
+ */
+ if (proxy)
+ msg_info("proxy-%s: %s: %s;%s",
+ (state->err == CLEANUP_STAT_OK) ? "accept" : "reject",
+ state->where, STR(proxy->reply), smtpd_whatsup(state));
+
+ /*
+ * Cleanup. The client may send another MAIL command.
+ */
+ saved_err = state->err;
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (why)
+ vstring_free(why);
+ return (saved_err);
+}
+
+/* skip_bdat - skip content and respond to BDAT error */
+
+static int skip_bdat(SMTPD_STATE *state, off_t chunk_size,
+ bool final_chunk, const char *format,...)
+{
+ va_list ap;
+ off_t done;
+ off_t len;
+
+ /*
+ * Read and discard content from the remote SMTP client. TODO: drop the
+ * connection in case of overload.
+ */
+ for (done = 0; done < chunk_size; done += len) {
+ if ((len = chunk_size - done) > VSTREAM_BUFSIZE)
+ len = VSTREAM_BUFSIZE;
+ smtp_fread_buf(state->buffer, len, state->client);
+ }
+
+ /*
+ * Send the response to the remote SMTP client.
+ */
+ va_start(ap, format);
+ vsmtpd_chat_reply(state, format, ap);
+ va_end(ap);
+
+ /*
+ * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET.
+ */
+ if (final_chunk)
+ mail_reset(state);
+ else
+ state->bdat_state = SMTPD_BDAT_STAT_ERROR;
+ return (-1);
+}
+
+/* bdat_cmd - process BDAT command */
+
+static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ off_t chunk_size;
+ bool final_chunk;
+ off_t done;
+ off_t read_len;
+ char *start;
+ int len;
+ int curr_rec_type;
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t);
+ int (*out_fprintf) (VSTREAM *, int, const char *,...);
+ VSTREAM *out_stream;
+ int out_error;
+
+ /*
+ * Hang up if the BDAT command is disabled. The next input would be raw
+ * message content and that would trigger lots of command errors.
+ */
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented");
+ return (-1);
+ }
+
+ /*
+ * Hang up if the BDAT command is malformed. The next input would be raw
+ * message content and that would trigger lots of command errors.
+ */
+ if (argc < 2 || argc > 3 || !alldig(argv[1].strval)
+ || (chunk_size = off_cvt_string(argv[1].strval)) < 0
+ || ((final_chunk = (argc == 3))
+ && strcasecmp(argv[2].strval, "LAST") != 0)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ msg_warn("%s: malformed BDAT command syntax from %s: %.100s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr, printable(vstring_str(state->buffer), '?'));
+ smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]");
+ return (-1);
+ }
+
+ /*
+ * If deadlines are enabled, increase the time budget as message content
+ * arrives.
+ */
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline,
+ var_smtpd_min_data_rate);
+
+ /*
+ * Block abuse involving empty chunks (alternatively, we could count
+ * "BDAT 0" as a "NOOP", but then we would have to refactor the code that
+ * enforces the junk command limit). Clients that send a message as a
+ * sequence of "BDAT 1" should not be a problem: the Postfix BDAT
+ * implementation should be efficient enough to handle that.
+ */
+ if (chunk_size == 0 && !final_chunk) {
+ msg_warn("%s: null BDAT request from %s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr);
+ return skip_bdat(state, chunk_size, final_chunk,
+ "551 5.7.1 Null BDAT request");
+ }
+
+ /*
+ * BDAT commands may be pipelined within a MAIL transaction. After a BDAT
+ * request fails, keep accepting BDAT requests and skipping BDAT payloads
+ * to maintain synchronization with the remote SMTP client, until the
+ * client sends BDAT LAST or RSET.
+ */
+ if (state->bdat_state == SMTPD_BDAT_STAT_ERROR)
+ return skip_bdat(state, chunk_size, final_chunk,
+ "551 5.0.0 Discarded %ld bytes after earlier error",
+ (long) chunk_size);
+
+ /*
+ * Special handling for the first BDAT command in a MAIL transaction,
+ * treating it as a kind of "DATA" command for the purpose of policy
+ * evaluation.
+ */
+ if (!SMTPD_PROCESSING_BDAT(state)) {
+
+ /*
+ * With ESMTP command pipelining a client may send BDAT before the
+ * server has replied to all RCPT commands. For this reason we cannot
+ * treat BDAT without valid recipients as a protocol error. Worse,
+ * RFC 3030 does not discuss the role of BDAT commands in RFC 2920
+ * command groups (batches of commands that may be sent without
+ * waiting for a response to each individual command). Therefore we
+ * have to allow for clients that pipeline the entire SMTP session
+ * after EHLO, including multiple MAIL transactions.
+ */
+ if (state->rcpt_count == 0) {
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ /* TODO: maybe remove this from the DATA and BDAT handlers. */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return skip_bdat(state, chunk_size, final_chunk,
+ "503 5.5.1 Error: need RCPT command");
+ } else {
+ return skip_bdat(state, chunk_size, final_chunk,
+ "554 5.5.1 Error: no valid recipients");
+ }
+ }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_data(state)) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk, "%s", err);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_data_event(state->milters)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk, "%s", err);
+ }
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE,
+ SMTPD_CMD_DATA) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk,
+ "%s", STR(proxy->reply));
+ }
+ }
+ /* Block too large chunks. */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && state->act_size > var_message_limit - chunk_size) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("%s: BDAT request from %s exceeds message size limit",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr);
+ return skip_bdat(state, chunk_size, final_chunk,
+ "552 5.3.4 Chunk exceeds message size limit");
+ }
+
+ /*
+ * One level of indirection to choose between normal or proxied
+ * operation. We want to avoid massive code duplication within tons of
+ * if-else clauses. TODO: store this in its own data structure, or in
+ * SMTPD_STATE.
+ */
+ proxy = state->proxy;
+ if (proxy) {
+ out_stream = proxy->stream;
+ out_record = proxy->rec_put;
+ out_fprintf = proxy->rec_fprintf;
+ out_error = CLEANUP_STAT_PROXY;
+ } else {
+ out_stream = state->cleanup;
+ out_record = rec_put;
+ out_fprintf = rec_fprintf;
+ out_error = CLEANUP_STAT_WRITE;
+ }
+ if (!SMTPD_PROCESSING_BDAT(state)) {
+ common_pre_message_handling(state, out_record, out_fprintf,
+ out_stream, out_error);
+ if (state->bdat_get_buffer == 0)
+ state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE);
+ else
+ VSTRING_RESET(state->bdat_get_buffer);
+ state->bdat_prev_rec_type = 0;
+ }
+ state->bdat_state = SMTPD_BDAT_STAT_OK;
+ state->where = SMTPD_AFTER_BDAT;
+
+ /*
+ * Copy the message content. If the cleanup process has a problem, keep
+ * reading until the remote stops sending, then complain. Produce typed
+ * records from the SMTP stream so we can handle data that spans buffers.
+ */
+
+ /*
+ * Instead of reading the entire BDAT chunk into memory, read the chunk
+ * one fragment at a time. The loops below always make one iteration, to
+ * avoid code duplication for the "BDAT 0 LAST" case (empty chunk).
+ */
+ done = 0;
+ do {
+
+ /*
+ * Do not skip the smtp_fread_buf() call if read_len == 0. We still
+ * need the side effects which include resetting the buffer write
+ * position. Skipping the call would invalidate the buffer state.
+ *
+ * Caution: smtp_fread_buf() will long jump after EOF or timeout.
+ */
+ if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE)
+ read_len = VSTREAM_BUFSIZE;
+ smtp_fread_buf(state->buffer, read_len, state->client);
+ state->bdat_get_stream = vstream_memreopen(
+ state->bdat_get_stream, state->buffer, O_RDONLY);
+
+ /*
+ * Read lines from the fragment. The last line may continue in the
+ * next fragment, or in the next chunk.
+ */
+ do {
+ if (smtp_get_noexcept(state->bdat_get_buffer,
+ state->bdat_get_stream,
+ var_line_limit,
+ SMTP_GET_FLAG_APPEND) == '\n') {
+ /* Stopped at end-of-line. */
+ curr_rec_type = REC_TYPE_NORM;
+ } else if (!vstream_feof(state->bdat_get_stream)) {
+ /* Stopped at var_line_limit. */
+ curr_rec_type = REC_TYPE_CONT;
+ } else if (VSTRING_LEN(state->bdat_get_buffer) > 0
+ && final_chunk && read_len == chunk_size - done) {
+ /* Stopped at final chunk end; handle missing end-of-line. */
+ curr_rec_type = REC_TYPE_NORM;
+ } else {
+ /* Stopped at fragment end; empty buffer or not at chunk end. */
+ /* Skip the out_record() and VSTRING_RESET() calls below. */
+ break;
+ }
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
+ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->bdat_get_buffer);
+ len = VSTRING_LEN(state->bdat_get_buffer);
+ if (state->err == CLEANUP_STAT_OK) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_message_limit - state->act_size < len + 2) {
+ state->err = CLEANUP_STAT_SIZE;
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id ? state->queue_id : "NOQUEUE");
+ } else {
+ state->act_size += len + 2;
+ if (*start == '.' && proxy != 0
+ && state->bdat_prev_rec_type != REC_TYPE_CONT)
+ if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0)
+ state->err = out_error;
+ if (state->err == CLEANUP_STAT_OK
+ && out_record(out_stream, curr_rec_type,
+ vstring_str(state->bdat_get_buffer),
+ VSTRING_LEN(state->bdat_get_buffer)) < 0)
+ state->err = out_error;
+ }
+ }
+ VSTRING_RESET(state->bdat_get_buffer);
+ state->bdat_prev_rec_type = curr_rec_type;
+ } while (!vstream_feof(state->bdat_get_stream));
+ done += read_len;
+ } while (done < chunk_size);
+
+ /*
+ * Special handling for BDAT LAST (successful or unsuccessful).
+ */
+ if (final_chunk) {
+ state->where = SMTPD_AFTER_EOM;
+ return common_post_message_handling(state);
+ }
+
+ /*
+ * Unsuccessful non-final BDAT command. common_post_message_handling()
+ * resets all MAIL transaction state including BDAT state. To avoid
+ * useless error messages due to pipelined BDAT commands, enter the
+ * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT
+ * payloads.
+ */
+ else if (state->err != CLEANUP_STAT_OK) {
+ /* NOT: state->where = SMTPD_AFTER_EOM; */
+ (void) common_post_message_handling(state);
+ state->bdat_state = SMTPD_BDAT_STAT_ERROR;
+ return (-1);
+ }
+
+ /*
+ * Successful non-final BDAT command.
+ */
+ else {
+ smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size);
+ return (0);
+ }
+}
+
+/* rset_cmd - process RSET */
+
+static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: RSET");
+ return (-1);
+ }
+
+ /*
+ * Restore state to right after HELO/EHLO command.
+ */
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* noop_cmd - process NOOP */
+
+static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * XXX 2821 incompatibility: Section 4.1.1.9 says that NOOP can have a
+ * parameter string which is to be ignored. NOOP instructions with
+ * parameters? Go figure.
+ *
+ * RFC 2821 violates RFC 821, which says that NOOP takes no parameters.
+ */
+#ifdef RFC821_SYNTAX
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: NOOP");
+ return (-1);
+ }
+#endif
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* vrfy_cmd - process VRFY */
+
+static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err = 0;
+ int rate;
+ int smtputf8 = 0;
+ int saved_flags;
+
+ /*
+ * The SMTP standard (RFC 821) disallows unquoted special characters in
+ * the VRFY argument. Common practice violates the standard, however.
+ * Postfix accommodates common practice where it violates the standard.
+ *
+ * XXX Impedance mismatch! The SMTP command tokenizer preserves quoting,
+ * whereas the recipient restrictions checks expect unquoted (internal)
+ * address forms. Therefore we must parse out the address, or we must
+ * stop doing recipient restriction checks and lose the opportunity to
+ * say "user unknown" at the SMTP port.
+ *
+ * XXX 2821 incompatibility and brain damage: Section 4.5.1 requires that
+ * VRFY is implemented. RFC 821 specifies that VRFY is optional. It gets
+ * even worse: section 3.5.3 says that a 502 (command recognized but not
+ * implemented) reply is not fully compliant.
+ *
+ * Thus, an RFC 2821 compliant implementation cannot refuse to supply
+ * information in reply to VRFY queries. That is simply bogus. The only
+ * reply we could supply is a generic 252 reply. This causes spammers to
+ * add tons of bogus addresses to their mailing lists (spam harvesting by
+ * trying out large lists of potential recipient names with VRFY).
+ */
+#define SLOPPY 0
+
+ if (var_disable_vrfy_cmd) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "502 5.5.1 VRFY command is disabled");
+ return (-1);
+ }
+ /* Fix 20140707: handle missing address. */
+ if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0
+ && argc > 1 && strcasecmp(argv[argc - 1].strval, "SMTPUTF8") == 0) {
+ argc--; /* RFC 6531 */
+ smtputf8 = 1;
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: VRFY address%s",
+ var_smtputf8_enable ? " [SMTPUTF8]" : "");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_crcpt_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_crcpt_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Recipient address rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s",
+ state->addr);
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0
+ && (err[0] == '5' || err[0] == '4')) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (extract_addr(state, argv + 1, REJECT_EMPTY_ADDR, SLOPPY, smtputf8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ /* Fix 20140707: Check the VRFY command. */
+ if (smtputf8 == 0 && var_strict_smtputf8) {
+ if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ mail_reset(state);
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address");
+ return (-1);
+ }
+ }
+ /* Use state->addr_buf, with the unquoted result from extract_addr() */
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ /* Fix 20161206: allow UTF8 in smtpd_recipient_restrictions. */
+ saved_flags = state->flags;
+ if (smtputf8)
+ state->flags |= SMTPD_FLAG_SMTPUTF8;
+ err = smtpd_check_rcpt(state, STR(state->addr_buf));
+ state->flags = saved_flags;
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * XXX 2821 new feature: Section 3.5.1 requires that the VRFY response is
+ * either "full name <user@domain>" or "user@domain". Postfix replies
+ * with the string that was provided by the client, whether or not it is
+ * in fully qualified domain form and the address is in <>.
+ *
+ * Reply code 250 is reserved for the case where the address is verified;
+ * reply code 252 should be used when no definitive certainty exists.
+ */
+ smtpd_chat_reply(state, "252 2.0.0 %s", argv[1].strval);
+ return (0);
+}
+
+/* etrn_cmd - process ETRN command */
+
+static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+
+ /*
+ * Sanity checks.
+ */
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0
+ && (err[0] == '5' || err[0] == '4')) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc != 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 Syntax: ETRN domain");
+ return (-1);
+ }
+ if (argv[1].strval[0] == '@' || argv[1].strval[0] == '#')
+ argv[1].strval++;
+
+ /*
+ * As an extension to RFC 1985 we also allow an RFC 2821 address literal
+ * enclosed in [].
+ *
+ * XXX There does not appear to be an ETRN parameter to indicate that the
+ * domain name is UTF-8.
+ */
+ if (!valid_hostname(argv[1].strval, DONT_GRIPE)
+ && !valid_mailhost_literal(argv[1].strval, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Error: invalid parameter syntax");
+ return (-1);
+ }
+
+ /*
+ * XXX The implementation borrows heavily from the code that implements
+ * UCE restrictions. These typically return 450 or 550 when a request is
+ * rejected. RFC 1985 requires that 459 be sent when the server refuses
+ * to perform the request.
+ */
+ if (SMTPD_STAND_ALONE(state)) {
+ msg_warn("do not use ETRN in \"sendmail -bs\" mode");
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ }
+ if ((err = smtpd_check_etrn(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ switch (flush_send_site(argv[1].strval)) {
+ case FLUSH_STAT_OK:
+ smtpd_chat_reply(state, "250 Queuing started");
+ return (0);
+ case FLUSH_STAT_DENY:
+ msg_warn("reject: ETRN %.100s... from %s",
+ argv[1].strval, state->namaddr);
+ smtpd_chat_reply(state, "459 <%s>: service unavailable",
+ argv[1].strval);
+ return (-1);
+ case FLUSH_STAT_BAD:
+ msg_warn("bad ETRN %.100s... from %s", argv[1].strval, state->namaddr);
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ default:
+ msg_warn("unable to talk to fast flush service");
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ }
+}
+
+/* quit_cmd - process QUIT command */
+
+static int quit_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv)
+{
+ int out_pending = vstream_bufstat(state->client, VSTREAM_BST_OUT_PEND);
+
+ /*
+ * Don't bother checking the syntax.
+ */
+ smtpd_chat_reply(state, "221 2.0.0 Bye");
+
+ /*
+ * When the "." and quit replies are pipelined, make sure they are
+ * flushed now, to avoid repeated mail deliveries in case of a crash in
+ * the "clean up before disconnect" code.
+ *
+ * XXX When this was added in Postfix 2.1 we used vstream_fflush(). As of
+ * Postfix 2.3 we use smtp_flush() for better error reporting.
+ */
+ if (out_pending > 0)
+ smtp_flush(state->client);
+ return (0);
+}
+
+/* xclient_cmd - override SMTP client attributes */
+
+static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_TOKEN *argp;
+ char *raw_value;
+ char *attr_value;
+ char *attr_name;
+ int update_namaddr = 0;
+ int name_status;
+ static const NAME_CODE peer_codes[] = {
+ XCLIENT_UNAVAILABLE, SMTPD_PEER_CODE_PERM,
+ XCLIENT_TEMPORARY, SMTPD_PEER_CODE_TEMP,
+ 0, SMTPD_PEER_CODE_OK,
+ };
+ static const NAME_CODE proto_names[] = {
+ MAIL_PROTO_SMTP, 1,
+ MAIL_PROTO_ESMTP, 2,
+ 0, -1,
+ };
+ int got_helo = 0;
+ int got_proto = 0;
+
+#ifdef USE_SASL_AUTH
+ int got_login = 0;
+ char *saved_username;
+
+#endif
+
+ /*
+ * Sanity checks.
+ *
+ * XXX The XCLIENT command will override its own access control, so that
+ * connection count/rate restrictions can be correctly simulated.
+ */
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...",
+ XCLIENT_CMD);
+ return (-1);
+ }
+ if (xclient_hosts && xclient_hosts->error)
+ cant_permit_command(state, XCLIENT_CMD);
+ if (!xclient_allowed) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization");
+ return (-1);
+ }
+#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
+
+ /*
+ * Initialize.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+
+ /*
+ * Iterate over all attribute=value elements.
+ */
+ for (argp = argv + 1; argp < argv + argc; argp++) {
+ attr_name = argp->strval;
+
+ if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected");
+ return (-1);
+ }
+ if (strlen(raw_value) > 255) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long");
+ return (-1);
+ }
+
+ /*
+ * Backwards compatibility: Postfix prior to version 2.3 does not
+ * xtext encode attribute values.
+ */
+ attr_value = xtext_unquote(state->expand_buf, raw_value) ?
+ STR(state->expand_buf) : raw_value;
+
+ /*
+ * For safety's sake mask non-printable characters. We'll do more
+ * specific censoring later.
+ */
+ printable(attr_value, '?');
+
+#define UPDATE_STR(s, v) do { \
+ const char *_v = (v); \
+ if (s) myfree(s); \
+ (s) = (_v) ? mystrdup(_v) : 0; \
+ } while(0)
+
+ /*
+ * NAME=substitute SMTP client hostname (and reverse/forward name, in
+ * case of success). Also updates the client hostname lookup status
+ * code.
+ */
+ if (STREQ(attr_name, XCLIENT_NAME)) {
+ name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value);
+ if (name_status != SMTPD_PEER_CODE_OK) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_NAME, attr_value);
+ return (-1);
+ }
+ }
+ state->name_status = name_status;
+ UPDATE_STR(state->name, attr_value);
+ update_namaddr = 1;
+ if (name_status == SMTPD_PEER_CODE_OK) {
+ UPDATE_STR(state->reverse_name, attr_value);
+ state->reverse_name_status = name_status;
+ }
+ }
+
+ /*
+ * REVERSE_NAME=substitute SMTP client reverse hostname. Also updates
+ * the client reverse hostname lookup status code.
+ */
+ else if (STREQ(attr_name, XCLIENT_REVERSE_NAME)) {
+ name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value);
+ if (name_status != SMTPD_PEER_CODE_OK) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_REVERSE_NAME, attr_value);
+ return (-1);
+ }
+ }
+ state->reverse_name_status = name_status;
+ UPDATE_STR(state->reverse_name, attr_value);
+ }
+
+ /*
+ * ADDR=substitute SMTP client network address.
+ */
+ else if (STREQ(attr_name, XCLIENT_ADDR)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_ADDR_UNKNOWN;
+ UPDATE_STR(state->addr, attr_value);
+ UPDATE_STR(state->rfc_addr, attr_value);
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value, &state->rfc_addr,
+ &state->addr,
+ &state->addr_family) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_ADDR, attr_value);
+ return (-1);
+ }
+ }
+ update_namaddr = 1;
+ }
+
+ /*
+ * PORT=substitute SMTP client port number.
+ */
+ else if (STREQ(attr_name, XCLIENT_PORT)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_PORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->port, attr_value);
+ update_namaddr = 1;
+ }
+
+ /*
+ * HELO=substitute SMTP client HELO parameter. Censor special
+ * characters that could mess up message headers.
+ */
+ else if (STREQ(attr_name, XCLIENT_HELO)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_HELO_UNKNOWN;
+ } else {
+ if (strlen(attr_value) > VALID_HOSTNAME_LEN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_HELO, attr_value);
+ return (-1);
+ }
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->helo_name, attr_value);
+ got_helo = 1;
+ }
+
+ /*
+ * PROTO=SMTP protocol name.
+ */
+ else if (STREQ(attr_name, XCLIENT_PROTO)) {
+ if (name_code(proto_names, NAME_CODE_FLAG_NONE, attr_value) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_PROTO, attr_value);
+ return (-1);
+ }
+ UPDATE_STR(state->protocol, uppercase(attr_value));
+ got_proto = 1;
+ }
+
+ /*
+ * LOGIN=sasl_username. Sets the authentication method as XCLIENT.
+ * This can be used even if SASL authentication is turned off in
+ * main.cf. We can't make it easier than that.
+ */
+#ifdef USE_SASL_AUTH
+ else if (STREQ(attr_name, XCLIENT_LOGIN)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE) == 0) {
+ smtpd_sasl_auth_extern(state, attr_value, XCLIENT_CMD);
+ got_login = 1;
+ }
+ }
+#endif
+
+ /*
+ * DESTADDR=substitute SMTP server network address.
+ */
+ else if (STREQ(attr_name, XCLIENT_DESTADDR)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = SERVER_ADDR_UNKNOWN;
+ UPDATE_STR(state->dest_addr, attr_value);
+ } else {
+#define NO_NORM_RFC_ADDR ((char **) 0)
+#define NO_NORM_ADDR_FAMILY ((int *) 0)
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value, NO_NORM_RFC_ADDR,
+ &state->dest_addr,
+ NO_NORM_ADDR_FAMILY) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_DESTADDR, attr_value);
+ return (-1);
+ }
+ }
+ /* XXX Require same address family as client address. */
+ }
+
+ /*
+ * DESTPORT=substitute SMTP server port number.
+ */
+ else if (STREQ(attr_name, XCLIENT_DESTPORT)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = SERVER_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_DESTPORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->dest_port, attr_value);
+ }
+
+ /*
+ * Unknown attribute name. Complain.
+ */
+ else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s",
+ XCLIENT_CMD, attr_name);
+ return (-1);
+ }
+ }
+
+ /*
+ * Update the combined name and address when either has changed.
+ */
+ if (update_namaddr) {
+ if (state->namaddr)
+ myfree(state->namaddr);
+ state->namaddr =
+ SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, state->port);
+ }
+
+ /*
+ * XXX Compatibility: when the client issues XCLIENT then we have to go
+ * back to initial server greeting stage, otherwise we can't correctly
+ * simulate smtpd_client_restrictions (with smtpd_delay_reject=0) and
+ * Milter connect restrictions.
+ *
+ * XXX Compatibility: for accurate simulation we must also reset the HELO
+ * information. We keep the information if it was specified in the
+ * XCLIENT command.
+ *
+ * XXX The client connection count/rate control must be consistent in its
+ * use of client address information in connect and disconnect events. We
+ * re-evaluate xclient so that we correctly simulate connection
+ * concurrency and connection rate restrictions.
+ *
+ * XXX Duplicated from smtpd_proto().
+ */
+ xclient_allowed =
+ namadr_list_match(xclient_hosts, state->name, state->addr);
+ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((state)) == 0 && bare_lf_mask
+ && !namadr_list_match(bare_lf_excl, state->name, state->addr)) ?
+ bare_lf_mask : 0;
+ /* NOT: tls_reset() */
+ if (got_helo == 0)
+ helo_reset(state);
+ if (got_proto == 0 && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ }
+#ifdef USE_SASL_AUTH
+ /* XXX What if they send the parameters via multiple commands? */
+ if (got_login == 0)
+ smtpd_sasl_auth_reset(state);
+ if (smtpd_sasl_is_active(state)) {
+ if (got_login)
+ saved_username = mystrdup(state->sasl_username);
+ smtpd_sasl_deactivate(state);
+#ifdef USE_TLS
+ if (state->tls_context != 0) /* TLS from XCLIENT proxy? */
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ else
+#endif
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+ if (got_login) {
+ smtpd_sasl_auth_extern(state, saved_username, XCLIENT_CMD);
+ myfree(saved_username);
+ }
+ }
+#endif
+ chat_reset(state, 0);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (state->milters)
+ milter_disc_event(state->milters);
+ /* Following duplicates the top-level connect/disconnect handler. */
+ teardown_milters(state);
+ setup_milters(state);
+ vstream_longjmp(state->client, SMTP_ERR_NONE);
+ return (0);
+}
+
+/* xforward_cmd - forward logging attributes */
+
+static int xforward_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_TOKEN *argp;
+ char *raw_value;
+ char *attr_value;
+ char *attr_name;
+ int updated = 0;
+ static const NAME_CODE xforward_flags[] = {
+ XFORWARD_NAME, SMTPD_STATE_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTPD_STATE_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTPD_STATE_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTPD_STATE_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTPD_STATE_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTPD_STATE_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTPD_STATE_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ static const char *context_name[] = {
+ MAIL_ATTR_RWR_LOCAL, /* Postfix internal form */
+ MAIL_ATTR_RWR_REMOTE, /* Postfix internal form */
+ };
+ static const NAME_CODE xforward_to_context[] = {
+ XFORWARD_DOM_LOCAL, 0, /* XFORWARD representation */
+ XFORWARD_DOM_REMOTE, 1, /* XFORWARD representation */
+ 0, -1,
+ };
+ int flag;
+ int context_code;
+
+ /*
+ * Sanity checks.
+ */
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...",
+ XFORWARD_CMD);
+ return (-1);
+ }
+ if (xforward_hosts && xforward_hosts->error)
+ cant_permit_command(state, XFORWARD_CMD);
+ if (!xforward_allowed) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization");
+ return (-1);
+ }
+
+ /*
+ * Initialize.
+ */
+ if (state->xforward.flags == 0)
+ smtpd_xforward_preset(state);
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+
+ /*
+ * Iterate over all attribute=value elements.
+ */
+ for (argp = argv + 1; argp < argv + argc; argp++) {
+ attr_name = argp->strval;
+
+ if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected");
+ return (-1);
+ }
+ if (strlen(raw_value) > 255) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long");
+ return (-1);
+ }
+
+ /*
+ * Backwards compatibility: Postfix prior to version 2.3 does not
+ * xtext encode attribute values.
+ */
+ attr_value = xtext_unquote(state->expand_buf, raw_value) ?
+ STR(state->expand_buf) : raw_value;
+
+ /*
+ * For safety's sake mask non-printable characters. We'll do more
+ * specific censoring later.
+ */
+ printable(attr_value, '?');
+
+ flag = name_code(xforward_flags, NAME_CODE_FLAG_NONE, attr_name);
+ switch (flag) {
+
+ /*
+ * NAME=up-stream host name, not necessarily in the DNS. Censor
+ * special characters that could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_NAME:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_NAME, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->xforward.name, attr_value);
+ break;
+
+ /*
+ * ADDR=up-stream host network address, not necessarily on the
+ * Internet. Censor special characters that could mess up message
+ * headers.
+ */
+ case SMTPD_STATE_XFORWARD_ADDR:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_ADDR_UNKNOWN;
+ UPDATE_STR(state->xforward.addr, attr_value);
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value,
+ &state->xforward.rfc_addr,
+ &state->xforward.addr,
+ NO_NORM_ADDR_FAMILY) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_ADDR, attr_value);
+ return (-1);
+ }
+ }
+ break;
+
+ /*
+ * PORT=up-stream port number.
+ */
+ case SMTPD_STATE_XFORWARD_PORT:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_PORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->xforward.port, attr_value);
+ break;
+
+ /*
+ * HELO=hostname that the up-stream MTA introduced itself with
+ * (not necessarily SMTP HELO). Censor special characters that
+ * could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_HELO:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_HELO_UNKNOWN;
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.helo_name, attr_value);
+ break;
+
+ /*
+ * PROTO=up-stream protocol, not necessarily SMTP or ESMTP.
+ * Censor special characters that could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_PROTO:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_PROTO_UNKNOWN;
+ } else {
+ if (strlen(attr_value) > 64) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_PROTO, attr_value);
+ return (-1);
+ }
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.protocol, attr_value);
+ break;
+
+ /*
+ * IDENT=local message identifier on the up-stream MTA. Censor
+ * special characters that could mess up logging or macro
+ * expansions.
+ */
+ case SMTPD_STATE_XFORWARD_IDENT:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_IDENT_UNKNOWN;
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.ident, attr_value);
+ break;
+
+ /*
+ * DOMAIN=local or remote.
+ */
+ case SMTPD_STATE_XFORWARD_DOMAIN:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE))
+ attr_value = XFORWARD_DOM_LOCAL;
+ if ((context_code = name_code(xforward_to_context,
+ NAME_CODE_FLAG_NONE,
+ attr_value)) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_DOMAIN, attr_value);
+ return (-1);
+ }
+ UPDATE_STR(state->xforward.domain, context_name[context_code]);
+ break;
+
+ /*
+ * Unknown attribute name. Complain.
+ */
+ default:
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s",
+ XFORWARD_CMD, attr_name);
+ return (-1);
+ }
+ updated |= flag;
+ }
+ state->xforward.flags |= updated;
+
+ /*
+ * Update the combined name and address when either has changed. Use only
+ * the name when no address is available.
+ */
+ if (updated & (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR
+ | SMTPD_STATE_XFORWARD_PORT)) {
+ if (state->xforward.namaddr)
+ myfree(state->xforward.namaddr);
+ state->xforward.namaddr =
+ IS_AVAIL_CLIENT_ADDR(state->xforward.addr) ?
+ SMTPD_BUILD_NAMADDRPORT(state->xforward.name,
+ state->xforward.addr,
+ state->xforward.port) :
+ mystrdup(state->xforward.name);
+ }
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* chat_reset - notify postmaster and reset conversation log */
+
+static void chat_reset(SMTPD_STATE *state, int threshold)
+{
+
+ /*
+ * Notify the postmaster if there were errors. This usually indicates a
+ * client configuration problem, or that someone is trying nasty things.
+ * Either is significant enough to bother the postmaster. XXX Can't
+ * report problems when running in stand-alone mode: postmaster notices
+ * require availability of the cleanup service.
+ */
+ if (state->history != 0 && state->history->argc > threshold) {
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (state->error_mask & state->notify_mask))
+ smtpd_chat_notify(state);
+ state->error_mask = 0;
+ smtpd_chat_reset(state);
+ }
+}
+
+#ifdef USE_TLS
+
+/* smtpd_start_tls - turn on TLS or force disconnect */
+
+static void smtpd_start_tls(SMTPD_STATE *state)
+{
+ int rate;
+ int cert_present;
+ int requirecert;
+
+#ifdef USE_TLSPROXY
+
+ /*
+ * This is non-production code, for tlsproxy(8) load testing only. It
+ * implements enough to enable some Postfix features that depend on TLS
+ * encryption.
+ *
+ * To insert tlsproxy(8) between this process and the SMTP client, we swap
+ * the file descriptors between the state->tlsproxy and state->client
+ * VSTREAMS, so that we don't lose all the user-configurable
+ * state->client attributes (such as longjump buffers or timeouts).
+ *
+ * As we implement tlsproxy support in the Postfix SMTP client we should
+ * develop a usable abstraction that encapsulates this stream plumbing in
+ * a library module.
+ */
+ vstream_control(state->tlsproxy, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ vstream_control(state->client, CA_VSTREAM_CTL_SWAP_FD(state->tlsproxy),
+ CA_VSTREAM_CTL_END);
+ (void) vstream_fclose(state->tlsproxy); /* direct-to-client stream! */
+ state->tlsproxy = 0;
+
+ /*
+ * After plumbing the plaintext stream, receive the TLS context object.
+ * For this we must use the same VSTREAM buffer that we also use to
+ * receive subsequent SMTP commands. The attribute protocol is robust
+ * enough that an adversary cannot inject their own bogus TLS context
+ * attributes into the stream.
+ */
+ state->tls_context = tls_proxy_context_receive(state->client);
+
+ /*
+ * XXX Maybe it is better to send this information to tlsproxy(8) when
+ * requesting service, effectively making a remote tls_server_start()
+ * call.
+ */
+ requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls);
+
+#else /* USE_TLSPROXY */
+ TLS_SERVER_START_PROPS props;
+ static char *cipher_grade;
+ static VSTRING *cipher_exclusions;
+
+ /*
+ * Wrapper mode uses a dedicated port and always requires TLS.
+ *
+ * XXX In non-wrapper mode, it is possible to require client certificate
+ * verification without requiring TLS. Since certificates can be verified
+ * only while TLS is turned on, this means that Postfix will happily
+ * perform SMTP transactions when the client does not use the STARTTLS
+ * command. For this reason, Postfix does not require client certificate
+ * verification unless TLS is required.
+ *
+ * The cipher grade and exclusions don't change between sessions. Compute
+ * just once and cache.
+ */
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ if (cipher_grade == 0) {
+ cipher_grade = var_smtpd_enforce_tls ?
+ var_smtpd_tls_mand_ciph : var_smtpd_tls_ciph;
+ cipher_exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_excl_ciph);
+ if (var_smtpd_enforce_tls)
+ ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_mand_excl);
+ if (ask_client_cert)
+ ADD_EXCLUDE(cipher_exclusions, "aNULL");
+ }
+
+ /*
+ * Perform the TLS handshake now. Check the client certificate
+ * requirements later, if necessary.
+ */
+ requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls);
+
+ state->tls_context =
+ TLS_SERVER_START(&props,
+ ctx = smtpd_tls_ctx,
+ stream = state->client,
+ fd = -1,
+ timeout = var_smtpd_starttls_tmout,
+ requirecert = requirecert,
+ serverid = state->service,
+ namaddr = state->namaddr,
+ cipher_grade = cipher_grade,
+ cipher_exclusions = STR(cipher_exclusions),
+ mdalg = var_smtpd_tls_fpt_dgst);
+
+#endif /* USE_TLSPROXY */
+
+ /*
+ * For new (i.e. not re-used) TLS sessions, increment the client's new
+ * TLS session rate counter. We enforce the limit here only for human
+ * factors reasons (reduce the WTF factor), even though it is too late to
+ * save the CPU that was already burnt on PKI ops. The real safety
+ * mechanism applies with future STARTTLS commands (or wrappermode
+ * connections), prior to the SSL handshake.
+ *
+ * XXX The client event count/rate control must be consistent in its use of
+ * client address information in connect and disconnect events. For now
+ * we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (var_smtpd_cntls_limit > 0
+ && (state->tls_context == 0 || state->tls_context->session_reused == 0)
+ && SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("New TLS session rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ if (state->tls_context)
+ smtpd_chat_reply(state,
+ "421 4.7.0 %s Error: too many new TLS sessions from %s",
+ var_myhostname, state->namaddr);
+ /* XXX Use regular return to signal end of session. */
+ vstream_longjmp(state->client, SMTP_ERR_QUIET);
+ }
+
+ /*
+ * When the TLS handshake fails, the conversation is in an unknown state.
+ * There is nothing we can do except to disconnect from the client.
+ */
+ if (state->tls_context == 0)
+ vstream_longjmp(state->client, SMTP_ERR_EOF);
+
+ /*
+ * If we are requiring verified client certs, enforce the constraint
+ * here. We have a usable TLS session with the client, so no need to
+ * disable I/O, ... we can even be polite and send "421 ...".
+ */
+ if (requirecert && TLS_CERT_IS_TRUSTED(state->tls_context) == 0) {
+
+ /*
+ * In non-wrappermode, fetch the next command (should be EHLO). Reply
+ * with 421, then disconnect (as a side-effect of replying with 421).
+ */
+ cert_present = TLS_CERT_IS_PRESENT(state->tls_context);
+ msg_info("NOQUEUE: abort: TLS from %s: %s",
+ state->namaddr, cert_present ?
+ "Client certificate not trusted" :
+ "No client certificate presented");
+ if (var_smtpd_tls_wrappermode == 0)
+ smtpd_chat_query(state);
+ smtpd_chat_reply(state, "421 4.7.1 %s Error: %s",
+ var_myhostname, cert_present ?
+ "Client certificate not trusted" :
+ "No client certificate presented");
+ state->error_mask |= MAIL_ERROR_POLICY;
+ return;
+ }
+
+ /*
+ * When TLS is turned on, we may offer AUTH methods that would not be
+ * offered within a plain-text session.
+ *
+ * XXX Always refresh SASL the mechanism list after STARTTLS. Dovecot
+ * responses may depend on whether the SMTP connection is encrypted.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ /* Non-wrappermode, presumably. */
+ if (smtpd_sasl_is_active(state)) {
+ smtpd_sasl_auth_reset(state);
+ smtpd_sasl_deactivate(state);
+ }
+ /* Wrappermode and non-wrappermode. */
+ if (smtpd_sasl_is_active(state) == 0)
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ }
+#endif
+}
+
+/* starttls_cmd - respond to STARTTLS */
+
+static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+ const char *err;
+ int rate;
+
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: STARTTLS");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) {
+ if (err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ /* Sendmail compatibility: map 4xx into 454. */
+ else if (err[0] == '4') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "454 4.3.0 Try again later");
+ return (-1);
+ }
+ }
+ if (state->tls_context != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "554 5.5.1 Error: TLS already active");
+ return (-1);
+ }
+ if (var_smtpd_use_tls == 0
+ || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented");
+ return (-1);
+ }
+#ifdef USE_TLSPROXY
+
+ /*
+ * Note: state->tlsproxy is left open when smtp_flush() calls longjmp(),
+ * so we garbage-collect the VSTREAM in smtpd_state_reset().
+ */
+#define PROXY_OPEN_FLAGS \
+ (TLS_PROXY_FLAG_ROLE_SERVER | TLS_PROXY_FLAG_SEND_CONTEXT)
+
+ state->tlsproxy =
+ tls_proxy_legacy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS,
+ state->client, state->addr,
+ state->port, var_smtpd_tmout,
+ state->service);
+ if (state->tlsproxy == 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* RFC 3207 Section 4. */
+ smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem");
+ return (-1);
+ }
+#else /* USE_TLSPROXY */
+ if (smtpd_tls_ctx == 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* RFC 3207 Section 4. */
+ smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem");
+ return (-1);
+ }
+#endif /* USE_TLSPROXY */
+
+ /*
+ * Enforce TLS handshake rate limit when this client negotiated too many
+ * new TLS sessions in the recent past.
+ *
+ * XXX The client event count/rate control must be consistent in its use of
+ * client address information in connect and disconnect events. For now
+ * we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (var_smtpd_cntls_limit > 0
+ && SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls_stat(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Refusing STARTTLS request from %s for service %s",
+ state->namaddr, state->service);
+ smtpd_chat_reply(state,
+ "454 4.7.0 Error: too many new TLS sessions from %s",
+ state->namaddr);
+#ifdef USE_TLSPROXY
+ (void) vstream_fclose(state->tlsproxy);
+ state->tlsproxy = 0;
+#endif
+ return (-1);
+ }
+ smtpd_chat_reply(state, "220 2.0.0 Ready to start TLS");
+ /* Flush before we switch read/write routines or file descriptors. */
+ smtp_flush(state->client);
+ /* At this point there must not be any pending plaintext. */
+ vstream_fpurge(state->client, VSTREAM_PURGE_BOTH);
+
+ /*
+ * Reset all inputs to the initial state.
+ *
+ * XXX RFC 2487 does not forbid the use of STARTTLS while mail transfer is
+ * in progress, so we have to allow it even when it makes no sense.
+ */
+ helo_reset(state);
+ mail_reset(state);
+ rcpt_reset(state);
+
+ /*
+ * Turn on TLS, using code that is shared with TLS wrapper mode. This
+ * code does not return when the handshake fails.
+ */
+ smtpd_start_tls(state);
+ return (0);
+}
+
+/* tls_reset - undo STARTTLS */
+
+static void tls_reset(SMTPD_STATE *state)
+{
+ int failure = 0;
+
+ /*
+ * Don't waste time when we lost contact.
+ */
+ if (state->tls_context) {
+ if (vstream_feof(state->client) || vstream_ferror(state->client))
+ failure = 1;
+ vstream_fflush(state->client); /* NOT: smtp_flush() */
+#ifdef USE_TLSPROXY
+ tls_proxy_context_free(state->tls_context);
+#else
+ tls_server_stop(smtpd_tls_ctx, state->client, var_smtpd_starttls_tmout,
+ failure, state->tls_context);
+#endif
+ state->tls_context = 0;
+ }
+}
+
+#endif
+
+#if !defined(USE_TLS) || !defined(USE_SASL_AUTH)
+
+/* unimpl_cmd - dummy for functionality that is not compiled in */
+
+static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * When a connection is closed we want to log the request counts for
+ * unimplemented STARTTLS or AUTH commands separately, instead of logging
+ * those commands as "unknown". By handling unimplemented commands with
+ * this dummy function, we avoid messing up the command processing loop.
+ */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented");
+ return (-1);
+}
+
+#endif
+
+ /*
+ * The table of all SMTP commands that we know. Set the junk limit flag on
+ * any command that can be repeated an arbitrary number of times without
+ * triggering a tarpit delay of some sort.
+ */
+typedef struct SMTPD_CMD {
+ char *name;
+ int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *);
+ int flags;
+ int success_count;
+ int total_count;
+} SMTPD_CMD;
+
+ /*
+ * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM,
+ * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined
+ * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands
+ * can only appear as the last command in a group". RFC 3030 allows BDAT
+ * commands to be pipelined as well.
+ */
+#define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */
+#define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */
+#define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */
+
+static SMTPD_CMD smtpd_cmd_table[] = {
+ {SMTPD_CMD_HELO, helo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_EHLO, ehlo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_XCLIENT, xclient_cmd, SMTPD_CMD_FLAG_PRE_TLS},
+ {SMTPD_CMD_XFORWARD, xforward_cmd,},
+#ifdef USE_TLS
+ {SMTPD_CMD_STARTTLS, starttls_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+#else
+ {SMTPD_CMD_STARTTLS, unimpl_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+#endif
+#ifdef USE_SASL_AUTH
+ {SMTPD_CMD_AUTH, smtpd_sasl_auth_cmd_wrapper,},
+#else
+ {SMTPD_CMD_AUTH, unimpl_cmd,},
+#endif
+ {SMTPD_CMD_MAIL, mail_cmd,},
+ {SMTPD_CMD_RCPT, rcpt_cmd,},
+ {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_BDAT, bdat_cmd,},
+ {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,},
+ {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_ETRN, etrn_cmd, SMTPD_CMD_FLAG_LIMIT,},
+ {SMTPD_CMD_QUIT, quit_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+ {0,},
+};
+
+static STRING_LIST *smtpd_noop_cmds;
+static STRING_LIST *smtpd_forbid_cmds;
+
+/* smtpd_flag_ill_pipelining - flag pipelining protocol violation */
+
+static int smtpd_flag_ill_pipelining(SMTPD_STATE *state)
+{
+
+ /*
+ * This code will not return after I/O error, timeout, or EOF. VSTREAM
+ * exceptions must be enabled in advance with smtp_stream_setup().
+ */
+ if (vstream_peek(state->client) == 0
+ && peekfd(vstream_fileno(state->client)) > 0)
+ (void) vstream_ungetc(state->client, smtp_fgetc(state->client));
+ if (vstream_peek(state->client) > 0) {
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+ escape(state->expand_buf, vstream_peek_data(state->client),
+ vstream_peek(state->client) < 100 ?
+ vstream_peek(state->client) : 100);
+ msg_info("improper command pipelining after %s from %s: %s",
+ state->where, state->namaddr, STR(state->expand_buf));
+ state->flags |= SMTPD_FLAG_ILL_PIPELINING;
+ return (1);
+ }
+ return (0);
+}
+
+/* smtpd_proto - talk the SMTP protocol */
+
+static void smtpd_proto(SMTPD_STATE *state)
+{
+ int argc;
+ SMTPD_TOKEN *argv;
+ SMTPD_CMD *cmdp;
+ const char *ehlo_words;
+ const char *err;
+ int status;
+ const char *cp;
+
+#ifdef USE_TLS
+ int tls_rate;
+
+#endif
+
+ /*
+ * Print a greeting banner and run the state machine. Read SMTP commands
+ * one line at a time. According to the standard, a sender or recipient
+ * address could contain an escaped newline. I think this is perverse,
+ * and anyone depending on this is really asking for trouble.
+ *
+ * In case of mail protocol trouble, the program jumps back to this place,
+ * so that it can perform the necessary cleanup before talking to the
+ * next client. The setjmp/longjmp primitives are like a sharp tool: use
+ * with care. I would certainly recommend against the use of
+ * setjmp/longjmp in programs that change privilege levels.
+ *
+ * In case of file system trouble the program terminates after logging the
+ * error and after informing the client. In all other cases (out of
+ * memory, panic) the error is logged, and the msg_cleanup() exit handler
+ * cleans up, but no attempt is made to inform the client of the nature
+ * of the problem.
+ *
+ * With deadlines enabled, do not increase the time budget while receiving a
+ * command, because that would give an attacker too much time.
+ */
+ vstream_control(state->client, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END);
+ while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE)
+ /* void */ ;
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, 0);
+ switch (status) {
+
+ default:
+ msg_panic("smtpd_proto: unknown error reading from %s",
+ state->namaddr);
+ break;
+
+ case SMTP_ERR_TIME:
+ state->reason = REASON_TIMEOUT;
+ if (vstream_setjmp(state->client) == 0)
+ smtpd_chat_reply(state, "421 4.4.2 %s Error: timeout exceeded",
+ var_myhostname);
+ break;
+
+ case SMTP_ERR_EOF:
+ state->reason = REASON_LOST_CONNECTION;
+ break;
+
+ case SMTP_ERR_QUIET:
+ break;
+
+ case SMTP_ERR_DATA:
+ msg_info("%s: reject: %s from %s: "
+ "421 4.3.0 %s Server local data error",
+ (state->queue_id ? state->queue_id : "NOQUEUE"),
+ state->where, state->namaddr, var_myhostname);
+ state->error_mask |= MAIL_ERROR_DATA;
+ if (vstream_setjmp(state->client) == 0)
+ smtpd_chat_reply(state, "421 4.3.0 %s Server local data error",
+ var_myhostname);
+ break;
+
+ case 0:
+
+ /*
+ * Don't bother doing anything if some pre-SMTP handshake (haproxy)
+ * did not work out.
+ */
+ if (state->flags & SMTPD_FLAG_HANGUP) {
+ smtpd_chat_reply(state, "421 4.3.0 %s Server local error",
+ var_myhostname);
+ break;
+ }
+
+ /*
+ * In TLS wrapper mode, turn on TLS using code that is shared with
+ * the STARTTLS command. This code does not return when the handshake
+ * fails.
+ *
+ * Enforce TLS handshake rate limit when this client negotiated too many
+ * new TLS sessions in the recent past.
+ *
+ * XXX This means we don't complete a TLS handshake just to tell the
+ * client that we don't provide service. TLS wrapper mode is
+ * obsolete, so we don't have to provide perfect support.
+ */
+#ifdef USE_TLS
+ if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode
+ && state->tls_context == 0) {
+#ifdef USE_TLSPROXY
+ /* We garbage-collect the VSTREAM in smtpd_state_reset() */
+ state->tlsproxy =
+ tls_proxy_legacy_open(var_tlsproxy_service,
+ PROXY_OPEN_FLAGS,
+ state->client, state->addr,
+ state->port, var_smtpd_tmout,
+ state->service);
+ if (state->tlsproxy == 0) {
+ msg_warn("Wrapper-mode request dropped from %s for service %s."
+ " TLS context initialization failed. For details see"
+ " earlier warnings in your logs.",
+ state->namaddr, state->service);
+ break;
+ }
+#else /* USE_TLSPROXY */
+ if (smtpd_tls_ctx == 0) {
+ msg_warn("Wrapper-mode request dropped from %s for service %s."
+ " TLS context initialization failed. For details see"
+ " earlier warnings in your logs.",
+ state->namaddr, state->service);
+ break;
+ }
+#endif /* USE_TLSPROXY */
+ if (var_smtpd_cntls_limit > 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls_stat(anvil_clnt, state->service,
+ state->addr, &tls_rate) == ANVIL_STAT_OK
+ && tls_rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Refusing TLS service request from %s for service %s",
+ state->namaddr, state->service);
+ break;
+ }
+ smtpd_start_tls(state);
+ }
+#endif
+
+ /*
+ * If the client spoke before the server sends the initial greeting,
+ * raise a flag and log the content of the protocol violation. This
+ * check MUST NOT apply to TLS wrappermode connections.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && vstream_context(state->client) == 0 /* not postscreen */
+ && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0
+ && smtpd_flag_ill_pipelining(state)
+ && var_smtpd_forbid_unauth_pipe) {
+ smtpd_chat_reply(state,
+ "554 5.5.0 Error: SMTP protocol synchronization");
+ break;
+ }
+
+ /*
+ * XXX The client connection count/rate control must be consistent in
+ * its use of client address information in connect and disconnect
+ * events. For now we exclude xclient authorized hosts from
+ * connection count/rate control.
+ *
+ * XXX Must send connect/disconnect events to the anvil server even when
+ * this service is not connection count or rate limited, otherwise it
+ * will discard client message or recipient rate information too
+ * early or too late.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_connect(anvil_clnt, state->service, state->addr,
+ &state->conn_count, &state->conn_rate)
+ == ANVIL_STAT_OK) {
+ if (var_smtpd_cconn_limit > 0
+ && state->conn_count > var_smtpd_cconn_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Connection concurrency limit exceeded: %d from %s for service %s",
+ state->conn_count, state->namaddr, state->service);
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s",
+ var_myhostname, state->addr);
+ break;
+ }
+ if (var_smtpd_crate_limit > 0
+ && state->conn_rate > var_smtpd_crate_limit) {
+ msg_warn("Connection rate limit exceeded: %d from %s for service %s",
+ state->conn_rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s",
+ var_myhostname, state->addr);
+ break;
+ }
+ }
+
+ /*
+ * Determine what server ESMTP features to suppress, typically to
+ * avoid inter-operability problems. Moved up so we don't send 421
+ * immediately after sending the initial server response.
+ */
+ if (ehlo_discard_maps == 0
+ || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0)
+ ehlo_words = var_smtpd_ehlo_dis_words;
+ state->ehlo_discard_mask = ehlo_mask(ehlo_words);
+
+ /* XXX We use the real client for connect access control. */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_client(state)) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ state->access_denied = mystrdup(err);
+ smtpd_chat_reply(state, "%s", state->access_denied);
+ state->error_count++;
+ }
+
+ /*
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses
+ * other than the initial greeting and any response to HELO or EHLO
+ * are prefaced with a status code as defined in RFC 3463.
+ */
+
+ /*
+ * XXX If a Milter rejects CONNECT, reply with 220 except in case of
+ * hard reject or 421 (disconnect). The reply persists so it will
+ * apply to MAIL FROM and to other commands such as AUTH, STARTTLS,
+ * and VRFY. Note: after a Milter CONNECT reject, we must not reject
+ * HELO or EHLO, but we do change the feature list that is announced
+ * in the EHLO response.
+ */
+ else {
+ err = 0;
+ if (state->milters != 0) {
+ milter_macro_callback(state->milters, smtpd_milter_eval,
+ (void *) state);
+ if ((err = milter_conn_event(state->milters, state->name,
+ state->addr,
+ strcmp(state->port, CLIENT_PORT_UNKNOWN) ?
+ state->port : "0",
+ state->addr_family)) != 0)
+ err = check_milter_reply(state, err);
+ }
+ if (err && err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "554 %s ESMTP not accepting connections",
+ var_myhostname);
+ state->error_count++;
+ } else if (err && strncmp(err, "421", 3) == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "421 %s Service unavailable - try again later",
+ var_myhostname);
+ /* Not: state->error_count++; */
+ } else {
+ smtpd_chat_reply(state, "220 %s", var_smtpd_banner);
+ }
+ }
+
+ /*
+ * SASL initialization for plaintext mode.
+ *
+ * XXX Backwards compatibility: allow AUTH commands when the AUTH
+ * announcement is suppressed via smtpd_sasl_exceptions_networks.
+ *
+ * XXX Safety: don't enable SASL with "smtpd_tls_auth_only = yes" and
+ * non-TLS build.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable && smtpd_sasl_is_active(state) == 0
+#ifdef USE_TLS
+ && state->tls_context == 0 && !var_smtpd_tls_auth_only
+#else
+ && var_smtpd_tls_auth_only == 0
+#endif
+ )
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+#endif
+
+ /*
+ * The command read/execute loop.
+ */
+ for (;;) {
+ if (state->flags & SMTPD_FLAG_HANGUP)
+ break;
+ smtp_stream_setup(state->client, var_smtpd_tmout,
+ var_smtpd_req_deadline, 0);
+ if (state->error_count >= var_smtpd_hard_erlim) {
+ state->reason = REASON_ERROR_LIMIT;
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many errors",
+ var_myhostname);
+ break;
+ }
+ watchdog_pat();
+ smtpd_chat_query(state);
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) {
+ log_whatsup(state, "reject", "bare <LF> received");
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
+ var_smtpd_forbid_bare_lf_code, var_myhostname);
+ break;
+ }
+ /* Safety: protect internal interfaces against malformed UTF-8. */
+ if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer),
+ LEN(state->buffer)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax");
+ state->error_count++;
+ continue;
+ }
+ /* Move into smtpd_chat_query() and update session transcript. */
+ if (smtpd_cmd_filter != 0) {
+ for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++)
+ /* void */ ;
+ if ((cp = dict_get(smtpd_cmd_filter, cp)) != 0) {
+ msg_info("%s: replacing command \"%.100s\" with \"%.100s\"",
+ state->namaddr, STR(state->buffer), cp);
+ vstring_strcpy(state->buffer, cp);
+ } else if (smtpd_cmd_filter->error != 0) {
+ msg_warn("%s:%s lookup error for \"%.100s\"",
+ smtpd_cmd_filter->type, smtpd_cmd_filter->name,
+ printable(STR(state->buffer), '?'));
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+ }
+ }
+ if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 5.5.2 Error: bad syntax");
+ state->error_count++;
+ continue;
+ }
+ /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */
+ if (*var_smtpd_noop_cmds
+ && string_list_match(smtpd_noop_cmds, argv[0].strval)) {
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ if (state->junk_cmds++ > var_smtpd_junk_cmd_limit)
+ state->error_count++;
+ continue;
+ }
+ for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(argv[0].strval, cmdp->name) == 0)
+ break;
+ cmdp->total_count += 1;
+ /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */
+ if (cmdp->name == 0) {
+ state->where = SMTPD_CMD_UNKNOWN;
+ if (is_header(argv[0].strval)
+ || (*var_smtpd_forbid_cmds
+ && string_list_match(smtpd_forbid_cmds, argv[0].strval))) {
+ VSTRING *escape_buf = vstring_alloc(100);
+
+ msg_warn("non-SMTP command from %s: %.100s",
+ state->namaddr,
+ vstring_str(escape(escape_buf,
+ vstring_str(state->buffer),
+ VSTRING_LEN(state->buffer))));
+ smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye.");
+ vstring_free(escape_buf);
+ break;
+ }
+ }
+ /* XXX We use the real client for connect access control. */
+ if (state->access_denied && cmdp->action != quit_cmd) {
+ /* XXX Exception for Milter override. */
+ if (strncmp(state->access_denied + 1, "21", 2) == 0) {
+ smtpd_chat_reply(state, "%s", state->access_denied);
+ continue;
+ }
+ smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s",
+ state->namaddr); /* RFC 2821 Sec 3.1 */
+ state->error_count++;
+ continue;
+ }
+ /* state->access_denied == 0 || cmdp->action == quit_cmd */
+ if (cmdp->name == 0) {
+ if (state->milters != 0
+ && (err = milter_unknown_event(state->milters,
+ argv[0].strval)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ } else
+ smtpd_chat_reply(state, "500 5.5.2 Error: command not recognized");
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ state->error_count++;
+ continue;
+ }
+#ifdef USE_TLS
+ if (var_smtpd_enforce_tls &&
+ !state->tls_context &&
+ (cmdp->flags & SMTPD_CMD_FLAG_PRE_TLS) == 0) {
+ smtpd_chat_reply(state,
+ "530 5.7.0 Must issue a STARTTLS command first");
+ state->error_count++;
+ continue;
+ }
+#endif
+ state->where = cmdp->name;
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0
+ || (cmdp->flags & SMTPD_CMD_FLAG_LAST))
+ && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0
+ && smtpd_flag_ill_pipelining(state)
+ && var_smtpd_forbid_unauth_pipe) {
+ smtpd_chat_reply(state,
+ "554 5.5.0 Error: SMTP protocol synchronization");
+ break;
+ }
+ if (cmdp->action(state, argc, argv) != 0)
+ state->error_count++;
+ else
+ cmdp->success_count += 1;
+ if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT)
+ && state->junk_cmds++ > var_smtpd_junk_cmd_limit)
+ state->error_count++;
+ if (cmdp->action == quit_cmd)
+ break;
+ }
+ break;
+ }
+
+ /*
+ * XXX The client connection count/rate control must be consistent in its
+ * use of client address information in connect and disconnect events.
+ * For now we exclude xclient authorized hosts from connection count/rate
+ * control.
+ *
+ * XXX Must send connect/disconnect events to the anvil server even when
+ * this service is not connection count or rate limited, otherwise it
+ * will discard client message or recipient rate information too early or
+ * too late.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr))
+ anvil_clnt_disconnect(anvil_clnt, state->service, state->addr);
+
+ /*
+ * Log abnormal session termination, in case postmaster notification has
+ * been turned off. In the log, indicate the last recognized state before
+ * things went wrong. Don't complain about clients that go away without
+ * sending QUIT. Log the byte count after DATA to help diagnose MTU
+ * troubles.
+ */
+ if (state->reason && state->where) {
+ if (strcmp(state->where, SMTPD_AFTER_DATA) == 0) {
+ msg_info("%s after %s (%lu bytes) from %s", /* 2.5 compat */
+ state->reason, SMTPD_CMD_DATA, /* 2.5 compat */
+ (long) (state->act_size + vstream_peek(state->client)),
+ state->namaddr);
+ } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) {
+ msg_info("%s after %s (%lu bytes) from %s",
+ state->reason, SMTPD_CMD_BDAT,
+ (long) (state->act_size + VSTRING_LEN(state->buffer)
+ + VSTRING_LEN(state->bdat_get_buffer)),
+ state->namaddr);
+ } else if (strcmp(state->where, SMTPD_AFTER_EOM)
+ || strcmp(state->reason, REASON_LOST_CONNECTION)) {
+ msg_info("%s after %s from %s",
+ state->reason, state->where, state->namaddr);
+ }
+ }
+
+ /*
+ * Cleanup whatever information the client gave us during the SMTP
+ * dialog.
+ *
+ * XXX Duplicated in xclient_cmd().
+ */
+#ifdef USE_TLS
+ tls_reset(state);
+#endif
+ helo_reset(state);
+#ifdef USE_SASL_AUTH
+ smtpd_sasl_auth_reset(state);
+ if (smtpd_sasl_is_active(state)) {
+ smtpd_sasl_deactivate(state);
+ }
+#endif
+ chat_reset(state, 0);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (state->milters)
+ milter_disc_event(state->milters);
+}
+
+/* smtpd_format_cmd_stats - format per-command statistics */
+
+static char *smtpd_format_cmd_stats(VSTRING *buf)
+{
+ SMTPD_CMD *cmdp;
+ int all_success = 0;
+ int all_total = 0;
+
+ /*
+ * Log the statistics. Note that this loop produces no output when no
+ * command was received. We address that after the loop.
+ */
+ VSTRING_RESET(buf);
+ for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) {
+ if (cmdp->total_count > 0) {
+ vstring_sprintf_append(buf, " %s=%d",
+ cmdp->name ? cmdp->name : "unknown",
+ cmdp->success_count);
+ if (cmdp->success_count != cmdp->total_count)
+ vstring_sprintf_append(buf, "/%d", cmdp->total_count);
+ all_success += cmdp->success_count;
+ all_total += cmdp->total_count;
+ }
+ if (cmdp->name == 0)
+ break;
+ }
+
+ /*
+ * Reset the per-command counters.
+ *
+ * Fix 20190621: the command counter resetting code was moved from the SMTP
+ * protocol handler to this place, because the protocol handler was never
+ * called after HaProxy handshake error, causing stale numbers to be
+ * logged.
+ */
+ for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) {
+ cmdp->success_count = cmdp->total_count = 0;
+ if (cmdp->name == 0)
+ break;
+ }
+
+ /*
+ * Log total numbers, so that logfile analyzers will see something even
+ * if the above loop produced no output. When no commands were received
+ * log "0/0" to simplify the identification of abnormal sessions: any
+ * statistics with [0-9]/ indicate that there was a problem.
+ */
+ vstring_sprintf_append(buf, " commands=%d", all_success);
+ if (all_success != all_total || all_total == 0)
+ vstring_sprintf_append(buf, "/%d", all_total);
+ return (lowercase(STR(buf)));
+}
+
+/* setup_milters - set up Milters after a connection is established */
+
+static void setup_milters(SMTPD_STATE *state)
+{
+ const char *milter_string;
+
+ /*
+ * Postcondition: either state->milters is set, or the
+ * INPUT_TRANSP_MILTER flag is passed down-stream.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (smtpd_input_transp_mask & INPUT_TRANSP_MILTER) == 0
+ && ((smtpd_milter_maps
+ && (milter_string =
+ maps_find(smtpd_milter_maps, state->addr, 0)) != 0)
+ || *(milter_string = var_smtpd_milters) != 0)
+ && strcasecmp(milter_string, SMTPD_MILTERS_DISABLE) != 0) {
+ state->milters = milter_create(milter_string,
+ 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);
+ }
+
+ /*
+ * Safety: disable non_smtpd_milters when not sending our own mail filter
+ * list. Otherwise the next stage could handle this message as a local
+ * submission.
+ */
+ if (state->milters == 0)
+ smtpd_input_transp_mask |= INPUT_TRANSP_MILTER;
+}
+
+/* teardown_milters - release resources */
+
+static void teardown_milters(SMTPD_STATE *state)
+{
+ if (state->milters) {
+ milter_free(state->milters);
+ state->milters = 0;
+ }
+ smtpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+}
+
+
+/* smtpd_service - service one client */
+
+static void smtpd_service(VSTREAM *stream, char *service, char **argv)
+{
+ SMTPD_STATE state;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (SMTPD_STAND_ALONE_STREAM(stream) == 0
+ && inet_proto_info()->ai_family_list[0] == 0)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This routine runs when a client has connected to our network port, or
+ * when the smtp server is run in stand-alone mode (input from pipe).
+ *
+ * Look up and sanitize the peer name, then initialize some connection-
+ * specific state. When the name service is hosed, hostname lookup will
+ * take a while. This is why I always run a local name server on critical
+ * machines.
+ */
+ smtpd_state_init(&state, stream, service);
+ msg_info("connect from %s", state.namaddr);
+
+ /*
+ * Disable TLS when running in stand-alone mode via "sendmail -bs".
+ */
+ if (SMTPD_STAND_ALONE((&state))) {
+ var_smtpd_use_tls = 0;
+ var_smtpd_enforce_tls = 0;
+ var_smtpd_tls_auth_only = 0;
+ }
+
+ /*
+ * XCLIENT must not override its own access control.
+ */
+ xclient_allowed = SMTPD_STAND_ALONE((&state)) == 0 &&
+ namadr_list_match(xclient_hosts, state.name, state.addr);
+
+ /*
+ * Overriding XFORWARD access control makes no sense, either.
+ */
+ xforward_allowed = SMTPD_STAND_ALONE((&state)) == 0 &&
+ namadr_list_match(xforward_hosts, state.name, state.addr);
+
+ /*
+ * Reject or normalize bare LF, with compatibility exclusions.
+ */
+ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((&state)) == 0 && bare_lf_mask
+ && !namadr_list_match(bare_lf_excl, state.name, state.addr)) ?
+ bare_lf_mask : 0;
+
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+ debug_peer_check(state.name, state.addr);
+
+ /*
+ * Set up Milters, or disable Milters down-stream.
+ */
+ setup_milters(&state); /* duplicates xclient_cmd */
+
+ /*
+ * Provide the SMTP service.
+ */
+ smtpd_proto(&state);
+
+ /*
+ * After the client has gone away, clean up whatever we have set up at
+ * connection time.
+ */
+ msg_info("disconnect from %s%s", state.namaddr,
+ smtpd_format_cmd_stats(state.buffer));
+ teardown_milters(&state); /* duplicates xclient_cmd */
+ smtpd_state_reset(&state);
+ debug_peer_restore();
+}
+
+/* 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);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize denylist/etc. patterns before entering the chroot jail, in
+ * case they specify a filename pattern.
+ */
+ smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN,
+ var_smtpd_noop_cmds);
+ smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS,
+ MATCH_FLAG_RETURN,
+ var_smtpd_forbid_cmds);
+ verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN,
+ var_verp_clients);
+ xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN,
+ var_xclient_hosts);
+ xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN,
+ var_xforward_hosts);
+ hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_SMTPD_HOGGERS),
+ var_smtpd_hoggers);
+ bare_lf_excl = namadr_list_init(VAR_SMTPD_FORBID_BARE_LF_EXCL,
+ MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS),
+ var_smtpd_forbid_bare_lf_excl);
+ if ((bare_lf_mask = name_code(bare_lf_mask_table, NAME_CODE_FLAG_NONE,
+ var_smtpd_forbid_bare_lf)) < 0)
+ msg_fatal("bad parameter value: '%s = %s'",
+ VAR_SMTPD_FORBID_BARE_LF, var_smtpd_forbid_bare_lf);
+
+ /*
+ * Open maps before dropping privileges so we can read passwords etc.
+ *
+ * XXX We should not do this in stand-alone (sendmail -bs) mode, but we
+ * can't use SMTPD_STAND_ALONE(state) here. This means "sendmail -bs"
+ * will try to connect to proxymap when invoked by root for mail
+ * submission. To fix, we would have to pass stand-alone mode information
+ * via different means. For now we have to tell people not to run mail
+ * clients as root.
+ */
+ if (getuid() == 0 || getuid() == var_owner_uid)
+ smtpd_check_init();
+ smtpd_expand_init();
+ debug_peer_init();
+
+ if (var_smtpd_sasl_enable)
+#ifdef USE_SASL_AUTH
+ smtpd_sasl_initialize();
+
+ if (*var_smtpd_sasl_exceptions_networks)
+ sasl_exceptions_networks =
+ namadr_list_init(VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS,
+ MATCH_FLAG_RETURN,
+ var_smtpd_sasl_exceptions_networks);
+#else
+ msg_warn("%s is true, but SASL support is not compiled in",
+ VAR_SMTPD_SASL_ENABLE);
+#endif
+
+ if (*var_smtpd_cmd_filter)
+ smtpd_cmd_filter = dict_open(var_smtpd_cmd_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * XXX Temporary fix to pretend that we consistently implement TLS
+ * security levels. We implement only a subset for now. If we implement
+ * more levels, wrappermode should override only weaker TLS security
+ * levels.
+ *
+ * Note: tls_level_lookup() logs no warning.
+ */
+ if (!var_smtpd_tls_wrappermode && *var_smtpd_tls_level) {
+ switch (tls_level_lookup(var_smtpd_tls_level)) {
+ default:
+ msg_fatal("Invalid TLS level \"%s\"", var_smtpd_tls_level);
+ /* NOTREACHED */
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
+ VAR_SMTPD_TLS_LEVEL, var_smtpd_tls_level);
+ /* FALLTHROUGH */
+ case TLS_LEV_ENCRYPT:
+ var_smtpd_enforce_tls = var_smtpd_use_tls = 1;
+ break;
+ case TLS_LEV_MAY:
+ var_smtpd_enforce_tls = 0;
+ var_smtpd_use_tls = 1;
+ break;
+ case TLS_LEV_NONE:
+ var_smtpd_enforce_tls = var_smtpd_use_tls = 0;
+ break;
+ }
+ }
+
+ /*
+ * With TLS wrapper mode, we run on a dedicated port and turn on TLS
+ * before actually speaking the SMTP protocol. This implies TLS enforce
+ * mode.
+ *
+ * With non-wrapper mode, TLS enforce mode implies that we don't advertise
+ * AUTH before the client issues STARTTLS.
+ */
+ var_smtpd_enforce_tls = var_smtpd_tls_wrappermode || var_smtpd_enforce_tls;
+ var_smtpd_tls_auth_only = var_smtpd_tls_auth_only || var_smtpd_enforce_tls;
+ var_smtpd_use_tls = var_smtpd_use_tls || var_smtpd_enforce_tls;
+
+ /*
+ * Keys can only be loaded when running with suitable permissions. When
+ * called from "sendmail -bs" this is not the case, so we must not
+ * announce STARTTLS support.
+ */
+ if (getuid() == 0 || getuid() == var_owner_uid) {
+ if (var_smtpd_use_tls) {
+#ifdef USE_TLS
+#ifndef USE_TLSPROXY
+ TLS_SERVER_INIT_PROPS props;
+ const char *cert_file;
+ int have_server_cert;
+ int no_server_cert_ok;
+ int require_server_cert;
+
+ /*
+ * Can't use anonymous ciphers if we want client certificates.
+ * Must use anonymous ciphers if we have no certificates.
+ *
+ * XXX: Ugh! Too many booleans!
+ */
+ ask_client_cert = require_server_cert =
+ (var_smtpd_tls_ask_ccert
+ || (var_smtpd_enforce_tls && var_smtpd_tls_req_ccert));
+ if (strcasecmp(var_smtpd_tls_cert_file, "none") == 0) {
+ no_server_cert_ok = 1;
+ cert_file = "";
+ } else {
+ no_server_cert_ok = 0;
+ cert_file = var_smtpd_tls_cert_file;
+ }
+
+ have_server_cert = *cert_file != 0;
+ have_server_cert |= *var_smtpd_tls_eccert_file != 0;
+ have_server_cert |= *var_smtpd_tls_dcert_file != 0;
+
+ if (*var_smtpd_tls_chain_files != 0) {
+ if (!have_server_cert)
+ have_server_cert = 1;
+ else
+ msg_warn("Both %s and one or more of the legacy "
+ " %s, %s or %s are non-empty; the legacy "
+ " parameters will be ignored",
+ VAR_SMTPD_TLS_CHAIN_FILES,
+ VAR_SMTPD_TLS_CERT_FILE,
+ VAR_SMTPD_TLS_ECCERT_FILE,
+ VAR_SMTPD_TLS_DCERT_FILE);
+ }
+ /* Some TLS configuration errors are not show stoppers. */
+ if (!have_server_cert && require_server_cert)
+ msg_warn("Need a server cert to request client certs");
+ if (!var_smtpd_enforce_tls && var_smtpd_tls_req_ccert)
+ msg_warn("Can't require client certs unless TLS is required");
+ /* After a show-stopper error, reply with 454 to STARTTLS. */
+ if (have_server_cert
+ || (no_server_cert_ok && !require_server_cert)) {
+
+ tls_pre_jail_init(TLS_ROLE_SERVER);
+
+ /*
+ * Large parameter lists are error-prone, so we emulate a
+ * language feature that C does not have natively: named
+ * parameter lists.
+ */
+ smtpd_tls_ctx =
+ TLS_SERVER_INIT(&props,
+ log_param = VAR_SMTPD_TLS_LOGLEVEL,
+ log_level = var_smtpd_tls_loglevel,
+ verifydepth = var_smtpd_tls_ccert_vd,
+ cache_type = TLS_MGR_SCACHE_SMTPD,
+ set_sessid = var_smtpd_tls_set_sessid,
+ chain_files = var_smtpd_tls_chain_files,
+ cert_file = cert_file,
+ key_file = var_smtpd_tls_key_file,
+ dcert_file = var_smtpd_tls_dcert_file,
+ dkey_file = var_smtpd_tls_dkey_file,
+ eccert_file = var_smtpd_tls_eccert_file,
+ eckey_file = var_smtpd_tls_eckey_file,
+ CAfile = var_smtpd_tls_CAfile,
+ CApath = var_smtpd_tls_CApath,
+ dh1024_param_file
+ = var_smtpd_tls_dh1024_param_file,
+ dh512_param_file
+ = var_smtpd_tls_dh512_param_file,
+ eecdh_grade = var_smtpd_tls_eecdh,
+ protocols = var_smtpd_enforce_tls ?
+ var_smtpd_tls_mand_proto :
+ var_smtpd_tls_proto,
+ ask_ccert = ask_client_cert,
+ mdalg = var_smtpd_tls_fpt_dgst);
+ } else {
+ msg_warn("No server certs available. TLS won't be enabled");
+ }
+#endif /* USE_TLSPROXY */
+#else
+ msg_warn("TLS has been selected, but TLS support is not compiled in");
+#endif
+ }
+ }
+
+ /*
+ * flush client.
+ */
+ flush_init();
+
+ /*
+ * EHLO keyword filter.
+ */
+ if (*var_smtpd_ehlo_dis_maps)
+ ehlo_discard_maps = maps_create(VAR_SMTPD_EHLO_DIS_MAPS,
+ var_smtpd_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * Per-client Milter support.
+ */
+ if (*var_smtpd_milter_maps)
+ smtpd_milter_maps = maps_create(VAR_SMTPD_MILTER_MAPS,
+ var_smtpd_milter_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * DNS reply filter.
+ */
+ if (*var_smtpd_dns_re_filter)
+ dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER,
+ var_smtpd_dns_re_filter);
+
+ /*
+ * Reject footer.
+ */
+ if (*var_smtpd_rej_ftr_maps)
+ smtpd_chat_pre_jail_init();
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize the receive transparency options: do we want unknown
+ * recipient checks, address mapping, header_body_checks?.
+ */
+ smtpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+
+ /*
+ * Initialize before-queue filter options: do we want speed-matching
+ * support so that the entire message is received before we contact a
+ * before-queue content filter?
+ */
+ if (*var_smtpd_proxy_filt)
+ smtpd_proxy_opts =
+ smtpd_proxy_parse_opts(VAR_SMTPD_PROXY_OPTS, var_smtpd_proxy_opts);
+
+ /*
+ * Sanity checks. The queue_minfree value should be at least as large as
+ * (process_limit * message_size_limit) but that is unpractical, so we
+ * arbitrarily pick a small multiple of the per-message size limit. This
+ * helps to avoid many unneeded (re)transmissions.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_queue_minfree)
+ && ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_queue_minfree / 1.5 < var_message_limit)
+ msg_warn("%s(%lu) should be at least 1.5*%s(%lu)",
+ VAR_QUEUE_MINFREE, (unsigned long) var_queue_minfree,
+ VAR_MESSAGE_LIMIT, (unsigned long) var_message_limit);
+
+ /*
+ * Connection rate management.
+ */
+ if (var_smtpd_crate_limit || var_smtpd_cconn_limit
+ || var_smtpd_cmail_limit || var_smtpd_crcpt_limit
+ || var_smtpd_cntls_limit || var_smtpd_cauth_limit)
+ anvil_clnt = anvil_clnt_create();
+
+ /*
+ * header_from_format support, for postmaster notifications.
+ */
+ smtpd_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_NINT_TABLE nint_table[] = {
+ VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0,
+ VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0,
+ VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0,
+ VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0,
+ VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0,
+ VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0,
+ VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0,
+ VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, 0, 0,
+ VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, 0, 0,
+ VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, 0, 0,
+ VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code, 0, 0,
+ VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code, 0, 0,
+ VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, 0, 0,
+ VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, 0, 0,
+ VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0,
+ VAR_SMTPD_RCPT_OVERLIM, DEF_SMTPD_RCPT_OVERLIM, &var_smtpd_rcpt_overlim, 1, 0,
+ VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0,
+ VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode, 200, 599,
+ VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode, 200, 599,
+ VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode, 200, 499,
+ VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode, 200, 499,
+ VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code, 0, 0,
+ VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code, 0, 0,
+ VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, 0, 0,
+ VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0,
+ VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0,
+ VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, 0, 0,
+ VAR_SMTPD_FORBID_BARE_LF_CODE, DEF_SMTPD_FORBID_BARE_LF_CODE, &var_smtpd_forbid_bare_lf_code, 500, 599,
+ VAR_SMTPD_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0,
+ VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
+ VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0,
+ VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0,
+ VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0,
+ VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0,
+#endif
+ VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0,
+ VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0,
+ VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0,
+ VAR_SMTPD_MIN_DATA_RATE, DEF_SMTPD_MIN_DATA_RATE, &var_smtpd_min_data_rate, 1, 0,
+ 0,
+ };
+ static const CONFIG_LONG_TABLE long_table[] = {
+ VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0,
+ VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0,
+ VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0,
+ VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0,
+ VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, &var_smtpd_policy_tmout, 1, 0,
+ VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, &var_smtpd_policy_idle, 1, 0,
+ VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, &var_smtpd_policy_ttl, 1, 0,
+#ifdef USE_TLS
+ VAR_SMTPD_STARTTLS_TMOUT, DEF_SMTPD_STARTTLS_TMOUT, &var_smtpd_starttls_tmout, 1, 0,
+#endif
+ 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,
+ VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0,
+ VAR_SMTPD_UPROXY_TMOUT, DEF_SMTPD_UPROXY_TMOUT, &var_smtpd_uproxy_tmout, 1, 0,
+ VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, &var_smtpd_policy_try_delay, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
+ VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env,
+ VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd,
+ VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
+ VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable,
+ VAR_SMTPD_SASL_AUTH_HDR, DEF_SMTPD_SASL_AUTH_HDR, &var_smtpd_sasl_auth_hdr,
+ VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from,
+ VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt,
+ VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls,
+ VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
+ VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode,
+ VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only,
+#ifdef USE_TLS
+ VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
+ VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header,
+ VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
+#endif
+ VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
+ VAR_SMTPD_DELAY_OPEN, DEF_SMTPD_DELAY_OPEN, &var_smtpd_delay_open,
+ VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
+ VAR_SMTPD_FORBID_UNAUTH_PIPE, DEF_SMTPD_FORBID_UNAUTH_PIPE, &var_smtpd_forbid_unauth_pipe,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_RELAY_BEFORE_RCPT_CHECKS, DEF_RELAY_BEFORE_RCPT_CHECKS, &var_relay_before_rcpt_checks,
+ VAR_SMTPD_REQ_DEADLINE, DEF_SMTPD_REQ_DEADLINE, &var_smtpd_req_deadline,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0,
+ VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0,
+ VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0,
+ VAR_RELAY_CHECKS, DEF_RELAY_CHECKS, &var_relay_checks, 0, 0,
+ VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0,
+ VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0,
+ VAR_DATA_CHECKS, DEF_DATA_CHECKS, &var_data_checks, 0, 0,
+ VAR_EOD_CHECKS, DEF_EOD_CHECKS, &var_eod_checks, 0, 0,
+ VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0,
+ VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
+ 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_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
+ VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
+ VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0,
+ VAR_SMTPD_SASL_PATH, DEF_SMTPD_SASL_PATH, &var_smtpd_sasl_path, 1, 0,
+ VAR_SMTPD_SASL_SERVICE, DEF_SMTPD_SASL_SERVICE, &var_smtpd_sasl_service, 1, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0,
+ VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0,
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0,
+ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
+ VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0,
+ VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
+ VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0,
+ VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
+ VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0,
+ VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0,
+ VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0,
+ VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0,
+ VAR_SMTPD_PROXY_OPTS, DEF_SMTPD_PROXY_OPTS, &var_smtpd_proxy_opts, 0, 0,
+ VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ VAR_XCLIENT_HOSTS, DEF_XCLIENT_HOSTS, &var_xclient_hosts, 0, 0,
+ VAR_XFORWARD_HOSTS, DEF_XFORWARD_HOSTS, &var_xforward_hosts, 0, 0,
+ VAR_SMTPD_HOGGERS, DEF_SMTPD_HOGGERS, &var_smtpd_hoggers, 0, 0,
+ VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, 0, 0,
+ VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0,
+ VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0,
+#ifdef USE_TLS
+ VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0,
+ VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0,
+ VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0,
+ VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0,
+ VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0,
+ VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0,
+ VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0,
+ VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0,
+ VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0,
+ VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0,
+ VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0,
+ VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0,
+ VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0,
+ VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0,
+ VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0,
+ VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0,
+ VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0,
+ VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0,
+ VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0,
+ VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0,
+ VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0,
+ VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
+#endif
+ VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0,
+ VAR_SMTPD_SASL_TYPE, DEF_SMTPD_SASL_TYPE, &var_smtpd_sasl_type, 1, 0,
+ VAR_SMTPD_SASL_MECH_FILTER, DEF_SMTPD_SASL_MECH_FILTER, &var_smtpd_sasl_mech_filter, 0, 0,
+ VAR_SMTPD_MILTERS, DEF_SMTPD_MILTERS, &var_smtpd_milters, 0, 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_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_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0,
+ VAR_SMTPD_MILTER_MAPS, DEF_SMTPD_MILTER_MAPS, &var_smtpd_milter_maps, 0, 0,
+ VAR_STRESS, DEF_STRESS, &var_stress, 0, 0,
+ VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0,
+ VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0,
+ VAR_REJECT_TMPF_ACT, DEF_REJECT_TMPF_ACT, &var_reject_tmpf_act, 1, 0,
+ VAR_UNK_NAME_TF_ACT, DEF_UNK_NAME_TF_ACT, &var_unk_name_tf_act, 1, 0,
+ VAR_UNK_ADDR_TF_ACT, DEF_UNK_ADDR_TF_ACT, &var_unk_addr_tf_act, 1, 0,
+ VAR_UNV_RCPT_TF_ACT, DEF_UNV_RCPT_TF_ACT, &var_unv_rcpt_tf_act, 1, 0,
+ VAR_UNV_FROM_TF_ACT, DEF_UNV_FROM_TF_ACT, &var_unv_from_tf_act, 1, 0,
+ VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0,
+#ifdef USE_TLSPROXY
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+#endif
+ VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, 0, 0,
+ VAR_SMTPD_UPROXY_PROTO, DEF_SMTPD_UPROXY_PROTO, &var_smtpd_uproxy_proto, 0, 0,
+ VAR_SMTPD_POLICY_DEF_ACTION, DEF_SMTPD_POLICY_DEF_ACTION, &var_smtpd_policy_def_action, 1, 0,
+ VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0,
+ VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0,
+ VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ VAR_SMTPD_FORBID_BARE_LF_EXCL, DEF_SMTPD_FORBID_BARE_LF_EXCL, &var_smtpd_forbid_bare_lf_excl, 0, 0,
+ VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0,
+ 0,
+ };
+ static const CONFIG_RAW_TABLE raw_table[] = {
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0,
+ VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply, 1, 0,
+ VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, smtpd_service,
+ CA_MAIL_SERVER_NINT_TABLE(nint_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_LONG_TABLE(long_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_RAW_TABLE(raw_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ 0);
+}
diff --git a/src/smtpd/smtpd.h b/src/smtpd/smtpd.h
new file mode 100644
index 0000000..0bb5bbb
--- /dev/null
+++ b/src/smtpd/smtpd.h
@@ -0,0 +1,446 @@
+/*++
+/* NAME
+/* smtpd 3h
+/* SUMMARY
+/* smtp server
+/* SYNOPSIS
+/* include "smtpd.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_stream.h>
+
+ /*
+ * Postfix TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * Milter library.
+ */
+#include <milter.h>
+
+ /*
+ * Variables that keep track of conversation state. There is only one SMTP
+ * conversation at a time, so the state variables can be made global. And
+ * some of this has to be global anyway, so that the run-time error handler
+ * can clean up in case of a fatal error deep down in some library routine.
+ */
+typedef struct SMTPD_DEFER {
+ int active; /* is this active */
+ VSTRING *reason; /* reason for deferral */
+ VSTRING *dsn; /* DSN detail */
+ int code; /* SMTP reply code */
+ int class; /* error notification class */
+} SMTPD_DEFER;
+
+typedef struct {
+ int flags; /* XFORWARD server state */
+ char *name; /* name for access control */
+ char *addr; /* address for access control */
+ char *port; /* port for logging */
+ char *namaddr; /* name[address]:port */
+ char *rfc_addr; /* address for RFC 2821 */
+ char *protocol; /* email protocol */
+ char *helo_name; /* helo/ehlo parameter */
+ char *ident; /* local message identifier */
+ char *domain; /* rewrite context */
+} SMTPD_XFORWARD_ATTR;
+
+typedef struct {
+ int flags; /* see below */
+ int err; /* cleanup server/queue file errors */
+ VSTREAM *client; /* SMTP client handle */
+ VSTRING *buffer; /* SMTP client buffer */
+ VSTRING *addr_buf; /* internalized address buffer */
+ char *service; /* for event rate control */
+ struct timeval arrival_time; /* start of MAIL FROM transaction */
+ char *name; /* verified client hostname */
+ char *reverse_name; /* unverified client hostname */
+ char *addr; /* client host address string */
+ char *port; /* port for logging */
+ char *namaddr; /* name[address]:port */
+ char *rfc_addr; /* address for RFC 2821 */
+ int addr_family; /* address family */
+ char *dest_addr; /* Dovecot AUTH, Milter {daemon_addr} */
+ char *dest_port; /* Milter {daemon_port} */
+ struct sockaddr_storage sockaddr; /* binary client endpoint */
+ SOCKADDR_SIZE sockaddr_len; /* binary client endpoint */
+ struct sockaddr_storage dest_sockaddr; /* binary local endpoint */
+ SOCKADDR_SIZE dest_sockaddr_len; /* binary local endpoint */
+ int name_status; /* 2=ok 4=soft 5=hard 6=forged */
+ int reverse_name_status; /* 2=ok 4=soft 5=hard */
+ int conn_count; /* connections from this client */
+ int conn_rate; /* connection rate for this client */
+ int error_count; /* reset after DOT */
+ int error_mask; /* client errors */
+ int notify_mask; /* what to report to postmaster */
+ char *helo_name; /* client HELO/EHLO argument */
+ char *queue_id; /* from cleanup server/queue file */
+ VSTREAM *cleanup; /* cleanup server/queue file handle */
+ MAIL_STREAM *dest; /* another server/file handle */
+ int rcpt_count; /* number of accepted recipients */
+ char *access_denied; /* fixme */
+ ARGV *history; /* protocol transcript */
+ char *reason; /* cause of connection loss */
+ char *sender; /* sender address */
+ char *encoding; /* owned by mail_cmd() */
+ char *verp_delims; /* owned by mail_cmd() */
+ char *recipient; /* recipient address */
+ char *etrn_name; /* client ETRN argument */
+ char *protocol; /* SMTP or ESMTP */
+ char *where; /* protocol stage */
+ int recursion; /* Kellerspeicherpegelanzeiger */
+ off_t msg_size; /* MAIL FROM message size */
+ off_t act_size; /* END-OF-DATA message size */
+ int junk_cmds; /* counter */
+ int rcpt_overshoot; /* counter */
+ char *rewrite_context; /* address rewriting context */
+
+ /*
+ * SASL specific.
+ */
+#ifdef USE_SASL_AUTH
+ struct XSASL_SERVER *sasl_server;
+ VSTRING *sasl_reply;
+ char *sasl_mechanism_list;
+ char *sasl_method;
+ char *sasl_username;
+ char *sasl_sender;
+#endif
+
+ /*
+ * Specific to smtpd access checks.
+ */
+ int sender_rcptmap_checked; /* sender validated against maps */
+ int recipient_rcptmap_checked; /* recipient validated against maps */
+ int warn_if_reject; /* force reject into warning */
+ SMTPD_DEFER defer_if_reject; /* force reject into deferral */
+ SMTPD_DEFER defer_if_permit; /* force permit into deferral */
+ int defer_if_permit_client; /* force permit into warning */
+ int defer_if_permit_helo; /* force permit into warning */
+ int defer_if_permit_sender; /* force permit into warning */
+ int discard; /* discard message */
+ char *saved_filter; /* postponed filter action */
+ char *saved_redirect; /* postponed redirect action */
+ ARGV *saved_bcc; /* postponed bcc action */
+ int saved_flags; /* postponed hold/discard */
+#ifdef DELAY_ACTION
+ int saved_delay; /* postponed deferred delay */
+#endif
+ VSTRING *expand_buf; /* scratch space for $name expansion */
+ ARGV *prepend; /* prepended headers */
+ VSTRING *instance; /* policy query correlation */
+ int seqno; /* policy query correlation */
+ int ehlo_discard_mask; /* suppressed EHLO features */
+ char *dsn_envid; /* temporary MAIL FROM state */
+ int dsn_ret; /* temporary MAIL FROM state */
+ VSTRING *dsn_buf; /* scratch space for xtext expansion */
+ VSTRING *dsn_orcpt_buf; /* scratch space for ORCPT parsing */
+
+ /*
+ * Pass-through proxy client.
+ */
+ struct SMTPD_PROXY *proxy;
+ char *proxy_mail; /* owned by mail_cmd() */
+
+ /*
+ * XFORWARD server state.
+ */
+ SMTPD_XFORWARD_ATTR xforward; /* up-stream logging info */
+
+ /*
+ * TLS related state.
+ */
+#ifdef USE_TLS
+#ifdef USE_TLSPROXY
+ VSTREAM *tlsproxy; /* tlsproxy(8) temp. handle */
+#endif
+ TLS_SESS_STATE *tls_context; /* TLS session state */
+#endif
+
+ /*
+ * Milter support.
+ */
+ const char **milter_argv; /* SMTP command vector */
+ ssize_t milter_argc; /* SMTP command vector */
+ const char *milter_reject_text; /* input to call-back from Milter */
+ MILTERS *milters; /* Milter initialization status. */
+
+ /*
+ * EHLO temporary space.
+ */
+ VSTRING *ehlo_buf;
+ ARGV *ehlo_argv;
+
+ /*
+ * BDAT processing state.
+ */
+#define SMTPD_BDAT_STAT_NONE 0 /* not processing BDAT */
+#define SMTPD_BDAT_STAT_OK 1 /* accepting BDAT chunks */
+#define SMTPD_BDAT_STAT_ERROR 2 /* skipping BDAT chunks */
+ int bdat_state; /* see above */
+ VSTREAM *bdat_get_stream; /* memory stream from BDAT chunk */
+ VSTRING *bdat_get_buffer; /* read from memory stream */
+ int bdat_prev_rec_type;
+} SMTPD_STATE;
+
+#define SMTPD_FLAG_HANGUP (1<<0) /* 421/521 disconnect */
+#define SMTPD_FLAG_ILL_PIPELINING (1<<1) /* inappropriate pipelining */
+#define SMTPD_FLAG_AUTH_USED (1<<2) /* don't reuse SASL state */
+#define SMTPD_FLAG_SMTPUTF8 (1<<3) /* RFC 6531/2 transaction */
+#define SMTPD_FLAG_NEED_MILTER_ABORT (1<<4) /* undo milter_mail_event() */
+
+ /* Security: don't reset SMTPD_FLAG_AUTH_USED. */
+#define SMTPD_MASK_MAIL_KEEP \
+ ~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */
+
+#define SMTPD_STATE_XFORWARD_INIT (1<<0) /* xforward preset done */
+#define SMTPD_STATE_XFORWARD_NAME (1<<1) /* client name received */
+#define SMTPD_STATE_XFORWARD_ADDR (1<<2) /* client address received */
+#define SMTPD_STATE_XFORWARD_PROTO (1<<3) /* protocol received */
+#define SMTPD_STATE_XFORWARD_HELO (1<<4) /* client helo received */
+#define SMTPD_STATE_XFORWARD_IDENT (1<<5) /* message identifier */
+#define SMTPD_STATE_XFORWARD_DOMAIN (1<<6) /* address context */
+#define SMTPD_STATE_XFORWARD_PORT (1<<7) /* client port received */
+
+#define SMTPD_STATE_XFORWARD_CLIENT_MASK \
+ (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR \
+ | SMTPD_STATE_XFORWARD_PROTO | SMTPD_STATE_XFORWARD_HELO \
+ | SMTPD_STATE_XFORWARD_PORT)
+
+extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *, const char *);
+extern void smtpd_state_reset(SMTPD_STATE *);
+
+ /*
+ * Conversation stages. This is used for "lost connection after XXX"
+ * diagnostics.
+ */
+#define SMTPD_AFTER_CONNECT "CONNECT"
+#define SMTPD_AFTER_DATA "DATA content"
+#define SMTPD_AFTER_BDAT "BDAT content"
+#define SMTPD_AFTER_EOM "END-OF-MESSAGE"
+
+ /*
+ * Other stages. These are sometimes used to change the way information is
+ * logged or what information will be available for access control.
+ */
+#define SMTPD_CMD_HELO "HELO"
+#define SMTPD_CMD_EHLO "EHLO"
+#define SMTPD_CMD_STARTTLS "STARTTLS"
+#define SMTPD_CMD_AUTH "AUTH"
+#define SMTPD_CMD_MAIL "MAIL"
+#define SMTPD_CMD_RCPT "RCPT"
+#define SMTPD_CMD_DATA "DATA"
+#define SMTPD_CMD_BDAT "BDAT"
+#define SMTPD_CMD_EOD SMTPD_AFTER_EOM /* XXX Was: END-OF-DATA */
+#define SMTPD_CMD_RSET "RSET"
+#define SMTPD_CMD_NOOP "NOOP"
+#define SMTPD_CMD_VRFY "VRFY"
+#define SMTPD_CMD_ETRN "ETRN"
+#define SMTPD_CMD_QUIT "QUIT"
+#define SMTPD_CMD_XCLIENT "XCLIENT"
+#define SMTPD_CMD_XFORWARD "XFORWARD"
+#define SMTPD_CMD_UNKNOWN "UNKNOWN"
+
+ /*
+ * Representation of unknown and non-existent client information. Throughout
+ * Postfix, we use the "unknown" string value for unknown client information
+ * (e.g., unknown remote client hostname), and we use the empty string, null
+ * pointer or "no queue file record" for non-existent client information
+ * (e.g., no HELO command, or local submission).
+ *
+ * Inside the SMTP server, unknown real client attributes are represented by
+ * the string "unknown", and non-existent HELO is represented as a null
+ * pointer. The SMTP server uses this same representation internally for
+ * forwarded client attributes; the XFORWARD syntax makes no distinction
+ * between unknown (remote submission) and non-existent (local submission).
+ *
+ * The SMTP client sends forwarded client attributes only when upstream client
+ * attributes exist (i.e. remote submission). Thus, local submissions will
+ * appear to come from an SMTP-based content filter, which is acceptable.
+ *
+ * Known/unknown client attribute values use the SMTP server's internal
+ * representation in queue files, in queue manager delivery requests, and in
+ * delivery agent $name expansions.
+ *
+ * Non-existent attribute values are never present in queue files. Non-existent
+ * information is represented as empty strings in queue manager delivery
+ * requests and in delivery agent $name expansions.
+ */
+#define CLIENT_ATTR_UNKNOWN "unknown"
+
+#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_HELO_UNKNOWN 0
+#define CLIENT_PROTO_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_IDENT_UNKNOWN 0
+#define CLIENT_DOMAIN_UNKNOWN 0
+#define CLIENT_LOGIN_UNKNOWN 0
+
+#define SERVER_ATTR_UNKNOWN "unknown"
+
+#define SERVER_ADDR_UNKNOWN SERVER_ATTR_UNKNOWN
+#define SERVER_PORT_UNKNOWN SERVER_ATTR_UNKNOWN
+
+#define IS_AVAIL_CLIENT_ATTR(v) ((v) && strcmp((v), CLIENT_ATTR_UNKNOWN))
+
+#define IS_AVAIL_CLIENT_NAME(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_ADDR(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_PORT(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_NAMADDR(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_HELO(v) ((v) != 0)
+#define IS_AVAIL_CLIENT_PROTO(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_IDENT(v) ((v) != 0)
+#define IS_AVAIL_CLIENT_DOMAIN(v) ((v) != 0)
+
+ /*
+ * If running in stand-alone mode, do not try to talk to Postfix daemons but
+ * write to queue file instead.
+ */
+#define SMTPD_STAND_ALONE_STREAM(stream) \
+ (stream == VSTREAM_IN && getuid() != var_owner_uid)
+
+#define SMTPD_STAND_ALONE(state) \
+ (state->client == VSTREAM_IN && getuid() != var_owner_uid)
+
+ /*
+ * If running as proxy front-end, disable actions that require communication
+ * with the cleanup server.
+ */
+#define USE_SMTPD_PROXY(state) \
+ (SMTPD_STAND_ALONE(state) == 0 && *var_smtpd_proxy_filt)
+
+ /*
+ * Are we in a MAIL transaction?
+ */
+#define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0)
+
+ /*
+ * Are we processing BDAT requests?
+ */
+#define SMTPD_PROCESSING_BDAT(state) \
+ ((state)->bdat_state != SMTPD_BDAT_STAT_NONE)
+
+ /*
+ * SMTPD peer information lookup.
+ */
+extern void smtpd_peer_init(SMTPD_STATE *state);
+extern void smtpd_peer_reset(SMTPD_STATE *state);
+extern void smtpd_peer_from_default(SMTPD_STATE *);
+extern int smtpd_peer_from_haproxy(SMTPD_STATE *);
+
+#define SMTPD_PEER_CODE_OK 2
+#define SMTPD_PEER_CODE_TEMP 4
+#define SMTPD_PEER_CODE_PERM 5
+#define SMTPD_PEER_CODE_FORGED 6
+
+ /*
+ * Construct name[addr] or name[addr]:port as appropriate
+ */
+#define SMTPD_BUILD_NAMADDRPORT(name, addr, port) \
+ concatenate((name), "[", (addr), "]", \
+ var_smtpd_client_port_log ? ":" : (char *) 0, \
+ (port), (char *) 0)
+
+ /*
+ * Don't mix information from the current SMTP session with forwarded
+ * information from an up-stream session.
+ */
+#define HAVE_FORWARDED_CLIENT_ATTR(s) \
+ ((s)->xforward.flags & SMTPD_STATE_XFORWARD_CLIENT_MASK)
+
+#define FORWARD_CLIENT_ATTR(s, a) \
+ (HAVE_FORWARDED_CLIENT_ATTR(s) ? \
+ (s)->xforward.a : (s)->a)
+
+#define FORWARD_ADDR(s) FORWARD_CLIENT_ATTR((s), rfc_addr)
+#define FORWARD_NAME(s) FORWARD_CLIENT_ATTR((s), name)
+#define FORWARD_NAMADDR(s) FORWARD_CLIENT_ATTR((s), namaddr)
+#define FORWARD_PROTO(s) FORWARD_CLIENT_ATTR((s), protocol)
+#define FORWARD_HELO(s) FORWARD_CLIENT_ATTR((s), helo_name)
+#define FORWARD_PORT(s) FORWARD_CLIENT_ATTR((s), port)
+
+ /*
+ * Mixing is not a problem with forwarded local message identifiers.
+ */
+#define HAVE_FORWARDED_IDENT(s) \
+ ((s)->xforward.ident != 0)
+
+#define FORWARD_IDENT(s) \
+ (HAVE_FORWARDED_IDENT(s) ? \
+ (s)->xforward.ident : (s)->queue_id)
+
+ /*
+ * Mixing is not a problem with forwarded address rewriting contexts.
+ */
+#define FORWARD_DOMAIN(s) \
+ (((s)->xforward.flags & SMTPD_STATE_XFORWARD_DOMAIN) ? \
+ (s)->xforward.domain : (s)->rewrite_context)
+
+extern void smtpd_xforward_init(SMTPD_STATE *);
+extern void smtpd_xforward_preset(SMTPD_STATE *);
+extern void smtpd_xforward_reset(SMTPD_STATE *);
+
+ /*
+ * Transparency: before mail is queued, do we check for unknown recipients,
+ * do we allow address mapping, automatic bcc, header/body checks?
+ */
+extern int smtpd_input_transp_mask;
+
+ /*
+ * More Milter support.
+ */
+extern MILTERS *smtpd_milters;
+
+ /*
+ * Message size multiplication factor for free space check.
+ */
+extern double smtpd_space_multf;
+
+ /*
+ * header_from_format support.
+ */
+extern int smtpd_hfrom_format;
+
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
diff --git a/src/smtpd/smtpd_acl.in b/src/smtpd/smtpd_acl.in
new file mode 100644
index 0000000..daed26c
--- /dev/null
+++ b/src/smtpd/smtpd_acl.in
@@ -0,0 +1,120 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+relay_domains porcupine.org
+smtpd_null_access_lookup_key <>
+#
+# Test check_domain_access()
+#
+helo_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+helo foo.dunno.com
+# Expect: OK
+helo bar.dunno.com
+# Expect: OK
+helo foo.duuno.com
+#
+# Test check_namadr_access(), domain part
+#
+client_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+client foo.dunno.com 131.155.210.17
+# Expect: OK
+client bar.dunno.com 131.155.210.17
+# Expect: OK
+client bar.dunno.com 131.155.210.19
+#
+# Test check_namadr_access(), address part
+#
+# Expect: OK
+client bar.duno.com 131.155.210.17
+# Expect: REJECT
+client bar.duno.com 131.155.210.19
+# Expect: REJECT
+client bar.duno.com 44.33.22.11
+# Expect: OK
+client bar.duno.com 44.33.22.55
+# Expect: REJECT
+client bar.duno.com 44.33.44.33
+#
+# Test check_mail_access()
+#
+sender_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+mail reject@dunno.domain
+# Expect: OK
+mail ok@dunno.domain
+# Expect: OK
+mail anyone@dunno.domain
+# Expect: OK
+mail bad-sender@dunno.domain
+#
+# Again, with a domain that rejects by default
+#
+# Expect: REJECT
+mail reject@reject.domain
+# Expect: OK
+mail ok@reject.domain
+# Expect: REJECT
+mail anyone@reject.domain
+# Expect: REJECT
+mail good-sender@reject.domain
+#
+# Again, with a domain that accepts by default
+#
+# Expect: REJECT
+mail reject@ok.domain
+# Expect: OK
+mail ok@ok.domain
+# Expect: OK
+mail anyone@ok.domain
+# Expect: OK
+mail bad-sender@ok.domain
+#
+# Test check_mail_access()
+#
+recipient_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+rcpt reject@dunno.domain
+# Expect: REJECT
+recipient_delimiter +
+rcpt reject+ext@dunno.domain
+recipient_delimiter |
+# Expect: OK
+rcpt ok@dunno.domain
+# Expect: OK
+recipient_delimiter +
+rcpt ok+ext@dunno.domain
+recipient_delimiter |
+# Expect: OK
+rcpt anyone@dunno.domain
+# Expect: OK
+rcpt bad-sender@dunno.domain
+#
+# Again, with a domain that rejects by default
+#
+# Expect: REJECT
+rcpt reject@reject.domain
+# Expect: OK
+rcpt ok@reject.domain
+# Expect: REJECT
+rcpt anyone@reject.domain
+# Expect: REJECT
+rcpt good-sender@reject.domain
+#
+# Again, with a domain that accepts by default
+#
+# Expect: REJECT
+rcpt reject@ok.domain
+# Expect: OK
+rcpt ok@ok.domain
+# Expect: OK
+rcpt anyone@ok.domain
+# Expect: OK
+rcpt bad-sender@ok.domain
+#
+# check_sender_access specific
+#
+mail <>
diff --git a/src/smtpd/smtpd_acl.ref b/src/smtpd/smtpd_acl.ref
new file mode 100644
index 0000000..4b7f6e9
--- /dev/null
+++ b/src/smtpd/smtpd_acl.ref
@@ -0,0 +1,187 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> smtpd_null_access_lookup_key <>
+OK
+>>> #
+>>> # Test check_domain_access()
+>>> #
+>>> helo_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> helo foo.dunno.com
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied; proto=SMTP helo=<foo.dunno.com>
+554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied
+>>> # Expect: OK
+>>> helo bar.dunno.com
+OK
+>>> # Expect: OK
+>>> helo foo.duuno.com
+OK
+>>> #
+>>> # Test check_namadr_access(), domain part
+>>> #
+>>> client_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> client foo.dunno.com 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied
+>>> # Expect: OK
+>>> client bar.dunno.com 131.155.210.17
+OK
+>>> # Expect: OK
+>>> client bar.dunno.com 131.155.210.19
+OK
+>>> #
+>>> # Test check_namadr_access(), address part
+>>> #
+>>> # Expect: OK
+>>> client bar.duno.com 131.155.210.17
+OK
+>>> # Expect: REJECT
+>>> client bar.duno.com 131.155.210.19
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[131.155.210.19]: 554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied
+>>> # Expect: REJECT
+>>> client bar.duno.com 44.33.22.11
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.22.11]: 554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied
+>>> # Expect: OK
+>>> client bar.duno.com 44.33.22.55
+OK
+>>> # Expect: REJECT
+>>> client bar.duno.com 44.33.44.33
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.44.33]: 554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> mail reject@dunno.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied; from=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@dunno.domain
+OK
+>>> # Expect: OK
+>>> mail anyone@dunno.domain
+OK
+>>> # Expect: OK
+>>> mail bad-sender@dunno.domain
+OK
+>>> #
+>>> # Again, with a domain that rejects by default
+>>> #
+>>> # Expect: REJECT
+>>> mail reject@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied; from=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@reject.domain
+OK
+>>> # Expect: REJECT
+>>> mail anyone@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied; from=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied
+>>> # Expect: REJECT
+>>> mail good-sender@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied; from=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied
+>>> #
+>>> # Again, with a domain that accepts by default
+>>> #
+>>> # Expect: REJECT
+>>> mail reject@ok.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied; from=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@ok.domain
+OK
+>>> # Expect: OK
+>>> mail anyone@ok.domain
+OK
+>>> # Expect: OK
+>>> mail bad-sender@ok.domain
+OK
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> rcpt reject@dunno.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied
+>>> # Expect: REJECT
+>>> recipient_delimiter +
+OK
+>>> rcpt reject+ext@dunno.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject+ext@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied
+>>> recipient_delimiter |
+OK
+>>> # Expect: OK
+>>> rcpt ok@dunno.domain
+OK
+>>> # Expect: OK
+>>> recipient_delimiter +
+OK
+>>> rcpt ok+ext@dunno.domain
+OK
+>>> recipient_delimiter |
+OK
+>>> # Expect: OK
+>>> rcpt anyone@dunno.domain
+OK
+>>> # Expect: OK
+>>> rcpt bad-sender@dunno.domain
+OK
+>>> #
+>>> # Again, with a domain that rejects by default
+>>> #
+>>> # Expect: REJECT
+>>> rcpt reject@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied
+>>> # Expect: OK
+>>> rcpt ok@reject.domain
+OK
+>>> # Expect: REJECT
+>>> rcpt anyone@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied
+>>> # Expect: REJECT
+>>> rcpt good-sender@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied
+>>> #
+>>> # Again, with a domain that accepts by default
+>>> #
+>>> # Expect: REJECT
+>>> rcpt reject@ok.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied
+>>> # Expect: OK
+>>> rcpt ok@ok.domain
+OK
+>>> # Expect: OK
+>>> rcpt anyone@ok.domain
+OK
+>>> # Expect: OK
+>>> rcpt bad-sender@ok.domain
+OK
+>>> #
+>>> # check_sender_access specific
+>>> #
+>>> mail <>
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 550 5.7.1 <>: Sender address rejected: Go away postmaster; from=<> proto=SMTP helo=<foo.duuno.com>
+550 5.7.1 <>: Sender address rejected: Go away postmaster
diff --git a/src/smtpd/smtpd_addr_valid.in b/src/smtpd/smtpd_addr_valid.in
new file mode 100644
index 0000000..10b5f01
--- /dev/null
+++ b/src/smtpd/smtpd_addr_valid.in
@@ -0,0 +1,35 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever}
+mydestination example.com
+myorigin example.com
+
+sender_canonical_maps inline:{foo@example.com=foo_canon@example.com}
+recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com}
+
+sender_restrictions reject_unlisted_sender
+# Expect accept
+mail bar_canon@example.com
+# Expect accept
+mail bar@example.com
+# Expect accept
+mail foo_canon@example.com
+# Expect accept
+mail foo@example.com
+# Expect reject
+mail baz@example.com
+
+recipient_restrictions reject_unlisted_recipient
+# Expect accept
+rcpt bar_canon@example.com
+# Expect accept
+rcpt bar@example.com
+# Expect accept
+rcpt foo_canon@example.com
+# Expect reject
+rcpt foo@example.com
+# Expect reject
+mail baz@example.com
diff --git a/src/smtpd/smtpd_addr_valid.ref b/src/smtpd/smtpd_addr_valid.ref
new file mode 100644
index 0000000..3bf610c
--- /dev/null
+++ b/src/smtpd/smtpd_addr_valid.ref
@@ -0,0 +1,57 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever}
+OK
+>>> mydestination example.com
+OK
+>>> myorigin example.com
+OK
+>>>
+>>> sender_canonical_maps inline:{foo@example.com=foo_canon@example.com}
+OK
+>>> recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com}
+OK
+>>>
+>>> sender_restrictions reject_unlisted_sender
+OK
+>>> # Expect accept
+>>> mail bar_canon@example.com
+OK
+>>> # Expect accept
+>>> mail bar@example.com
+OK
+>>> # Expect accept
+>>> mail foo_canon@example.com
+OK
+>>> # Expect accept
+>>> mail foo@example.com
+OK
+>>> # Expect reject
+>>> mail baz@example.com
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP
+550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table
+>>>
+>>> recipient_restrictions reject_unlisted_recipient
+OK
+>>> # Expect accept
+>>> rcpt bar_canon@example.com
+OK
+>>> # Expect accept
+>>> rcpt bar@example.com
+OK
+>>> # Expect accept
+>>> rcpt foo_canon@example.com
+OK
+>>> # Expect reject
+>>> rcpt foo@example.com
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table; from=<baz@example.com> to=<foo@example.com> proto=SMTP
+550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table
+>>> # Expect reject
+>>> mail baz@example.com
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP
+550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table
diff --git a/src/smtpd/smtpd_chat.c b/src/smtpd/smtpd_chat.c
new file mode 100644
index 0000000..278e536
--- /dev/null
+++ b/src/smtpd/smtpd_chat.c
@@ -0,0 +1,352 @@
+/*++
+/* NAME
+/* smtpd_chat 3
+/* SUMMARY
+/* SMTP server request/response support
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_chat.h>
+/*
+/* void smtpd_chat_pre_jail_init(void)
+/*
+/* int smtpd_chat_query_limit(state, limit)
+/* SMTPD_STATE *state;
+/* int limit;
+/*
+/* void smtpd_chat_query(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reply(state, format, ...)
+/* SMTPD_STATE *state;
+/* char *format;
+/*
+/* void smtpd_chat_notify(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* This module implements SMTP server support for request/reply
+/* conversations, and maintains a limited SMTP transaction log.
+/*
+/* smtpd_chat_pre_jail_init() performs one-time initialization.
+/*
+/* smtpd_chat_query_limit() reads a line from the client that is
+/* at most "limit" bytes long. A copy is appended to the SMTP
+/* transaction log. The return value is non-zero for a complete
+/* line or else zero if the length limit was exceeded.
+/*
+/* smtpd_chat_query() receives a client request and appends a copy
+/* to the SMTP transaction log.
+/*
+/* smtpd_chat_reply() formats a server reply, sends it to the
+/* client, and appends a copy to the SMTP transaction log.
+/* When soft_bounce is enabled, all 5xx (reject) responses are
+/* replaced by 4xx (try again). In case of a 421 reply the
+/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect.
+/*
+/* smtpd_chat_notify() sends a copy of the SMTP transaction log
+/* to the postmaster for review. The postmaster notice is sent only
+/* when delivery is possible immediately. It is an error to call
+/* smtpd_chat_notify() when no SMTP transaction log exists.
+/*
+/* smtpd_chat_reset() resets the transaction log. This is
+/* typically done at the beginning of an SMTP session, or
+/* within a session to discard non-error information.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors.
+/* 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 <setjmp.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <line_wrap.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <maps.h>
+#include <post_mail.h>
+#include <mail_error.h>
+#include <smtp_reply_footer.h>
+#include <hfrom_format.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_expand.h"
+#include "smtpd_chat.h"
+
+ /*
+ * Reject footer.
+ */
+static MAPS *smtpd_rej_ftr_maps;
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* smtpd_chat_pre_jail_init - initialize */
+
+void smtpd_chat_pre_jail_init(void)
+{
+ static int init_count = 0;
+
+ if (init_count++ != 0)
+ msg_panic("smtpd_chat_pre_jail_init: multiple calls");
+
+ /*
+ * SMTP server reject footer.
+ */
+ if (*var_smtpd_rej_ftr_maps)
+ smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
+ var_smtpd_rej_ftr_maps,
+ DICT_FLAG_LOCK);
+}
+
+/* smtp_chat_reset - reset SMTP transaction log */
+
+void smtpd_chat_reset(SMTPD_STATE *state)
+{
+ if (state->history) {
+ argv_free(state->history);
+ state->history = 0;
+ }
+}
+
+/* smtp_chat_append - append record to SMTP transaction log */
+
+static void smtp_chat_append(SMTPD_STATE *state, char *direction,
+ const char *text)
+{
+ char *line;
+
+ if (state->notify_mask == 0)
+ return;
+
+ if (state->history == 0)
+ state->history = argv_alloc(10);
+ line = concatenate(direction, text, (char *) 0);
+ argv_add(state->history, line, (char *) 0);
+ myfree(line);
+}
+
+/* smtpd_chat_query - receive and record an SMTP request */
+
+int smtpd_chat_query_limit(SMTPD_STATE *state, int limit)
+{
+ int last_char;
+
+ /*
+ * We can't parse or store input that exceeds var_line_limit, so we skip
+ * over it to avoid loss of synchronization.
+ */
+ last_char = smtp_get(state->buffer, state->client, limit,
+ SMTP_GET_FLAG_SKIP);
+ smtp_chat_append(state, "In: ", STR(state->buffer));
+ if (last_char != '\n')
+ msg_warn("%s: request longer than %d: %.30s...",
+ state->namaddr, limit,
+ printable(STR(state->buffer), '?'));
+
+ if (msg_verbose)
+ msg_info("< %s: %s", state->namaddr, STR(state->buffer));
+ return (last_char == '\n');
+}
+
+/* smtpd_chat_reply - format, send and record an SMTP response */
+
+void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vsmtpd_chat_reply(state, format, ap);
+ va_end(ap);
+}
+
+/* vsmtpd_chat_reply - format, send and record an SMTP response */
+
+void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
+{
+ int delay = 0;
+ char *cp;
+ char *next;
+ char *end;
+ const char *footer;
+
+ /*
+ * Slow down clients that make errors. Sleep-on-anything slows down
+ * clients that make an excessive number of errors within a session.
+ */
+ if (state->error_count >= var_smtpd_soft_erlim)
+ sleep(delay = var_smtpd_err_sleep);
+
+ vstring_vsprintf(state->buffer, format, ap);
+
+ if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
+ && ((smtpd_rej_ftr_maps != 0
+ && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)
+ || *(footer = var_smtpd_rej_footer) != 0))
+ smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter),
+ smtpd_expand_lookup, (void *) state);
+
+ /* All 5xx replies must have a 5.xx.xx detail code. */
+ for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
+ if (var_soft_bounce) {
+ if (cp[0] == '5') {
+ cp[0] = '4';
+ if (cp[4] == '5')
+ cp[4] = '4';
+ }
+ }
+ /* This is why we use strlen() above instead of VSTRING_LEN(). */
+ if ((next = strstr(cp, "\r\n")) != 0) {
+ *next = 0;
+ if (next[2] != 0)
+ cp[3] = '-'; /* contact footer kludge */
+ else
+ next = end; /* strip trailing \r\n */
+ } else {
+ next = end;
+ }
+ smtp_chat_append(state, "Out: ", cp);
+
+ if (msg_verbose)
+ msg_info("> %s: %s", state->namaddr, cp);
+
+ smtp_fputs(cp, next - cp, state->client);
+ if (next < end)
+ cp = next + 2;
+ else
+ break;
+ }
+
+ /*
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined SMTP sessions that have lots of server-side
+ * delays (tarpit delays or DNS lookups for UCE restrictions).
+ */
+ if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
+ vstream_fflush(state->client);
+
+ /*
+ * Abort immediately if the connection is broken.
+ */
+ if (vstream_ftimeout(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_TIME);
+ if (vstream_ferror(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_EOF);
+
+ /*
+ * Orderly disconnect in case of 421 or 521 reply.
+ */
+ if (strncmp(STR(state->buffer), "421", 3) == 0
+ || strncmp(STR(state->buffer), "521", 3) == 0)
+ state->flags |= SMTPD_FLAG_HANGUP;
+}
+
+/* print_line - line_wrap callback */
+
+static void print_line(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *notice = (VSTREAM *) context;
+
+ post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
+}
+
+/* smtpd_chat_notify - notify postmaster */
+
+void smtpd_chat_notify(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_chat_notify";
+ VSTREAM *notice;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (state->history == 0)
+ msg_panic("%s: no conversation history", myname);
+ if (msg_verbose)
+ msg_info("%s: notify postmaster", myname);
+
+ /*
+ * Construct a message for the postmaster, explaining what this is all
+ * about. This is junk mail: don't send it when the mail posting service
+ * is unavailable, and use the double bounce sender address to prevent
+ * mail bounce wars. Always prepend one space to message content that we
+ * generate from untrusted data.
+ */
+#define NULL_TRACE_FLAGS 0
+#define NO_QUEUE_ID ((VSTRING *) 0)
+#define LENGTH 78
+#define INDENT 4
+
+ notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ (state->error_mask & MAIL_ERROR_BOUNCE) ?
+ var_bounce_rcpt : var_error_rcpt,
+ MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS,
+ SMTPUTF8_FLAG_NONE, NO_QUEUE_ID);
+ if (notice == 0) {
+ msg_warn("postmaster notify: %m");
+ return;
+ }
+ if (smtpd_hfrom_format == HFROM_FORMAT_CODE_STD) {
+ post_mail_fprintf(notice, "From: Mail Delivery System <%s>",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt);
+ } else {
+ post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
+ }
+ post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
+ var_mail_name, state->namaddr);
+ post_mail_fputs(notice, "");
+ post_mail_fputs(notice, "Transcript of session follows.");
+ post_mail_fputs(notice, "");
+ argv_terminate(state->history);
+ for (cpp = state->history->argv; *cpp; cpp++)
+ line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
+ (void *) notice);
+ post_mail_fputs(notice, "");
+ if (state->reason)
+ post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "For other details, see the local mail logfile");
+ (void) post_mail_fclose(notice);
+}
diff --git a/src/smtpd/smtpd_chat.h b/src/smtpd/smtpd_chat.h
new file mode 100644
index 0000000..9fbe178
--- /dev/null
+++ b/src/smtpd/smtpd_chat.h
@@ -0,0 +1,45 @@
+/*++
+/* NAME
+/* smtpd_chat 3h
+/* SUMMARY
+/* SMTP server request/response support
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_chat.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * External interface.
+ */
+extern void smtpd_chat_pre_jail_init(void);
+extern void smtpd_chat_reset(SMTPD_STATE *);
+extern int smtpd_chat_query_limit(SMTPD_STATE *, int);
+extern void smtpd_chat_query(SMTPD_STATE *);
+extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...);
+extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list);
+extern void smtpd_chat_notify(SMTPD_STATE *);
+
+#define smtpd_chat_query(state) \
+ ((void) smtpd_chat_query_limit((state), var_line_limit))
+
+/* 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/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c
new file mode 100644
index 0000000..996a19e
--- /dev/null
+++ b/src/smtpd/smtpd_check.c
@@ -0,0 +1,6445 @@
+/*++
+/* NAME
+/* smtpd_check 3
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check.h"
+/*
+/* void smtpd_check_init()
+/*
+/* int smtpd_check_addr(sender, address, smtputf8)
+/* const char *sender;
+/* const char *address;
+/* int smtputf8;
+/*
+/* char *smtpd_check_rewrite(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_client(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_helo(state, helohost)
+/* SMTPD_STATE *state;
+/* char *helohost;
+/*
+/* char *smtpd_check_mail(state, sender)
+/* SMTPD_STATE *state;
+/* char *sender;
+/*
+/* char *smtpd_check_rcpt(state, recipient)
+/* SMTPD_STATE *state;
+/* char *recipient;
+/*
+/* char *smtpd_check_etrn(state, destination)
+/* SMTPD_STATE *state;
+/* char *destination;
+/*
+/* char *smtpd_check_data(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_eod(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_size(state, size)
+/* SMTPD_STATE *state;
+/* off_t size;
+/*
+/* char *smtpd_check_queue(state)
+/* SMTPD_STATE *state;
+/* AUXILIARY FUNCTIONS
+/* void log_whatsup(state, action, text)
+/* SMTPD_STATE *state;
+/* const char *action;
+/* const char *text;
+/* DESCRIPTION
+/* This module implements additional checks on SMTP client requests.
+/* A client request is validated in the context of the session state.
+/* The result is either an error response (including the numerical
+/* code) or the result is a null pointer in case of success.
+/*
+/* smtpd_check_init() initializes. This function should be called
+/* once during the process life time.
+/*
+/* smtpd_check_addr() sanity checks an email address and returns
+/* non-zero in case of badness. The sender argument provides sender
+/* context for address resolution and caching, or a null pointer
+/* if information is unavailable.
+/*
+/* smtpd_check_rewrite() should be called before opening a queue
+/* file or proxy connection, in order to establish the proper
+/* header address rewriting context.
+/*
+/* Each of the following routines scrutinizes the argument passed to
+/* an SMTP command such as HELO, MAIL FROM, RCPT TO, or scrutinizes
+/* the initial client connection request. The administrator can
+/* specify what restrictions apply.
+/*
+/* Restrictions are specified via configuration parameters named
+/* \fIsmtpd_{client,helo,sender,recipient}_restrictions.\fR Each
+/* configuration parameter specifies a list of zero or more
+/* restrictions that are applied in the order as specified.
+/* .PP
+/* smtpd_check_client() validates the client host name or address.
+/* Relevant configuration parameters:
+/* .IP smtpd_client_restrictions
+/* Restrictions on the names or addresses of clients that may connect
+/* to this SMTP server.
+/* .PP
+/* smtpd_check_helo() validates the hostname provided with the
+/* HELO/EHLO commands. Relevant configuration parameters:
+/* .IP smtpd_helo_restrictions
+/* Restrictions on the hostname that is sent with the HELO/EHLO
+/* command.
+/* .PP
+/* smtpd_check_mail() validates the sender address provided with
+/* a MAIL FROM request. Relevant configuration parameters:
+/* .IP smtpd_sender_restrictions
+/* Restrictions on the sender address that is sent with the MAIL FROM
+/* command.
+/* .PP
+/* smtpd_check_rcpt() validates the recipient address provided
+/* with an RCPT TO request. Relevant configuration parameters:
+/* .IP smtpd_recipient_restrictions
+/* Restrictions on the recipient address that is sent with the RCPT
+/* TO command.
+/* .IP local_recipient_maps
+/* Tables of user names (not addresses) that exist in $mydestination.
+/* Mail for local users not in these tables is rejected.
+/* .PP
+/* smtpd_check_etrn() validates the domain name provided with the
+/* ETRN command, and other client-provided information. Relevant
+/* configuration parameters:
+/* .IP smtpd_etrn_restrictions
+/* Restrictions on the hostname that is sent with the HELO/EHLO
+/* command.
+/* .PP
+/* smtpd_check_size() checks if a message with the given size can
+/* be received (zero means that the message size is unknown). The
+/* message is rejected when
+/* the message size exceeds the non-zero bound specified with the
+/* \fImessage_size_limit\fR configuration parameter. This is a
+/* permanent error.
+/*
+/* smtpd_check_queue() checks the available queue file system
+/* space. The message is rejected when:
+/* .IP \(bu
+/* The available queue file system space is less than the amount
+/* specified with the \fImin_queue_free\fR configuration parameter.
+/* This is a temporary error.
+/* .IP \(bu
+/* The available queue file system space is less than twice the
+/* message size limit. This is a temporary error.
+/* .PP
+/* smtpd_check_data() enforces generic restrictions after the
+/* client has sent the DATA command.
+/*
+/* smtpd_check_eod() enforces generic restrictions after the
+/* client has sent the END-OF-DATA command.
+/*
+/* Arguments:
+/* .IP name
+/* The client hostname, or \fIunknown\fR.
+/* .IP addr
+/* The client address.
+/* .IP helohost
+/* The hostname given with the HELO command.
+/* .IP sender
+/* The sender address given with the MAIL FROM command.
+/* .IP recipient
+/* The recipient address given with the RCPT TO or VRFY command.
+/* .IP size
+/* The message size given with the MAIL FROM command (zero if unknown).
+/* .PP
+/* log_whatsup() logs "<queueid>: <action>: <protocol state>
+/* from: <client-name[client-addr]>: <text>" plus the protocol
+/* (SMTP or ESMTP), and if available, EHLO, MAIL FROM, or RCPT
+/* TO.
+/* BUGS
+/* Policies like these should not be hard-coded in C, but should
+/* be user-programmable instead.
+/* SEE ALSO
+/* namadr_list(3) host access control
+/* domain_list(3) domain access control
+/* fsspace(3) free file system space
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <fsspace.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <argv.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <htable.h>
+#include <ctable.h>
+#include <mac_expand.h>
+#include <attr_clnt.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <ip_match.h>
+#include <valid_utf8_hostname.h>
+#include <midna_domain.h>
+#include <mynetworks.h>
+#include <name_code.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Global library. */
+
+#include <string_list.h>
+#include <namadr_list.h>
+#include <domain_list.h>
+#include <mail_params.h>
+#include <resolve_clnt.h>
+#include <mail_error.h>
+#include <resolve_local.h>
+#include <own_inet_addr.h>
+#include <mail_conf.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <match_parent_style.h>
+#include <strip_addr.h>
+#include <cleanup_user.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <mail_addr.h>
+#include <verify_clnt.h>
+#include <input_transp.h>
+#include <is_header.h>
+#include <valid_mailhost_addr.h>
+#include <dsn_util.h>
+#include <conv_time.h>
+#include <xtext.h>
+#include <smtp_stream.h>
+#include <attr_override.h>
+#include <map_search.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_sasl_glue.h"
+#include "smtpd_check.h"
+#include "smtpd_dsn_fix.h"
+#include "smtpd_resolve.h"
+#include "smtpd_expand.h"
+
+ /*
+ * Eject seat in case of parsing problems.
+ */
+static jmp_buf smtpd_check_buf;
+
+ /*
+ * Results of restrictions. Errors are negative; see dict.h.
+ */
+#define SMTPD_CHECK_DUNNO 0 /* indifferent */
+#define SMTPD_CHECK_OK 1 /* explicitly permit */
+#define SMTPD_CHECK_REJECT 2 /* explicitly reject */
+
+ /*
+ * Intermediate results. These are static to avoid unnecessary stress on the
+ * memory manager routines.
+ */
+static VSTRING *error_text;
+static CTABLE *smtpd_rbl_cache;
+static CTABLE *smtpd_rbl_byte_cache;
+
+ /*
+ * Pre-opened SMTP recipient maps so we can reject mail for unknown users.
+ * XXX This does not belong here and will eventually become part of the
+ * trivial-rewrite resolver.
+ */
+static MAPS *local_rcpt_maps;
+static MAPS *send_canon_maps;
+static MAPS *rcpt_canon_maps;
+static MAPS *canonical_maps;
+static MAPS *virt_alias_maps;
+static MAPS *virt_mailbox_maps;
+static MAPS *relay_rcpt_maps;
+
+#ifdef TEST
+
+static STRING_LIST *virt_alias_doms;
+static STRING_LIST *virt_mailbox_doms;
+
+#endif
+
+ /*
+ * Response templates for various rbl domains.
+ */
+static MAPS *rbl_reply_maps;
+
+ /*
+ * Pre-opened sender to login name mapping.
+ */
+static MAPS *smtpd_sender_login_maps;
+
+ /*
+ * Pre-opened access control lists.
+ */
+static DOMAIN_LIST *relay_domains;
+static NAMADR_LIST *mynetworks_curr;
+static NAMADR_LIST *mynetworks_new;
+static NAMADR_LIST *perm_mx_networks;
+
+#ifdef USE_TLS
+static MAPS *relay_ccerts;
+
+#endif
+
+ /*
+ * How to do parent domain wildcard matching, if any.
+ */
+static int access_parent_style;
+
+ /*
+ * Pre-parsed restriction lists.
+ */
+static ARGV *client_restrctions;
+static ARGV *helo_restrctions;
+static ARGV *mail_restrctions;
+static ARGV *relay_restrctions;
+static ARGV *fake_relay_restrctions;
+static ARGV *rcpt_restrctions;
+static ARGV *etrn_restrctions;
+static ARGV *data_restrctions;
+static ARGV *eod_restrictions;
+
+static HTABLE *smtpd_rest_classes;
+static HTABLE *policy_clnt_table;
+static HTABLE *map_command_table;
+
+static ARGV *local_rewrite_clients;
+
+ /*
+ * The routine that recursively applies restrictions.
+ */
+static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *);
+
+ /*
+ * Recipient table check.
+ */
+static int check_sender_rcpt_maps(SMTPD_STATE *, const char *);
+static int check_recipient_rcpt_maps(SMTPD_STATE *, const char *);
+static int check_rcpt_maps(SMTPD_STATE *, const char *, const char *,
+ const char *);
+
+ /*
+ * Tempfail actions;
+ */
+static int unk_name_tf_act;
+static int unk_addr_tf_act;
+static int unv_rcpt_tf_act;
+static int unv_from_tf_act;
+
+ /*
+ * Optional permit logging.
+ */
+static STRING_LIST *smtpd_acl_perm_log;
+
+ /*
+ * YASLM.
+ */
+#define STR vstring_str
+#define CONST_STR(x) ((const char *) vstring_str(x))
+#define UPDATE_STRING(ptr,val) { if (ptr) myfree(ptr); ptr = mystrdup(val); }
+
+ /*
+ * If some decision can't be made due to a temporary error, then change
+ * other decisions into deferrals.
+ *
+ * XXX Deferrals can be postponed only with restrictions that are based on
+ * client-specified information: this restricts their use to parameters
+ * given in HELO, MAIL FROM, RCPT TO commands.
+ *
+ * XXX Deferrals must not be postponed after client hostname lookup failure.
+ * The reason is that the effect of access tables may depend on whether a
+ * client hostname is available or not. Thus, the reject_unknown_client
+ * restriction must defer immediately when lookup fails, otherwise incorrect
+ * results happen with:
+ *
+ * reject_unknown_client, hostname-based allow-list, reject
+ *
+ * XXX With warn_if_reject, don't raise the defer_if_permit flag when a
+ * reject-style restriction fails. Instead, log the warning for the
+ * resulting defer message.
+ *
+ * XXX With warn_if_reject, do raise the defer_if_reject flag when a
+ * permit-style restriction fails. Otherwise, we could reject legitimate
+ * mail.
+ */
+static int PRINTFLIKE(5, 6) defer_if(SMTPD_DEFER *, int, int, const char *, const char *,...);
+static int PRINTFLIKE(5, 6) smtpd_check_reject(SMTPD_STATE *, int, int, const char *, const char *,...);
+
+#define DEFER_IF_REJECT2(state, class, code, dsn, fmt, a1, a2) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2))
+#define DEFER_IF_REJECT3(state, class, code, dsn, fmt, a1, a2, a3) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3))
+#define DEFER_IF_REJECT4(state, class, code, dsn, fmt, a1, a2, a3, a4) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4))
+
+ /*
+ * The following choose between DEFER_IF_PERMIT (only if warn_if_reject is
+ * turned off) and plain DEFER. See tempfail_actions[] below for the mapping
+ * from names to numeric action code.
+ */
+#define DEFER_ALL_ACT 0
+#define DEFER_IF_PERMIT_ACT 1
+
+#define DEFER_IF_PERMIT2(type, state, class, code, dsn, fmt, a1, a2) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2)))
+#define DEFER_IF_PERMIT3(type, state, class, code, dsn, fmt, a1, a2, a3) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3)))
+#define DEFER_IF_PERMIT4(type, state, class, code, dsn, fmt, a1, a2, a3, a4) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)))
+
+ /*
+ * Cached RBL lookup state.
+ */
+typedef struct {
+ char *txt; /* TXT content or NULL */
+ DNS_RR *a; /* A records */
+} SMTPD_RBL_STATE;
+
+static void *rbl_pagein(const char *, void *);
+static void rbl_pageout(void *, void *);
+static void *rbl_byte_pagein(const char *, void *);
+static void rbl_byte_pageout(void *, void *);
+
+ /*
+ * Context for RBL $name expansion.
+ */
+typedef struct {
+ SMTPD_STATE *state; /* general state */
+ char *domain; /* query domain */
+ const char *what; /* rejected value */
+ const char *class; /* name of rejected value */
+ const char *txt; /* randomly selected trimmed TXT rr */
+} SMTPD_RBL_EXPAND_CONTEXT;
+
+ /*
+ * Multiplication factor for free space check. Free space must be at least
+ * smtpd_space_multf * message_size_limit.
+ */
+double smtpd_space_multf = 1.5;
+
+ /*
+ * SMTPD policy client. Most attributes are ATTR_CLNT attributes.
+ */
+typedef struct {
+ ATTR_CLNT *client; /* client handle */
+ char *def_action; /* default action */
+ char *policy_context; /* context of policy request */
+} SMTPD_POLICY_CLNT;
+
+ /*
+ * Table-driven parsing of main.cf parameter overrides for specific policy
+ * clients. We derive the override names from the corresponding main.cf
+ * parameter names by skipping the redundant "smtpd_policy_service_" prefix.
+ */
+static ATTR_OVER_TIME time_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_INT int_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_REQ_LIMIT, 0, 0, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TRY_LIMIT, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_STR str_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_DEF_ACTION, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_CONTEXT, 0, 1, 0,
+ 0,
+};
+
+#define link_override_table_to_variable(table, var) \
+ do { table[var##_offset].target = &var; } while (0)
+
+#define smtpd_policy_tmout_offset 0
+#define smtpd_policy_idle_offset 1
+#define smtpd_policy_ttl_offset 2
+#define smtpd_policy_try_delay_offset 3
+
+#define smtpd_policy_req_limit_offset 0
+#define smtpd_policy_try_limit_offset 1
+
+#define smtpd_policy_def_action_offset 0
+#define smtpd_policy_context_offset 1
+
+ /*
+ * Search order names must be distinct, non-empty, and non-null.
+ */
+#define SMTPD_ACL_SEARCH_NAME_CERT_FPRINT "cert_fingerprint"
+#define SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT "pubkey_fingerprint"
+#define SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN "issuer_cn"
+#define SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN "subject_cn"
+
+ /*
+ * Search order tokens must be distinct, and 1..126 inclusive, so that they
+ * can be stored in a character string without concerns about signed versus
+ * unsigned. Code 127 is reserved by map_search(3).
+ */
+#define SMTPD_ACL_SEARCH_CODE_CERT_FPRINT 1
+#define SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT 2
+#define SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN 3
+#define SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN 4
+
+ /*
+ * Mapping from search-list names and to search-list codes.
+ */
+static const NAME_CODE search_actions[] = {
+ SMTPD_ACL_SEARCH_NAME_CERT_FPRINT, SMTPD_ACL_SEARCH_CODE_CERT_FPRINT,
+ SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT, SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT,
+ SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN, SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN,
+ SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN, SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN,
+ 0, MAP_SEARCH_CODE_UNKNOWN,
+};
+
+/* policy_client_register - register policy service endpoint */
+
+static void policy_client_register(const char *name)
+{
+ static const char myname[] = "policy_client_register";
+ SMTPD_POLICY_CLNT *policy_client;
+ char *saved_name = 0;
+ const char *policy_name = 0;
+ char *cp;
+ const char *sep = CHARS_COMMA_SP;
+ const char *parens = CHARS_BRACE;
+ char *err;
+
+ if (policy_clnt_table == 0)
+ policy_clnt_table = htable_create(1);
+
+ if (htable_find(policy_clnt_table, name) == 0) {
+
+ /*
+ * Allow per-service overrides for main.cf global settings.
+ */
+ int smtpd_policy_tmout = var_smtpd_policy_tmout;
+ int smtpd_policy_idle = var_smtpd_policy_idle;
+ int smtpd_policy_ttl = var_smtpd_policy_ttl;
+ int smtpd_policy_try_delay = var_smtpd_policy_try_delay;
+ int smtpd_policy_req_limit = var_smtpd_policy_req_limit;
+ int smtpd_policy_try_limit = var_smtpd_policy_try_limit;
+ const char *smtpd_policy_def_action = var_smtpd_policy_def_action;
+ const char *smtpd_policy_context = var_smtpd_policy_context;
+
+ link_override_table_to_variable(time_table, smtpd_policy_tmout);
+ link_override_table_to_variable(time_table, smtpd_policy_idle);
+ link_override_table_to_variable(time_table, smtpd_policy_ttl);
+ link_override_table_to_variable(time_table, smtpd_policy_try_delay);
+ link_override_table_to_variable(int_table, smtpd_policy_req_limit);
+ link_override_table_to_variable(int_table, smtpd_policy_try_limit);
+ link_override_table_to_variable(str_table, smtpd_policy_def_action);
+ link_override_table_to_variable(str_table, smtpd_policy_context);
+
+ if (*name == parens[0]) {
+ cp = saved_name = mystrdup(name);
+ if ((err = extpar(&cp, parens, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("policy service syntax error: %s", cp);
+ if ((policy_name = mystrtok(&cp, sep)) == 0)
+ msg_fatal("empty policy service: \"%s\"", name);
+ attr_override(cp, sep, parens,
+ CA_ATTR_OVER_TIME_TABLE(time_table),
+ CA_ATTR_OVER_INT_TABLE(int_table),
+ CA_ATTR_OVER_STR_TABLE(str_table),
+ CA_ATTR_OVER_END);
+ } else {
+ policy_name = name;
+ }
+ if (msg_verbose)
+ msg_info("%s: name=\"%s\" default_action=\"%s\" max_idle=%d "
+ "max_ttl=%d request_limit=%d retry_delay=%d "
+ "timeout=%d try_limit=%d policy_context=\"%s\"",
+ myname, policy_name, smtpd_policy_def_action,
+ smtpd_policy_idle, smtpd_policy_ttl,
+ smtpd_policy_req_limit, smtpd_policy_try_delay,
+ smtpd_policy_tmout, smtpd_policy_try_limit,
+ smtpd_policy_context);
+
+ /*
+ * Create the client.
+ */
+ policy_client = (SMTPD_POLICY_CLNT *) mymalloc(sizeof(*policy_client));
+ policy_client->client = attr_clnt_create(policy_name,
+ smtpd_policy_tmout,
+ smtpd_policy_idle,
+ smtpd_policy_ttl);
+
+ attr_clnt_control(policy_client->client,
+ ATTR_CLNT_CTL_REQ_LIMIT, smtpd_policy_req_limit,
+ ATTR_CLNT_CTL_TRY_LIMIT, smtpd_policy_try_limit,
+ ATTR_CLNT_CTL_TRY_DELAY, smtpd_policy_try_delay,
+ ATTR_CLNT_CTL_END);
+ policy_client->def_action = mystrdup(smtpd_policy_def_action);
+ policy_client->policy_context = mystrdup(smtpd_policy_context);
+ htable_enter(policy_clnt_table, name, (void *) policy_client);
+ if (saved_name)
+ myfree(saved_name);
+ }
+}
+
+/* command_map_register - register access table for maps lookup */
+
+static void command_map_register(const char *name)
+{
+ MAPS *maps;
+
+ if (map_command_table == 0)
+ map_command_table = htable_create(1);
+
+ if (htable_find(map_command_table, name) == 0) {
+ maps = maps_create(name, name, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ (void) htable_enter(map_command_table, name, (void *) maps);
+ }
+}
+
+/* smtpd_check_parse - pre-parse restrictions */
+
+static ARGV *smtpd_check_parse(int flags, const char *checks)
+{
+ char *saved_checks = mystrdup(checks);
+ ARGV *argv = argv_alloc(1);
+ char *bp = saved_checks;
+ char *name;
+ char *last = 0;
+ const MAP_SEARCH *map_search;
+
+ /*
+ * Pre-parse the restriction list, and open any dictionaries that we
+ * encounter. Dictionaries must be opened before entering the chroot
+ * jail.
+ */
+#define SMTPD_CHECK_PARSE_POLICY (1<<0)
+#define SMTPD_CHECK_PARSE_MAPS (1<<1)
+#define SMTPD_CHECK_PARSE_ALL (~0)
+
+ while ((name = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ argv_add(argv, name, (char *) 0);
+ if ((flags & SMTPD_CHECK_PARSE_POLICY)
+ && last && strcasecmp(last, CHECK_POLICY_SERVICE) == 0) {
+ policy_client_register(name);
+ } else if ((flags & SMTPD_CHECK_PARSE_MAPS)
+ && (*name == *CHARS_BRACE || strchr(name, ':') != 0)) {
+ if ((map_search = map_search_create(name)) != 0)
+ command_map_register(map_search->map_type_name);
+ }
+ last = name;
+ }
+ argv_terminate(argv);
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_checks);
+ return (argv);
+}
+
+#ifndef TEST
+
+/* has_required - make sure required restriction is present */
+
+static int has_required(ARGV *restrictions, const char **required)
+{
+ char **rest;
+ const char **reqd;
+ ARGV *expansion;
+
+ /*
+ * Recursively check list membership.
+ */
+ for (rest = restrictions->argv; *rest; rest++) {
+ if (strcasecmp(*rest, WARN_IF_REJECT) == 0 && rest[1] != 0) {
+ rest += 1;
+ continue;
+ }
+ if (strcasecmp(*rest, PERMIT_ALL) == 0) {
+ if (rest[1] != 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ rest[1], rest[0]);
+ return (0);
+ }
+ for (reqd = required; *reqd; reqd++)
+ if (strcasecmp(*rest, *reqd) == 0)
+ return (1);
+ /* XXX This lookup operation should not be case-sensitive. */
+ if ((expansion = (ARGV *) htable_find(smtpd_rest_classes, *rest)) != 0)
+ if (has_required(expansion, required))
+ return (1);
+ }
+ return (0);
+}
+
+/* fail_required - handle failure to use required restriction */
+
+static void fail_required(const char *name, const char **required)
+{
+ const char *myname = "fail_required";
+ const char **reqd;
+ VSTRING *example;
+
+ /*
+ * Sanity check.
+ */
+ if (required[0] == 0)
+ msg_panic("%s: null required list", myname);
+
+ /*
+ * Go bust.
+ */
+ example = vstring_alloc(10);
+ for (reqd = required; *reqd; reqd++)
+ vstring_sprintf_append(example, "%s%s", *reqd,
+ reqd[1] == 0 ? "" : reqd[2] == 0 ? " or " : ", ");
+ msg_fatal("in parameter %s, specify at least one working instance of: %s",
+ name, STR(example));
+}
+
+#endif
+
+/* smtpd_check_init - initialize once during process lifetime */
+
+void smtpd_check_init(void)
+{
+ char *saved_classes;
+ const char *name;
+ const char *value;
+ char *cp;
+
+#ifndef TEST
+ static const char *rcpt_required[] = {
+ REJECT_UNAUTH_DEST,
+ DEFER_UNAUTH_DEST,
+ REJECT_ALL,
+ DEFER_ALL,
+ DEFER_IF_PERMIT,
+ CHECK_RELAY_DOMAINS,
+ 0,
+ };
+
+#endif
+ static NAME_CODE tempfail_actions[] = {
+ DEFER_ALL, DEFER_ALL_ACT,
+ DEFER_IF_PERMIT, DEFER_IF_PERMIT_ACT,
+ 0, -1,
+ };
+
+ /*
+ * Pre-open access control lists before going to jail.
+ */
+ mynetworks_curr =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS), var_mynetworks);
+ mynetworks_new =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS), mynetworks_host());
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS,
+ match_parent_style(VAR_RELAY_DOMAINS),
+ var_relay_domains);
+ perm_mx_networks =
+ namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_PERM_MX_NETWORKS),
+ var_perm_mx_networks);
+#ifdef USE_TLS
+ relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_smtpd_relay_ccerts,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+#endif
+
+ /*
+ * Pre-parse and pre-open the recipient maps.
+ */
+ local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ virt_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS,
+ var_virt_mailbox_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ relay_rcpt_maps = maps_create(VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+#ifdef TEST
+ virt_alias_doms = string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_NONE,
+ var_virt_alias_doms);
+ virt_mailbox_doms = string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_NONE,
+ var_virt_mailbox_doms);
+#endif
+
+ access_parent_style = match_parent_style(SMTPD_ACCESS_MAPS);
+
+ /*
+ * Templates for RBL rejection replies.
+ */
+ rbl_reply_maps = maps_create(VAR_RBL_REPLY_MAPS, var_rbl_reply_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * Sender to login name mapping.
+ */
+ smtpd_sender_login_maps = maps_create(VAR_SMTPD_SND_AUTH_MAPS,
+ var_smtpd_snd_auth_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * error_text is used for returning error responses.
+ */
+ error_text = vstring_alloc(10);
+
+ /*
+ * Initialize the resolved address cache. Note: the cache persists across
+ * SMTP sessions so we cannot make it dependent on session state.
+ */
+ smtpd_resolve_init(100);
+
+ /*
+ * Initialize the RBL lookup cache. Note: the cache persists across SMTP
+ * sessions so we cannot make it dependent on session state.
+ */
+ smtpd_rbl_cache = ctable_create(100, rbl_pagein, rbl_pageout, (void *) 0);
+ smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein,
+ rbl_byte_pageout, (void *) 0);
+
+ /*
+ * Initialize access map search list support before parsing restriction
+ * lists.
+ */
+ map_search_init(search_actions);
+
+ /*
+ * Pre-parse the restriction lists. At the same time, pre-open tables
+ * before going to jail.
+ */
+ client_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_client_checks);
+ helo_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_helo_checks);
+ mail_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_mail_checks);
+ relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_relay_checks);
+ if (warn_compat_break_relay_restrictions)
+ fake_relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ FAKE_RELAY_CHECKS);
+ rcpt_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_rcpt_checks);
+ etrn_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_etrn_checks);
+ data_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_data_checks);
+ eod_restrictions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_eod_checks);
+
+ /*
+ * Parse the pre-defined restriction classes.
+ */
+ smtpd_rest_classes = htable_create(1);
+ if (*var_rest_classes) {
+ cp = saved_classes = mystrdup(var_rest_classes);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if ((value = mail_conf_lookup_eval(name)) == 0 || *value == 0)
+ msg_fatal("restriction class `%s' needs a definition", name);
+ /* XXX This store operation should not be case-sensitive. */
+ htable_enter(smtpd_rest_classes, name,
+ (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ value));
+ }
+ myfree(saved_classes);
+ }
+
+ /*
+ * This is the place to specify definitions for complex restrictions such
+ * as check_relay_domains in terms of more elementary restrictions.
+ */
+#if 0
+ htable_enter(smtpd_rest_classes, "check_relay_domains",
+ smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ "permit_mydomain reject_unauth_destination"));
+#endif
+ htable_enter(smtpd_rest_classes, REJECT_SENDER_LOGIN_MISMATCH,
+ (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ REJECT_AUTH_SENDER_LOGIN_MISMATCH
+ " " REJECT_UNAUTH_SENDER_LOGIN_MISMATCH));
+
+ /*
+ * People screw up the relay restrictions too often. Require that they
+ * list at least one restriction that rejects mail by default. We allow
+ * relay restrictions to be empty for sites that require backwards
+ * compatibility.
+ */
+#ifndef TEST
+ if (!has_required(rcpt_restrctions, rcpt_required)
+ && !has_required(relay_restrctions, rcpt_required))
+ fail_required(VAR_RELAY_CHECKS " or " VAR_RCPT_CHECKS, rcpt_required);
+#endif
+
+ /*
+ * Local rewrite policy.
+ */
+ local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS,
+ var_local_rwr_clients);
+
+ /*
+ * Tempfail_actions.
+ *
+ * XXX This name-to-number mapping should be encapsulated in a separate
+ * mail_conf_name_code.c module.
+ */
+ if ((unk_name_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unk_name_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNK_NAME_TF_ACT, var_unk_name_tf_act);
+ if ((unk_addr_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unk_addr_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNK_ADDR_TF_ACT, var_unk_addr_tf_act);
+ if ((unv_rcpt_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unv_rcpt_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNV_RCPT_TF_ACT, var_unv_rcpt_tf_act);
+ if ((unv_from_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unv_from_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNV_FROM_TF_ACT, var_unv_from_tf_act);
+ if (msg_verbose) {
+ msg_info("%s = %s", VAR_UNK_NAME_TF_ACT, tempfail_actions[unk_name_tf_act].name);
+ msg_info("%s = %s", VAR_UNK_ADDR_TF_ACT, tempfail_actions[unk_addr_tf_act].name);
+ msg_info("%s = %s", VAR_UNV_RCPT_TF_ACT, tempfail_actions[unv_rcpt_tf_act].name);
+ msg_info("%s = %s", VAR_UNV_FROM_TF_ACT, tempfail_actions[unv_from_tf_act].name);
+ }
+
+ /*
+ * Optional permit logging.
+ */
+ smtpd_acl_perm_log = string_list_init(VAR_SMTPD_ACL_PERM_LOG,
+ MATCH_FLAG_RETURN,
+ var_smtpd_acl_perm_log);
+}
+
+/* log_whatsup - log as much context as we have */
+
+void log_whatsup(SMTPD_STATE *state, const char *whatsup,
+ const char *text)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ vstring_sprintf(buf, "%s: %s: %s from %s: %s;",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ whatsup, state->where, state->namaddr, text);
+ if (state->sender)
+ vstring_sprintf_append(buf, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recipient)
+ vstring_sprintf_append(buf, " to=<%s>",
+ info_log_addr_form_recipient(state->recipient));
+ if (state->protocol)
+ vstring_sprintf_append(buf, " proto=%s", state->protocol);
+ if (state->helo_name)
+ vstring_sprintf_append(buf, " helo=<%s>", state->helo_name);
+ msg_info("%s", STR(buf));
+ vstring_free(buf);
+}
+
+/* smtpd_acl_permit - permit request with optional logging */
+
+static int PRINTFLIKE(5, 6) smtpd_acl_permit(SMTPD_STATE *state,
+ const char *action,
+ const char *reply_class,
+ const char *reply_name,
+ const char *format,...)
+{
+ const char myname[] = "smtpd_acl_permit";
+ va_list ap;
+ const char *whatsup;
+
+#ifdef notdef
+#define NO_PRINT_ARGS ""
+#else
+#define NO_PRINT_ARGS "%s", ""
+#endif
+
+ /*
+ * First, find out if (and how) this permit action should be logged.
+ */
+ if (msg_verbose)
+ msg_info("%s: checking %s settings", myname, VAR_SMTPD_ACL_PERM_LOG);
+
+ if (state->defer_if_permit.active) {
+ /* This action is overruled. Do not log. */
+ whatsup = 0;
+ } else if (string_list_match(smtpd_acl_perm_log, action) != 0) {
+ /* This is not a test. Logging is enabled. */
+ whatsup = "permit";
+ } else {
+ /* This is not a test. Logging is disabled. */
+ whatsup = 0;
+ }
+ if (whatsup != 0) {
+ vstring_sprintf(error_text, "action=%s for %s=%s",
+ action, reply_class, reply_name);
+ if (format && *format) {
+ vstring_strcat(error_text, " ");
+ va_start(ap, format);
+ vstring_vsprintf_append(error_text, format, ap);
+ va_end(ap);
+ }
+ log_whatsup(state, whatsup, STR(error_text));
+ } else {
+ if (msg_verbose)
+ msg_info("%s: %s: no match", myname, VAR_SMTPD_ACL_PERM_LOG);
+ }
+ return (SMTPD_CHECK_OK);
+}
+
+/* smtpd_check_reject - do the boring things that must be done */
+
+static int smtpd_check_reject(SMTPD_STATE *state, int error_class,
+ int code, const char *dsn,
+ const char *format,...)
+{
+ va_list ap;
+ int warn_if_reject;
+ const char *whatsup;
+
+ /*
+ * Do not reject mail if we were asked to warn only. However,
+ * configuration/software/data errors cannot be converted into warnings.
+ */
+ if (state->warn_if_reject && error_class != MAIL_ERROR_SOFTWARE
+ && error_class != MAIL_ERROR_RESOURCE
+ && error_class != MAIL_ERROR_DATA) {
+ warn_if_reject = 1;
+ whatsup = "reject_warning";
+ } else {
+ warn_if_reject = 0;
+ whatsup = "reject";
+ }
+
+ /*
+ * Update the error class mask, and format the response. XXX What about
+ * multi-line responses? For now we cheat and send whitespace.
+ *
+ * Format the response before complaining about configuration errors, so
+ * that we can show the error in context.
+ */
+ state->error_mask |= error_class;
+ vstring_sprintf(error_text, "%d %s ", code, dsn);
+ va_start(ap, format);
+ vstring_vsprintf_append(error_text, format, ap);
+ va_end(ap);
+
+ /*
+ * Validate the response, that is, the response must begin with a
+ * three-digit status code, and the first digit must be 4 or 5. If the
+ * response is bad, log a warning and send a generic response instead.
+ */
+ if (code < 400 || code > 599) {
+ msg_warn("SMTP reply code configuration error: %s", STR(error_text));
+ vstring_strcpy(error_text, "450 4.7.1 Service unavailable");
+ }
+ if (!dsn_valid(STR(error_text) + 4)) {
+ msg_warn("DSN detail code configuration error: %s", STR(error_text));
+ vstring_strcpy(error_text, "450 4.7.1 Service unavailable");
+ }
+
+ /*
+ * Ensure RFC compliance. We could do this inside smtpd_chat_reply() and
+ * switch to multi-line for long replies.
+ */
+ vstring_truncate(error_text, 510);
+ printable(STR(error_text), ' ');
+
+ /*
+ * Force this rejection into deferral because of some earlier temporary
+ * error that may have prevented us from accepting mail, and report the
+ * earlier problem instead.
+ */
+ if (!warn_if_reject && state->defer_if_reject.active && STR(error_text)[0] == '5') {
+ state->warn_if_reject = state->defer_if_reject.active = 0;
+ return (smtpd_check_reject(state, state->defer_if_reject.class,
+ state->defer_if_reject.code,
+ STR(state->defer_if_reject.dsn),
+ "%s", STR(state->defer_if_reject.reason)));
+ }
+
+ /*
+ * Soft bounce safety net.
+ *
+ * XXX The code below also appears in the Postfix SMTP server reply output
+ * routine. It is duplicated here in order to avoid discrepancies between
+ * the reply codes that are shown in "reject" logging and the reply codes
+ * that are actually sent to the SMTP client.
+ *
+ * Implementing the soft_bounce safety net in the SMTP server reply output
+ * routine has the advantage that it covers all 5xx replies, including
+ * SMTP protocol or syntax errors, which makes soft_bounce great for
+ * non-destructive tests (especially by people who are paranoid about
+ * losing mail).
+ *
+ * We could eliminate the code duplication and implement the soft_bounce
+ * safety net only in the code below. But then the safety net would cover
+ * the UCE restrictions only. This would be at odds with documentation
+ * which says soft_bounce changes all 5xx replies into 4xx ones.
+ */
+ if (var_soft_bounce && STR(error_text)[0] == '5')
+ STR(error_text)[0] = '4';
+
+ /*
+ * In any case, enforce consistency between the SMTP code and DSN code.
+ * SMTP has the higher precedence since it came here first.
+ */
+ STR(error_text)[4] = STR(error_text)[0];
+
+ /*
+ * Log what is happening. When the sysadmin discards policy violation
+ * postmaster notices, this may be the only trace left that service was
+ * rejected. Print the request, client name/address, and response.
+ */
+ log_whatsup(state, whatsup, STR(error_text));
+
+ return (warn_if_reject ? 0 : SMTPD_CHECK_REJECT);
+}
+
+/* defer_if - prepare to change our mind */
+
+static int defer_if(SMTPD_DEFER *defer, int error_class,
+ int code, const char *dsn,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Keep the first reason for this type of deferral, to minimize
+ * confusion.
+ */
+ if (defer->active == 0) {
+ defer->active = 1;
+ defer->class = error_class;
+ defer->code = code;
+ if (defer->dsn == 0)
+ defer->dsn = vstring_alloc(10);
+ vstring_strcpy(defer->dsn, dsn);
+ if (defer->reason == 0)
+ defer->reason = vstring_alloc(10);
+ va_start(ap, fmt);
+ vstring_vsprintf(defer->reason, fmt, ap);
+ va_end(ap);
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_dict_retry - reject with temporary failure if dict lookup fails */
+
+static NORETURN reject_dict_retry(SMTPD_STATE *state, const char *reply_name)
+{
+ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_DATA,
+ 451, "4.3.0",
+ "<%s>: Temporary lookup failure",
+ reply_name));
+}
+
+/* reject_server_error - reject with temporary failure after non-dict error */
+
+static NORETURN reject_server_error(SMTPD_STATE *state)
+{
+ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
+ 451, "4.3.5",
+ "Server configuration error"));
+}
+
+/* check_mail_addr_find - reject with temporary failure if dict lookup fails */
+
+static const char *check_mail_addr_find(SMTPD_STATE *state,
+ const char *reply_name,
+ MAPS *maps, const char *key,
+ char **ext)
+{
+ const char *result;
+
+ if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0)
+ return (result);
+ if (maps->error == DICT_ERR_RETRY)
+ /* Warning is already logged. */
+ reject_dict_retry(state, reply_name);
+ else
+ reject_server_error(state);
+}
+
+/* reject_unknown_reverse_name - fail if reverse client hostname is unknown */
+
+static int reject_unknown_reverse_name(SMTPD_STATE *state)
+{
+ const char *myname = "reject_unknown_reverse_name";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->reverse_name);
+
+ if (state->reverse_name_status != SMTPD_PEER_CODE_OK)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ state->reverse_name_status == SMTPD_PEER_CODE_PERM ?
+ var_unk_client_code : 450, "4.7.1",
+ "Client host rejected: cannot find your reverse hostname, [%s]",
+ state->addr));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unknown_client - fail if client hostname is unknown */
+
+static int reject_unknown_client(SMTPD_STATE *state)
+{
+ const char *myname = "reject_unknown_client";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ /* RFC 7372: Email Authentication Status Codes. */
+ if (state->name_status != SMTPD_PEER_CODE_OK)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ state->name_status >= SMTPD_PEER_CODE_PERM ?
+ var_unk_client_code : 450, "4.7.25",
+ "Client host rejected: cannot find your hostname, [%s]",
+ state->addr));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_plaintext_session - fail if session is not encrypted */
+
+static int reject_plaintext_session(SMTPD_STATE *state)
+{
+ const char *myname = "reject_plaintext_session";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+#ifdef USE_TLS
+ if (state->tls_context == 0)
+#endif
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_plaintext_code, "4.7.1",
+ "Session encryption is required"));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* permit_inet_interfaces - succeed if client my own address */
+
+static int permit_inet_interfaces(SMTPD_STATE *state)
+{
+ const char *myname = "permit_inet_interfaces";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ if (own_inet_addr((struct sockaddr *) &(state->sockaddr)))
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* permit_mynetworks - succeed if client is in a trusted network */
+
+static int permit_mynetworks(SMTPD_STATE *state)
+{
+ const char *myname = "permit_mynetworks";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ if (namadr_list_match(mynetworks_curr, state->name, state->addr)) {
+ if (warn_compat_break_mynetworks_style
+ && !namadr_list_match(mynetworks_new, state->name, state->addr))
+ msg_info("using backwards-compatible default setting "
+ VAR_MYNETWORKS_STYLE "=%s to permit request from "
+ "client \"%s\"", var_mynetworks_style, state->namaddr);
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ } else if (mynetworks_curr->error == 0)
+ return (SMTPD_CHECK_DUNNO);
+ else
+ return (mynetworks_curr->error);
+}
+
+/* dup_if_truncate - save hostname and truncate if it ends in dot */
+
+static char *dup_if_truncate(char *name)
+{
+ ssize_t len;
+ char *result;
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ *
+ * XXX This should not be distributed all over the code. Problem is,
+ * addresses can enter the system via multiple paths: networks, local
+ * forward/alias/include files, even as the result of address rewriting.
+ */
+ if ((len = strlen(name)) > 1
+ && name[len - 1] == '.'
+ && name[len - 2] != '.') {
+ result = mystrndup(name, len - 1);
+ } else
+ result = name;
+ return (result);
+}
+
+/* reject_invalid_hostaddr - fail if host address is incorrect */
+
+static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_invalid_hostaddr";
+ ssize_t len;
+ char *test_addr;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ if (addr[0] == '[' && (len = strlen(addr)) > 2 && addr[len - 1] == ']') {
+ test_addr = mystrndup(addr + 1, len - 2);
+ } else
+ test_addr = addr;
+
+ /*
+ * Validate the address.
+ */
+ if (!valid_mailhost_addr(test_addr, DONT_GRIPE))
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_bad_name_code, "5.5.2",
+ "<%s>: %s rejected: invalid ip address",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_addr != addr)
+ myfree(test_addr);
+
+ return (stat);
+}
+
+/* reject_invalid_hostname - fail if host/domain syntax is incorrect */
+
+static int reject_invalid_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_invalid_hostname";
+ char *test_name;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ */
+ test_name = dup_if_truncate(name);
+
+ /*
+ * Validate the HELO/EHLO hostname. Fix 20140706: EAI not allowed here.
+ */
+ if (!valid_hostname(test_name, DONT_GRIPE)
+ && !valid_hostaddr(test_name, DONT_GRIPE)) /* XXX back compat */
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_bad_name_code, "5.5.2",
+ "<%s>: %s rejected: Invalid name",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_name != name)
+ myfree(test_name);
+
+ return (stat);
+}
+
+/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */
+
+static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_non_fqdn_hostname";
+ char *test_name;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ */
+ test_name = dup_if_truncate(name);
+
+ /*
+ * Validate the hostname. For backwards compatibility, permit non-ASCII
+ * names only when the client requested SMTPUTF8 support.
+ */
+ if (valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8,
+ test_name, DONT_GRIPE) == 0 || strchr(test_name, '.') == 0)
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_non_fqdn_code, "5.5.2",
+ "<%s>: %s rejected: need fully-qualified hostname",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_name != name)
+ myfree(test_name);
+
+ return (stat);
+}
+
+/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */
+
+static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_unknown_hostname";
+ int dns_status;
+ DNS_RR *dummy;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+#ifdef T_AAAA
+#define RR_ADDR_TYPES T_A, T_AAAA
+#else
+#define RR_ADDR_TYPES T_A
+#endif
+
+ dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0,
+ (VSTRING *) 0, DNS_REQ_FLAG_STOP_OK,
+ RR_ADDR_TYPES, T_MX, 0);
+ if (dummy)
+ dns_rr_free(dummy);
+ /* Allow MTA names to have nullMX records. */
+ if (dns_status != DNS_OK && dns_status != DNS_NULLMX) {
+ if (dns_status == DNS_POLICY) {
+ msg_warn("%s: address or MX lookup error: %s",
+ name, "DNS reply filter drops all results");
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (dns_status != DNS_RETRY)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_unk_name_code, "4.7.1",
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ dns_status == DNS_INVAL ?
+ "Malformed DNS server reply" :
+ "Host not found"));
+ else
+ return (DEFER_IF_PERMIT2(unk_name_tf_act, state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: Host not found",
+ reply_name, reply_class));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */
+
+static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unknown_mailhost";
+ int dns_status;
+ DNS_RR *dummy;
+ const char *aname;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ name = aname;
+ }
+#endif
+
+#define MAILHOST_LOOKUP_FLAGS \
+ (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL | \
+ DNS_REQ_FLAG_STOP_NULLMX | DNS_REQ_FLAG_STOP_MX_POLICY)
+
+ dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0,
+ (VSTRING *) 0, MAILHOST_LOOKUP_FLAGS,
+ T_MX, RR_ADDR_TYPES, 0);
+ if (dummy)
+ dns_rr_free(dummy);
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ if (dns_status == DNS_POLICY) {
+ msg_warn("%s: MX or address lookup error: %s",
+ name, "DNS reply filter drops all results");
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (dns_status == DNS_NULLMX)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ 550 : 556,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.7.27" : "4.1.10",
+ "<%s>: %s rejected: Domain %s "
+ "does not accept mail (nullMX)",
+ reply_name, reply_class, name));
+ if (dns_status != DNS_RETRY)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_unk_addr_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.8" : "4.1.2",
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ dns_status == DNS_INVAL ?
+ "Malformed DNS server reply" :
+ "Domain not found"));
+ else
+ return (DEFER_IF_PERMIT2(unk_addr_tf_act, state, MAIL_ERROR_POLICY,
+ 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.8" : "4.1.2",
+ "<%s>: %s rejected: Domain not found",
+ reply_name, reply_class));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+static int permit_auth_destination(SMTPD_STATE *state, char *recipient);
+
+/* permit_tls_clientcerts - OK/DUNNO for message relaying, or set dict_errno */
+
+static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
+{
+#ifdef USE_TLS
+ const char *found = 0;
+
+ if (!state->tls_context)
+ return SMTPD_CHECK_DUNNO;
+
+ if (TLS_CERT_IS_TRUSTED(state->tls_context) && permit_all_certs) {
+ if (msg_verbose)
+ msg_info("Relaying allowed for all verified client certificates");
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ int i;
+ char *prints[2];
+
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+
+ prints[0] = state->tls_context->peer_cert_fprint;
+ prints[1] = state->tls_context->peer_pkey_fprint;
+
+ /* After lookup error, leave relay_ccerts->error at non-zero value. */
+ for (i = 0; i < 2; ++i) {
+ found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE);
+ if (found != 0) {
+ if (msg_verbose)
+ msg_info("Relaying allowed for certified client: %s", found);
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ } else if (relay_ccerts->error != 0) {
+ msg_warn("relay_clientcerts: lookup error for fingerprint '%s', "
+ "pkey fingerprint %s", prints[0], prints[1]);
+ return (relay_ccerts->error);
+ }
+ }
+ if (msg_verbose)
+ msg_info("relay_clientcerts: No match for fingerprint '%s', "
+ "pkey fingerprint %s", prints[0], prints[1]);
+ } else if (!var_smtpd_tls_ask_ccert) {
+ msg_warn("%s is requested, but \"%s = no\"", permit_all_certs ?
+ PERMIT_TLS_ALL_CLIENTCERTS : PERMIT_TLS_CLIENTCERTS,
+ VAR_SMTPD_TLS_ACERT);
+ }
+#endif
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* check_relay_domains - OK/FAIL for message relaying */
+
+static int check_relay_domains(SMTPD_STATE *state, char *recipient,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "check_relay_domains";
+
+#if 1
+ static int once;
+
+ if (once == 0) {
+ once = 1;
+ msg_warn("support for restriction \"%s\" will be removed from %s; "
+ "use \"%s\" instead",
+ CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST);
+ }
+#endif
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Permit if the client matches the relay_domains list.
+ */
+ if (domain_list_match(relay_domains, state->name)) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to permit "
+ "request from client \"%s\"", state->name);
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * Permit authorized destinations.
+ */
+ if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_OK);
+
+ /*
+ * Deny relaying between sites that both are not in relay_domains.
+ */
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_relay_code, "5.7.1",
+ "<%s>: %s rejected: Relay access denied",
+ reply_name, reply_class));
+}
+
+/* permit_auth_destination - OK for message relaying */
+
+static int permit_auth_destination(SMTPD_STATE *state, char *recipient)
+{
+ const char *myname = "permit_auth_destination";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(state->sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * Handle special case that is not supposed to happen.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_OK);
+ domain += 1;
+
+ /*
+ * Skip source-routed non-local or virtual mail (uncertain destination).
+ */
+ if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Permit final delivery: the destination matches mydestination,
+ * virtual_alias_domains, or virtual_mailbox_domains.
+ */
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_OK);
+
+ /*
+ * Permit if the destination matches the relay_domains list.
+ */
+ if (reply->flags & RESOLVE_CLASS_RELAY) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for domain \"%s\"", domain);
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * Skip when not matched
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unauth_destination - FAIL for message relaying */
+
+static int reject_unauth_destination(SMTPD_STATE *state, char *recipient,
+ int reply_code, const char *reply_dsn)
+{
+ const char *myname = "reject_unauth_destination";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Skip authorized destination.
+ */
+ if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Reject relaying to sites that are not listed in relay_domains.
+ */
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ reply_code, reply_dsn,
+ "<%s>: Relay access denied",
+ recipient));
+}
+
+/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */
+
+static int reject_unauth_pipelining(SMTPD_STATE *state,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unauth_pipelining";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->where);
+
+ if (state->flags & SMTPD_FLAG_ILL_PIPELINING)
+ return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL,
+ 503, "5.5.0",
+ "<%s>: %s rejected: Improper use of SMTP command pipelining",
+ reply_name, reply_class));
+
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* all_auth_mx_addr - match host addresses against permit_mx_backup_networks */
+
+static int all_auth_mx_addr(SMTPD_STATE *state, char *host,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "all_auth_mx_addr";
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *rr;
+ DNS_RR *addr_list;
+ int dns_status;
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * If we can't lookup the host, defer.
+ */
+#define NOPE 0
+#define YUP 1
+
+ /*
+ * Verify that all host addresses are within permit_mx_backup_networks.
+ */
+ dns_status = dns_lookup_v(host, 0, &addr_list, (VSTRING *) 0, (VSTRING *) 0,
+ DNS_REQ_FLAG_NONE, inet_proto_info()->dns_atype_list);
+ /* DNS_NULLMX is not applicable here. */
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up host "
+ "%s as mail exchanger: %s",
+ reply_name, reply_class, host,
+ dns_status == DNS_POLICY ?
+ "DNS reply filter policy" :
+ dns_strerror(dns_get_h_errno()));
+ return (NOPE);
+ }
+ for (rr = addr_list; rr != 0; rr = rr->next) {
+ if (dns_rr_to_pa(rr, &hostaddr) == 0) {
+ msg_warn("%s: skipping record type %s for host %s: %m",
+ myname, dns_strtype(rr->type), host);
+ continue;
+ }
+ if (msg_verbose)
+ msg_info("%s: checking: %s", myname, hostaddr.buf);
+
+ if (!namadr_list_match(perm_mx_networks, host, hostaddr.buf)) {
+ if (perm_mx_networks->error == 0) {
+
+ /*
+ * Reject: at least one IP address is not listed in
+ * permit_mx_backup_networks.
+ */
+ if (msg_verbose)
+ msg_info("%s: address %s for %s does not match %s",
+ myname, hostaddr.buf, host, VAR_PERM_MX_NETWORKS);
+ } else {
+ msg_warn("%s: %s lookup error for address %s for %s",
+ myname, VAR_PERM_MX_NETWORKS, hostaddr.buf, host);
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to verify host %s as mail exchanger",
+ reply_name, reply_class, host);
+ }
+ dns_rr_free(addr_list);
+ return (NOPE);
+ }
+ }
+ dns_rr_free(addr_list);
+ return (YUP);
+}
+
+/* has_my_addr - see if this host name lists one of my network addresses */
+
+static int has_my_addr(SMTPD_STATE *state, const char *host,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "has_my_addr";
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ MAI_HOSTADDR_STR hostaddr;
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * If we can't lookup the host, defer rather than reject.
+ */
+#define YUP 1
+#define NOPE 0
+
+ aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0);
+ if (aierr) {
+ DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up mail exchanger host %s: %s",
+ reply_name, reply_class, host, MAI_STRERROR(aierr));
+ return (NOPE);
+ }
+#define HAS_MY_ADDR_RETURN(x) { freeaddrinfo(res0); return (x); }
+
+ for (res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ if (msg_verbose)
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: addr %s", myname, hostaddr.buf);
+ }
+ if (own_inet_addr(res->ai_addr))
+ HAS_MY_ADDR_RETURN(YUP);
+ if (proxy_inet_addr(res->ai_addr))
+ HAS_MY_ADDR_RETURN(YUP);
+ }
+ if (msg_verbose)
+ msg_info("%s: host %s: no match", myname, host);
+
+ HAS_MY_ADDR_RETURN(NOPE);
+}
+
+/* i_am_mx - is this machine listed as MX relay */
+
+static int i_am_mx(SMTPD_STATE *state, DNS_RR *mx_list,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "i_am_mx";
+ DNS_RR *mx;
+
+ /*
+ * Compare hostnames first. Only if no name match is found, go through
+ * the trouble of host address lookups.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (msg_verbose)
+ msg_info("%s: resolve hostname: %s", myname, (char *) mx->data);
+ if (resolve_local((char *) mx->data) > 0)
+ return (YUP);
+ /* if no match or error, match interface addresses instead. */
+ }
+
+ /*
+ * Argh. Do further DNS lookups and match interface addresses.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (msg_verbose)
+ msg_info("%s: address lookup: %s", myname, (char *) mx->data);
+ if (has_my_addr(state, (char *) mx->data, reply_name, reply_class))
+ return (YUP);
+ }
+
+ /*
+ * This machine is not listed as MX relay.
+ */
+ if (msg_verbose)
+ msg_info("%s: I am not listed as MX relay", myname);
+ return (NOPE);
+}
+
+/* permit_mx_primary - authorize primary MX relays */
+
+static int permit_mx_primary(SMTPD_STATE *state, DNS_RR *mx_list,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "permit_mx_primary";
+ DNS_RR *mx;
+
+ if (msg_verbose)
+ msg_info("%s", myname);
+
+ /*
+ * See if each best MX host has all IP addresses in
+ * permit_mx_backup_networks.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (!all_auth_mx_addr(state, (char *) mx->data, reply_name, reply_class))
+ return (NOPE);
+ }
+
+ /*
+ * All IP addresses of the best MX hosts are within
+ * permit_mx_backup_networks.
+ */
+ return (YUP);
+}
+
+/* permit_mx_backup - permit use of me as MX backup for recipient domain */
+
+static int permit_mx_backup(SMTPD_STATE *state, const char *recipient,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "permit_mx_backup";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+ const char *adomain;
+ DNS_RR *mx_list;
+ DNS_RR *middle;
+ DNS_RR *rest;
+ int dns_status;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(state->sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * For backwards compatibility, emulate permit_auth_destination. However,
+ * old permit_mx_backup implementations allow source routing with local
+ * address class.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_OK);
+ domain += 1;
+#if 0
+ if (reply->flags & RESOLVE_CLASS_LOCAL)
+ return (SMTPD_CHECK_OK);
+#endif
+ if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
+ return (SMTPD_CHECK_DUNNO);
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_OK);
+ if (reply->flags & RESOLVE_CLASS_RELAY) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for domain \"%s\"", domain);
+ return (SMTPD_CHECK_OK);
+ }
+ if (msg_verbose)
+ msg_info("%s: not local: %s", myname, recipient);
+
+ /*
+ * Skip numerical forms that didn't match the local system.
+ */
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+
+ /*
+ * Look up the list of MX host names for this domain. If no MX host is
+ * found, perhaps it is a CNAME for the local machine. Clients aren't
+ * supposed to send CNAMEs in SMTP commands, but it happens anyway. If we
+ * can't look up the destination, play safe and turn reject into defer.
+ */
+ dns_status = dns_lookup(domain, T_MX, 0, &mx_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+#if 0
+ if (dns_status == DNS_NOTFOUND)
+ return (has_my_addr(state, domain, reply_name, reply_class) ?
+ SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO);
+#endif
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ /* We don't special-case DNS_NULLMX. */
+ if (dns_status == DNS_RETRY || dns_status == DNS_POLICY)
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up mail "
+ "exchanger information: %s",
+ reply_name, reply_class,
+ dns_status == DNS_POLICY ?
+ "DNS reply filter policy" :
+ dns_strerror(dns_get_h_errno()));
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * Separate MX list into primaries and backups.
+ */
+ mx_list = dns_rr_sort(mx_list, dns_rr_compare_pref_any);
+ for (middle = mx_list; /* see below */ ; middle = rest) {
+ rest = middle->next;
+ if (rest == 0)
+ break;
+ if (rest->pref != mx_list->pref) {
+ middle->next = 0;
+ break;
+ }
+ }
+ /* postcondition: middle->next = 0, rest may be 0. */
+
+#define PERMIT_MX_BACKUP_RETURN(x) do { \
+ middle->next = rest; \
+ dns_rr_free(mx_list); \
+ return (x); \
+ } while (0)
+
+ /*
+ * First, see if we match any of the primary MX servers.
+ */
+ if (i_am_mx(state, mx_list, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * Then, see if we match any of the backup MX servers.
+ */
+ if (rest == 0 || !i_am_mx(state, rest, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * Optionally, see if the primary MX hosts are in a restricted list of
+ * networks.
+ */
+ if (*var_perm_mx_networks
+ && !permit_mx_primary(state, mx_list, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * The destination passed all requirements.
+ */
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_OK);
+}
+
+/* reject_non_fqdn_address - fail if address is not in fqdn form */
+
+static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_non_fqdn_address";
+ char *domain;
+ char *test_dom;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Locate the domain information.
+ */
+ if ((domain = strrchr(addr, '@')) != 0)
+ domain++;
+ else
+ domain = "";
+
+ /*
+ * Skip forms that we can't handle yet.
+ */
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Truncate names ending in dot but not dot-dot.
+ */
+ test_dom = dup_if_truncate(domain);
+
+ /*
+ * Validate the domain. For backwards compatibility, permit non-ASCII
+ * names only when the client requested SMTPUTF8 support.
+ */
+ if (!*test_dom || !valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8,
+ test_dom, DONT_GRIPE) || !strchr(test_dom, '.'))
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_non_fqdn_code, "4.5.2",
+ "<%s>: %s rejected: need fully-qualified address",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_dom != domain)
+ myfree(test_dom);
+
+ return (stat);
+}
+
+/* reject_unknown_address - fail if address does not resolve */
+
+static int reject_unknown_address(SMTPD_STATE *state, const char *addr,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unknown_address";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ state->recipient : state->sender, addr);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, addr);
+
+ /*
+ * Skip local destinations and non-DNS forms.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_DUNNO);
+ domain += 1;
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_DUNNO);
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Look up the name in the DNS.
+ */
+ return (reject_unknown_mailhost(state, domain, reply_name, reply_class));
+}
+
+/* reject_unverified_address - fail if address bounces */
+
+static int reject_unverified_address(SMTPD_STATE *state, const char *addr,
+ const char *reply_name, const char *reply_class,
+ int unv_addr_dcode, int unv_addr_rcode,
+ int unv_addr_tf_act,
+ const char *alt_reply)
+{
+ const char *myname = "reject_unverified_address";
+ VSTRING *why = vstring_alloc(10);
+ int rqst_status = SMTPD_CHECK_DUNNO;
+ int rcpt_status;
+ int verify_status;
+ int count;
+ int reject_code = 0;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Verify the address. Don't waste too much of their or our time.
+ */
+ for (count = 0; /* see below */ ; /* see below */ ) {
+ verify_status = verify_clnt_query(addr, &rcpt_status, why);
+ if (verify_status != VRFY_STAT_OK || rcpt_status != DEL_RCPT_STAT_TODO)
+ break;
+ if (++count >= var_verify_poll_count)
+ break;
+ sleep(var_verify_poll_delay);
+ }
+ if (verify_status != VRFY_STAT_OK) {
+ msg_warn("%s service failure", var_verify_service);
+ rqst_status =
+ DEFER_IF_PERMIT2(unv_addr_tf_act, state, MAIL_ERROR_POLICY,
+ 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: address verification problem",
+ reply_name, reply_class);
+ } else {
+ switch (rcpt_status) {
+ default:
+ msg_warn("unknown address verification status %d", rcpt_status);
+ break;
+ case DEL_RCPT_STAT_TODO:
+ case DEL_RCPT_STAT_DEFER:
+ reject_code = unv_addr_dcode;
+ break;
+ case DEL_RCPT_STAT_OK:
+ break;
+ case DEL_RCPT_STAT_BOUNCE:
+ reject_code = unv_addr_rcode;
+ break;
+ }
+ if (reject_code >= 400 && *alt_reply)
+ vstring_strcpy(why, alt_reply);
+ switch (reject_code / 100) {
+ case 2:
+ break;
+ case 4:
+ rqst_status =
+ DEFER_IF_PERMIT3(unv_addr_tf_act, state, MAIL_ERROR_POLICY,
+ reject_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: unverified address: %.250s",
+ reply_name, reply_class, STR(why));
+ break;
+ default:
+ if (reject_code != 0)
+ rqst_status =
+ smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ reject_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: undeliverable address: %s",
+ reply_name, reply_class, STR(why));
+ break;
+ }
+ }
+ vstring_free(why);
+ return (rqst_status);
+}
+
+/* can_delegate_action - can we delegate this to the cleanup server */
+
+#ifndef TEST
+
+static int not_in_client_helo(SMTPD_STATE *, const char *, const char *, const char *);
+
+static int can_delegate_action(SMTPD_STATE *state, const char *table,
+ const char *action, const char *reply_class)
+{
+
+ /*
+ * If we're not using the cleanup server, then there is no way that we
+ * can support actions such as FILTER or HOLD that are delegated to the
+ * cleanup server.
+ */
+ if (USE_SMTPD_PROXY(state)) {
+ msg_warn("access table %s: with %s specified, action %s is unavailable",
+ table, VAR_SMTPD_PROXY_FILT, action);
+ return (0);
+ }
+
+ /*
+ * ETRN does not receive mail so we can't store queue file records.
+ */
+ if (strcmp(state->where, SMTPD_CMD_ETRN) == 0) {
+ msg_warn("access table %s: action %s is unavailable in %s",
+ table, action, VAR_ETRN_CHECKS);
+ return (0);
+ }
+ return (not_in_client_helo(state, table, action, reply_class));
+}
+
+/* not_in_client_helo - not in client or helo restriction context */
+
+static int not_in_client_helo(SMTPD_STATE *state, const char *table,
+ const char *action,
+ const char *unused_reply_class)
+{
+
+ /*
+ * If delay_reject=no, then client and helo restrictions take effect
+ * immediately, outside any particular mail transaction context. For
+ * example, rejecting HELO does not affect subsequent mail deliveries.
+ * Thus, if delay_reject=no, client and helo actions such as FILTER or
+ * HOLD also should not affect subsequent mail deliveries. Hmm...
+ *
+ * XXX If the MAIL FROM command is rejected then we have to reset access map
+ * side effects such as FILTER.
+ */
+ if (state->sender == 0) {
+ msg_warn("access table %s: with %s=%s, "
+ "action %s is always skipped in %s or %s restrictions",
+ table, VAR_SMTPD_DELAY_REJECT, CONFIG_BOOL_NO,
+ action, SMTPD_NAME_CLIENT, SMTPD_NAME_HELO);
+ /* XXX What about ETRN? */
+ return (0);
+ }
+ return (1);
+}
+
+#endif
+
+/* check_table_result - translate table lookup result into pass/reject */
+
+static int check_table_result(SMTPD_STATE *state, const char *table,
+ const char *value, const char *datum,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_table_result";
+ int code;
+ ARGV *restrictions;
+ jmp_buf savebuf;
+ int status;
+ const char *cmd_text;
+ int cmd_len;
+ static char def_dsn[] = "5.7.1";
+ DSN_SPLIT dp;
+ static VSTRING *buf;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ /*
+ * Parse into command and text. Do not change the input.
+ */
+ cmd_text = value + strcspn(value, " \t");
+ cmd_len = cmd_text - value;
+ vstring_strncpy(buf, value, cmd_len);
+ while (*cmd_text && ISSPACE(*cmd_text))
+ cmd_text++;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s %s", myname, table, value, datum);
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ /*
+ * DUNNO means skip this table. Silently ignore optional text.
+ */
+ if (STREQUAL(value, "DUNNO", cmd_len))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * REJECT means NO. Use optional text or generate a generic error
+ * response.
+ */
+ if (STREQUAL(value, "REJECT", cmd_len)) {
+ dsn_split(&dp, "5.7.1", cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_map_reject_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+
+ /*
+ * DEFER means "try again". Use optional text or generate a generic error
+ * response.
+ */
+ if (STREQUAL(value, "DEFER", cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+ /*
+ * HANGUP. Text is optional. Drop the connection without sending any
+ * reply.
+ *
+ * Note: this is an unsupported test feature. No attempt is made to maintain
+ * compatibility between successive versions.
+ */
+ if (STREQUAL(value, "HANGUP", cmd_len)) {
+ shutdown(vstream_fileno(state->client), SHUT_RDWR);
+ log_whatsup(state, "hangup", cmd_text);
+ vstream_longjmp(state->client, SMTP_ERR_QUIET);
+ }
+
+ /*
+ * INFO. Text is optional.
+ */
+ if (STREQUAL(value, "INFO", cmd_len)) {
+ log_whatsup(state, "info", cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * WARN. Text is optional.
+ */
+ if (STREQUAL(value, "WARN", cmd_len)) {
+ log_whatsup(state, "warn", cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * FILTER means deliver to content filter. But we may still change our
+ * mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "FILTER", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "FILTER", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (*cmd_text == 0) {
+ msg_warn("access table %s entry \"%s\" has FILTER entry without value",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else if (strchr(cmd_text, ':') == 0) {
+ msg_warn("access table %s entry \"%s\" requires transport:destination",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "filter", STR(error_text));
+#ifndef TEST
+ UPDATE_STRING(state->saved_filter, cmd_text);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * HOLD means deliver later. But we may still change our mind, and
+ * reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "HOLD", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "HOLD", reply_class) == 0
+ || (state->saved_flags & CLEANUP_FLAG_HOLD))
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers HOLD action");
+ log_whatsup(state, "hold", STR(error_text));
+#ifndef TEST
+ state->saved_flags |= CLEANUP_FLAG_HOLD;
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * DELAY means deliver later. But we may still change our mind, and
+ * reject/discard the message for other reasons.
+ *
+ * This feature is deleted 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", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "DELAY", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (*cmd_text == 0) {
+ msg_warn("access table %s entry \"%s\" has DELAY entry without value",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (conv_time(cmd_text, &defer_delay, 's') == 0) {
+ msg_warn("access table %s entry \"%s\" has invalid DELAY argument \"%s\"",
+ table, datum, cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers DELAY action");
+ log_whatsup(state, "delay", STR(error_text));
+#ifndef TEST
+ state->saved_delay = defer_delay;
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+#endif
+
+ /*
+ * DISCARD means silently discard and claim successful delivery.
+ */
+ if (STREQUAL(value, "DISCARD", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "DISCARD", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers DISCARD action");
+ log_whatsup(state, "discard", STR(error_text));
+#ifndef TEST
+ state->saved_flags |= CLEANUP_FLAG_DISCARD;
+ state->discard = 1;
+#endif
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+ }
+
+ /*
+ * REDIRECT means deliver to designated recipient. But we may still
+ * change our mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "REDIRECT", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "REDIRECT", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strchr(cmd_text, '@') == 0) {
+ msg_warn("access table %s entry \"%s\" requires user@domain target",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers REDIRECT %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "redirect", STR(error_text));
+#ifndef TEST
+ UPDATE_STRING(state->saved_redirect, cmd_text);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * BCC means deliver to designated recipient. But we may still change our
+ * mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "BCC", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "BCC", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strchr(cmd_text, '@') == 0) {
+ msg_warn("access table %s entry \"%s\" requires user@domain target",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers BCC %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "bcc", STR(error_text));
+#ifndef TEST
+ if (state->saved_bcc == 0)
+ state->saved_bcc = argv_alloc(1);
+ argv_add(state->saved_bcc, cmd_text, (char *) 0);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * DEFER_IF_PERMIT changes "permit" into "maybe". Use optional text or
+ * generate a generic error response.
+ */
+ if (STREQUAL(value, DEFER_IF_PERMIT, cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ return (DEFER_IF_PERMIT3(DEFER_IF_PERMIT_ACT, state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Service unavailable"));
+ }
+
+ /*
+ * DEFER_IF_REJECT changes "reject" into "maybe". Use optional text or
+ * generate a generic error response.
+ */
+ if (STREQUAL(value, DEFER_IF_REJECT, cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * PREPEND prepends the specified message header text.
+ */
+ if (STREQUAL(value, "PREPEND", cmd_len)) {
+#ifndef TEST
+ /* XXX what about ETRN. */
+ if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) {
+ msg_warn("access table %s: action PREPEND must be used before %s",
+ table, VAR_EOD_CHECKS);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (*cmd_text == 0 || is_header(cmd_text) == 0) {
+ msg_warn("access table %s entry \"%s\" requires header: text",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ if (state->prepend == 0)
+ state->prepend = argv_alloc(1);
+ argv_add(state->prepend, cmd_text, (char *) 0);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * All-numeric result probably means OK - some out-of-band authentication
+ * mechanism uses this as time stamp.
+ */
+ if (alldig(value))
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+
+ /*
+ * 4xx or 5xx means NO as well. smtpd_check_reject() will validate the
+ * response status code.
+ *
+ * If the caller specifies an RFC 3463 enhanced status code, put it
+ * immediately after the SMTP status code as described in RFC 2034.
+ */
+ if (cmd_len == 3 && *cmd_text
+ && (value[0] == '4' || value[0] == '5')
+ && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
+ code = atoi(value);
+ def_dsn[0] = value[0];
+ dsn_split(&dp, def_dsn, cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+
+ /*
+ * OK or RELAY means YES. Ignore trailing text.
+ */
+ if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len))
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+
+ /*
+ * Unfortunately, maps must be declared ahead of time so they can be
+ * opened before we go to jail. We could insist that the RHS can only
+ * contain a pre-defined restriction class name, but that would be too
+ * restrictive. Instead we warn if an access table references any map.
+ *
+ * XXX Don't use passwd files or address rewriting maps as access tables.
+ */
+ if (strchr(value, ':') != 0) {
+ msg_warn("access table %s has entry with lookup table: %s",
+ table, value);
+ msg_warn("do not specify lookup tables inside SMTPD access maps");
+ msg_warn("define a restriction class and specify its name instead.");
+ reject_server_error(state);
+ }
+
+ /*
+ * Don't get carried away with recursion.
+ */
+ if (state->recursion > 100) {
+ msg_warn("access table %s entry %s causes unreasonable recursion",
+ table, value);
+ reject_server_error(state);
+ }
+
+ /*
+ * Recursively evaluate the restrictions given in the right-hand side. In
+ * the dark ages, an empty right-hand side meant OK. Make some
+ * discouraging comments.
+ *
+ * XXX Jump some hoops to avoid a minute memory leak in case of a file
+ * configuration error.
+ */
+#define ADDROF(x) ((char *) &(x))
+
+ restrictions = argv_splitq(value, CHARS_COMMA_SP, CHARS_BRACE);
+ memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
+ status = setjmp(smtpd_check_buf);
+ if (status != 0) {
+ argv_free(restrictions);
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ longjmp(smtpd_check_buf, status);
+ }
+ if (restrictions->argc == 0) {
+ msg_warn("access table %s entry %s has empty value",
+ table, value);
+ status = SMTPD_CHECK_OK;
+ } else {
+ status = generic_checks(state, restrictions, reply_name,
+ reply_class, def_acl);
+ }
+ argv_free(restrictions);
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), sizeof(smtpd_check_buf));
+ return (status);
+}
+
+/* check_access - table lookup without substring magic */
+
+static int check_access(SMTPD_STATE *state, const char *table, const char *name,
+ int flags, int *found, const char *reply_name,
+ const char *reply_class, const char *def_acl)
+{
+ const char *myname = "check_access";
+ const char *value;
+ MAPS *maps;
+
+#define CHK_ACCESS_RETURN(x,y) \
+ { *found = y; return(x); }
+#define FULL 0
+#define PARTIAL DICT_FLAG_FIXED
+#define FOUND 1
+#define MISSED 0
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ if ((value = maps_find(maps, name, flags)) != 0)
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_domain_access - domainname-based table lookup */
+
+static int check_domain_access(SMTPD_STATE *state, const char *table,
+ const char *domain, int flags,
+ int *found, const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_domain_access";
+ const char *name;
+ const char *next;
+ const char *value;
+ MAPS *maps;
+ int maybe_numerical = 1;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, domain);
+
+ /*
+ * Try the name and its parent domains. Including top-level domains.
+ *
+ * Helo names can end in ".". The test below avoids lookups of the empty
+ * key, because Berkeley DB cannot deal with it. [Victor Duchovni, Morgan
+ * Stanley].
+ *
+ * TODO(wietse) move to mail_domain_find library module.
+ */
+#define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); }
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ for (name = domain; *name != 0; name = next) {
+ if ((value = maps_find(maps, name, flags)) != 0)
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ /* Don't apply subdomain magic to numerical hostnames. */
+ if (maybe_numerical
+ && (maybe_numerical = valid_hostaddr(domain, DONT_GRIPE)) != 0)
+ break;
+ if ((next = strchr(name + 1, '.')) == 0)
+ break;
+ if (access_parent_style == MATCH_FLAG_PARENT)
+ next += 1;
+ flags = PARTIAL;
+ }
+ CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_addr_access - address-based table lookup */
+
+static int check_addr_access(SMTPD_STATE *state, const char *table,
+ const char *address, int flags,
+ int *found, const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_addr_access";
+ char *addr;
+ const char *value;
+ MAPS *maps;
+ int delim;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, address);
+
+ /*
+ * Try the address and its parent networks.
+ *
+ * TODO(wietse) move to mail_ipaddr_find library module.
+ */
+#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); }
+
+ addr = STR(vstring_strcpy(error_text, address));
+#ifdef HAS_IPV6
+ if (strchr(addr, ':') != 0)
+ delim = ':';
+ else
+#endif
+ delim = '.';
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ do {
+ if ((value = maps_find(maps, addr, flags)) != 0)
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ flags = PARTIAL;
+ } while (split_at_right(addr, delim));
+
+ CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_namadr_access - OK/FAIL based on host name/address lookup */
+
+static int check_namadr_access(SMTPD_STATE *state, const char *table,
+ const char *name, const char *addr,
+ int flags, int *found,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_namadr_access";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: name %s addr %s", myname, name, addr);
+
+ /*
+ * Look up the host name, or parent domains thereof. XXX A domain
+ * wildcard may pre-empt a more specific address table entry.
+ */
+ if ((status = check_domain_access(state, table, name, flags,
+ found, reply_name, reply_class,
+ def_acl)) != 0 || *found)
+ return (status);
+
+ /*
+ * Look up the network address, or parent networks thereof.
+ */
+ if ((status = check_addr_access(state, table, addr, flags,
+ found, reply_name, reply_class,
+ def_acl)) != 0 || *found)
+ return (status);
+
+ /*
+ * Undecided when the host was not found.
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* check_server_access - access control by server host name or address */
+
+static int check_server_access(SMTPD_STATE *state, const char *table,
+ const char *name,
+ int type,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_server_access";
+ const char *domain;
+ const char *adomain;
+ int dns_status;
+ DNS_RR *server_list;
+ DNS_RR *server;
+ int found = 0;
+ MAI_HOSTADDR_STR addr_string;
+ int aierr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ int status;
+ const INET_PROTO_INFO *proto_info;
+
+ /*
+ * Sanity check.
+ */
+ if (type != T_MX && type != T_NS && type != T_A
+#ifdef HAS_IPV6
+ && type != T_AAAA
+#endif
+ )
+ msg_panic("%s: unexpected resource type \"%s\" in request",
+ myname, dns_strtype(type));
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, dns_strtype(type), name);
+
+ /*
+ * Skip over local-part.
+ */
+ if ((domain = strrchr(name, '@')) != 0)
+ domain += 1;
+ else
+ domain = name;
+
+ /*
+ * Treat an address literal as its own MX server, just like we treat a
+ * name without MX record as its own MX server. There is, however, no
+ * applicable NS server equivalent.
+ */
+ if (*domain == '[') {
+ char *saved_addr;
+ const char *bare_addr;
+ ssize_t len;
+
+ if (type != T_A && type != T_MX)
+ return (SMTPD_CHECK_DUNNO);
+ len = strlen(domain);
+ if (domain[len - 1] != ']')
+ return (SMTPD_CHECK_DUNNO);
+ /* Memory leak alert: no early returns after this point. */
+ saved_addr = mystrndup(domain + 1, len - 2);
+ if ((bare_addr = valid_mailhost_addr(saved_addr, DONT_GRIPE)) == 0)
+ status = SMTPD_CHECK_DUNNO;
+ else
+ status = check_addr_access(state, table, bare_addr, FULL,
+ &found, reply_name, reply_class,
+ def_acl);
+ myfree(saved_addr);
+ return (status);
+ }
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+
+ /*
+ * If the request is type A or AAAA, fabricate an MX record that points
+ * to the domain name itself, and skip name-based access control.
+ *
+ * If the domain name does not exist then we apply no restriction.
+ *
+ * If the domain name exists but no MX record exists, fabricate an MX record
+ * that points to the domain name itself.
+ *
+ * If the domain name exists but no NS record exists, look up parent domain
+ * NS records.
+ *
+ * XXX 20150707 Work around broken DNS servers that reply with NXDOMAIN
+ * instead of "no data".
+ */
+ if (type == T_A
+#ifdef HAS_IPV6
+ || type == T_AAAA
+#endif
+ ) {
+ server_list = dns_rr_create(domain, domain, T_MX, C_IN, 0, 0,
+ domain, strlen(domain) + 1);
+ } else {
+ dns_status = dns_lookup(domain, type, 0, &server_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status == DNS_NULLMX)
+ return (SMTPD_CHECK_DUNNO);
+ if (dns_status == DNS_NOTFOUND /* Not: h_errno == NO_DATA */ ) {
+ if (type == T_MX) {
+ server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0,
+ domain, strlen(domain) + 1);
+ dns_status = DNS_OK;
+ } else if (type == T_NS /* && h_errno == NO_DATA */ ) {
+ while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
+ domain += 1;
+ dns_status = dns_lookup(domain, type, 0, &server_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */ )
+ break;
+ }
+ }
+ }
+ if (dns_status != DNS_OK) {
+ msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type),
+ domain && domain[1] ? domain : name,
+ dns_status == DNS_POLICY ?
+ "DNS reply filter policy" :
+ dns_strerror(dns_get_h_errno()));
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * No bare returns after this point or we have a memory leak.
+ */
+#define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); }
+
+ /*
+ * Check the hostnames first, then the addresses.
+ */
+ proto_info = inet_proto_info();
+ for (server = server_list; server != 0; server = server->next) {
+ if (msg_verbose)
+ msg_info("%s: %s hostname check: %s",
+ myname, dns_strtype(type), (char *) server->data);
+ if (valid_hostaddr((char *) server->data, DONT_GRIPE)) {
+ if ((status = check_addr_access(state, table, (char *) server->data,
+ FULL, &found, reply_name, reply_class,
+ def_acl)) != 0 || found)
+ CHECK_SERVER_RETURN(status);
+ continue;
+ }
+ if (type != T_A && type != T_AAAA
+ && ((status = check_domain_access(state, table, (char *) server->data,
+ FULL, &found, reply_name, reply_class,
+ def_acl)) != 0 || found))
+ CHECK_SERVER_RETURN(status);
+ if ((aierr = hostname_to_sockaddr((char *) server->data,
+ (char *) 0, 0, &res0)) != 0) {
+ if (type != T_A && type != T_AAAA)
+ msg_warn("Unable to look up %s host %s for %s %s: %s",
+ dns_strtype(type), (char *) server->data,
+ reply_class, reply_name, MAI_STRERROR(aierr));
+ continue;
+ }
+ /* Now we must also free the addrinfo result. */
+ if (msg_verbose)
+ msg_info("%s: %s host address check: %s",
+ myname, dns_strtype(type), (char *) server->data);
+ for (res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ if (msg_verbose)
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, server->data);
+ continue;
+ }
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &addr_string, (MAI_SERVPORT_STR *) 0, 0);
+ status = check_addr_access(state, table, addr_string.buf, FULL,
+ &found, reply_name, reply_class,
+ def_acl);
+ if (status != 0 || found) {
+ freeaddrinfo(res0); /* 200412 */
+ CHECK_SERVER_RETURN(status);
+ }
+ }
+ freeaddrinfo(res0); /* 200412 */
+ }
+ CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
+}
+
+/* check_ccert_access - access for TLS clients by certificate fingerprint */
+
+static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
+ const char *def_acl)
+{
+ int result = SMTPD_CHECK_DUNNO;
+
+#ifdef USE_TLS
+ const char *myname = "check_ccert_access";
+ int found;
+ const MAP_SEARCH *acl;
+ const char default_search[] = {
+ SMTPD_ACL_SEARCH_CODE_CERT_FPRINT,
+ SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT,
+ 0,
+ };
+ const char *search_order;
+
+ /*
+ * Look up the acl search list. If there is no ACL then we don't have a
+ * table to check.
+ */
+ if ((acl = map_search_lookup(acl_spec)) == 0) {
+ msg_warn("See earlier parsing error messages for '%s", acl_spec);
+ return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, 451, "4.3.5",
+ "Server configuration error"));
+ }
+ if ((search_order = acl->search_order) == 0)
+ search_order = default_search;
+ if (msg_verbose)
+ msg_info("%s: search_order length=%ld",
+ myname, (long) strlen(search_order));
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ const char *action;
+ const char *match_this;
+ const char *known_action;
+
+ for (action = search_order; *action; action++) {
+ switch (*action) {
+ case SMTPD_ACL_SEARCH_CODE_CERT_FPRINT:
+ match_this = state->tls_context->peer_cert_fprint;
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
+ "certificate fingerprints");
+ break;
+ case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT:
+ match_this = state->tls_context->peer_pkey_fprint;
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
+ "certificate fingerprints");
+ break;
+ default:
+ known_action = str_name_code(search_actions, *action);
+ if (known_action == 0)
+ msg_panic("%s: unknown action #%d in '%s'",
+ myname, *action, acl_spec);
+ msg_warn("%s: unexpected action '%s' in '%s'",
+ myname, known_action, acl_spec);
+ return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
+ 451, "4.3.5",
+ "Server configuration error"));
+ }
+ if (msg_verbose)
+ msg_info("%s: look up %s %s",
+ myname, str_name_code(search_actions, *action),
+ match_this);
+
+ /*
+ * Log the peer CommonName when access is denied. Non-printable
+ * characters will be neutered by smtpd_check_reject(). The SMTP
+ * client name and address are always syslogged as part of a
+ * "reject" event. XXX Should log the thing that is rejected
+ * (fingerprint etc.) or would that give away too much?
+ */
+ result = check_access(state, acl->map_type_name, match_this,
+ DICT_FLAG_NONE, &found,
+ state->tls_context->peer_CN,
+ SMTPD_NAME_CCERT, def_acl);
+ if (result != SMTPD_CHECK_DUNNO)
+ break;
+ }
+ } else if (!var_smtpd_tls_ask_ccert) {
+ msg_warn("%s is requested, but \"%s = no\"",
+ CHECK_CCERT_ACL, VAR_SMTPD_TLS_ACERT);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: no client certificate", myname);
+ }
+#endif
+ return (result);
+}
+
+/* check_sasl_access - access by SASL user name */
+
+#ifdef USE_SASL_AUTH
+
+static int check_sasl_access(SMTPD_STATE *state, const char *table,
+ const char *def_acl)
+{
+ int result;
+ int unused_found;
+ char *sane_username = printable(mystrdup(state->sasl_username), '_');
+
+ result = check_access(state, table, state->sasl_username,
+ DICT_FLAG_NONE, &unused_found, sane_username,
+ SMTPD_NAME_SASL_USER, def_acl);
+ myfree(sane_username);
+ return (result);
+}
+
+#endif
+
+/* check_mail_access - OK/FAIL based on mail address lookup */
+
+static int check_mail_access(SMTPD_STATE *state, const char *table,
+ const char *addr, int *found,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_mail_access";
+ const RESOLVE_REPLY *reply;
+ const char *value;
+ int lookup_strategy;
+ int status;
+ MAPS *maps;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ state->recipient : state->sender, addr);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, addr);
+
+ /*
+ * Garbage in, garbage out. Every address from rewrite_clnt_internal()
+ * and from resolve_clnt_query() must be fully qualified.
+ */
+ if (strrchr(CONST_STR(reply->recipient), '@') == 0) {
+ msg_warn("%s: no @domain in address: %s", myname,
+ CONST_STR(reply->recipient));
+ return (0);
+ }
+
+ /*
+ * Source-routed (non-local or virtual) recipient addresses are too
+ * suspicious for returning an "OK" result. The complicated expression
+ * below was brought to you by the keyboard of Victor Duchovni, Morgan
+ * Stanley and hacked up a bit by Wietse.
+ */
+#define SUSPICIOUS(reply, reply_class) \
+ (var_allow_untrust_route == 0 \
+ && (reply->flags & RESOLVE_FLAG_ROUTED) \
+ && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0)
+
+ /*
+ * Look up user+foo@domain if the address has an extension, user@domain
+ * otherwise.
+ */
+ lookup_strategy = MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN
+ | MA_FIND_LOCALPART_AT
+ | (access_parent_style == MATCH_FLAG_PARENT ?
+ MA_FIND_PDMS : MA_FIND_PDDMDS);
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ return (check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class,
+ def_acl));
+ }
+ if ((value = mail_addr_find_strategy(maps, CONST_STR(reply->recipient),
+ (char **) 0, lookup_strategy)) != 0) {
+ *found = 1;
+ status = check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class, def_acl);
+ return (status == SMTPD_CHECK_OK && SUSPICIOUS(reply, reply_class) ?
+ SMTPD_CHECK_DUNNO : status);
+ } else if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ return (check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class,
+ def_acl));
+ }
+
+ /*
+ * Undecided when no match found.
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* Support for different DNSXL lookup results. */
+
+static SMTPD_RBL_STATE dnsxl_stat_soft[1];
+
+#define SMTPD_DNSXL_STAT_SOFT(dnsxl_res) ((dnsxl_res) == dnsxl_stat_soft)
+#define SMTPD_DNXSL_STAT_HARD(dnsxl_res) ((dnsxl_res) == 0)
+#define SMTPD_DNSXL_STAT_OK(dnsxl_res) \
+ !(SMTPD_DNXSL_STAT_HARD(dnsxl_res) || SMTPD_DNSXL_STAT_SOFT(dnsxl_res))
+
+/* rbl_pagein - look up an RBL lookup result */
+
+static void *rbl_pagein(const char *query, void *unused_context)
+{
+ DNS_RR *txt_list;
+ VSTRING *why;
+ int dns_status;
+ SMTPD_RBL_STATE *rbl = 0;
+ DNS_RR *addr_list;
+ DNS_RR *rr;
+ DNS_RR *next;
+ VSTRING *buf;
+ int space_left;
+
+ /*
+ * Do the query. If the DNS lookup produces no definitive reply, give the
+ * requestor the benefit of the doubt. We can't block all email simply
+ * because an RBL server is unavailable.
+ *
+ * Don't do this for AAAA records. Yet.
+ */
+ why = vstring_alloc(10);
+ dns_status = dns_lookup(query, T_A, 0, &addr_list, (VSTRING *) 0, why);
+ if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND) {
+ msg_warn("%s: RBL lookup error: %s", query, STR(why));
+ rbl = dnsxl_stat_soft;
+ }
+ vstring_free(why);
+ if (dns_status != DNS_OK)
+ return ((void *) rbl);
+
+ /*
+ * Save the result. Yes, we cache negative results as well as positive
+ * results. Concatenate multiple TXT records, up to some limit.
+ */
+#define RBL_TXT_LIMIT 500
+
+ rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl));
+ dns_status = dns_lookup(query, T_TXT, 0, &txt_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status == DNS_OK) {
+ buf = vstring_alloc(1);
+ space_left = RBL_TXT_LIMIT;
+ for (rr = txt_list; rr != 0 && space_left > 0; rr = next) {
+ vstring_strncat(buf, rr->data, (int) rr->data_len > space_left ?
+ space_left : rr->data_len);
+ space_left = RBL_TXT_LIMIT - VSTRING_LEN(buf);
+ next = rr->next;
+ if (next && space_left > 3) {
+ vstring_strcat(buf, " / ");
+ space_left -= 3;
+ }
+ }
+ rbl->txt = vstring_export(buf);
+ dns_rr_free(txt_list);
+ } else {
+ if (dns_status == DNS_POLICY)
+ msg_warn("%s: TXT lookup error: %s",
+ query, "DNS reply filter drops all results");
+ rbl->txt = 0;
+ }
+ rbl->a = addr_list;
+ return ((void *) rbl);
+}
+
+/* rbl_pageout - discard an RBL lookup result */
+
+static void rbl_pageout(void *data, void *unused_context)
+{
+ SMTPD_RBL_STATE *rbl = (SMTPD_RBL_STATE *) data;
+
+ if (SMTPD_DNSXL_STAT_OK(rbl)) {
+ if (rbl->txt)
+ myfree(rbl->txt);
+ if (rbl->a)
+ dns_rr_free(rbl->a);
+ myfree((void *) rbl);
+ }
+}
+
+/* rbl_byte_pagein - parse RBL reply pattern, save byte codes */
+
+static void *rbl_byte_pagein(const char *query, void *unused_context)
+{
+ VSTRING *byte_codes = vstring_alloc(100);
+ char *saved_query = mystrdup(query);
+ char *saved_byte_codes;
+ char *err;
+
+ if ((err = ip_match_parse(byte_codes, saved_query)) != 0)
+ msg_fatal("RBL reply error: %s", err);
+ saved_byte_codes = ip_match_save(byte_codes);
+ myfree(saved_query);
+ vstring_free(byte_codes);
+ return (saved_byte_codes);
+}
+
+/* rbl_byte_pageout - discard parsed RBL reply byte codes */
+
+static void rbl_byte_pageout(void *data, void *unused_context)
+{
+ myfree(data);
+}
+
+/* rbl_expand_lookup - RBL specific $name expansion */
+
+static const char *rbl_expand_lookup(const char *name, int mode,
+ void *context)
+{
+ SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context;
+ SMTPD_STATE *state = rbl_exp->state;
+
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("rbl_expand_lookup: ${%s}", name);
+
+ /*
+ * Be sure to return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_RBL_CODE)) {
+ vstring_sprintf(state->expand_buf, "%d", var_maps_rbl_code);
+ return (STR(state->expand_buf));
+ } else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) {
+ return (rbl_exp->domain);
+ } else if (STREQ(name, MAIL_ATTR_RBL_REASON)) {
+ return (rbl_exp->txt);
+ } else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {/* LaMont compat */
+ return (rbl_exp->txt);
+ } else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) {
+ return (rbl_exp->what);
+ } else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) {
+ return (rbl_exp->class);
+ } else {
+ return (smtpd_expand_lookup(name, mode, (void *) state));
+ }
+}
+
+/* rbl_reject_reply - format reply after RBL reject */
+
+static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl,
+ const char *rbl_domain,
+ const char *what,
+ const char *reply_class)
+{
+ const char *myname = "rbl_reject_reply";
+ VSTRING *why = 0;
+ const char *template = 0;
+ SMTPD_RBL_EXPAND_CONTEXT rbl_exp;
+ int result;
+ DSN_SPLIT dp;
+ int code;
+
+ /*
+ * Use the server-specific reply template or use the default one.
+ */
+ if (*var_rbl_reply_maps) {
+ template = maps_find(rbl_reply_maps, rbl_domain, DICT_FLAG_NONE);
+ if (rbl_reply_maps->error)
+ reject_server_error(state);
+ }
+ why = vstring_alloc(100);
+ rbl_exp.state = state;
+ rbl_exp.domain = mystrdup(rbl_domain);
+ (void) split_at(rbl_exp.domain, '=');
+ rbl_exp.what = what;
+ rbl_exp.class = reply_class;
+ rbl_exp.txt = (rbl->txt == 0 ? "" : rbl->txt);
+
+ for (;;) {
+ if (template == 0)
+ template = var_def_rbl_reply;
+ if (mac_expand(why, template, MAC_EXP_FLAG_NONE,
+ STR(smtpd_expand_filter), rbl_expand_lookup,
+ (void *) &rbl_exp) == 0)
+ break;
+ if (template == var_def_rbl_reply)
+ msg_fatal("%s: bad default rbl reply template: %s",
+ myname, var_def_rbl_reply);
+ msg_warn("%s: bad rbl reply template for domain %s: %s",
+ myname, rbl_domain, template);
+ template = 0; /* pretend not found */
+ }
+
+ /*
+ * XXX Impedance mis-match.
+ *
+ * Validate the response, that is, the response must begin with a
+ * three-digit status code, and the first digit must be 4 or 5. If the
+ * response is bad, log a warning and send a generic response instead.
+ */
+ if ((STR(why)[0] != '4' && STR(why)[0] != '5')
+ || !ISDIGIT(STR(why)[1]) || !ISDIGIT(STR(why)[2])
+ || STR(why)[3] != ' ') {
+ msg_warn("rbl response code configuration error: %s", STR(why));
+ result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1", "Service unavailable");
+ } else {
+ code = atoi(STR(why));
+ dsn_split(&dp, "4.7.1", STR(why) + 4);
+ result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "%s", *dp.text ?
+ dp.text : "Service unavailable");
+ }
+
+ /*
+ * Clean up.
+ */
+ myfree(rbl_exp.domain);
+ vstring_free(why);
+
+ return (result);
+}
+
+/* rbl_match_addr - match address list */
+
+static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *byte_codes)
+{
+ const char *myname = "rbl_match_addr";
+ DNS_RR *rr;
+
+ for (rr = rbl->a; rr != 0; rr = rr->next) {
+ if (rr->type == T_A) {
+ if (ip_match_execute(byte_codes, rr->data))
+ return (1);
+ } else {
+ msg_warn("%s: skipping record type %s for query %s",
+ myname, dns_strtype(rr->type), rr->qname);
+ }
+ }
+ return (0);
+}
+
+/* find_dnsxl_addr - look up address in DNSXL */
+
+static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state,
+ const char *rbl_domain,
+ const char *addr)
+{
+ const char *myname = "find_dnsxl_addr";
+ ARGV *octets;
+ VSTRING *query;
+ int i;
+ SMTPD_RBL_STATE *rbl;
+ const char *reply_addr;
+ const char *byte_codes;
+ struct addrinfo *res;
+ unsigned char *ipv6_addr;
+
+ query = vstring_alloc(100);
+
+ /*
+ * Reverse the client IPV6 address, represented as 32 hexadecimal
+ * nibbles. We use the binary address to avoid tricky code. Asking for an
+ * AAAA record makes no sense here. Just like with IPv4 we use the lookup
+ * result as a bit mask, not as an IP address.
+ */
+#ifdef HAS_IPV6
+ if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) {
+ if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0
+ || res->ai_family != PF_INET6)
+ msg_fatal("%s: unable to convert address %s", myname, addr);
+ ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr);
+ for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--)
+ vstring_sprintf_append(query, "%x.%x.",
+ ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4);
+ freeaddrinfo(res);
+ } else
+#endif
+
+ /*
+ * Reverse the client IPV4 address, represented as four decimal octet
+ * values. We use the textual address for convenience.
+ */
+ {
+ octets = argv_split(addr, ".");
+ for (i = octets->argc - 1; i >= 0; i--) {
+ vstring_strcat(query, octets->argv[i]);
+ vstring_strcat(query, ".");
+ }
+ argv_free(octets);
+ }
+
+ /*
+ * Tack on the RBL domain name and query the DNS for an A record.
+ */
+ vstring_strcat(query, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
+ rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
+ if (reply_addr != 0)
+ byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr);
+
+ /*
+ * If the record exists, match the result address.
+ */
+ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0
+ && !rbl_match_addr(rbl, byte_codes))
+ rbl = 0;
+ vstring_free(query);
+ return (rbl);
+}
+
+/* reject_rbl_addr - reject address in DNS deny list */
+
+static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain,
+ const char *addr, const char *reply_class)
+{
+ const char *myname = "reject_rbl_addr";
+ const SMTPD_RBL_STATE *rbl;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, reply_class, addr);
+
+ rbl = find_dnsxl_addr(state, rbl_domain, addr);
+ if (!SMTPD_DNSXL_STAT_OK(rbl)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class));
+ }
+}
+
+/* permit_dnswl_addr - permit address in DNSWL */
+
+static int permit_dnswl_addr(SMTPD_STATE *state, const char *dnswl_domain,
+ const char *addr, const char *reply_class)
+{
+ const char *myname = "permit_dnswl_addr";
+ const SMTPD_RBL_STATE *dnswl_result;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /* Safety: don't allowlist unauthorized recipients. */
+ if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0
+ && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ dnswl_result = find_dnsxl_addr(state, dnswl_domain, addr);
+ if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) {
+ /* XXX: Make configurable as dnswl_tempfail_action. */
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: %s",
+ addr, reply_class,
+ "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) {
+ return (SMTPD_CHECK_OK);
+ } else {
+ /* Future proofing, in case find_dnsxl_addr() result is changed. */
+ msg_panic("%s: find_dnsxl_addr API failure", myname);
+ }
+}
+
+/* find_dnsxl_domain - reject if domain in DNS deny list */
+
+static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state,
+ const char *rbl_domain, const char *what)
+{
+ VSTRING *query;
+ SMTPD_RBL_STATE *rbl;
+ const char *domain;
+ const char *reply_addr;
+ const char *byte_codes;
+ const char *suffix;
+ const char *adomain;
+
+ /*
+ * Extract the domain, tack on the RBL domain name and query the DNS for
+ * an A record.
+ */
+ if ((domain = strrchr(what, '@')) != 0) {
+ domain += 1;
+ if (domain[0] == '[')
+ return (SMTPD_CHECK_DUNNO);
+ } else
+ domain = what;
+
+ /*
+ * XXX Some Spamhaus RHSBL rejects lookups with "No IP queries" even if
+ * the name has an alphanumerical prefix. We play safe, and skip both
+ * RHSBL and RHSWL queries for names ending in a numerical suffix.
+ */
+ if (domain[0] == 0)
+ return (SMTPD_CHECK_DUNNO);
+ suffix = strrchr(domain, '.');
+ if (alldig(suffix == 0 ? domain : suffix + 1))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Fix 20140706: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+ if (domain[0] == 0 || valid_hostname(domain, DONT_GRIPE) == 0)
+ return (SMTPD_CHECK_DUNNO);
+
+ query = vstring_alloc(100);
+ vstring_sprintf(query, "%s.%s", domain, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
+ rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
+ if (reply_addr != 0)
+ byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr);
+
+ /*
+ * If the record exists, match the result address.
+ */
+ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0
+ && !rbl_match_addr(rbl, byte_codes))
+ rbl = 0;
+ vstring_free(query);
+ return (rbl);
+}
+
+/* reject_rbl_domain - reject if domain in DNS deny list */
+
+static int reject_rbl_domain(SMTPD_STATE *state, const char *rbl_domain,
+ const char *what, const char *reply_class)
+{
+ const char *myname = "reject_rbl_domain";
+ const SMTPD_RBL_STATE *rbl;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, rbl_domain, what);
+
+ rbl = find_dnsxl_domain(state, rbl_domain, what);
+ if (!SMTPD_DNSXL_STAT_OK(rbl)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class));
+ }
+}
+
+/* permit_dnswl_domain - permit domain in DNSWL */
+
+static int permit_dnswl_domain(SMTPD_STATE *state, const char *dnswl_domain,
+ const char *what, const char *reply_class)
+{
+ const char *myname = "permit_dnswl_domain";
+ const SMTPD_RBL_STATE *dnswl_result;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, what);
+
+ /* Safety: don't allowlist unauthorized recipients. */
+ if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0
+ && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ dnswl_result = find_dnsxl_domain(state, dnswl_domain, what);
+ if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) {
+ /* XXX: Make configurable as rhswl_tempfail_action. */
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: %s",
+ what, reply_class,
+ "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) {
+ return (SMTPD_CHECK_OK);
+ } else {
+ /* Future proofing, in case find_dnsxl_addr() result is changed. */
+ msg_panic("%s: find_dnsxl_addr API failure", myname);
+ }
+}
+
+/* reject_maps_rbl - reject if client address in DNS deny list */
+
+static int reject_maps_rbl(SMTPD_STATE *state)
+{
+ const char *myname = "reject_maps_rbl";
+ char *saved_domains = mystrdup(var_maps_rbl_domains);
+ char *bp = saved_domains;
+ char *rbl_domain;
+ int result = SMTPD_CHECK_DUNNO;
+ static int warned;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->addr);
+
+ if (warned == 0) {
+ warned++;
+ msg_warn("support for restriction \"%s\" will be removed from %s; "
+ "use \"%s domain-name\" instead",
+ REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT);
+ }
+ while ((rbl_domain = mystrtok(&bp, CHARS_COMMA_SP)) != 0) {
+ result = reject_rbl_addr(state, rbl_domain, state->addr,
+ SMTPD_NAME_CLIENT);
+ if (result != SMTPD_CHECK_DUNNO)
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ myfree(saved_domains);
+
+ return (result);
+}
+
+#ifdef USE_SASL_AUTH
+
+/* reject_auth_sender_login_mismatch - logged in client must own sender address */
+
+static int reject_auth_sender_login_mismatch(SMTPD_STATE *state, const char *sender, int allow_unknown_sender)
+{
+ const RESOLVE_REPLY *reply;
+ const char *owners;
+ char *saved_owners;
+ char *cp;
+ char *name;
+ int found = 0;
+
+#define ALLOW_UNKNOWN_SENDER 1
+#define FORBID_UNKNOWN_SENDER 0
+
+ /*
+ * Reject if the client is logged in and does not own the sender address.
+ */
+ if (smtpd_sender_login_maps && state->sasl_username) {
+ reply = smtpd_resolve_addr(state->recipient, sender);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, sender);
+ if ((owners = check_mail_addr_find(state, sender, smtpd_sender_login_maps,
+ STR(reply->recipient), (char **) 0)) != 0) {
+ cp = saved_owners = mystrdup(owners);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if (strcasecmp_utf8(state->sasl_username, name) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ myfree(saved_owners);
+ } else if (allow_unknown_sender)
+ return (SMTPD_CHECK_DUNNO);
+ if (!found)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1",
+ "<%s>: Sender address rejected: not owned by user %s",
+ sender, state->sasl_username));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unauth_sender_login_mismatch - sender requires client is logged in */
+
+static int reject_unauth_sender_login_mismatch(SMTPD_STATE *state, const char *sender)
+{
+ const RESOLVE_REPLY *reply;
+
+ /*
+ * Reject if the client is not logged in and the sender address has an
+ * owner.
+ */
+ if (smtpd_sender_login_maps && !state->sasl_username) {
+ reply = smtpd_resolve_addr(state->recipient, sender);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, sender);
+ if (check_mail_addr_find(state, sender, smtpd_sender_login_maps,
+ STR(reply->recipient), (char **) 0) != 0)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1",
+ "<%s>: Sender address rejected: not logged in", sender));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+#endif
+
+/* valid_utf8_action - validate UTF-8 policy server response */
+
+static int valid_utf8_action(const char *server, const char *action)
+{
+ int retval;
+
+ if ((retval = valid_utf8_string(action, strlen(action))) == 0)
+ msg_warn("malformed UTF-8 in policy server %s response: \"%s\"",
+ server, action);
+ return (retval);
+}
+
+/* check_policy_service - check delegated policy service */
+
+static int check_policy_service(SMTPD_STATE *state, const char *server,
+ const char *reply_name, const char *reply_class,
+ const char *def_acl)
+{
+ static int warned = 0;
+ static VSTRING *action = 0;
+ SMTPD_POLICY_CLNT *policy_clnt;
+
+#ifdef USE_TLS
+ VSTRING *subject_buf;
+ VSTRING *issuer_buf;
+ const char *subject;
+ const char *issuer;
+
+#endif
+ int ret;
+
+ /*
+ * Sanity check.
+ */
+ if (!policy_clnt_table
+ || (policy_clnt = (SMTPD_POLICY_CLNT *)
+ htable_find(policy_clnt_table, server)) == 0)
+ msg_panic("check_policy_service: no client endpoint for server %s",
+ server);
+
+ /*
+ * Initialize.
+ */
+ if (action == 0)
+ action = vstring_alloc(10);
+
+#ifdef USE_TLS
+#define ENCODE_CN(coded_CN, coded_CN_buf, CN) do { \
+ if (!TLS_CERT_IS_TRUSTED(state->tls_context) || *(CN) == 0) { \
+ coded_CN_buf = 0; \
+ coded_CN = ""; \
+ } else { \
+ coded_CN_buf = vstring_alloc(strlen(CN) + 1); \
+ xtext_quote(coded_CN_buf, CN, ""); \
+ coded_CN = STR(coded_CN_buf); \
+ } \
+ } while (0);
+
+ ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN);
+ ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN);
+
+ /*
+ * XXX: Too noisy to warn for each policy lookup, especially because we
+ * don't even know whether the policy server will use the fingerprint. So
+ * warn at most once per process, though on only lightly loaded servers,
+ * it might come close to one warning per inbound message.
+ */
+ if (!warned
+ && warn_compat_break_smtpd_tls_fpt_dgst
+ && state->tls_context
+ && state->tls_context->peer_cert_fprint
+ && *state->tls_context->peer_cert_fprint) {
+ warned = 1;
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ }
+#endif
+
+ if (attr_clnt_request(policy_clnt->client,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"),
+ SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE,
+ STREQ(state->where, SMTPD_CMD_BDAT) ?
+ SMTPD_CMD_DATA : state->where),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_REVERSE_CLIENT_NAME,
+ state->reverse_name),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR,
+ state->dest_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT,
+ state->dest_port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_HELO_NAME,
+ state->helo_name ? state->helo_name : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER,
+ state->sender ? state->sender : ""),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP,
+ state->recipient ? state->recipient : ""),
+ SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT,
+ ((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) ||
+ (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) ||
+ (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ?
+ state->rcpt_count : 0),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID,
+ state->queue_id ? state->queue_id : ""),
+ SEND_ATTR_STR(MAIL_ATTR_INSTANCE,
+ STR(state->instance)),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE,
+ (unsigned long) (state->act_size > 0 ?
+ state->act_size : state->msg_size)),
+ SEND_ATTR_STR(MAIL_ATTR_ETRN_DOMAIN,
+ state->etrn_name ? state->etrn_name : ""),
+ SEND_ATTR_STR(MAIL_ATTR_STRESS, var_stress),
+#ifdef USE_SASL_AUTH
+ SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD,
+ state->sasl_method ? state->sasl_method : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME,
+ state->sasl_username ? state->sasl_username : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER,
+ state->sasl_sender ? state->sasl_sender : ""),
+#endif
+#ifdef USE_TLS
+#define IF_ENCRYPTED(x, y) ((state->tls_context && ((x) != 0)) ? (x) : (y))
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_SUBJECT, subject),
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_ISSUER, issuer),
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_CERT_FPRINT,
+ IF_ENCRYPTED(state->tls_context->peer_cert_fprint, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_PKEY_FPRINT,
+ IF_ENCRYPTED(state->tls_context->peer_pkey_fprint, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CRYPTO_PROTOCOL,
+ IF_ENCRYPTED(state->tls_context->protocol, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CRYPTO_CIPHER,
+ IF_ENCRYPTED(state->tls_context->cipher_name, "")),
+ SEND_ATTR_INT(MAIL_ATTR_CRYPTO_KEYSIZE,
+ IF_ENCRYPTED(state->tls_context->cipher_usebits, 0)),
+#endif
+ SEND_ATTR_STR(MAIL_ATTR_POL_CONTEXT,
+ policy_clnt->policy_context),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_STR(MAIL_ATTR_ACTION, action),
+ ATTR_TYPE_END) != 1
+ || (var_smtputf8_enable && valid_utf8_action(server, STR(action)) == 0)) {
+ NOCLOBBER static int nesting_level = 0;
+ jmp_buf savebuf;
+ int status;
+
+ /*
+ * Safety to prevent recursive execution of the default action.
+ */
+ nesting_level += 1;
+ memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
+ status = setjmp(smtpd_check_buf);
+ if (status != 0) {
+ nesting_level -= 1;
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ longjmp(smtpd_check_buf, status);
+ }
+ ret = check_table_result(state, server, nesting_level == 1 ?
+ policy_clnt->def_action :
+ DEF_SMTPD_POLICY_DEF_ACTION,
+ "policy query", reply_name,
+ reply_class, def_acl);
+ nesting_level -= 1;
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ } else {
+
+ /*
+ * XXX This produces bogus error messages when the reply is
+ * malformed.
+ */
+ ret = check_table_result(state, server, STR(action),
+ "policy query", reply_name,
+ reply_class, def_acl);
+ }
+#ifdef USE_TLS
+ if (subject_buf)
+ vstring_free(subject_buf);
+ if (issuer_buf)
+ vstring_free(issuer_buf);
+#endif
+ return (ret);
+}
+
+/* is_map_command - restriction has form: check_xxx_access type:name */
+
+static int is_map_command(SMTPD_STATE *state, const char *name,
+ const char *command, char ***argp)
+{
+
+ /*
+ * This is a three-valued function: (a) this is not a check_xxx_access
+ * command, (b) this is a malformed check_xxx_access command, (c) this is
+ * a well-formed check_xxx_access command. That's too clumsy for function
+ * result values, so we use regular returns for (a) and (c), and use long
+ * jumps for the error case (b).
+ */
+ if (strcasecmp(name, command) != 0) {
+ return (0);
+ } else if (*(*argp + 1) == 0 || strchr(*(*argp += 1), ':') == 0) {
+ msg_warn("restriction %s: bad argument \"%s\": need maptype:mapname",
+ command, **argp);
+ reject_server_error(state);
+ } else {
+ return (1);
+ }
+}
+
+/* forbid_allowlist - disallow allowlisting */
+
+static void forbid_allowlist(SMTPD_STATE *state, const char *name,
+ int status, const char *target)
+{
+ if (state->discard == 0 && status == SMTPD_CHECK_OK) {
+ msg_warn("restriction %s returns OK for %s", name, target);
+ msg_warn("this is not allowed for security reasons");
+ msg_warn("use DUNNO instead of OK if you want to make an exception");
+ reject_server_error(state);
+ }
+}
+
+/* generic_checks - generic restrictions */
+
+static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "generic_checks";
+ char **cpp;
+ const char *name;
+ int status = 0;
+ ARGV *list;
+ int found;
+ int saved_recursion = state->recursion++;
+
+ if (msg_verbose)
+ msg_info(">>> START %s RESTRICTIONS <<<", reply_class);
+
+ for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) {
+
+ if (state->discard != 0)
+ break;
+
+ if (msg_verbose)
+ msg_info("%s: name=%s", myname, name);
+
+ /*
+ * Pseudo restrictions.
+ */
+ if (strcasecmp(name, WARN_IF_REJECT) == 0) {
+ if (state->warn_if_reject == 0)
+ state->warn_if_reject = state->recursion;
+ continue;
+ }
+
+ /*
+ * Spoof the is_map_command() routine, so that we do not have to make
+ * special cases for the implicit short-hand access map notation.
+ */
+#define NO_DEF_ACL 0
+
+ if (strchr(name, ':') != 0) {
+ if (def_acl == NO_DEF_ACL) {
+ msg_warn("specify one of (%s, %s, %s, %s, %s, %s) before %s restriction \"%s\"",
+ CHECK_CLIENT_ACL, CHECK_REVERSE_CLIENT_ACL, CHECK_HELO_ACL, CHECK_SENDER_ACL,
+ CHECK_RECIP_ACL, CHECK_ETRN_ACL, reply_class, name);
+ reject_server_error(state);
+ }
+ name = def_acl;
+ cpp -= 1;
+ }
+
+ /*
+ * Generic restrictions.
+ */
+ if (strcasecmp(name, PERMIT_ALL) == 0) {
+ status = smtpd_acl_permit(state, name, reply_class,
+ reply_name, NO_PRINT_ARGS);
+ if (status == SMTPD_CHECK_OK && cpp[1] != 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], PERMIT_ALL);
+ } else if (strcasecmp(name, DEFER_ALL) == 0) {
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_defer_code, "4.3.2",
+ "<%s>: %s rejected: Try again later",
+ reply_name, reply_class);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], DEFER_ALL);
+ } else if (strcasecmp(name, REJECT_ALL) == 0) {
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_reject_code, "5.7.1",
+ "<%s>: %s rejected: Access denied",
+ reply_name, reply_class);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], REJECT_ALL);
+ } else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) {
+ status = reject_unauth_pipelining(state, reply_name, reply_class);
+ } else if (strcasecmp(name, CHECK_POLICY_SERVICE) == 0) {
+ if (cpp[1] == 0 || strchr(cpp[1], ':') == 0) {
+ msg_warn("restriction %s must be followed by transport:server",
+ CHECK_POLICY_SERVICE);
+ reject_server_error(state);
+ } else
+ status = check_policy_service(state, *++cpp, reply_name,
+ reply_class, def_acl);
+ } else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) {
+ status = DEFER_IF_PERMIT2(DEFER_IF_PERMIT_ACT,
+ state, MAIL_ERROR_POLICY,
+ 450, "4.7.0",
+ "<%s>: %s rejected: defer_if_permit requested",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, DEFER_IF_REJECT) == 0) {
+ DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY,
+ 450, "4.7.0",
+ "<%s>: %s rejected: defer_if_reject requested",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, SLEEP) == 0) {
+ if (cpp[1] == 0 || alldig(cpp[1]) == 0) {
+ msg_warn("restriction %s must be followed by number", SLEEP);
+ reject_server_error(state);
+ } else
+ sleep(atoi(*++cpp));
+ } else if (strcasecmp(name, REJECT_PLAINTEXT_SESSION) == 0) {
+ status = reject_plaintext_session(state);
+ }
+
+ /*
+ * Client name/address restrictions.
+ */
+ else if (strcasecmp(name, REJECT_UNKNOWN_CLIENT_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_UNKNOWN_CLIENT) == 0) {
+ status = reject_unknown_client(state);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_REVERSE_HOSTNAME) == 0) {
+ status = reject_unknown_reverse_name(state);
+ } else if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) {
+ status = permit_inet_interfaces(state);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) {
+ status = permit_mynetworks(state);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (is_map_command(state, name, CHECK_CLIENT_ACL, &cpp)) {
+ status = check_namadr_access(state, *cpp, state->name, state->addr,
+ FULL, &found, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_ACL, &cpp)) {
+ status = check_namadr_access(state, *cpp, state->reverse_name, state->addr,
+ FULL, &found, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->reverse_name);
+ } else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) {
+ status = reject_maps_rbl(state);
+ } else if (strcasecmp(name, REJECT_RBL_CLIENT) == 0
+ || strcasecmp(name, REJECT_RBL) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else
+ status = reject_rbl_addr(state, *(cpp += 1), state->addr,
+ SMTPD_NAME_CLIENT);
+ } else if (strcasecmp(name, PERMIT_DNSWL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ status = permit_dnswl_addr(state, *(cpp += 1), state->addr,
+ SMTPD_NAME_CLIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->name, "unknown") != 0)
+ status = reject_rbl_domain(state, *cpp, state->name,
+ SMTPD_NAME_CLIENT);
+ }
+ } else if (strcasecmp(name, PERMIT_RHSWL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = permit_dnswl_domain(state, *cpp, state->name,
+ SMTPD_NAME_CLIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name,
+ SMTPD_NAME_CLIENT, state->namaddr, NO_PRINT_ARGS);
+ }
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_REVERSE_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->reverse_name, "unknown") != 0)
+ status = reject_rbl_domain(state, *cpp, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT);
+ }
+ } else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) {
+ status = check_ccert_access(state, *cpp, def_acl);
+ } else if (is_map_command(state, name, CHECK_SASL_ACL, &cpp)) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sasl_username && state->sasl_username[0])
+ status = check_sasl_access(state, *cpp, def_acl);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_NS, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_CLIENT_MX_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_MX, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_CLIENT_A_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_A, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_NS_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_NS, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->reverse_name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_MX_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_MX, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->reverse_name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_A_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_A, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_allowlist(state, name, status, state->reverse_name);
+ }
+ }
+
+ /*
+ * HELO/EHLO parameter restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_HELO_ACL, &cpp)) {
+ if (state->helo_name)
+ status = check_domain_access(state, *cpp, state->helo_name,
+ FULL, &found, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ } else if (strcasecmp(name, REJECT_INVALID_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_invalid_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, REJECT_UNKNOWN_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_UNKNOWN_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_unknown_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) {
+ msg_warn("restriction %s is deprecated. Use %s or %s instead",
+ PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS, PERMIT_SASL_AUTH);
+ if (state->helo_name) {
+ if (state->helo_name[strspn(state->helo_name, "0123456789.:")] == 0
+ && (status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO)) == 0)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_HELO,
+ state->helo_name, NO_PRINT_ARGS);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_NS, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_allowlist(state, name, status, state->helo_name);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_MX, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_allowlist(state, name, status, state->helo_name);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_A_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_A, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_allowlist(state, name, status, state->helo_name);
+ }
+ } else if (strcasecmp(name, REJECT_NON_FQDN_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_non_fqdn_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_HELO) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (state->helo_name)
+ status = reject_rbl_domain(state, *cpp, state->helo_name,
+ SMTPD_NAME_HELO);
+ }
+ }
+
+ /*
+ * Sender mail address restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_SENDER_ACL, &cpp)) {
+ if (state->sender && *state->sender)
+ status = check_mail_access(state, *cpp, state->sender,
+ &found, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ if (state->sender && !*state->sender)
+ status = check_access(state, *cpp, var_smtpd_null_key, FULL,
+ &found, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unknown_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_SENDDOM) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unknown_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_UNVERIFIED_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unverified_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER,
+ var_unv_from_dcode, var_unv_from_rcode,
+ unv_from_tf_act,
+ var_unv_from_why);
+ } else if (strcasecmp(name, REJECT_NON_FQDN_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_non_fqdn_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_AUTH_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender)
+ status = reject_auth_sender_login_mismatch(state,
+ state->sender, FORBID_UNKNOWN_SENDER);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (strcasecmp(name, REJECT_KNOWN_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender) {
+ if (state->sasl_username)
+ status = reject_auth_sender_login_mismatch(state,
+ state->sender, ALLOW_UNKNOWN_SENDER);
+ else
+ status = reject_unauth_sender_login_mismatch(state, state->sender);
+ }
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (strcasecmp(name, REJECT_UNAUTH_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender)
+ status = reject_unauth_sender_login_mismatch(state, state->sender);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_NS, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_allowlist(state, name, status, state->sender);
+ }
+ } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_MX, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_allowlist(state, name, status, state->sender);
+ }
+ } else if (is_map_command(state, name, CHECK_SENDER_A_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_A, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_allowlist(state, name, status, state->sender);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ cpp += 1;
+ if (state->sender && *state->sender)
+ status = reject_rbl_domain(state, *cpp, state->sender,
+ SMTPD_NAME_SENDER);
+ }
+ } else if (strcasecmp(name, REJECT_UNLISTED_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = check_sender_rcpt_maps(state, state->sender);
+ }
+
+ /*
+ * Recipient mail address restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_RECIP_ACL, &cpp)) {
+ if (state->recipient)
+ status = check_mail_access(state, *cpp, state->recipient,
+ &found, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ } else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) {
+ if (state->recipient) {
+ status = permit_mx_backup(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) {
+ if (state->recipient) {
+ status = permit_auth_destination(state, state->recipient);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) {
+ if (state->recipient)
+ status = reject_unauth_destination(state, state->recipient,
+ var_relay_code, "5.7.1");
+ } else if (strcasecmp(name, DEFER_UNAUTH_DEST) == 0) {
+ if (state->recipient)
+ status = reject_unauth_destination(state, state->recipient,
+ var_relay_code - 100, "4.7.1");
+ } else if (strcasecmp(name, CHECK_RELAY_DOMAINS) == 0) {
+ if (state->recipient)
+ status = check_relay_domains(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], CHECK_RELAY_DOMAINS);
+ } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (smtpd_sasl_is_active(state)) {
+ status = permit_sasl_auth(state,
+ SMTPD_CHECK_OK, SMTPD_CHECK_DUNNO);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ }
+#endif
+ } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 1);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 0);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) {
+ if (state->recipient)
+ status = reject_unknown_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ } else if (strcasecmp(name, REJECT_NON_FQDN_RCPT) == 0) {
+ if (state->recipient)
+ status = reject_non_fqdn_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_NS, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_allowlist(state, name, status, state->recipient);
+ }
+ } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_MX, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_allowlist(state, name, status, state->recipient);
+ }
+ } else if (is_map_command(state, name, CHECK_RECIP_A_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_A, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_allowlist(state, name, status, state->recipient);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ cpp += 1;
+ if (state->recipient)
+ status = reject_rbl_domain(state, *cpp, state->recipient,
+ SMTPD_NAME_RECIPIENT);
+ }
+ } else if (strcasecmp(name, CHECK_RCPT_MAPS) == 0
+ || strcasecmp(name, REJECT_UNLISTED_RCPT) == 0) {
+ if (state->recipient && *state->recipient)
+ status = check_recipient_rcpt_maps(state, state->recipient);
+ } else if (strcasecmp(name, REJECT_MUL_RCPT_BOUNCE) == 0) {
+ if (state->sender && *state->sender == 0 && state->rcpt_count
+ > (strcmp(state->where, SMTPD_CMD_RCPT) != 0))
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_mul_rcpt_code, "5.5.3",
+ "<%s>: %s rejected: Multi-recipient bounce",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, REJECT_UNVERIFIED_RECIP) == 0) {
+ if (state->recipient && *state->recipient)
+ status = reject_unverified_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT,
+ var_unv_rcpt_dcode, var_unv_rcpt_rcode,
+ unv_rcpt_tf_act,
+ var_unv_rcpt_why);
+ }
+
+ /*
+ * ETRN domain name restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_ETRN_ACL, &cpp)) {
+ if (state->etrn_name)
+ status = check_domain_access(state, *cpp, state->etrn_name,
+ FULL, &found, state->etrn_name,
+ SMTPD_NAME_ETRN, def_acl);
+ }
+
+ /*
+ * User-defined restriction class.
+ */
+ else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) {
+ status = generic_checks(state, list, reply_name,
+ reply_class, def_acl);
+ }
+
+ /*
+ * Error: undefined restriction name.
+ */
+ else {
+ msg_warn("unknown smtpd restriction: \"%s\"", name);
+ reject_server_error(state);
+ }
+ if (msg_verbose)
+ msg_info("%s: name=%s status=%d", myname, name, status);
+
+ if (status < 0) {
+ if (status == DICT_ERR_RETRY)
+ reject_dict_retry(state, reply_name);
+ else
+ reject_server_error(state);
+ }
+ if (state->warn_if_reject >= state->recursion)
+ state->warn_if_reject = 0;
+
+ if (status != 0)
+ break;
+
+ if (state->defer_if_permit.active && state->defer_if_reject.active)
+ break;
+ }
+ if (msg_verbose)
+ msg_info(">>> END %s RESTRICTIONS <<<", reply_class);
+
+ state->recursion = saved_recursion;
+
+ /* In case the list terminated with one or more warn_if_mumble. */
+ if (state->warn_if_reject >= state->recursion)
+ state->warn_if_reject = 0;
+
+ return (status);
+}
+
+/* smtpd_check_addr - address sanity check */
+
+int smtpd_check_addr(const char *sender, const char *addr, int smtputf8)
+{
+ const RESOLVE_REPLY *resolve_reply;
+ const char *myname = "smtpd_check_addr";
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: addr=%s", myname, addr);
+
+ /*
+ * Catch syntax errors early on if we can, but be prepared to re-compute
+ * the result later when the cache fills up with lots of recipients, at
+ * which time errors can still happen.
+ */
+ if (addr == 0 || *addr == 0)
+ return (0);
+ resolve_reply = smtpd_resolve_addr(sender, addr);
+ if (resolve_reply->flags & RESOLVE_FLAG_ERROR)
+ return (-1);
+
+ /*
+ * Backwards compatibility: if the client does not request SMTPUTF8
+ * support, then behave like Postfix < 3.0 trivial-rewrite, and don't
+ * allow non-ASCII email domains. Historically, Postfix does not reject
+ * UTF8 etc. in the address localpart.
+ */
+ if (smtputf8 == 0
+ && (domain = strrchr(STR(resolve_reply->recipient), '@')) != 0
+ && *(domain += 1) != 0 && !allascii(domain))
+ return (-1);
+
+ return (0);
+}
+
+/* smtpd_check_rewrite - choose address qualification context */
+
+char *smtpd_check_rewrite(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_check_rewrite";
+ int status;
+ char **cpp;
+ MAPS *maps;
+ char *name;
+
+ /*
+ * We don't use generic_checks() because it produces results that aren't
+ * applicable such as DEFER or REJECT.
+ */
+ for (cpp = local_rewrite_clients->argv; *cpp != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: trying: %s", myname, *cpp);
+ status = SMTPD_CHECK_DUNNO;
+ if (strchr(name = *cpp, ':') != 0) {
+ name = CHECK_ADDR_MAP;
+ cpp -= 1;
+ }
+ if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) {
+ status = permit_inet_interfaces(state);
+ } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) {
+ status = permit_mynetworks(state);
+ } else if (is_map_command(state, name, CHECK_ADDR_MAP, &cpp)) {
+ if ((maps = (MAPS *) htable_find(map_command_table, *cpp)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *cpp);
+ if (maps_find(maps, state->addr, 0) != 0)
+ status = SMTPD_CHECK_OK;
+ else if (maps->error != 0) {
+ /* Warning is already logged. */
+ status = maps->error;
+ }
+ } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (smtpd_sasl_is_active(state))
+ status = permit_sasl_auth(state, SMTPD_CHECK_OK,
+ SMTPD_CHECK_DUNNO);
+#endif
+ } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 1);
+ } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 0);
+ } else {
+ msg_warn("parameter %s: invalid request: %s",
+ VAR_LOC_RWR_CLIENTS, name);
+ continue;
+ }
+ if (status < 0) {
+ if (status == DICT_ERR_RETRY) {
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ log_whatsup(state, "reject",
+ "451 4.3.0 Temporary lookup error");
+ return ("451 4.3.0 Temporary lookup error");
+ } else {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ log_whatsup(state, "reject",
+ "451 4.3.5 Server configuration error");
+ return ("451 4.3.5 Server configuration error");
+ }
+ }
+ if (status == SMTPD_CHECK_OK) {
+ state->rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ return (0);
+ }
+ }
+ state->rewrite_context = MAIL_ATTR_RWR_REMOTE;
+ return (0);
+}
+
+/* smtpd_check_client - validate client name or address */
+
+char *smtpd_check_client(SMTPD_STATE *state)
+{
+ int status;
+
+ /*
+ * Initialize.
+ */
+ if (state->name == 0 || state->addr == 0)
+ return (0);
+
+#define SMTPD_CHECK_RESET() { \
+ state->recursion = 0; \
+ state->warn_if_reject = 0; \
+ state->defer_if_reject.active = 0; \
+ }
+
+ /*
+ * Reset the defer_if_permit flag.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && client_restrctions->argc)
+ status = generic_checks(state, client_restrctions, state->namaddr,
+ SMTPD_NAME_CLIENT, CHECK_CLIENT_ACL);
+ state->defer_if_permit_client = state->defer_if_permit.active;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_helo - validate HELO hostname */
+
+char *smtpd_check_helo(SMTPD_STATE *state, char *helohost)
+{
+ int status;
+ char *saved_helo;
+
+ /*
+ * Initialize.
+ */
+ if (helohost == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+#define SMTPD_CHECK_PUSH(backup, current, new) { \
+ backup = current; \
+ current = (new ? mystrdup(new) : 0); \
+ }
+
+#define SMTPD_CHECK_POP(current, backup) { \
+ if (current) myfree(current); \
+ current = backup; \
+ }
+
+ SMTPD_CHECK_PUSH(saved_helo, state->helo_name, helohost);
+
+#define SMTPD_CHECK_HELO_RETURN(x) { \
+ SMTPD_CHECK_POP(state->helo_name, saved_helo); \
+ return (x); \
+ }
+
+ /*
+ * Restore the defer_if_permit flag to its value before HELO/EHLO, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && helo_restrctions->argc)
+ status = generic_checks(state, helo_restrctions, state->helo_name,
+ SMTPD_NAME_HELO, CHECK_HELO_ACL);
+ state->defer_if_permit_helo = state->defer_if_permit.active;
+
+ SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_mail - validate sender address, driver */
+
+char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
+{
+ int status;
+ char *saved_sender;
+
+ /*
+ * Initialize.
+ */
+ if (sender == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_sender, state->sender, sender);
+
+#define SMTPD_CHECK_MAIL_RETURN(x) { \
+ SMTPD_CHECK_POP(state->sender, saved_sender); \
+ return (x); \
+ }
+
+ /*
+ * Restore the defer_if_permit flag to its value before MAIL FROM, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage. The client may skip the helo/ehlo.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client
+ | state->defer_if_permit_helo;
+ state->sender_rcptmap_checked = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && mail_restrctions->argc)
+ status = generic_checks(state, mail_restrctions, sender,
+ SMTPD_NAME_SENDER, CHECK_SENDER_ACL);
+ state->defer_if_permit_sender = state->defer_if_permit.active;
+
+ /*
+ * If the "reject_unlisted_sender" restriction still needs to be applied,
+ * validate the sender here.
+ */
+ if (var_smtpd_rej_unl_from
+ && status != SMTPD_CHECK_REJECT && state->sender_rcptmap_checked == 0
+ && state->discard == 0 && *sender)
+ status = check_sender_rcpt_maps(state, sender);
+
+ SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_rcpt - validate recipient address, driver */
+
+char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
+{
+ int status;
+ char *saved_recipient;
+ char *err;
+ ARGV *restrctions[2];
+ int n;
+ int rcpt_index;
+ int relay_index;
+
+ /*
+ * Initialize.
+ */
+ if (recipient == 0)
+ return (0);
+
+ /*
+ * XXX 2821: Section 3.6 requires that "postmaster" be accepted even when
+ * specified without a fully qualified domain name.
+ */
+ if (strcasecmp(recipient, "postmaster") == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient);
+
+#define SMTPD_CHECK_RCPT_RETURN(x) { \
+ SMTPD_CHECK_POP(state->recipient, saved_recipient); \
+ return (x); \
+ }
+
+ /*
+ * The "check_recipient_maps" restriction is relevant only when
+ * responding to RCPT TO or VRFY.
+ */
+ state->recipient_rcptmap_checked = 0;
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0
+ || (err = smtpd_check_mail(state, state->sender)) != 0)
+ SMTPD_CHECK_RCPT_RETURN(err);
+
+ /*
+ * Restore the defer_if_permit flag to its value before RCPT TO, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_sender;
+
+ /*
+ * Apply restrictions in the order as specified. We allow relay
+ * restrictions to be empty, for sites that require backwards
+ * compatibility.
+ *
+ * If compatibility_level < 1 and smtpd_relay_restrictions is left at its
+ * default value, find out if the new smtpd_relay_restrictions default
+ * value would block the request, without logging REJECT messages.
+ * Approach: evaluate fake relay restrictions (permit_mynetworks,
+ * permit_sasl_authenticated, permit_auth_destination) and log a warning
+ * if the result is DUNNO instead of OK, i.e. a reject_unauth_destination
+ * at the end would have blocked the request.
+ *
+ * If warn_compat_break_relay_restrictions is true, always evaluate
+ * smtpd_relay_restrictions last (rcpt_index == 0). The backwards
+ * compatibility warning says that it avoids blocking a recipient (with
+ * "Relay access denied"); that is not useful information when moments
+ * later, smtpd_recipient_restrictions blocks the recipient anyway (with
+ * 'Relay access denied' or some other cause).
+ */
+ SMTPD_CHECK_RESET();
+ rcpt_index = (var_relay_before_rcpt_checks
+ && !warn_compat_break_relay_restrictions);
+ relay_index = !rcpt_index;
+
+ restrctions[rcpt_index] = rcpt_restrctions;
+ restrctions[relay_index] = warn_compat_break_relay_restrictions ?
+ fake_relay_restrctions : relay_restrctions;
+ for (n = 0; n < 2; n++) {
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && restrctions[n]->argc)
+ status = generic_checks(state, restrctions[n],
+ recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL);
+ if (n == relay_index && warn_compat_break_relay_restrictions
+ && status == SMTPD_CHECK_DUNNO) {
+ msg_info("using backwards-compatible default setting \""
+ VAR_RELAY_CHECKS " = (empty)\" to avoid \"Relay "
+ "access denied\" error for recipient \"%s\" from "
+ "client \"%s\"", state->recipient, state->namaddr);
+ }
+ if (status == SMTPD_CHECK_REJECT)
+ break;
+ }
+ if (status == SMTPD_CHECK_REJECT
+ && warn_compat_relay_before_rcpt_checks && n == 0)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_BEFORE_RCPT_CHECKS "=no to reject "
+ "recipient \"%s\" from client \"%s\"",
+ state->recipient, state->namaddr);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ /*
+ * If the "reject_unlisted_recipient" restriction still needs to be
+ * applied, validate the recipient here.
+ */
+ if (var_smtpd_rej_unl_rcpt
+ && status != SMTPD_CHECK_REJECT
+ && state->recipient_rcptmap_checked == 0
+ && state->discard == 0)
+ status = check_recipient_rcpt_maps(state, recipient);
+
+ SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_etrn - validate ETRN request */
+
+char *smtpd_check_etrn(SMTPD_STATE *state, char *domain)
+{
+ int status;
+ char *saved_etrn_name;
+ char *err;
+
+ /*
+ * Initialize.
+ */
+ if (domain == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_etrn_name, state->etrn_name, domain);
+
+#define SMTPD_CHECK_ETRN_RETURN(x) { \
+ SMTPD_CHECK_POP(state->etrn_name, saved_etrn_name); \
+ return (x); \
+ }
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0)
+ SMTPD_CHECK_ETRN_RETURN(err);
+
+ /*
+ * Restore the defer_if_permit flag to its value before ETRN, and do not
+ * set the flag when it was already raised by a previous protocol stage.
+ * The client may skip the helo/ehlo.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client
+ | state->defer_if_permit_helo;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && etrn_restrctions->argc)
+ status = generic_checks(state, etrn_restrctions, domain,
+ SMTPD_NAME_ETRN, CHECK_ETRN_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* check_recipient_rcpt_maps - generic_checks() recipient table check */
+
+static int check_recipient_rcpt_maps(SMTPD_STATE *state, const char *recipient)
+{
+
+ /*
+ * Duplicate suppression. There's an implicit check_recipient_maps
+ * restriction at the end of all recipient restrictions.
+ */
+ if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
+ return (0);
+ if (state->recipient_rcptmap_checked == 1)
+ return (0);
+ if (state->warn_if_reject == 0)
+ /* We really validate the recipient address. */
+ state->recipient_rcptmap_checked = 1;
+ return (check_rcpt_maps(state, state->sender, recipient,
+ SMTPD_NAME_RECIPIENT));
+}
+
+/* check_sender_rcpt_maps - generic_checks() sender table check */
+
+static int check_sender_rcpt_maps(SMTPD_STATE *state, const char *sender)
+{
+
+ /*
+ * Duplicate suppression. There's an implicit check_sender_maps
+ * restriction at the end of all sender restrictions.
+ */
+ if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
+ return (0);
+ if (state->sender_rcptmap_checked == 1)
+ return (0);
+ if (state->warn_if_reject == 0)
+ /* We really validate the sender address. */
+ state->sender_rcptmap_checked = 1;
+ return (check_rcpt_maps(state, state->recipient, sender,
+ SMTPD_NAME_SENDER));
+}
+
+/* check_rcpt_maps - generic_checks() interface for recipient table check */
+
+static int check_rcpt_maps(SMTPD_STATE *state, const char *sender,
+ const char *recipient,
+ const char *reply_class)
+{
+ const RESOLVE_REPLY *reply;
+ DSN_SPLIT dp;
+
+ if (msg_verbose)
+ msg_info(">>> CHECKING %s VALIDATION MAPS <<<", reply_class);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * Make complex expressions more readable?
+ */
+#define MATCH(map, rcpt) \
+ check_mail_addr_find(state, recipient, map, rcpt, (char **) 0)
+
+#define NOMATCH(map, rcpt) (MATCH(map, rcpt) == 0)
+
+ /*
+ * XXX We assume the recipient address is OK if it matches a canonical
+ * map or virtual alias map. Eventually, the address resolver should give
+ * us the final resolved recipient address, and the SMTP server should
+ * write the final resolved recipient address to the output record
+ * stream. See also the next comment block on recipients in virtual alias
+ * domains.
+ */
+ if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient))
+ || (strcmp(reply_class, SMTPD_NAME_SENDER) == 0
+ && MATCH(send_canon_maps, CONST_STR(reply->recipient)))
+ || MATCH(canonical_maps, CONST_STR(reply->recipient))
+ || MATCH(virt_alias_maps, CONST_STR(reply->recipient)))
+ return (0);
+
+ /*
+ * At this point, anything that resolves to the error mailer is known to
+ * be undeliverable.
+ *
+ * XXX Until the address resolver does final address resolution, known and
+ * unknown recipients in virtual alias domains will both resolve to
+ * "error:user unknown".
+ */
+ if (strcmp(STR(reply->transport), MAIL_SERVICE_ERROR) == 0) {
+ dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1", STR(reply->nexthop));
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ (reply->flags & RESOLVE_CLASS_ALIAS) ?
+ var_virt_alias_code : 550,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ recipient, reply_class,
+ dp.text));
+ }
+ if (strcmp(STR(reply->transport), MAIL_SERVICE_RETRY) == 0) {
+ dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.0" : "4.1.1", STR(reply->nexthop));
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, 450,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ recipient, reply_class,
+ dp.text));
+ }
+
+ /*
+ * Search the recipient lookup tables of the respective address class.
+ *
+ * XXX Use the less expensive maps_find() (built-in case folding) instead of
+ * the baroque mail_addr_find(). But then we have to strip the domain and
+ * deal with address extensions ourselves.
+ *
+ * XXX But that would break sites that use the virtual delivery agent for
+ * local delivery, because the virtual delivery agent requires
+ * user@domain style addresses in its user database.
+ */
+#define MATCH_LEFT(l, r, n) \
+ (strncasecmp_utf8((l), (r), (n)) == 0 && (r)[n] == '@')
+
+ switch (reply->flags & RESOLVE_CLASS_MASK) {
+
+ /*
+ * Reject mail to unknown addresses in local domains (domains that
+ * match $mydestination or ${proxy,inet}_interfaces).
+ */
+ case RESOLVE_CLASS_LOCAL:
+ if (*var_local_rcpt_maps
+ /* Generated by bounce, absorbed by qmgr. */
+ && !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient),
+ strlen(var_double_bounce_sender))
+ /* Absorbed by qmgr. */
+ && !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient),
+ strlen(MAIL_ADDR_POSTMASTER))
+ /* Generated by bounce. */
+ && !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient),
+ strlen(MAIL_ADDR_MAIL_DAEMON))
+ && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_local_rcpt_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in local recipient table" : ""));
+ break;
+
+ /*
+ * Reject mail to unknown addresses in virtual mailbox domains.
+ */
+ case RESOLVE_CLASS_VIRTUAL:
+ if (*var_virt_mailbox_maps
+ && NOMATCH(virt_mailbox_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_virt_mailbox_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in virtual mailbox table" : ""));
+ break;
+
+ /*
+ * Reject mail to unknown addresses in relay domains.
+ */
+ case RESOLVE_CLASS_RELAY:
+ if (*var_relay_rcpt_maps
+ && NOMATCH(relay_rcpt_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_relay_rcpt_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in relay recipient table" : ""));
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for address \"%s\"", recipient);
+ break;
+ }
+
+ /*
+ * Accept all other addresses - including addresses that passed the above
+ * tests because of some table lookup problem.
+ */
+ return (0);
+}
+
+/* smtpd_check_size - check optional SIZE parameter value */
+
+char *smtpd_check_size(SMTPD_STATE *state, off_t size)
+{
+ int status;
+
+ /*
+ * Return here in case of serious trouble.
+ */
+ SMTPD_CHECK_RESET();
+ if ((status = setjmp(smtpd_check_buf)) != 0)
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+
+ /*
+ * Check against file size limit.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit) && size > var_message_limit) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ 552, "5.3.4",
+ "Message size exceeds fixed limit");
+ return (STR(error_text));
+ }
+ return (0);
+}
+
+/* smtpd_check_queue - check queue space */
+
+char *smtpd_check_queue(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_check_queue";
+ struct fsspace fsbuf;
+ int status;
+
+ /*
+ * Return here in case of serious trouble.
+ */
+ SMTPD_CHECK_RESET();
+ if ((status = setjmp(smtpd_check_buf)) != 0)
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+
+ /*
+ * Avoid overflow/underflow when comparing message size against available
+ * space.
+ */
+#define BLOCKS(x) ((x) / fsbuf.block_size)
+
+ fsspace(".", &fsbuf);
+ if (msg_verbose)
+ msg_info("%s: blocks %lu avail %lu min_free %lu msg_size_limit %lu",
+ myname,
+ (unsigned long) fsbuf.block_size,
+ (unsigned long) fsbuf.block_free,
+ (unsigned long) var_queue_minfree,
+ (unsigned long) var_message_limit);
+ if (BLOCKS(var_queue_minfree) >= fsbuf.block_free
+ || BLOCKS(var_message_limit) >= fsbuf.block_free / smtpd_space_multf) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE,
+ 452, "4.3.1",
+ "Insufficient system storage");
+ msg_warn("not enough free space in mail queue: %lu bytes < "
+ "%g*message size limit",
+ (unsigned long) fsbuf.block_free * fsbuf.block_size,
+ smtpd_space_multf);
+ return (STR(error_text));
+ }
+ return (0);
+}
+
+/* smtpd_check_data - check DATA command */
+
+char *smtpd_check_data(SMTPD_STATE *state)
+{
+ int status;
+ char *NOCLOBBER saved_recipient;
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine. We
+ * provide no recipient information in the case of multiple recipients,
+ * This restriction applies to all recipients alike, and logging only one
+ * of them would be misleading.
+ */
+ if (state->rcpt_count > 1) {
+ saved_recipient = state->recipient;
+ state->recipient = 0;
+ }
+
+ /*
+ * Reset the defer_if_permit flag. This is necessary when some recipients
+ * were accepted but the last one was rejected.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ *
+ * XXX We cannot specify a default target for a bare access map.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && data_restrctions->argc)
+ status = generic_checks(state, data_restrctions,
+ SMTPD_CMD_DATA, SMTPD_NAME_DATA, NO_DEF_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ if (state->rcpt_count > 1)
+ state->recipient = saved_recipient;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_eod - check end-of-data command */
+
+char *smtpd_check_eod(SMTPD_STATE *state)
+{
+ int status;
+ char *NOCLOBBER saved_recipient;
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine. We
+ * provide no recipient information in the case of multiple recipients,
+ * This restriction applies to all recipients alike, and logging only one
+ * of them would be misleading.
+ */
+ if (state->rcpt_count > 1) {
+ saved_recipient = state->recipient;
+ state->recipient = 0;
+ }
+
+ /*
+ * Reset the defer_if_permit flag. This is necessary when some recipients
+ * were accepted but the last one was rejected.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ *
+ * XXX We cannot specify a default target for a bare access map.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && eod_restrictions->argc)
+ status = generic_checks(state, eod_restrictions,
+ SMTPD_CMD_EOD, SMTPD_NAME_EOD, NO_DEF_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ if (state->rcpt_count > 1)
+ state->recipient = saved_recipient;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program to try out all these restrictions without having to go live.
+ * This is not entirely stand-alone, as it requires access to the Postfix
+ * rewrite/resolve service. This is just for testing code, not for debugging
+ * configuration files.
+ */
+#include <stdlib.h>
+
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+
+#include <mail_conf.h>
+#include <rewrite_clnt.h>
+#include <dns.h>
+
+#include <smtpd_chat.h>
+
+int smtpd_input_transp_mask;
+
+ /*
+ * Dummies. These are never set.
+ */
+char *var_client_checks = "";
+char *var_helo_checks = "";
+char *var_mail_checks = "";
+char *var_relay_checks = "";
+char *var_rcpt_checks = "";
+char *var_etrn_checks = "";
+char *var_data_checks = "";
+char *var_eod_checks = "";
+char *var_smtpd_uproxy_proto = "";
+int var_smtpd_uproxy_tmout = 0;
+
+#ifdef USE_TLS
+char *var_relay_ccerts = "";
+
+#endif
+char *var_notify_classes = "";
+char *var_smtpd_policy_def_action = "";
+char *var_smtpd_policy_context = "";
+
+ /*
+ * String-valued configuration parameters.
+ */
+char *var_maps_rbl_domains;
+char *var_rest_classes;
+char *var_alias_maps;
+char *var_send_canon_maps;
+char *var_rcpt_canon_maps;
+char *var_canonical_maps;
+char *var_virt_alias_maps;
+char *var_virt_alias_doms;
+char *var_virt_mailbox_maps;
+char *var_virt_mailbox_doms;
+char *var_local_rcpt_maps;
+char *var_perm_mx_networks;
+char *var_smtpd_null_key;
+char *var_smtpd_snd_auth_maps;
+char *var_rbl_reply_maps;
+char *var_smtpd_exp_filter;
+char *var_def_rbl_reply;
+char *var_relay_rcpt_maps;
+char *var_verify_sender;
+char *var_smtpd_sasl_opts;
+char *var_local_rwr_clients;
+char *var_smtpd_relay_ccerts;
+char *var_unv_from_why;
+char *var_unv_rcpt_why;
+char *var_stress;
+char *var_unk_name_tf_act;
+char *var_unk_addr_tf_act;
+char *var_unv_rcpt_tf_act;
+char *var_unv_from_tf_act;
+char *var_smtpd_acl_perm_log;
+
+typedef struct {
+ char *name;
+ char *defval;
+ char **target;
+} STRING_TABLE;
+
+#undef DEF_VIRT_ALIAS_MAPS
+#define DEF_VIRT_ALIAS_MAPS ""
+
+#undef DEF_LOCAL_RCPT_MAPS
+#define DEF_LOCAL_RCPT_MAPS ""
+
+static const STRING_TABLE string_table[] = {
+ VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains,
+ VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin,
+ VAR_MYDEST, DEF_MYDEST, &var_mydest,
+ VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces,
+ VAR_PROXY_INTERFACES, DEF_PROXY_INTERFACES, &var_proxy_interfaces,
+ VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim,
+ VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes,
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps,
+ VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps,
+ VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps,
+ VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps,
+ VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms,
+ VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps,
+ VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks,
+ VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match,
+ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps,
+ VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key,
+ VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender,
+ VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps,
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter,
+ VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply,
+ VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps,
+ VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender,
+ VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name,
+ VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts,
+ VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients,
+ VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts,
+ VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why,
+ VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why,
+ VAR_STRESS, DEF_STRESS, &var_stress,
+ /* XXX Can't use ``$name'' type default values below. */
+ VAR_UNK_NAME_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_name_tf_act,
+ VAR_UNK_ADDR_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_addr_tf_act,
+ VAR_UNV_RCPT_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_rcpt_tf_act,
+ VAR_UNV_FROM_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_from_tf_act,
+ /* XXX Can't use ``$name'' type default values above. */
+ VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log,
+ VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter,
+ VAR_INFO_LOG_ADDR_FORM, DEF_INFO_LOG_ADDR_FORM, &var_info_log_addr_form,
+ /* XXX No static initialization with "", because owned by a library. */
+ VAR_MYNETWORKS, "", &var_mynetworks,
+ VAR_RELAY_DOMAINS, "", &var_relay_domains,
+ 0,
+};
+
+/* string_init - initialize string parameters */
+
+static void string_init(void)
+{
+ const STRING_TABLE *sp;
+
+ for (sp = string_table; sp->name; sp++)
+ sp->target[0] = mystrdup(sp->defval);
+}
+
+/* string_update - update string parameter */
+
+static int string_update(char **argv)
+{
+ const STRING_TABLE *sp;
+
+ for (sp = string_table; sp->name; sp++) {
+ if (strcasecmp(argv[0], sp->name) == 0) {
+ myfree(sp->target[0]);
+ sp->target[0] = mystrdup(argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+ /*
+ * Integer parameters.
+ */
+long var_queue_minfree; /* XXX use off_t */
+typedef struct {
+ char *name;
+ int defval;
+ int *target;
+} INT_TABLE;
+
+int var_unk_client_code;
+int var_bad_name_code;
+int var_unk_name_code;
+int var_unk_addr_code;
+int var_relay_code;
+int var_maps_rbl_code;
+int var_map_reject_code;
+int var_map_defer_code;
+int var_reject_code;
+int var_defer_code;
+int var_non_fqdn_code;
+int var_smtpd_delay_reject;
+int var_allow_untrust_route;
+int var_mul_rcpt_code;
+int var_unv_from_rcode;
+int var_unv_from_dcode;
+int var_unv_rcpt_rcode;
+int var_unv_rcpt_dcode;
+int var_local_rcpt_code;
+int var_relay_rcpt_code;
+int var_virt_mailbox_code;
+int var_virt_alias_code;
+int var_show_unk_rcpt_table;
+int var_verify_poll_count;
+int var_verify_poll_delay;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
+int var_smtpd_policy_req_limit;
+int var_smtpd_policy_try_limit;
+int var_smtpd_policy_try_delay;
+int var_smtpd_rej_unl_from;
+int var_smtpd_rej_unl_rcpt;
+int var_plaintext_code;
+bool var_smtpd_peername_lookup;
+bool var_smtpd_client_port_log;
+char *var_smtpd_dns_re_filter;
+bool var_smtpd_tls_ask_ccert;
+
+#define int_table test_int_table
+
+static const INT_TABLE int_table[] = {
+ "msg_verbose", 0, &msg_verbose,
+ VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code,
+ VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code,
+ VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code,
+ VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code,
+ VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code,
+ VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code,
+ VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code,
+ VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code,
+ VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code,
+ VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code,
+ VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
+ VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
+ VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code,
+ VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode,
+ VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode,
+ VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode,
+ VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode,
+ VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code,
+ VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code,
+ VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code,
+ VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_VERIFY_POLL_COUNT, 3, &var_verify_poll_count,
+ VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from,
+ VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt,
+ VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code,
+ VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
+ VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
+ VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
+ 0,
+};
+
+/* int_init - initialize int parameters */
+
+static void int_init(void)
+{
+ const INT_TABLE *sp;
+
+ for (sp = int_table; sp->name; sp++)
+ sp->target[0] = sp->defval;
+}
+
+/* int_update - update int parameter */
+
+static int int_update(char **argv)
+{
+ const INT_TABLE *ip;
+
+ for (ip = int_table; ip->name; ip++) {
+ if (strcasecmp(argv[0], ip->name) == 0) {
+ if (!ISDIGIT(*argv[1]))
+ msg_fatal("bad number: %s %s", ip->name, argv[1]);
+ ip->target[0] = atoi(argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+ /*
+ * Boolean parameters.
+ */
+bool var_relay_before_rcpt_checks;
+
+ /*
+ * Restrictions.
+ */
+typedef struct {
+ char *name;
+ ARGV **target;
+} REST_TABLE;
+
+static const REST_TABLE rest_table[] = {
+ "client_restrictions", &client_restrctions,
+ "helo_restrictions", &helo_restrctions,
+ "sender_restrictions", &mail_restrctions,
+ "relay_restrictions", &relay_restrctions,
+ "recipient_restrictions", &rcpt_restrctions,
+ "etrn_restrictions", &etrn_restrctions,
+ 0,
+};
+
+/* rest_update - update restriction */
+
+static int rest_update(char **argv)
+{
+ const REST_TABLE *rp;
+
+ for (rp = rest_table; rp->name; rp++) {
+ if (strcasecmp(rp->name, argv[0]) == 0) {
+ argv_free(rp->target[0]);
+ rp->target[0] = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* rest_class - (re)define a restriction class */
+
+static void rest_class(char *class)
+{
+ char *cp = class;
+ char *name;
+ HTABLE_INFO *entry;
+
+ if (smtpd_rest_classes == 0)
+ smtpd_rest_classes = htable_create(1);
+
+ if ((name = mystrtok(&cp, CHARS_COMMA_SP)) == 0)
+ msg_panic("rest_class: null class name");
+ if ((entry = htable_locate(smtpd_rest_classes, name)) != 0)
+ argv_free((ARGV *) entry->value);
+ else
+ entry = htable_enter(smtpd_rest_classes, name, (void *) 0);
+ entry->value = (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, cp);
+}
+
+/* resolve_clnt_init - initialize reply */
+
+void resolve_clnt_init(RESOLVE_REPLY *reply)
+{
+ reply->flags = 0;
+ reply->transport = vstring_alloc(100);
+ reply->nexthop = vstring_alloc(100);
+ reply->recipient = vstring_alloc(100);
+}
+
+void resolve_clnt_free(RESOLVE_REPLY *reply)
+{
+ vstring_free(reply->transport);
+ vstring_free(reply->nexthop);
+ vstring_free(reply->recipient);
+}
+
+bool var_smtpd_sasl_enable = 0;
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_activate - stub */
+
+void smtpd_sasl_activate(SMTPD_STATE *state, const char *opts_name,
+ const char *opts_var)
+{
+ msg_panic("smtpd_sasl_activate was called");
+}
+
+/* smtpd_sasl_deactivate - stub */
+
+void smtpd_sasl_deactivate(SMTPD_STATE *state)
+{
+ msg_panic("smtpd_sasl_deactivate was called");
+}
+
+/* permit_sasl_auth - stub */
+
+int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot)
+{
+ return (ifnot);
+}
+
+/* smtpd_sasl_state_init - the real deal */
+
+void smtpd_sasl_state_init(SMTPD_STATE *state)
+{
+ state->sasl_username = 0;
+ state->sasl_method = 0;
+ state->sasl_sender = 0;
+}
+
+#endif
+
+/* verify_clnt_query - stub */
+
+int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why)
+{
+ *addr_status = DEL_RCPT_STAT_OK;
+ return (VRFY_STAT_OK);
+}
+
+/* rewrite_clnt_internal - stub */
+
+VSTRING *rewrite_clnt_internal(const char *context, const char *addr,
+ VSTRING *result)
+{
+ if (addr == STR(result))
+ msg_panic("rewrite_clnt_internal: result clobbers input");
+ if (*addr && strchr(addr, '@') == 0)
+ msg_fatal("%s: address rewriting is disabled", addr);
+ vstring_strcpy(result, addr);
+ return (result);
+}
+
+/* resolve_clnt_query - stub */
+
+void resolve_clnt(const char *class, const char *unused_sender, const char *addr,
+ RESOLVE_REPLY *reply)
+{
+ const char *domain;
+ int rc;
+
+ if (addr == CONST_STR(reply->recipient))
+ msg_panic("resolve_clnt_query: result clobbers input");
+ if (strchr(addr, '%'))
+ msg_fatal("%s: address rewriting is disabled", addr);
+ if ((domain = strrchr(addr, '@')) == 0)
+ msg_fatal("%s: unqualified address", addr);
+ domain += 1;
+ if ((rc = resolve_local(domain)) > 0) {
+ reply->flags = RESOLVE_CLASS_LOCAL;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_LOCAL);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (rc < 0) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (string_list_match(virt_alias_doms, domain)) {
+ reply->flags = RESOLVE_CLASS_ALIAS;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_ERROR);
+ vstring_strcpy(reply->nexthop, "user unknown");
+ } else if (virt_alias_doms->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (string_list_match(virt_mailbox_doms, domain)) {
+ reply->flags = RESOLVE_CLASS_VIRTUAL;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_VIRTUAL);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (virt_mailbox_doms->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (domain_list_match(relay_domains, domain)) {
+ reply->flags = RESOLVE_CLASS_RELAY;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_RELAY);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (relay_domains->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else {
+ reply->flags = RESOLVE_CLASS_DEFAULT;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_SMTP);
+ vstring_strcpy(reply->nexthop, domain);
+ }
+ vstring_strcpy(reply->recipient, addr);
+}
+
+/* smtpd_chat_reset - stub */
+
+void smtpd_chat_reset(SMTPD_STATE *unused_state)
+{
+}
+
+/* usage - scream and terminate */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s", myname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ SMTPD_STATE state;
+ ARGV *args;
+ char *bp;
+ char *resp;
+ char *addr;
+
+ /*
+ * Initialization. Use dummies for client information.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 1)
+ usage(argv[0]);
+ string_init();
+ int_init();
+ smtpd_check_init();
+ smtpd_expand_init();
+ (void) inet_proto_init(argv[0], INET_PROTO_NAME_IPV4);
+ smtpd_state_init(&state, VSTREAM_IN, "smtpd");
+ state.queue_id = "<queue id>";
+
+ /*
+ * Main loop: update config parameters or test the client, helo, sender
+ * and recipient restrictions.
+ */
+ while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) {
+
+ /*
+ * Tokenize the command. Note, the comma is not a separator, so that
+ * restriction lists can be entered as comma-separated lists.
+ */
+ bp = STR(buf);
+ if (!isatty(0)) {
+ vstream_printf(">>> %s\n", bp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bp == '#')
+ continue;
+
+ if (*bp == '!') {
+ vstream_printf("exit %d\n", system(bp + 1));
+ continue;
+ }
+ args = argv_splitq(bp, CHARS_SPACE, CHARS_BRACE);
+
+ /*
+ * Recognize the command.
+ */
+ resp = "bad command";
+ switch (args->argc) {
+
+ /*
+ * Emtpy line.
+ */
+ case 0:
+ argv_free(args);
+ continue;
+
+ /*
+ * Special case: rewrite context.
+ */
+ case 1:
+ if (strcasecmp(args->argv[0], "rewrite") == 0) {
+ resp = smtpd_check_rewrite(&state);
+ break;
+ }
+
+ /*
+ * Other parameter-less commands.
+ */
+ if (strcasecmp(args->argv[0], "flush_dnsxl_cache") == 0) {
+ if (smtpd_rbl_cache) {
+ ctable_free(smtpd_rbl_cache);
+ ctable_free(smtpd_rbl_byte_cache);
+ }
+ smtpd_rbl_cache = ctable_create(100, rbl_pagein,
+ rbl_pageout, (void *) 0);
+ smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein,
+ rbl_byte_pageout, (void *) 0);
+ resp = 0;
+ break;
+ }
+
+ /*
+ * Special case: client identity.
+ */
+ case 4:
+ case 3:
+ if (strcasecmp(args->argv[0], "client") == 0) {
+ state.where = SMTPD_AFTER_CONNECT;
+ UPDATE_STRING(state.name, args->argv[1]);
+ UPDATE_STRING(state.reverse_name, args->argv[1]);
+ UPDATE_STRING(state.addr, args->argv[2]);
+ if (args->argc == 4)
+ state.name_status =
+ state.reverse_name_status =
+ atoi(args->argv[3]);
+ else if (strcmp(state.name, "unknown") == 0)
+ state.name_status =
+ state.reverse_name_status =
+ SMTPD_PEER_CODE_TEMP;
+ else
+ state.name_status =
+ state.reverse_name_status =
+ SMTPD_PEER_CODE_OK;
+ if (state.namaddr)
+ myfree(state.namaddr);
+ state.namaddr = concatenate(state.name, "[", state.addr,
+ "]", (char *) 0);
+ resp = smtpd_check_client(&state);
+ }
+ break;
+
+ /*
+ * Try config settings.
+ */
+#define UPDATE_MAPS(ptr, var, val, lock) \
+ { if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); }
+
+#define UPDATE_LIST(ptr, var, val) \
+ { if (ptr) string_list_free(ptr); \
+ ptr = string_list_init(var, MATCH_FLAG_NONE, val); }
+
+ case 2:
+ if (strcasecmp(args->argv[0], VAR_MYDEST) == 0) {
+ UPDATE_STRING(var_mydest, args->argv[1]);
+ resolve_local_init();
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_MAPS) == 0) {
+ UPDATE_STRING(var_virt_alias_maps, args->argv[1]);
+ UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS,
+ var_virt_alias_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) {
+ UPDATE_STRING(var_virt_alias_doms, args->argv[1]);
+ UPDATE_LIST(virt_alias_doms, VAR_VIRT_ALIAS_DOMS,
+ var_virt_alias_doms);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) {
+ UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]);
+ UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS,
+ var_virt_mailbox_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) {
+ UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]);
+ UPDATE_LIST(virt_mailbox_doms, VAR_VIRT_MAILBOX_DOMS,
+ var_virt_mailbox_doms);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_LOCAL_RCPT_MAPS) == 0) {
+ UPDATE_STRING(var_local_rcpt_maps, args->argv[1]);
+ UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS,
+ var_local_rcpt_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RELAY_RCPT_MAPS) == 0) {
+ UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]);
+ UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS,
+ var_relay_rcpt_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_CANONICAL_MAPS) == 0) {
+ UPDATE_STRING(var_canonical_maps, args->argv[1]);
+ UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS,
+ var_canonical_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_SEND_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_send_canon_maps, args->argv[1]);
+ UPDATE_MAPS(send_canon_maps, VAR_SEND_CANON_MAPS,
+ var_send_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RCPT_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_rcpt_canon_maps, args->argv[1]);
+ UPDATE_MAPS(rcpt_canon_maps, VAR_RCPT_CANON_MAPS,
+ var_rcpt_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RBL_REPLY_MAPS) == 0) {
+ UPDATE_STRING(var_rbl_reply_maps, args->argv[1]);
+ UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS,
+ var_rbl_reply_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_MYNETWORKS) == 0) {
+ /* NOT: UPDATE_STRING */
+ namadr_list_free(mynetworks_curr);
+ mynetworks_curr =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS),
+ args->argv[1]);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RELAY_DOMAINS) == 0) {
+ /* NOT: UPDATE_STRING */
+ domain_list_free(relay_domains);
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS,
+ match_parent_style(VAR_RELAY_DOMAINS),
+ args->argv[1]);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_PERM_MX_NETWORKS) == 0) {
+ UPDATE_STRING(var_perm_mx_networks, args->argv[1]);
+ domain_list_free(perm_mx_networks);
+ perm_mx_networks =
+ namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_PERM_MX_NETWORKS),
+ args->argv[1]);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_SMTPD_DNS_RE_FILTER) == 0) {
+ /* NOT: UPDATE_STRING */
+ dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, args->argv[1]);
+ resp = 0;
+ break;
+ }
+#ifdef USE_TLS
+ if (strcasecmp(args->argv[0], VAR_RELAY_CCERTS) == 0) {
+ UPDATE_STRING(var_smtpd_relay_ccerts, args->argv[1]);
+ UPDATE_MAPS(relay_ccerts, VAR_RELAY_CCERTS,
+ var_smtpd_relay_ccerts, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX);
+ resp = 0;
+ }
+#endif
+ if (strcasecmp(args->argv[0], "restriction_class") == 0) {
+ rest_class(args->argv[1]);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_LOC_RWR_CLIENTS) == 0) {
+ UPDATE_STRING(var_local_rwr_clients, args->argv[1]);
+ argv_free(local_rewrite_clients);
+ local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS,
+ var_local_rwr_clients);
+ }
+ if (int_update(args->argv)
+ || string_update(args->argv)
+ || rest_update(args->argv)) {
+ resp = 0;
+ break;
+ }
+
+ /*
+ * Try restrictions.
+ */
+#define TRIM_ADDR(src, res) { \
+ if (*(res = src) == '<') { \
+ res += strlen(res) - 1; \
+ if (*res == '>') \
+ *res = 0; \
+ res = src + 1; \
+ } \
+ }
+
+ if (strcasecmp(args->argv[0], "helo") == 0) {
+ state.where = "HELO";
+ resp = smtpd_check_helo(&state, args->argv[1]);
+ UPDATE_STRING(state.helo_name, args->argv[1]);
+ } else if (strcasecmp(args->argv[0], "mail") == 0) {
+ state.where = "MAIL";
+ TRIM_ADDR(args->argv[1], addr);
+ UPDATE_STRING(state.sender, addr);
+ resp = smtpd_check_mail(&state, addr);
+ } else if (strcasecmp(args->argv[0], "rcpt") == 0) {
+ state.where = "RCPT";
+ TRIM_ADDR(args->argv[1], addr);
+ resp = smtpd_check_rcpt(&state, addr);
+#ifdef USE_TLS
+ } else if (strcasecmp(args->argv[0], "fingerprint") == 0) {
+ if (state.tls_context == 0) {
+ state.tls_context =
+ (TLS_SESS_STATE *) mymalloc(sizeof(*state.tls_context));
+ memset((void *) state.tls_context, 0,
+ sizeof(*state.tls_context));
+ state.tls_context->peer_cert_fprint =
+ state.tls_context->peer_pkey_fprint = 0;
+ }
+ state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT;
+ UPDATE_STRING(state.tls_context->peer_cert_fprint,
+ args->argv[1]);
+ state.tls_context->peer_pkey_fprint =
+ state.tls_context->peer_cert_fprint;
+ resp = "OK";
+ break;
+#endif
+ }
+ break;
+
+ /*
+ * Show commands.
+ */
+ default:
+ if (strcasecmp(args->argv[0], "check_rewrite") == 0) {
+ smtpd_check_rewrite(&state);
+ resp = state.rewrite_context;
+ break;
+ }
+ resp = "Commands...\n\
+ client <name> <address> [<code>]\n\
+ helo <hostname>\n\
+ sender <address>\n\
+ recipient <address>\n\
+ check_rewrite\n\
+ msg_verbose <level>\n\
+ client_restrictions <restrictions>\n\
+ helo_restrictions <restrictions>\n\
+ sender_restrictions <restrictions>\n\
+ recipient_restrictions <restrictions>\n\
+ restriction_class name,<restrictions>\n\
+ flush_dnsxl_cache\n\
+ \n\
+ Note: no address rewriting \n";
+ break;
+ }
+ vstream_printf("%s\n", resp ? resp : "OK");
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(args);
+ }
+ vstring_free(buf);
+ smtpd_state_reset(&state);
+#define FREE_STRING(s) { if (s) myfree(s); }
+ FREE_STRING(state.helo_name);
+ FREE_STRING(state.sender);
+#ifdef USE_TLS
+ if (state.tls_context) {
+ FREE_STRING(state.tls_context->peer_cert_fprint);
+ myfree((void *) state.tls_context);
+ }
+#endif
+ exit(0);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_check.h b/src/smtpd/smtpd_check.h
new file mode 100644
index 0000000..bf0fe00
--- /dev/null
+++ b/src/smtpd/smtpd_check.h
@@ -0,0 +1,44 @@
+/*++
+/* NAME
+/* smtpd_check 3h
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void smtpd_check_init(void);
+extern int smtpd_check_addr(const char *, const char *, int);
+extern char *smtpd_check_rewrite(SMTPD_STATE *);
+extern char *smtpd_check_client(SMTPD_STATE *);
+extern char *smtpd_check_helo(SMTPD_STATE *, char *);
+extern char *smtpd_check_mail(SMTPD_STATE *, char *);
+extern char *smtpd_check_size(SMTPD_STATE *, off_t);
+extern char *smtpd_check_queue(SMTPD_STATE *);
+extern char *smtpd_check_rcpt(SMTPD_STATE *, char *);
+extern char *smtpd_check_etrn(SMTPD_STATE *, char *);
+extern char *smtpd_check_data(SMTPD_STATE *);
+extern char *smtpd_check_eod(SMTPD_STATE *);
+extern char *smtpd_check_policy(SMTPD_STATE *, char *);
+extern void log_whatsup(SMTPD_STATE *, const char *, const char *);
+
+/* 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/smtpd/smtpd_check.in b/src/smtpd/smtpd_check.in
new file mode 100644
index 0000000..efeba5b
--- /dev/null
+++ b/src/smtpd/smtpd_check.in
@@ -0,0 +1,182 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+#
+# Test the client restrictions.
+#
+client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access
+client unknown 131.155.210.17
+client unknown 168.100.3.13
+client random.bad.domain 123.123.123.123
+client friend.bad.domain 123.123.123.123
+client bad.domain 123.123.123.123
+client wzv.win.tue.nl 131.155.210.17
+client aa.win.tue.nl 131.155.210.18
+client_restrictions permit_mynetworks
+#
+# Test the helo restrictions
+#
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access
+client unknown 131.155.210.17
+helo foo.
+client foo 123.123.123.123
+helo foo.
+helo foo
+helo spike.porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+helo random.bad.domain
+helo friend.bad.domain
+helo_restrictions reject_invalid_hostname,reject_unknown_hostname
+helo 123.123.123.123
+helo [123.123.123.123]
+helo [::]
+helo [ipv6:::]
+helo [ipv6::::]
+helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname
+helo 123.123.123.123
+#
+# Test the sender restrictions
+#
+sender_restrictions permit_mynetworks,reject_unknown_client
+client unknown 131.155.210.17
+mail foo@ibm.com
+client unknown 168.100.3.13
+mail foo@ibm.com
+client foo 123.123.123.123
+mail foo@ibm.com
+sender_restrictions reject_unknown_address
+mail foo@ibm.com
+mail foo@bad.domain
+sender_restrictions hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail Reject@this.address
+mail foo@bad.domain
+mail foo@Bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# Test the recipient restrictions
+#
+recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+client unknown 131.155.210.17
+rcpt foo@ibm.com
+client unknown 168.100.3.13
+rcpt foo@ibm.com
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_relay_domains
+client foo.porcupine.org 168.100.3.13
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail foo@bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# RBL
+#
+client_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.3.2
+client foo 127.0.0.2
+#
+# Hybrids
+#
+recipient_restrictions check_relay_domains
+client foo 131.155.210.17
+rcpt foo@ibm.com
+recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains
+client foo 131.155.210.17
+rcpt foo@porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains
+helo bad.domain
+rcpt foo@porcupine.org
+helo 131.155.210.17
+rcpt foo@porcupine.org
+recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains
+mail foo@bad.domain
+rcpt foo@porcupine.org
+mail foo@friend.bad.domain
+rcpt foo@porcupine.org
+#
+# MX backup
+#
+#mydestination spike.porcupine.org,localhost.porcupine.org
+#inet_interfaces 168.100.3.2,127.0.0.1
+#recipient_restrictions permit_mx_backup,reject
+#rcpt wietse@wzv.win.tue.nl
+#rcpt wietse@trouble.org
+#rcpt wietse@porcupine.org
+#
+# Deferred restrictions
+#
+client_restrictions permit
+helo_restrictions permit
+sender_restrictions permit
+recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access
+helo bad.domain
+mail foo@good.domain
+rcpt foo@porcupine.org
+helo good.domain
+mail foo@bad.domain
+rcpt foo@porcupine.org
+#
+# FQDN restrictions
+#
+helo_restrictions reject_non_fqdn_hostname
+sender_restrictions reject_non_fqdn_sender
+recipient_restrictions reject_non_fqdn_recipient
+helo foo.bar.
+helo foo.bar
+helo foo
+mail foo@foo.bar.
+mail foo@foo.bar
+mail foo@foo
+mail foo
+rcpt foo@foo.bar.
+rcpt foo@foo.bar
+rcpt foo@foo
+rcpt foo
+#
+# Numerical HELO checks
+#
+helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname
+helo [1.2.3.4]
+helo [321.255.255.255]
+helo [0.255.255.255]
+helo [1.2.3.321]
+helo [1.2.3]
+helo [1.2.3.4.5]
+helo [1..2.3.4]
+helo [.1.2.3.4]
+helo [1.2.3.4.5.]
+helo 1.2.3.4
+helo 321.255.255.255
+helo 0.255.255.255
+helo 1.2.3.321
+helo 1.2.3
+helo 1.2.3.4.5
+helo 1..2.3.4
+helo .1.2.3.4
+helo 1.2.3.4.5.
+#
+# The defer restriction
+#
+defer_code 444
+helo_restrictions defer
+helo foobar
diff --git a/src/smtpd/smtpd_check.in2 b/src/smtpd/smtpd_check.in2
new file mode 100644
index 0000000..804fde1
--- /dev/null
+++ b/src/smtpd/smtpd_check.in2
@@ -0,0 +1,116 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+#
+# Test the client restrictions.
+#
+client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access
+client unknown 131.155.210.17
+client unknown 168.100.3.13
+client random.bad.domain 123.123.123.123
+client friend.bad.domain 123.123.123.123
+client bad.domain 123.123.123.123
+client wzv.win.tue.nl 131.155.210.17
+client aa.win.tue.nl 131.155.210.18
+client_restrictions permit_mynetworks
+#
+# Test the helo restrictions
+#
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access
+client unknown 131.155.210.17
+helo foo.
+client foo 123.123.123.123
+helo foo.
+helo foo
+helo spike.porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access
+helo random.bad.domain
+helo friend.bad.domain
+#
+# Test the sender restrictions
+#
+sender_restrictions permit_mynetworks,reject_unknown_client
+client unknown 131.155.210.17
+mail foo@ibm.com
+client unknown 168.100.3.13
+mail foo@ibm.com
+client foo 123.123.123.123
+mail foo@ibm.com
+sender_restrictions reject_unknown_address
+mail foo@ibm.com
+mail foo@bad.domain
+sender_restrictions check_sender_access,hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail Reject@this.address
+mail foo@bad.domain
+mail foo@Bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# Test the recipient restrictions
+#
+recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+client unknown 131.155.210.17
+rcpt foo@ibm.com
+client unknown 168.100.3.13
+rcpt foo@ibm.com
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_relay_domains
+client foo.porcupine.org 168.100.3.13
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_recipient_access,hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail foo@bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# RBL
+#
+client_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.3.2
+client foo 127.0.0.2
+#
+# unknown sender/recipient domain
+#
+unknown_address_reject_code 554
+recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain
+mail wietse@porcupine.org
+rcpt wietse@porcupine.org
+rcpt wietse@no.recipient.domain
+mail wietse@no.sender.domain
+rcpt wietse@porcupine.org
+#
+# {permit_auth,reject_unauth}_destination
+#
+relay_domains foo.com,bar.com
+mail user@some.where
+recipient_restrictions permit_auth_destination,reject
+rcpt user@foo.org
+rcpt user@foo.com
+recipient_restrictions reject_unauth_destination,permit
+rcpt user@foo.org
+rcpt user@foo.com
+#
+# unknown client tests
+#
+unknown_client_reject_code 550
+client_restrictions reject_unknown_client
+client spike.porcupine.org 160.100.189.2 2
+client unknown 1.1.1.1 4
+client unknown 1.1.1.1 5
diff --git a/src/smtpd/smtpd_check.in3 b/src/smtpd/smtpd_check.in3
new file mode 100644
index 0000000..808f562
--- /dev/null
+++ b/src/smtpd/smtpd_check.in3
@@ -0,0 +1,27 @@
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+relay_domains porcupine.org
+local_recipient_maps unix:passwd.byname
+client unknown 131.155.210.17
+canonical_maps tcp:localhost:200
+#
+recipient_restrictions permit
+rcpt no.such.user@[127.0.0.1]
+#
+virtual_alias_maps tcp:localhost:100
+#
+recipient_restrictions permit_mx_backup
+rcpt wietse@nowhere1.com
+#
+recipient_restrictions check_relay_domains
+rcpt wietse@nowhere2.com
+#
+recipient_restrictions reject_unknown_recipient_domain
+rcpt wietse@nowhere3.com
+#
+recipient_restrictions permit_auth_destination
+rcpt wietse@nowhere4.com
+#
+recipient_restrictions reject_unauth_destination
+rcpt wietse@nowhere5.com
diff --git a/src/smtpd/smtpd_check.in4 b/src/smtpd/smtpd_check.in4
new file mode 100644
index 0000000..d401de9
--- /dev/null
+++ b/src/smtpd/smtpd_check.in4
@@ -0,0 +1,19 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+#
+# Test the new access map features
+#
+sender_restrictions hash:./smtpd_check_access
+mail rejecttext@bad.domain
+mail filter@filter.domain
+mail filtertext@filter.domain
+mail filtertexttext@filter.domain
+mail hold@hold.domain
+mail holdtext@hold.domain
+mail discard@hold.domain
+mail discardtext@hold.domain
+mail dunnotext@dunno.domain
diff --git a/src/smtpd/smtpd_check.ref b/src/smtpd/smtpd_check.ref
new file mode 100644
index 0000000..8051f01
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref
@@ -0,0 +1,398 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> client random.bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client friend.bad.domain 123.123.123.123
+OK
+>>> client bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client wzv.win.tue.nl 131.155.210.17
+OK
+>>> client aa.win.tue.nl 131.155.210.18
+./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP
+554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210
+>>> client_restrictions permit_mynetworks
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client foo 123.123.123.123
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.>
+450 4.7.1 <foo.>: Helo command rejected: Host not found
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo>
+450 4.7.1 <foo>: Helo command rejected: Host not found
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+OK
+>>> helo random.bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain>
+554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain
+>>> helo friend.bad.domain
+OK
+>>> helo_restrictions reject_invalid_hostname,reject_unknown_hostname
+OK
+>>> helo 123.123.123.123
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found; proto=SMTP helo=<123.123.123.123>
+450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found
+>>> helo [123.123.123.123]
+OK
+>>> helo [::]
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[::]>
+501 5.5.2 <[::]>: Helo command rejected: invalid ip address
+>>> helo [ipv6:::]
+OK
+>>> helo [ipv6::::]
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[ipv6::::]>
+501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address
+>>> helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname
+OK
+>>> helo 123.123.123.123
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions permit_mynetworks,reject_unknown_client
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> mail foo@ibm.com
+./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> mail foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> mail foo@ibm.com
+OK
+>>> sender_restrictions reject_unknown_address
+OK
+>>> mail foo@ibm.com
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail Reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@Bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo.porcupine.org 168.100.3.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> client_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> #
+>>> # Hybrids
+>>> #
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> client foo 131.155.210.17
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+OK
+>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> helo bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> helo 131.155.210.17
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<131.155.210.17>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<131.155.210.17>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> #
+>>> # MX backup
+>>> #
+>>> #mydestination spike.porcupine.org,localhost.porcupine.org
+>>> #inet_interfaces 168.100.3.2,127.0.0.1
+>>> #recipient_restrictions permit_mx_backup,reject
+>>> #rcpt wietse@wzv.win.tue.nl
+>>> #rcpt wietse@trouble.org
+>>> #rcpt wietse@porcupine.org
+>>> #
+>>> # Deferred restrictions
+>>> #
+>>> client_restrictions permit
+OK
+>>> helo_restrictions permit
+OK
+>>> sender_restrictions permit
+OK
+>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access
+OK
+>>> helo bad.domain
+OK
+>>> mail foo@good.domain
+OK
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@good.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> helo good.domain
+OK
+>>> mail foo@bad.domain
+OK
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<good.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> #
+>>> # FQDN restrictions
+>>> #
+>>> helo_restrictions reject_non_fqdn_hostname
+OK
+>>> sender_restrictions reject_non_fqdn_sender
+OK
+>>> recipient_restrictions reject_non_fqdn_recipient
+OK
+>>> helo foo.bar.
+OK
+>>> helo foo.bar
+OK
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname; from=<foo@bad.domain> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname
+>>> mail foo@foo.bar.
+OK
+>>> mail foo@foo.bar
+OK
+>>> mail foo@foo
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address; from=<foo@foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address
+>>> mail foo
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo>: Sender address rejected: need fully-qualified address; from=<foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Sender address rejected: need fully-qualified address
+>>> rcpt foo@foo.bar.
+OK
+>>> rcpt foo@foo.bar
+OK
+>>> rcpt foo@foo
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo@foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address
+>>> rcpt foo
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address
+>>> #
+>>> # Numerical HELO checks
+>>> #
+>>> helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname
+OK
+>>> helo [1.2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> helo [321.255.255.255]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[321.255.255.255]>
+501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address
+>>> helo [0.255.255.255]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[0.255.255.255]>
+501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.321]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.321]>
+501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3]>
+501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.4.5]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5]>
+501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address
+>>> helo [1..2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1..2.3.4]>
+501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address
+>>> helo [.1.2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[.1.2.3.4]>
+501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.4.5.]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5.]>
+501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> helo 321.255.255.255
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<321.255.255.255>
+501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address
+>>> helo 0.255.255.255
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<0.255.255.255>
+501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.321
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.321>
+501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address
+>>> helo 1.2.3
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3>
+501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4.5
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5>
+501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address
+>>> helo 1..2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1..2.3.4>
+501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address
+>>> helo .1.2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<.1.2.3.4>
+501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4.5.
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5.>
+501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address
+>>> #
+>>> # The defer restriction
+>>> #
+>>> defer_code 444
+OK
+>>> helo_restrictions defer
+OK
+>>> helo foobar
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 444 4.3.2 <foobar>: Helo command rejected: Try again later; from=<foo> proto=SMTP helo=<foobar>
+444 4.3.2 <foobar>: Helo command rejected: Try again later
diff --git a/src/smtpd/smtpd_check.ref2 b/src/smtpd/smtpd_check.ref2
new file mode 100644
index 0000000..e22f9e3
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref2
@@ -0,0 +1,236 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> client random.bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client friend.bad.domain 123.123.123.123
+OK
+>>> client bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client wzv.win.tue.nl 131.155.210.17
+OK
+>>> client aa.win.tue.nl 131.155.210.18
+./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP
+554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210
+>>> client_restrictions permit_mynetworks
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client foo 123.123.123.123
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.>
+450 4.7.1 <foo.>: Helo command rejected: Host not found
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo>
+450 4.7.1 <foo>: Helo command rejected: Host not found
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access
+OK
+>>> helo random.bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain>
+554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain
+>>> helo friend.bad.domain
+OK
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions permit_mynetworks,reject_unknown_client
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> mail foo@ibm.com
+./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> mail foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> mail foo@ibm.com
+OK
+>>> sender_restrictions reject_unknown_address
+OK
+>>> mail foo@ibm.com
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found
+>>> sender_restrictions check_sender_access,hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail Reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@Bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.3.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo.porcupine.org 168.100.3.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_recipient_access,hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> client_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> #
+>>> # unknown sender/recipient domain
+>>> #
+>>> unknown_address_reject_code 554
+OK
+>>> recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain
+OK
+>>> mail wietse@porcupine.org
+OK
+>>> rcpt wietse@porcupine.org
+OK
+>>> rcpt wietse@no.recipient.domain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found; from=<wietse@porcupine.org> to=<wietse@no.recipient.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found
+>>> mail wietse@no.sender.domain
+OK
+>>> rcpt wietse@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found; from=<wietse@no.sender.domain> to=<wietse@porcupine.org> proto=SMTP helo=<friend.bad.domain>
+554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found
+>>> #
+>>> # {permit_auth,reject_unauth}_destination
+>>> #
+>>> relay_domains foo.com,bar.com
+OK
+>>> mail user@some.where
+OK
+>>> recipient_restrictions permit_auth_destination,reject
+OK
+>>> rcpt user@foo.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied
+>>> rcpt user@foo.com
+OK
+>>> recipient_restrictions reject_unauth_destination,permit
+OK
+>>> rcpt user@foo.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Relay access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <user@foo.org>: Relay access denied
+>>> rcpt user@foo.com
+OK
+>>> #
+>>> # unknown client tests
+>>> #
+>>> unknown_client_reject_code 550
+OK
+>>> client_restrictions reject_unknown_client
+OK
+>>> client spike.porcupine.org 160.100.189.2 2
+OK
+>>> client unknown 1.1.1.1 4
+./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]
+>>> client unknown 1.1.1.1 5
+./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain>
+550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]
diff --git a/src/smtpd/smtpd_check.ref4 b/src/smtpd/smtpd_check.ref4
new file mode 100644
index 0000000..8e9a6df
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref4
@@ -0,0 +1,38 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test the new access map features
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail rejecttext@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text; from=<rejecttext@bad.domain> proto=SMTP
+554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text
+>>> mail filter@filter.domain
+./smtpd_check: warning: access table hash:./smtpd_check_access entry "filter@filter.domain" has FILTER entry without value
+OK
+>>> mail filtertext@filter.domain
+./smtpd_check: warning: access table hash:./smtpd_check_access entry "filtertext@filter.domain" requires transport:destination
+OK
+>>> mail filtertexttext@filter.domain
+./smtpd_check: <queue id>: filter: MAIL from localhost[127.0.0.1]: <filtertexttext@filter.domain>: Sender address triggers FILTER text:text; from=<filtertexttext@filter.domain> proto=SMTP
+OK
+>>> mail hold@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <hold@hold.domain>: Sender address triggers HOLD action; from=<hold@hold.domain> proto=SMTP
+OK
+>>> mail holdtext@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <holdtext@hold.domain>: Sender address text; from=<holdtext@hold.domain> proto=SMTP
+OK
+>>> mail discard@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discard@hold.domain>: Sender address triggers DISCARD action; from=<discard@hold.domain> proto=SMTP
+OK
+>>> mail discardtext@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discardtext@hold.domain>: Sender address text; from=<discardtext@hold.domain> proto=SMTP
+OK
+>>> mail dunnotext@dunno.domain
+OK
diff --git a/src/smtpd/smtpd_check_access b/src/smtpd/smtpd_check_access
new file mode 100644
index 0000000..788276a
--- /dev/null
+++ b/src/smtpd/smtpd_check_access
@@ -0,0 +1,91 @@
+bad.domain 554 match bad.domain
+friend.bad.domain OK
+bad-sender@ 554 match bad-sender@
+bad-sender@good.domain OK
+good-sender@ OK
+131.155.210 554 match 131.155.210
+131.155.210.17 OK
+131.155.210.19 REJECT
+reject@this.address 554 match reject@this.address
+open_user@some.site open
+strict_user@some.site strict
+auth_client 123456
+
+dunno.com dunno
+foo.dunno.com reject
+
+44.33.22 dunno
+44.33.22.11 REJECT
+44.33 REJECT
+
+reject@dunno.domain REJECT
+ok@dunno.domain OK
+dunno.domain DUNNO
+
+reject@reject.domain REJECT
+ok@reject.domain OK
+reject.domain REJECT
+
+reject@ok.domain REJECT
+ok@ok.domain OK
+ok.domain OK
+<> 550 Go away postmaster
+
+54.187.136.235 reject bizsat.net, gypsysoul.org spam
+
+blackholes.mail-abuse.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+rhsbl.porcupine.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+dnswl.porcupine.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+rejecttext@bad.domain reject text
+filter@filter.domain filter
+filtertext@filter.domain filter text
+filtertexttext@filter.domain filter text:text
+hold@hold.domain hold
+holdtext@hold.domain hold text
+discard@hold.domain discard
+discardtext@hold.domain discard text
+dunnotext@dunno.domain dunno text
+64.94.110.11 reject Verisign wild-card
+topica.com reject
+10.10.10.10 reject mail server 10.10.10.10
+spike.porcupine.org reject ns or mx server spike.porcupine.org
+241 reject class E subnet
+4.1.1_dsn reject 4.1.1 reject
+4.1.2_dsn reject 4.1.2 reject
+4.1.3_dsn reject 4.1.3 reject
+4.1.4_dsn reject 4.1.4 reject
+4.1.5_dsn reject 4.1.5 reject
+4.1.6_dsn reject 4.1.6 reject
+4.1.7_dsn reject 4.1.7 reject
+4.1.8_dsn reject 4.1.8 reject
+4.4.0_dsn reject 4.4.0 reject
+user@4.1.1_dsn reject 4.1.1 reject
+user@4.1.2_dsn reject 4.1.2 reject
+user@4.1.3_dsn reject 4.1.3 reject
+user@4.1.4_dsn reject 4.1.4 reject
+user@4.1.5_dsn reject 4.1.5 reject
+user@4.1.6_dsn reject 4.1.6 reject
+user@4.1.7_dsn reject 4.1.7 reject
+user@4.1.8_dsn reject 4.1.8 reject
+user@4.4.0_dsn reject 4.4.0 reject
diff --git a/src/smtpd/smtpd_check_backup.in b/src/smtpd/smtpd_check_backup.in
new file mode 100644
index 0000000..7fb9242
--- /dev/null
+++ b/src/smtpd/smtpd_check_backup.in
@@ -0,0 +1,20 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+#
+# MX backup
+#
+mydestination wzv.porcupine.org,localhost.porcupine.org
+inet_interfaces 168.100.3.7,127.0.0.1
+recipient_restrictions permit_mx_backup,reject
+rcpt wietse@wzv.porcupine.org
+rcpt wietse@backup.porcupine.org
+rcpt wietse@porcupine.org
+permit_mx_backup_networks 168.100.3.5
+rcpt wietse@backup.porcupine.org
+permit_mx_backup_networks 168.100.3.4
+rcpt wietse@backup.porcupine.org
diff --git a/src/smtpd/smtpd_check_backup.ref b/src/smtpd/smtpd_check_backup.ref
new file mode 100644
index 0000000..8f4a0f2
--- /dev/null
+++ b/src/smtpd/smtpd_check_backup.ref
@@ -0,0 +1,34 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> #
+>>> # MX backup
+>>> #
+>>> mydestination wzv.porcupine.org,localhost.porcupine.org
+OK
+>>> inet_interfaces 168.100.3.7,127.0.0.1
+OK
+>>> recipient_restrictions permit_mx_backup,reject
+OK
+>>> rcpt wietse@wzv.porcupine.org
+OK
+>>> rcpt wietse@backup.porcupine.org
+OK
+>>> rcpt wietse@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied; to=<wietse@porcupine.org> proto=SMTP
+554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied
+>>> permit_mx_backup_networks 168.100.3.5
+OK
+>>> rcpt wietse@backup.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied; to=<wietse@backup.porcupine.org> proto=SMTP
+554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied
+>>> permit_mx_backup_networks 168.100.3.4
+OK
+>>> rcpt wietse@backup.porcupine.org
+OK
diff --git a/src/smtpd/smtpd_check_dsn.in b/src/smtpd/smtpd_check_dsn.in
new file mode 100644
index 0000000..cf174e8
--- /dev/null
+++ b/src/smtpd/smtpd_check_dsn.in
@@ -0,0 +1,60 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+#
+# Test the client restrictions.
+#
+client_restrictions hash:./smtpd_check_access
+client 4.1.1_dsn 1.2.3.4
+client 4.1.2_dsn 1.2.3.4
+client 4.1.3_dsn 1.2.3.4
+client 4.1.4_dsn 1.2.3.4
+client 4.1.5_dsn 1.2.3.4
+client 4.1.6_dsn 1.2.3.4
+client 4.1.7_dsn 1.2.3.4
+client 4.1.8_dsn 1.2.3.4
+client 4.4.0_dsn 1.2.3.4
+client dummy dummy
+#
+# Test the helo restrictions
+#
+helo_restrictions hash:./smtpd_check_access
+helo 4.1.1_dsn
+helo 4.1.2_dsn
+helo 4.1.3_dsn
+helo 4.1.4_dsn
+helo 4.1.5_dsn
+helo 4.1.6_dsn
+helo 4.1.7_dsn
+helo 4.1.8_dsn
+helo 4.4.0_dsn
+#
+# Test the sender restrictions
+#
+sender_restrictions hash:./smtpd_check_access
+mail user@4.1.1_dsn
+mail user@4.1.2_dsn
+mail user@4.1.3_dsn
+mail user@4.1.4_dsn
+mail user@4.1.5_dsn
+mail user@4.1.6_dsn
+mail user@4.1.7_dsn
+mail user@4.1.8_dsn
+mail user@4.4.0_dsn
+#
+# Test the recipient restrictions
+#
+recipient_restrictions hash:./smtpd_check_access
+rcpt user@4.1.1_dsn
+rcpt user@4.1.2_dsn
+rcpt user@4.1.3_dsn
+rcpt user@4.1.4_dsn
+rcpt user@4.1.5_dsn
+rcpt user@4.1.6_dsn
+rcpt user@4.1.7_dsn
+rcpt user@4.1.8_dsn
+rcpt user@4.4.0_dsn
diff --git a/src/smtpd/smtpd_check_dsn.ref b/src/smtpd/smtpd_check_dsn.ref
new file mode 100644
index 0000000..168676d
--- /dev/null
+++ b/src/smtpd/smtpd_check_dsn.ref
@@ -0,0 +1,163 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions hash:./smtpd_check_access
+OK
+>>> client 4.1.1_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.1 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.1_dsn[1.2.3.4]: 554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.2_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.2 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.2_dsn[1.2.3.4]: 554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.3_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.3 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.3_dsn[1.2.3.4]: 554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.4_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.4 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.4_dsn[1.2.3.4]: 554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.5_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.5 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.5_dsn[1.2.3.4]: 554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.6_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.6 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.6_dsn[1.2.3.4]: 554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.7_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.7 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.7_dsn[1.2.3.4]: 554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.8_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.8 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.8_dsn[1.2.3.4]: 554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.4.0_dsn 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from 4.4.0_dsn[1.2.3.4]: 554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client dummy dummy
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions hash:./smtpd_check_access
+OK
+>>> helo 4.1.1_dsn
+./smtpd_check: mapping DSN status 4.1.1 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.1_dsn>
+554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject
+>>> helo 4.1.2_dsn
+./smtpd_check: mapping DSN status 4.1.2 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.2_dsn>
+554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject
+>>> helo 4.1.3_dsn
+./smtpd_check: mapping DSN status 4.1.3 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.3_dsn>
+554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject
+>>> helo 4.1.4_dsn
+./smtpd_check: mapping DSN status 4.1.4 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.4_dsn>
+554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject
+>>> helo 4.1.5_dsn
+./smtpd_check: mapping DSN status 4.1.5 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.5_dsn>
+554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject
+>>> helo 4.1.6_dsn
+./smtpd_check: mapping DSN status 4.1.6 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.6_dsn>
+554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject
+>>> helo 4.1.7_dsn
+./smtpd_check: mapping DSN status 4.1.7 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.7_dsn>
+554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject
+>>> helo 4.1.8_dsn
+./smtpd_check: mapping DSN status 4.1.8 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.8_dsn>
+554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject
+>>> helo 4.4.0_dsn
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail user@4.1.1_dsn
+./smtpd_check: mapping DSN status 4.1.1 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject; from=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject
+>>> mail user@4.1.2_dsn
+./smtpd_check: mapping DSN status 4.1.2 into Sender address status 4.1.8
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject; from=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject
+>>> mail user@4.1.3_dsn
+./smtpd_check: mapping DSN status 4.1.3 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject; from=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject
+>>> mail user@4.1.4_dsn
+./smtpd_check: mapping DSN status 4.1.4 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject; from=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject
+>>> mail user@4.1.5_dsn
+./smtpd_check: mapping DSN status 4.1.5 into Sender address status 4.1.0
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject; from=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject
+>>> mail user@4.1.6_dsn
+./smtpd_check: mapping DSN status 4.1.6 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject; from=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject
+>>> mail user@4.1.7_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject; from=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject
+>>> mail user@4.1.8_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject; from=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject
+>>> mail user@4.4.0_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject; from=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> rcpt user@4.1.1_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.2_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.3_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.4_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.5_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.6_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.7_dsn
+./smtpd_check: mapping DSN status 4.1.7 into Recipient address status 4.1.3
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.8_dsn
+./smtpd_check: mapping DSN status 4.1.8 into Recipient address status 4.1.2
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.4.0_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject
diff --git a/src/smtpd/smtpd_dns_filter.in b/src/smtpd/smtpd_dns_filter.in
new file mode 100644
index 0000000..df1d8ef
--- /dev/null
+++ b/src/smtpd/smtpd_dns_filter.in
@@ -0,0 +1,83 @@
+#
+# Initialize
+#
+client localhost 127.0.0.1
+smtpd_delay_reject 0
+#
+# Test reject_unknown_helo_hostname
+#
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+helo_restrictions reject_unknown_helo_hostname,permit
+# EXPECT OK + "all MX records dropped" warning.
+helo xn--1xa.porcupine.org
+# EXPECT OK (nullmx has A record)
+helo nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+helo nxdomain.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+# EXPECT OK (host has AAAA record).
+mail user@spike.porcupine.org
+helo spike.porcupine.org
+# EXPECT OK + "all A records dropped" warning + no delayed reject.
+helo fist.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/error.reg
+# EXPECT OK + "filter config error" warning + delayed reject.
+helo spike.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+# EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject.
+helo nullmx.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+helo nxdomain.porcupine.org
+#
+# Test reject_unknown_sender_domain (same code as
+# reject_unknown_recipient_domain).
+#
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+helo localhost
+sender_restrictions reject_unknown_sender_domain
+# EXPECT OK + "all MX records dropped" warning.
+mail user@xn--1xa.porcupine.org
+# EXPECT reject (nullmx is not filtered).
+mail user@nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+mail user@nxdomain.porcupine.org
+# EXPECT OK
+mail user@localhost
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+# EXPECT OK (host has AAAA record).
+mail user@spike.porcupine.org
+# EXPECT OK + "all A records dropped" warning.
+mail user@fist.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/error.reg
+# EXPECT OK + "filter config error" warning + delayed reject.
+mail user@xn--1xa.porcupine.org
+rcpt user
+# EXPECT reject (nullmx is not filtered).
+mail user@nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+mail user@nxdomain.porcupine.org
+#
+# Test reject_rbl_client
+#
+client_restrictions reject_rbl_client,dnsbltest.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+flush_dnsxl_cache
+# EXPECT reject + A and TXT record.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+flush_dnsxl_cache
+# EXPECT OK + "all A results dropped" warning.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/no-txt.reg
+flush_dnsxl_cache
+# EXPECT reject + A record, "all TXT results dropped" warning.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/error.reg
+flush_dnsxl_cache
+# EXPECT OK + "filter configuration error"
+client localhost 127.0.0.2
diff --git a/src/smtpd/smtpd_dns_filter.ref b/src/smtpd/smtpd_dns_filter.ref
new file mode 100644
index 0000000..ce1710f
--- /dev/null
+++ b/src/smtpd/smtpd_dns_filter.ref
@@ -0,0 +1,163 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> client localhost 127.0.0.1
+OK
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test reject_unknown_helo_hostname
+>>> #
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> helo_restrictions reject_unknown_helo_hostname,permit
+OK
+>>> # EXPECT OK + "all MX records dropped" warning.
+>>> helo xn--1xa.porcupine.org
+./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org.
+./smtpd_check: warning: xn--1xa.porcupine.org: address or MX lookup error: DNS reply filter drops all results
+OK
+>>> # EXPECT OK (nullmx has A record)
+>>> helo nullmx.porcupine.org
+OK
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> # EXPECT OK (host has AAAA record).
+>>> mail user@spike.porcupine.org
+OK
+>>> helo spike.porcupine.org
+./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.3.2
+OK
+>>> # EXPECT OK + "all A records dropped" warning + no delayed reject.
+>>> helo fist.porcupine.org
+./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.3.4
+./smtpd_check: warning: fist.porcupine.org: address or MX lookup error: DNS reply filter drops all results
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> # EXPECT OK + "filter config error" warning + delayed reject.
+>>> helo spike.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found
+>>> # EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject.
+>>> helo nullmx.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<nullmx.porcupine.org>
+450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> #
+>>> # Test reject_unknown_sender_domain (same code as
+>>> # reject_unknown_recipient_domain).
+>>> #
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> helo localhost
+OK
+>>> sender_restrictions reject_unknown_sender_domain
+OK
+>>> # EXPECT OK + "all MX records dropped" warning.
+>>> mail user@xn--1xa.porcupine.org
+./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org.
+./smtpd_check: warning: xn--1xa.porcupine.org: MX or address lookup error: DNS reply filter drops all results
+OK
+>>> # EXPECT reject (nullmx is not filtered).
+>>> mail user@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost>
+550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> mail user@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> # EXPECT OK
+>>> mail user@localhost
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> # EXPECT OK (host has AAAA record).
+>>> mail user@spike.porcupine.org
+./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.3.2
+OK
+>>> # EXPECT OK + "all A records dropped" warning.
+>>> mail user@fist.porcupine.org
+./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.3.4
+./smtpd_check: warning: fist.porcupine.org: MX or address lookup error: DNS reply filter drops all results
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> # EXPECT OK + "filter config error" warning + delayed reject.
+>>> mail user@xn--1xa.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> rcpt user
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found; from=<user@xn--1xa.porcupine.org> to=<user> proto=SMTP helo=<localhost>
+450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found
+>>> # EXPECT reject (nullmx is not filtered).
+>>> mail user@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost>
+550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> mail user@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> #
+>>> # Test reject_rbl_client
+>>> #
+>>> client_restrictions reject_rbl_client,dnsbltest.porcupine.org
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT reject + A and TXT record.
+>>> client localhost 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT OK + "all A results dropped" warning.
+>>> client localhost 127.0.0.2
+./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN A 127.0.0.2
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: DNS reply filter drops all results
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-txt.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT reject + A record, "all TXT results dropped" warning.
+>>> client localhost 127.0.0.2
+./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: TXT lookup error: DNS reply filter drops all results
+./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT OK + "filter configuration error"
+>>> client localhost 127.0.0.2
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: Invalid DNS reply filter syntax
+OK
diff --git a/src/smtpd/smtpd_dnswl.in b/src/smtpd/smtpd_dnswl.in
new file mode 100644
index 0000000..db25474
--- /dev/null
+++ b/src/smtpd/smtpd_dnswl.in
@@ -0,0 +1,60 @@
+#
+# Initialize.
+#
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+mydestination porcupine.org
+relay_domains porcupine.org
+helo foobar
+
+#
+# DNSWL (by IP address)
+#
+
+# Allowlist overrides reject.
+client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+client spike.porcupine.org 168.100.3.2
+
+# Allowlist does not fire - reject.
+client_restrictions permit_dnswl_client,porcupine.org,reject
+client spike.porcupine.org 168.100.3.2
+
+# Allowlist does not override reject_unauth_destination.
+client_restrictions permit
+recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+
+#
+# RHSWL (by domain name)
+#
+
+# Allowlist overrides reject.
+client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+# Non-allowlisted client name - reject.
+client spike.porcupine.org 168.100.3.2
+# Allowlisted client name - accept.
+client example.tld 168.100.3.2
+
+# Allowlist does not override reject_unauth_destination.
+client_restrictions permit
+recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+# Non-allowlisted client name.
+client spike.porcupine.org 168.100.3.2
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+# Allowlisted client name.
+client example.tld 168.100.3.2
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+# Numeric TLD - dunno.
+rcpt wietse@12345
+rcpt wietse@12345.porcupine.org
+rcpt wietse@porcupine.12345
diff --git a/src/smtpd/smtpd_dnswl.ref b/src/smtpd/smtpd_dnswl.ref
new file mode 100644
index 0000000..dacda6c
--- /dev/null
+++ b/src/smtpd/smtpd_dnswl.ref
@@ -0,0 +1,94 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> mydestination porcupine.org
+OK
+>>> relay_domains porcupine.org
+OK
+>>> helo foobar
+OK
+>>>
+>>> #
+>>> # DNSWL (by IP address)
+>>> #
+>>>
+>>> # Allowlist overrides reject.
+>>> client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>>
+>>> # Allowlist does not fire - reject.
+>>> client_restrictions permit_dnswl_client,porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied
+>>>
+>>> # Allowlist does not override reject_unauth_destination.
+>>> client_restrictions permit
+OK
+>>> recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>>
+>>> #
+>>> # RHSWL (by domain name)
+>>> #
+>>>
+>>> # Allowlist overrides reject.
+>>> client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+OK
+>>> # Non-allowlisted client name - reject.
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied
+>>> # Allowlisted client name - accept.
+>>> client example.tld 168.100.3.2
+OK
+>>>
+>>> # Allowlist does not override reject_unauth_destination.
+>>> client_restrictions permit
+OK
+>>> recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+OK
+>>> # Non-allowlisted client name.
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>> # Allowlisted client name.
+>>> client example.tld 168.100.3.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>> # Numeric TLD - dunno.
+>>> rcpt wietse@12345
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 <wietse@12345>: Relay access denied; to=<wietse@12345> proto=SMTP helo=<foobar>
+554 5.7.1 <wietse@12345>: Relay access denied
+>>> rcpt wietse@12345.porcupine.org
+OK
+>>> rcpt wietse@porcupine.12345
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 <wietse@porcupine.12345>: Relay access denied; to=<wietse@porcupine.12345> proto=SMTP helo=<foobar>
+554 5.7.1 <wietse@porcupine.12345>: Relay access denied
diff --git a/src/smtpd/smtpd_dsn_fix.c b/src/smtpd/smtpd_dsn_fix.c
new file mode 100644
index 0000000..d436967
--- /dev/null
+++ b/src/smtpd/smtpd_dsn_fix.c
@@ -0,0 +1,149 @@
+/*++
+/* NAME
+/* smtpd_dsn_fix 3
+/* SUMMARY
+/* fix DSN status
+/* SYNOPSIS
+/* #include <smtpd_dsn_fix.h>
+/*
+/* const char *smtpd_dsn_fix(status, reply_class)
+/* const char *status;
+/* const char *reply_class;
+/* DESCRIPTION
+/* smtpd_dsn_fix() transforms DSN status codes according to the
+/* status information that is actually being reported. The
+/* following transformations are implemented:
+/* .IP \(bu
+/* Transform a recipient address DSN into a sender address DSN
+/* when reporting sender address status information, and vice
+/* versa. This transformation may be needed because some Postfix
+/* access control features don't know whether the address being
+/* rejected is a sender or recipient. Examples are smtpd access
+/* tables, rbl reply templates, and the error mailer.
+/* .IP \(bu
+/* Transform a sender or recipient address DSN into a non-address
+/* DSN when reporting non-address status information. For
+/* example, if something rejects HELO with DSN status 4.1.1
+/* (unknown recipient address), then we send the more neutral
+/* 4.0.0 DSN instead. This transformation is needed when the
+/* same smtpd access map entry or rbl reply template is used
+/* for both address and non-address information.
+/* .PP
+/* A non-address DSN is not transformed
+/* when reporting sender or recipient address status information,
+/* as there are many legitimate instances of such usage.
+/*
+/* It is left up to the caller to update the initial DSN digit
+/* appropriately; in Postfix this is done as late as possible,
+/* because hard rejects may be changed into soft rejects for
+/* all kinds of reasons.
+/*
+/* Arguments:
+/* .IP status
+/* A DSN status as per RFC 3463.
+/* .IP reply_class
+/* SMTPD_NAME_SENDER, SMTPD_NAME_RECIPIENT or some other
+/* null-terminated string.
+/* 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 <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include <smtpd_dsn_fix.h>
+
+struct dsn_map {
+ const char *micro_code; /* Final digits in mailbox D.S.N. */
+ const char *sender_dsn; /* Replacement sender D.S.N. */
+ const char *rcpt_dsn; /* Replacement recipient D.S.N. */
+};
+
+static struct dsn_map dsn_map[] = {
+ /* - Sender - Recipient */
+ "1", SND_DSN, "4.1.1", /* 4.1.1: Bad dest mbox addr */
+ "2", "4.1.8", "4.1.2", /* 4.1.2: Bad dest system addr */
+ "3", "4.1.7", "4.1.3", /* 4.1.3: Bad dest mbox addr syntax */
+ "4", SND_DSN, "4.1.4", /* 4.1.4: Dest mbox addr ambiguous */
+ "5", "4.1.0", "4.1.5", /* 4.1.5: Dest mbox addr valid */
+ "6", SND_DSN, "4.1.6", /* 4.1.6: Mailbox has moved */
+ "7", "4.1.7", "4.1.3", /* 4.1.7: Bad sender mbox addr syntax */
+ "8", "4.1.8", "4.1.2", /* 4.1.8: Bad sender system addr */
+ 0, "4.1.0", "4.1.0", /* Default mapping */
+};
+
+/* smtpd_dsn_fix - fix DSN status */
+
+const char *smtpd_dsn_fix(const char *status, const char *reply_class)
+{
+ struct dsn_map *dp;
+ const char *result = status;
+
+ /*
+ * Update an address-specific DSN according to what is being rejected.
+ */
+ if (ISDIGIT(status[0]) && strncmp(status + 1, ".1.", 3) == 0) {
+
+ /*
+ * Fix recipient address DSN while rejecting a sender address. Don't
+ * let future recipient-specific DSN codes slip past us.
+ */
+ if (strcmp(reply_class, SMTPD_NAME_SENDER) == 0) {
+ for (dp = dsn_map; dp->micro_code != 0; dp++)
+ if (strcmp(status + 4, dp->micro_code) == 0)
+ break;
+ result = dp->sender_dsn;
+ }
+
+ /*
+ * Fix sender address DSN while rejecting a recipient address. Don't
+ * let future sender-specific DSN codes slip past us.
+ */
+ else if (strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) {
+ for (dp = dsn_map; dp->micro_code != 0; dp++)
+ if (strcmp(status + 4, dp->micro_code) == 0)
+ break;
+ result = dp->rcpt_dsn;
+ }
+
+ /*
+ * Fix address-specific DSN while rejecting a non-address.
+ */
+ else {
+ result = "4.0.0";
+ }
+
+ /*
+ * Give them a clue of what is going on.
+ */
+ if (strcmp(status + 2, result + 2) != 0)
+ msg_info("mapping DSN status %s into %s status %c%s",
+ status, reply_class, status[0], result + 1);
+ return (result);
+ }
+
+ /*
+ * Don't update a non-address DSN. There are many legitimate uses for
+ * these while rejecting address or non-address information.
+ */
+ else {
+ return (status);
+ }
+}
diff --git a/src/smtpd/smtpd_dsn_fix.h b/src/smtpd/smtpd_dsn_fix.h
new file mode 100644
index 0000000..c608e34
--- /dev/null
+++ b/src/smtpd/smtpd_dsn_fix.h
@@ -0,0 +1,44 @@
+/*++
+/* NAME
+/* smtpd_check 3h
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check_int.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Internal interface.
+ */
+#define SMTPD_NAME_CLIENT "Client host"
+#define SMTPD_NAME_REV_CLIENT "Unverified Client host"
+#define SMTPD_NAME_CCERT "Client certificate"
+#define SMTPD_NAME_SASL_USER "SASL login name"
+#define SMTPD_NAME_HELO "Helo command"
+#define SMTPD_NAME_SENDER "Sender address"
+#define SMTPD_NAME_RECIPIENT "Recipient address"
+#define SMTPD_NAME_ETRN "Etrn command"
+#define SMTPD_NAME_DATA "Data command"
+#define SMTPD_NAME_EOD "End-of-data"
+
+ /*
+ * Workaround for absence of "bad sender address" status code: use "bad
+ * sender address syntax" instead. If we were to use "4.1.0" then we would
+ * lose the critical distinction between sender and recipient problems.
+ */
+#define SND_DSN "4.1.7"
+
+extern const char *smtpd_dsn_fix(const char *, const char *);
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_error.in b/src/smtpd/smtpd_error.in
new file mode 100644
index 0000000..a7fb3e2
--- /dev/null
+++ b/src/smtpd/smtpd_error.in
@@ -0,0 +1,81 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+#
+# Test check_domain_access()
+#
+helo_restrictions fail:1_helo_access
+# Expect: REJECT (temporary lookup failure)
+helo foobar
+#
+# Test check_namadr_access()
+#
+client_restrictions fail:1_client_access
+# Expect: REJECT (temporary lookup failure)
+client foo.dunno.com 131.155.210.17
+#
+# Test check_mail_access()
+#
+sender_restrictions fail:1_sender_access
+# Expect: REJECT (temporary lookup failure)
+mail reject@dunno.domain
+#
+# Test check_rcpt_access()
+#
+recipient_restrictions fail:1_rcpt_access
+# Expect: REJECT (temporary lookup failure)
+rcpt reject@dunno.domain
+# Expect: OK
+rcpt postmaster
+#
+# Test mynetworks in generic_checks().
+#
+mynetworks fail:1_mynetworks
+#
+# Expect REJECT (temporary lookup failure)
+#
+recipient_restrictions permit_mynetworks
+rcpt reject@dunno.domain
+#
+# Test mynetworks.
+#
+mynetworks 168.100.3.1/27
+#
+# Expect REJECT (server configuration error)
+#
+rcpt reject@dunno.domain
+#
+# check_sender_access specific
+#
+smtpd_null_access_lookup_key <>
+mail <>
+#
+# Test permit_tls_client_certs in generic_restrictions
+#
+relay_clientcerts fail:1_certs
+fingerprint abcdef
+recipient_restrictions permit_tls_clientcerts
+rcpt reject@dunno.domain
+#
+# Test smtpd_check_rewrite().
+#
+local_header_rewrite_clients fail:1_rewrite
+#
+# Expect: REJECT (temporary lookup failure)
+#
+rewrite
+#
+# Test resolve_local()
+#
+mydestination example.com
+recipient_restrictions reject_unauth_destination
+rcpt user@example.com
+mydestination fail:1_mydestination
+rcpt user@example.com
+#
+# Test virtual alias lookup.
+#
+mydestination example.com
+virtual_alias_maps fail:1_virtual
+rcpt user@example.com
diff --git a/src/smtpd/smtpd_error.ref b/src/smtpd/smtpd_error.ref
new file mode 100644
index 0000000..d375522
--- /dev/null
+++ b/src/smtpd/smtpd_error.ref
@@ -0,0 +1,135 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test check_domain_access()
+>>> #
+>>> helo_restrictions fail:1_helo_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> helo foobar
+./smtpd_check: warning: fail:1_helo_access lookup error for "foobar"
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 451 4.3.5 <foobar>: Helo command rejected: Server configuration error; proto=SMTP helo=<foobar>
+451 4.3.5 <foobar>: Helo command rejected: Server configuration error
+>>> #
+>>> # Test check_namadr_access()
+>>> #
+>>> client_restrictions fail:1_client_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> client foo.dunno.com 131.155.210.17
+./smtpd_check: warning: fail:1_client_access lookup error for "foo.dunno.com"
+./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error; proto=SMTP helo=<foobar>
+451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> sender_restrictions fail:1_sender_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> mail reject@dunno.domain
+./smtpd_check: warning: fail:1_sender_access lookup error for "reject@dunno.domain"
+./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error; from=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error
+>>> #
+>>> # Test check_rcpt_access()
+>>> #
+>>> recipient_restrictions fail:1_rcpt_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: fail:1_rcpt_access lookup error for "reject@dunno.domain"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error
+>>> # Expect: OK
+>>> rcpt postmaster
+OK
+>>> #
+>>> # Test mynetworks in generic_checks().
+>>> #
+>>> mynetworks fail:1_mynetworks
+OK
+>>> #
+>>> # Expect REJECT (temporary lookup failure)
+>>> #
+>>> recipient_restrictions permit_mynetworks
+OK
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: mynetworks: fail:1_mynetworks: table lookup problem
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # Test mynetworks.
+>>> #
+>>> mynetworks 168.100.3.1/27
+OK
+>>> #
+>>> # Expect REJECT (server configuration error)
+>>> #
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.3.1/27", perhaps you should use "168.100.3.0/27" instead
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # check_sender_access specific
+>>> #
+>>> smtpd_null_access_lookup_key <>
+OK
+>>> mail <>
+./smtpd_check: warning: fail:1_sender_access lookup error for "<>"
+./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <>: Sender address rejected: Server configuration error; from=<> proto=SMTP helo=<foobar>
+451 4.3.5 <>: Sender address rejected: Server configuration error
+>>> #
+>>> # Test permit_tls_client_certs in generic_restrictions
+>>> #
+>>> relay_clientcerts fail:1_certs
+OK
+>>> fingerprint abcdef
+OK
+>>> recipient_restrictions permit_tls_clientcerts
+OK
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: fail:1_certs lookup error for "abcdef"
+./smtpd_check: warning: relay_clientcerts: lookup error for fingerprint 'abcdef', pkey fingerprint abcdef
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # Test smtpd_check_rewrite().
+>>> #
+>>> local_header_rewrite_clients fail:1_rewrite
+OK
+>>> #
+>>> # Expect: REJECT (temporary lookup failure)
+>>> #
+>>> rewrite
+./smtpd_check: warning: fail:1_rewrite lookup error for "131.155.210.17"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 Temporary lookup error; from=<> proto=SMTP helo=<foobar>
+451 4.3.0 Temporary lookup error
+>>> #
+>>> # Test resolve_local()
+>>> #
+>>> mydestination example.com
+OK
+>>> recipient_restrictions reject_unauth_destination
+OK
+>>> rcpt user@example.com
+OK
+>>> mydestination fail:1_mydestination
+OK
+>>> rcpt user@example.com
+./smtpd_check: warning: mydestination: fail:1_mydestination: table lookup problem
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar>
+451 4.3.0 <user@example.com>: Temporary lookup failure
+>>> #
+>>> # Test virtual alias lookup.
+>>> #
+>>> mydestination example.com
+OK
+>>> virtual_alias_maps fail:1_virtual
+OK
+>>> rcpt user@example.com
+./smtpd_check: warning: fail:1_virtual lookup error for "user@example.com"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar>
+451 4.3.0 <user@example.com>: Temporary lookup failure
diff --git a/src/smtpd/smtpd_exp.in b/src/smtpd/smtpd_exp.in
new file mode 100644
index 0000000..8370404
--- /dev/null
+++ b/src/smtpd/smtpd_exp.in
@@ -0,0 +1,62 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.3.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+rbl_reply_maps hash:smtpd_check_access
+helo foobar
+#
+# RBL
+#
+mail sname@sdomain
+recipient_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.3.2
+rcpt rname@rdomain
+client foo 127.0.0.2
+rcpt rname@rdomain
+#
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
+client spike.porcupine.org 168.100.3.2
+rcpt rname@rdomain
+client foo 127.0.0.2
+rcpt rname@rdomain
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
+client foo 127.0.0.2
+rcpt rname@rdomain
+client foo 127.0.0.1
+rcpt rname@rdomain
+#
+# RHSBL sender domain name
+#
+recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org
+client spike.porcupine.org 168.100.3.2
+mail sname@example.tld
+rcpt rname@rdomain
+mail sname@sdomain
+rcpt rname@rdomain
+#
+# RHSBL client domain name
+#
+recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org
+client example.tld 1.2.3.4
+mail sname@sdomain
+rcpt rname@rdomain
+#
+# RHSBL recipient domain name
+#
+recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org
+client spike.porcupine.org 168.100.3.2
+mail sname@sdomain
+rcpt rname@rdomain
+rcpt rname@example.tld
+#
+# RHSBL helo domain name
+#
+recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org
+helo example.tld
+mail sname@sdomain
+rcpt rname@rdomain
diff --git a/src/smtpd/smtpd_exp.ref b/src/smtpd/smtpd_exp.ref
new file mode 100644
index 0000000..22c027e
--- /dev/null
+++ b/src/smtpd/smtpd_exp.ref
@@ -0,0 +1,111 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.3.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> rbl_reply_maps hash:smtpd_check_access
+OK
+>>> helo foobar
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> mail sname@sdomain
+OK
+>>> recipient_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> #
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> rcpt rname@rdomain
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> client foo 127.0.0.1
+OK
+>>> rcpt rname@rdomain
+OK
+>>> #
+>>> # RHSBL sender domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> mail sname@example.tld
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address; from=<sname@example.tld> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> #
+>>> # RHSBL client domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org
+OK
+>>> client example.tld 1.2.3.4
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from example.tld[1.2.3.4]: 554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host
+>>> #
+>>> # RHSBL recipient domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> rcpt rname@example.tld
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address; from=<sname@sdomain> to=<rname@example.tld> proto=SMTP helo=<foobar>
+554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address
+>>> #
+>>> # RHSBL helo domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org
+OK
+>>> helo example.tld
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld>
+554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command
diff --git a/src/smtpd/smtpd_expand.c b/src/smtpd/smtpd_expand.c
new file mode 100644
index 0000000..8362bd7
--- /dev/null
+++ b/src/smtpd/smtpd_expand.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* smtpd_expand 3
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_expand.h>
+/*
+/* void smtpd_expand_init()
+/*
+/* int smtpd_expand(state, result, template, flags)
+/* SMTPD_STATE *state;
+/* VSTRING *result;
+/* const char *template;
+/* int flags;
+/* LOW_LEVEL INTERFACE
+/* VSTRING *smtpd_expand_filter;
+/*
+/* const char *smtpd_expand_lookup(name, unused_mode, context)
+/* const char *name;
+/* int unused_mode;
+/* void *context;
+/* const char *template;
+/* DESCRIPTION
+/* This module expands session-related macros.
+/*
+/* smtpd_expand_init() performs one-time initialization.
+/*
+/* smtpd_expand() expands macros in the template, using session
+/* attributes in the state argument, and writes the result to
+/* the result argument. The flags and result value are as with
+/* mac_expand().
+/*
+/* smtpd_expand_filter and smtpd_expand_lookup() provide access
+/* to lower-level interfaces that are used by smtpd_expand().
+/* smtpd_expand_lookup() returns null when a string is not
+/* found (or when it is a null pointer).
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors. smtpd_expand() returns the binary
+/* OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF
+/* (undefined macro name).
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mac_expand.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_expand.h>
+
+ /*
+ * Pre-parsed expansion filter.
+ */
+VSTRING *smtpd_expand_filter;
+
+ /*
+ * SLMs.
+ */
+#define STR vstring_str
+
+/* smtpd_expand_init - initialize once during process lifetime */
+
+void smtpd_expand_init(void)
+{
+
+ /*
+ * Expand the expansion filter :-)
+ */
+ smtpd_expand_filter = vstring_alloc(10);
+ unescape(smtpd_expand_filter, var_smtpd_exp_filter);
+}
+
+/* smtpd_expand_unknown - report unknown macro name */
+
+static void smtpd_expand_unknown(const char *name)
+{
+ msg_warn("unknown macro name \"%s\" in expansion request", name);
+}
+
+/* smtpd_expand_addr - return address or substring thereof */
+
+static const char *smtpd_expand_addr(VSTRING *buf, const char *addr,
+ const char *name, int prefix_len)
+{
+ const char *p;
+ const char *suffix;
+
+ /*
+ * Return NULL only for unknown names in expansion requests.
+ */
+ if (addr == 0)
+ return ("");
+
+ suffix = name + prefix_len;
+
+ /*
+ * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP.
+ */
+ if (*suffix == 0) {
+ if (*addr)
+ return (addr);
+ else
+ return ("<>");
+ }
+
+ /*
+ * "sender_name" or "recipient_name".
+ */
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+
+ else if (STREQ(suffix, MAIL_ATTR_S_NAME)) {
+ if (*addr) {
+ if ((p = strrchr(addr, '@')) != 0) {
+ vstring_strncpy(buf, addr, p - addr);
+ return (STR(buf));
+ } else {
+ return (addr);
+ }
+ } else
+ return ("<>");
+ }
+
+ /*
+ * "sender_domain" or "recipient_domain".
+ */
+ else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) {
+ if (*addr) {
+ if ((p = strrchr(addr, '@')) != 0) {
+ return (p + 1);
+ } else {
+ return ("");
+ }
+ } else
+ return ("");
+ }
+
+ /*
+ * Unknown. Return NULL to indicate an "unknown name" error.
+ */
+ else {
+ smtpd_expand_unknown(name);
+ return (0);
+ }
+}
+
+/* smtpd_expand_lookup - generic SMTP attribute $name expansion */
+
+const char *smtpd_expand_lookup(const char *name, int unused_mode,
+ void *context)
+{
+ SMTPD_STATE *state = (SMTPD_STATE *) context;
+ time_t now;
+ struct tm *lt;
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("smtpd_expand_lookup: ${%s}", name);
+
+#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
+#define CONST_LEN(x) (sizeof(x) - 1)
+
+ /*
+ * Don't query main.cf parameters, as the result of expansion could
+ * reveal system-internal information in server replies.
+ *
+ * XXX: This said, multiple servers may be behind a single client-visible
+ * name or IP address, and each may generate its own logs. Therefore, it
+ * may be useful to expose the replying MTA id (myhostname) in the
+ * contact footer, to identify the right logs. So while we don't expose
+ * the raw configuration dictionary, we do expose "$myhostname" as
+ * expanded in var_myhostname.
+ *
+ * Return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_SERVER_NAME)) {
+ return (var_myhostname);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) {
+ return (state->namaddr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) {
+ return (state->port);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) {
+ return (state->addr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) {
+ return (state->name);
+ } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) {
+ return (state->reverse_name);
+ } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) {
+ return (state->helo_name ? state->helo_name : "");
+ } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) {
+ return (smtpd_expand_addr(state->expand_buf, state->sender,
+ name, CONST_LEN(MAIL_ATTR_SENDER)));
+ } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) {
+ return (smtpd_expand_addr(state->expand_buf, state->recipient,
+ name, CONST_LEN(MAIL_ATTR_RECIP)));
+ } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
+ if (time(&now) == (time_t) -1)
+ msg_fatal("time lookup failed: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(state->expand_buf);
+ do {
+ VSTRING_SPACE(state->expand_buf, 100);
+ } while (strftime(STR(state->expand_buf),
+ vstring_avail(state->expand_buf),
+ "%b %d %H:%M:%S", lt) == 0);
+ return (STR(state->expand_buf));
+ } else {
+ smtpd_expand_unknown(name);
+ return (0);
+ }
+}
+
+/* smtpd_expand - expand session attributes in string */
+
+int smtpd_expand(SMTPD_STATE *state, VSTRING *result,
+ const char *template, int flags)
+{
+ return (mac_expand(result, template, flags, STR(smtpd_expand_filter),
+ smtpd_expand_lookup, (void *) state));
+}
diff --git a/src/smtpd/smtpd_expand.h b/src/smtpd/smtpd_expand.h
new file mode 100644
index 0000000..eb95983
--- /dev/null
+++ b/src/smtpd/smtpd_expand.h
@@ -0,0 +1,40 @@
+/*++
+/* NAME
+/* smtpd_expand 3h
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_expand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <mac_expand.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *smtpd_expand_filter;
+void smtpd_expand_init(void);
+const char *smtpd_expand_lookup(const char *, int, void *);
+int smtpd_expand(SMTPD_STATE *, VSTRING *, const char *, int);
+
+/* 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/smtpd/smtpd_haproxy.c b/src/smtpd/smtpd_haproxy.c
new file mode 100644
index 0000000..542c3fe
--- /dev/null
+++ b/src/smtpd/smtpd_haproxy.c
@@ -0,0 +1,135 @@
+/*++
+/* NAME
+/* smtpd_haproxy 3
+/* SUMMARY
+/* Postfix SMTP server haproxy adapter
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* int smtpd_peer_from_haproxy(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_peer_from_haproxy() receives endpoint address and
+/* port information via the haproxy protocol.
+/*
+/* The following summarizes what the Postfix SMTP server expects
+/* from an up-stream proxy adapter.
+/* .IP \(bu
+/* Call smtpd_peer_from_default() if the up-stream proxy
+/* indicates that the connection is not proxied. In that case,
+/* a proxy adapter MUST NOT update any STATE fields: the
+/* smtpd_peer_from_default() function will do that instead.
+/* .IP \(bu
+/* Validate protocol, address and port syntax. Permit only
+/* protocols that are configured with the main.cf:inet_protocols
+/* setting.
+/* .IP \(bu
+/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when
+/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols.
+/* .IP \(bu
+/* Update the following session context fields: addr, port,
+/* rfc_addr, addr_family, dest_addr, dest_port. The addr_family
+/* field applies to the client address.
+/* .IP \(bu
+/* Dynamically allocate storage for string information with
+/* mystrdup(). In case of error, leave unassigned string fields
+/* at their initial zero value.
+/* .IP \(bu
+/* Log a clear warning message that explains why a request
+/* fails.
+/* .IP \(bu
+/* Never talk to the remote SMTP client.
+/* .PP
+/* Arguments:
+/* .IP state
+/* Session context.
+/* DIAGNOSTICS
+/* Warnings: I/O errors, malformed haproxy line.
+/*
+/* The result value is 0 in case of success, -1 in case of
+/* error.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_params.h>
+#include <valid_mailhost_addr.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* smtpd_peer_from_haproxy - initialize peer information from haproxy */
+
+int smtpd_peer_from_haproxy(SMTPD_STATE *state)
+{
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int non_proxy = 0;
+
+ if (read_wait(vstream_fileno(state->client), var_smtpd_uproxy_tmout) < 0) {
+ msg_warn("haproxy read: timeout error");
+ return (-1);
+ }
+ if (haproxy_srvr_receive(vstream_fileno(state->client), &non_proxy,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port) < 0) {
+ return (-1);
+ }
+ if (non_proxy) {
+ smtpd_peer_from_default(state);
+ return (0);
+ }
+ state->addr = mystrdup(smtp_client_addr.buf);
+ if (strrchr(state->addr, ':') != 0) {
+ state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0);
+ state->addr_family = AF_INET6;
+ } else {
+ state->rfc_addr = mystrdup(state->addr);
+ state->addr_family = AF_INET;
+ }
+ state->port = mystrdup(smtp_client_port.buf);
+
+ /*
+ * The Dovecot authentication server needs the server IP address.
+ */
+ state->dest_addr = mystrdup(smtp_server_addr.buf);
+ state->dest_port = mystrdup(smtp_server_port.buf);
+ return (0);
+}
diff --git a/src/smtpd/smtpd_milter.c b/src/smtpd/smtpd_milter.c
new file mode 100644
index 0000000..5deba67
--- /dev/null
+++ b/src/smtpd/smtpd_milter.c
@@ -0,0 +1,229 @@
+/*++
+/* NAME
+/* smtpd_milter 3
+/* SUMMARY
+/* SMTP server milter glue
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_milter.h>
+/*
+/* const char *smtpd_milter_eval(name, context)
+/* const char *name;
+/* void *context;
+/* DESCRIPTION
+/* smtpd_milter_eval() is a milter(3) call-back routine to
+/* expand Sendmail macros before they are sent to filters.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors.
+/* 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 <split_at.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <quote_821_local.h>
+
+/* Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_sasl_glue.h>
+#include <smtpd_resolve.h>
+#include <smtpd_milter.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* smtpd_milter_eval - evaluate milter macro */
+
+const char *smtpd_milter_eval(const char *name, void *ptr)
+{
+ SMTPD_STATE *state = (SMTPD_STATE *) ptr;
+ const RESOLVE_REPLY *reply;
+ char *cp;
+
+ /*
+ * On-the-fly initialization.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ /*
+ * 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.
+ */
+ if (strcmp(name, S8_MAC__) == 0) {
+ vstring_sprintf(state->expand_buf, "%s [%s]",
+ state->reverse_name, state->addr);
+ if (strcasecmp_utf8(state->name, state->reverse_name) != 0)
+ vstring_strcat(state->expand_buf, " (may be forged)");
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_J) == 0)
+ return (var_myhostname);
+ if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0)
+ return (state->rfc_addr);
+ if (strcmp(name, S8_MAC_CLIENT_PORT) == 0)
+ return (strcmp(state->port, CLIENT_PORT_UNKNOWN) ? state->port : "0");
+ if (strcmp(name, S8_MAC_CLIENT_CONN) == 0) {
+ vstring_sprintf(state->expand_buf, "%d", state->conn_count);
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_CLIENT_NAME) == 0)
+ return (state->name);
+ if (strcmp(name, S8_MAC_CLIENT_PTR) == 0)
+ return (state->reverse_name);
+ if (strcmp(name, S8_MAC_CLIENT_RES) == 0)
+ return (state->name_status == SMTPD_PEER_CODE_OK ? "OK" :
+ state->name_status == SMTPD_PEER_CODE_FORGED ? "FORGED" :
+ state->name_status == SMTPD_PEER_CODE_TEMP ? "TEMP" : "FAIL");
+
+ if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0)
+ return (state->dest_addr);
+ if (strcmp(name, S8_MAC_DAEMON_PORT) == 0)
+ return (state->dest_port);
+
+ /*
+ * HELO macros.
+ */
+#ifdef USE_TLS
+#define IF_ENCRYPTED(x) (state->tls_context ? (x) : 0)
+#define IF_TRUSTED(x) (TLS_CERT_IS_TRUSTED(state->tls_context) ? (x) : 0)
+
+ if (strcmp(name, S8_MAC_TLS_VERSION) == 0)
+ return (IF_ENCRYPTED(state->tls_context->protocol));
+ if (strcmp(name, S8_MAC_CIPHER) == 0)
+ return (IF_ENCRYPTED(state->tls_context->cipher_name));
+ if (strcmp(name, S8_MAC_CIPHER_BITS) == 0) {
+ if (state->tls_context == 0)
+ return (0);
+ vstring_sprintf(state->expand_buf, "%d",
+ IF_ENCRYPTED(state->tls_context->cipher_usebits));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_CERT_SUBJECT) == 0)
+ return (IF_TRUSTED(state->tls_context->peer_CN));
+ if (strcmp(name, S8_MAC_CERT_ISSUER) == 0)
+ return (IF_TRUSTED(state->tls_context->issuer_CN));
+#endif
+
+ /*
+ * MAIL FROM macros.
+ */
+#define IF_SASL_ENABLED(s) ((s) ? (s) : 0)
+
+ if (strcmp(name, S8_MAC_I) == 0)
+ return (state->queue_id);
+#ifdef USE_SASL_AUTH
+ if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
+ return (IF_SASL_ENABLED(state->sasl_method));
+ if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
+ return (IF_SASL_ENABLED(state->sasl_username));
+ if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
+ return (IF_SASL_ENABLED(state->sasl_sender));
+#endif
+ if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) {
+ if (state->sender == 0)
+ return (0);
+ if (state->sender[0] == 0)
+ return ("");
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ /* Sendmail 8.13 does not externalize the null string. */
+ if (STR(reply->recipient)[0])
+ quote_821_local(state->expand_buf, STR(reply->recipient));
+ else
+ vstring_strcpy(state->expand_buf, STR(reply->recipient));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_MAIL_HOST) == 0) {
+ if (state->sender == 0)
+ return (0);
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ return (STR(reply->nexthop));
+ }
+ if (strcmp(name, S8_MAC_MAIL_MAILER) == 0) {
+ if (state->sender == 0)
+ return (0);
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ return (STR(reply->transport));
+ }
+
+ /*
+ * RCPT TO macros.
+ */
+ if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->recipient[0] == 0)
+ return ("");
+ if (state->milter_reject_text) {
+ /* 554 5.7.1 <user@example.com>: Relay access denied */
+ vstring_strcpy(state->expand_buf, state->milter_reject_text + 4);
+ cp = split_at(STR(state->expand_buf), ' ');
+ return (cp ? split_at(cp, ' ') : cp);
+ }
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ /* Sendmail 8.13 does not externalize the null string. */
+ if (STR(reply->recipient)[0])
+ quote_821_local(state->expand_buf, STR(reply->recipient));
+ else
+ vstring_strcpy(state->expand_buf, STR(reply->recipient));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_RCPT_HOST) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->milter_reject_text) {
+ /* 554 5.7.1 <user@example.com>: Relay access denied */
+ vstring_strcpy(state->expand_buf, state->milter_reject_text + 4);
+ (void) split_at(STR(state->expand_buf), ' ');
+ return (STR(state->expand_buf));
+ }
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ return (STR(reply->nexthop));
+ }
+ if (strcmp(name, S8_MAC_RCPT_MAILER) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->milter_reject_text)
+ return (S8_RCPT_MAILER_ERROR);
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ return (STR(reply->transport));
+ }
+ return (0);
+}
diff --git a/src/smtpd/smtpd_milter.h b/src/smtpd/smtpd_milter.h
new file mode 100644
index 0000000..4006bde
--- /dev/null
+++ b/src/smtpd/smtpd_milter.h
@@ -0,0 +1,27 @@
+/*++
+/* NAME
+/* smtpd_milter 3h
+/* SUMMARY
+/* SMTP server milter glue
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_milter.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *smtpd_milter_eval(const char *, void *);
+
+/* 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
+/*--*/
+
diff --git a/src/smtpd/smtpd_nullmx.in b/src/smtpd/smtpd_nullmx.in
new file mode 100644
index 0000000..58eede3
--- /dev/null
+++ b/src/smtpd/smtpd_nullmx.in
@@ -0,0 +1,58 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+#smtpd_delay_reject 0
+#mynetworks 127.0.0.0/8,168.100.3.0/28
+#relay_domains porcupine.org
+#maps_rbl_domains dnsbltest.porcupine.org
+#rbl_reply_maps hash:smtpd_check_access
+#helo foobar
+#
+# reject_unknown_helo_hostname
+#
+smtpd_delay_reject 0
+helo_restrictions reject_unknown_helo_hostname
+client spike.porcupine.org 168.100.3.2
+mail sname@sdomain
+rcpt rname@rdomain
+helo nxdomain.porcupine.org
+helo nullmx.porcupine.org
+helo spike.porcupine.org
+#
+# reject_unknown_sender_domain
+#
+smtpd_delay_reject 0
+sender_restrictions reject_unknown_sender_domain
+client spike.porcupine.org 168.100.3.2
+helo spike.porcupine.org
+rcpt rname@rdomain
+mail sname@nxdomain.porcupine.org
+mail sname@nullmx.porcupine.org
+mail sname@spike.porcupine.org
+#
+# reject_unknown_recipient_domain
+#
+smtpd_delay_reject 0
+sender_restrictions permit
+recipient_restrictions reject_unknown_recipient_domain
+relay_restrictions reject_unauth_destination
+client spike.porcupine.org 168.100.3.2
+helo spike.porcupine.org
+mail sname@sdomain
+relay_domains nxdomain.porcupine.org
+rcpt rname@nxdomain.porcupine.org
+relay_domains nullmx.porcupine.org
+rcpt rname@nullmx.porcupine.org
+relay_domains spike.porcupine.org
+rcpt rname@spike.porcupine.org
+#
+# check_mx_access
+#
+smtpd_delay_reject 0
+sender_restrictions check_sender_mx_access,hash:smtpd_check_access
+client spike.porcupine.org 168.100.3.2
+mail sname@nxdomain.porcupine.org
+mail sname@nullmx.porcupine.org
+mail sname@spike.porcupine.org
diff --git a/src/smtpd/smtpd_nullmx.ref b/src/smtpd/smtpd_nullmx.ref
new file mode 100644
index 0000000..410c0c0
--- /dev/null
+++ b/src/smtpd/smtpd_nullmx.ref
@@ -0,0 +1,100 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> #smtpd_delay_reject 0
+>>> #mynetworks 127.0.0.0/8,168.100.3.0/28
+>>> #relay_domains porcupine.org
+>>> #maps_rbl_domains dnsbltest.porcupine.org
+>>> #rbl_reply_maps hash:smtpd_check_access
+>>> #helo foobar
+>>> #
+>>> # reject_unknown_helo_hostname
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> helo_restrictions reject_unknown_helo_hostname
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.2]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<sname@sdomain> proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> helo nullmx.porcupine.org
+OK
+>>> helo spike.porcupine.org
+OK
+>>> #
+>>> # reject_unknown_sender_domain
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions reject_unknown_sender_domain
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> helo spike.porcupine.org
+OK
+>>> rcpt rname@rdomain
+OK
+>>> mail sname@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<sname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> mail sname@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> mail sname@spike.porcupine.org
+OK
+>>> #
+>>> # reject_unknown_recipient_domain
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions permit
+OK
+>>> recipient_restrictions reject_unknown_recipient_domain
+OK
+>>> relay_restrictions reject_unauth_destination
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> helo spike.porcupine.org
+OK
+>>> mail sname@sdomain
+OK
+>>> relay_domains nxdomain.porcupine.org
+OK
+>>> rcpt rname@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found; from=<sname@sdomain> to=<rname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found
+>>> relay_domains nullmx.porcupine.org
+OK
+>>> rcpt rname@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@sdomain> to=<rname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> relay_domains spike.porcupine.org
+OK
+>>> rcpt rname@spike.porcupine.org
+OK
+>>> #
+>>> # check_mx_access
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions check_sender_mx_access,hash:smtpd_check_access
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> mail sname@nxdomain.porcupine.org
+./smtpd_check: warning: Unable to look up MX host nxdomain.porcupine.org for Sender address sname@nxdomain.porcupine.org: hostname nor servname provided, or not known
+OK
+>>> mail sname@nullmx.porcupine.org
+OK
+>>> mail sname@spike.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org; from=<sname@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org
diff --git a/src/smtpd/smtpd_peer.c b/src/smtpd/smtpd_peer.c
new file mode 100644
index 0000000..3a5c1d4
--- /dev/null
+++ b/src/smtpd/smtpd_peer.c
@@ -0,0 +1,658 @@
+/*++
+/* NAME
+/* smtpd_peer 3
+/* SUMMARY
+/* look up peer name/address information
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_peer_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_peer_reset(state)
+/* SMTPD_STATE *state;
+/* AUXILIARY METHODS
+/* void smtpd_peer_from_default(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* The smtpd_peer_init() routine attempts to produce a printable
+/* version of the peer name and address of the specified socket.
+/* Where information is unavailable, the name and/or address
+/* are set to "unknown".
+/*
+/* Alternatively, the peer address and port may be obtained
+/* from a proxy server.
+/*
+/* This module uses the local name service via getaddrinfo()
+/* and getnameinfo(). It does not query the DNS directly.
+/*
+/* smtpd_peer_init() updates the following fields:
+/* .IP name
+/* The verified client hostname. This name is represented by
+/* the string "unknown" when 1) the address->name lookup failed,
+/* 2) the name->address mapping fails, or 3) the name->address
+/* mapping does not produce the client IP address.
+/* .IP reverse_name
+/* The unverified client hostname as found with address->name
+/* lookup; it is not verified for consistency with the client
+/* IP address result from name->address lookup.
+/* .IP forward_name
+/* The unverified client hostname as found with address->name
+/* lookup followed by name->address lookup; it is not verified
+/* for consistency with the result from address->name lookup.
+/* For example, when the address->name lookup produces as
+/* hostname an alias, the name->address lookup will produce
+/* as hostname the expansion of that alias, so that the two
+/* lookups produce different names.
+/* .IP addr
+/* Printable representation of the client address.
+/* .IP namaddr
+/* String of the form: "name[addr]:port".
+/* .IP rfc_addr
+/* String of the form "ipv4addr" or "ipv6:ipv6addr" for use
+/* in Received: message headers.
+/* .IP dest_addr
+/* Server address, used by the Dovecot authentication server,
+/* available as Milter {daemon_addr} macro, and as server_address
+/* policy delegation attribute.
+/* .IP dest_port
+/* Server port, available as Milter {daemon_port} macro, and
+/* as server_port policy delegation attribute.
+/* .IP name_status
+/* The name_status result field specifies how the name
+/* information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name lookup and name->address lookup produced
+/* the client IP address.
+/* .IP 4
+/* The address->name lookup or name->address lookup failed
+/* with a recoverable error.
+/* .IP 5
+/* The address->name lookup or name->address lookup failed
+/* with an unrecoverable error, or the result did not match
+/* the client IP address.
+/* .RE
+/* .IP reverse_name_status
+/* The reverse_name_status result field specifies how the
+/* reverse_name information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name lookup succeeded.
+/* .IP 4
+/* The address->name lookup failed with a recoverable error.
+/* .IP 5
+/* The address->name lookup failed with an unrecoverable error.
+/* .RE
+/* .IP forward_name_status
+/* The forward_name_status result field specifies how the
+/* forward_name information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name and name->address lookup succeeded.
+/* .IP 4
+/* The address->name lookup or name->address failed with a
+/* recoverable error.
+/* .IP 5
+/* The address->name lookup or name->address failed with an
+/* unrecoverable error.
+/* .RE
+/* .PP
+/* smtpd_peer_reset() releases memory allocated by smtpd_peer_init().
+/*
+/* smtpd_peer_from_default() looks up connection information
+/* when an up-stream proxy indicates that a connection is not
+/* proxied.
+/* 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>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <htable.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <valid_mailhost_addr.h>
+#include <mail_params.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+
+static const INET_PROTO_INFO *proto_info;
+
+ /*
+ * XXX If we make local port information available via logging, then we must
+ * also support these attributes with the XFORWARD command.
+ *
+ * XXX If support were to be added for Milter applications in down-stream MTAs,
+ * then consistency demands that we propagate a lot of Sendmail macro
+ * information via the XFORWARD command. Otherwise we could end up with a
+ * very confusing situation.
+ */
+
+/* smtpd_peer_sockaddr_to_hostaddr - client address/port to printable form */
+
+static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_peer_sockaddr_to_hostaddr";
+ struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
+ SOCKADDR_SIZE sa_length = state->sockaddr_len;
+
+ /*
+ * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd,
+ * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the
+ * final else clause, pretend the origin is localhost[127.0.0.1], and
+ * become an open relay).
+ */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ ) {
+ MAI_HOSTADDR_STR client_addr;
+ MAI_SERVPORT_STR client_port;
+ MAI_HOSTADDR_STR server_addr;
+ MAI_SERVPORT_STR server_port;
+ int aierr;
+ char *colonp;
+
+ /*
+ * Sanity check: we can't use sockets that we're not configured for.
+ */
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
+ msg_fatal("cannot handle socket type %s with \"%s = %s\"",
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+ "other", VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Sorry, but there are some things that we just cannot do while
+ * connected to the network.
+ */
+ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
+ msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu",
+ (unsigned long) getuid(), (unsigned long) geteuid());
+ msg_fatal("the Postfix SMTP server must run with $%s privileges",
+ VAR_MAIL_OWNER);
+ }
+
+ /*
+ * Convert the client address to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
+ &client_port, 0)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ state->port = mystrdup(client_port.buf);
+
+ /*
+ * XXX Require that the infrastructure strips off the IPv6 datalink
+ * suffix to avoid false alarms with strict address syntax checks.
+ */
+#ifdef HAS_IPV6
+ if (strchr(client_addr.buf, '%') != 0)
+ msg_panic("%s: address %s has datalink suffix",
+ myname, client_addr.buf);
+#endif
+
+ /*
+ * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
+ * but only if IPv4 support is enabled (why would anyone want to turn
+ * it off)? With IPv4 support enabled we have no need for the IPv6
+ * form in logging, hostname verification and access checks.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
+ && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
+ && (colonp = strrchr(client_addr.buf, ':')) != 0) {
+ struct addrinfo *res0;
+
+ if (msg_verbose > 1)
+ msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
+ myname, client_addr.buf, colonp + 1);
+
+ state->addr = mystrdup(colonp + 1);
+ state->rfc_addr = mystrdup(colonp + 1);
+ state->addr_family = AF_INET;
+ aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
+ if (aierr)
+ msg_fatal("%s: cannot convert %s from string to binary: %s",
+ myname, state->addr, MAI_STRERROR(aierr));
+ sa_length = res0->ai_addrlen;
+ if (sa_length > sizeof(state->sockaddr))
+ sa_length = sizeof(state->sockaddr);
+ memcpy((void *) sa, res0->ai_addr, sa_length);
+ freeaddrinfo(res0); /* 200412 */
+ }
+
+ /*
+ * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
+ * a prefix of 'IPv6:'. We do this consistently for all IPv6
+ * addresses that appear in headers or envelopes. The fact
+ * that valid_mailhost_addr() enforces the form helps of course.
+ * We use the form without IPV6: prefix when doing access
+ * control, or when accessing the connection cache.
+ */
+ else {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr =
+ concatenate(IPV6_COL, client_addr.buf, (char *) 0);
+ state->addr_family = sa->sa_family;
+ }
+ }
+
+ /*
+ * An IPv4 address is in dotted quad decimal form.
+ */
+ else
+#endif
+ {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr = mystrdup(client_addr.buf);
+ state->addr_family = sa->sa_family;
+ }
+
+ /*
+ * Convert the server address/port to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr((struct sockaddr *)
+ &state->dest_sockaddr,
+ state->dest_sockaddr_len,
+ &server_addr,
+ &server_port, 0)) != 0)
+ msg_fatal("%s: cannot convert server address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ /* TODO: convert IPv4-in-IPv6 to IPv4 form. */
+ state->dest_addr = mystrdup(server_addr.buf);
+ state->dest_port = mystrdup(server_port.buf);
+
+ return (0);
+ }
+
+ /*
+ * It's not Internet.
+ */
+ else {
+ return (-1);
+ }
+}
+
+/* smtpd_peer_sockaddr_to_hostname - client hostname lookup */
+
+static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state)
+{
+ struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
+ SOCKADDR_SIZE sa_length = state->sockaddr_len;
+ MAI_HOSTNAME_STR client_name;
+ int aierr;
+
+ /*
+ * Look up and sanity check the client hostname.
+ *
+ * It is unsafe to allow numeric hostnames, especially because there exists
+ * pressure to turn off the name->addr double check. In that case an
+ * attacker could trivally bypass access restrictions.
+ *
+ * sockaddr_to_hostname() already rejects malformed or numeric names.
+ */
+#define TEMP_AI_ERROR(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+
+#define REJECT_PEER_NAME(state, code) { \
+ myfree(state->name); \
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
+ state->name_status = code; \
+ }
+
+ if (var_smtpd_peername_lookup == 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->name_status = SMTPD_PEER_CODE_PERM;
+ state->reverse_name_status = SMTPD_PEER_CODE_PERM;
+ } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->name_status = (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
+ state->reverse_name_status = (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
+ } else {
+ struct addrinfo *res0;
+ struct addrinfo *res;
+
+ state->name = mystrdup(client_name.buf);
+ state->reverse_name = mystrdup(client_name.buf);
+ state->name_status = SMTPD_PEER_CODE_OK;
+ state->reverse_name_status = SMTPD_PEER_CODE_OK;
+
+ /*
+ * Reject the hostname if it does not list the peer address. Without
+ * further validation or qualification, such information must not be
+ * allowed to enter the audit trail, as people would draw false
+ * conclusions.
+ */
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
+ if (aierr) {
+ msg_warn("hostname %s does not resolve to address %s: %s",
+ state->name, state->addr, MAI_STRERROR(aierr));
+ REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED));
+ } else {
+ for (res = res0; /* void */ ; res = res->ai_next) {
+ if (res == 0) {
+ msg_warn("hostname %s does not resolve to address %s",
+ state->name, state->addr);
+ REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED);
+ break;
+ }
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, state->name);
+ continue;
+ }
+ if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
+ break; /* keep peer name */
+ }
+ freeaddrinfo(res0);
+ }
+ }
+}
+
+/* smtpd_peer_hostaddr_to_sockaddr - convert numeric string to binary */
+
+static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_peer_hostaddr_to_sockaddr";
+ struct addrinfo *res;
+ int aierr;
+
+ if ((aierr = hostaddr_to_sockaddr(state->addr, state->port,
+ SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ if (res->ai_addrlen > sizeof(state->sockaddr))
+ msg_panic("%s: address length > struct sockaddr_storage", myname);
+ memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen);
+ state->sockaddr_len = res->ai_addrlen;
+ freeaddrinfo(res);
+}
+
+/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */
+
+static void smtpd_peer_not_inet(SMTPD_STATE *state)
+{
+
+ /*
+ * If it's not Internet, assume the client is local, and avoid using the
+ * naming service because that can hang when the machine is disconnected.
+ */
+ state->name = mystrdup("localhost");
+ state->reverse_name = mystrdup("localhost");
+#ifdef AF_INET6
+ if (proto_info->sa_family_list[0] == PF_INET6) {
+ state->addr = mystrdup("::1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */
+ } else
+#endif
+ {
+ state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */
+ }
+ state->addr_family = AF_UNSPEC;
+ state->name_status = SMTPD_PEER_CODE_OK;
+ state->reverse_name_status = SMTPD_PEER_CODE_OK;
+ state->port = mystrdup("0"); /* XXX bogus. */
+
+ state->dest_addr = mystrdup(state->addr); /* XXX bogus. */
+ state->dest_port = mystrdup(state->port); /* XXX bogus. */
+}
+
+/* smtpd_peer_no_client - peer went away, or peer info unavailable */
+
+static void smtpd_peer_no_client(SMTPD_STATE *state)
+{
+ smtpd_peer_reset(state);
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->addr_family = AF_UNSPEC;
+ state->name_status = SMTPD_PEER_CODE_PERM;
+ state->reverse_name_status = SMTPD_PEER_CODE_PERM;
+ state->port = mystrdup(CLIENT_PORT_UNKNOWN);
+
+ state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN);
+ state->dest_port = mystrdup(SERVER_PORT_UNKNOWN);
+}
+
+/* smtpd_peer_from_pass_attr - initialize from attribute hash */
+
+static void smtpd_peer_from_pass_attr(SMTPD_STATE *state)
+{
+ HTABLE *attr = (HTABLE *) vstream_context(state->client);
+ const char *cp;
+
+ /*
+ * Extract the client endpoint information from the attribute hash.
+ */
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_ADDR)) == 0)
+ msg_fatal("missing client address from proxy");
+ if (strrchr(cp, ':') != 0) {
+ if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv6 client address syntax from proxy: %s", cp);
+ state->addr = mystrdup(cp);
+ state->rfc_addr = concatenate(IPV6_COL, cp, (char *) 0);
+ state->addr_family = AF_INET6;
+ } else {
+ if (valid_ipv4_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv4 client address syntax from proxy: %s", cp);
+ state->addr = mystrdup(cp);
+ state->rfc_addr = mystrdup(cp);
+ state->addr_family = AF_INET;
+ }
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_PORT)) == 0)
+ msg_fatal("missing client port from proxy");
+ if (valid_hostport(cp, DO_GRIPE) == 0)
+ msg_fatal("bad TCP client port number syntax from proxy: %s", cp);
+ state->port = mystrdup(cp);
+
+ /*
+ * The Dovecot authentication server needs the server IP address.
+ */
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_ADDR)) == 0)
+ msg_fatal("missing server address from proxy");
+ if (valid_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv6 server address syntax from proxy: %s", cp);
+ state->dest_addr = mystrdup(cp);
+
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_PORT)) == 0)
+ msg_fatal("missing server port from proxy");
+ if (valid_hostport(cp, DO_GRIPE) == 0)
+ msg_fatal("bad TCP server port number syntax from proxy: %s", cp);
+ state->dest_port = mystrdup(cp);
+
+ /*
+ * Convert the client address from string to binary form.
+ */
+ smtpd_peer_hostaddr_to_sockaddr(state);
+}
+
+/* smtpd_peer_from_default - try to initialize peer information from socket */
+
+void smtpd_peer_from_default(SMTPD_STATE *state)
+{
+
+ /*
+ * The "no client" routine provides surrogate information so that the
+ * application can produce sensible logging when a client disconnects
+ * before the server wakes up. The "not inet" routine provides surrogate
+ * state for (presumably) local IPC channels.
+ */
+ state->sockaddr_len = sizeof(state->sockaddr);
+ state->dest_sockaddr_len = sizeof(state->dest_sockaddr);
+ if (getpeername(vstream_fileno(state->client),
+ (struct sockaddr *) &state->sockaddr,
+ &state->sockaddr_len) <0
+ || getsockname(vstream_fileno(state->client),
+ (struct sockaddr *) &state->dest_sockaddr,
+ &state->dest_sockaddr_len) < 0) {
+ if (errno == ENOTSOCK)
+ smtpd_peer_not_inet(state);
+ else
+ smtpd_peer_no_client(state);
+ } else {
+ if (smtpd_peer_sockaddr_to_hostaddr(state) < 0)
+ smtpd_peer_not_inet(state);
+ }
+}
+
+/* smtpd_peer_from_proxy - get endpoint info from proxy agent */
+
+static void smtpd_peer_from_proxy(SMTPD_STATE *state)
+{
+ typedef struct {
+ const char *name;
+ int (*endpt_lookup) (SMTPD_STATE *);
+ } SMTPD_ENDPT_LOOKUP_INFO;
+ static const SMTPD_ENDPT_LOOKUP_INFO smtpd_endpt_lookup_info[] = {
+ HAPROXY_PROTO_NAME, smtpd_peer_from_haproxy,
+ 0,
+ };
+ const SMTPD_ENDPT_LOOKUP_INFO *pp;
+
+ /*
+ * When the proxy information is unavailable, we can't maintain an audit
+ * trail or enforce access control, therefore we forcibly hang up.
+ */
+ for (pp = smtpd_endpt_lookup_info; /* see below */ ; pp++) {
+ if (pp->name == 0)
+ msg_fatal("unsupported %s value: %s",
+ VAR_SMTPD_UPROXY_PROTO, var_smtpd_uproxy_proto);
+ if (strcmp(var_smtpd_uproxy_proto, pp->name) == 0)
+ break;
+ }
+ if (pp->endpt_lookup(state) < 0) {
+ smtpd_peer_from_default(state);
+ state->flags |= SMTPD_FLAG_HANGUP;
+ } else {
+ smtpd_peer_hostaddr_to_sockaddr(state);
+ }
+}
+
+/* smtpd_peer_init - initialize peer information */
+
+void smtpd_peer_init(SMTPD_STATE *state)
+{
+
+ /*
+ * Initialize.
+ */
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+
+ /*
+ * Prepare for partial initialization after error.
+ */
+ memset((void *) &(state->sockaddr), 0, sizeof(state->sockaddr));
+ state->sockaddr_len = 0;
+ state->name = 0;
+ state->reverse_name = 0;
+ state->addr = 0;
+ state->namaddr = 0;
+ state->rfc_addr = 0;
+ state->port = 0;
+ state->dest_addr = 0;
+ state->dest_port = 0;
+
+ /*
+ * Determine the remote SMTP client address and port.
+ *
+ * XXX In stand-alone mode, don't assume that the peer will be a local
+ * process. That could introduce a gaping hole when the SMTP daemon is
+ * hooked up to the network via inetd or some other super-server.
+ */
+ if (vstream_context(state->client) != 0) {
+ smtpd_peer_from_pass_attr(state);
+ if (*var_smtpd_uproxy_proto != 0)
+ msg_warn("ignoring non-empty %s setting behind postscreen",
+ VAR_SMTPD_UPROXY_PROTO);
+ } else if (SMTPD_STAND_ALONE(state) || *var_smtpd_uproxy_proto == 0) {
+ smtpd_peer_from_default(state);
+ } else {
+ smtpd_peer_from_proxy(state);
+ }
+
+ /*
+ * Determine the remote SMTP client hostname. Note: some of the handlers
+ * above provide surrogate endpoint information in case of error. In that
+ * case, leave the surrogate information alone.
+ */
+ if (state->name == 0)
+ smtpd_peer_sockaddr_to_hostname(state);
+
+ /*
+ * Do the name[addr]:port formatting for pretty reports.
+ */
+ state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr,
+ state->port);
+}
+
+/* smtpd_peer_reset - destroy peer information */
+
+void smtpd_peer_reset(SMTPD_STATE *state)
+{
+ if (state->name)
+ myfree(state->name);
+ if (state->reverse_name)
+ myfree(state->reverse_name);
+ if (state->addr)
+ myfree(state->addr);
+ if (state->namaddr)
+ myfree(state->namaddr);
+ if (state->rfc_addr)
+ myfree(state->rfc_addr);
+ if (state->port)
+ myfree(state->port);
+ if (state->dest_addr)
+ myfree(state->dest_addr);
+ if (state->dest_port)
+ myfree(state->dest_port);
+}
diff --git a/src/smtpd/smtpd_proxy.c b/src/smtpd/smtpd_proxy.c
new file mode 100644
index 0000000..1c1f9fc
--- /dev/null
+++ b/src/smtpd/smtpd_proxy.c
@@ -0,0 +1,1171 @@
+/*++
+/* NAME
+/* smtpd_proxy 3
+/* SUMMARY
+/* SMTP server pass-through proxy client
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_proxy.h>
+/*
+/* typedef struct {
+/* .in +4
+/* VSTREAM *stream; /* SMTP proxy or replay log */
+/* VSTRING *buffer; /* last SMTP proxy response */
+/* /* other fields... */
+/* .in -4
+/* } SMTPD_PROXY;
+/*
+/* int smtpd_proxy_create(state, flags, service, timeout,
+/* ehlo_name, mail_from)
+/* SMTPD_STATE *state;
+/* int flags;
+/* const char *service;
+/* int timeout;
+/* const char *ehlo_name;
+/* const char *mail_from;
+/*
+/* int proxy->cmd(state, expect, format, ...)
+/* SMTPD_PROXY *proxy;
+/* SMTPD_STATE *state;
+/* int expect;
+/* const char *format;
+/*
+/* void smtpd_proxy_free(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_proxy_parse_opts(param_name, param_val)
+/* const char *param_name;
+/* const char *param_val;
+/* RECORD-LEVEL ROUTINES
+/* int proxy->rec_put(proxy->stream, rec_type, data, len)
+/* SMTPD_PROXY *proxy;
+/* int rec_type;
+/* const char *data;
+/* ssize_t len;
+/*
+/* int proxy->rec_fprintf(proxy->stream, rec_type, format, ...)
+/* SMTPD_PROXY *proxy;
+/* int rec_type;
+/* cont char *format;
+/* DESCRIPTION
+/* The functions in this module implement a pass-through proxy
+/* client.
+/*
+/* In order to minimize the intrusiveness of pass-through
+/* proxying, 1) the proxy server must support the same MAIL
+/* FROM/RCPT syntax that Postfix supports, 2) the record-level
+/* routines for message content proxying have the same interface
+/* as the routines that are used for non-proxied mail.
+/*
+/* smtpd_proxy_create() takes a description of a before-queue
+/* filter. Depending on flags, it either arranges to buffer
+/* up commands and message content until the entire message
+/* is received, or it immediately connects to the proxy service,
+/* sends EHLO, sends client information with the XFORWARD
+/* command if possible, sends the MAIL FROM command, and
+/* receives the reply.
+/* A non-zero result value means trouble: either the proxy is
+/* unavailable, or it did not send the expected reply.
+/* All results are reported via the proxy->buffer field in a
+/* form that can be sent to the SMTP client. An unexpected
+/* 2xx or 3xx proxy server response is replaced by a generic
+/* error response to avoid support problems.
+/* In case of error, smtpd_proxy_create() updates the
+/* state->error_mask and state->err fields, and leaves the
+/* SMTPD_PROXY handle in an unconnected state. Destroy the
+/* handle after reporting the error reply in the proxy->buffer
+/* field.
+/*
+/* proxy->cmd() formats and either buffers up the command and
+/* expected response until the entire message is received, or
+/* it immediately sends the specified command to the proxy
+/* server, and receives the proxy server reply.
+/* A non-zero result value means trouble: either the proxy is
+/* unavailable, or it did not send the expected reply.
+/* All results are reported via the proxy->buffer field in a
+/* form that can be sent to the SMTP client. An unexpected
+/* 2xx or 3xx proxy server response is replaced by a generic
+/* error response to avoid support problems.
+/* In case of error, proxy->cmd() updates the state->error_mask
+/* and state->err fields.
+/*
+/* smtpd_proxy_free() destroys a proxy server handle and resets
+/* the state->proxy field.
+/*
+/* smtpd_proxy_parse_opts() parses main.cf processing options.
+/*
+/* proxy->rec_put() is a rec_put() clone that either buffers
+/* up arbitrary message content records until the entire message
+/* is received, or that immediately sends it to the proxy
+/* server.
+/* All data is expected to be in SMTP dot-escaped form.
+/* All errors are reported as a REC_TYPE_ERROR result value,
+/* with the state->error_mask, state->err and proxy-buffer
+/* fields given appropriate values.
+/*
+/* proxy->rec_fprintf() is a rec_fprintf() clone that formats
+/* message content and either buffers up the record until the
+/* entire message is received, or that immediately sends it
+/* to the proxy server.
+/* All data is expected to be in SMTP dot-escaped form.
+/* All errors are reported as a REC_TYPE_ERROR result value,
+/* with the state->error_mask, state->err and proxy-buffer
+/* fields given appropriate values.
+/*
+/* Arguments:
+/* .IP flags
+/* Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire
+/* message before contacting a before-queue content filter.
+/* Note: when this feature is requested, the before-queue
+/* filter MUST use the same 2xx, 4xx or 5xx reply code for all
+/* recipients of a multi-recipient message.
+/* .IP server
+/* The SMTP proxy server host:port. The host or host: part is optional.
+/* This argument is not duplicated.
+/* .IP timeout
+/* Time limit for connecting to the proxy server and for
+/* sending and receiving proxy server commands and replies.
+/* .IP ehlo_name
+/* The EHLO Hostname that will be sent to the proxy server.
+/* This argument is not duplicated.
+/* .IP mail_from
+/* The MAIL FROM command. This argument is not duplicated.
+/* .IP state
+/* SMTP server state.
+/* .IP expect
+/* Expected proxy server reply status code range. A warning is logged
+/* when an unexpected reply is received. Specify one of the following:
+/* .RS
+/* .IP SMTPD_PROX_WANT_OK
+/* The caller expects a reply in the 200 range.
+/* .IP SMTPD_PROX_WANT_MORE
+/* The caller expects a reply in the 300 range.
+/* .IP SMTPD_PROX_WANT_ANY
+/* The caller has no expectation. Do not warn for unexpected replies.
+/* .IP SMTPD_PROX_WANT_NONE
+/* Do not bother waiting for a reply.
+/* .RE
+/* .IP format
+/* A format string.
+/* .IP stream
+/* Connection to proxy server.
+/* .IP data
+/* Pointer to the content of one message content record.
+/* .IP len
+/* The length of a message content record.
+/* SEE ALSO
+/* smtpd(8) Postfix smtp server
+/* DIAGNOSTICS
+/* Panic: internal API violations.
+/*
+/* Fatal errors: memory allocation problem.
+/*
+/* Warnings: unexpected response from proxy server, unable
+/* to connect to proxy server, proxy server read/write error,
+/* proxy speed-adjust buffer read/write error.
+/* 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 <ctype.h>
+#include <unistd.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 <connect.h>
+#include <name_code.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_error.h>
+#include <smtp_stream.h>
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <xtext.h>
+#include <record.h>
+#include <mail_queue.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_proxy.h>
+
+ /*
+ * XFORWARD server features, recognized by the pass-through proxy client.
+ */
+#define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */
+#define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */
+#define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */
+#define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */
+#define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */
+#define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */
+#define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */
+
+ /*
+ * Spead-matching: we use an unlinked file for transient storage.
+ */
+static VSTREAM *smtpd_proxy_replay_stream;
+
+ /*
+ * Forward declarations.
+ */
+static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int);
+static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int);
+static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...);
+static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t);
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* smtpd_proxy_xforward_flush - flush forwarding information */
+
+static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
+{
+ int ret;
+
+ if (VSTRING_LEN(buf) > 0) {
+ ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
+ XFORWARD_CMD "%s", STR(buf));
+ VSTRING_RESET(buf);
+ return (ret);
+ }
+ return (0);
+}
+
+/* smtpd_proxy_xforward_send - send forwarding information */
+
+static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf,
+ const char *name,
+ int value_available,
+ const char *value)
+{
+ size_t new_len;
+ int ret;
+
+#define CONSTR_LEN(s) (sizeof(s) - 1)
+#define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
+
+ if (!value_available)
+ value = XFORWARD_UNAVAILABLE;
+
+ /*
+ * Encode the attribute value.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+ xtext_quote(state->expand_buf, value, "");
+
+ /*
+ * How much space does this attribute need? SPACE name = value.
+ */
+ new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
+ if (new_len > PAYLOAD_LIMIT)
+ msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
+ XFORWARD_CMD, name, value);
+
+ /*
+ * Flush the buffer if we need to, and store the attribute.
+ */
+ if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
+ if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0)
+ return (ret);
+ vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
+
+ return (0);
+}
+
+/* smtpd_proxy_connect - open proxy connection */
+
+static int smtpd_proxy_connect(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ int fd;
+ char *lines;
+ char *words;
+ VSTRING *buf;
+ int bad;
+ char *word;
+ static const NAME_CODE known_xforward_features[] = {
+ XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ int server_xforward_features;
+ int (*connect_fn) (const char *, int, int);
+ const char *endpoint;
+
+ /*
+ * Find connection method (default inet)
+ */
+ if (strncasecmp("unix:", proxy->service_name, 5) == 0) {
+ endpoint = proxy->service_name + 5;
+ connect_fn = unix_connect;
+ } else {
+ if (strncasecmp("inet:", proxy->service_name, 5) == 0)
+ endpoint = proxy->service_name + 5;
+ else
+ endpoint = proxy->service_name;
+ connect_fn = inet_connect;
+ }
+
+ /*
+ * Connect to proxy.
+ */
+ if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) {
+ msg_warn("connect to proxy filter %s: %m", proxy->service_name);
+ return (smtpd_proxy_rdwr_error(state, 0));
+ }
+ proxy->service_stream = vstream_fdopen(fd, O_RDWR);
+ /* Needed by our DATA-phase record emulation routines. */
+ vstream_control(proxy->service_stream,
+ CA_VSTREAM_CTL_CONTEXT((void *) state),
+ CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (connect_fn == inet_connect)
+ vstream_tweak_tcp(proxy->service_stream);
+ smtp_timeout_setup(proxy->service_stream, proxy->timeout);
+
+ /*
+ * Get server greeting banner.
+ *
+ * If this fails then we have a problem because the proxy should always
+ * accept our connection. Make up our own response instead of passing
+ * back a negative greeting banner: the proxy open is delayed to the
+ * point that the client expects a MAIL FROM or RCPT TO reply.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+
+ /*
+ * Send our own EHLO command. If this fails then we have a problem
+ * because the proxy should always accept our EHLO command. Make up our
+ * own response instead of passing back a negative EHLO reply: the proxy
+ * open is delayed to the point that the remote SMTP client expects a
+ * MAIL FROM or RCPT TO reply.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s",
+ proxy->ehlo_name)) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+
+ /*
+ * Parse the EHLO reply and see if we can forward logging information.
+ */
+ server_xforward_features = 0;
+ lines = STR(proxy->reply);
+ while ((words = mystrtok(&lines, "\r\n")) != 0) {
+ if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
+ if (strcasecmp(word, XFORWARD_CMD) == 0)
+ while ((word = mystrtok(&words, " \t")) != 0)
+ server_xforward_features |=
+ name_code(known_xforward_features,
+ NAME_CODE_FLAG_NONE, word);
+ }
+ }
+
+ /*
+ * Send XFORWARD attributes. For robustness, explicitly specify what SMTP
+ * session attributes are known and unknown. Make up our own response
+ * instead of passing back a negative XFORWARD reply: the proxy open is
+ * delayed to the point that the remote SMTP client expects a MAIL FROM
+ * or RCPT TO reply.
+ */
+ if (server_xforward_features) {
+ buf = vstring_alloc(100);
+ bad =
+ (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME,
+ IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
+ FORWARD_NAME(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR,
+ IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
+ FORWARD_ADDR(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT,
+ IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
+ FORWARD_PORT(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO,
+ IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
+ FORWARD_HELO(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT,
+ IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
+ FORWARD_IDENT(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO,
+ IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
+ FORWARD_PROTO(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1,
+ STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
+ XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE))
+ || smtpd_proxy_xforward_flush(state, buf));
+ vstring_free(buf);
+ if (bad) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+ }
+
+ /*
+ * Pass-through the remote SMTP client's MAIL FROM command. If this
+ * fails, then we have a problem because the proxy should always accept
+ * any MAIL FROM command that was accepted by us.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s",
+ proxy->mail_from) != 0) {
+ /* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+ return (0);
+}
+
+/* smtpd_proxy_fake_server_reply - produce generic error response */
+
+static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status)
+{
+ const CLEANUP_STAT_DETAIL *detail;
+
+ /*
+ * Either we have no server reply (connection refused), or we have an
+ * out-of-protocol server reply, so we make up a generic server error
+ * response instead.
+ */
+ detail = cleanup_stat_detail(status);
+ vstring_sprintf(state->proxy->reply,
+ "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+}
+
+/* smtpd_proxy_replay_rdwr_error - report replay log I/O error */
+
+static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state)
+{
+
+ /*
+ * Log an appropriate warning message.
+ */
+ msg_warn("proxy speed-adjust log I/O error: %m");
+
+ /*
+ * Set the appropriate flags and server reply.
+ */
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ /* Update state->err in case we are past the client's DATA command. */
+ state->err |= CLEANUP_STAT_PROXY;
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ return (-1);
+}
+
+/* smtpd_proxy_rdwr_error - report proxy communication error */
+
+static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err)
+{
+ const char *myname = "smtpd_proxy_rdwr_error";
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Sanity check.
+ */
+ if (err != 0 && err != SMTP_ERR_NONE && proxy == 0)
+ msg_panic("%s: proxy error %d without proxy handle", myname, err);
+
+ /*
+ * Log an appropriate warning message.
+ */
+ switch (err) {
+ case 0:
+ case SMTP_ERR_NONE:
+ break;
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection with proxy %s", proxy->service_name);
+ break;
+ case SMTP_ERR_TIME:
+ msg_warn("timeout talking to proxy %s", proxy->service_name);
+ break;
+ default:
+ msg_panic("%s: unknown proxy %s error %d",
+ myname, proxy->service_name, err);
+ }
+
+ /*
+ * Set the appropriate flags and server reply.
+ */
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* Update state->err in case we are past the client's DATA command. */
+ state->err |= CLEANUP_STAT_PROXY;
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ return (-1);
+}
+
+/* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */
+
+static int smtpd_proxy_replay_send(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_proxy_replay_send";
+ static VSTRING *replay_buf = 0;
+ SMTPD_PROXY *proxy = state->proxy;
+ int rec_type;
+ int expect = SMTPD_PROX_WANT_BAD;
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_proxy_replay_stream == 0)
+ msg_panic("%s: no before-queue filter speed-adjust log", myname);
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(smtpd_proxy_replay_stream)
+ || vstream_feof(smtpd_proxy_replay_stream)
+ || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
+ || vstream_fflush(smtpd_proxy_replay_stream))
+ /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ /*
+ * Delayed connection to the before-queue filter.
+ */
+ if (smtpd_proxy_connect(state) < 0)
+ return (-1);
+
+ /*
+ * Replay the speed-match log. We do sanity check record content, but we
+ * don't implement a protocol state engine here, since we are reading
+ * from a file that we just wrote ourselves.
+ *
+ * This is different than the MailChannels patented solution that
+ * multiplexes a large number of slowed-down inbound connections over a
+ * small number of fast connections to a local MTA.
+ *
+ * - MailChannels receives mail directly from the Internet. It uses one
+ * connection to the local MTA to reject invalid recipients before
+ * receiving the entire email message at reduced bit rates, and then uses
+ * a different connection to quickly deliver the message to the local
+ * MTA.
+ *
+ * - Postfix receives mail directly from the Internet. The Postfix SMTP
+ * server rejects invalid recipients before receiving the entire message
+ * over the Internet, and then delivers the message quickly to a local
+ * SMTP-based content filter.
+ */
+ if (replay_buf == 0)
+ replay_buf = vstring_alloc(100);
+ if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ for (;;) {
+ switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
+ REC_FLAG_NONE)) {
+
+ /*
+ * Message content.
+ */
+ case REC_TYPE_NORM:
+ case REC_TYPE_CONT:
+ if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
+ STR(replay_buf), LEN(replay_buf)) < 0)
+ return (-1);
+ break;
+
+ /*
+ * Expected server reply type.
+ */
+ case REC_TYPE_RCPT:
+ if (!alldig(STR(replay_buf))
+ || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
+ msg_panic("%s: malformed server reply type: %s",
+ myname, STR(replay_buf));
+ break;
+
+ /*
+ * Client command, or void. Bail out on the first negative proxy
+ * response. This is OK, because the filter must use the same
+ * reply code for all recipients of a multi-recipient message.
+ */
+ case REC_TYPE_FROM:
+ if (expect == SMTPD_PROX_WANT_BAD)
+ msg_panic("%s: missing server reply type", myname);
+ if (smtpd_proxy_cmd(state, expect, "%s", STR(replay_buf)) < 0)
+ return (-1);
+ expect = SMTPD_PROX_WANT_BAD;
+ break;
+
+ /*
+ * Explicit end marker, instead of implicit EOF.
+ */
+ case REC_TYPE_END:
+ return (0);
+
+ /*
+ * Errors.
+ */
+ case REC_TYPE_ERROR:
+ return (smtpd_proxy_replay_rdwr_error(state));
+ default:
+ msg_panic("%s: unexpected record type; %d", myname, rec_type);
+ }
+ }
+}
+
+/* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */
+
+static int PRINTFLIKE(3, 4) smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(smtpd_proxy_replay_stream)
+ || vstream_feof(smtpd_proxy_replay_stream))
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ /*
+ * Save the expected reply first, so that the replayer can safely
+ * overwrite the input buffer with the command.
+ */
+ rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect);
+
+ /*
+ * The command can be omitted at the start of an SMTP session. This is
+ * not documented as part of the official interface because it is used
+ * only internally to this module.
+ */
+
+ /*
+ * Save the command to the replay log, and send it to the before-queue
+ * filter after we have received the entire message.
+ */
+ va_start(ap, fmt);
+ rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap);
+ va_end(ap);
+
+ /*
+ * If we just saved the "." command, replay the log.
+ */
+ return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state));
+}
+
+/* smtpd_proxy_cmd - send command to proxy, receive reply */
+
+static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ va_list ap;
+ char *cp;
+ int last_char;
+ int err = 0;
+ static VSTRING *buffer = 0;
+
+ /*
+ * Errors first. Be prepared for delayed errors from the DATA phase.
+ */
+ if (vstream_ferror(proxy->service_stream)
+ || vstream_feof(proxy->service_stream)
+ || (err = vstream_setjmp(proxy->service_stream)) != 0) {
+ return (smtpd_proxy_rdwr_error(state, err));
+ }
+
+ /*
+ * Format the command.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(proxy->request, fmt, ap);
+ va_end(ap);
+
+ /*
+ * The command can be omitted at the start of an SMTP session. This is
+ * not documented as part of the official interface because it is used
+ * only internally to this module.
+ */
+ if (LEN(proxy->request) > 0) {
+
+ /*
+ * Optionally log the command first, so that we can see in the log
+ * what the program is trying to do.
+ */
+ if (msg_verbose)
+ msg_info("> %s: %s", proxy->service_name, STR(proxy->request));
+
+ /*
+ * Send the command to the proxy server. Since we're going to read a
+ * reply immediately, there is no need to flush buffers.
+ */
+ smtp_fputs(STR(proxy->request), LEN(proxy->request),
+ proxy->service_stream);
+ }
+
+ /*
+ * Early return if we don't want to wait for a server reply (such as
+ * after sending QUIT).
+ */
+ if (expect == SMTPD_PROX_WANT_NONE)
+ return (0);
+
+ /*
+ * Censor out non-printable characters in server responses and save
+ * complete multi-line responses if possible.
+ *
+ * We can't parse or store input that exceeds var_line_limit, so we just
+ * skip over it to simplify the remainder of the code below.
+ */
+ VSTRING_RESET(proxy->reply);
+ if (buffer == 0)
+ buffer = vstring_alloc(10);
+ for (;;) {
+ last_char = smtp_get(buffer, proxy->service_stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
+ printable(STR(buffer), '?');
+ if (last_char != '\n')
+ msg_warn("%s: response longer than %d: %.30s...",
+ proxy->service_name, var_line_limit,
+ STR(buffer));
+ if (msg_verbose)
+ msg_info("< %s: %.100s", proxy->service_name, STR(buffer));
+
+ /*
+ * Defend against a denial of service attack by limiting the amount
+ * of multi-line text that we are willing to store.
+ */
+ if (LEN(proxy->reply) < var_line_limit) {
+ if (VSTRING_LEN(proxy->reply))
+ vstring_strcat(proxy->reply, "\r\n");
+ vstring_strcat(proxy->reply, STR(buffer));
+ }
+
+ /*
+ * Parse the response into code and text. Ignore unrecognized
+ * garbage. This means that any character except space (or end of
+ * line) will have the same effect as the '-' line continuation
+ * character.
+ */
+ for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
+ /* void */ ;
+ if (cp - STR(buffer) == 3) {
+ if (*cp == '-')
+ continue;
+ if (*cp == ' ' || *cp == 0)
+ break;
+ }
+ msg_warn("received garbage from proxy %s: %.100s",
+ proxy->service_name, STR(buffer));
+ }
+
+ /*
+ * Log a warning in case the proxy does not send the expected response.
+ * Silently accept any response when the client expressed no expectation.
+ *
+ * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx
+ * proxy replies. They are a source of support problems, so we replace
+ * them by generic server error replies.
+ */
+ if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) {
+ msg_warn("proxy %s rejected \"%s\": \"%s\"",
+ proxy->service_name, LEN(proxy->request) == 0 ?
+ "connection request" : STR(proxy->request),
+ STR(proxy->reply));
+ if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK
+ || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) {
+ smtpd_proxy_rdwr_error(state, 0);
+ }
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+/* smtpd_proxy_save_rec_put - save message content to replay log */
+
+static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type,
+ const char *data, ssize_t len)
+{
+ const char *myname = "smtpd_proxy_save_rec_put";
+ int ret;
+
+#define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s))
+
+ /*
+ * Sanity check.
+ */
+ if (stream == 0)
+ msg_panic("%s: attempt to use closed stream", myname);
+
+ /*
+ * Send one content record. Errors and results must be as with rec_put().
+ */
+ if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)
+ ret = rec_put(stream, rec_type, data, len);
+ else
+ msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
+
+ /*
+ * Errors last.
+ */
+ if (ret != rec_type) {
+ (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
+ return (REC_TYPE_ERROR);
+ }
+ return (rec_type);
+}
+
+/* smtpd_proxy_rec_put - send message content, rec_put() clone */
+
+static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
+ const char *data, ssize_t len)
+{
+ const char *myname = "smtpd_proxy_rec_put";
+ int err = 0;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(stream) || vstream_feof(stream)
+ || (err = vstream_setjmp(stream)) != 0) {
+ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
+ return (REC_TYPE_ERROR);
+ }
+
+ /*
+ * Send one content record. Errors and results must be as with rec_put().
+ */
+ if (rec_type == REC_TYPE_NORM)
+ smtp_fputs(data, len, stream);
+ else if (rec_type == REC_TYPE_CONT)
+ smtp_fwrite(data, len, stream);
+ else
+ msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
+ return (rec_type);
+}
+
+/* smtpd_proxy_save_rec_fprintf - save message content to replay log */
+
+static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type,
+ const char *fmt,...)
+{
+ const char *myname = "smtpd_proxy_save_rec_fprintf";
+ va_list ap;
+ int ret;
+
+ /*
+ * Sanity check.
+ */
+ if (stream == 0)
+ msg_panic("%s: attempt to use closed stream", myname);
+
+ /*
+ * Save one content record. Errors and results must be as with
+ * rec_fprintf().
+ */
+ va_start(ap, fmt);
+ if (rec_type == REC_TYPE_NORM)
+ ret = rec_vfprintf(stream, rec_type, fmt, ap);
+ else
+ msg_panic("%s: need REC_TYPE_NORM", myname);
+ va_end(ap);
+
+ /*
+ * Errors last.
+ */
+ if (ret != rec_type) {
+ (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
+ return (REC_TYPE_ERROR);
+ }
+ return (rec_type);
+}
+
+/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
+
+static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
+ const char *fmt,...)
+{
+ const char *myname = "smtpd_proxy_rec_fprintf";
+ va_list ap;
+ int err = 0;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(stream) || vstream_feof(stream)
+ || (err = vstream_setjmp(stream)) != 0) {
+ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
+ return (REC_TYPE_ERROR);
+ }
+
+ /*
+ * Send one content record. Errors and results must be as with
+ * rec_fprintf().
+ */
+ va_start(ap, fmt);
+ if (rec_type == REC_TYPE_NORM)
+ smtp_vprintf(stream, fmt, ap);
+ else
+ msg_panic("%s: need REC_TYPE_NORM", myname);
+ va_end(ap);
+ return (rec_type);
+}
+
+#ifndef NO_TRUNCATE
+
+/* smtpd_proxy_replay_setup - prepare the replay logfile */
+
+static int smtpd_proxy_replay_setup(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_proxy_replay_setup";
+ off_t file_offs;
+
+ /*
+ * Where possible reuse an existing replay logfile, because creating a
+ * file is expensive compared to reading or writing. For security reasons
+ * we must truncate the file before reuse. For performance reasons we
+ * should truncate the file immediately after the end of a mail
+ * transaction. We enforce the security guarantee upon reuse, by
+ * requiring that no I/O happened since the file was truncated. This is
+ * less expensive than truncating the file redundantly.
+ */
+ if (smtpd_proxy_replay_stream != 0) {
+ /* vstream_ftell() won't invoke the kernel, so all errors are mine. */
+ if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0)
+ msg_panic("%s: bad before-queue filter speed-adjust log offset %lu",
+ myname, (unsigned long) file_offs);
+ vstream_clearerr(smtpd_proxy_replay_stream);
+ if (msg_verbose)
+ msg_info("%s: reuse speed-adjust stream fd=%d", myname,
+ vstream_fileno(smtpd_proxy_replay_stream));
+ /* Here, smtpd_proxy_replay_stream != 0 */
+ }
+
+ /*
+ * Create a new replay logfile.
+ */
+ if (smtpd_proxy_replay_stream == 0) {
+ smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0,
+ (struct timeval *) 0);
+ if (smtpd_proxy_replay_stream == 0)
+ return (smtpd_proxy_replay_rdwr_error(state));
+ if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0)
+ msg_warn("remove before-queue filter speed-adjust log %s: %m",
+ VSTREAM_PATH(smtpd_proxy_replay_stream));
+ if (msg_verbose)
+ msg_info("%s: new speed-adjust stream fd=%d", myname,
+ vstream_fileno(smtpd_proxy_replay_stream));
+ }
+
+ /*
+ * Needed by our DATA-phase record emulation routines.
+ */
+ vstream_control(smtpd_proxy_replay_stream,
+ CA_VSTREAM_CTL_CONTEXT((void *) state),
+ CA_VSTREAM_CTL_END);
+ return (0);
+}
+
+#endif
+
+/* smtpd_proxy_create - set up smtpd proxy handle */
+
+int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service,
+ int timeout, const char *ehlo_name,
+ const char *mail_from)
+{
+ SMTPD_PROXY *proxy;
+
+ /*
+ * When an operation has many arguments it is safer to use named
+ * parameters, and have the compiler enforce the argument count.
+ */
+#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \
+ ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \
+ (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \
+ (p)->a10, (p)->a11, (p)->a12, (p))
+
+ /*
+ * Sanity check.
+ */
+ if (state->proxy != 0)
+ msg_panic("smtpd_proxy_create: handle still exists");
+
+ /*
+ * Connect to the before-queue filter immediately.
+ */
+ if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) {
+ state->proxy =
+ SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10),
+ reply = vstring_alloc(10),
+ cmd = smtpd_proxy_cmd,
+ rec_fprintf = smtpd_proxy_rec_fprintf,
+ rec_put = smtpd_proxy_rec_put,
+ flags = flags, service_stream = 0,
+ service_name = service, timeout = timeout,
+ ehlo_name = ehlo_name, mail_from = mail_from);
+ if (smtpd_proxy_connect(state) < 0) {
+ /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */
+ return (-1);
+ }
+ proxy->stream = proxy->service_stream;
+ return (0);
+ }
+
+ /*
+ * Connect to the before-queue filter after we receive the entire
+ * message. Open the replay logfile early to simplify code. The file is
+ * reused for multiple mail transactions, so there is no need to minimize
+ * its life time.
+ */
+ else {
+#ifdef NO_TRUNCATE
+ msg_panic("smtpd_proxy_create: speed-adjust support is not available");
+#else
+ if (smtpd_proxy_replay_setup(state) < 0)
+ return (-1);
+ state->proxy =
+ SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream,
+ request = vstring_alloc(10),
+ reply = vstring_alloc(10),
+ cmd = smtpd_proxy_save_cmd,
+ rec_fprintf = smtpd_proxy_save_rec_fprintf,
+ rec_put = smtpd_proxy_save_rec_put,
+ flags = flags, service_stream = 0,
+ service_name = service, timeout = timeout,
+ ehlo_name = ehlo_name, mail_from = mail_from);
+ return (0);
+#endif
+ }
+}
+
+/* smtpd_proxy_close - close proxy connection without destroying handle */
+
+void smtpd_proxy_close(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber
+ * the END-OF-DATA reply.
+ */
+ if (proxy->service_stream != 0) {
+ if (vstream_feof(proxy->service_stream) == 0
+ && vstream_ferror(proxy->service_stream) == 0)
+ (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE,
+ SMTPD_CMD_QUIT);
+ (void) vstream_fclose(proxy->service_stream);
+ if (proxy->stream == proxy->service_stream)
+ proxy->stream = 0;
+ proxy->service_stream = 0;
+ }
+}
+
+/* smtpd_proxy_free - destroy smtpd proxy handle */
+
+void smtpd_proxy_free(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Clean up.
+ */
+ if (proxy->service_stream != 0)
+ (void) smtpd_proxy_close(state);
+ if (proxy->request != 0)
+ vstring_free(proxy->request);
+ if (proxy->reply != 0)
+ vstring_free(proxy->reply);
+ myfree((void *) proxy);
+ state->proxy = 0;
+
+ /*
+ * Reuse the replay logfile if possible. For security reasons we must
+ * truncate the replay logfile before reuse. For performance reasons we
+ * should truncate the replay logfile immediately after the end of a mail
+ * transaction. We truncate the file here, and enforce the security
+ * guarantee by requiring that no I/O happens before the file is reused.
+ */
+ if (smtpd_proxy_replay_stream == 0)
+ return;
+ if (vstream_ferror(smtpd_proxy_replay_stream)) {
+ /* Errors are already reported. */
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+ /* Flush output from aborted transaction before truncating the file!! */
+ if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) {
+ msg_warn("seek before-queue filter speed-adjust log: %m");
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+ if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) {
+ msg_warn("truncate before-queue filter speed-adjust log: %m");
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+}
+
+/* smtpd_proxy_parse_opts - parse main.cf options */
+
+int smtpd_proxy_parse_opts(const char *param_name, const char *param_val)
+{
+ static const NAME_MASK proxy_opts_table[] = {
+ SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST,
+ 0, 0,
+ };
+ int flags;
+
+ /*
+ * The optional before-filter speed-adjust buffers use disk space.
+ * However, we don't know if they compete for storage space with the
+ * after-filter queue, so we can't simply bump up the free space
+ * requirement to 2.5 * message_size_limit.
+ */
+ flags = name_mask(param_name, proxy_opts_table, param_val);
+ if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) {
+#ifdef NO_TRUNCATE
+ msg_warn("smtpd_proxy %s support is not available",
+ SMTPD_PROXY_NAME_SPEED_ADJUST);
+ flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST;
+#endif
+ }
+ return (flags);
+}
diff --git a/src/smtpd/smtpd_proxy.h b/src/smtpd/smtpd_proxy.h
new file mode 100644
index 0000000..3d35d07
--- /dev/null
+++ b/src/smtpd/smtpd_proxy.h
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* smtpd_proxy 3h
+/* SUMMARY
+/* SMTP server pass-through proxy client
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_proxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Application-specific.
+ */
+typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_CMD_FN) (SMTPD_STATE *, int, const char *,...);
+typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_REC_FPRINTF_FN) (VSTREAM *, int, const char *,...);
+typedef int (*SMTPD_PROXY_REC_PUT_FN) (VSTREAM *, int, const char *, ssize_t);
+
+typedef struct SMTPD_PROXY {
+ /* Public. */
+ VSTREAM *stream;
+ VSTRING *request; /* proxy request buffer */
+ VSTRING *reply; /* proxy reply buffer */
+ SMTPD_PROXY_CMD_FN cmd;
+ SMTPD_PROXY_REC_FPRINTF_FN rec_fprintf;
+ SMTPD_PROXY_REC_PUT_FN rec_put;
+ /* Private. */
+ int flags;
+ VSTREAM *service_stream;
+ const char *service_name;
+ int timeout;
+ const char *ehlo_name;
+ const char *mail_from;
+} SMTPD_PROXY;
+
+#define SMTPD_PROXY_FLAG_SPEED_ADJUST (1<<0)
+
+#define SMTPD_PROXY_NAME_SPEED_ADJUST "speed_adjust"
+
+#define SMTPD_PROX_WANT_BAD 0xff /* Do not use */
+#define SMTPD_PROX_WANT_NONE '\0' /* Do not receive reply */
+#define SMTPD_PROX_WANT_ANY '0' /* Expect any reply */
+#define SMTPD_PROX_WANT_OK '2' /* Expect 2XX reply */
+#define SMTPD_PROX_WANT_MORE '3' /* Expect 3XX reply */
+
+extern int smtpd_proxy_create(SMTPD_STATE *, int, const char *, int, const char *, const char *);
+extern void smtpd_proxy_close(SMTPD_STATE *);
+extern void smtpd_proxy_free(SMTPD_STATE *);
+extern int smtpd_proxy_parse_opts(const char *, const char *);
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_resolve.c b/src/smtpd/smtpd_resolve.c
new file mode 100644
index 0000000..1dd6914
--- /dev/null
+++ b/src/smtpd/smtpd_resolve.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* smtpd_resolve 3
+/* SUMMARY
+/* caching resolve client
+/* SYNOPSIS
+/* #include <smtpd_resolve.h>
+/*
+/* void smtpd_resolve_init(cache_size)
+/* int cache_size;
+/*
+/* const RESOLVE_REPLY *smtpd_resolve_addr(sender, addr)
+/* const char *sender;
+/* const char *addr;
+/* DESCRIPTION
+/* This module maintains a resolve client cache that persists
+/* across SMTP sessions (not process life times). Addresses
+/* are always resolved in local rewriting context.
+/*
+/* smtpd_resolve_init() initializes the cache and must be
+/* called before the cache can be used. This function may also
+/* be called to flush the cache after an address class update.
+/*
+/* smtpd_resolve_addr() resolves one address or returns
+/* a known result from cache.
+/*
+/* Arguments:
+/* .IP cache_size
+/* The requested cache size.
+/* .IP sender
+/* The message sender, or null pointer.
+/* .IP addr
+/* The address to resolve.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* The recipient address is always case folded to lowercase.
+/* Changing this requires great care, since the address is used
+/* for policy 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <ctable.h>
+#include <stringops.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <rewrite_clnt.h>
+#include <resolve_clnt.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd_resolve.h>
+
+static CTABLE *smtpd_resolve_cache;
+
+#define STR(x) vstring_str(x)
+#define SENDER_ADDR_JOIN_CHAR '\n'
+
+/* resolve_pagein - page in an address resolver result */
+
+static void *resolve_pagein(const char *sender_plus_addr, void *unused_context)
+{
+ const char myname[] = "resolve_pagein";
+ static VSTRING *query;
+ static VSTRING *junk;
+ static VSTRING *sender_buf;
+ RESOLVE_REPLY *reply;
+ const char *sender;
+ const char *addr;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (query == 0) {
+ query = vstring_alloc(10);
+ junk = vstring_alloc(10);
+ sender_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Initialize.
+ */
+ reply = (RESOLVE_REPLY *) mymalloc(sizeof(*reply));
+ resolve_clnt_init(reply);
+
+ /*
+ * Split the sender and address.
+ */
+ vstring_strcpy(junk, sender_plus_addr);
+ sender = STR(junk);
+ if ((addr = split_at(STR(junk), SENDER_ADDR_JOIN_CHAR)) == 0)
+ msg_panic("%s: bad search key: \"%s\"", myname, sender_plus_addr);
+
+ /*
+ * Resolve the address.
+ */
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, sender, sender_buf);
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, addr, query);
+ resolve_clnt_query_from(STR(sender_buf), STR(query), reply);
+ vstring_strcpy(junk, STR(reply->recipient));
+ casefold(reply->recipient, STR(junk)); /* XXX */
+
+ /*
+ * Save the result.
+ */
+ return ((void *) reply);
+}
+
+/* resolve_pageout - page out an address resolver result */
+
+static void resolve_pageout(void *data, void *unused_context)
+{
+ RESOLVE_REPLY *reply = (RESOLVE_REPLY *) data;
+
+ resolve_clnt_free(reply);
+ myfree((void *) reply);
+}
+
+/* smtpd_resolve_init - set up global cache */
+
+void smtpd_resolve_init(int cache_size)
+{
+
+ /*
+ * Flush a pre-existing cache. The smtpd_check test program requires this
+ * after an address class change.
+ */
+ if (smtpd_resolve_cache)
+ ctable_free(smtpd_resolve_cache);
+
+ /*
+ * Initialize the resolved address cache. Note: the cache persists across
+ * SMTP sessions so we cannot make it dependent on session state.
+ */
+ smtpd_resolve_cache = ctable_create(cache_size, resolve_pagein,
+ resolve_pageout, (void *) 0);
+}
+
+/* smtpd_resolve_addr - resolve cached address */
+
+const RESOLVE_REPLY *smtpd_resolve_addr(const char *sender, const char *addr)
+{
+ static VSTRING *sender_plus_addr_buf;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (sender_plus_addr_buf == 0)
+ sender_plus_addr_buf = vstring_alloc(10);
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_resolve_cache == 0)
+ msg_panic("smtpd_resolve_addr: missing initialization");
+
+ /*
+ * Reply from the read-through cache.
+ */
+ vstring_sprintf(sender_plus_addr_buf, "%s%c%s",
+ sender ? sender : RESOLVE_NULL_FROM,
+ SENDER_ADDR_JOIN_CHAR, addr);
+ return (const RESOLVE_REPLY *)
+ ctable_locate(smtpd_resolve_cache, STR(sender_plus_addr_buf));
+}
diff --git a/src/smtpd/smtpd_resolve.h b/src/smtpd/smtpd_resolve.h
new file mode 100644
index 0000000..cd0257a
--- /dev/null
+++ b/src/smtpd/smtpd_resolve.h
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* smtpd_resolve 3h
+/* SUMMARY
+/* caching resolve client
+/* SYNOPSIS
+/* include <smtpd_resolve.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <resolve_clnt.h>
+
+ /*
+ * External interface.
+ */
+extern void smtpd_resolve_init(int);
+extern const RESOLVE_REPLY *smtpd_resolve_addr(const char*, const char *);
+
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
diff --git a/src/smtpd/smtpd_sasl_glue.c b/src/smtpd/smtpd_sasl_glue.c
new file mode 100644
index 0000000..2dc6aad
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_glue.c
@@ -0,0 +1,396 @@
+/*++
+/* NAME
+/* smtpd_sasl_glue 3
+/* SUMMARY
+/* Postfix SMTP server, SASL support interface
+/* SYNOPSIS
+/* #include "smtpd_sasl_glue.h"
+/*
+/* void smtpd_sasl_state_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_sasl_initialize()
+/*
+/* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val)
+/* SMTPD_STATE *state;
+/* const char *sasl_opts_name;
+/* const char *sasl_opts_val;
+/*
+/* char *smtpd_sasl_authenticate(state, sasl_method, init_response)
+/* SMTPD_STATE *state;
+/* const char *sasl_method;
+/* const char *init_response;
+/*
+/* void smtpd_sasl_logout(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_sasl_login(state, sasl_username, sasl_method)
+/* SMTPD_STATE *state;
+/* const char *sasl_username;
+/* const char *sasl_method;
+/*
+/* void smtpd_sasl_deactivate(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_sasl_is_active(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_sasl_set_inactive(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* This module encapsulates most of the detail specific to SASL
+/* authentication.
+/*
+/* smtpd_sasl_state_init() performs minimal server state
+/* initialization to support external authentication (e.g.,
+/* XCLIENT) without having to enable SASL in main.cf. This
+/* should always be called at process startup.
+/*
+/* smtpd_sasl_initialize() initializes the SASL library. This
+/* routine should be called once at process start-up. It may
+/* need access to the file system for run-time loading of
+/* plug-in modules. There is no corresponding cleanup routine.
+/*
+/* smtpd_sasl_activate() performs per-connection initialization.
+/* This routine should be called once at the start of every
+/* connection. The sasl_opts_name and sasl_opts_val parameters
+/* are the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
+/*
+/* smtpd_sasl_authenticate() implements the authentication
+/* dialog. The result is zero in case of success, -1 in case
+/* of failure. smtpd_sasl_authenticate() updates the following
+/* state structure members:
+/* .IP sasl_method
+/* The authentication method that was successfully applied.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .IP sasl_username
+/* The username that was successfully authenticated.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .PP
+/* smtpd_sasl_login() records the result of successful external
+/* authentication, i.e. without invoking smtpd_sasl_authenticate(),
+/* but produces an otherwise equivalent result.
+/*
+/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate().
+/* This routine exists for the sake of symmetry.
+/*
+/* smtpd_sasl_deactivate() performs per-connection cleanup.
+/* This routine should be called at the end of every connection.
+/*
+/* smtpd_sasl_is_active() is a predicate that returns true
+/* if the SMTP server session state is between smtpd_sasl_activate()
+/* and smtpd_sasl_deactivate().
+/*
+/* smtpd_sasl_set_inactive() initializes the SMTP session
+/* state before the first smtpd_sasl_activate() call.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP session context.
+/* .IP sasl_opts_name
+/* Security options parameter name.
+/* .IP sasl_opts_val
+/* Security options parameter value.
+/* .IP sasl_method
+/* A SASL mechanism name
+/* .IP init_reply
+/* An optional initial client response.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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 <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <sasl_mech_filter.h>
+#include <string_list.h>
+
+/* XSASL library. */
+
+#include <xsasl.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_sasl_glue.h"
+#include "smtpd_chat.h"
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * SASL mechanism filter.
+ */
+static STRING_LIST *smtpd_sasl_mech_filter;
+
+/*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+ /*
+ * SASL server implementation handle.
+ */
+static XSASL_SERVER_IMPL *smtpd_sasl_impl;
+
+/* smtpd_sasl_initialize - per-process initialization */
+
+void smtpd_sasl_initialize(void)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_sasl_impl)
+ msg_panic("smtpd_sasl_initialize: repeated call");
+
+ /*
+ * Initialize the SASL library.
+ */
+ if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
+ var_smtpd_sasl_path)) == 0)
+ msg_fatal("SASL per-process initialization failed");
+
+ /*
+ * Initialize the SASL mechanism filter.
+ */
+ smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
+ MATCH_FLAG_NONE,
+ var_smtpd_sasl_mech_filter);
+}
+
+/* smtpd_sasl_activate - per-connection initialization */
+
+void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
+ const char *sasl_opts_val)
+{
+ const char *mechanism_list;
+ const char *filtered_mechanism_list;
+ XSASL_SERVER_CREATE_ARGS create_args;
+ int tls_flag;
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_sasl_is_active(state))
+ msg_panic("smtpd_sasl_activate: already active");
+
+ /*
+ * Initialize SASL-specific state variables. Use long-lived storage for
+ * base 64 conversion results, rather than local variables, to avoid
+ * memory leaks when a read or write routine returns abnormally after
+ * timeout or I/O error.
+ */
+ state->sasl_reply = vstring_alloc(20);
+ state->sasl_mechanism_list = 0;
+
+ /*
+ * Set up a new server context for this connection.
+ */
+#ifdef USE_TLS
+ tls_flag = state->tls_context != 0;
+#else
+ tls_flag = 0;
+#endif
+#define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
+#define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
+
+ if ((state->sasl_server =
+ XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
+ stream = state->client,
+ addr_family = state->addr_family,
+ server_addr = ADDR_OR_EMPTY(state->dest_addr,
+ SERVER_ADDR_UNKNOWN),
+ server_port = ADDR_OR_EMPTY(state->dest_port,
+ SERVER_PORT_UNKNOWN),
+ client_addr = ADDR_OR_EMPTY(state->addr,
+ CLIENT_ADDR_UNKNOWN),
+ client_port = ADDR_OR_EMPTY(state->port,
+ CLIENT_PORT_UNKNOWN),
+ service = var_smtpd_sasl_service,
+ user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
+ security_options = sasl_opts_val,
+ tls_flag = tls_flag)) == 0)
+ msg_fatal("SASL per-connection initialization failed");
+
+ /*
+ * Get the list of authentication mechanisms.
+ */
+ if ((mechanism_list =
+ xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
+ msg_fatal("no SASL authentication mechanisms");
+ filtered_mechanism_list =
+ sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
+ if (*filtered_mechanism_list == 0)
+ msg_fatal("%s discards all mechanisms in '%s'",
+ VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
+ state->sasl_mechanism_list = mystrdup(filtered_mechanism_list);
+}
+
+/* smtpd_sasl_state_init - initialize state to allow extern authentication. */
+
+void smtpd_sasl_state_init(SMTPD_STATE *state)
+{
+ /* Initialization to support external authentication (e.g., XCLIENT). */
+ state->sasl_username = 0;
+ state->sasl_method = 0;
+ state->sasl_sender = 0;
+}
+
+/* smtpd_sasl_deactivate - per-connection cleanup */
+
+void smtpd_sasl_deactivate(SMTPD_STATE *state)
+{
+ if (state->sasl_reply) {
+ vstring_free(state->sasl_reply);
+ state->sasl_reply = 0;
+ }
+ if (state->sasl_mechanism_list) {
+ myfree(state->sasl_mechanism_list);
+ state->sasl_mechanism_list = 0;
+ }
+ if (state->sasl_username) {
+ myfree(state->sasl_username);
+ state->sasl_username = 0;
+ }
+ if (state->sasl_method) {
+ myfree(state->sasl_method);
+ state->sasl_method = 0;
+ }
+ if (state->sasl_sender) {
+ myfree(state->sasl_sender);
+ state->sasl_sender = 0;
+ }
+ if (state->sasl_server) {
+ xsasl_server_free(state->sasl_server);
+ state->sasl_server = 0;
+ }
+}
+
+/* smtpd_sasl_authenticate - per-session authentication */
+
+int smtpd_sasl_authenticate(SMTPD_STATE *state,
+ const char *sasl_method,
+ const char *init_response)
+{
+ int status;
+ const char *sasl_username;
+
+ /*
+ * SASL authentication protocol start-up. Process any initial client
+ * response that was sent along in the AUTH command.
+ */
+ for (status = xsasl_server_first(state->sasl_server, sasl_method,
+ init_response, state->sasl_reply);
+ status == XSASL_AUTH_MORE;
+ status = xsasl_server_next(state->sasl_server, STR(state->buffer),
+ state->sasl_reply)) {
+
+ /*
+ * Send a server challenge.
+ */
+ smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
+
+ /*
+ * Receive the client response. "*" means that the client gives up.
+ */
+ if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
+ smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
+ return (-1);
+ }
+ if (strcmp(STR(state->buffer), "*") == 0) {
+ msg_warn("%s: SASL %s authentication aborted",
+ state->namaddr, sasl_method);
+ smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
+ return (-1);
+ }
+ }
+ if (status != XSASL_AUTH_DONE) {
+ sasl_username = xsasl_server_get_username(state->sasl_server);
+ msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
+ state->namaddr, sasl_method, *STR(state->sasl_reply) ?
+ STR(state->sasl_reply) : "(reason unavailable)",
+ sasl_username ? sasl_username : "(unavailable)");
+ /* RFC 4954 Section 6. */
+ if (status == XSASL_AUTH_TEMP)
+ smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
+ STR(state->sasl_reply));
+ else
+ smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
+ STR(state->sasl_reply));
+ return (-1);
+ }
+ /* RFC 4954 Section 6. */
+ smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
+ if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
+ msg_panic("cannot look up the authenticated SASL username");
+ state->sasl_username = mystrdup(sasl_username);
+ printable(state->sasl_username, '?');
+ state->sasl_method = mystrdup(sasl_method);
+ printable(state->sasl_method, '?');
+
+ return (0);
+}
+
+/* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
+
+void smtpd_sasl_logout(SMTPD_STATE *state)
+{
+ if (state->sasl_username) {
+ myfree(state->sasl_username);
+ state->sasl_username = 0;
+ }
+ if (state->sasl_method) {
+ myfree(state->sasl_method);
+ state->sasl_method = 0;
+ }
+}
+
+/* smtpd_sasl_login - set login information */
+
+void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
+ const char *sasl_method)
+{
+ if (state->sasl_username)
+ myfree(state->sasl_username);
+ state->sasl_username = mystrdup(sasl_username);
+ if (state->sasl_method)
+ myfree(state->sasl_method);
+ state->sasl_method = mystrdup(sasl_method);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_sasl_glue.h b/src/smtpd/smtpd_sasl_glue.h
new file mode 100644
index 0000000..d81eec1
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_glue.h
@@ -0,0 +1,41 @@
+/*++
+/* NAME
+/* smtpd_sasl_glue 3h
+/* SUMMARY
+/* Postfix SMTP server, SASL support interface
+/* SYNOPSIS
+/* #include "smtpd_sasl_glue.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SASL protocol interface
+ */
+extern void smtpd_sasl_state_init(SMTPD_STATE *);
+extern void smtpd_sasl_initialize(void);
+extern void smtpd_sasl_activate(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_deactivate(SMTPD_STATE *);
+extern int smtpd_sasl_authenticate(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_login(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_logout(SMTPD_STATE *);
+extern int permit_sasl_auth(SMTPD_STATE *, int, int);
+
+#define smtpd_sasl_is_active(s) ((s)->sasl_server != 0)
+#define smtpd_sasl_set_inactive(s) ((void) ((s)->sasl_server = 0))
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtpd/smtpd_sasl_proto.c b/src/smtpd/smtpd_sasl_proto.c
new file mode 100644
index 0000000..476752d
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_proto.c
@@ -0,0 +1,274 @@
+/*++
+/* NAME
+/* smtpd_sasl_proto 3
+/* SUMMARY
+/* Postfix SMTP protocol support for SASL authentication
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_sasl_proto.h"
+/*
+/* int smtpd_sasl_auth_cmd(state, argc, argv)
+/* SMTPD_STATE *state;
+/* int argc;
+/* SMTPD_TOKEN *argv;
+/*
+/* void smtpd_sasl_auth_extern(state, username, method)
+/* SMTPD_STATE *state;
+/* const char *username;
+/* const char *method;
+/*
+/* void smtpd_sasl_auth_reset(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_sasl_mail_opt(state, sender)
+/* SMTPD_STATE *state;
+/* const char *sender;
+/*
+/* void smtpd_sasl_mail_reset(state)
+/* SMTPD_STATE *state;
+/*
+/* static int permit_sasl_auth(state, authenticated, unauthenticated)
+/* SMTPD_STATE *state;
+/* int authenticated;
+/* int unauthenticated;
+/* DESCRIPTION
+/* This module contains random chunks of code that implement
+/* the SMTP protocol interface for SASL negotiation. The goal
+/* is to reduce clutter of the main SMTP server source code.
+/*
+/* smtpd_sasl_auth_cmd() implements the AUTH command and updates
+/* the following state structure members:
+/* .IP sasl_method
+/* The authentication method that was successfully applied.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .IP sasl_username
+/* The username that was successfully authenticated.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .PP
+/* smtpd_sasl_auth_reset() cleans up after the AUTH command.
+/* This is required before smtpd_sasl_auth_cmd() can be used again.
+/* This may be called even if SASL authentication is turned off
+/* in main.cf.
+/*
+/* smtpd_sasl_auth_extern() records authentication information
+/* that is received from an external source.
+/* This may be called even if SASL authentication is turned off
+/* in main.cf.
+/*
+/* smtpd_sasl_mail_opt() implements the SASL-specific AUTH=sender
+/* option to the MAIL FROM command. The result is an error response
+/* in case of problems.
+/*
+/* smtpd_sasl_mail_reset() performs cleanup for the SASL-specific
+/* AUTH=sender option to the MAIL FROM command.
+/*
+/* permit_sasl_auth() permits access from an authenticated client.
+/* This test fails for clients that use anonymous authentication.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP session context.
+/* .IP argc
+/* Number of command line tokens.
+/* .IP argv
+/* The command line parsed into tokens.
+/* .IP sender
+/* Sender address from the AUTH=sender option in the MAIL FROM
+/* command.
+/* .IP authenticated
+/* Result for authenticated client.
+/* .IP unauthenticated
+/* Result for unauthenticated client.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <mail_error.h>
+#include <ehlo_mask.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_token.h"
+#include "smtpd_chat.h"
+#include "smtpd_sasl_proto.h"
+#include "smtpd_sasl_glue.h"
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_auth_cmd - process AUTH command */
+
+int smtpd_sasl_auth_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ char *auth_mechanism;
+ char *initial_response;
+ const char *err;
+
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) || !smtpd_sasl_is_active(state)
+ || (state->ehlo_discard_mask & EHLO_MASK_AUTH)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: authentication not enabled");
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) {
+ if (err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ /* Sendmail compatibility: map 4xx into 454. */
+ else if (err[0] == '4') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "454 4.3.0 Try again later");
+ return (-1);
+ }
+ }
+#ifdef USE_TLS
+ if (var_smtpd_tls_auth_only && !state->tls_context) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ /* RFC 4954, Section 4. */
+ smtpd_chat_reply(state, "504 5.5.4 Encryption required for requested authentication mechanism");
+ return (-1);
+ }
+#endif
+ if (state->sasl_username) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: already authenticated");
+ return (-1);
+ }
+ if (argc < 2 || argc > 3) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: AUTH mechanism");
+ return (-1);
+ }
+ /* Don't reuse the SASL handle after authentication failure. */
+#ifndef XSASL_TYPE_CYRUS
+#define XSASL_TYPE_CYRUS "cyrus"
+#endif
+ if (state->flags & SMTPD_FLAG_AUTH_USED) {
+ smtpd_sasl_deactivate(state);
+#ifdef USE_TLS
+ if (state->tls_context != 0)
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ else
+#endif
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+ } else if (strcmp(var_smtpd_sasl_type, XSASL_TYPE_CYRUS) == 0) {
+ state->flags |= SMTPD_FLAG_AUTH_USED;
+ }
+
+ /*
+ * All authentication failures shall be logged. The 5xx reply code from
+ * the SASL authentication routine triggers tar-pit delays, which help to
+ * slow down password guessing attacks.
+ */
+ auth_mechanism = argv[1].strval;
+ initial_response = (argc == 3 ? argv[2].strval : 0);
+ return (smtpd_sasl_authenticate(state, auth_mechanism, initial_response));
+}
+
+/* smtpd_sasl_mail_opt - SASL-specific MAIL FROM option */
+
+char *smtpd_sasl_mail_opt(SMTPD_STATE *state, const char *addr)
+{
+
+ /*
+ * Do not store raw RFC2554 protocol data.
+ */
+#if 0
+ if (state->sasl_username == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return ("503 5.5.4 Error: send AUTH command first");
+ }
+#endif
+ if (state->sasl_sender != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return ("503 5.5.4 Error: multiple AUTH= options");
+ }
+ if (strcmp(addr, "<>") != 0) {
+ state->sasl_sender = mystrdup(addr);
+ printable(state->sasl_sender, '?');
+ }
+ return (0);
+}
+
+/* smtpd_sasl_mail_reset - SASL-specific MAIL FROM cleanup */
+
+void smtpd_sasl_mail_reset(SMTPD_STATE *state)
+{
+ if (state->sasl_sender) {
+ myfree(state->sasl_sender);
+ state->sasl_sender = 0;
+ }
+}
+
+/* permit_sasl_auth - OK for authenticated connection */
+
+int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot)
+{
+ if (state->sasl_method && strcasecmp(state->sasl_method, "anonymous"))
+ return (ifyes);
+ return (ifnot);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_sasl_proto.h b/src/smtpd/smtpd_sasl_proto.h
new file mode 100644
index 0000000..d52bd4b
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_proto.h
@@ -0,0 +1,37 @@
+/*++
+/* NAME
+/* smtpd_sasl_proto 3h
+/* SUMMARY
+/* Postfix SMTP protocol support for SASL authentication
+/* SYNOPSIS
+/* #include "smtpd_sasl_proto.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SMTP protocol interface.
+ */
+extern int smtpd_sasl_auth_cmd(SMTPD_STATE *, int, SMTPD_TOKEN *);
+extern void smtpd_sasl_auth_reset(SMTPD_STATE *);
+extern char *smtpd_sasl_mail_opt(SMTPD_STATE *, const char *);
+extern void smtpd_sasl_mail_reset(SMTPD_STATE *);
+
+#define smtpd_sasl_auth_extern smtpd_sasl_login
+#define smtpd_sasl_auth_reset smtpd_sasl_logout
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtpd/smtpd_server.in b/src/smtpd/smtpd_server.in
new file mode 100644
index 0000000..691b8c6
--- /dev/null
+++ b/src/smtpd/smtpd_server.in
@@ -0,0 +1,56 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+relay_domains porcupine.org
+client spike.porcupine.org 168.100.3.2
+#
+# Check MX access
+#
+helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_mx_access,inline:{168.100.3.2=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_mx_access,inline:{168.100.3.2=reject}
+rcpt foo@www.porcupine.org
+rcpt foo@example.tld
+rcpt foo@postfix.org
+#
+# Check NS access
+#
+helo_restrictions check_helo_ns_access,inline:{168.100.3.75=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_ns_access,inline:{168.100.3.75=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_ns_access,inline:{168.100.3.75=reject}
+rcpt foo@www.porcupine.org
+rcpt foo@example.tld
+rcpt foo@postfix.org
+#
+# Check A access
+#
+helo_restrictions check_helo_a_access,inline:{168.100.3.2=reject}
+helo spike.porcupine.org
+helo www.porcupine.org
+client_restrictions check_client_a_access,inline:{168.100.3.2=reject}
+client spike.porcupine.org 1.2.3.4
+client www.porcupine.org 1.2.3.4
+reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.3.2=reject}
+client spike.porcupine.org 1.2.3.4
+client www.porcupine.org 1.2.3.4
+sender_restrictions check_sender_a_access,inline:{168.100.3.2=reject}
+mail foo@spike.porcupine.org
+mail foo@www.porcupine.org
+recipient_restrictions check_recipient_a_access,inline:{168.100.3.2=reject}
+rcpt foo@spike.porcupine.org
+rcpt foo@www.porcupine.org
diff --git a/src/smtpd/smtpd_server.ref b/src/smtpd/smtpd_server.ref
new file mode 100644
index 0000000..b396a57
--- /dev/null
+++ b/src/smtpd/smtpd_server.ref
@@ -0,0 +1,118 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> relay_domains porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> #
+>>> # Check MX access
+>>> #
+>>> helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied
+>>> helo example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Helo command example.tld: hostname nor servname provided, or not known
+OK
+>>> helo foo@postfix.org
+OK
+>>> sender_restrictions check_sender_mx_access,inline:{168.100.3.2=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied
+>>> mail example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Sender address example.tld: hostname nor servname provided, or not known
+OK
+>>> mail foo@postfix.org
+OK
+>>> recipient_restrictions check_recipient_mx_access,inline:{168.100.3.2=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Recipient address foo@example.tld: hostname nor servname provided, or not known
+OK
+>>> rcpt foo@postfix.org
+OK
+>>> #
+>>> # Check NS access
+>>> #
+>>> helo_restrictions check_helo_ns_access,inline:{168.100.3.75=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied
+>>> helo example.tld
+./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+OK
+>>> helo foo@postfix.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied
+>>> sender_restrictions check_sender_ns_access,inline:{168.100.3.75=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied
+>>> mail example.tld
+./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+OK
+>>> mail foo@postfix.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied
+>>> recipient_restrictions check_recipient_ns_access,inline:{168.100.3.75=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@example.tld
+./smtpd_check: warning: Unable to look up NS host for foo@example.tld: Host not found
+OK
+>>> rcpt foo@postfix.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied
+>>> #
+>>> # Check A access
+>>> #
+>>> helo_restrictions check_helo_a_access,inline:{168.100.3.2=reject}
+OK
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied
+>>> helo www.porcupine.org
+OK
+>>> client_restrictions check_client_a_access,inline:{168.100.3.2=reject}
+OK
+>>> client spike.porcupine.org 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied
+>>> client www.porcupine.org 1.2.3.4
+OK
+>>> reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.3.2=reject}
+bad command
+>>> client spike.porcupine.org 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied
+>>> client www.porcupine.org 1.2.3.4
+OK
+>>> sender_restrictions check_sender_a_access,inline:{168.100.3.2=reject}
+OK
+>>> mail foo@spike.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied; from=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied
+>>> mail foo@www.porcupine.org
+OK
+>>> recipient_restrictions check_recipient_a_access,inline:{168.100.3.2=reject}
+OK
+>>> rcpt foo@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied; from=<foo@www.porcupine.org> to=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@www.porcupine.org
+OK
diff --git a/src/smtpd/smtpd_state.c b/src/smtpd/smtpd_state.c
new file mode 100644
index 0000000..f2f5f89
--- /dev/null
+++ b/src/smtpd/smtpd_state.c
@@ -0,0 +1,248 @@
+/*++
+/* NAME
+/* smtpd_state 3
+/* SUMMARY
+/* Postfix SMTP server
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_state_init(state, stream, service)
+/* SMTPD_STATE *state;
+/* VSTREAM *stream;
+/* const char *service;
+/*
+/* void smtpd_state_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_state_init() initializes session context.
+/*
+/* smtpd_state_reset() cleans up session context.
+/*
+/* Arguments:
+/* .IP state
+/* Session context.
+/* .IP stream
+/* Stream connected to peer. The stream is not copied.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <mail_error.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_chat.h"
+#include "smtpd_sasl_glue.h"
+
+/* smtpd_state_init - initialize after connection establishment */
+
+void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
+ const char *service)
+{
+
+ /*
+ * Initialize the state information for this connection, and fill in the
+ * connection-specific fields.
+ */
+ state->flags = 0;
+ state->err = CLEANUP_STAT_OK;
+ state->client = stream;
+ state->service = mystrdup(service);
+ state->buffer = vstring_alloc(100);
+ state->addr_buf = vstring_alloc(100);
+ state->conn_count = state->conn_rate = 0;
+ state->error_count = 0;
+ state->error_mask = 0;
+ state->notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ state->helo_name = 0;
+ state->queue_id = 0;
+ state->cleanup = 0;
+ state->dest = 0;
+ state->rcpt_count = 0;
+ state->access_denied = 0;
+ state->history = 0;
+ state->reason = 0;
+ state->sender = 0;
+ state->verp_delims = 0;
+ state->recipient = 0;
+ state->etrn_name = 0;
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ state->where = SMTPD_AFTER_CONNECT;
+ state->recursion = 0;
+ state->msg_size = 0;
+ state->act_size = 0;
+ state->junk_cmds = 0;
+ state->rcpt_overshoot = 0;
+ state->defer_if_permit_client = 0;
+ state->defer_if_permit_helo = 0;
+ state->defer_if_permit_sender = 0;
+ state->defer_if_reject.dsn = 0;
+ state->defer_if_reject.reason = 0;
+ state->defer_if_permit.dsn = 0;
+ state->defer_if_permit.reason = 0;
+ state->discard = 0;
+ state->expand_buf = 0;
+ state->prepend = 0;
+ state->proxy = 0;
+ state->proxy_mail = 0;
+ state->saved_filter = 0;
+ state->saved_redirect = 0;
+ state->saved_bcc = 0;
+ state->saved_flags = 0;
+#ifdef DELAY_ACTION
+ state->saved_delay = 0;
+#endif
+ state->instance = vstring_alloc(10);
+ state->seqno = 0;
+ state->rewrite_context = 0;
+#if 0
+ state->ehlo_discard_mask = ~0;
+#else
+ state->ehlo_discard_mask = 0;
+#endif
+ state->dsn_envid = 0;
+ state->dsn_buf = vstring_alloc(100);
+ state->dsn_orcpt_buf = vstring_alloc(100);
+#ifdef USE_TLS
+#ifdef USE_TLSPROXY
+ state->tlsproxy = 0;
+#endif
+ state->tls_context = 0;
+#endif
+
+ /*
+ * Minimal initialization to support external authentication (e.g.,
+ * XCLIENT) without having to enable SASL in main.cf.
+ */
+#ifdef USE_SASL_AUTH
+ if (SMTPD_STAND_ALONE(state))
+ var_smtpd_sasl_enable = 0;
+ smtpd_sasl_set_inactive(state);
+ smtpd_sasl_state_init(state);
+#endif
+
+ state->milter_argv = 0;
+ state->milter_argc = 0;
+ state->milters = 0;
+
+ /*
+ * Initialize peer information.
+ */
+ smtpd_peer_init(state);
+
+ /*
+ * Initialize xforward information.
+ */
+ smtpd_xforward_init(state);
+
+ /*
+ * Initialize the conversation history.
+ */
+ smtpd_chat_reset(state);
+
+ state->ehlo_argv = 0;
+ state->ehlo_buf = 0;
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_NONE;
+ state->bdat_get_stream = 0;
+ state->bdat_get_buffer = 0;
+}
+
+/* smtpd_state_reset - cleanup after disconnect */
+
+void smtpd_state_reset(SMTPD_STATE *state)
+{
+
+ /*
+ * When cleaning up, touch only those fields that smtpd_state_init()
+ * filled in. The other fields are taken care of by their own
+ * "destructor" functions.
+ */
+ if (state->service)
+ myfree(state->service);
+ if (state->buffer)
+ vstring_free(state->buffer);
+ if (state->addr_buf)
+ vstring_free(state->addr_buf);
+ if (state->access_denied)
+ myfree(state->access_denied);
+ if (state->protocol)
+ myfree(state->protocol);
+ smtpd_peer_reset(state);
+
+ /*
+ * Buffers that are created on the fly and that may be shared among mail
+ * deliveries within the same SMTP session.
+ */
+ if (state->defer_if_permit.dsn)
+ vstring_free(state->defer_if_permit.dsn);
+ if (state->defer_if_permit.reason)
+ vstring_free(state->defer_if_permit.reason);
+ if (state->defer_if_reject.dsn)
+ vstring_free(state->defer_if_reject.dsn);
+ if (state->defer_if_reject.reason)
+ vstring_free(state->defer_if_reject.reason);
+ if (state->expand_buf)
+ vstring_free(state->expand_buf);
+ if (state->instance)
+ vstring_free(state->instance);
+ if (state->dsn_buf)
+ vstring_free(state->dsn_buf);
+ if (state->dsn_orcpt_buf)
+ vstring_free(state->dsn_orcpt_buf);
+#if (defined(USE_TLS) && defined(USE_TLSPROXY))
+ if (state->tlsproxy) /* still open after longjmp */
+ vstream_fclose(state->tlsproxy);
+#endif
+
+ /*
+ * BDAT.
+ */
+ if (state->bdat_get_stream)
+ (void) vstream_fclose(state->bdat_get_stream);
+ if (state->bdat_get_buffer)
+ vstring_free(state->bdat_get_buffer);
+}
diff --git a/src/smtpd/smtpd_token.c b/src/smtpd/smtpd_token.c
new file mode 100644
index 0000000..927088f
--- /dev/null
+++ b/src/smtpd/smtpd_token.c
@@ -0,0 +1,233 @@
+/*++
+/* NAME
+/* smtpd_token 3
+/* SUMMARY
+/* tokenize SMTPD command
+/* SYNOPSIS
+/* #include <smtpd_token.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int tokval;
+/* char *strval;
+/* /* other stuff... */
+/* .in -4
+/* } SMTPD_TOKEN;
+/*
+/* int smtpd_token(str, argvp)
+/* char *str;
+/* SMTPD_TOKEN **argvp;
+/* DESCRIPTION
+/* smtpd_token() routine converts the string in \fIstr\fR to an
+/* array of tokens in \fIargvp\fR. The number of tokens is returned
+/* via the function return value.
+/*
+/* Token types:
+/* .IP SMTPD_TOK_OTHER
+/* The token is something else.
+/* .IP SMTPD_TOK_ERROR
+/* A malformed token.
+/* BUGS
+/* This tokenizer understands just enough to tokenize SMTPD commands.
+/* It understands backslash escapes, white space, quoted strings,
+/* and addresses (including quoted text) enclosed by < and >.
+/* The input is broken up into tokens by whitespace, except for
+/* whitespace that is protected by quotes etc.
+/* 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 <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <mvect.h>
+
+/* Application-specific. */
+
+#include "smtpd_token.h"
+
+/* smtp_quoted - read until closing quote */
+
+static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last)
+{
+ static VSTRING *stack;
+ int wanted;
+ int c;
+
+ /*
+ * Parser stack. `ch' is always the most-recently entered character.
+ */
+#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch);
+#define LEAVE_CHAR(buf, ch) { \
+ vstring_truncate(buf, VSTRING_LEN(buf) - 1); \
+ ch = vstring_end(buf)[-1]; \
+ }
+
+ if (stack == 0)
+ stack = vstring_alloc(1);
+ VSTRING_RESET(stack);
+ ENTER_CHAR(stack, wanted = last);
+
+ VSTRING_ADDCH(arg->vstrval, start);
+ for (;;) {
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ VSTRING_ADDCH(arg->vstrval, c);
+ if (c == '\\') { /* parse escape sequence */
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ VSTRING_ADDCH(arg->vstrval, c);
+ } else if (c == wanted) { /* closing quote etc. */
+ if (VSTRING_LEN(stack) == 1)
+ return (cp);
+ LEAVE_CHAR(stack, wanted);
+ } else if (c == '"') {
+ ENTER_CHAR(stack, wanted = '"'); /* highest precedence */
+ } else if (c == '<' && wanted == '>') {
+ ENTER_CHAR(stack, wanted = '>'); /* lowest precedence */
+ }
+ }
+ arg->tokval = SMTPD_TOK_ERROR; /* missing end */
+ return (cp);
+}
+
+/* smtp_next_token - extract next token from input, update cp */
+
+static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg)
+{
+ int c;
+
+ VSTRING_RESET(arg->vstrval);
+ arg->tokval = SMTPD_TOK_OTHER;
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x,y,l) (strncasecmp((x), (y), (l)) == 0)
+
+ for (;;) {
+ if ((c = *cp) == 0) /* end of input */
+ break;
+ cp++;
+ if (ISSPACE(c)) { /* whitespace, skip */
+ while (*cp && ISSPACE(*cp))
+ cp++;
+ if (LEN(arg->vstrval) > 0) /* end of token */
+ break;
+ } else if (c == '<') { /* <stuff> */
+ cp = smtp_quoted(cp, arg, c, '>');
+ } else if (c == '"') { /* "stuff" */
+ cp = smtp_quoted(cp, arg, c, c);
+ } else if (c == ':') { /* this is gross, but... */
+ VSTRING_ADDCH(arg->vstrval, c);
+ if (STREQ(STR(arg->vstrval), "to:", LEN(arg->vstrval))
+ || STREQ(STR(arg->vstrval), "from:", LEN(arg->vstrval)))
+ break;
+ } else { /* other */
+ if (c == '\\') {
+ VSTRING_ADDCH(arg->vstrval, c);
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ }
+ VSTRING_ADDCH(arg->vstrval, c);
+ }
+ }
+ if (LEN(arg->vstrval) <= 0) /* no token found */
+ return (0);
+ VSTRING_TERMINATE(arg->vstrval);
+ arg->strval = vstring_str(arg->vstrval);
+ return (cp);
+}
+
+/* smtpd_token_init - initialize token structures */
+
+static void smtpd_token_init(char *ptr, ssize_t count)
+{
+ SMTPD_TOKEN *arg;
+ int n;
+
+ for (arg = (SMTPD_TOKEN *) ptr, n = 0; n < count; arg++, n++)
+ arg->vstrval = vstring_alloc(10);
+}
+
+/* smtpd_token - tokenize SMTPD command */
+
+int smtpd_token(char *cp, SMTPD_TOKEN **argvp)
+{
+ static SMTPD_TOKEN *smtp_argv;
+ static MVECT mvect;
+ int n;
+
+ if (smtp_argv == 0)
+ smtp_argv = (SMTPD_TOKEN *) mvect_alloc(&mvect, sizeof(*smtp_argv), 1,
+ smtpd_token_init, (MVECT_FN) 0);
+ for (n = 0; /* void */ ; n++) {
+ smtp_argv = (SMTPD_TOKEN *) mvect_realloc(&mvect, n + 1);
+ if ((cp = smtp_next_token(cp, smtp_argv + n)) == 0)
+ break;
+ }
+ *argvp = smtp_argv;
+ return (n);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program for the SMTPD command tokenizer.
+ */
+
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *vp = vstring_alloc(10);
+ int tok_argc;
+ SMTPD_TOKEN *tok_argv;
+ int i;
+
+ for (;;) {
+ if (isatty(STDIN_FILENO))
+ vstream_printf("enter SMTPD command: ");
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(vp, VSTREAM_IN) == VSTREAM_EOF)
+ break;
+ if (*vstring_str(vp) == '#')
+ continue;
+ if (!isatty(STDIN_FILENO))
+ vstream_printf("%s\n", vstring_str(vp));
+ tok_argc = smtpd_token(vstring_str(vp), &tok_argv);
+ for (i = 0; i < tok_argc; i++) {
+ vstream_printf("Token type: %s\n",
+ tok_argv[i].tokval == SMTPD_TOK_OTHER ? "other" :
+ tok_argv[i].tokval == SMTPD_TOK_ERROR ? "error" :
+ "unknown");
+ vstream_printf("Token value: %s\n", tok_argv[i].strval);
+ }
+ }
+ vstring_free(vp);
+ exit(0);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_token.h b/src/smtpd/smtpd_token.h
new file mode 100644
index 0000000..88489fa
--- /dev/null
+++ b/src/smtpd/smtpd_token.h
@@ -0,0 +1,40 @@
+/*++
+/* NAME
+/* smtpd_token 3h
+/* SUMMARY
+/* tokenize SMTPD command
+/* SYNOPSIS
+/* #include <smtpd_token.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct SMTPD_TOKEN {
+ int tokval;
+ char *strval;
+ VSTRING *vstrval;
+} SMTPD_TOKEN;
+
+#define SMTPD_TOK_OTHER 0
+#define SMTPD_TOK_ADDR 1
+#define SMTPD_TOK_ERROR 2
+
+extern int smtpd_token(char *, SMTPD_TOKEN **);
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_token.in b/src/smtpd/smtpd_token.in
new file mode 100644
index 0000000..d67d5d0
--- /dev/null
+++ b/src/smtpd/smtpd_token.in
@@ -0,0 +1,12 @@
+mail from:<wietse@porcupine.org>
+mail from:<"wietse venema"@porcupine.org>
+mail from:wietse@porcupine.org
+mail from:<wietse @ porcupine.org>
+mail from:<"wietse venema"@porcupine.org ("wietse ) venema")>
+mail from:<"wietse venema" <wietse@porcupine.org>>
+mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+mail from:"wietse venema"@porcupine.org
+mail from:wietse\ venema@porcupine.org
+mail to:<"wietse venema>
+mail to:<wietse@[stuff>
+mail to:<wietse@["stuff]>
diff --git a/src/smtpd/smtpd_token.ref b/src/smtpd/smtpd_token.ref
new file mode 100644
index 0000000..21dc969
--- /dev/null
+++ b/src/smtpd/smtpd_token.ref
@@ -0,0 +1,84 @@
+mail from:<wietse@porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <wietse@porcupine.org>
+mail from:<"wietse venema"@porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org>
+mail from:wietse@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: wietse@porcupine.org
+mail from:<wietse @ porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <wietse @ porcupine.org>
+mail from:<"wietse venema"@porcupine.org ("wietse ) venema")>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org ("wietse ) venema")>
+mail from:<"wietse venema" <wietse@porcupine.org>>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema" <wietse@porcupine.org>>
+mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+mail from:"wietse venema"@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: "wietse venema"@porcupine.org
+mail from:wietse\ venema@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: wietse\ venema@porcupine.org
+mail to:<"wietse venema>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <"wietse venema>
+mail to:<wietse@[stuff>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: other
+Token value: <wietse@[stuff>
+mail to:<wietse@["stuff]>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <wietse@["stuff]>
diff --git a/src/smtpd/smtpd_xforward.c b/src/smtpd/smtpd_xforward.c
new file mode 100644
index 0000000..053d377
--- /dev/null
+++ b/src/smtpd/smtpd_xforward.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* smtpd_xforward 3
+/* SUMMARY
+/* maintain XCLIENT information
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_xforward_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_xforward_preset(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_xforward_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_xforward_init() zeroes the attributes for storage of
+/* XFORWARD command parameters.
+/*
+/* smtpd_xforward_preset() takes the result from smtpd_xforward_init()
+/* and sets all fields to the same "unknown" value that regular
+/* client attributes would have.
+/*
+/* smtpd_xforward_reset() restores the state from smtpd_xforward_init().
+/* 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 <mymalloc.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+
+/* smtpd_xforward_init - initialize xforward attributes */
+
+void smtpd_xforward_init(SMTPD_STATE *state)
+{
+ state->xforward.flags = 0;
+ state->xforward.name = 0;
+ state->xforward.addr = 0;
+ state->xforward.port = 0;
+ state->xforward.namaddr = 0;
+ state->xforward.protocol = 0;
+ state->xforward.helo_name = 0;
+ state->xforward.ident = 0;
+ state->xforward.domain = 0;
+}
+
+/* smtpd_xforward_preset - set xforward attributes to "unknown" */
+
+void smtpd_xforward_preset(SMTPD_STATE *state)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (state->xforward.flags)
+ msg_panic("smtpd_xforward_preset: bad flags: 0x%x",
+ state->xforward.flags);
+
+ /*
+ * This is a temporary solution. Unknown forwarded attributes get the
+ * same values as unknown normal attributes, so that we don't break
+ * assumptions in pre-existing code.
+ */
+ state->xforward.flags = SMTPD_STATE_XFORWARD_INIT;
+ state->xforward.name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->xforward.addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->xforward.port = mystrdup(CLIENT_PORT_UNKNOWN);
+ state->xforward.namaddr = mystrdup(CLIENT_NAMADDR_UNKNOWN);
+ state->xforward.rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ /* Leave helo at zero. */
+ state->xforward.protocol = mystrdup(CLIENT_PROTO_UNKNOWN);
+ /* Leave ident at zero. */
+ /* Leave domain context at zero. */
+}
+
+/* smtpd_xforward_reset - reset xforward attributes */
+
+void smtpd_xforward_reset(SMTPD_STATE *state)
+{
+#define FREE_AND_WIPE(s) { if (s) myfree(s); s = 0; }
+
+ state->xforward.flags = 0;
+ FREE_AND_WIPE(state->xforward.name);
+ FREE_AND_WIPE(state->xforward.addr);
+ FREE_AND_WIPE(state->xforward.port);
+ FREE_AND_WIPE(state->xforward.namaddr);
+ FREE_AND_WIPE(state->xforward.rfc_addr);
+ FREE_AND_WIPE(state->xforward.protocol);
+ FREE_AND_WIPE(state->xforward.helo_name);
+ FREE_AND_WIPE(state->xforward.ident);
+ FREE_AND_WIPE(state->xforward.domain);
+}
diff --git a/src/smtpstone/.indent.pro b/src/smtpstone/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtpstone/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtpstone/.printfck b/src/smtpstone/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtpstone/.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/smtpstone/Makefile.in b/src/smtpstone/Makefile.in
new file mode 100644
index 0000000..f86e019
--- /dev/null
+++ b/src/smtpstone/Makefile.in
@@ -0,0 +1,172 @@
+SHELL = /bin/sh
+SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c
+OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+INC_DIR = ../../include
+PROG = smtp-source smtp-sink qmqp-source qmqp-sink
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+smtp-sink: smtp-sink.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-sink.o $(LIBS) $(SYSLIBS)
+
+smtp-source: smtp-source.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-source.o $(LIBS) $(SYSLIBS)
+
+qmqp-sink: qmqp-sink.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS)
+
+qmqp-source: qmqp-source.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source \
+ ../../bin/qmqp-sink
+
+../../bin/smtp-source: smtp-source
+ cp $? $@
+
+../../bin/smtp-sink: smtp-sink
+ cp $? $@
+
+../../bin/qmqp-source: qmqp-source
+ cp $? $@
+
+../../bin/qmqp-sink: qmqp-sink
+ cp $? $@
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+qmqp-sink.o: ../../include/check_arg.h
+qmqp-sink.o: ../../include/events.h
+qmqp-sink.o: ../../include/htable.h
+qmqp-sink.o: ../../include/inet_proto.h
+qmqp-sink.o: ../../include/iostuff.h
+qmqp-sink.o: ../../include/listen.h
+qmqp-sink.o: ../../include/mail_version.h
+qmqp-sink.o: ../../include/msg.h
+qmqp-sink.o: ../../include/msg_vstream.h
+qmqp-sink.o: ../../include/mymalloc.h
+qmqp-sink.o: ../../include/netstring.h
+qmqp-sink.o: ../../include/qmqp_proto.h
+qmqp-sink.o: ../../include/sys_defs.h
+qmqp-sink.o: ../../include/vbuf.h
+qmqp-sink.o: ../../include/vstream.h
+qmqp-sink.o: ../../include/vstring.h
+qmqp-sink.o: qmqp-sink.c
+qmqp-source.o: ../../include/check_arg.h
+qmqp-source.o: ../../include/connect.h
+qmqp-source.o: ../../include/events.h
+qmqp-source.o: ../../include/get_hostname.h
+qmqp-source.o: ../../include/host_port.h
+qmqp-source.o: ../../include/inet_proto.h
+qmqp-source.o: ../../include/iostuff.h
+qmqp-source.o: ../../include/mail_date.h
+qmqp-source.o: ../../include/mail_version.h
+qmqp-source.o: ../../include/msg.h
+qmqp-source.o: ../../include/msg_vstream.h
+qmqp-source.o: ../../include/myaddrinfo.h
+qmqp-source.o: ../../include/mymalloc.h
+qmqp-source.o: ../../include/netstring.h
+qmqp-source.o: ../../include/qmqp_proto.h
+qmqp-source.o: ../../include/sane_connect.h
+qmqp-source.o: ../../include/split_at.h
+qmqp-source.o: ../../include/sys_defs.h
+qmqp-source.o: ../../include/valid_hostname.h
+qmqp-source.o: ../../include/valid_mailhost_addr.h
+qmqp-source.o: ../../include/vbuf.h
+qmqp-source.o: ../../include/vstream.h
+qmqp-source.o: ../../include/vstring.h
+qmqp-source.o: qmqp-source.c
+smtp-sink.o: ../../include/check_arg.h
+smtp-sink.o: ../../include/chroot_uid.h
+smtp-sink.o: ../../include/events.h
+smtp-sink.o: ../../include/get_hostname.h
+smtp-sink.o: ../../include/htable.h
+smtp-sink.o: ../../include/inet_proto.h
+smtp-sink.o: ../../include/iostuff.h
+smtp-sink.o: ../../include/listen.h
+smtp-sink.o: ../../include/mail_date.h
+smtp-sink.o: ../../include/mail_version.h
+smtp-sink.o: ../../include/make_dirs.h
+smtp-sink.o: ../../include/msg.h
+smtp-sink.o: ../../include/msg_vstream.h
+smtp-sink.o: ../../include/myaddrinfo.h
+smtp-sink.o: ../../include/mymalloc.h
+smtp-sink.o: ../../include/myrand.h
+smtp-sink.o: ../../include/sane_accept.h
+smtp-sink.o: ../../include/smtp_stream.h
+smtp-sink.o: ../../include/stringops.h
+smtp-sink.o: ../../include/sys_defs.h
+smtp-sink.o: ../../include/vbuf.h
+smtp-sink.o: ../../include/vstream.h
+smtp-sink.o: ../../include/vstring.h
+smtp-sink.o: ../../include/vstring_vstream.h
+smtp-sink.o: smtp-sink.c
+smtp-source.o: ../../include/check_arg.h
+smtp-source.o: ../../include/compat_va_copy.h
+smtp-source.o: ../../include/connect.h
+smtp-source.o: ../../include/events.h
+smtp-source.o: ../../include/get_hostname.h
+smtp-source.o: ../../include/host_port.h
+smtp-source.o: ../../include/inet_proto.h
+smtp-source.o: ../../include/iostuff.h
+smtp-source.o: ../../include/mail_date.h
+smtp-source.o: ../../include/mail_version.h
+smtp-source.o: ../../include/msg.h
+smtp-source.o: ../../include/msg_vstream.h
+smtp-source.o: ../../include/myaddrinfo.h
+smtp-source.o: ../../include/mymalloc.h
+smtp-source.o: ../../include/sane_connect.h
+smtp-source.o: ../../include/smtp_stream.h
+smtp-source.o: ../../include/split_at.h
+smtp-source.o: ../../include/sys_defs.h
+smtp-source.o: ../../include/valid_hostname.h
+smtp-source.o: ../../include/valid_mailhost_addr.h
+smtp-source.o: ../../include/vbuf.h
+smtp-source.o: ../../include/vstream.h
+smtp-source.o: ../../include/vstring.h
+smtp-source.o: ../../include/vstring_vstream.h
+smtp-source.o: smtp-source.c
diff --git a/src/smtpstone/hashed-deferred b/src/smtpstone/hashed-deferred
new file mode 100644
index 0000000..93f0e12
--- /dev/null
+++ b/src/smtpstone/hashed-deferred
@@ -0,0 +1,37 @@
+Delivering 1000 deferred messages over the loopback transport,
+outbound concurrency 10. smtp-sink pipelining disabled. Machine is
+P230, BSD/OS 3.1, 64MB memory.
+
+hashing is 16 directories per level
+
+flat deferred queue
+
+ start: Sun Feb 21 16:42:37 EST 1999
+ done: Feb 21 16:44:35
+ time: 1:58 = 118 seconds
+
+ start: Sun Feb 21 16:48:01 EST 1999
+ done: Feb 21 16:49:51
+ time: 1:50 = 110 seconds
+
+hashed deferred queue, depth=1 (16 directories)
+
+ start: Sun Feb 21 17:29:36 EST 1999
+ done: Feb 21 17:31:32
+ time: 1:56 = 116 seconds
+
+ start: Sun Feb 21 17:33:36 EST 1999
+ done: Feb 21 17:35:24
+ time: 1:48 = 108 seconds
+
+ start: Sun Feb 21 17:37:08 EST 1999
+ done: Feb 21 17:39:02
+ time: 1:52 = 112 seconds
+
+Hashing does not slow down deliveries.
+
+However the problem is scanning an empty deferred queue. On an idle
+machine, it takes some 5 seconds to scan an empty depth=2 deferred
+queue unless the blocks happen to be cached. During those 5 seconds
+the queue manager will not pay attention to I/O from delivery
+agents, which is bad.
diff --git a/src/smtpstone/hashed-incoming b/src/smtpstone/hashed-incoming
new file mode 100644
index 0000000..7bb1993
--- /dev/null
+++ b/src/smtpstone/hashed-incoming
@@ -0,0 +1,48 @@
+Sending 1000 messages through postfix over the loopback transport,
+inbound concurrency 10. smtp-sink pipelining disabled. Machine is
+P230, BSD/OS 3.1
+
+accepted = time for postfix to accept all messages
+moved = number of messages delivered by postfix when all mail accepted
+done = time for postfix to deliver last message
+
+hashing is 16 directories per level
+
+flat incoming queue
+
+ start: Sun Feb 21 15:12:44 EST 1999
+ accepted: 66.10 real 0.50 user 1.56 sys
+ moved: 122
+ done: Feb 21 15:15:19
+
+ total time: 2:35 = 155 seconds
+
+ start: Sun Feb 21 15:30:44 EST 1999
+ accepted: 67.47 real 0.48 user 1.60 sys
+ moved:
+ done: Feb 21 15:33:10
+
+ total time: 2:26 = 146 seconds
+
+hashed incoming queue, depth=1 (16 subdirs)
+
+ start: Sun Feb 21 15:39:43 EST 1999
+ accepted: 84.42 real 0.49 user 1.66 sys
+ moved: 144
+ done: Feb 21 15:42:27
+
+ total time: 2:44 = 164 seconds
+
+ start: Sun Feb 21 15:42:43 EST 1999
+ accepted: 84.57 real 0.46 user 1.67 sys
+ moved:
+ done: Feb 21 15:45:34
+
+ total time: 2:51 = 171 seconds
+
+ start: Sun Feb 21 15:47:11 EST 1999
+ accepted: 84.11 real 0.34 user 1.72 sys
+ moved:
+ done: Feb 21 15:49:54
+
+ total time: 2:43 = 163 seconds
diff --git a/src/smtpstone/mx-deliver b/src/smtpstone/mx-deliver
new file mode 100644
index 0000000..a4f44a0
--- /dev/null
+++ b/src/smtpstone/mx-deliver
@@ -0,0 +1,20 @@
+MX needs 24 seconds to deliver 100 SMTP messages to one local user.
+smtpd/local-delivery process limit = 100
+
+/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@fist.porcupine.org fist
+ 10.47 real 0.12 user 0.16 sys
+Jun 8 14:45:25 fist mx:smtpd[19432]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:45:49 fist mx:local[19444]: 085788: to=<wietse@porcupine.org>, relay=l
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse@fist.porcupine.org fist
+ 9.10 real 0.06 user 0.26 sys
+Jun 8 14:46:42 fist mx:smtpd[19443]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:47:06 fist mx:local[19446]: 085792: to=<wietse@porcupine.org>, relay=l
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@fist.porcupine.org fist
+ 9.84 real 0.09 user 0.28 sys
+Jun 8 14:48:03 fist mx:smtpd[19458]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:48:28 fist mx:local[19479]: 085795: to=<wietse@porcupine.org>, relay=l
+Total time: 25 seconds
diff --git a/src/smtpstone/mx-explode b/src/smtpstone/mx-explode
new file mode 100644
index 0000000..e997147
--- /dev/null
+++ b/src/smtpstone/mx-explode
@@ -0,0 +1,33 @@
+MX needs 12 seconds per 1000 different remote destinations.
+smtp process limit = 100, bundle_recipients = 0.
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.13 real 0.07 user 0.27 sys
+Jun 8 13:32:18 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1
+Jun 8 13:32:31 fist mx:smtp[18209]: 085688: to=<544foo@spike.porcupine.org>, re
+Total time: 13 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 2.55 real 0.21 user 0.48 sys
+Jun 8 13:33:23 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1
+Jun 8 13:33:48 fist mx:smtp[18184]: 085693: to=<1041foo@spike.porcupine.org>, r
+Total time: 25 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+[test generating machine ran out of resources while receiving mail]
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.38 real 0.17 user 0.16 sys
+Jun 8 15:20:33 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1
+Jun 8 15:20:46 fist mx:smtp[27724]: 085687: to=<493foo@spike.porcupine.org>, re
+Total time: 13 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 2.64 real 0.23 user 0.46 sys
+Jun 8 15:20:52 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1
+Jun 8 15:21:16 fist mx:smtp[27743]: 085687: to=<1086foo@spike.porcupine.org>, r
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+[test generating machine ran out of resources while receiving mail]
+
diff --git a/src/smtpstone/mx-relay b/src/smtpstone/mx-relay
new file mode 100644
index 0000000..a5930cb
--- /dev/null
+++ b/src/smtpstone/mx-relay
@@ -0,0 +1,20 @@
+MX needs 19 seconds to relay 100 messages with one recipient.
+smtp/smtpd process limit = 100, bundle_recipients = 0.
+
+/usr/bin/time ./smtp-source -s 5 -m 100 fist
+ 9.56 real 0.07 user 0.20 sys
+Jun 8 14:33:19 fist mx:smtpd[19366]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:33:36 fist mx:smtp[19382]: 085781: to=<foo@spike.porcupine.org>, relay
+Total time: 17 seconds
+
+/usr/bin/time ./smtp-source -s 10 -m 100 fist
+ 8.95 real 0.12 user 0.19 sys
+Jun 8 14:34:22 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:34:41 fist mx:smtp[19378]: 085792: to=<foo@spike.porcupine.org>, relay
+Total time: 19 seconds
+
+/usr/bin/time ./smtp-source -s 20 -m 100 fist
+ 9.79 real 0.11 user 0.27 sys
+Jun 8 14:35:18 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:35:38 fist mx:smtp[19382]: 085794: to=<foo@spike.porcupine.org>, relay
+Total time: 20 seconds
diff --git a/src/smtpstone/performance b/src/smtpstone/performance
new file mode 100644
index 0000000..805676f
--- /dev/null
+++ b/src/smtpstone/performance
@@ -0,0 +1,28 @@
+List performance: time to forward one SMTP message with 1000, 2000
+and 5000 different remote destinations. Outbound SMTP concurrency
+= 100.
+
+dests 1000 2000 5000 time per 1000
+=============================================
+qmail 15 32 80 16
+mx 13 25 (*) 13
+
+(*) message sink host saturated under the load
+
+Local delivery performance: time to deliver 100 SMTP messages to
+one recipient. Outbound SMTP concurrency = 100, inbound SMTP
+concurrency = 5, 10, 20.
+
+concur 5 10 20 average time
+============================================
+qmail 62 59 58 60
+mx 24 24 25 24
+
+Relay performance: time to forward 100 SMTP messages with one
+recipient. Outbound SMTP concurrency = 100, inbound SMTP concurrency
+= 5, 10, 20.
+
+concur 5 10 20 average time
+============================================
+qmail 56 54 54 55
+mx 17 19 20 19
diff --git a/src/smtpstone/qmail-deliver b/src/smtpstone/qmail-deliver
new file mode 100644
index 0000000..e00ab39
--- /dev/null
+++ b/src/smtpstone/qmail-deliver
@@ -0,0 +1,20 @@
+Qmail needs 59 seconds to deliver 100 SMTP messages to one local recipient.
+Default configuration, concurrencyremote = 100.
+
+/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse fist
+ 41.45 real 0.07 user 0.21 sys
+Jun 8 12:17:20 fist qmail: 865786640.494072 new msg 39901
+Jun 8 12:18:22 fist qmail: 865786702.301087 end msg 39982
+Total time: 62 sec
+
+/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse fist
+ 26.50 real 0.10 user 0.22 sys
+Jun 8 12:14:49 fist qmail: 865786489.089492 new msg 39901
+Jun 8 12:15:48 fist qmail: 865786548.316898 end msg 39928
+Total time: 59 sec
+
+/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse fist
+ 21.15 real 0.08 user 0.30 sys
+Jun 8 12:19:18 fist qmail: 865786758.939755 new msg 39903
+Jun 8 12:20:16 fist qmail: 865786816.739912 end msg 40031
+Total time: 58 sec
diff --git a/src/smtpstone/qmail-explode b/src/smtpstone/qmail-explode
new file mode 100644
index 0000000..ed40e2c
--- /dev/null
+++ b/src/smtpstone/qmail-explode
@@ -0,0 +1,20 @@
+Qmail needs 16 seconds per 1000 destinations.
+Default configuration, concurrencyremote = 100.
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.16 real 0.09 user 0.24 sys
+Jun 8 14:57:17 fist qmail: 865796237.334359 new msg 39906
+Jun 8 14:57:32 fist qmail: 865796252.632756 delivery 2154: success: 168.100.189
+Total time: 15 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 1.99 real 0.23 user 0.45 sys
+Jun 8 14:58:11 fist qmail: 865796291.817523 new msg 39907
+Jun 8 14:58:43 fist qmail: 865796323.174117 delivery 4116: success: 168.100.189
+Total time: 32 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+ 4.63 real 0.58 user 1.10 sys
+Jun 8 14:59:23 fist qmail: 865796363.346735 new msg 39908
+Jun 8 15:00:43 fist qmail: 865796443.209168 delivery 9153: success: 168.100.189
+Total time: 80 seconds
diff --git a/src/smtpstone/qmail-relay b/src/smtpstone/qmail-relay
new file mode 100644
index 0000000..8718e5e
--- /dev/null
+++ b/src/smtpstone/qmail-relay
@@ -0,0 +1,20 @@
+Qmail needs 54 seconds to relay 100 messages with one recipient.
+Default configuration, concurrencyremote = 100.
+
+spike_4% /usr/bin/time ./smtp-source -s 5 -m 100 fist
+ 43.77 real 0.05 user 0.23 sys
+Jun 8 12:08:36 fist qmail: 865786116.744366 new msg 39901
+Jun 8 12:09:32 fist qmail: 865786172.791473 end msg 39921
+Total time: 56 sec
+
+/usr/bin/time ./smtp-source -s 10 -m 100 fist
+ 26.66 real 0.06 user 0.26 sys
+Jun 8 12:06:20 fist qmail: 865785980.185885 new msg 39901
+Jun 8 12:07:14 fist qmail: 865786034.306429 end msg 39920
+Total time: 54 sec
+
+spike_8% /usr/bin/time ./smtp-source -s 20 -m 100 fist
+ 20.94 real 0.11 user 0.27 sys
+Jun 8 12:10:52 fist qmail: 865786252.412648 new msg 39901
+Jun 8 12:11:46 fist qmail: 865786306.080605 end msg 39962
+Total time: 54 sec
diff --git a/src/smtpstone/qmqp-sink.c b/src/smtpstone/qmqp-sink.c
new file mode 100644
index 0000000..2fcbdd1
--- /dev/null
+++ b/src/smtpstone/qmqp-sink.c
@@ -0,0 +1,325 @@
+/*++
+/* NAME
+/* qmqp-sink 1
+/* SUMMARY
+/* parallelized QMQP test server
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR]
+/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR
+/*
+/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR]
+/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR
+/* DESCRIPTION
+/* \fBqmqp-sink\fR listens on the named host (or address) and port.
+/* It receives messages from the network and throws them away.
+/* The purpose is to measure QMQP client performance, not protocol
+/* compliance.
+/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
+/* UNIX-domain sockets.
+/* IPv4 and IPv6 are the default.
+/* This program is the complement of the \fBqmqp-source\fR(1) program.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Support IPv4 only. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Support IPv6 only. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-c\fR
+/* Display a running counter that is updated whenever a delivery
+/* is completed.
+/* .IP \fB-v\fR
+/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP
+/* conversation.
+/* .IP "\fB-x \fItime\fR"
+/* Terminate after \fItime\fR seconds. This is to facilitate memory
+/* leak testing.
+/* SEE ALSO
+/* qmqp-source(1), QMQP message generator
+/* 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>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <signal.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <netstring.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+typedef struct {
+ VSTREAM *stream; /* client connection */
+ int count; /* bytes to go */
+} SINK_STATE;
+
+static int var_tmout;
+static VSTRING *buffer;
+static void disconnect(SINK_STATE *);
+static int count_deliveries;
+static int counter;
+
+/* send_reply - finish conversation */
+
+static void send_reply(SINK_STATE *state)
+{
+ vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK);
+ NETSTRING_PUT_BUF(state->stream, buffer);
+ netstring_fflush(state->stream);
+ if (count_deliveries) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ disconnect(state);
+}
+
+/* read_data - read over-all netstring data */
+
+static void read_data(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+ int fd = vstream_fileno(state->stream);
+ int count;
+
+ /*
+ * Refill the VSTREAM buffer, if necessary.
+ */
+ if (VSTREAM_GETC(state->stream) == VSTREAM_EOF)
+ netstring_except(state->stream, vstream_ftimeout(state->stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ state->count--;
+
+ /*
+ * Flush the VSTREAM buffer. As documented, vstream_fseek() discards
+ * unread input.
+ */
+ if ((count = vstream_peek(state->stream)) > 0) {
+ state->count -= count;
+ if (state->count <= 0) {
+ send_reply(state);
+ return;
+ }
+ vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH);
+ }
+
+ /*
+ * Do not block while waiting for the arrival of more data.
+ */
+ event_disable_readwrite(fd);
+ event_enable_read(fd, read_data, context);
+}
+
+/* read_length - read over-all netstring length */
+
+static void read_length(int event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown error reading input");
+
+ case NETSTRING_ERR_TIME:
+ msg_panic("attempt to read non-readable socket");
+ /* NOTREACHED */
+
+ case NETSTRING_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_FORMAT:
+ msg_warn("netstring format error");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_SIZE:
+ msg_warn("netstring size error");
+ disconnect(state);
+ return;
+
+ /*
+ * Include the netstring terminator in the read byte count. This
+ * violates abstractions.
+ */
+ case 0:
+ state->count = netstring_get_length(state->stream) + 1;
+ read_data(event, context);
+ return;
+ }
+}
+
+/* disconnect - handle disconnection events */
+
+static void disconnect(SINK_STATE *state)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ myfree((void *) state);
+}
+
+/* connect_event - handle connection events */
+
+static void connect_event(int unused_event, void *context)
+{
+ int sock = CAST_ANY_PTR_TO_INT(context);
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SINK_STATE *state;
+ int fd;
+
+ if ((fd = accept(sock, sa, &len)) >= 0) {
+ if (msg_verbose)
+ msg_info("connect (%s)",
+#ifdef AF_LOCAL
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
+#else
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ "unknown protocol family");
+ non_blocking(fd, NON_BLOCKING);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ state->stream = vstream_fdopen(fd, O_RDWR);
+ vstream_tweak_sock(state->stream);
+ netstring_setup(state->stream, var_tmout);
+ event_enable_read(fd, read_length, (void *) state);
+ }
+}
+
+/* terminate - voluntary exit */
+
+static void terminate(int unused_event, void *unused_context)
+{
+ exit(0);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int sock;
+ int backlog;
+ int ch;
+ int ttl;
+ const char *protocols = INET_PROTO_NAME_ALL;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Fix 20051207.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Initialize diagnostics.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'c':
+ count_deliveries++;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ if ((ttl = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ event_request_timer(terminate, (void *) 0, ttl);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 2)
+ usage(argv[0]);
+ if ((backlog = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ buffer = vstring_alloc(1024);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ sock = inet_listen(argv[optind], backlog, BLOCKING);
+ }
+
+ /*
+ * Start the event handler.
+ */
+ event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock));
+ for (;;)
+ event_loop(-1);
+}
diff --git a/src/smtpstone/qmqp-source.c b/src/smtpstone/qmqp-source.c
new file mode 100644
index 0000000..9231aec
--- /dev/null
+++ b/src/smtpstone/qmqp-source.c
@@ -0,0 +1,673 @@
+/*++
+/* NAME
+/* qmqp-source 1
+/* SUMMARY
+/* parallelized QMQP test generator
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/*
+/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBqmqp-source\fR connects to the named host and TCP port (default 628)
+/* and sends one or more messages to it, either sequentially
+/* or in parallel. The program speaks the QMQP protocol.
+/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
+/* IPv4 and IPv6 are the default.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Connect to the server with IPv4. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Connect to the server with IPv6. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-c\fR
+/* Display a running counter that is incremented each time
+/* a delivery completes.
+/* .IP "\fB-C \fIcount\fR"
+/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times
+/* before giving up. The default count is 1. Specify a larger count in
+/* order to work around a problem with TCP/IP stacks that send RESET
+/* when the listen queue is full.
+/* .IP "\fB-f \fIfrom\fR"
+/* Use the specified sender address (default: <foo@myhostname>).
+/* .IP "\fB-l \fIlength\fR"
+/* Send \fIlength\fR bytes as message payload. The length
+/* includes the message headers.
+/* .IP "\fB-m \fImessage_count\fR"
+/* Send the specified number of messages (default: 1).
+/* .IP "\fB-M \fImyhostname\fR"
+/* Use the specified hostname or [address] in the default
+/* sender and recipient addresses, instead of the machine
+/* hostname.
+/* .IP "\fB-r \fIrecipient_count\fR"
+/* Send the specified number of recipients per transaction (default: 1).
+/* Recipient names are generated by prepending a number to the
+/* recipient address.
+/* .IP "\fB-s \fIsession_count\fR"
+/* Run the specified number of QMQP sessions in parallel (default: 1).
+/* .IP "\fB-t \fIto\fR"
+/* Use the specified recipient address (default: <foo@myhostname>).
+/* .IP "\fB-R \fIinterval\fR"
+/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP \fB-v\fR
+/* Make the program more verbose, for debugging purposes.
+/* .IP "\fB-w \fIinterval\fR"
+/* Wait a fixed time between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* SEE ALSO
+/* qmqp-sink(1), QMQP message dump
+/* 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>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <netstring.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+
+/* Global library. */
+
+#include <mail_date.h>
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same QMQP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct SESSION {
+ int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
+ int rcpt_count; /* # of recipients to go */
+ VSTREAM *stream; /* open connection */
+ int connect_count; /* # of connect()s to retry */
+ struct SESSION *next; /* connect() queue linkage */
+} SESSION;
+
+static SESSION *last_session; /* connect() queue tail */
+
+static VSTRING *buffer;
+static int var_line_limit = 10240;
+static int var_timeout = 300;
+static const char *var_myhostname;
+static int session_count;
+static int message_count = 1;
+static struct sockaddr_storage ss;
+
+#undef sun
+static struct sockaddr_un sun;
+static struct sockaddr *sa;
+static int sa_length;
+static int recipients = 1;
+static char *defaddr;
+static char *recipient;
+static char *sender;
+static int message_length = 1024;
+static int count = 0;
+static int counter = 0;
+static int connect_count = 1;
+static int random_delay = 0;
+static int fixed_delay = 0;
+static const char *mydate;
+static int mypid;
+
+static void enqueue_connect(SESSION *);
+static void start_connect(SESSION *);
+static void connect_done(int, void *);
+
+static void send_data(SESSION *);
+static void receive_reply(int, void *);
+
+static VSTRING *message_buffer;
+static VSTRING *sender_buffer;
+static VSTRING *recipient_buffer;
+
+/* Silly little macros. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* random_interval - generate a random value in 0 .. (small) interval */
+
+static int random_interval(int interval)
+{
+ return (rand() % (interval + 1));
+}
+
+/* socket_error - look up and reset the last socket error */
+
+static int socket_error(int sock)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Some Solaris 2 versions have getsockopt() itself return the error,
+ * instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
+
+/* exception_text - translate exceptions from the netstring module */
+
+static char *exception_text(int except)
+{
+ ;
+
+ switch (except) {
+ case NETSTRING_ERR_EOF:
+ return ("lost connection");
+ case NETSTRING_ERR_TIME:
+ return ("timeout");
+ case NETSTRING_ERR_FORMAT:
+ return ("netstring format error");
+ case NETSTRING_ERR_SIZE:
+ return ("netstring size exceeds limit");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+ /* NOTREACHED */
+}
+
+/* startup - connect to server but do not wait */
+
+static void startup(SESSION *session)
+{
+ if (message_count-- <= 0) {
+ myfree((void *) session);
+ session_count--;
+ return;
+ }
+ enqueue_connect(session);
+}
+
+/* start_event - invoke startup from timer context */
+
+static void start_event(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ startup(session);
+}
+
+/* start_another - start another session */
+
+static void start_another(SESSION *session)
+{
+ if (random_delay > 0) {
+ event_request_timer(start_event, (void *) session,
+ random_interval(random_delay));
+ } else if (fixed_delay > 0) {
+ event_request_timer(start_event, (void *) session, fixed_delay);
+ } else {
+ startup(session);
+ }
+}
+
+/* enqueue_connect - queue a connection request */
+
+static void enqueue_connect(SESSION *session)
+{
+ session->next = 0;
+ if (last_session == 0) {
+ last_session = session;
+ start_connect(session);
+ } else {
+ last_session->next = session;
+ last_session = session;
+ }
+}
+
+/* dequeue_connect - connection request completed */
+
+static void dequeue_connect(SESSION *session)
+{
+ if (session == last_session) {
+ if (session->next != 0)
+ msg_panic("dequeue_connect: queue ends after last");
+ last_session = 0;
+ } else {
+ if (session->next == 0)
+ msg_panic("dequeue_connect: queue ends before last");
+ start_connect(session->next);
+ }
+}
+
+/* fail_connect - handle failed startup */
+
+static void fail_connect(SESSION *session)
+{
+ if (session->connect_count-- == 1)
+ msg_fatal("connect: %m");
+ msg_warn("connect: %m");
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+#ifdef MISSING_USLEEP
+ doze(10);
+#else
+ usleep(10);
+#endif
+ start_connect(session);
+}
+
+/* start_connect - start TCP handshake */
+
+static void start_connect(SESSION *session)
+{
+ int fd;
+ struct linger linger;
+
+ /*
+ * Some systems don't set the socket error when connect() fails early
+ * (loopback) so we must deal with the error immediately, rather than
+ * retrieving it later with getsockopt(). We can't use MSG_PEEK to
+ * distinguish between server disconnect and connection refused.
+ */
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ (void) non_blocking(fd, NON_BLOCKING);
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger,
+ sizeof(linger)) < 0)
+ msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
+ session->stream = vstream_fdopen(fd, O_RDWR);
+ event_enable_write(fd, connect_done, (void *) session);
+ netstring_setup(session->stream, var_timeout);
+ if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
+ fail_connect(session);
+}
+
+/* connect_done - send message sender info */
+
+static void connect_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int fd = vstream_fileno(session->stream);
+
+ /*
+ * Try again after some delay when the connection failed, in case they
+ * run a Mickey Mouse protocol stack.
+ */
+ if (socket_error(fd) < 0) {
+ fail_connect(session);
+ } else {
+ dequeue_connect(session);
+ non_blocking(fd, BLOCKING);
+ event_disable_readwrite(fd);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )
+ vstream_tweak_tcp(session->stream);
+ send_data(session);
+ }
+}
+
+/* send_data - send message+sender+recipients */
+
+static void send_data(SESSION *session)
+{
+ int fd = vstream_fileno(session->stream);
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+
+ /*
+ * Send the message content, by wrapping three netstrings into an
+ * over-all netstring.
+ *
+ * XXX This should be done more carefully to avoid blocking when sending
+ * large messages over slow networks.
+ */
+ netstring_put_multi(session->stream,
+ STR(message_buffer), LEN(message_buffer),
+ STR(sender_buffer), LEN(sender_buffer),
+ STR(recipient_buffer), LEN(recipient_buffer),
+ (char *) 0);
+ netstring_fflush(session->stream);
+
+ /*
+ * Wake me up when the server replies or when something bad happens.
+ */
+ event_enable_read(fd, receive_reply, (void *) session);
+}
+
+/* receive_reply - read server reply */
+
+static void receive_reply(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while receiving server reply", exception_text(except));
+
+ /*
+ * Receive and process the server reply.
+ */
+ netstring_get(session->stream, buffer, var_line_limit);
+ if (msg_verbose)
+ vstream_printf("<< %.*s\n", (int) LEN(buffer), STR(buffer));
+ if (STR(buffer)[0] != QMQP_STAT_OK)
+ msg_fatal("%s error: %.*s",
+ STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" :
+ STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" :
+ "unknown", (int) LEN(buffer) - 1, STR(buffer) + 1);
+
+ /*
+ * Update the optional running counter.
+ */
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Finish this session. QMQP sends only one message per session.
+ */
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s -cv -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -R delay -w delay host[:port]", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - parse JCL and start the machine */
+
+int main(int argc, char **argv)
+{
+ SESSION *session;
+ char *host;
+ char *port;
+ char *path;
+ int path_len;
+ int sessions = 1;
+ int ch;
+ ssize_t len;
+ int n;
+ int i;
+ char *buf;
+ const char *parse_err;
+ struct addrinfo *res;
+ int aierr;
+ const char *protocols = INET_PROTO_NAME_ALL;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ signal(SIGPIPE, SIG_IGN);
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'c':
+ count++;
+ break;
+ case 'C':
+ if ((connect_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'l':
+ if ((message_length = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'm':
+ if ((message_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'M':
+ if (*optarg == '[') {
+ if (!valid_mailhost_literal(optarg, DO_GRIPE))
+ msg_fatal("bad address literal: %s", optarg);
+ } else {
+ if (!valid_hostname(optarg, DO_GRIPE))
+ msg_fatal("bad hostname: %s", optarg);
+ }
+ var_myhostname = optarg;
+ break;
+ case 'r':
+ if ((recipients = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'R':
+ if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 's':
+ if ((sessions = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 't':
+ recipient = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 1)
+ usage(argv[0]);
+
+ if (random_delay > 0)
+ srand(getpid());
+
+ /*
+ * Translate endpoint address to internal form.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ path = argv[optind] + 5;
+ path_len = strlen(path);
+ if (path_len >= (int) sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len);
+ sa = (struct sockaddr *) &sun;
+ sa_length = sizeof(sun);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ buf = mystrdup(argv[optind]);
+ if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0)
+ msg_fatal("%s: %s", argv[optind], parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr));
+ myfree(buf);
+ sa = (struct sockaddr *) &ss;
+ if (res->ai_addrlen > sizeof(ss))
+ msg_fatal("address length %d > buffer length %d",
+ (int) res->ai_addrlen, (int) sizeof(ss));
+ memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
+ sa_length = res->ai_addrlen;
+#ifdef HAS_SA_LEN
+ sa->sa_len = sa_length;
+#endif
+ freeaddrinfo(res);
+ }
+
+ /*
+ * Allocate space for temporary buffer.
+ */
+ buffer = vstring_alloc(100);
+
+ /*
+ * Make sure we have sender and recipient addresses.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = get_hostname();
+ if (sender == 0 || recipient == 0) {
+ vstring_sprintf(buffer, "foo@%s", var_myhostname);
+ defaddr = mystrdup(vstring_str(buffer));
+ if (sender == 0)
+ sender = defaddr;
+ if (recipient == 0)
+ recipient = defaddr;
+ }
+
+ /*
+ * Prepare some results that may be used multiple times: the message
+ * content netstring, the sender netstring, and the recipient netstrings.
+ */
+ mydate = mail_date(time((time_t *) 0));
+ mypid = getpid();
+
+ message_buffer = vstring_alloc(message_length + 200);
+ vstring_sprintf(buffer,
+ "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n",
+ sender, recipient, mydate, mypid, var_myhostname);
+ for (n = 1; LEN(buffer) < message_length; n++) {
+ for (i = 0; i < n && i < 79; i++)
+ VSTRING_ADDCH(buffer, 'X');
+ VSTRING_ADDCH(buffer, '\n');
+ }
+ STR(buffer)[message_length - 1] = '\n';
+ netstring_memcpy(message_buffer, STR(buffer), message_length);
+
+ len = strlen(sender);
+ sender_buffer = vstring_alloc(len);
+ netstring_memcpy(sender_buffer, sender, len);
+
+ if (recipients == 1) {
+ len = strlen(recipient);
+ recipient_buffer = vstring_alloc(len);
+ netstring_memcpy(recipient_buffer, recipient, len);
+ } else {
+ recipient_buffer = vstring_alloc(100);
+ for (n = 0; n < recipients; n++) {
+ vstring_sprintf(buffer, "%d%s", n, recipient);
+ netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer));
+ }
+ }
+
+ /*
+ * Start sessions.
+ */
+ while (sessions-- > 0) {
+ session = (SESSION *) mymalloc(sizeof(*session));
+ session->stream = 0;
+ session->xfer_count = 0;
+ session->connect_count = connect_count;
+ session->next = 0;
+ session_count++;
+ startup(session);
+ }
+ for (;;) {
+ event_loop(-1);
+ if (session_count <= 0 && message_count <= 0) {
+ if (count) {
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+ }
+ }
+}
diff --git a/src/smtpstone/smtp-sink.c b/src/smtpstone/smtp-sink.c
new file mode 100644
index 0000000..c34f21d
--- /dev/null
+++ b/src/smtpstone/smtp-sink.c
@@ -0,0 +1,1643 @@
+/*++
+/* NAME
+/* smtp-sink 1
+/* SUMMARY
+/* parallelized SMTP/LMTP test server
+/* SYNOPSIS
+/* .fi
+/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR
+/* \fIbacklog\fR
+/*
+/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR
+/* DESCRIPTION
+/* \fBsmtp-sink\fR listens on the named host (or address) and port.
+/* It takes SMTP messages from the network and throws them away.
+/* The purpose is to measure client performance, not protocol
+/* compliance.
+/*
+/* \fBsmtp-sink\fR may also be configured to capture each mail
+/* delivery transaction to file. Since disk latencies are large
+/* compared to network delays, this mode of operation can
+/* reduce the maximal performance by several orders of magnitude.
+/*
+/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
+/* UNIX-domain sockets.
+/* IPv4 and IPv6 are the default.
+/* This program is the complement of the \fBsmtp-source\fR(1) program.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Support IPv4 only. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Support IPv6 only. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-8\fR
+/* Do not announce 8BITMIME support.
+/* .IP \fB-a\fR
+/* Do not announce SASL authentication support.
+/* .IP "\fB-A \fIdelay\fR"
+/* Wait \fIdelay\fR seconds after responding to DATA, then
+/* abort prematurely with a 550 reply status. Do not read
+/* further input from the client; this is an attempt to block
+/* the client before it sends ".". Specify a zero delay value
+/* to abort immediately.
+/* .IP "\fB-b \fIsoft-bounce-reply\fR"
+/* Use \fIsoft-bounce-reply\fR for soft reject responses. The
+/* default reply is "450 4.3.0 Error: command failed".
+/* .IP "\fB-B \fIhard-bounce-reply\fR"
+/* Use \fIhard-bounce-reply\fR for hard reject responses. The
+/* default reply is "500 5.3.0 Error: command failed".
+/* .IP \fB-c\fR
+/* Display running counters that are updated whenever an SMTP
+/* session ends, a QUIT command is executed, or when "." is
+/* received.
+/* .IP \fB-C\fR
+/* Disable XCLIENT support.
+/* .IP "\fB-d \fIdump-template\fR"
+/* Dump each mail transaction to a single-message file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3) and appending a pseudo-random hexadecimal number
+/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
+/* .IP "\fB-D \fIdump-template\fR"
+/* Append mail transactions to a multi-message dump file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3).
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
+/* .IP \fB-e\fR
+/* Do not announce ESMTP support.
+/* .IP \fB-E\fR
+/* Do not announce ENHANCEDSTATUSCODES support.
+/* .IP "\fB-f \fIcommand,command,...\fR"
+/* Reject the specified commands with a hard (5xx) error code.
+/* This option implies \fB-p\fR.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP \fB-F\fR
+/* Disable XFORWARD support.
+/* .IP "\fB-h\fI hostname\fR"
+/* Use \fIhostname\fR in the SMTP greeting, in the HELO response,
+/* and in the EHLO response. The default hostname is "smtp-sink".
+/* .IP "\fB-H\fI delay\fR"
+/* Delay the first read operation after receiving DATA (time
+/* in seconds). Combine with a large test message and a small
+/* TCP window size (see the \fB-T\fR option) to test the Postfix
+/* client write_wait() implementation.
+/* .IP \fB-L\fR
+/* Enable LMTP instead of SMTP.
+/* .IP "\fB-m \fIcount\fR (default: 256)"
+/* An upper bound on the maximal number of simultaneous
+/* connections that \fBsmtp-sink\fR will handle. This prevents
+/* the process from running out of file descriptors. Excess
+/* connections will stay queued in the TCP/IP stack.
+/* .IP "\fB-M \fIcount\fR"
+/* Terminate after receiving \fIcount\fR messages.
+/* .IP "\fB-n \fIcount\fR"
+/* Terminate after \fIcount\fR sessions.
+/* .IP \fB-N\fR
+/* Do not announce support for DSN.
+/* .IP \fB-p\fR
+/* Do not announce support for ESMTP command pipelining.
+/* .IP \fB-P\fR
+/* Change the server greeting so that it appears to come through
+/* a CISCO PIX system. Implies \fB-e\fR.
+/* .IP "\fB-q \fIcommand,command,...\fR"
+/* Disconnect (without replying) after receiving one of the
+/* specified commands.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-Q \fIcommand,command,...\fR"
+/* Send a 421 reply and disconnect after receiving one
+/* of the specified commands.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-r \fIcommand,command,...\fR"
+/* Reject the specified commands with a soft (4xx) error code.
+/* This option implies \fB-p\fR.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-R \fIroot-directory\fR"
+/* Change the process root directory to the specified location.
+/* This option requires super-user privileges. See also the
+/* \fB-u\fR option.
+/* .IP "\fB-s \fIcommand,command,...\fR"
+/* Log the named commands to syslogd.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-S start-string\fR"
+/* An optional string that is prepended to each message that is
+/* written to a dump file (see the dump file format description
+/* below). The following C escape sequences are supported: \ea
+/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er
+/* (carriage return), \et (horizontal tab), \ev (vertical tab),
+/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash
+/* character).
+/* .IP "\fB-t \fItimeout\fR (default: 100)"
+/* Limit the time for receiving a command or sending a response.
+/* The time limit is specified in seconds.
+/* .IP "\fB-T \fIwindowsize\fR"
+/* Override the default TCP window size. To work around
+/* broken TCP window scaling implementations, specify a
+/* value > 0 and < 65536.
+/* .IP "\fB-u \fIusername\fR"
+/* Switch to the specified user privileges after opening the
+/* network socket and optionally changing the process root
+/* directory. This option is required when the process runs
+/* with super-user privileges. See also the \fB-R\fR option.
+/* .IP \fB-v\fR
+/* Show the SMTP conversations.
+/* .IP "\fB-w \fIdelay\fR"
+/* Wait \fIdelay\fR seconds before responding to a DATA command.
+/* .IP "\fB-W \fIcommand:delay[:odds]\fR"
+/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR.
+/* If \fIodds\fR is also specified (a number between 1-99
+/* inclusive), wait for a random multiple of \fIdelay\fR. The
+/* random multiplier is equal to the number of times the program
+/* needs to roll a dice with a range of 0..99 inclusive, before
+/* the dice produces a result greater than or equal to \fIodds\fR.
+/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR
+/* Listen on network interface \fIhost\fR (default: any interface)
+/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be
+/* specified in numeric or symbolic form.
+/* .IP \fBunix:\fR\fIpathname\fR
+/* Listen on the UNIX-domain socket at \fIpathname\fR.
+/* .IP \fIbacklog\fR
+/* The maximum length of the queue of pending connections,
+/* as defined by the \fBlisten\fR(2) system call.
+/* DUMP FILE FORMAT
+/* .ad
+/* .fi
+/* Each dumped message contains a sequence of text lines,
+/* terminated with the newline character. The sequence of
+/* information is as follows:
+/* .IP \(bu
+/* The optional string specified with the \fB-S\fR option.
+/* .IP \(bu
+/* The \fBsmtp-sink\fR generated headers as documented below.
+/* .IP \(bu
+/* The message header and body as received from the SMTP client.
+/* .IP \(bu
+/* An empty line.
+/* .PP
+/* The format of the \fBsmtp-sink\fR generated headers is as
+/* follows:
+/* .IP "\fBX-Client-Addr: \fItext\fR"
+/* The client IP address without enclosing []. An IPv6 address
+/* is prefixed with "ipv6:". This record is always present.
+/* .IP "\fBX-Client-Proto: \fItext\fR"
+/* The client protocol: SMTP, ESMTP or LMTP. This record is
+/* always present.
+/* .IP "\fBX-Helo-Args: \fItext\fR"
+/* The arguments of the last HELO or EHLO command before this
+/* mail delivery transaction. This record is present only if
+/* the client sent a recognizable HELO or EHLO command before
+/* the DATA command.
+/* .IP "\fBX-Mail-Args: \fItext\fR"
+/* The arguments of the MAIL command that started this mail
+/* delivery transaction. This record is present exactly once.
+/* .IP "\fBX-Rcpt-Args: \fItext\fR"
+/* The arguments of an RCPT command within this mail delivery
+/* transaction. There is one record for each RCPT command, and
+/* they are in the order as sent by the client.
+/* .IP "\fBReceived: \fItext\fR"
+/* A message header for compatibility with mail processing
+/* software. This three-line header marks the end of the headers
+/* provided by \fBsmtp-sink\fR, and is formatted as follows:
+/* .RS
+/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])"
+/* The HELO or EHLO command argument and client IP address.
+/* If the client did not send HELO or EHLO, the client IP
+/* address is used instead.
+/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
+/* The hostname specified with the \fB-h\fR option, the client
+/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
+/* portion of the per-message capture file name.
+/* .IP \fItime-stamp\fR
+/* A time stamp as defined in RFC 2822.
+/* .RE
+/* SEE ALSO
+/* smtp-source(1), SMTP/LMTP message generator
+/* 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>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <get_hostname.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <make_dirs.h>
+#include <myrand.h>
+#include <chroot_uid.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+typedef struct SINK_STATE {
+ VSTREAM *stream;
+ VSTRING *buffer;
+ int data_state;
+ int (*read_fn) (struct SINK_STATE *);
+ int in_mail;
+ int rcpts;
+ char *push_back_ptr;
+ /* Capture file information for fake Received: header */
+ MAI_HOSTADDR_STR client_addr; /* IP address */
+ char *addr_prefix; /* ipv6: or empty */
+ char *helo_args; /* text after HELO or EHLO */
+ const char *client_proto; /* SMTP, ESMTP, LMTP */
+ time_t start_time; /* MAIL command time */
+ int id; /* pseudo-random */
+ VSTREAM *dump_file; /* dump file or null */
+ void (*delayed_response) (struct SINK_STATE *state, const char *);
+ char *delayed_args;
+} SINK_STATE;
+
+#define ST_ANY 0
+#define ST_CR 1
+#define ST_CR_LF 2
+#define ST_CR_LF_DOT 3
+#define ST_CR_LF_DOT_CR 4
+#define ST_CR_LF_DOT_CR_LF 5
+
+#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0)
+#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++)
+#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text))
+
+#ifndef DEF_MAX_CLIENT_COUNT
+#define DEF_MAX_CLIENT_COUNT 256
+#endif
+
+#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed"
+#define HARD_ERROR_RESP "500 5.3.0 Error: command failed"
+
+ /*
+ * We can't rely on vstream auto-flushing, so we have to prepare for the
+ * next read request.
+ */
+#define SMTP_FLUSH(fp) do { \
+ if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \
+ smtp_flush(fp); \
+ } while (0)
+
+static int var_tmout = 100;
+static int var_max_line_length = 2048;
+static char *var_myhostname;
+static char *soft_error_resp = SOFT_ERROR_RESP;
+static char *hard_error_resp = HARD_ERROR_RESP;
+static int command_read(SINK_STATE *);
+static int data_read(SINK_STATE *);
+static void disconnect(SINK_STATE *);
+static void read_timeout(int, void *);
+static void read_event(int, void *);
+static int show_count;
+static int sess_count;
+static int quit_count;
+static int mesg_count;
+static int max_quit_count;
+static int max_msg_quit_count;
+static int disable_pipelining;
+static int disable_8bitmime;
+static int disable_esmtp;
+static int enable_lmtp;
+static int pretend_pix;
+static int disable_saslauth;
+static int disable_xclient;
+static int disable_xforward;
+static int disable_enh_status;
+static int disable_dsn;
+static int max_client_count = DEF_MAX_CLIENT_COUNT;
+static int client_count;
+static int sock;
+static int abort_delay = -1;
+static int data_read_delay = 0;
+
+static char *single_template; /* individual template */
+static char *shared_template; /* shared template */
+static VSTRING *start_string; /* dump content prefix */
+
+static const INET_PROTO_INFO *proto_info;
+
+#define STR(x) vstring_str(x)
+
+/* do_stats - show counters */
+
+static void do_stats(void)
+{
+ vstream_printf("sess=%d quit=%d mesg=%d\r",
+ sess_count, quit_count, mesg_count);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+/* hard_err_resp - generic hard error response */
+
+static void hard_err_resp(SINK_STATE *state)
+{
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ SMTP_FLUSH(state->stream);
+}
+
+/* soft_err_resp - generic soft error response */
+
+static void soft_err_resp(SINK_STATE *state)
+{
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ SMTP_FLUSH(state->stream);
+}
+
+/* exp_path_template - expand template pathname, static result */
+
+static VSTRING *exp_path_template(const char *template, time_t start_time)
+{
+ static VSTRING *path_buf = 0;
+ struct tm *lt;
+
+ if (path_buf == 0)
+ path_buf = vstring_alloc(100);
+ else
+ VSTRING_RESET(path_buf);
+ lt = localtime(&start_time);
+ while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0)
+ VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100);
+ VSTRING_SKIP(path_buf);
+ return (path_buf);
+}
+
+/* make_parent_dir - create parent directory or bust */
+
+static void make_parent_dir(const char *path, mode_t mode)
+{
+ const char *parent;
+
+ parent = sane_dirname((VSTRING *) 0, path);
+ if (make_dirs(parent, mode) < 0)
+ msg_fatal("mkdir %s: %m", parent);
+}
+
+/* mail_file_open - open mail capture file */
+
+static void mail_file_open(SINK_STATE *state)
+{
+ const char *myname = "mail_file_open";
+ VSTRING *path_buf;
+ ssize_t len;
+ int tries = 0;
+
+ /*
+ * Save the start time for later.
+ */
+ time(&(state->start_time));
+
+ /*
+ * Expand the per-message dumpfile pathname template.
+ */
+ path_buf = exp_path_template(single_template, state->start_time);
+
+ /*
+ * Append a random hexadecimal string to the pathname and create a new
+ * file. Retry with a different path if the file already exists. Create
+ * intermediate directories on the fly when the template specifies
+ * multiple pathname segments.
+ */
+#define ID_FORMAT "%08x"
+
+ for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) {
+ if (++tries > 100)
+ msg_fatal("%s: something is looping", myname);
+ state->id = myrand();
+ vstring_sprintf_append(path_buf, ID_FORMAT, state->id);
+ if ((state->dump_file = vstream_fopen(STR(path_buf),
+ O_RDWR | O_CREAT | O_EXCL,
+ 0644)) != 0) {
+ break;
+ } else if (errno == EEXIST) {
+ continue;
+ } else if (errno == ENOENT) {
+ make_parent_dir(STR(path_buf), 0755);
+ continue;
+ } else {
+ msg_fatal("open %s: %m", STR(path_buf));
+ }
+ }
+
+ /*
+ * Don't leave temporary files behind.
+ */
+ if (shared_template != 0 && unlink(STR(path_buf)) < 0)
+ msg_fatal("unlink %s: %m", STR(path_buf));
+
+ /*
+ * Do initial header records.
+ */
+ if (start_string)
+ vstream_fprintf(state->dump_file, "%s", STR(start_string));
+ vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n",
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto);
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args);
+ /* Note: there may be more than one recipient. */
+}
+
+/* mail_file_finish_header - do final smtp-sink generated header records */
+
+static void mail_file_finish_header(SINK_STATE *state)
+{
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n",
+ state->helo_args, state->addr_prefix,
+ state->client_addr.buf);
+ else
+ vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n",
+ state->addr_prefix, state->client_addr.buf,
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)"
+ " with %s id " ID_FORMAT ";\n",
+ var_myhostname, state->client_proto, state->id);
+ vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time));
+}
+
+/* mail_file_cleanup - common cleanup for capture file */
+
+static void mail_file_cleanup(SINK_STATE *state)
+{
+ (void) vstream_fclose(state->dump_file);
+ state->dump_file = 0;
+}
+
+/* mail_file_finish - handle message completion for capture file */
+
+static void mail_file_finish(SINK_STATE *state)
+{
+
+ /*
+ * Optionally append the captured message to a shared dumpfile.
+ */
+ if (shared_template) {
+ const char *out_path;
+ VSTREAM *out_fp;
+ ssize_t count;
+
+ /*
+ * Expand the shared dumpfile pathname template.
+ */
+ out_path = STR(exp_path_template(shared_template, state->start_time));
+
+ /*
+ * Open the shared dump file.
+ */
+#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
+#define OUT_OPEN_MODE 0644
+
+ if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE))
+ == 0 && errno == ENOENT) {
+ make_parent_dir(out_path, 0755);
+ out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE);
+ }
+ if (out_fp == 0)
+ msg_fatal("open %s: %m", out_path);
+
+ /*
+ * Append message content from single-message dump file.
+ */
+ if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file));
+ VSTRING_RESET(state->buffer);
+ for (;;) {
+ count = vstream_fread(state->dump_file, STR(state->buffer),
+ vstring_avail(state->buffer));
+ if (count <= 0)
+ break;
+ if (vstream_fwrite(out_fp, STR(state->buffer), count) != count)
+ msg_fatal("append file %s: %m", out_path);
+ }
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file));
+ if (vstream_fclose(out_fp))
+ msg_fatal("append file %s: %m", out_path);
+ }
+ mail_file_cleanup(state);
+}
+
+/* mail_file_reset - abort mail to capture file */
+
+static void mail_file_reset(SINK_STATE *state)
+{
+ if (shared_template == 0
+ && unlink(VSTREAM_PATH(state->dump_file)) < 0
+ && errno != ENOENT)
+ msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file));
+ mail_file_cleanup(state);
+}
+
+/* mail_cmd_reset - reset mail transaction information */
+
+static void mail_cmd_reset(SINK_STATE *state)
+{
+ state->in_mail = 0;
+ /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */
+ if (state->dump_file)
+ mail_file_reset(state);
+}
+
+/* ehlo_response - respond to EHLO command */
+
+static void ehlo_response(SINK_STATE *state, const char *args)
+{
+#define SKIP(cp, cond) do { \
+ for (/* void */; *cp && (cond); cp++) \
+ /* void */; \
+ } while (0)
+
+ /* EHLO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ if (enable_lmtp == 0)
+ state->client_proto = "ESMTP";
+ smtp_printf(state->stream, "250-%s", var_myhostname);
+ if (!disable_pipelining)
+ smtp_printf(state->stream, "250-PIPELINING");
+ if (!disable_8bitmime)
+ smtp_printf(state->stream, "250-8BITMIME");
+ if (!disable_saslauth)
+ smtp_printf(state->stream, "250-AUTH PLAIN LOGIN");
+ if (!disable_xclient)
+ smtp_printf(state->stream, "250-XCLIENT NAME HELO");
+ if (!disable_xforward)
+ smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO");
+ if (!disable_enh_status)
+ smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES");
+ if (!disable_dsn)
+ smtp_printf(state->stream, "250-DSN");
+ /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */
+ smtp_printf(state->stream, "250 ");
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
+}
+
+/* helo_response - respond to HELO command */
+
+static void helo_response(SINK_STATE *state, const char *args)
+{
+ /* HELO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ state->client_proto = "SMTP";
+ smtp_printf(state->stream, "250 %s", var_myhostname);
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
+}
+
+/* ok_response - send 250 OK */
+
+static void ok_response(SINK_STATE *state, const char *unused_args)
+{
+ smtp_printf(state->stream, "250 2.0.0 Ok");
+ SMTP_FLUSH(state->stream);
+}
+
+/* rset_response - reset, send 250 OK */
+
+static void rset_response(SINK_STATE *state, const char *unused_args)
+{
+ mail_cmd_reset(state);
+ smtp_printf(state->stream, "250 2.1.0 Ok");
+ SMTP_FLUSH(state->stream);
+}
+
+/* mail_response - reset recipient count, send 250 OK */
+
+static void mail_response(SINK_STATE *state, const char *args)
+{
+ if (state->in_mail) {
+ smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ state->in_mail++;
+ state->rcpts = 0;
+ smtp_printf(state->stream, "250 2.1.0 Ok");
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ mail_file_open(state);
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args);
+ }
+}
+
+/* rcpt_response - bump recipient count, send 250 OK */
+
+static void rcpt_response(SINK_STATE *state, const char *args)
+{
+ if (state->in_mail == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ state->rcpts++;
+ smtp_printf(state->stream, "250 2.1.5 Ok");
+ SMTP_FLUSH(state->stream);
+ /* Note: there may be more than one recipient per mail transaction. */
+ if (state->dump_file) {
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args);
+ }
+}
+
+/* abort_event - delayed abort after DATA command */
+
+static void abort_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ smtp_printf(state->stream, "550 This violates SMTP");
+ SMTP_FLUSH(state->stream);
+ disconnect(state);
+}
+
+/* delay_read_event - resume input event handling */
+
+static void delay_read_event(int event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ if (event != EVENT_TIME)
+ msg_panic("delay_read_event: non-timer event %d", event);
+
+ event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+/* delay_read - temporarily suspend input event handling */
+
+static void delay_read(SINK_STATE *state, int delay)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ event_request_timer(delay_read_event, (void *) state, delay);
+}
+
+/* data_response - respond to DATA command */
+
+static void data_response(SINK_STATE *state, const char *unused_args)
+{
+ if (state->in_mail == 0 || state->rcpts == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ /* Not: ST_ANY. */
+ state->data_state = ST_CR_LF;
+ smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
+ SMTP_FLUSH(state->stream);
+ if (abort_delay < 0) {
+ state->read_fn = data_read;
+ /* Todo: move into code that invokes the command response function. */
+ if (data_read_delay > 0)
+ delay_read(state, data_read_delay);
+ } else {
+ /* Stop reading, send premature 550, and disconnect. */
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_event, (void *) state);
+ event_request_timer(abort_event, (void *) state, abort_delay);
+ }
+ if (state->dump_file)
+ mail_file_finish_header(state);
+}
+
+/* dot_resp_hard - hard error response to . command */
+
+static void dot_resp_hard(SINK_STATE *state)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ } else {
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* dot_resp_soft - soft error response to . command */
+
+static void dot_resp_soft(SINK_STATE *state)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ } else {
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* dot_response - response to . command */
+
+static void dot_response(SINK_STATE *state, const char *unused_args)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "250 2.2.0 Ok");
+ } else {
+ smtp_printf(state->stream, "250 2.0.0 Ok");
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* quit_response - respond to QUIT command */
+
+static void quit_response(SINK_STATE *state, const char *unused_args)
+{
+ smtp_printf(state->stream, "221 Bye");
+ smtp_flush(state->stream); /* not: SMTP_FLUSH */
+ if (show_count)
+ quit_count++;
+}
+
+/* conn_response - respond to connect command */
+
+static void conn_response(SINK_STATE *state, const char *unused_args)
+{
+ if (pretend_pix)
+ smtp_printf(state->stream, "220 ********");
+ else if (disable_esmtp)
+ smtp_printf(state->stream, "220 %s", var_myhostname);
+ else
+ smtp_printf(state->stream, "220 %s ESMTP", var_myhostname);
+ SMTP_FLUSH(state->stream);
+}
+
+/* delay_event - delayed command response */
+
+static void delay_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ state->delayed_response(state, state->delayed_args);
+ myfree(state->delayed_args);
+ state->delayed_args = 0;
+ break;
+ }
+
+ if (state->delayed_response == quit_response) {
+ disconnect(state);
+ return;
+ }
+ state->delayed_response = 0;
+
+ /* Resume input event handling after the delayed response. */
+ event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+/* data_read - read data from socket */
+
+static int data_read(SINK_STATE *state)
+{
+ int ch;
+ struct data_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ static struct data_trans data_trans[] = {
+ ST_ANY, '\r', ST_CR,
+ ST_CR, '\n', ST_CR_LF,
+ ST_CR_LF, '.', ST_CR_LF_DOT,
+ ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR,
+ ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF,
+ };
+ struct data_trans *dp;
+
+ /*
+ * A read may result in EOF, but is never supposed to time out - a time
+ * out means that we were trying to read when no data was available.
+ */
+ for (;;) {
+ if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
+ return (-1);
+ for (dp = data_trans; dp->state != state->data_state; dp++)
+ /* void */ ;
+
+ /*
+ * Try to match the current character desired by the state machine.
+ * If that fails, try to restart the machine with a match for its
+ * first state. This covers the case of a CR/LF/CR/LF sequence
+ * (empty line) right before the end of the message data.
+ */
+ if (ch == dp->want)
+ state->data_state = dp->next_state;
+ else if (ch == data_trans[0].want)
+ state->data_state = data_trans[0].next_state;
+ else
+ state->data_state = ST_ANY;
+ if (state->dump_file) {
+ if (ch != '\r' && state->data_state != ST_CR_LF_DOT)
+ VSTREAM_PUTC(ch, state->dump_file);
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file));
+ }
+ if (state->data_state == ST_CR_LF_DOT_CR_LF) {
+ PUSH_BACK_SET(state, ".\r\n");
+ state->read_fn = command_read;
+ state->data_state = ST_ANY;
+ if (state->dump_file)
+ mail_file_finish(state);
+ mail_cmd_reset(state);
+ if (show_count || max_msg_quit_count > 0) {
+ mesg_count++;
+ if (show_count)
+ do_stats();
+ if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count)
+ exit(0);
+ }
+ break;
+ }
+
+ /*
+ * We must avoid blocking I/O, so get out of here as soon as both the
+ * VSTREAM and kernel read buffers dry up.
+ */
+ if (vstream_peek(state->stream) <= 0
+ && readable(vstream_fileno(state->stream)) <= 0)
+ return (0);
+ }
+ return (0);
+}
+
+ /*
+ * The table of all SMTP commands that we can handle.
+ */
+typedef struct SINK_COMMAND {
+ const char *name;
+ void (*response) (SINK_STATE *, const char *);
+ void (*hard_response) (SINK_STATE *);
+ void (*soft_response) (SINK_STATE *);
+ int flags;
+ int delay;
+ int delay_odds;
+} SINK_COMMAND;
+
+#define FLAG_ENABLE (1<<0) /* command is enabled */
+#define FLAG_SYSLOG (1<<1) /* log the command */
+#define FLAG_HARD_ERR (1<<2) /* report hard error */
+#define FLAG_SOFT_ERR (1<<3) /* report soft error */
+#define FLAG_DISCONNECT (1<<4) /* disconnect */
+#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */
+
+static SINK_COMMAND command_table[] = {
+ "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0,
+ "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ 0,
+};
+
+/* reset_cmd_flags - reset per-command command flags */
+
+static void reset_cmd_flags(const char *cmd, int flags)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+ cmdp->flags &= ~flags;
+}
+
+/* set_cmd_flags - set per-command command flags */
+
+static void set_cmd_flags(const char *cmd, int flags)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+ cmdp->flags |= flags;
+}
+
+/* set_cmds_flags - set per-command flags for multiple commands */
+
+static void set_cmds_flags(const char *cmds, int flags)
+{
+ char *saved_cmds;
+ char *cp;
+ char *cmd;
+
+ saved_cmds = cp = mystrdup(cmds);
+ while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0)
+ set_cmd_flags(cmd, flags);
+ myfree(saved_cmds);
+}
+
+/* set_cmd_delay - set per-command delay */
+
+static void set_cmd_delay(const char *cmd, int delay, int odds)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+
+ if (delay <= 0)
+ msg_fatal("non-positive '%s' delay", cmd);
+ if (odds < 0 || odds > 99)
+ msg_fatal("delay odds for '%s' out of range", cmd);
+
+ cmdp->delay = delay;
+ cmdp->delay_odds = odds;
+}
+
+/* set_cmd_delay_arg - set per-command delay from option argument */
+
+static void set_cmd_delay_arg(char *arg)
+{
+ char *cp;
+ char *saved_arg;
+ char *cmd;
+ char *delay;
+ char *odds;
+
+ saved_arg = cp = mystrdup(arg);
+ cmd = mystrtok(&cp, ":");
+ delay = mystrtok(&cp, ":");
+ if (cmd == 0 || delay == 0)
+ msg_fatal("invalid command delay argument: %s", arg);
+ odds = mystrtok(&cp, "");
+ set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0);
+ myfree(saved_arg);
+}
+
+/* command_resp - respond to command */
+
+static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp,
+ const char *command, const char *args)
+{
+ /* We use raw syslog. Sanitize data content and length. */
+ if (cmdp->flags & FLAG_SYSLOG)
+ syslog(LOG_INFO, "%s %.100s", command, args);
+ if (cmdp->flags & FLAG_DISCONNECT)
+ return (-1);
+ if (cmdp->flags & FLAG_CLOSE) {
+ smtp_printf(state->stream, "421 4.0.0 Server closing connection");
+ return (-1);
+ }
+ if (cmdp->flags & FLAG_HARD_ERR) {
+ cmdp->hard_response(state);
+ return (0);
+ }
+ if (cmdp->flags & FLAG_SOFT_ERR) {
+ cmdp->soft_response(state);
+ return (0);
+ }
+ if (cmdp->delay > 0) {
+ int delay = cmdp->delay;
+
+ if (cmdp->delay_odds > 0)
+ for (delay = 0;
+ ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds;
+ delay += cmdp->delay)
+ /* NOP */ ;
+ /* Suspend input event handling while delaying the command response. */
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ event_request_timer(delay_event, (void *) state, delay);
+ state->delayed_response = cmdp->response;
+ state->delayed_args = mystrdup(args);
+ } else {
+ cmdp->response(state, args);
+ if (cmdp->response == quit_response)
+ return (-1);
+ }
+ return (0);
+}
+
+/* command_read - talk the SMTP protocol, server side */
+
+static int command_read(SINK_STATE *state)
+{
+ char *command;
+ SINK_COMMAND *cmdp;
+ int ch;
+ struct cmd_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ static struct cmd_trans cmd_trans[] = {
+ ST_ANY, '\r', ST_CR,
+ ST_CR, '\n', ST_CR_LF,
+ 0, 0, 0,
+ };
+ struct cmd_trans *cp;
+ char *ptr;
+
+ /*
+ * A read may result in EOF, but is never supposed to time out - a time
+ * out means that we were trying to read when no data was available.
+ */
+#define NEXT_CHAR(state) \
+ (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream))
+
+ if (state->data_state == ST_CR_LF)
+ state->data_state = ST_ANY; /* XXX */
+ for (;;) {
+ if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF)
+ return (-1);
+
+ /*
+ * Sanity check. We don't want to store infinitely long commands.
+ */
+ if (VSTRING_LEN(state->buffer) >= var_max_line_length) {
+ msg_warn("command line too long");
+ return (-1);
+ }
+ VSTRING_ADDCH(state->buffer, ch);
+
+ /*
+ * Try to match the current character desired by the state machine.
+ * If that fails, try to restart the machine with a match for its
+ * first state.
+ */
+ for (cp = cmd_trans; cp->state != state->data_state; cp++)
+ if (cp->want == 0)
+ msg_panic("command_read: unknown state: %d", state->data_state);
+ if (ch == cp->want)
+ state->data_state = cp->next_state;
+ else if (ch == cmd_trans[0].want)
+ state->data_state = cmd_trans[0].next_state;
+ else
+ state->data_state = ST_ANY;
+ if (state->data_state == ST_CR_LF)
+ break;
+
+ /*
+ * We must avoid blocking I/O, so get out of here as soon as both the
+ * VSTREAM and kernel read buffers dry up.
+ *
+ * XXX Solaris non-blocking read() may fail on a socket when ioctl
+ * FIONREAD reports there is unread data. Diagnosis by Max Pashkov.
+ * As a workaround we use readable() (which uses poll or select())
+ * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added
+ * 20020604.
+ */
+ if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0
+ && readable(vstream_fileno(state->stream)) <= 0)
+ return (0);
+ }
+
+ /*
+ * Properly terminate the result, and reset the buffer write pointer for
+ * reading the next command. This is ugly, but not as ugly as trying to
+ * deal with all the early returns below.
+ */
+ vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
+ VSTRING_TERMINATE(state->buffer);
+ state->data_state = ST_CR_LF;
+ VSTRING_RESET(state->buffer);
+
+ /*
+ * Got a complete command line. Parse it.
+ */
+ ptr = vstring_str(state->buffer);
+ if (msg_verbose)
+ msg_info("%s", ptr);
+ if ((command = mystrtok(&ptr, " \t")) == 0) {
+ smtp_printf(state->stream, "500 5.5.2 Error: unknown command");
+ SMTP_FLUSH(state->stream);
+ return (0);
+ }
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(command, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) {
+ smtp_printf(state->stream, "500 5.5.1 Error: unknown command");
+ SMTP_FLUSH(state->stream);
+ return (0);
+ }
+ return (command_resp(state, cmdp, command, printable(ptr, '?')));
+}
+
+/* read_timeout - handle timer event */
+
+static void read_timeout(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ /*
+ * We don't send anything to the client, because we would have to set up
+ * an smtp_stream exception handler first. And that is just too much
+ * trouble.
+ */
+ msg_warn("read timeout");
+ disconnect(state);
+}
+
+/* read_event - handle command or data read events */
+
+static void read_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ /*
+ * The input reading routine not only reads input (with vstream calls)
+ * but also produces output (with smtp_stream calls). Because the output
+ * routines can raise timeout or EOF exceptions with vstream_longjmp(),
+ * the input reading routine needs to set up corresponding exception
+ * handlers with vstream_setjmp(). Guarding the input operations in the
+ * same manner is not useful: we must read input in non-blocking mode, so
+ * we never get called when the socket stays unreadable too long. And EOF
+ * is already trivial to detect with the vstream calls.
+ */
+ do {
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ if (state->read_fn(state) < 0) {
+ if (msg_verbose)
+ msg_info("disconnect");
+ disconnect(state);
+ return;
+ }
+ }
+ } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0);
+
+ /*
+ * Reset the idle timer. Wait until the next input event, or until the
+ * idle timer goes off.
+ */
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+static void connect_event(int, void *);
+
+/* disconnect - handle disconnection events */
+
+static void disconnect(SINK_STATE *state)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ if (show_count) {
+ sess_count++;
+ do_stats();
+ }
+ vstream_fclose(state->stream);
+ vstring_free(state->buffer);
+ /* Clean up file capture attributes. */
+ if (state->helo_args)
+ myfree(state->helo_args);
+ /* Delete incomplete mail transaction. */
+ mail_cmd_reset(state);
+ if (state->delayed_args)
+ myfree(state->delayed_args);
+ myfree((void *) state);
+ if (max_quit_count > 0 && quit_count >= max_quit_count)
+ exit(0);
+ if (client_count-- == max_client_count)
+ event_enable_read(sock, connect_event, (void *) 0);
+}
+
+/* connect_event - handle connection events */
+
+static void connect_event(int unused_event, void *unused_context)
+{
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SINK_STATE *state;
+ int fd;
+
+ if ((fd = sane_accept(sock, sa, &len)) >= 0) {
+ /* Safety: limit the number of open sockets and capture files. */
+ if (++client_count == max_client_count)
+ event_disable_readwrite(sock);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family))
+ SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr,
+ (MAI_SERVPORT_STR *) 0, sa->sa_family);
+ else
+ strncpy(state->client_addr.buf, "local", sizeof("local") + 0);
+ if (msg_verbose)
+ msg_info("connect (%s %s)",
+#ifdef AF_LOCAL
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
+#else
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ "unknown protocol family",
+ state->client_addr.buf);
+ non_blocking(fd, NON_BLOCKING);
+ state->stream = vstream_fdopen(fd, O_RDWR);
+ vstream_tweak_sock(state->stream);
+ state->buffer = vstring_alloc(1024);
+ state->read_fn = command_read;
+ state->data_state = ST_ANY;
+ PUSH_BACK_SET(state, "");
+ smtp_timeout_setup(state->stream, var_tmout);
+ state->in_mail = 0;
+ state->rcpts = 0;
+ state->delayed_response = 0;
+ state->delayed_args = 0;
+ /* Initialize file capture attributes. */
+#ifdef AF_INET6
+ if (sa->sa_family == AF_INET6)
+ state->addr_prefix = "ipv6:";
+ else
+#endif
+ state->addr_prefix = "";
+
+ state->helo_args = 0;
+ state->client_proto = enable_lmtp ? "LMTP" : "SMTP";
+ state->start_time = 0;
+ state->id = 0;
+ state->dump_file = 0;
+
+ /*
+ * We use the smtp_stream module to produce output. That module
+ * throws an exception via vstream_longjmp() in case of a timeout or
+ * lost connection error. Therefore we must prepare to handle these
+ * exceptions with vstream_setjmp().
+ */
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ if (command_resp(state, command_table, "connect", "") < 0)
+ disconnect(state);
+ else if (command_table->delay == 0) {
+ event_enable_read(fd, read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+ }
+ }
+ }
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int backlog;
+ int ch;
+ int delay;
+ const char *protocols = INET_PROTO_NAME_ALL;
+ const char *root_dir = 0;
+ const char *user_privs = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Fix 20051207.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Initialize diagnostics.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case '8':
+ disable_8bitmime = 1;
+ break;
+ case 'a':
+ disable_saslauth = 1;
+ break;
+ case 'A':
+ if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0)
+ usage(argv[0]);
+ break;
+ case 'b':
+ if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) {
+ msg_error("bad soft error reply: %s", optarg);
+ usage(argv[0]);
+ } else
+ soft_error_resp = optarg;
+ break;
+ case 'B':
+ if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) {
+ msg_error("bad hard error reply: %s", optarg);
+ usage(argv[0]);
+ } else
+ hard_error_resp = optarg;
+ break;
+ case 'c':
+ show_count++;
+ break;
+ case 'C':
+ disable_xclient = 1;
+ reset_cmd_flags("xclient", FLAG_ENABLE);
+ break;
+ case 'd':
+ single_template = optarg;
+ break;
+ case 'D':
+ shared_template = optarg;
+ break;
+ case 'e':
+ disable_esmtp = 1;
+ break;
+ case 'E':
+ disable_enh_status = 1;
+ break;
+ case 'f':
+ set_cmds_flags(optarg, FLAG_HARD_ERR);
+ disable_pipelining = 1;
+ break;
+ case 'F':
+ disable_xforward = 1;
+ reset_cmd_flags("xforward", FLAG_ENABLE);
+ break;
+ case 'h':
+ var_myhostname = optarg;
+ break;
+ case 'H':
+ if ((data_read_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad data read delay: %s", optarg);
+ break;
+ case 'L':
+ enable_lmtp = 1;
+ break;
+ case 'm':
+ if ((max_client_count = atoi(optarg)) <= 0)
+ msg_fatal("bad concurrency limit: %s", optarg);
+ break;
+ case 'M':
+ if ((max_msg_quit_count = atoi(optarg)) <= 0)
+ msg_fatal("bad message quit count: %s", optarg);
+ break;
+ case 'n':
+ if ((max_quit_count = atoi(optarg)) <= 0)
+ msg_fatal("bad quit count: %s", optarg);
+ break;
+ case 'N':
+ disable_dsn = 1;
+ break;
+ case 'p':
+ disable_pipelining = 1;
+ break;
+ case 'P':
+ pretend_pix = 1;
+ disable_esmtp = 1;
+ break;
+ case 'q':
+ set_cmds_flags(optarg, FLAG_DISCONNECT);
+ break;
+ case 'Q':
+ set_cmds_flags(optarg, FLAG_CLOSE);
+ break;
+ case 'r':
+ set_cmds_flags(optarg, FLAG_SOFT_ERR);
+ disable_pipelining = 1;
+ break;
+ case 'R':
+ root_dir = optarg;
+ break;
+ case 's':
+ openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
+ set_cmds_flags(optarg, FLAG_SYSLOG);
+ break;
+ case 'S':
+ start_string = vstring_alloc(10);
+ unescape(start_string, optarg);
+ break;
+ case 't':
+ if ((var_tmout = atoi(optarg)) <= 0)
+ msg_fatal("bad timeout: %s", optarg);
+ break;
+ case 'T':
+ if ((inet_windowsize = atoi(optarg)) <= 0)
+ msg_fatal("bad TCP window size: %s", optarg);
+ break;
+ case 'u':
+ user_privs = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if ((delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ set_cmd_delay("data", delay, 0);
+ break;
+ case 'W':
+ set_cmd_delay_arg(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 2)
+ usage(argv[0]);
+ if ((backlog = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+ if (single_template && shared_template)
+ msg_fatal("use only one of -d or -D, but not both");
+ if (geteuid() == 0 && user_privs == 0)
+ msg_fatal("-u option is required if running as root");
+
+ /*
+ * Initialize.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = "smtp-sink";
+ set_cmds_flags(enable_lmtp ? "lhlo" :
+ disable_esmtp ? "helo" :
+ "helo, ehlo", FLAG_ENABLE);
+ proto_info = inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ sock = inet_listen(argv[optind], backlog, BLOCKING);
+ }
+ if (user_privs)
+ chroot_uid(root_dir, user_privs);
+
+ if (single_template)
+ mysrand((int) time((time_t *) 0));
+ else if (shared_template)
+ single_template = shared_template;
+
+ /*
+ * Start the event handler.
+ */
+ event_enable_read(sock, connect_event, (void *) 0);
+ for (;;)
+ event_loop(-1);
+}
diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c
new file mode 100644
index 0000000..be388d6
--- /dev/null
+++ b/src/smtpstone/smtp-source.c
@@ -0,0 +1,1188 @@
+/*++
+/* NAME
+/* smtp-source 1
+/* SUMMARY
+/* parallelized SMTP/LMTP test generator
+/* SYNOPSIS
+/* .fi
+/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/*
+/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR
+/* (default: port 25)
+/* and sends one or more messages to it, either sequentially
+/* or in parallel. The program speaks either SMTP (default) or
+/* LMTP.
+/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
+/* IPv4 and IPv6 are the default.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Connect to the server with IPv4. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Connect to the server with IPv6. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP "\fB-A\fR"
+/* Don't abort when the server sends something other than the
+/* expected positive reply code.
+/* .IP \fB-c\fR
+/* Display a running counter that is incremented each time
+/* an SMTP DATA command completes.
+/* .IP "\fB-C \fIcount\fR"
+/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times
+/* before giving up. The default count is 1. Specify a larger count in
+/* order to work around a problem with TCP/IP stacks that send RESET
+/* when the listen queue is full.
+/* .IP \fB-d\fR
+/* Don't disconnect after sending a message; send the next
+/* message over the same connection.
+/* .IP "\fB-f \fIfrom\fR"
+/* Use the specified sender address (default: <foo@myhostname>).
+/* .IP "\fB-F \fIfile\fR"
+/* Send the pre-formatted message header and body in the
+/* specified \fIfile\fR, while prepending '.' before lines that
+/* begin with '.', and while appending CRLF after each line.
+/* .IP "\fB-l \fIlength\fR"
+/* Send \fIlength\fR bytes as message payload. The length does not
+/* include message headers.
+/* .IP \fB-L\fR
+/* Speak LMTP rather than SMTP.
+/* .IP "\fB-m \fImessage_count\fR"
+/* Send the specified number of messages (default: 1).
+/* .IP "\fB-M \fImyhostname\fR"
+/* Use the specified hostname or [address] in the HELO command
+/* and in the default sender and recipient addresses, instead
+/* of the machine hostname.
+/* .IP "\fB-N\fR"
+/* Prepend a non-repeating sequence number to each recipient
+/* address. This avoids the artificial 100% hit rate in the
+/* resolve and rewrite client caches and exercises the
+/* trivial-rewrite daemon, better approximating Postfix
+/* performance under real-life work-loads.
+/* .IP \fB-o\fR
+/* Old mode: don't send HELO, and don't send message headers.
+/* .IP "\fB-r \fIrecipient_count\fR"
+/* Send the specified number of recipients per transaction (default: 1).
+/* Recipient names are generated by prepending a number to the
+/* recipient address.
+/* .IP "\fB-R \fIinterval\fR"
+/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP "\fB-s \fIsession_count\fR"
+/* Run the specified number of SMTP sessions in parallel (default: 1).
+/* .IP "\fB-S \fIsubject\fR"
+/* Send mail with the named subject line (default: none).
+/* .IP "\fB-t \fIto\fR"
+/* Use the specified recipient address (default: <foo@myhostname>).
+/* .IP "\fB-T \fIwindowsize\fR"
+/* Override the default TCP window size. To work around
+/* broken TCP window scaling implementations, specify a
+/* value > 0 and < 65536.
+/* .IP \fB-v\fR
+/* Make the program more verbose, for debugging purposes.
+/* .IP "\fB-w \fIinterval\fR"
+/* Wait a fixed time between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default
+/* port is \fBsmtp\fR.
+/* .IP \fBunix:\fIpathname\fR
+/* Connect to the UNIX-domain socket at \fIpathname\fR.
+/* BUGS
+/* No SMTP command pipelining support.
+/* SEE ALSO
+/* smtp-sink(1), SMTP/LMTP message dump
+/* 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>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+#include <compat_va_copy.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same SMTP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct SESSION {
+ int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
+ int rcpt_count; /* # of recipients to go */
+ int rcpt_accepted; /* # of recipients accepted */
+ VSTREAM *stream; /* open connection */
+ int connect_count; /* # of connect()s to retry */
+ struct SESSION *next; /* connect() queue linkage */
+} SESSION;
+
+static SESSION *last_session; /* connect() queue tail */
+
+ /*
+ * Structure with broken-up SMTP server response.
+ */
+typedef struct { /* server response */
+ int code; /* status */
+ char *str; /* text */
+ VSTRING *buf; /* origin of text */
+} RESPONSE;
+
+static VSTRING *buffer;
+static int var_line_limit = 10240;
+static int var_timeout = 300;
+static const char *var_myhostname;
+static int session_count;
+static int message_count = 1;
+static struct sockaddr_storage ss;
+
+#undef sun
+static struct sockaddr_un sun;
+static struct sockaddr *sa;
+static int sa_length;
+static int recipients = 1;
+static char *defaddr;
+static char *recipient;
+static char *sender;
+static char *message_data;
+static int message_length;
+static int disconnect = 1;
+static int count = 0;
+static int counter = 0;
+static int send_helo_first = 1;
+static int send_headers = 1;
+static int connect_count = 1;
+static int random_delay = 0;
+static int fixed_delay = 0;
+static int talk_lmtp = 0;
+static char *subject = 0;
+static int number_rcpts = 0;
+static int allow_reject = 0;
+
+static void enqueue_connect(SESSION *);
+static void start_connect(SESSION *);
+static void connect_done(int, void *);
+static void read_banner(int, void *);
+static void send_helo(SESSION *);
+static void helo_done(int, void *);
+static void send_mail(SESSION *);
+static void mail_done(int, void *);
+static void send_rcpt(int, void *);
+static void rcpt_done(int, void *);
+static void send_data(int, void *);
+static void data_done(int, void *);
+static void dot_done(int, void *);
+static void send_rset(int, void *);
+static void rset_done(int, void *);
+static void send_quit(SESSION *);
+static void quit_done(int, void *);
+static void close_session(SESSION *);
+
+/* random_interval - generate a random value in 0 .. (small) interval */
+
+static int random_interval(int interval)
+{
+ return (rand() % (interval + 1));
+}
+
+/* command - send an SMTP command */
+
+static void command(VSTREAM *stream, char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ /*
+ * Optionally, log the command before actually sending, so we can see
+ * what the program is trying to do.
+ */
+ if (msg_verbose) {
+ va_list ap2;
+
+ VA_COPY(ap2, ap);
+ vmsg_info(fmt, ap2);
+ va_end(ap2);
+ }
+ smtp_vprintf(stream, fmt, ap);
+ va_end(ap);
+ smtp_flush(stream);
+}
+
+/* socket_error - look up and reset the last socket error */
+
+static int socket_error(int sock)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Some Solaris 2 versions have getsockopt() itself return the error,
+ * instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
+
+/* response - read and process SMTP server response */
+
+static RESPONSE *response(VSTREAM *stream, VSTRING *buf)
+{
+ static RESPONSE rdata;
+ int more;
+ char *cp;
+
+ /*
+ * Initialize the response data buffer. smtp_get() defends against a
+ * denial of service attack by limiting the amount of single-line text,
+ * and the loop below limits the amount of multi-line text that we are
+ * willing to store.
+ */
+ if (rdata.buf == 0)
+ rdata.buf = vstring_alloc(100);
+
+ /*
+ * Censor out non-printable characters in server responses. Concatenate
+ * multi-line server responses. Separate the status code from the text.
+ * Leave further parsing up to the application.
+ */
+#define BUF ((char *) vstring_str(buf))
+ VSTRING_RESET(rdata.buf);
+ for (;;) {
+ smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
+ for (cp = BUF; *cp != 0; cp++)
+ if (!ISPRINT(*cp) && !ISSPACE(*cp))
+ *cp = '?';
+ cp = BUF;
+ if (msg_verbose)
+ msg_info("<<< %s", cp);
+ while (ISDIGIT(*cp))
+ cp++;
+ rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
+ if ((more = (*cp == '-')) != 0)
+ cp++;
+ while (ISSPACE(*cp))
+ cp++;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ vstring_strcat(rdata.buf, cp);
+ if (more == 0)
+ break;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ VSTRING_ADDCH(rdata.buf, '\n');
+ }
+ VSTRING_TERMINATE(rdata.buf);
+ rdata.str = vstring_str(rdata.buf);
+ return (&rdata);
+}
+
+/* exception_text - translate exceptions from the smtp_stream module */
+
+static char *exception_text(int except)
+{
+ switch (except) {
+ case SMTP_ERR_EOF:
+ return ("lost connection");
+ case SMTP_ERR_TIME:
+ return ("timeout");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+ /* NOTREACHED */
+}
+
+/* startup - connect to server but do not wait */
+
+static void startup(SESSION *session)
+{
+ if (message_count-- <= 0) {
+ myfree((void *) session);
+ session_count--;
+ return;
+ }
+ if (session->stream == 0) {
+ enqueue_connect(session);
+ } else {
+ send_mail(session);
+ }
+}
+
+/* start_event - invoke startup from timer context */
+
+static void start_event(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ startup(session);
+}
+
+/* start_another - start another session */
+
+static void start_another(SESSION *session)
+{
+ if (random_delay > 0) {
+ event_request_timer(start_event, (void *) session,
+ random_interval(random_delay));
+ } else if (fixed_delay > 0) {
+ event_request_timer(start_event, (void *) session, fixed_delay);
+ } else {
+ startup(session);
+ }
+}
+
+/* enqueue_connect - queue a connection request */
+
+static void enqueue_connect(SESSION *session)
+{
+ session->next = 0;
+ if (last_session == 0) {
+ last_session = session;
+ start_connect(session);
+ } else {
+ last_session->next = session;
+ last_session = session;
+ }
+}
+
+/* dequeue_connect - connection request completed */
+
+static void dequeue_connect(SESSION *session)
+{
+ if (session == last_session) {
+ if (session->next != 0)
+ msg_panic("dequeue_connect: queue ends after last");
+ last_session = 0;
+ } else {
+ if (session->next == 0)
+ msg_panic("dequeue_connect: queue ends before last");
+ start_connect(session->next);
+ }
+}
+
+/* fail_connect - handle failed startup */
+
+static void fail_connect(SESSION *session)
+{
+ if (session->connect_count-- == 1)
+ msg_fatal("connect: %m");
+ msg_warn("connect: %m");
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+#ifdef MISSING_USLEEP
+ doze(10);
+#else
+ usleep(10);
+#endif
+ start_connect(session);
+}
+
+/* start_connect - start TCP handshake */
+
+static void start_connect(SESSION *session)
+{
+ int fd;
+ struct linger linger;
+
+ /*
+ * Some systems don't set the socket error when connect() fails early
+ * (loopback) so we must deal with the error immediately, rather than
+ * retrieving it later with getsockopt(). We can't use MSG_PEEK to
+ * distinguish between server disconnect and connection refused.
+ */
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ (void) non_blocking(fd, NON_BLOCKING);
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger,
+ sizeof(linger)) < 0)
+ msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
+ session->stream = vstream_fdopen(fd, O_RDWR);
+ event_enable_write(fd, connect_done, (void *) session);
+ smtp_timeout_setup(session->stream, var_timeout);
+ if (inet_windowsize > 0)
+ set_inet_windowsize(fd, inet_windowsize);
+ if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
+ fail_connect(session);
+}
+
+/* connect_done - send message sender info */
+
+static void connect_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int fd = vstream_fileno(session->stream);
+
+ /*
+ * Try again after some delay when the connection failed, in case they
+ * run a Mickey Mouse protocol stack.
+ */
+ if (socket_error(fd) < 0) {
+ fail_connect(session);
+ } else {
+ non_blocking(fd, BLOCKING);
+ /* Disable write events. */
+ event_disable_readwrite(fd);
+ event_enable_read(fd, read_banner, (void *) session);
+ dequeue_connect(session);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )
+ vstream_tweak_tcp(session->stream);
+ }
+}
+
+/* read_banner - receive SMTP server greeting */
+
+static void read_banner(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while reading server greeting", exception_text(except));
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ if (((resp = response(session->stream, buffer))->code / 100) == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("rejected at server banner: %d %s", resp->code, resp->str);
+ } else {
+ msg_fatal("rejected at server banner: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send helo or send the envelope sender address.
+ */
+ if (send_helo_first)
+ send_helo(session);
+ else
+ send_mail(session);
+}
+
+/* send_helo - send hostname */
+
+static void send_helo(SESSION *session)
+{
+ int except;
+ const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO");
+
+ /*
+ * Send the standard greeting with our hostname
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
+
+ command(session->stream, "%s %s", protocol, var_myhostname);
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session);
+}
+
+/* helo_done - handle HELO response */
+
+static void helo_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+ const char *protocol = (talk_lmtp ? "LHLO" : "HELO");
+
+ /*
+ * Get response to HELO command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str);
+ }
+
+ send_mail(session);
+}
+
+/* send_mail - send envelope sender */
+
+static void send_mail(SESSION *session)
+{
+ int except;
+
+ /*
+ * Send the envelope sender address.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending sender", exception_text(except));
+
+ command(session->stream, "MAIL FROM:<%s>", sender);
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session);
+}
+
+/* mail_done - handle MAIL response */
+
+static void mail_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to MAIL command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending sender", exception_text(except));
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ session->rcpt_count = recipients;
+ session->rcpt_done = 0;
+ session->rcpt_accepted = 0;
+ send_rcpt(unused, context);
+ } else if (allow_reject) {
+ msg_warn("sender rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ send_rset(unused, context);
+ } else {
+ msg_fatal("sender rejected: %d %s", resp->code, resp->str);
+ }
+}
+
+/* send_rcpt - send recipient address */
+
+static void send_rcpt(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Send envelope recipient address.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending recipient", exception_text(except));
+
+ if (session->rcpt_count > 1 || number_rcpts > 0)
+ command(session->stream, "RCPT TO:<%d%s>",
+ number_rcpts ? number_rcpts++ : session->rcpt_count,
+ recipient);
+ else
+ command(session->stream, "RCPT TO:<%s>", recipient);
+ session->rcpt_count--;
+ session->rcpt_done++;
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session);
+}
+
+/* rcpt_done - handle RCPT completion */
+
+static void rcpt_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to RCPT command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending recipient", exception_text(except));
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ session->rcpt_accepted++;
+ } else if (allow_reject) {
+ msg_warn("recipient rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("recipient rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send another RCPT command or send DATA.
+ */
+ if (session->rcpt_count > 0)
+ send_rcpt(unused, context);
+ else if (session->rcpt_accepted > 0)
+ send_data(unused, context);
+ else
+ send_rset(unused, context);
+}
+
+/* send_data - send DATA command */
+
+static void send_data(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Request data transmission.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending DATA command", exception_text(except));
+ command(session->stream, "DATA");
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), data_done, (void *) session);
+}
+
+/* data_done - send message content */
+
+static void data_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+ static const char *mydate;
+ static int mypid;
+
+ /*
+ * Get response to DATA command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending DATA command", exception_text(except));
+ if ((resp = response(session->stream, buffer))->code == 354) {
+ /* see below */ ;
+ } else if (allow_reject) {
+ msg_warn("data rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ send_rset(unused, context);
+ return;
+ } else {
+ msg_fatal("data rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send basic header to keep mailers that bother to examine them happy.
+ */
+ if (send_headers) {
+ if (mydate == 0) {
+ mydate = mail_date(time((time_t *) 0));
+ mypid = getpid();
+ }
+ smtp_printf(session->stream, "From: <%s>", sender);
+ smtp_printf(session->stream, "To: <%s>", recipient);
+ smtp_printf(session->stream, "Date: %s", mydate);
+ smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>",
+ mypid, vstream_fileno(session->stream), message_count, var_myhostname);
+ if (subject)
+ smtp_printf(session->stream, "Subject: %s", subject);
+ smtp_fputs("", 0, session->stream);
+ }
+
+ /*
+ * Send some garbage.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ if (message_length == 0) {
+ smtp_fputs("La de da de da 1.", 17, session->stream);
+ smtp_fputs("La de da de da 2.", 17, session->stream);
+ smtp_fputs("La de da de da 3.", 17, session->stream);
+ smtp_fputs("La de da de da 4.", 17, session->stream);
+ } else {
+
+ /*
+ * XXX This may cause the process to block with message content
+ * larger than VSTREAM_BUFIZ bytes.
+ */
+ smtp_fputs(message_data, message_length, session->stream);
+ }
+
+ /*
+ * Send end of message and process the server response.
+ */
+ command(session->stream, ".");
+
+ /*
+ * Update the running counter.
+ */
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session);
+}
+
+/* dot_done - send QUIT or start another transaction */
+
+static void dot_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to "." command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ do { /* XXX this could block */
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("end of data rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("end of data rejected: %d %s", resp->code, resp->str);
+ }
+ } while (talk_lmtp && --session->rcpt_done > 0);
+ session->xfer_count++;
+
+ /*
+ * Say goodbye or send the next message.
+ */
+ if (disconnect || message_count < 1) {
+ send_quit(session);
+ } else {
+ event_disable_readwrite(vstream_fileno(session->stream));
+ start_another(session);
+ }
+}
+
+/* send_rset - send RSET command */
+
+static void send_rset(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ command(session->stream, "RSET");
+ event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session);
+}
+
+/* rset_done - handle RSET reply */
+
+static void rset_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to RSET command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */
+ } else if (allow_reject) {
+ msg_warn("rset rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("rset rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Say goodbye or send the next message.
+ */
+ if (disconnect || message_count < 1) {
+ send_quit(session);
+ } else {
+ event_disable_readwrite(vstream_fileno(session->stream));
+ start_another(session);
+ }
+}
+
+/* send_quit - send QUIT command */
+
+static void send_quit(SESSION *session)
+{
+ command(session->stream, "QUIT");
+ event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session);
+}
+
+/* quit_done - disconnect */
+
+static void quit_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ (void) response(session->stream, buffer);
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* close_session - disconnect, for example after 421 or 521 reply */
+
+static void close_session(SESSION *session)
+{
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - parse JCL and start the machine */
+
+int main(int argc, char **argv)
+{
+ SESSION *session;
+ char *host;
+ char *port;
+ char *path;
+ int path_len;
+ int sessions = 1;
+ int ch;
+ int i;
+ char *buf;
+ const char *parse_err;
+ struct addrinfo *res;
+ int aierr;
+ const char *protocols = INET_PROTO_NAME_ALL;
+ char *message_file = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ signal(SIGPIPE, SIG_IGN);
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'A':
+ allow_reject = 1;
+ break;
+ case 'c':
+ count++;
+ break;
+ case 'C':
+ if ((connect_count = atoi(optarg)) <= 0)
+ msg_fatal("bad connection count: %s", optarg);
+ break;
+ case 'd':
+ disconnect = 0;
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'F':
+ if (message_file == 0 && message_length > 0)
+ msg_fatal("-l option cannot be used with -F");
+ message_file = optarg;
+ break;
+ case 'l':
+ if (message_file != 0)
+ msg_fatal("-l option cannot be used with -F");
+ if ((message_length = atoi(optarg)) <= 0)
+ msg_fatal("bad message length: %s", optarg);
+ break;
+ case 'L':
+ talk_lmtp = 1;
+ break;
+ case 'm':
+ if ((message_count = atoi(optarg)) <= 0)
+ msg_fatal("bad message count: %s", optarg);
+ break;
+ case 'M':
+ if (*optarg == '[') {
+ if (!valid_mailhost_literal(optarg, DO_GRIPE))
+ msg_fatal("bad address literal: %s", optarg);
+ } else {
+ if (!valid_hostname(optarg, DO_GRIPE))
+ msg_fatal("bad hostname: %s", optarg);
+ }
+ var_myhostname = optarg;
+ break;
+ case 'N':
+ number_rcpts = 1;
+ break;
+ case 'o':
+ send_helo_first = 0;
+ send_headers = 0;
+ break;
+ case 'r':
+ if ((recipients = atoi(optarg)) <= 0)
+ msg_fatal("bad recipient count: %s", optarg);
+ break;
+ case 'R':
+ if (fixed_delay > 0)
+ msg_fatal("do not use -w and -R options at the same time");
+ if ((random_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad random delay: %s", optarg);
+ break;
+ case 's':
+ if ((sessions = atoi(optarg)) <= 0)
+ msg_fatal("bad session count: %s", optarg);
+ break;
+ case 'S':
+ subject = optarg;
+ break;
+ case 't':
+ recipient = optarg;
+ break;
+ case 'T':
+ if ((inet_windowsize = atoi(optarg)) <= 0)
+ msg_fatal("bad TCP window size: %s", optarg);
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if (random_delay > 0)
+ msg_fatal("do not use -w and -R options at the same time");
+ if ((fixed_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad fixed delay: %s", optarg);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 1)
+ usage(argv[0]);
+
+ if (random_delay > 0)
+ srand(getpid());
+
+ /*
+ * Initialize the message content, SMTP encoded. smtp_fputs() will append
+ * another \r\n but we don't care.
+ */
+ if (message_file != 0) {
+ VSTREAM *fp;
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *msg = vstring_alloc(100);
+
+ if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", message_file);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
+ if (*vstring_str(buf) == '.')
+ VSTRING_ADDCH(msg, '.');
+ vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf));
+ vstring_memcat(msg, "\r\n", 2);
+ }
+ if (vstream_ferror(fp))
+ msg_fatal("read %s: %m", message_file);
+ vstream_fclose(fp);
+ vstring_free(buf);
+ message_length = VSTRING_LEN(msg);
+ message_data = vstring_export(msg);
+ send_headers = 0;
+ } else if (message_length > 0) {
+ message_data = mymalloc(message_length);
+ memset(message_data, 'X', message_length);
+ for (i = 80; i < message_length; i += 80) {
+ message_data[i - 80] = "0123456789"[(i / 80) % 10];
+ message_data[i - 2] = '\r';
+ message_data[i - 1] = '\n';
+ }
+ }
+
+ /*
+ * Translate endpoint address to internal form.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ path = argv[optind] + 5;
+ path_len = strlen(path);
+ if (path_len >= (int) sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len);
+ sa = (struct sockaddr *) &sun;
+ sa_length = sizeof(sun);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ buf = mystrdup(argv[optind]);
+ if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0)
+ msg_fatal("%s: %s", argv[optind], parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr));
+ myfree(buf);
+ sa = (struct sockaddr *) &ss;
+ if (res->ai_addrlen > sizeof(ss))
+ msg_fatal("address length %d > buffer length %d",
+ (int) res->ai_addrlen, (int) sizeof(ss));
+ memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
+ sa_length = res->ai_addrlen;
+#ifdef HAS_SA_LEN
+ sa->sa_len = sa_length;
+#endif
+ freeaddrinfo(res);
+ }
+
+ /*
+ * smtp_get() makes sure the SMTP server cannot run us out of memory by
+ * sending never-ending lines of text.
+ */
+ if (buffer == 0)
+ buffer = vstring_alloc(100);
+
+ /*
+ * Make sure we have sender and recipient addresses.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = get_hostname();
+ if (sender == 0 || recipient == 0) {
+ vstring_sprintf(buffer, "foo@%s", var_myhostname);
+ defaddr = mystrdup(vstring_str(buffer));
+ if (sender == 0)
+ sender = defaddr;
+ if (recipient == 0)
+ recipient = defaddr;
+ }
+
+ /*
+ * Start sessions.
+ */
+ while (sessions-- > 0) {
+ session = (SESSION *) mymalloc(sizeof(*session));
+ session->stream = 0;
+ session->xfer_count = 0;
+ session->connect_count = connect_count;
+ session->next = 0;
+ session_count++;
+ startup(session);
+ }
+ for (;;) {
+ event_loop(-1);
+ if (session_count <= 0 && message_count <= 0) {
+ if (count) {
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+ }
+ }
+}
diff --git a/src/smtpstone/throughput b/src/smtpstone/throughput
new file mode 100644
index 0000000..4853d75
--- /dev/null
+++ b/src/smtpstone/throughput
@@ -0,0 +1,28 @@
+Host: P233 BSD/OS 3.1 smtp-source and smtp-sink on the same host,
+100 msgs in 10 sessions.
+
+send = time to send 100 msgs into postfix
+rest = time for Postfix to finish
+total = total elapsed time
+
+19990627
+
+send rest total
+14 10 25
+10 8 18
+ 9 10 19
+ 9 17 26
+ 8 11 19
+ 8 9 17
+
+19990906
+
+send rest total
+10 15 25
+ 9 10 19
+ 8 9 17
+ 9 8 17
+ 8 9 17
+ 9 8 17
+ 8 9 17
+ 8 8 16
diff --git a/src/smtpstone/vmail-local b/src/smtpstone/vmail-local
new file mode 100644
index 0000000..84269b2
--- /dev/null
+++ b/src/smtpstone/vmail-local
@@ -0,0 +1,43 @@
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:08:27 EDT 1998
+ 6.29 real 0.08 user 0.17 sys
+May 9 22:08:27 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:08:58 nipple postfix/local[2330]: 5082D53AD0: to=<wietse@porcupine.org
+Elapsed 31
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:13 EDT 1998
+ 5.94 real 0.09 user 0.16 sys
+May 9 22:09:13 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:09:40 nipple postfix/local[2260]: 5082FBE00B: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:43 EDT 1998
+ 5.97 real 0.06 user 0.17 sys
+May 9 22:09:43 nipple postfix/local[2321]: 5082C7E2F3: to=<wietse@porcupine.org
+May 9 22:10:13 nipple postfix/local[2517]: 5082C8772C: to=<wietse@porcupine.org
+Elapsed 30
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:10:30 EDT 1998
+ 6.26 real 0.08 user 0.20 sys
+May 9 22:10:30 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:10:58 nipple postfix/local[2330]: 5082C7A2C9: to=<wietse@porcupine.org
+Elapsed 28
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:06 EDT 1998
+ 6.04 real 0.05 user 0.25 sys
+May 9 22:11:06 nipple wietse[100]: postfix/smtpd[2675]: connect from fist.porcu
+May 9 22:11:33 nipple postfix/local[2685]: 5082D23842: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:38 EDT 1998
+ 6.11 real 0.06 user 0.24 sys
+May 9 22:11:38 nipple postfix/local[2686]: 5082E318AD: to=<wietse@porcupine.org
+May 9 22:12:07 nipple postfix/local[2687]: 5082CBF5EC: to=<wietse@porcupine.org
+Elapsed 31
+
+Elapsed: 29, throughput: 3.4 msg/s (local_delivery_concurrency not set)
diff --git a/src/smtpstone/vmail-relay b/src/smtpstone/vmail-relay
new file mode 100644
index 0000000..e9055b5
--- /dev/null
+++ b/src/smtpstone/vmail-relay
@@ -0,0 +1,57 @@
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:54:48 EDT 1998
+ 7.09 real 0.03 user 0.18 sys
+May 9 21:54:48 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:05 nipple wietse[100]: postfix/smtp[2215]: 508672B24D: to=<foo@fist
+Elapsed 17
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:09 EDT 1998
+ 6.02 real 0.09 user 0.13 sys
+May 9 21:55:09 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:23 nipple wietse[100]: postfix/smtp[2217]: 50864BCA59: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:26 EDT 1998
+ 6.06 real 0.07 user 0.17 sys
+May 9 21:55:27 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:55:41 nipple wietse[100]: postfix/smtp[2218]: 508753CB5D: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:55:53 EDT 1998
+ 6.01 real 0.13 user 0.14 sys
+May 9 21:55:53 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:08 nipple wietse[100]: postfix/smtp[2216]: 50868D8D44: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:10 EDT 1998
+ 6.01 real 0.14 user 0.13 sys
+May 9 21:56:10 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:24 nipple wietse[100]: postfix/smtp[2229]: 50875825BA: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:28 EDT 1998
+ 6.10 real 0.03 user 0.23 sys
+May 9 21:56:28 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:56:43 nipple wietse[100]: postfix/smtp[2229]: 508651D724: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:56:58 EDT 1998
+ 6.02 real 0.10 user 0.21 sys
+May 9 21:56:58 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:13 nipple wietse[100]: postfix/smtp[2231]: 5085D279A7: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:57:18 EDT 1998
+ 6.04 real 0.09 user 0.19 sys
+May 9 21:57:18 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:32 nipple wietse[100]: postfix/smtp[2230]: 508774A28A: to=<foo@fist
+Elapsed 14
+
+Elapsed: 14.4 s, throughput: 6.9 msg/s
diff --git a/src/spawn/.indent.pro b/src/spawn/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/spawn/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/spawn/.printfck b/src/spawn/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/spawn/.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/spawn/Makefile.in b/src/spawn/Makefile.in
new file mode 100644
index 0000000..74c6d6d
--- /dev/null
+++ b/src/spawn/Makefile.in
@@ -0,0 +1,82 @@
+SHELL = /bin/sh
+SRCS = spawn.c
+OBJS = spawn.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = spawn
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+spawn.o: ../../include/argv.h
+spawn.o: ../../include/check_arg.h
+spawn.o: ../../include/dict.h
+spawn.o: ../../include/htable.h
+spawn.o: ../../include/mail_conf.h
+spawn.o: ../../include/mail_params.h
+spawn.o: ../../include/mail_parm_split.h
+spawn.o: ../../include/mail_server.h
+spawn.o: ../../include/mail_version.h
+spawn.o: ../../include/msg.h
+spawn.o: ../../include/myflock.h
+spawn.o: ../../include/mymalloc.h
+spawn.o: ../../include/set_eugid.h
+spawn.o: ../../include/spawn_command.h
+spawn.o: ../../include/split_at.h
+spawn.o: ../../include/sys_defs.h
+spawn.o: ../../include/timed_wait.h
+spawn.o: ../../include/vbuf.h
+spawn.o: ../../include/vstream.h
+spawn.o: ../../include/vstring.h
+spawn.o: spawn.c
diff --git a/src/spawn/spawn.c b/src/spawn/spawn.c
new file mode 100644
index 0000000..686b0da
--- /dev/null
+++ b/src/spawn/spawn.c
@@ -0,0 +1,373 @@
+/*++
+/* NAME
+/* spawn 8
+/* SUMMARY
+/* Postfix external command spawner
+/* SYNOPSIS
+/* \fBspawn\fR [generic Postfix daemon options] command_attributes...
+/* DESCRIPTION
+/* The \fBspawn\fR(8) daemon provides the Postfix equivalent
+/* of \fBinetd\fR.
+/* It listens on a port as specified in the Postfix \fBmaster.cf\fR file
+/* and spawns an external command whenever a connection is established.
+/* The connection can be made over local IPC (such as UNIX-domain
+/* sockets) or over non-local IPC (such as TCP sockets).
+/* The command's standard input, output and error streams are connected
+/* directly to the communication endpoint.
+/*
+/* This daemon expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/* COMMAND ATTRIBUTE SYNTAX
+/* .ad
+/* .fi
+/* The external command attributes are given in the \fBmaster.cf\fR
+/* file at the end of a service definition. The syntax is as follows:
+/* .IP "\fBuser\fR=\fIusername\fR (required)"
+/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
+/* The external command is executed with the rights of the
+/* specified \fIusername\fR. The software refuses to execute
+/* commands with root privileges, or with the privileges of the
+/* mail system owner. If \fIgroupname\fR is specified, the
+/* corresponding group ID is used instead of the group ID
+/* of \fIusername\fR.
+/* .IP "\fBargv\fR=\fIcommand\fR... (required)"
+/* The command to be executed. This must be specified as the
+/* last command attribute.
+/* The command is executed directly, i.e. without interpretation of
+/* shell meta characters by a shell command interpreter.
+/* BUGS
+/* In order to enforce standard Postfix process resource controls,
+/* the \fBspawn\fR(8) daemon runs only one external command at a time.
+/* As such, it presents a noticeable overhead by wasting precious
+/* process resources. The \fBspawn\fR(8) daemon is expected to be
+/* replaced by a more structural solution.
+/* DIAGNOSTICS
+/* The \fBspawn\fR(8) daemon reports abnormal child exits.
+/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* SECURITY
+/* .fi
+/* .ad
+/* This program needs root privilege in order to execute external
+/* commands as the specified user. It is therefore security sensitive.
+/* However the \fBspawn\fR(8) daemon does not talk to the external command
+/* and thus is not vulnerable to data-driven attacks.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\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.
+/*
+/* In the text below, \fItransport\fR is the first field of the entry
+/* in the \fBmaster.cf\fR file.
+/* RESOURCE AND RATE CONTROL
+/* .ad
+/* .fi
+/* .IP "\fBtransport_time_limit ($command_time_limit)\fR"
+/* A transport-specific override for the command_time_limit parameter
+/* value, where \fItransport\fR is the master.cf name of the message
+/* delivery transport.
+/* MISCELLANEOUS
+/* .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 "\fBexport_environment (see 'postconf -d' output)\fR"
+/* The list of environment variables that a Postfix process will export
+/* to non-Postfix processes.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <spawn_command.h>
+#include <split_at.h>
+#include <timed_wait.h>
+#include <set_eugid.h>
+
+/* Global library. */
+
+#include <mail_version.h>
+
+/* Single server skeleton. */
+
+#include <mail_params.h>
+#include <mail_server.h>
+#include <mail_conf.h>
+#include <mail_parm_split.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters. Values are taken from the config file, after
+ * prepending the service name to _name, and so on.
+ */
+int var_command_maxtime; /* system-wide */
+
+ /*
+ * For convenience. Instead of passing around lists of parameters, bundle
+ * them up in convenient structures.
+ */
+typedef struct {
+ char **argv; /* argument vector */
+ uid_t uid; /* command privileges */
+ gid_t gid; /* command privileges */
+ int time_limit; /* per-service time limit */
+} SPAWN_ATTR;
+
+/* get_service_attr - get service attributes */
+
+static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
+{
+ const char *myname = "get_service_attr";
+ struct passwd *pwd;
+ struct group *grp;
+ char *user; /* user name */
+ char *group; /* group name */
+
+ /*
+ * Initialize.
+ */
+ user = 0;
+ group = 0;
+ attr->argv = 0;
+
+ /*
+ * Figure out the command time limit for this transport.
+ */
+ attr->time_limit =
+ get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
+
+ /*
+ * Iterate over the command-line attribute list.
+ */
+ for ( /* void */ ; *argv != 0; argv++) {
+
+ /*
+ * user=username[:groupname]
+ */
+ if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
+ user = *argv + sizeof("user=") - 1;
+ if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
+ if (*group == 0)
+ group = 0;
+ if ((pwd = getpwnam(user)) == 0)
+ msg_fatal("unknown user name: %s", user);
+ attr->uid = pwd->pw_uid;
+ if (group != 0) {
+ if ((grp = getgrnam(group)) == 0)
+ msg_fatal("unknown group name: %s", group);
+ attr->gid = grp->gr_gid;
+ } else {
+ attr->gid = pwd->pw_gid;
+ }
+ }
+
+ /*
+ * argv=command...
+ */
+ else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
+ *argv += sizeof("argv=") - 1; /* XXX clobbers argv */
+ attr->argv = argv;
+ break;
+ }
+
+ /*
+ * Bad.
+ */
+ else
+ msg_fatal("unknown attribute name: %s", *argv);
+ }
+
+ /*
+ * Sanity checks. Verify that every member has an acceptable value.
+ */
+ if (user == 0)
+ msg_fatal("missing user= attribute");
+ if (attr->argv == 0)
+ msg_fatal("missing argv= attribute");
+ if (attr->uid == 0)
+ msg_fatal("request to deliver as root");
+ if (attr->uid == var_owner_uid)
+ msg_fatal("request to deliver as mail system owner");
+ if (attr->gid == 0)
+ msg_fatal("request to use privileged group id %ld", (long) attr->gid);
+ if (attr->gid == var_owner_gid)
+ msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
+ if (attr->uid == (uid_t) (-1))
+ msg_fatal("user must not have user ID -1");
+ if (attr->gid == (gid_t) (-1))
+ msg_fatal("user must not have group ID -1");
+
+ /*
+ * Give the poor tester a clue of what is going on.
+ */
+ if (msg_verbose)
+ msg_info("%s: uid %ld, gid %ld; time %d",
+ myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
+}
+
+/* spawn_service - perform service for client */
+
+static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
+{
+ const char *myname = "spawn_service";
+ static SPAWN_ATTR attr;
+ WAIT_STATUS_T status;
+ ARGV *export_env;
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to running an external command.
+ */
+ if (msg_verbose)
+ msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
+
+ /*
+ * Look up service attributes and config information only once. This is
+ * safe since the information comes from a trusted source.
+ */
+ if (attr.argv == 0) {
+ get_service_attr(&attr, service, argv);
+ }
+
+ /*
+ * Execute the command.
+ */
+ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
+ status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)),
+ CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)),
+ CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)),
+ CA_SPAWN_CMD_UID(attr.uid),
+ CA_SPAWN_CMD_GID(attr.gid),
+ CA_SPAWN_CMD_ARGV(attr.argv),
+ CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit),
+ CA_SPAWN_CMD_EXPORT(export_env->argv),
+ CA_SPAWN_CMD_END);
+ argv_free(export_env);
+
+ /*
+ * Warn about unsuccessful completion.
+ */
+ if (!NORMAL_EXIT_STATUS(status)) {
+ if (WIFEXITED(status))
+ msg_warn("command %s exit status %d",
+ attr.argv[0], WEXITSTATUS(status));
+ if (WIFSIGNALED(status))
+ msg_warn("command %s killed by signal %d",
+ attr.argv[0], WTERMSIG(status));
+ }
+}
+
+/* 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);
+ }
+}
+
+/* drop_privileges - drop privileges most of the time */
+
+static void drop_privileges(char *unused_name, char **unused_argv)
+{
+ set_eugid(var_owner_uid, var_owner_gid);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, spawn_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_POST_INIT(drop_privileges),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_PRIVILEGED,
+ 0);
+}
diff --git a/src/tls/.indent.pro b/src/tls/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tls/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in
new file mode 100644
index 0000000..948afab
--- /dev/null
+++ b/src/tls/Makefile.in
@@ -0,0 +1,601 @@
+SHELL = /bin/sh
+SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \
+ tls_prng_exch.c tls_stream.c tls_bio_ops.c tls_misc.c tls_dh.c \
+ tls_verify.c tls_dane.c tls_certkey.c tls_session.c \
+ tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \
+ tls_level.c \
+ tls_proxy_clnt.c tls_proxy_context_print.c tls_proxy_context_scan.c \
+ tls_proxy_client_init_print.c tls_proxy_client_init_scan.c \
+ tls_proxy_server_init_print.c tls_proxy_server_init_scan.c \
+ tls_proxy_client_start_print.c tls_proxy_client_start_scan.c \
+ tls_proxy_server_start_print.c tls_proxy_server_start_scan.c \
+ tls_proxy_client_misc.c
+OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \
+ tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \
+ tls_verify.o tls_dane.o tls_certkey.o tls_session.o \
+ tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \
+ tls_level.o \
+ tls_proxy_clnt.o tls_proxy_context_print.o tls_proxy_context_scan.o \
+ tls_proxy_client_print.o tls_proxy_client_scan.o \
+ tls_proxy_server_print.o tls_proxy_server_scan.o \
+ tls_proxy_client_misc.o
+HDRS = tls.h tls_prng.h tls_scache.h tls_mgr.h tls_proxy.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)tls$(LIB_SUFFIX)
+TESTPROG= tls_dh tls_mgr tls_dane tls_certkey
+
+LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: tls_certkey_tests
+
+tls_certkey_tests: test
+ @echo Testing loading of keys and certs
+ @for pem in goodchains.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ done; \
+ for pem in *-mixed-*.pem ; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -m $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k $$pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in $$pem): OK"; \
+ case $$pem in good-*) \
+ ln -sf $$pem tmpkey.pem; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k tmpkey.pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in tmpkey.pem): OK"; \
+ rm -f tmpkey.pem;; \
+ esac; \
+ done; \
+ for pem in bad-*.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 && exit 1 || : ok; \
+ egrep -v 'TLS library problem' $$pem.out | diff $$pem.ref - || \
+ exit 1; \
+ echo " $$pem: OK"; \
+ done
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+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 $(LIB) *core $(TESTPROG) junk *.pem.out
+ rm -rf printfck
+
+tidy: clean
+
+tls_dh: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_mgr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_dane: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_certkey: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+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'
+tls_bio_ops.o: ../../include/argv.h
+tls_bio_ops.o: ../../include/check_arg.h
+tls_bio_ops.o: ../../include/dns.h
+tls_bio_ops.o: ../../include/iostuff.h
+tls_bio_ops.o: ../../include/msg.h
+tls_bio_ops.o: ../../include/myaddrinfo.h
+tls_bio_ops.o: ../../include/name_code.h
+tls_bio_ops.o: ../../include/name_mask.h
+tls_bio_ops.o: ../../include/sock_addr.h
+tls_bio_ops.o: ../../include/sys_defs.h
+tls_bio_ops.o: ../../include/vbuf.h
+tls_bio_ops.o: ../../include/vstream.h
+tls_bio_ops.o: ../../include/vstring.h
+tls_bio_ops.o: tls.h
+tls_bio_ops.o: tls_bio_ops.c
+tls_certkey.o: ../../include/argv.h
+tls_certkey.o: ../../include/check_arg.h
+tls_certkey.o: ../../include/dns.h
+tls_certkey.o: ../../include/mail_params.h
+tls_certkey.o: ../../include/msg.h
+tls_certkey.o: ../../include/myaddrinfo.h
+tls_certkey.o: ../../include/name_code.h
+tls_certkey.o: ../../include/name_mask.h
+tls_certkey.o: ../../include/sock_addr.h
+tls_certkey.o: ../../include/sys_defs.h
+tls_certkey.o: ../../include/vbuf.h
+tls_certkey.o: ../../include/vstream.h
+tls_certkey.o: ../../include/vstring.h
+tls_certkey.o: tls.h
+tls_certkey.o: tls_certkey.c
+tls_client.o: ../../include/argv.h
+tls_client.o: ../../include/check_arg.h
+tls_client.o: ../../include/dict.h
+tls_client.o: ../../include/dns.h
+tls_client.o: ../../include/iostuff.h
+tls_client.o: ../../include/mail_params.h
+tls_client.o: ../../include/midna_domain.h
+tls_client.o: ../../include/msg.h
+tls_client.o: ../../include/myaddrinfo.h
+tls_client.o: ../../include/myflock.h
+tls_client.o: ../../include/mymalloc.h
+tls_client.o: ../../include/name_code.h
+tls_client.o: ../../include/name_mask.h
+tls_client.o: ../../include/sock_addr.h
+tls_client.o: ../../include/stringops.h
+tls_client.o: ../../include/sys_defs.h
+tls_client.o: ../../include/vbuf.h
+tls_client.o: ../../include/vstream.h
+tls_client.o: ../../include/vstring.h
+tls_client.o: tls.h
+tls_client.o: tls_client.c
+tls_client.o: tls_mgr.h
+tls_client.o: tls_scache.h
+tls_dane.o: ../../include/argv.h
+tls_dane.o: ../../include/check_arg.h
+tls_dane.o: ../../include/ctable.h
+tls_dane.o: ../../include/dns.h
+tls_dane.o: ../../include/events.h
+tls_dane.o: ../../include/hex_code.h
+tls_dane.o: ../../include/mail_params.h
+tls_dane.o: ../../include/midna_domain.h
+tls_dane.o: ../../include/msg.h
+tls_dane.o: ../../include/myaddrinfo.h
+tls_dane.o: ../../include/mymalloc.h
+tls_dane.o: ../../include/name_code.h
+tls_dane.o: ../../include/name_mask.h
+tls_dane.o: ../../include/safe_ultostr.h
+tls_dane.o: ../../include/sock_addr.h
+tls_dane.o: ../../include/split_at.h
+tls_dane.o: ../../include/stringops.h
+tls_dane.o: ../../include/sys_defs.h
+tls_dane.o: ../../include/timecmp.h
+tls_dane.o: ../../include/vbuf.h
+tls_dane.o: ../../include/vstream.h
+tls_dane.o: ../../include/vstring.h
+tls_dane.o: tls.h
+tls_dane.o: tls_dane.c
+tls_dh.o: ../../include/argv.h
+tls_dh.o: ../../include/check_arg.h
+tls_dh.o: ../../include/dns.h
+tls_dh.o: ../../include/mail_params.h
+tls_dh.o: ../../include/msg.h
+tls_dh.o: ../../include/myaddrinfo.h
+tls_dh.o: ../../include/mymalloc.h
+tls_dh.o: ../../include/name_code.h
+tls_dh.o: ../../include/name_mask.h
+tls_dh.o: ../../include/sock_addr.h
+tls_dh.o: ../../include/stringops.h
+tls_dh.o: ../../include/sys_defs.h
+tls_dh.o: ../../include/vbuf.h
+tls_dh.o: ../../include/vstream.h
+tls_dh.o: ../../include/vstring.h
+tls_dh.o: tls.h
+tls_dh.o: tls_dh.c
+tls_fprint.o: ../../include/argv.h
+tls_fprint.o: ../../include/check_arg.h
+tls_fprint.o: ../../include/dns.h
+tls_fprint.o: ../../include/mail_params.h
+tls_fprint.o: ../../include/msg.h
+tls_fprint.o: ../../include/myaddrinfo.h
+tls_fprint.o: ../../include/mymalloc.h
+tls_fprint.o: ../../include/name_code.h
+tls_fprint.o: ../../include/name_mask.h
+tls_fprint.o: ../../include/sock_addr.h
+tls_fprint.o: ../../include/stringops.h
+tls_fprint.o: ../../include/sys_defs.h
+tls_fprint.o: ../../include/vbuf.h
+tls_fprint.o: ../../include/vstream.h
+tls_fprint.o: ../../include/vstring.h
+tls_fprint.o: tls.h
+tls_fprint.o: tls_fprint.c
+tls_level.o: ../../include/argv.h
+tls_level.o: ../../include/check_arg.h
+tls_level.o: ../../include/dns.h
+tls_level.o: ../../include/myaddrinfo.h
+tls_level.o: ../../include/name_code.h
+tls_level.o: ../../include/name_mask.h
+tls_level.o: ../../include/sock_addr.h
+tls_level.o: ../../include/sys_defs.h
+tls_level.o: ../../include/vbuf.h
+tls_level.o: ../../include/vstream.h
+tls_level.o: ../../include/vstring.h
+tls_level.o: tls.h
+tls_level.o: tls_level.c
+tls_mgr.o: ../../include/argv.h
+tls_mgr.o: ../../include/attr.h
+tls_mgr.o: ../../include/attr_clnt.h
+tls_mgr.o: ../../include/check_arg.h
+tls_mgr.o: ../../include/dict.h
+tls_mgr.o: ../../include/htable.h
+tls_mgr.o: ../../include/iostuff.h
+tls_mgr.o: ../../include/mail_params.h
+tls_mgr.o: ../../include/mail_proto.h
+tls_mgr.o: ../../include/msg.h
+tls_mgr.o: ../../include/myflock.h
+tls_mgr.o: ../../include/mymalloc.h
+tls_mgr.o: ../../include/nvtable.h
+tls_mgr.o: ../../include/stringops.h
+tls_mgr.o: ../../include/sys_defs.h
+tls_mgr.o: ../../include/vbuf.h
+tls_mgr.o: ../../include/vstream.h
+tls_mgr.o: ../../include/vstring.h
+tls_mgr.o: tls_mgr.c
+tls_mgr.o: tls_mgr.h
+tls_mgr.o: tls_scache.h
+tls_misc.o: ../../include/argv.h
+tls_misc.o: ../../include/check_arg.h
+tls_misc.o: ../../include/dict.h
+tls_misc.o: ../../include/dns.h
+tls_misc.o: ../../include/mail_conf.h
+tls_misc.o: ../../include/mail_params.h
+tls_misc.o: ../../include/maps.h
+tls_misc.o: ../../include/msg.h
+tls_misc.o: ../../include/myaddrinfo.h
+tls_misc.o: ../../include/myflock.h
+tls_misc.o: ../../include/mymalloc.h
+tls_misc.o: ../../include/name_code.h
+tls_misc.o: ../../include/name_mask.h
+tls_misc.o: ../../include/sock_addr.h
+tls_misc.o: ../../include/stringops.h
+tls_misc.o: ../../include/sys_defs.h
+tls_misc.o: ../../include/valid_hostname.h
+tls_misc.o: ../../include/vbuf.h
+tls_misc.o: ../../include/vstream.h
+tls_misc.o: ../../include/vstring.h
+tls_misc.o: tls.h
+tls_misc.o: tls_misc.c
+tls_prng_dev.o: ../../include/connect.h
+tls_prng_dev.o: ../../include/iostuff.h
+tls_prng_dev.o: ../../include/msg.h
+tls_prng_dev.o: ../../include/mymalloc.h
+tls_prng_dev.o: ../../include/sys_defs.h
+tls_prng_dev.o: tls_prng.h
+tls_prng_dev.o: tls_prng_dev.c
+tls_prng_egd.o: ../../include/connect.h
+tls_prng_egd.o: ../../include/iostuff.h
+tls_prng_egd.o: ../../include/msg.h
+tls_prng_egd.o: ../../include/mymalloc.h
+tls_prng_egd.o: ../../include/sys_defs.h
+tls_prng_egd.o: tls_prng.h
+tls_prng_egd.o: tls_prng_egd.c
+tls_prng_exch.o: ../../include/iostuff.h
+tls_prng_exch.o: ../../include/msg.h
+tls_prng_exch.o: ../../include/myflock.h
+tls_prng_exch.o: ../../include/mymalloc.h
+tls_prng_exch.o: ../../include/sys_defs.h
+tls_prng_exch.o: tls_prng.h
+tls_prng_exch.o: tls_prng_exch.c
+tls_prng_file.o: ../../include/connect.h
+tls_prng_file.o: ../../include/iostuff.h
+tls_prng_file.o: ../../include/msg.h
+tls_prng_file.o: ../../include/mymalloc.h
+tls_prng_file.o: ../../include/sys_defs.h
+tls_prng_file.o: tls_prng.h
+tls_prng_file.o: tls_prng_file.c
+tls_proxy_client_misc.o: ../../include/argv.h
+tls_proxy_client_misc.o: ../../include/attr.h
+tls_proxy_client_misc.o: ../../include/check_arg.h
+tls_proxy_client_misc.o: ../../include/dns.h
+tls_proxy_client_misc.o: ../../include/htable.h
+tls_proxy_client_misc.o: ../../include/mail_params.h
+tls_proxy_client_misc.o: ../../include/msg.h
+tls_proxy_client_misc.o: ../../include/myaddrinfo.h
+tls_proxy_client_misc.o: ../../include/mymalloc.h
+tls_proxy_client_misc.o: ../../include/name_code.h
+tls_proxy_client_misc.o: ../../include/name_mask.h
+tls_proxy_client_misc.o: ../../include/nvtable.h
+tls_proxy_client_misc.o: ../../include/sock_addr.h
+tls_proxy_client_misc.o: ../../include/sys_defs.h
+tls_proxy_client_misc.o: ../../include/vbuf.h
+tls_proxy_client_misc.o: ../../include/vstream.h
+tls_proxy_client_misc.o: ../../include/vstring.h
+tls_proxy_client_misc.o: tls.h
+tls_proxy_client_misc.o: tls_proxy.h
+tls_proxy_client_misc.o: tls_proxy_client_misc.c
+tls_proxy_client_print.o: ../../include/argv.h
+tls_proxy_client_print.o: ../../include/argv_attr.h
+tls_proxy_client_print.o: ../../include/attr.h
+tls_proxy_client_print.o: ../../include/check_arg.h
+tls_proxy_client_print.o: ../../include/dns.h
+tls_proxy_client_print.o: ../../include/htable.h
+tls_proxy_client_print.o: ../../include/mail_params.h
+tls_proxy_client_print.o: ../../include/msg.h
+tls_proxy_client_print.o: ../../include/myaddrinfo.h
+tls_proxy_client_print.o: ../../include/mymalloc.h
+tls_proxy_client_print.o: ../../include/name_code.h
+tls_proxy_client_print.o: ../../include/name_mask.h
+tls_proxy_client_print.o: ../../include/nvtable.h
+tls_proxy_client_print.o: ../../include/sock_addr.h
+tls_proxy_client_print.o: ../../include/sys_defs.h
+tls_proxy_client_print.o: ../../include/vbuf.h
+tls_proxy_client_print.o: ../../include/vstream.h
+tls_proxy_client_print.o: ../../include/vstring.h
+tls_proxy_client_print.o: tls.h
+tls_proxy_client_print.o: tls_proxy.h
+tls_proxy_client_print.o: tls_proxy_client_print.c
+tls_proxy_client_scan.o: ../../include/argv.h
+tls_proxy_client_scan.o: ../../include/argv_attr.h
+tls_proxy_client_scan.o: ../../include/attr.h
+tls_proxy_client_scan.o: ../../include/check_arg.h
+tls_proxy_client_scan.o: ../../include/dns.h
+tls_proxy_client_scan.o: ../../include/htable.h
+tls_proxy_client_scan.o: ../../include/mail_params.h
+tls_proxy_client_scan.o: ../../include/msg.h
+tls_proxy_client_scan.o: ../../include/myaddrinfo.h
+tls_proxy_client_scan.o: ../../include/mymalloc.h
+tls_proxy_client_scan.o: ../../include/name_code.h
+tls_proxy_client_scan.o: ../../include/name_mask.h
+tls_proxy_client_scan.o: ../../include/nvtable.h
+tls_proxy_client_scan.o: ../../include/sock_addr.h
+tls_proxy_client_scan.o: ../../include/sys_defs.h
+tls_proxy_client_scan.o: ../../include/vbuf.h
+tls_proxy_client_scan.o: ../../include/vstream.h
+tls_proxy_client_scan.o: ../../include/vstring.h
+tls_proxy_client_scan.o: tls.h
+tls_proxy_client_scan.o: tls_proxy.h
+tls_proxy_client_scan.o: tls_proxy_client_scan.c
+tls_proxy_clnt.o: ../../include/argv.h
+tls_proxy_clnt.o: ../../include/attr.h
+tls_proxy_clnt.o: ../../include/check_arg.h
+tls_proxy_clnt.o: ../../include/connect.h
+tls_proxy_clnt.o: ../../include/dns.h
+tls_proxy_clnt.o: ../../include/htable.h
+tls_proxy_clnt.o: ../../include/iostuff.h
+tls_proxy_clnt.o: ../../include/mail_params.h
+tls_proxy_clnt.o: ../../include/mail_proto.h
+tls_proxy_clnt.o: ../../include/msg.h
+tls_proxy_clnt.o: ../../include/myaddrinfo.h
+tls_proxy_clnt.o: ../../include/mymalloc.h
+tls_proxy_clnt.o: ../../include/name_code.h
+tls_proxy_clnt.o: ../../include/name_mask.h
+tls_proxy_clnt.o: ../../include/nvtable.h
+tls_proxy_clnt.o: ../../include/sock_addr.h
+tls_proxy_clnt.o: ../../include/stringops.h
+tls_proxy_clnt.o: ../../include/sys_defs.h
+tls_proxy_clnt.o: ../../include/vbuf.h
+tls_proxy_clnt.o: ../../include/vstream.h
+tls_proxy_clnt.o: ../../include/vstring.h
+tls_proxy_clnt.o: tls.h
+tls_proxy_clnt.o: tls_proxy.h
+tls_proxy_clnt.o: tls_proxy_clnt.c
+tls_proxy_context_print.o: ../../include/argv.h
+tls_proxy_context_print.o: ../../include/attr.h
+tls_proxy_context_print.o: ../../include/check_arg.h
+tls_proxy_context_print.o: ../../include/dns.h
+tls_proxy_context_print.o: ../../include/htable.h
+tls_proxy_context_print.o: ../../include/myaddrinfo.h
+tls_proxy_context_print.o: ../../include/mymalloc.h
+tls_proxy_context_print.o: ../../include/name_code.h
+tls_proxy_context_print.o: ../../include/name_mask.h
+tls_proxy_context_print.o: ../../include/nvtable.h
+tls_proxy_context_print.o: ../../include/sock_addr.h
+tls_proxy_context_print.o: ../../include/sys_defs.h
+tls_proxy_context_print.o: ../../include/vbuf.h
+tls_proxy_context_print.o: ../../include/vstream.h
+tls_proxy_context_print.o: ../../include/vstring.h
+tls_proxy_context_print.o: tls.h
+tls_proxy_context_print.o: tls_proxy.h
+tls_proxy_context_print.o: tls_proxy_context_print.c
+tls_proxy_context_scan.o: ../../include/argv.h
+tls_proxy_context_scan.o: ../../include/attr.h
+tls_proxy_context_scan.o: ../../include/check_arg.h
+tls_proxy_context_scan.o: ../../include/dns.h
+tls_proxy_context_scan.o: ../../include/htable.h
+tls_proxy_context_scan.o: ../../include/msg.h
+tls_proxy_context_scan.o: ../../include/myaddrinfo.h
+tls_proxy_context_scan.o: ../../include/mymalloc.h
+tls_proxy_context_scan.o: ../../include/name_code.h
+tls_proxy_context_scan.o: ../../include/name_mask.h
+tls_proxy_context_scan.o: ../../include/nvtable.h
+tls_proxy_context_scan.o: ../../include/sock_addr.h
+tls_proxy_context_scan.o: ../../include/sys_defs.h
+tls_proxy_context_scan.o: ../../include/vbuf.h
+tls_proxy_context_scan.o: ../../include/vstream.h
+tls_proxy_context_scan.o: ../../include/vstring.h
+tls_proxy_context_scan.o: tls.h
+tls_proxy_context_scan.o: tls_proxy.h
+tls_proxy_context_scan.o: tls_proxy_context_scan.c
+tls_proxy_server_print.o: ../../include/argv.h
+tls_proxy_server_print.o: ../../include/attr.h
+tls_proxy_server_print.o: ../../include/check_arg.h
+tls_proxy_server_print.o: ../../include/dns.h
+tls_proxy_server_print.o: ../../include/htable.h
+tls_proxy_server_print.o: ../../include/myaddrinfo.h
+tls_proxy_server_print.o: ../../include/mymalloc.h
+tls_proxy_server_print.o: ../../include/name_code.h
+tls_proxy_server_print.o: ../../include/name_mask.h
+tls_proxy_server_print.o: ../../include/nvtable.h
+tls_proxy_server_print.o: ../../include/sock_addr.h
+tls_proxy_server_print.o: ../../include/sys_defs.h
+tls_proxy_server_print.o: ../../include/vbuf.h
+tls_proxy_server_print.o: ../../include/vstream.h
+tls_proxy_server_print.o: ../../include/vstring.h
+tls_proxy_server_print.o: tls.h
+tls_proxy_server_print.o: tls_proxy.h
+tls_proxy_server_print.o: tls_proxy_server_print.c
+tls_proxy_server_scan.o: ../../include/argv.h
+tls_proxy_server_scan.o: ../../include/attr.h
+tls_proxy_server_scan.o: ../../include/check_arg.h
+tls_proxy_server_scan.o: ../../include/dns.h
+tls_proxy_server_scan.o: ../../include/htable.h
+tls_proxy_server_scan.o: ../../include/myaddrinfo.h
+tls_proxy_server_scan.o: ../../include/mymalloc.h
+tls_proxy_server_scan.o: ../../include/name_code.h
+tls_proxy_server_scan.o: ../../include/name_mask.h
+tls_proxy_server_scan.o: ../../include/nvtable.h
+tls_proxy_server_scan.o: ../../include/sock_addr.h
+tls_proxy_server_scan.o: ../../include/sys_defs.h
+tls_proxy_server_scan.o: ../../include/vbuf.h
+tls_proxy_server_scan.o: ../../include/vstream.h
+tls_proxy_server_scan.o: ../../include/vstring.h
+tls_proxy_server_scan.o: tls.h
+tls_proxy_server_scan.o: tls_proxy.h
+tls_proxy_server_scan.o: tls_proxy_server_scan.c
+tls_rsa.o: tls_rsa.c
+tls_scache.o: ../../include/argv.h
+tls_scache.o: ../../include/check_arg.h
+tls_scache.o: ../../include/dict.h
+tls_scache.o: ../../include/hex_code.h
+tls_scache.o: ../../include/msg.h
+tls_scache.o: ../../include/myflock.h
+tls_scache.o: ../../include/mymalloc.h
+tls_scache.o: ../../include/stringops.h
+tls_scache.o: ../../include/sys_defs.h
+tls_scache.o: ../../include/timecmp.h
+tls_scache.o: ../../include/vbuf.h
+tls_scache.o: ../../include/vstream.h
+tls_scache.o: ../../include/vstring.h
+tls_scache.o: tls_scache.c
+tls_scache.o: tls_scache.h
+tls_seed.o: ../../include/argv.h
+tls_seed.o: ../../include/check_arg.h
+tls_seed.o: ../../include/dict.h
+tls_seed.o: ../../include/dns.h
+tls_seed.o: ../../include/msg.h
+tls_seed.o: ../../include/myaddrinfo.h
+tls_seed.o: ../../include/myflock.h
+tls_seed.o: ../../include/name_code.h
+tls_seed.o: ../../include/name_mask.h
+tls_seed.o: ../../include/sock_addr.h
+tls_seed.o: ../../include/sys_defs.h
+tls_seed.o: ../../include/vbuf.h
+tls_seed.o: ../../include/vstream.h
+tls_seed.o: ../../include/vstring.h
+tls_seed.o: tls.h
+tls_seed.o: tls_mgr.h
+tls_seed.o: tls_scache.h
+tls_seed.o: tls_seed.c
+tls_server.o: ../../include/argv.h
+tls_server.o: ../../include/check_arg.h
+tls_server.o: ../../include/dict.h
+tls_server.o: ../../include/dns.h
+tls_server.o: ../../include/hex_code.h
+tls_server.o: ../../include/iostuff.h
+tls_server.o: ../../include/mail_params.h
+tls_server.o: ../../include/msg.h
+tls_server.o: ../../include/myaddrinfo.h
+tls_server.o: ../../include/myflock.h
+tls_server.o: ../../include/mymalloc.h
+tls_server.o: ../../include/name_code.h
+tls_server.o: ../../include/name_mask.h
+tls_server.o: ../../include/sock_addr.h
+tls_server.o: ../../include/stringops.h
+tls_server.o: ../../include/sys_defs.h
+tls_server.o: ../../include/vbuf.h
+tls_server.o: ../../include/vstream.h
+tls_server.o: ../../include/vstring.h
+tls_server.o: tls.h
+tls_server.o: tls_mgr.h
+tls_server.o: tls_scache.h
+tls_server.o: tls_server.c
+tls_session.o: ../../include/argv.h
+tls_session.o: ../../include/check_arg.h
+tls_session.o: ../../include/dns.h
+tls_session.o: ../../include/mail_params.h
+tls_session.o: ../../include/msg.h
+tls_session.o: ../../include/myaddrinfo.h
+tls_session.o: ../../include/mymalloc.h
+tls_session.o: ../../include/name_code.h
+tls_session.o: ../../include/name_mask.h
+tls_session.o: ../../include/sock_addr.h
+tls_session.o: ../../include/sys_defs.h
+tls_session.o: ../../include/vbuf.h
+tls_session.o: ../../include/vstream.h
+tls_session.o: ../../include/vstring.h
+tls_session.o: tls.h
+tls_session.o: tls_session.c
+tls_stream.o: ../../include/argv.h
+tls_stream.o: ../../include/check_arg.h
+tls_stream.o: ../../include/dns.h
+tls_stream.o: ../../include/iostuff.h
+tls_stream.o: ../../include/msg.h
+tls_stream.o: ../../include/myaddrinfo.h
+tls_stream.o: ../../include/name_code.h
+tls_stream.o: ../../include/name_mask.h
+tls_stream.o: ../../include/sock_addr.h
+tls_stream.o: ../../include/sys_defs.h
+tls_stream.o: ../../include/vbuf.h
+tls_stream.o: ../../include/vstream.h
+tls_stream.o: ../../include/vstring.h
+tls_stream.o: tls.h
+tls_stream.o: tls_stream.c
+tls_verify.o: ../../include/argv.h
+tls_verify.o: ../../include/check_arg.h
+tls_verify.o: ../../include/dns.h
+tls_verify.o: ../../include/msg.h
+tls_verify.o: ../../include/myaddrinfo.h
+tls_verify.o: ../../include/mymalloc.h
+tls_verify.o: ../../include/name_code.h
+tls_verify.o: ../../include/name_mask.h
+tls_verify.o: ../../include/sock_addr.h
+tls_verify.o: ../../include/stringops.h
+tls_verify.o: ../../include/sys_defs.h
+tls_verify.o: ../../include/vbuf.h
+tls_verify.o: ../../include/vstream.h
+tls_verify.o: ../../include/vstring.h
+tls_verify.o: tls.h
+tls_verify.o: tls_verify.c
diff --git a/src/tls/bad-back-to-back-keys.pem b/src/tls/bad-back-to-back-keys.pem
new file mode 100644
index 0000000..b1895ec
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-back-to-back-keys.pem.ref b/src/tls/bad-back-to-back-keys.pem.ref
new file mode 100644
index 0000000..192e9ea
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading certificate chain: key at index 1 in bad-back-to-back-keys.pem not followed by a certificate
+unknown: warning: error loading private keys and certificates from: bad-back-to-back-keys.pem: disabling TLS support
diff --git a/src/tls/bad-ec-cert-before-key.pem b/src/tls/bad-ec-cert-before-key.pem
new file mode 100644
index 0000000..520927a
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-ec-cert-before-key.pem.ref b/src/tls/bad-ec-cert-before-key.pem.ref
new file mode 100644
index 0000000..71e5f78
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading chain from bad-ec-cert-before-key.pem: key not first
+unknown: warning: error loading private keys and certificates from: bad-ec-cert-before-key.pem: disabling TLS support
diff --git a/src/tls/bad-key-cert-mismatch.pem b/src/tls/bad-key-cert-mismatch.pem
new file mode 100644
index 0000000..1e78833
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem
@@ -0,0 +1,36 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-key-cert-mismatch.pem.ref b/src/tls/bad-key-cert-mismatch.pem.ref
new file mode 100644
index 0000000..a32b657
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: key at index 1 in bad-key-cert-mismatch.pem does not match next certificate
+unknown: warning: error loading private keys and certificates from: bad-key-cert-mismatch.pem: disabling TLS support
diff --git a/src/tls/bad-rsa-key-last.pem b/src/tls/bad-rsa-key-last.pem
new file mode 100644
index 0000000..de15808
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/bad-rsa-key-last.pem.ref b/src/tls/bad-rsa-key-last.pem.ref
new file mode 100644
index 0000000..a72c0d9
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: No certs for key at index 5 in bad-rsa-key-last.pem
+unknown: warning: error loading private keys and certificates from: bad-rsa-key-last.pem: disabling TLS support
diff --git a/src/tls/ecca-cert.pem b/src/tls/ecca-cert.pem
new file mode 100644
index 0000000..54418f1
--- /dev/null
+++ b/src/tls/ecca-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
diff --git a/src/tls/ecca-pkey.pem b/src/tls/ecca-pkey.pem
new file mode 100644
index 0000000..2d95f45
--- /dev/null
+++ b/src/tls/ecca-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecee-cert.pem b/src/tls/ecee-cert.pem
new file mode 100644
index 0000000..1afb2f5
--- /dev/null
+++ b/src/tls/ecee-cert.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
diff --git a/src/tls/ecee-pkey.pem b/src/tls/ecee-pkey.pem
new file mode 100644
index 0000000..ffec716
--- /dev/null
+++ b/src/tls/ecee-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecroot-cert.pem b/src/tls/ecroot-cert.pem
new file mode 100644
index 0000000..4f5041c
--- /dev/null
+++ b/src/tls/ecroot-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/ecroot-pkey.pem b/src/tls/ecroot-pkey.pem
new file mode 100644
index 0000000..0d36dc4
--- /dev/null
+++ b/src/tls/ecroot-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg85iR6mrXjfV9O38h
+9/KivPeTKxdjXHQ6j2VCWD8K8KahRANCAAR8MXvjqDNXBKh79SxW0WpJfUBIptgx
+1xwT2k4HkP98rlo+pmzL39gpIT1P78VwKBlrq4RE7JHnzdlQzJNAWgdG
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keyfirst.pem b/src/tls/good-mixed-keyfirst.pem
new file mode 100644
index 0000000..c7ba743
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem
@@ -0,0 +1,45 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
diff --git a/src/tls/good-mixed-keyfirst.pem.ref b/src/tls/good-mixed-keyfirst.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keylast.pem b/src/tls/good-mixed-keylast.pem
new file mode 100644
index 0000000..6ce758e
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keylast.pem.ref b/src/tls/good-mixed-keylast.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keymiddle.pem b/src/tls/good-mixed-keymiddle.pem
new file mode 100644
index 0000000..b3dda42
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/good-mixed-keymiddle.pem.ref b/src/tls/good-mixed-keymiddle.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/goodchains.pem b/src/tls/goodchains.pem
new file mode 100644
index 0000000..81b2b32
--- /dev/null
+++ b/src/tls/goodchains.pem
@@ -0,0 +1,121 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/goodchains.pem.ref b/src/tls/goodchains.pem.ref
new file mode 100644
index 0000000..ade7562
--- /dev/null
+++ b/src/tls/goodchains.pem.ref
@@ -0,0 +1,24 @@
+depth = 0
+issuer = /CN=RSA issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=RSA root CA
+subject = /CN=RSA issuer CA
+
+depth = 2
+issuer = /CN=RSA root CA
+subject = /CN=RSA root CA
+
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/mkcert.sh b/src/tls/mkcert.sh
new file mode 100644
index 0000000..c15f4e1
--- /dev/null
+++ b/src/tls/mkcert.sh
@@ -0,0 +1,264 @@
+#! /bin/bash
+#
+# Copyright (c) 2016 Viktor Dukhovni <openssl-users@dukhovni.org>.
+# All rights reserved.
+#
+# Licensed under the terms of the Postfix SECURE MAILER license
+# included with the Postfix source code.
+#
+# This file is dual-licensed and is also available under other terms.
+# Please contact the author.
+
+# 100 years should be enough for now
+if [ -z "$DAYS" ]; then
+ DAYS=36525
+fi
+
+if [ -z "$OPENSSL_SIGALG" ]; then
+ OPENSSL_SIGALG=sha256
+fi
+
+case ${OPENSSL_SIGALG} in
+ null) dopts=();;
+ *) dopts=(-"${OPENSSL_SIGALG}");;
+esac
+
+if [ -z "$REQMASK" ]; then
+ REQMASK=utf8only
+fi
+
+stderr_onerror() {
+ (
+ err=$("$@" >&3 2>&1) || {
+ printf "%s\n" "$err" >&2
+ exit 1
+ }
+ ) 3>&1
+}
+
+key() {
+ local key=$1; shift
+
+ local alg=rsa
+ if [ -n "$OPENSSL_KEYALG" ]; then
+ alg=$OPENSSL_KEYALG
+ fi
+
+ local bits=2048
+ if [ -n "$OPENSSL_KEYBITS" ]; then
+ bits=$OPENSSL_KEYBITS
+ fi
+
+ if [ ! -f "${key}.pem" ]; then
+ args=(-algorithm "$alg")
+ case $alg in
+ rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );;
+ ec) args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits")
+ args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);;
+ dsa) args=(-paramfile "$bits");;
+ ed25519) ;;
+ ed448) ;;
+ *) printf "Unsupported key algorithm: %s\n" "$alg" >&2; return 1;;
+ esac
+ stderr_onerror \
+ openssl genpkey "${args[@]}" -out "${key}.pem"
+ fi
+}
+
+# Usage: $0 req keyname dn1 dn2 ...
+req() {
+ local key=$1; shift
+
+ key "$key"
+ local errs
+
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -key "${key}.pem" \
+ -config <(printf "string_mask=%s\n[req]\n%s\n%s\n[dn]\n" \
+ "$REQMASK" "prompt = no" "distinguished_name = dn"
+ for dn in "$@"; do echo "$dn"; done)
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -subj / -key "${key}.pem" \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ stderr_onerror \
+ openssl x509 "${dopts[@]}" -req -out "${cert}.pem" \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days "${DAYS}"
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ if [ -n "$NC" ]; then
+ exts=$(printf "%s\nnameConstraints = %s\n" "$exts" "$NC")
+ fi
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+gen_nonbc_ca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid")
+ exts=$(printf "%s\nkeyUsage = %s\n" "$exts" "keyCertSign, cRLSign")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 genpc keyname certname eekeyname eecertname pcext1 pcext2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 genpc keyname certname eekeyname eecertname pcext ...
+genpc() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer:always" \
+ "basicConstraints = CA:false" \
+ "proxyCertInfo = critical, @pcexts";
+ echo "[pcexts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 geneealt keyname certname eekeyname eecertname alt1 alt2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 geneealt keyname certname eekeyname eecertname alt ...
+geneealt() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid" \
+ "basicConstraints = CA:false" \
+ "subjectAltName = @alts";
+ echo "[alts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+genee() {
+ local OPTIND=1
+ local purpose=serverAuth
+
+ while getopts p: o
+ do
+ case $o in
+ p) purpose="$OPTARG";;
+ *) echo "Usage: $0 genee [-p EKU] cn keyname certname cakeyname cacertname" >&2
+ return 1;;
+ esac
+ done
+
+ shift $((OPTIND - 1))
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = $purpose" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}" "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" \
+ -set_serial 1 -days "${DAYS}" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ csr=$(req_nocn "$key") || return 1
+ echo "$csr" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+"$@"
diff --git a/src/tls/rsaca-cert.pem b/src/tls/rsaca-cert.pem
new file mode 100644
index 0000000..1be736a
--- /dev/null
+++ b/src/tls/rsaca-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaca-pkey.pem b/src/tls/rsaca-pkey.pem
new file mode 100644
index 0000000..2381ae6
--- /dev/null
+++ b/src/tls/rsaca-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC97S2eIXPEZiF9
+GHdLo9Wd9ZiveCjHFTf/y9Iokf2Tha9/ZmtmLOwC9FEHlR/bOIVGjDPdZ9H5MFS9
+K/DVLApLj0vzLyQpVsU2zd6xM82mow/gXcltWNUH3RCObQCNsEkonhdxrYk5sR+N
+5Qx03mc9KocIfDTc4fo9PQ8GEvk30Jzu9C3ZkeVZLNEqBQ5sU7Nvw0bqf4glhnfO
+dwt0/5X4sXYZHVcBjbsbbhev8UhEIq09jlI451SHAjxqtGW894RSdMR6KosDQb/T
+8rXy5OATX/lVkwDRV58zb2alqzjvMkFkvkRyHX9vmvehthBSIFs/68KOFj/baMxE
+5T80HOj/AgMBAAECggEBAIY9Ch4XRMzO5uKVFVRoEwcXXHjBNAkqTS9F719veEv5
+lEY2rLhGDfY0msUCOMboVwK6+7mEtNsstugSE6GIBCrNuH/ElQmG49NNhRW4KKWb
+4Q/TGhhoTgHF1PrlvhtnOv4zZxyY7EHTmBrhhoFf5JZN5a0wpOht7EG2U1UWugEg
+/fOXp9XUsmJRxwp1jukqIi71GDVtoq3XCK7clluvNdeSlBn7D8dZ8cYUnMkqqaZA
+YYamyP3qF6UdxblW3xzGC3/0sLQvmHZOlMBuSpWUgr6NDsQPG4qXq10XePeteViw
+WnUqu11ILp8Ivoc20wJkokj1dCN3MOO0MyGcCxEX+5ECgYEA/P+Y+0AXq1j9yjNM
+aaXT3EZwSSAP/5MtZRiomFVwCgYbH6/hUV+OVih20Yhsl3MA5vgQTpSThZmmcBPC
+c3bn53OXVi6jnSzNzuQW28R8pSGoNnGT6TzG+J+my7ZxA0NTCAa72qhDgbI0dqd/
+sjk1E6hRtOkEI8fD5/IipkvxCpcCgYEAwC4FA0AA6Oy2YOAi1KeACZpIKIDcOFJz
+TLfuxhwnNi5C/1i99d5W0doCzaneECyadhGKPbv0DlVS2fe9Xm06PQLD2Y/+vkag
+iQ7V31JfwvoTQNTPVqnGcRu2ZmzI8Nja4/BcEEoxpPbMVbQ1ualkqcwpYu66ZlbD
+yWpCLSGFadkCgYEAvAkoZYzkSqjwr4jjAR6L0QrVR7Q5z8VOlvX10Iqno/uXyzxI
+Zdd0jdqzPNZ9hy6lfATg8daBsmlZh7FX88NrZt3Fm/s8BYSYTm2+A4cM8RqL0DMo
+MNDIPV9Dc+LcKgWuv6dplYE78zhEv++L/CWCqmKOn7wUJJfDpi+Tyy9kLm8CgYAy
++gIKYqfbIS8fc1TJ48Rqx6nsVIIVzokXCJMlqcIc9RiAcyGwXlHZSGMF+tEUqUAv
+oWdyCLEsPCXF+5kXuxF/rYQV6cRA5Ksgr/a7TjZomb0RrWFyM4aX6inv8Vs7x8oI
+PHGvQH76qxx4f1zg6rXw9F7mBz0aeFlmy/DR19pzwQKBgQCJWwlv7zisuTDXAuG/
+KF4VV6D2Y00pEHsxVx/RGeUXm+3HmqrZbcnccgivxyNMIAfuE5+7s2wfnjG38nnw
+DbZtp+rEcKX1h+95QnOlFxiowdlfX/ueOC2J59+Dc+R7UoDxqkQrSbdE5advHIl9
+Xb64SvwMhLzJYSYacv+fogoAPw==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaee-cert.pem b/src/tls/rsaee-cert.pem
new file mode 100644
index 0000000..9f923b4
--- /dev/null
+++ b/src/tls/rsaee-cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaee-pkey.pem b/src/tls/rsaee-pkey.pem
new file mode 100644
index 0000000..aa4e498
--- /dev/null
+++ b/src/tls/rsaee-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaroot-cert.pem b/src/tls/rsaroot-cert.pem
new file mode 100644
index 0000000..3911451
--- /dev/null
+++ b/src/tls/rsaroot-cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaroot-pkey.pem b/src/tls/rsaroot-pkey.pem
new file mode 100644
index 0000000..a5509b7
--- /dev/null
+++ b/src/tls/rsaroot-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoZoGzTiMbZczt
+QVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65AnK2vHNrE37DDRl2YT
+mP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6yuO07nut+rNFMSSj
+gnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0SoxQVSkyOIr/phPAk
+LWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2X8lMaz8ooT9OI/4l
+n/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu1ViLHIsm+55CJj5O
+I676Q4JJAgMBAAECggEBAKLmzk7KpnFpb+yPC5SnJ+65M+tWRxC4FQmyuEUz03Ky
+j5pJKPTGeFVkO3b27fLGMTN798E2LR+DCo6or+3VbmA9n0kZvItWOuiYt2G54hrM
+vD3wJB6KdIdUp0BIzeFNBStQi7oIS4UCfgPiQckV3F/t9tyGG1kKLLz5aAvlY0Qw
+iXylE9h6dDZyufLPlkq0KVVuZA2Em1oa6UaqSYgKPX5KN0Oa4cVmzqXVt0BGq1tB
+SW80CAtKIH2AhCvHQ98ZlWbt1XgzFeP/3idZdjF2H0y2cg9rBDwRmFBwVTceQSbY
+Ezrh5WW4okFpldb/qxjURuhwkPpf3vMe/jOIBUoXjRECgYEA2/Xoz4Z8RmhgPjh2
+QOXwSuCWyKVpOXbCoP9hH9stVbUaxDuU7/ywSVW0P+x7hImUXnnKDp6pJ+NzO96b
+u6nBuUH6tNP+GTyWFgwHJf//6rEQg9WGIK0srX8Epg+S0wCRlvxggIwXWQe3HY9P
+KWFJJdBJ165AMoc/yeYHEsK536UCgYEAw/3xTrrOqNnstLwRJw2uKYj8BDTMgCA4
+ZW96t6hYI/Pwweuu4MXVCaCnt4tzyrYVP6FcYXK5Oa0G8yTF32mk45Nmro9Q4ZhX
+Wyavk/P37W0Ep+vSVtbJ7/zgRLTGVh8LPn3T7dVcoXx/JSvEW8Ofp49GXHKq5l2V
+0luXyZmfVtUCgYEAw0Ijnf1TWqkTLoiuqOO1kLKYB5uWshUzpvms/Ttyng+7qBEA
+IJ3e2+rBrLE/4KLE260faiT8IlWtmKr+8fM67jqc1GMPwNVgokehHOGJC4yNDYrB
+m0Y/T/Bebw+KFdb+Ztq4y8QQgc7whcQO0Lv01CV3N4gOowwe2xpgkw3bNKUCgYAf
+a/gjAiodwgqEE17Anx3cBN06o2hh5kiEYrIO/ctbwEFKJcn8uVrlVz9sWswupLCV
+af5QlT8C8y2ZD701i09nOPuOYuW5tV3T/EjL9KI8C21iqpknWPo5IpamDUF7DzET
+TMMMb1eRgE82G2U4vQ08pOjH645groJVnl+gb6OvHQKBgCurbKUMy0kPGECptAJ9
+dgqC9fricGX63bMPIcnz0V7uqvYbalNPhlmhwvMCkPNebWgzMnLQM3zrh+VnmOR2
+43MfgCm/+hqhRIyt3PQcgKS7shzrf2xPFZRMZVJFRk+sbDaD0MhUxXQ4g34tHtFz
+ozsnCRsyRaJNG8hC49dTTrlh
+-----END PRIVATE KEY-----
diff --git a/src/tls/tls.h b/src/tls/tls.h
new file mode 100644
index 0000000..b1fdfea
--- /dev/null
+++ b/src/tls/tls.h
@@ -0,0 +1,728 @@
+#ifndef _TLS_H_INCLUDED_
+#define _TLS_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls 3h
+/* SUMMARY
+/* libtls internal interfaces
+/* SYNOPSIS
+/* #include <tls.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+#include <argv.h>
+
+ /*
+ * TLS enforcement levels. Non-sentinel values may also be used to indicate
+ * the actual security level of a session.
+ *
+ * XXX TLS_LEV_NOTFOUND no longer belongs in this list. The SMTP client will
+ * have to use something else to report that policy table lookup failed.
+ *
+ * The order of levels matters, but we hide most of the details in macros.
+ *
+ * "dane" vs. "fingerprint", both must lie between "encrypt" and "verify".
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS encryption must be applied.
+ *
+ * - Strictly above "encrypt", the peer certificate must match.
+ *
+ * - At "dane" and higher, the peer certificate must also be trusted. With
+ * "dane" the trust may be self-asserted, so we only log trust verification
+ * errors when TA associations are involved.
+ */
+#define TLS_LEV_INVALID -2 /* sentinel */
+#define TLS_LEV_NOTFOUND -1 /* XXX not in policy table */
+#define TLS_LEV_NONE 0 /* plain-text only */
+#define TLS_LEV_MAY 1 /* wildcard */
+#define TLS_LEV_ENCRYPT 2 /* encrypted connection */
+#define TLS_LEV_FPRINT 3 /* "peer" CA-less verification */
+#define TLS_LEV_HALF_DANE 4 /* DANE TLSA MX host, insecure MX RR */
+#define TLS_LEV_DANE 5 /* Opportunistic TLSA policy */
+#define TLS_LEV_DANE_ONLY 6 /* Required TLSA policy */
+#define TLS_LEV_VERIFY 7 /* certificate verified */
+#define TLS_LEV_SECURE 8 /* "secure" verification */
+
+#define TLS_REQUIRED(l) ((l) > TLS_LEV_MAY)
+#define TLS_MUST_MATCH(l) ((l) > TLS_LEV_ENCRYPT)
+#define TLS_MUST_PKIX(l) ((l) >= TLS_LEV_VERIFY)
+#define TLS_OPPORTUNISTIC(l) ((l) == TLS_LEV_MAY || (l) == TLS_LEV_DANE)
+#define TLS_DANE_BASED(l) \
+ ((l) >= TLS_LEV_HALF_DANE && (l) <= TLS_LEV_DANE_ONLY)
+#define TLS_NEVER_SECURED(l) ((l) == TLS_LEV_HALF_DANE)
+
+extern int tls_level_lookup(const char *);
+extern const char *str_tls_level(int);
+
+#ifdef USE_TLS
+
+ /*
+ * OpenSSL library.
+ */
+#include <openssl/lhash.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h> /* Legacy SSLEAY_VERSION_NUMBER */
+#include <openssl/evp.h> /* New OpenSSL 3.0 EVP_PKEY APIs */
+#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+
+ /* Appease indent(1) */
+#define x509_stack_t STACK_OF(X509)
+#define general_name_stack_t STACK_OF(GENERAL_NAME)
+#define ssl_cipher_stack_t STACK_OF(SSL_CIPHER)
+#define ssl_comp_stack_t STACK_OF(SSL_COMP)
+
+/*-
+ * Official way to check minimum OpenSSL API version from 3.0 onward.
+ * We simply define it false for all prior versions, where we typically also
+ * need the patch level to determine API compatibility.
+ */
+#ifndef OPENSSL_VERSION_PREREQ
+#define OPENSSL_VERSION_PREREQ(m,n) 0
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010100fUL)
+#error "OpenSSL releases prior to 1.1.1 are no longer supported"
+#endif
+
+ /*-
+ * Backwards compatibility with OpenSSL < 1.1.1a.
+ *
+ * In OpenSSL 1.1.1a the client-only interface SSL_get_server_tmp_key() was
+ * updated to work on both the client and the server, and was renamed to
+ * SSL_get_peer_tmp_key(), with the original name left behind as an alias. We
+ * use the new name when available.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x1010101fUL
+#undef SSL_get_signature_nid
+#define SSL_get_signature_nid(ssl, pnid) (NID_undef)
+#define tls_get_peer_dh_pubkey SSL_get_server_tmp_key
+#else
+#define tls_get_peer_dh_pubkey SSL_get_peer_tmp_key
+#endif
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+#define TLS_PEEK_PEER_CERT(ssl) SSL_get0_peer_certificate(ssl)
+#define TLS_FREE_PEER_CERT(x) ((void) 0)
+#define tls_set_bio_callback BIO_set_callback_ex
+#else
+#define TLS_PEEK_PEER_CERT(ssl) SSL_get_peer_certificate(ssl)
+#define TLS_FREE_PEER_CERT(x) X509_free(x)
+#define tls_set_bio_callback BIO_set_callback
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <name_mask.h>
+#include <name_code.h>
+
+ /*
+ * TLS library.
+ */
+#include <dns.h>
+
+ /*
+ * TLS role, presently for logging.
+ */
+typedef enum {
+ TLS_ROLE_CLIENT, TLS_ROLE_SERVER,
+} TLS_ROLE;
+
+typedef enum {
+ TLS_USAGE_NEW, TLS_USAGE_USED,
+} TLS_USAGE;
+
+ /*
+ * Names of valid tlsmgr(8) session caches.
+ */
+#define TLS_MGR_SCACHE_SMTPD "smtpd"
+#define TLS_MGR_SCACHE_SMTP "smtp"
+#define TLS_MGR_SCACHE_LMTP "lmtp"
+
+ /*
+ * RFC 6698, 7671, 7672 DANE
+ */
+#define TLS_DANE_TA 0 /* Match trust-anchor digests */
+#define TLS_DANE_EE 1 /* Match end-entity digests */
+
+#define TLS_DANE_CERT 0 /* Match the certificate digest */
+#define TLS_DANE_PKEY 1 /* Match the public key digest */
+
+#define TLS_DANE_FLAG_NORRS (1<<0) /* Nothing found in DNS */
+#define TLS_DANE_FLAG_EMPTY (1<<1) /* Nothing usable found in DNS */
+#define TLS_DANE_FLAG_ERROR (1<<2) /* TLSA record lookup error */
+
+#define tls_dane_unusable(dane) ((dane)->flags & TLS_DANE_FLAG_EMPTY)
+#define tls_dane_notfound(dane) ((dane)->flags & TLS_DANE_FLAG_NORRS)
+
+#define TLS_DANE_CACHE_TTL_MIN 1 /* A lot can happen in ~2 seconds */
+#define TLS_DANE_CACHE_TTL_MAX 100 /* Comparable to max_idle */
+
+ /*
+ * Certificate and public key digests (typically from TLSA RRs), grouped by
+ * algorithm.
+ */
+typedef struct TLS_TLSA {
+ uint8_t usage; /* DANE certificate usage */
+ uint8_t selector; /* DANE selector */
+ uint8_t mtype; /* Algorithm for this digest list */
+ uint16_t length; /* Length of associated data */
+ unsigned char *data; /* Associated data */
+ struct TLS_TLSA *next; /* Chain to next algorithm */
+} TLS_TLSA;
+
+typedef struct TLS_DANE {
+ TLS_TLSA *tlsa; /* TLSA records */
+ char *base_domain; /* Base domain of TLSA RRset */
+ int flags; /* Lookup status */
+ time_t expires; /* Expiration time of this record */
+ int refs; /* Reference count */
+} TLS_DANE;
+
+ /*
+ * tls_dane.c
+ */
+extern int tls_dane_avail(void);
+extern void tls_dane_loglevel(const char *, const char *);
+extern void tls_dane_flush(void);
+extern TLS_DANE *tls_dane_alloc(void);
+extern void tls_tlsa_free(TLS_TLSA *);
+extern void tls_dane_free(TLS_DANE *);
+extern void tls_dane_add_fpt_digests(TLS_DANE *, const char *, const char *,
+ int);
+extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int);
+extern int tls_dane_load_trustfile(TLS_DANE *, const char *);
+
+ /*
+ * TLS session context, also used by the VSTREAM call-back routines for SMTP
+ * input/output, and by OpenSSL call-back routines for key verification.
+ *
+ * Only some members are (read-only) accessible by the public.
+ */
+#define CCERT_BUFSIZ 256
+
+typedef struct {
+ /* Public, read-only. */
+ char *peer_CN; /* Peer Common Name */
+ char *issuer_CN; /* Issuer Common Name */
+ char *peer_sni; /* SNI sent to or by the peer */
+ char *peer_cert_fprint; /* ASCII certificate fingerprint */
+ char *peer_pkey_fprint; /* ASCII public key fingerprint */
+ int level; /* Effective security level */
+ int peer_status; /* Certificate and match status */
+ const char *protocol;
+ const char *cipher_name;
+ int cipher_usebits;
+ int cipher_algbits;
+ const char *kex_name; /* shared key-exchange algorithm */
+ const char *kex_curve; /* shared key-exchange ECDHE curve */
+ int kex_bits; /* shared FFDHE key exchange bits */
+ const char *clnt_sig_name; /* client's signature key algorithm */
+ const char *clnt_sig_curve; /* client's ECDSA curve name */
+ int clnt_sig_bits; /* client's RSA signature key bits */
+ const char *clnt_sig_dgst; /* client's signature digest */
+ const char *srvr_sig_name; /* server's signature key algorithm */
+ const char *srvr_sig_curve; /* server's ECDSA curve name */
+ int srvr_sig_bits; /* server's RSA signature key bits */
+ const char *srvr_sig_dgst; /* server's signature digest */
+ /* Private. */
+ SSL *con;
+ char *cache_type; /* tlsmgr(8) cache type if enabled */
+ int ticketed; /* Session ticket issued */
+ char *serverid; /* unique server identifier */
+ char *namaddr; /* nam[addr] for logging */
+ int log_mask; /* What to log */
+ int session_reused; /* this session was reused */
+ int am_server; /* Are we an SSL server or client? */
+ const char *mdalg; /* default message digest algorithm */
+ /* Built-in vs external SSL_accept/read/write/shutdown support. */
+ VSTREAM *stream; /* Blocking-mode SMTP session */
+ /* DANE TLSA trust input and verification state */
+ const TLS_DANE *dane; /* DANE TLSA digests */
+ X509 *errorcert; /* Error certificate closest to leaf */
+ int errordepth; /* Chain depth of error cert */
+ int errorcode; /* First error at error depth */
+ int must_fail; /* Failed to load trust settings */
+} TLS_SESS_STATE;
+
+ /*
+ * Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED
+ * only in the case of a hostname match.
+ */
+#define TLS_CERT_FLAG_PRESENT (1<<0)
+#define TLS_CERT_FLAG_ALTNAME (1<<1)
+#define TLS_CERT_FLAG_TRUSTED (1<<2)
+#define TLS_CERT_FLAG_MATCHED (1<<3)
+#define TLS_CERT_FLAG_SECURED (1<<4)
+
+#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT))
+#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME))
+#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED))
+#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED))
+#define TLS_CERT_IS_SECURED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_SECURED))
+
+ /*
+ * Opaque client context handle.
+ */
+typedef struct TLS_APPL_STATE TLS_APPL_STATE;
+
+#ifdef TLS_INTERNAL
+
+ /*
+ * Log mask details are internal to the library.
+ */
+extern int tls_log_mask(const char *, const char *);
+
+ /*
+ * What to log.
+ */
+#define TLS_LOG_NONE (1<<0)
+#define TLS_LOG_SUMMARY (1<<1)
+#define TLS_LOG_UNTRUSTED (1<<2)
+#define TLS_LOG_PEERCERT (1<<3)
+#define TLS_LOG_CERTMATCH (1<<4)
+#define TLS_LOG_VERBOSE (1<<5)
+#define TLS_LOG_CACHE (1<<6)
+#define TLS_LOG_DEBUG (1<<7)
+#define TLS_LOG_TLSPKTS (1<<8)
+#define TLS_LOG_ALLPKTS (1<<9)
+#define TLS_LOG_DANE (1<<10)
+
+ /*
+ * Client and Server application contexts
+ */
+struct TLS_APPL_STATE {
+ SSL_CTX *ssl_ctx;
+ SSL_CTX *sni_ctx;
+ int log_mask;
+ char *cache_type;
+};
+
+ /*
+ * tls_misc.c Application-context update and disposal.
+ */
+extern void tls_update_app_logmask(TLS_APPL_STATE *, int);
+extern void tls_free_app_context(TLS_APPL_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern void tls_param_init(void);
+extern int tls_library_init(void);
+
+ /*
+ * Protocol selection.
+ */
+#define TLS_PROTOCOL_INVALID (~0) /* All protocol bits masked */
+
+#ifdef SSL_TXT_SSLV2
+#define TLS_PROTOCOL_SSLv2 (1<<0) /* SSLv2 */
+#else
+#define SSL_TXT_SSLV2 "SSLv2"
+#define TLS_PROTOCOL_SSLv2 0 /* Unknown */
+#undef SSL_OP_NO_SSLv2
+#define SSL_OP_NO_SSLv2 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_SSLV3
+#define TLS_PROTOCOL_SSLv3 (1<<1) /* SSLv3 */
+#else
+#define SSL_TXT_SSLV3 "SSLv3"
+#define TLS_PROTOCOL_SSLv3 0 /* Unknown */
+#undef SSL_OP_NO_SSLv3
+#define SSL_OP_NO_SSLv3 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1
+#define TLS_PROTOCOL_TLSv1 (1<<2) /* TLSv1 */
+#else
+#define SSL_TXT_TLSV1 "TLSv1"
+#define TLS_PROTOCOL_TLSv1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1
+#define SSL_OP_NO_TLSv1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_1
+#define TLS_PROTOCOL_TLSv1_1 (1<<3) /* TLSv1_1 */
+#else
+#define SSL_TXT_TLSV1_1 "TLSv1.1"
+#define TLS_PROTOCOL_TLSv1_1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_1
+#define SSL_OP_NO_TLSv1_1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_2
+#define TLS_PROTOCOL_TLSv1_2 (1<<4) /* TLSv1_2 */
+#else
+#define SSL_TXT_TLSV1_2 "TLSv1.2"
+#define TLS_PROTOCOL_TLSv1_2 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_2
+#define SSL_OP_NO_TLSv1_2 0L /* Noop */
+#endif
+
+ /*
+ * OpenSSL 1.1.1 does not define a TXT macro for TLS 1.3, so we roll our
+ * own.
+ */
+#define TLS_PROTOCOL_TXT_TLSV1_3 "TLSv1.3"
+
+#if defined(TLS1_3_VERSION) && defined(SSL_OP_NO_TLSv1_3)
+#define TLS_PROTOCOL_TLSv1_3 (1<<5) /* TLSv1_3 */
+#else
+#define TLS_PROTOCOL_TLSv1_3 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_3
+#define SSL_OP_NO_TLSv1_3 0L /* Noop */
+#endif
+
+/*
+ * Always used when defined, SMTP has no truncation attacks.
+ */
+#ifndef SSL_OP_IGNORE_UNEXPECTED_EOF
+#define SSL_OP_IGNORE_UNEXPECTED_EOF 0L
+#endif
+
+#define TLS_KNOWN_PROTOCOLS \
+ ( TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3 | TLS_PROTOCOL_TLSv1 \
+ | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3 )
+#define TLS_SSL_OP_PROTOMASK(m) \
+ ((((m) & TLS_PROTOCOL_SSLv2) ? SSL_OP_NO_SSLv2 : 0L) \
+ | (((m) & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_3) ? SSL_OP_NO_TLSv1_3 : 0L))
+
+/*
+ * SSL options that are managed via dedicated Postfix features, rather than
+ * just exposed via hex codes or named elements of tls_ssl_options.
+ */
+#define TLS_SSL_OP_MANAGED_BITS \
+ (SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_IGNORE_UNEXPECTED_EOF | \
+ TLS_SSL_OP_PROTOMASK(~0))
+
+extern int tls_proto_mask_lims(const char *, int *, int *);
+
+ /*
+ * Cipher grade selection.
+ */
+#define TLS_CIPHER_NONE 0
+#define TLS_CIPHER_NULL 1
+#define TLS_CIPHER_EXPORT 2
+#define TLS_CIPHER_LOW 3
+#define TLS_CIPHER_MEDIUM 4
+#define TLS_CIPHER_HIGH 5
+
+extern const NAME_CODE tls_cipher_grade_table[];
+
+#define tls_cipher_grade(str) \
+ name_code(tls_cipher_grade_table, NAME_CODE_FLAG_NONE, (str))
+#define str_tls_cipher_grade(gr) \
+ str_name_code(tls_cipher_grade_table, (gr))
+
+ /*
+ * Cipher lists with exclusions.
+ */
+extern const char *tls_set_ciphers(TLS_SESS_STATE *, const char *,
+ const char *);
+
+ /*
+ * Populate TLS context with TLS 1.3-related signature parameters.
+ */
+extern void tls_get_signature_params(TLS_SESS_STATE *);
+
+#endif /* TLS_INTERNAL */
+
+ /*
+ * tls_client.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_CLIENT_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx;
+ VSTREAM *stream;
+ int fd; /* Event-driven file descriptor */
+ int timeout;
+ int tls_level; /* Security level */
+ const char *nexthop; /* destination domain */
+ const char *host; /* MX hostname */
+ const char *namaddr; /* nam[addr] for logging */
+ const char *sni; /* optional SNI name when not DANE */
+ const char *serverid; /* Session cache key */
+ const char *helo; /* Server name from EHLO response */
+ const char *protocols; /* Enabled protocols */
+ const char *cipher_grade; /* Minimum cipher grade */
+ const char *cipher_exclusions; /* Ciphers to exclude */
+ const ARGV *matchargv; /* Cert match patterns */
+ const char *mdalg; /* default message digest algorithm */
+ const TLS_DANE *dane; /* DANE TLSA verification */
+} TLS_CLIENT_START_PROPS;
+
+extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *);
+extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *,
+ const TLS_CLIENT_START_PROPS *);
+
+#define tls_client_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), (props))
+
+#define TLS_CLIENT_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ tls_client_init(TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, \
+ a6, a7, a8, a9, a10, a11, a12, a13, a14))
+
+#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17) \
+ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), (props)))
+
+ /*
+ * tls_server.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ int set_sessid;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *protocols;
+ const char *eecdh_grade;
+ const char *dh1024_param_file;
+ const char *dh512_param_file;
+ int ask_ccert;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx; /* TLS application context */
+ VSTREAM *stream; /* Client stream */
+ int fd; /* Event-driven file descriptor */
+ int timeout; /* TLS handshake timeout */
+ int requirecert; /* Insist on client cert? */
+ const char *serverid; /* Server instance (salt cache key) */
+ const char *namaddr; /* Client nam[addr] for logging */
+ const char *cipher_grade;
+ const char *cipher_exclusions;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_START_PROPS;
+
+extern TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props);
+extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *);
+
+#define tls_server_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_SERVER_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ tls_server_init((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \
+ ((props)->a20), (props)))
+
+#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+ tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), (props)))
+
+ /*
+ * tls_session.c
+ */
+extern void tls_session_stop(TLS_APPL_STATE *, VSTREAM *, int, int, TLS_SESS_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern const char *tls_compile_version(void);
+extern const char *tls_run_version(void);
+extern const char **tls_pkey_algorithms(void);
+extern void tls_log_summary(TLS_ROLE, TLS_USAGE, TLS_SESS_STATE *);
+extern void tls_pre_jail_init(TLS_ROLE);
+
+#ifdef TLS_INTERNAL
+
+#include <vstring.h>
+
+extern VSTRING *tls_session_passivate(SSL_SESSION *);
+extern SSL_SESSION *tls_session_activate(const char *, int);
+
+ /*
+ * tls_stream.c.
+ */
+extern void tls_stream_start(VSTREAM *, TLS_SESS_STATE *);
+extern void tls_stream_stop(VSTREAM *);
+
+ /*
+ * tls_bio_ops.c: a generic multi-personality driver that retries SSL
+ * operations until they are satisfied or until a hard error happens.
+ * Because of its ugly multi-personality user interface we invoke it via
+ * not-so-ugly single-personality wrappers.
+ */
+extern int tls_bio(int, int, TLS_SESS_STATE *,
+ int (*) (SSL *), /* handshake */
+ int (*) (SSL *, void *, int), /* read */
+ int (*) (SSL *, const void *, int), /* write */
+ void *, int);
+
+#define tls_bio_connect(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_connect, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_accept(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_accept, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_shutdown(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_shutdown, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_read(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ SSL_read, NULL, (buf), (len))
+#define tls_bio_write(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ NULL, SSL_write, (buf), (len))
+
+ /*
+ * tls_dh.c
+ */
+extern void tls_set_dh_from_file(const char *);
+extern void tls_tmp_dh(SSL_CTX *, int);
+extern void tls_auto_eecdh_curves(SSL_CTX *, const char *);
+
+ /*
+ * tls_verify.c
+ */
+extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *);
+extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *);
+extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
+extern void tls_log_verify_error(TLS_SESS_STATE *);
+
+ /*
+ * tls_dane.c
+ */
+extern void tls_dane_log(TLS_SESS_STATE *);
+extern void tls_dane_digest_init(SSL_CTX *, const EVP_MD *);
+extern int tls_dane_enable(TLS_SESS_STATE *);
+extern TLS_TLSA *tlsa_prepend(TLS_TLSA *, uint8_t, uint8_t, uint8_t,
+ const unsigned char *, uint16_t);
+
+ /*
+ * tls_fprint.c
+ */
+extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **);
+extern char *tls_digest_encode(const unsigned char *, int);
+extern char *tls_cert_fprint(X509 *, const char *);
+extern char *tls_pkey_fprint(X509 *, const char *);
+extern char *tls_serverid_digest(TLS_SESS_STATE *,
+ const TLS_CLIENT_START_PROPS *, const char *);
+
+ /*
+ * tls_certkey.c
+ */
+extern int tls_set_ca_certificate_info(SSL_CTX *, const char *, const char *);
+extern int tls_load_pem_chain(SSL *, const char *, const char *);
+extern int tls_set_my_certificate_key_info(SSL_CTX *, /* All */ const char *,
+ /* RSA */ const char *, const char *,
+ /* DSA */ const char *, const char *,
+ /* ECDSA */ const char *, const char *);
+
+ /*
+ * tls_misc.c
+ */
+extern int TLScontext_index;
+
+extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, SSL_CTX *, int);
+extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *);
+extern void tls_free_context(TLS_SESS_STATE *);
+extern void tls_check_version(void);
+extern long tls_bug_bits(void);
+extern void tls_print_errors(void);
+extern void tls_info_callback(const SSL *, int, int);
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+extern long tls_bio_dump_cb(BIO *, int, const char *, size_t, int, long,
+ int, size_t *);
+
+#else
+extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
+
+#endif
+extern const EVP_MD *tls_validate_digest(const char *);
+
+ /*
+ * tls_seed.c
+ */
+extern void tls_int_seed(void);
+extern int tls_ext_seed(int);
+
+#endif /* TLS_INTERNAL */
+
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+#endif /* USE_TLS */
+#endif /* _TLS_H_INCLUDED_ */
diff --git a/src/tls/tls_bio_ops.c b/src/tls/tls_bio_ops.c
new file mode 100644
index 0000000..9b66195
--- /dev/null
+++ b/src/tls/tls_bio_ops.c
@@ -0,0 +1,296 @@
+/*++
+/* NAME
+/* tls_bio_ops 3
+/* SUMMARY
+/* TLS network basic I/O management
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_bio_connect(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_accept(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_shutdown(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_read(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_write(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/* DESCRIPTION
+/* This module enforces VSTREAM-style timeouts on non-blocking
+/* I/O while performing TLS handshake or input/output operations.
+/*
+/* The Postfix VSTREAM read/write routines invoke the
+/* tls_bio_read/write routines to send and receive plain-text
+/* data. In addition, this module provides tls_bio_connect/accept
+/* routines that trigger the initial TLS handshake. The
+/* tls_bio_xxx routines invoke the corresponding SSL routines
+/* that translate the requests into TLS protocol messages.
+/*
+/* Whenever an SSL operation indicates that network input (or
+/* output) needs to happen, the tls_bio_xxx routines wait for
+/* the network to become readable (or writable) within the
+/* timeout limit, then retry the SSL operation. This works
+/* because the network socket is in non-blocking mode.
+/*
+/* tls_bio_connect() performs the SSL_connect() operation.
+/*
+/* tls_bio_accept() performs the SSL_accept() operation.
+/*
+/* tls_bio_shutdown() performs the SSL_shutdown() operation.
+/*
+/* tls_bio_read() performs the SSL_read() operation.
+/*
+/* tls_bio_write() performs the SSL_write() operation.
+/*
+/* Arguments:
+/* .IP fd
+/* Network socket.
+/* .IP buf
+/* Read/write buffer.
+/* .IP len
+/* Read/write request size.
+/* .IP timeout
+/* Read/write timeout.
+/* .IP TLScontext
+/* TLS session state.
+/* DIAGNOSTICS
+/* A result value > 0 means successful completion.
+/*
+/* A result value < 0 means that the requested operation did
+/* not complete due to TLS protocol failure, system call
+/* failure, or for any reason described under "in addition"
+/* below.
+/*
+/* A result value of 0 from tls_bio_shutdown() means that the
+/* operation is in progress. A result value of 0 from other
+/* tls_bio_ops(3) operations means that the remote party either
+/* closed the network connection or that it sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_bio_ops(3) routines the global
+/* errno value is non-zero when the requested operation did not
+/* complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when some network read/write
+/* operation did not complete within the time limit.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+
+#ifndef timersub
+/* res = a - b */
+#define timersub(a, b, res) do { \
+ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((res)->tv_usec < 0) { \
+ (res)->tv_sec--; \
+ (res)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* tls_bio - perform SSL input/output operation with extreme prejudice */
+
+int tls_bio(int fd, int timeout, TLS_SESS_STATE *TLScontext,
+ int (*hsfunc) (SSL *),
+ int (*rfunc) (SSL *, void *, int),
+ int (*wfunc) (SSL *, const void *, int),
+ void *buf, int num)
+{
+ const char *myname = "tls_bio";
+ int status;
+ int err;
+ int enable_deadline;
+ struct timeval time_left; /* amount of time left */
+ struct timeval time_deadline; /* time of deadline */
+ struct timeval time_now; /* time after SSL_mumble() call */
+
+ /*
+ * Compensation for interface mis-match: With VSTREAMs, timeout <= 0
+ * means wait forever; with the read/write_wait() calls below, we need to
+ * specify timeout < 0 instead.
+ *
+ * Safety: no time limit means no deadline.
+ */
+ if (timeout <= 0) {
+ timeout = -1;
+ enable_deadline = 0;
+ }
+
+ /*
+ * Deadline management is simpler than with VSTREAMs, because we don't
+ * need to decrement a per-stream time limit. We just work within the
+ * budget that is available for this tls_bio() call.
+ */
+ else {
+ enable_deadline =
+ vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE);
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_deadline);
+ time_deadline.tv_sec += timeout;
+ }
+ }
+
+ /*
+ * If necessary, retry the SSL handshake or read/write operation after
+ * handling any pending network I/O.
+ */
+ for (;;) {
+
+ /*
+ * Flush the per-thread SSL error queue. Otherwise, errors from other
+ * code that also uses TLS may confuse SSL_get_error(3).
+ */
+ ERR_clear_error();
+
+ if (hsfunc)
+ status = hsfunc(TLScontext->con);
+ else if (rfunc)
+ status = rfunc(TLScontext->con, buf, num);
+ else if (wfunc)
+ status = wfunc(TLScontext->con, buf, num);
+ else
+ msg_panic("%s: nothing to do here", myname);
+ err = SSL_get_error(TLScontext->con, status);
+
+ /*
+ * Correspondence between SSL_ERROR_* error codes and tls_bio_(read,
+ * write, accept, connect, shutdown) return values (for brevity:
+ * retval).
+ *
+ * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write)
+ * this is the number of plaintext bytes sent or received. With
+ * SSL_(accept, connect, shutdown) this means that the operation was
+ * completed successfully.
+ *
+ * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force
+ * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded.
+ *
+ * All other SSL_ERROR_* cases correspond with retval <= 0. With
+ * SSL_(read, write, accept, connect) retval == 0 means that the
+ * remote party either closed the network connection or that it
+ * requested TLS shutdown; with SSL_shutdown() retval == 0 means that
+ * our own shutdown request is in progress. With all operations
+ * retval < 0 means that there was an error. In the latter case,
+ * SSL_ERROR_SYSCALL means that error details are returned via the
+ * errno value.
+ *
+ * Find out if we must retry the operation and/or if there is pending
+ * network I/O.
+ *
+ * XXX If we're the first to invoke SSL_shutdown(), then the operation
+ * isn't really complete when the call returns. We could hide that
+ * anomaly here and repeat the call.
+ */
+ switch (err) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_now);
+ timersub(&time_deadline, &time_now, &time_left);
+ timeout = time_left.tv_sec + (time_left.tv_usec > 0);
+ if (timeout <= 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ }
+ if (err == SSL_ERROR_WANT_WRITE) {
+ if (write_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ } else {
+ if (read_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ }
+ break;
+
+ /*
+ * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP)
+ * etc. Historically, Postfix silently treated these as ordinary
+ * I/O errors so we don't really know how common they are. For
+ * now, we just log a warning.
+ */
+ default:
+ msg_warn("%s: unexpected SSL_ERROR code %d", myname, err);
+ /* FALLTHROUGH */
+
+ /*
+ * With tls_timed_read() and tls_timed_write() the caller is the
+ * VSTREAM library module which is unaware of TLS, so we log the
+ * TLS error stack here. In a better world, each VSTREAM I/O
+ * object would provide an error reporting method in addition to
+ * the timed_read and timed_write methods, so that we would not
+ * need to have ad-hoc code like this.
+ */
+ case SSL_ERROR_SSL:
+ if (rfunc || wfunc)
+ tls_print_errors();
+ /* FALLTHROUGH */
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_NONE:
+ errno = 0; /* avoid bogus warnings */
+ /* FALLTHROUGH */
+ case SSL_ERROR_SYSCALL:
+ return (status);
+ }
+ }
+}
+
+#endif
diff --git a/src/tls/tls_certkey.c b/src/tls/tls_certkey.c
new file mode 100644
index 0000000..09a35e0
--- /dev/null
+++ b/src/tls/tls_certkey.c
@@ -0,0 +1,721 @@
+/*++
+/* NAME
+/* tls_certkey 3
+/* SUMMARY
+/* public key certificate and private key loader
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_set_ca_certificate_info(ctx, CAfile, CApath)
+/* SSL_CTX *ctx;
+/* const char *CAfile;
+/* const char *CApath;
+/*
+/* int tls_set_my_certificate_key_info(ctx, chain_files,
+/* cert_file, key_file,
+/* dcert_file, dkey_file,
+/* eccert_file, eckey_file)
+/* SSL_CTX *ctx;
+/* const char *chain_files;
+/* const char *cert_file;
+/* const char *key_file;
+/* const char *dcert_file;
+/* const char *dkey_file;
+/* const char *eccert_file;
+/* const char *eckey_file;
+/*
+/* int tls_load_pem_chain(ssl, pem, origin);
+/* SSL *ssl;
+/* const char *pem;
+/* const char *origin;
+/* DESCRIPTION
+/* OpenSSL supports two options to specify CA certificates:
+/* either one file CAfile that contains all CA certificates,
+/* or a directory CApath with separate files for each
+/* individual CA, with symbolic links named after the hash
+/* values of the certificates. The second option is not
+/* convenient with a chrooted process.
+/*
+/* tls_set_ca_certificate_info() loads the CA certificate
+/* information for the specified TLS server or client context.
+/* The result is -1 on failure, 0 on success.
+/*
+/* tls_set_my_certificate_key_info() loads the public key
+/* certificates and private keys for the specified TLS server
+/* or client context. Up to 3 pairs of key pairs (RSA, DSA and
+/* ECDSA) may be specified; each certificate and key pair must
+/* match. The chain_files argument makes it possible to load
+/* keys and certificates for more than 3 algorithms, via either
+/* a single file, or a list of multiple files. The result is -1
+/* on failure, 0 on success.
+/*
+/* tls_load_pem_chain() loads one or more (key, cert, [chain])
+/* triples from an in-memory PEM blob. The "origin" argument
+/* is used for error logging, to identify the provenance of the
+/* PEM blob. "ssl" must be non-zero, and the keys and certificates
+/* will be loaded into that object.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+#define PEM_LOAD_STATE_NOGO -2 /* Unusable object or sequence */
+#define PEM_LOAD_STATE_FAIL -1 /* Error in libcrypto */
+#define PEM_LOAD_STATE_DONE 0 /* End of PEM file, return value only */
+#define PEM_LOAD_STATE_INIT 1 /* No PEM objects seen */
+#define PEM_LOAD_STATE_PKEY 2 /* Last object was a private key */
+#define PEM_LOAD_STATE_CERT 3 /* Last object was a certificate */
+#define PEM_LOAD_STATE_BOTH 4 /* Unordered, key + first cert seen */
+
+#define PEM_LOAD_READ_LAST 0 /* Reading last file */
+#define PEM_LOAD_READ_MORE 1 /* More files to be read */
+
+typedef struct pem_load_state_t {
+ const char *origin; /* PEM chain origin description */
+ const char *source; /* PEM BIO origin description */
+ const char *keysrc; /* Source of last key */
+ BIO *pembio; /* PEM input stream */
+ SSL_CTX *ctx; /* SSL connection factory */
+ SSL *ssl; /* SSL connection handle */
+ EVP_PKEY *pkey; /* current key */
+ X509 *cert; /* current certificate */
+ x509_stack_t *chain; /* current chain */
+ int keynum; /* Index of last key */
+ int objnum; /* Index in current source */
+ int state; /* Current state, never "DONE" */
+ int mixed; /* Single file with key anywhere */
+} pem_load_state_t;
+
+/* init_pem_load_state - fill in initial pem_load_state structure */
+
+static void init_pem_load_state(pem_load_state_t *st, SSL_CTX *ctx, SSL *ssl,
+ const char *origin)
+{
+ st->origin = origin;
+ st->source = origin;
+ st->keysrc = 0;
+ st->pembio = 0;
+ st->ctx = ctx;
+ st->ssl = ssl;
+ st->pkey = 0;
+ st->cert = 0;
+ st->chain = 0;
+ st->keynum = 0;
+ st->objnum = 0;
+ st->state = PEM_LOAD_STATE_INIT;
+ st->mixed = 0;
+}
+
+/* use_chain - load cert, key and chain into ctx or ssl */
+
+static int use_chain(pem_load_state_t *st)
+{
+ int ret;
+ int replace = 0;
+
+ /*
+ * With replace == 0, an error is returned if the algorithm slot is
+ * already taken, and a previous key + chain of the same type would be
+ * clobbered.
+ */
+ if (st->ctx)
+ ret = SSL_CTX_use_cert_and_key(st->ctx, st->cert, st->pkey, st->chain,
+ replace);
+ else
+ ret = SSL_use_cert_and_key(st->ssl, st->cert, st->pkey, st->chain,
+ replace);
+
+ /*
+ * SSL_[CTX_]_use_cert_key() uprefs all the objects in question, so we
+ * must free ours.
+ */
+ X509_free(st->cert);
+ st->cert = 0;
+ EVP_PKEY_free(st->pkey);
+ st->pkey = 0;
+ sk_X509_pop_free(st->chain, X509_free);
+ st->chain = 0;
+
+ return ret;
+}
+
+/* load_cert - decode and load a DER-encoded X509 certificate */
+
+static void load_cert(pem_load_state_t *st, unsigned char *buf,
+ long buflen)
+{
+ const unsigned char *p = buf;
+ X509 *cert = d2i_X509(0, &p, buflen);
+
+ /*
+ * When expecting one or more keys, each key must precede the associated
+ * certificate (chain).
+ */
+ if (!st->mixed && st->state == PEM_LOAD_STATE_INIT) {
+ msg_warn("error loading chain from %s: key not first", st->source);
+ if (cert)
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ if (!cert) {
+ msg_warn("error loading certificate (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ if (p - buf != buflen) {
+ msg_warn("error loading certificate (PEM object number %d) from %s:"
+ " excess data", st->objnum, st->source);
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+
+ /*
+ * The first certificate after a new key becomes the leaf certificate for
+ * that key. Subsequent certificates are added to the issuer chain.
+ *
+ * In "mixed" mode, the first certificate is either after the key, or else
+ * comes first.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_PKEY:
+ st->cert = cert;
+ st->state = st->mixed ? PEM_LOAD_STATE_BOTH : PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_INIT:
+ st->cert = cert;
+ st->state = PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_CERT:
+ case PEM_LOAD_STATE_BOTH:
+ if ((!st->chain && (st->chain = sk_X509_new_null()) == 0)
+ || !sk_X509_push(st->chain, cert)) {
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_FAIL;
+ }
+ return;
+ }
+}
+
+/* load_pkey - decode and load a DER-encoded private key */
+
+static void load_pkey(pem_load_state_t *st, int pkey_type,
+ unsigned char *buf, long buflen)
+{
+ const char *myname = "load_pkey";
+ const unsigned char *p = buf;
+ PKCS8_PRIV_KEY_INFO *p8;
+ EVP_PKEY *pkey = 0;
+
+ /*
+ * Keys are either algorithm-specific, or else (ideally) algorithm
+ * agnostic, in which case they are wrapped as PKCS#8 objects with an
+ * algorithm OID.
+ */
+ if (pkey_type != NID_undef) {
+ pkey = d2i_PrivateKey(pkey_type, 0, &p, buflen);
+ } else {
+ p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, buflen);
+ if (p8) {
+ pkey = EVP_PKCS82PKEY(p8);
+ PKCS8_PRIV_KEY_INFO_free(p8);
+ }
+ }
+
+ /*
+ * Except in "mixed" mode, where a single key appears anywhere in a file
+ * with multiple certificates, a given key is either at the first object
+ * we process, or occurs after a previous key and one or more associated
+ * certificates. Thus, encountering a key in a state other than "INIT"
+ * or "CERT" is an error, except in "mixed" mode where a second key is
+ * ignored with a warning.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_CERT:
+
+ /*
+ * When processing the key of a "next" chain, we're in the "CERT"
+ * state, and first complete the processing of the previous chain.
+ */
+ if (!st->mixed && !use_chain(st)) {
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s does not match the certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_INIT:
+
+ if (!pkey) {
+ msg_warn("error loading private key (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* Reject unexpected data beyond the end of the DER-encoded object */
+ if (p - buf != buflen) {
+ msg_warn("error loading private key (PEM object number %d) from"
+ " %s: excess data", st->objnum, st->source);
+ EVP_PKEY_free(pkey);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ /* All's well, update the state */
+ st->pkey = pkey;
+ if (st->state == PEM_LOAD_STATE_INIT)
+ st->state = PEM_LOAD_STATE_PKEY;
+ else if (st->mixed)
+ st->state = PEM_LOAD_STATE_BOTH;
+ else
+ st->state = PEM_LOAD_STATE_PKEY;
+ return;
+
+ case PEM_LOAD_STATE_PKEY:
+ case PEM_LOAD_STATE_BOTH:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ /* XXX: Legacy behavior was silent, should we stay silent? */
+ if (st->mixed) {
+ msg_warn("ignoring 2nd key at index %d in %s after 1st at %d",
+ st->objnum, st->source, st->keynum);
+ return;
+ }
+ /* else back-to-back keys */
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s not followed by a certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+
+ default:
+ msg_error("%s: internal error: bad state: %d", myname, st->state);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+}
+
+/* load_pem_object - load next pkey or cert from open BIO */
+
+static int load_pem_object(pem_load_state_t *st)
+{
+ char *name = 0;
+ char *header = 0;
+ unsigned char *buf = 0;
+ long buflen;
+ int pkey_type = NID_undef;
+
+ if (!PEM_read_bio(st->pembio, &name, &header, &buf, &buflen)) {
+ if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE)
+ return (st->state = PEM_LOAD_STATE_FAIL);
+
+ ERR_clear_error();
+ /* Clean EOF, preserve stored state for any next input file */
+ return (PEM_LOAD_STATE_DONE);
+ }
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ load_cert(st, buf, buflen);
+ } else if (strcmp(name, PEM_STRING_PKCS8INF) == 0
+ || ((pkey_type = EVP_PKEY_RSA) != NID_undef
+ && strcmp(name, PEM_STRING_RSA) == 0)
+ || ((pkey_type = EVP_PKEY_EC) != NID_undef
+ && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0)
+ || ((pkey_type = EVP_PKEY_DSA) != NID_undef
+ && strcmp(name, PEM_STRING_DSA) == 0)) {
+ load_pkey(st, pkey_type, buf, buflen);
+ } else if (!st->mixed) {
+ msg_warn("loading %s: ignoring PEM type: %s", st->source, name);
+ }
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(buf);
+ return (st->state);
+}
+
+/* load_pem_bio - load all key/certs from bio and free the bio */
+
+static int load_pem_bio(pem_load_state_t *st, int more)
+{
+ int state = st->state;
+
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * When "more" is PEM_LOAD_READ_MORE, more files will be loaded after the
+ * current file, and final processing for the last key and chain is
+ * deferred.
+ *
+ * When "more" is PEM_LOAD_READ_LAST, this is the last file in the list, and
+ * we validate the final chain.
+ *
+ * When st->mixed is true, this is the only file, and its key can occur at
+ * any location. In this case we load at most one key.
+ */
+ for (st->objnum = 1; state > PEM_LOAD_STATE_DONE; ++st->objnum) {
+ state = load_pem_object(st);
+ if ((st->mixed && st->keynum == 0 &&
+ (state == PEM_LOAD_STATE_PKEY || state == PEM_LOAD_STATE_BOTH))
+ || (!st->mixed && state == PEM_LOAD_STATE_PKEY)) {
+ /* Squirrel-away the current key location */
+ st->keynum = st->objnum;
+ st->keysrc = st->source;
+ }
+ }
+ /* We're responsible for unconditionally freeing the BIO */
+ BIO_free(st->pembio);
+
+ /* Success with current file, go back for more? */
+ if (more == PEM_LOAD_READ_MORE && state >= PEM_LOAD_STATE_DONE)
+ return 0;
+
+ /*
+ * If all is well so far, complete processing for the final chain.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_FAIL:
+ tls_print_errors();
+ break;
+ default:
+ break;
+ case PEM_LOAD_STATE_INIT:
+ msg_warn("No PEM data in %s", st->origin);
+ break;
+ case PEM_LOAD_STATE_PKEY:
+ msg_warn("No certs for key at index %d in %s", st->keynum, st->keysrc);
+ break;
+ case PEM_LOAD_STATE_CERT:
+ if (st->mixed) {
+ msg_warn("No private key found in %s", st->origin);
+ break;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_BOTH:
+ /* use_chain() frees the key and certs, and zeroes the pointers */
+ if (use_chain(st))
+ return (0);
+ msg_warn("key at index %d in %s does not match next certificate",
+ st->keynum, st->keysrc);
+ tls_print_errors();
+ break;
+ }
+ /* Free any left-over unused keys and certs */
+ EVP_PKEY_free(st->pkey);
+ X509_free(st->cert);
+ sk_X509_pop_free(st->chain, X509_free);
+
+ msg_warn("error loading private keys and certificates from: %s: %s",
+ st->origin, st->ctx ? "disabling TLS support" :
+ "aborting TLS handshake");
+ return (-1);
+}
+
+/* load_chain_files - load sequence of (key, cert, [chain]) from files */
+
+static int load_chain_files(SSL_CTX *ctx, const char *chain_files)
+{
+ pem_load_state_t st;
+ ARGV *files = argv_split(chain_files, CHARS_COMMA_SP);
+ char **filep;
+ int ret = 0;
+ int more;
+
+ init_pem_load_state(&st, ctx, 0, chain_files);
+ for (filep = files->argv; ret == 0 && *filep; ++filep) {
+ st.source = *filep;
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ st.state = PEM_LOAD_STATE_NOGO;
+ break;
+ }
+ more = filep[1] ? PEM_LOAD_READ_MORE : PEM_LOAD_READ_LAST;
+ /* load_pem_bio() frees the BIO */
+ ret = load_pem_bio(&st, more);
+ }
+ argv_free(files);
+ return (ret);
+}
+
+/* load_mixed_file - load certs with single key anywhere in the file */
+
+static int load_mixed_file(SSL_CTX *ctx, const char *file)
+{
+ pem_load_state_t st;
+
+ init_pem_load_state(&st, ctx, 0, file);
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ return (-1);
+ }
+ st.mixed = 1;
+ /* load_pem_bio() frees the BIO */
+ return load_pem_bio(&st, PEM_LOAD_READ_LAST);
+}
+
+/* tls_set_ca_certificate_info - load Certification Authority certificates */
+
+int tls_set_ca_certificate_info(SSL_CTX *ctx, const char *CAfile,
+ const char *CApath)
+{
+ if (*CAfile == 0)
+ CAfile = 0;
+ if (*CApath == 0)
+ CApath = 0;
+
+#define CA_PATH_FMT "%s%s%s"
+#define CA_PATH_ARGS(var, nextvar) \
+ var ? #var "=\"" : "", \
+ var ? var : "", \
+ var ? (nextvar ? "\", " : "\"") : ""
+
+ if (CAfile || CApath) {
+ if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) {
+ msg_info("cannot load Certification Authority data, "
+ CA_PATH_FMT CA_PATH_FMT ": disabling TLS support",
+ CA_PATH_ARGS(CAfile, CApath),
+ CA_PATH_ARGS(CApath, 0));
+ tls_print_errors();
+ return (-1);
+ }
+ if (var_tls_append_def_CA && !SSL_CTX_set_default_verify_paths(ctx)) {
+ msg_info("cannot set default OpenSSL certificate verification "
+ "paths: disabling TLS support");
+ tls_print_errors();
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* set_cert_stuff - specify certificate and key information */
+
+static int set_cert_stuff(SSL_CTX *ctx, const char *cert_type,
+ const char *cert_file,
+ const char *key_file)
+{
+
+ /*
+ * When the certfile and keyfile are one and the same, load both in a
+ * single pass, avoiding potential race conditions during key rollover.
+ */
+ if (strcmp(cert_file, key_file) == 0)
+ return (load_mixed_file(ctx, cert_file) == 0);
+
+ /*
+ * We need both the private key (in key_file) and the public key
+ * certificate (in cert_file).
+ *
+ * Code adapted from OpenSSL apps/s_cb.c.
+ */
+ ERR_clear_error();
+ if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
+ msg_warn("cannot get %s certificate from file \"%s\": "
+ "disabling TLS support", cert_type, cert_file);
+ tls_print_errors();
+ return (0);
+ }
+ if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) {
+ msg_warn("cannot get %s private key from file \"%s\": "
+ "disabling TLS support", cert_type, key_file);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (!SSL_CTX_check_private_key(ctx)) {
+ msg_warn("%s private key in %s does not match public key in %s: "
+ "disabling TLS support", cert_type, key_file, cert_file);
+ return (0);
+ }
+ return (1);
+}
+
+/* tls_set_my_certificate_key_info - load client or server certificates/keys */
+
+int tls_set_my_certificate_key_info(SSL_CTX *ctx, const char *chain_files,
+ const char *cert_file,
+ const char *key_file,
+ const char *dcert_file,
+ const char *dkey_file,
+ const char *eccert_file,
+ const char *eckey_file)
+{
+
+ /* The "chain_files" parameter overrides all the legacy parameters */
+ if (chain_files && *chain_files)
+ return load_chain_files(ctx, chain_files);
+
+ /*
+ * Lack of certificates is fine so long as we are prepared to use
+ * anonymous ciphers.
+ */
+ if (*cert_file && !set_cert_stuff(ctx, "RSA", cert_file, key_file))
+ return (-1); /* logged */
+ if (*dcert_file && !set_cert_stuff(ctx, "DSA", dcert_file, dkey_file))
+ return (-1); /* logged */
+#ifndef OPENSSL_NO_ECDH
+ if (*eccert_file && !set_cert_stuff(ctx, "ECDSA", eccert_file, eckey_file))
+ return (-1); /* logged */
+#else
+ if (*eccert_file)
+ msg_warn("ECDSA not supported. Ignoring ECDSA certificate file \"%s\"",
+ eccert_file);
+#endif
+ return (0);
+}
+
+/* tls_load_pem_chain - load in-memory PEM client or server chain */
+
+int tls_load_pem_chain(SSL *ssl, const char *pem, const char *origin)
+{
+ static VSTRING *obuf;
+ pem_load_state_t st;
+
+ if (!obuf)
+ obuf = vstring_alloc(100);
+ vstring_sprintf(obuf, "SNI data for %s", origin);
+ init_pem_load_state(&st, 0, ssl, vstring_str(obuf));
+
+ if ((st.pembio = BIO_new_mem_buf(pem, -1)) == NULL) {
+ msg_warn("error opening memory BIO for %s", st.origin);
+ tls_print_errors();
+ return (-1);
+ }
+ /* load_pem_bio() frees the BIO */
+ return (load_pem_bio(&st, PEM_LOAD_READ_LAST));
+}
+
+#ifdef TEST
+
+static NORETURN usage(void)
+{
+ fprintf(stderr, "usage: tls_certkey [-m] <chainfiles>\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int ch;
+ int mixed = 0;
+ int ret;
+ char *key_file = 0;
+ SSL_CTX *ctx;
+
+ if (!(ctx = SSL_CTX_new(TLS_client_method()))) {
+ tls_print_errors();
+ exit(1);
+ }
+ while ((ch = GETOPT(argc, argv, "mk:")) > 0) {
+ switch (ch) {
+ case 'k':
+ key_file = optarg;
+ break;
+ case 'm':
+ mixed = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (key_file)
+ ret = set_cert_stuff(ctx, "any", argv[0], key_file) == 0;
+ else if (mixed)
+ ret = load_mixed_file(ctx, argv[0]);
+ else
+ ret = load_chain_files(ctx, argv[0]);
+
+ if (ret != 0)
+ exit(1);
+
+ if (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST) != 1) {
+ fprintf(stderr, "error selecting first certificate\n");
+ tls_print_errors();
+ exit(1);
+ }
+ do {
+ STACK_OF(X509) *chain;
+ int i;
+
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) != 1) {
+ fprintf(stderr, "error locating certificate chain\n");
+ tls_print_errors();
+ exit(1);
+ }
+ for (i = 0; i <= sk_X509_num(chain); ++i) {
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+
+ if (i > 0)
+ cert = sk_X509_value(chain, i - 1);
+ else
+ cert = SSL_CTX_get0_certificate(ctx);
+
+ printf("depth = %d\n", i);
+
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ printf("issuer = %s\n", buf);
+
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ printf("subject = %s\n\n", buf);
+ }
+ } while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT) != 0);
+
+ exit(0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_client.c b/src/tls/tls_client.c
new file mode 100644
index 0000000..68f9255
--- /dev/null
+++ b/src/tls/tls_client.c
@@ -0,0 +1,1250 @@
+/*++
+/* NAME
+/* tls_client
+/* SUMMARY
+/* client-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_client_init(init_props)
+/* const TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* TLS_SESS_STATE *tls_client_start(start_props)
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* TLS_SESS_STATE *tls_client_post_connect(TLScontext, start_props)
+/* TLS_SESS_STATE *TLScontext;
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* void tls_client_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS clients,
+/* the OpenSSL library and the TLS entropy and cache manager.
+/*
+/* The SMTP client will attempt to verify the server hostname
+/* against the names listed in the server certificate. When
+/* a hostname match is required, the verification fails
+/* on certificate verification or hostname mis-match errors.
+/* When no hostname match is required, hostname verification
+/* failures are logged but they do not affect the TLS handshake
+/* or the SMTP session.
+/*
+/* The rules for peer name wild-card matching differ between
+/* RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while
+/* RFC RFC3207 (SMTP over TLS) does not specify a rule at all.
+/* Postfix uses a restrictive match algorithm. One asterisk
+/* ('*') is allowed as the left-most component of a wild-card
+/* certificate name; it matches the left-most component of
+/* the peer hostname.
+/*
+/* Another area where RFCs aren't always explicit is the
+/* handling of dNSNames in peer certificates. RFC 3207 (SMTP
+/* over TLS) does not mention dNSNames. Postfix follows the
+/* strict rules in RFC 2818 (HTTP over TLS), section 3.1: The
+/* Subject Alternative Name/dNSName has precedence over
+/* CommonName. If at least one dNSName is provided, Postfix
+/* verifies those against the peer hostname and ignores the
+/* CommonName, otherwise Postfix verifies the CommonName
+/* against the peer hostname.
+/*
+/* tls_client_init() is called once when the SMTP client
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so peer-specific certificate selection is not possible.
+/*
+/* tls_client_start() activates the TLS session over an established
+/* stream. We expect that network buffers are flushed and
+/* the TLS handshake can begin immediately.
+/*
+/* tls_client_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. This consists of one or more of TLS_CERT_FLAG_PRESENT,
+/* TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* At the fingerprint security level, if the peer presented a certificate
+/* the fingerprint of the certificate.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the TLS library historically provides,
+/* including tls_client_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that an event-driven application is responsible for calling
+/* and retrying SSL_connect(), SSL_read(), SSL_write() and
+/* SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven client
+/* invokes tls_client_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_client_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_connect(), and if successful,
+/* for invoking tls_client_post_connect() to finish the work
+/* that was started by tls_client_start(). In case of unrecoverable
+/* failure, tls_client_post_connect() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <argv.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <msg.h>
+#include <iostuff.h> /* non-blocking */
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* load_clnt_session - load session from client cache (non-callback) */
+
+static SSL_SESSION *load_clnt_session(TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "load_clnt_session";
+ SSL_SESSION *session = 0;
+ VSTRING *session_data = vstring_alloc(2048);
+
+ /*
+ * Prepare the query.
+ */
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("looking for session %s in %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * We only get here if the cache_type is not empty. This code is not
+ * called unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null client session cache type in session lookup",
+ myname);
+
+ /*
+ * Look up and activate the SSL_SESSION object. Errors are non-fatal,
+ * since caching is only an optimization.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, TLScontext->serverid,
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session) {
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("reloaded session %s from %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* new_client_session_cb - name new session and save it to client cache */
+
+static int new_client_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_client_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+
+ /*
+ * The cache name (if caching is enabled in tlsmgr(8)) and the cache ID
+ * string for this session are stored in the TLScontext. It cannot be
+ * null at this point.
+ */
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ /*
+ * We only get here if the cache_type is not empty. This callback is not
+ * set unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null session cache type in new session callback",
+ myname);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("save session %s to %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session object. Errors are non-fatal, since
+ * caching is only an optimization.
+ */
+ if ((session_data = tls_session_passivate(session)) != 0) {
+ tls_mgr_update(TLScontext->cache_type, TLScontext->serverid,
+ STR(session_data), LEN(session_data));
+ vstring_free(session_data);
+ }
+
+ /*
+ * Clean up.
+ */
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+/* uncache_session - remove session from the external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+
+ SSL_CTX_remove_session(ctx, session);
+ if (TLScontext->cache_type == 0 || TLScontext->serverid == 0)
+ return;
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("remove session %s from client cache", TLScontext->serverid);
+
+ tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
+}
+
+/* verify_extract_name - verify peer name and extract peer information */
+
+static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ int verbose;
+
+ verbose = TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT);
+
+ /*
+ * On exit both peer_CN and issuer_CN should be set.
+ */
+ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext);
+ TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
+
+ /*
+ * Is the certificate trust chain trusted and matched? Any required name
+ * checks are now performed internally in OpenSSL.
+ */
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+ if (TLScontext->must_fail) {
+ msg_panic("%s: cert valid despite trust init failure",
+ TLScontext->namaddr);
+ } else if (TLS_MUST_MATCH(TLScontext->level)) {
+
+ /*
+ * Fully secured only if not insecure like half-dane. We use
+ * TLS_CERT_FLAG_MATCHED to satisfy policy, but
+ * TLS_CERT_FLAG_SECURED to log the effective security.
+ *
+ * Would ideally also exclude "verify" (as opposed to "secure")
+ * here, because that can be subject to insecure MX indirection,
+ * but that's rather incompatible (and not even the case with
+ * explicitly chosen non-default match patterns). Users have
+ * been warned.
+ */
+ if (!TLS_NEVER_SECURED(TLScontext->level))
+ TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+
+ if (verbose) {
+ const char *peername = SSL_get0_peername(TLScontext->con);
+
+ if (peername)
+ msg_info("%s: matched peername: %s",
+ TLScontext->namaddr, peername);
+ tls_dane_log(TLScontext);
+ }
+ }
+ }
+
+ /*
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
+ * into the cache. We don't want mystery failures, so log the fact the
+ * real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_MATCHED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log", props->namaddr);
+ }
+}
+
+/* add_namechecks - tell OpenSSL what names to check */
+
+static void add_namechecks(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ SSL *ssl = TLScontext->con;
+ int namechecks_count = 0;
+ int i;
+
+ /* RFC6125: No part-label 'foo*bar.example.com' wildcards for SMTP */
+ SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+
+ for (i = 0; i < props->matchargv->argc; ++i) {
+ const char *name = props->matchargv->argv[i];
+ const char *aname;
+ int match_subdomain = 0;
+
+ if (strcasecmp(name, "nexthop") == 0) {
+ name = props->nexthop;
+ } else if (strcasecmp(name, "dot-nexthop") == 0) {
+ name = props->nexthop;
+ match_subdomain = 1;
+ } else if (strcasecmp(name, "hostname") == 0) {
+ name = props->host;
+ } else {
+ if (*name == '.') {
+ if (*++name == 0) {
+ msg_warn("%s: ignoring invalid match name: \".\"",
+ TLScontext->namaddr);
+ continue;
+ }
+ match_subdomain = 1;
+ }
+#ifndef NO_EAI
+ else {
+
+ /*
+ * Besides U+002E (full stop) IDNA2003 allows labels to be
+ * separated by any of the Unicode variants U+3002
+ * (ideographic full stop), U+FF0E (fullwidth full stop), and
+ * U+FF61 (halfwidth ideographic full stop). Their respective
+ * UTF-8 encodings are: E38082, EFBC8E and EFBDA1.
+ *
+ * IDNA2008 does not permit (upper) case and other variant
+ * differences in U-labels. The midna_domain_to_ascii()
+ * function, based on UTS46, normalizes such differences
+ * away.
+ *
+ * The IDNA to_ASCII conversion does not allow empty leading
+ * labels, so we handle these explicitly here.
+ */
+ unsigned char *cp = (unsigned char *) name;
+
+ if ((cp[0] == 0xe3 && cp[1] == 0x80 && cp[2] == 0x82)
+ || (cp[0] == 0xef && cp[1] == 0xbc && cp[2] == 0x8e)
+ || (cp[0] == 0xef && cp[1] == 0xbd && cp[2] == 0xa1)) {
+ if (name[3]) {
+ name = name + 3;
+ match_subdomain = 1;
+ }
+ }
+ }
+#endif
+ }
+
+ /*
+ * DNS subjectAltNames are required to be ASCII.
+ *
+ * Per RFC 6125 Section 6.4.4 Matching the CN-ID, follows the same rules
+ * (6.4.1, 6.4.2 and 6.4.3) that apply to subjectAltNames. In
+ * particular, 6.4.2 says that the reference identifier is coerced to
+ * ASCII, but no conversion is stated or implied for the CN-ID, so it
+ * seems it only matches if it is all ASCII. Otherwise, it is some
+ * other sort of name.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ name = aname;
+ }
+#endif
+
+ if (!match_subdomain) {
+ if (SSL_add1_host(ssl, name))
+ ++namechecks_count;
+ else
+ msg_warn("%s: error loading match name: \"%s\"",
+ TLScontext->namaddr, name);
+ } else {
+ char *dot_name = concatenate(".", name, (char *) 0);
+
+ if (SSL_add1_host(ssl, dot_name))
+ ++namechecks_count;
+ else
+ msg_warn("%s: error loading match name: \"%s\"",
+ TLScontext->namaddr, dot_name);
+ myfree(dot_name);
+ }
+ }
+
+ /*
+ * If we failed to add any names, OpenSSL will perform no namechecks, so
+ * we set the "must_fail" bit to avoid verification false-positives.
+ */
+ if (namechecks_count == 0) {
+ msg_warn("%s: could not configure peer name checks",
+ TLScontext->namaddr);
+ TLScontext->must_fail = 1;
+ }
+}
+
+/* tls_auth_enable - set up TLS authentication */
+
+static int tls_auth_enable(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const char *sni = 0;
+
+ if (props->sni && *props->sni) {
+#ifndef NO_EAI
+ const char *aname;
+
+#endif
+
+ /*
+ * MTA-STS policy plugin compatibility: with servername=hostname,
+ * Postfix must send the MX hostname (not CNAME expanded).
+ */
+ if (strcmp(props->sni, "hostname") == 0)
+ sni = props->host;
+ else if (strcmp(props->sni, "nexthop") == 0)
+ sni = props->nexthop;
+ else
+ sni = props->sni;
+
+ /*
+ * The SSL_set_tlsext_host_name() documentation does not promise that
+ * every implementation will convert U-label form to A-label form.
+ */
+#ifndef NO_EAI
+ if (!allascii(sni) && (aname = midna_domain_to_ascii(sni)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", sni, aname);
+ sni = aname;
+ }
+#endif
+ }
+ switch (TLScontext->level) {
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+
+ /*
+ * With DANE sessions, send an SNI hint. We don't care whether the
+ * server reports finding a matching certificate or not, so no
+ * callback is required to process the server response. Our use of
+ * SNI is limited to giving servers that make use of SNI the best
+ * opportunity to find the certificate they promised via the
+ * associated TLSA RRs.
+ *
+ * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and
+ * therefore valid for use with SNI.
+ */
+ if (SSL_dane_enable(TLScontext->con, 0) <= 0) {
+ msg_warn("%s: error enabling DANE-based certificate validation",
+ TLScontext->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ /* RFC7672 Section 3.1.1 specifies no name checks for DANE-EE(3) */
+ SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+
+ /* Per RFC7672 the SNI name is the TLSA base domain */
+ sni = props->dane->base_domain;
+ add_namechecks(TLScontext, props);
+ break;
+
+ case TLS_LEV_FPRINT:
+ /* Synthetic DANE for fingerprint security */
+ if (SSL_dane_enable(TLScontext->con, 0) <= 0) {
+ msg_warn("%s: error enabling fingerprint certificate validation",
+ props->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ break;
+
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ if (TLScontext->dane != 0 && TLScontext->dane->tlsa != 0) {
+ /* Synthetic DANE for per-destination trust-anchors */
+ if (SSL_dane_enable(TLScontext->con, NULL) <= 0) {
+ msg_warn("%s: error configuring local trust anchors",
+ props->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ }
+ add_namechecks(TLScontext, props);
+ break;
+ default:
+ break;
+ }
+
+ if (sni) {
+ if (strlen(sni) > TLSEXT_MAXLEN_host_name) {
+ msg_warn("%s: ignoring too long SNI hostname: %.100s",
+ props->namaddr, sni);
+ return (0);
+ }
+
+ /*
+ * Failure to set a valid SNI hostname is a memory allocation error,
+ * and thus transient. Since we must not cache the session if we
+ * failed to send the SNI name, we have little choice but to abort.
+ */
+ if (!SSL_set_tlsext_host_name(TLScontext->con, sni)) {
+ msg_warn("%s: error setting SNI hostname to: %s", props->namaddr,
+ sni);
+ return (0);
+ }
+
+ /*
+ * The saved value is not presently used client-side, but could later
+ * be logged if acked by the server (requires new client-side
+ * callback to detect the ack). For now this just maintains symmetry
+ * with the server code, where do record the received SNI for
+ * logging.
+ */
+ TLScontext->peer_sni = mystrdup(sni);
+ if (TLScontext->log_mask & TLS_LOG_DEBUG)
+ msg_info("%s: SNI hostname: %s", props->namaddr, sni);
+ }
+ return (1);
+}
+
+/* tls_client_init - initialize client-side TLS engine */
+
+TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
+{
+ SSL_CTX *client_ctx;
+ TLS_APPL_STATE *app_ctx;
+ const EVP_MD *fpt_alg;
+ long off = 0;
+ int cachable;
+ int scache_timeout;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the client-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if ((fpt_alg = tls_validate_digest(props->mdalg)) == 0) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. RFC2487 is only specified for TLSv1, but
+ * we want to be as compatible as possible, so we will start off with a
+ * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict
+ * this with the options setting later, anyhow.
+ */
+ ERR_clear_error();
+ client_ctx = SSL_CTX_new(TLS_client_method());
+ if (client_ctx == 0) {
+ msg_warn("cannot allocate client SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(client_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(client_ctx, props->verifydepth + 1);
+
+ /*
+ * This is a prerequisite for enabling DANE support in OpenSSL, but not a
+ * commitment to use DANE, thus suitable for both DANE and non-DANE TLS
+ * connections. Indeed we need this not just for DANE, but aslo for
+ * fingerprint and "tafile" support. Since it just allocates memory, it
+ * should never fail except when we're likely to fail anyway. Rather
+ * than try to run with crippled TLS support, just give up using TLS.
+ */
+ if (SSL_CTX_dane_enable(client_ctx) <= 0) {
+ msg_warn("OpenSSL DANE initialization failed: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+ tls_dane_digest_init(client_ctx, fpt_alg);
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol selection is destination dependent, so we delay the protocol
+ * selection options to the per-session SSL object.
+ */
+ off |= tls_bug_bits();
+ SSL_CTX_set_options(client_ctx, off);
+
+ /*
+ * Set the call-back routine for verbose logging.
+ */
+ if (log_mask & TLS_LOG_DEBUG)
+ SSL_CTX_set_info_callback(client_ctx, tls_info_callback);
+
+ /*
+ * Load the CA public key certificates for both the client cert and for
+ * the verification of server certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(client_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * We do not need a client certificate, so the certificates are only
+ * loaded (and checked) if supplied. A clever client would handle
+ * multiple client certificates and decide based on the list of
+ * acceptable CAs, sent by the server, which certificate to submit.
+ * OpenSSL does however not do this and also has no call-back hooks to
+ * easily implement it.
+ *
+ * Load the client public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). The client
+ * certificate is presented after the server chooses the session cipher,
+ * so we will just present the right cert for the chosen cipher (if it
+ * uses certificates).
+ */
+ if (tls_set_my_certificate_key_info(client_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * With OpenSSL 1.0.2 and later the client EECDH curve list becomes
+ * configurable with the preferred curve negotiated via the supported
+ * curves extension.
+ */
+ tls_auto_eecdh_curves(client_ctx, var_tls_eecdh_auto);
+
+ /*
+ * Finally, the setup for the server certificate checking, done "by the
+ * book".
+ */
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+
+ /*
+ * Initialize the session cache.
+ *
+ * Since the client does not search an internal cache, we simply disable it.
+ * It is only useful for expiring old sessions, but we do that in the
+ * tlsmgr(8).
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly (not
+ * via a callback).
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Allocate an application context, and populate with mandatory protocol
+ * and cipher data.
+ */
+ app_ctx = tls_alloc_app_context(client_ctx, 0, log_mask);
+
+ /*
+ * The external session cache is implemented by the tlsmgr(8) process.
+ */
+ if (cachable) {
+
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ /*
+ * OpenSSL does not use callbacks to load sessions from a client
+ * cache, so we must invoke that function directly. Apparently,
+ * OpenSSL does not provide a way to pass session names from here to
+ * call-back routines that do session lookup.
+ *
+ * OpenSSL can, however, automatically save newly created sessions for
+ * us by callback (we create the session name in the call-back
+ * function).
+ *
+ * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of
+ * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ * SSL_SESS_CACHE_NO_AUTO_CLEAR.
+ */
+#ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE
+#define SSL_SESS_CACHE_NO_INTERNAL_STORE 0
+#endif
+
+ SSL_CTX_set_session_cache_mode(client_ctx,
+ SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb);
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session to
+ * twice the cache lifetime. This way a session always lasts longer
+ * than its lifetime in the cache.
+ */
+ SSL_CTX_set_timeout(client_ctx, 2 * scache_timeout);
+ }
+ return (app_ctx);
+}
+
+ /*
+ * This is the actual startup routine for the connection. We expect that the
+ * buffers are flushed and the "220 Ready to start TLS" was received by us,
+ * so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
+{
+ int sts;
+ int protomask;
+ int min_proto;
+ int max_proto;
+ const char *cipher_list;
+ SSL_SESSION *session = 0;
+ TLS_SESS_STATE *TLScontext;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * When certificate verification is required, log trust chain validation
+ * errors even when disabled by default for opportunistic sessions. For
+ * DANE this only applies when using trust-anchor associations.
+ */
+ if (TLS_MUST_MATCH(props->tls_level))
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection to %s", props->namaddr);
+
+ /*
+ * First make sure we have valid protocol and cipher parameters
+ *
+ * Per-session protocol restrictions must be applied to the SSL connection,
+ * as restrictions in the global context cannot be cleared.
+ */
+ protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session",
+ props->namaddr, props->protocols);
+ return (0);
+ }
+
+ /*
+ * Though RFC7672 set the floor at SSLv3, we really can and should
+ * require TLS 1.0, since e.g. we send SNI, which is a TLS 1.0 extension.
+ * No DANE domains have been observed to support only SSLv3.
+ *
+ * XXX: Would be nice to make that TLS 1.2 at some point. Users can choose
+ * to exclude TLS 1.0 and TLS 1.1 if they find they don't run into any
+ * problems doing that.
+ */
+ if (TLS_DANE_BASED(props->tls_level))
+ protomask |= TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3;
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ *
+ * If session caching was enabled when TLS was initialized, the cache type
+ * is stored in the client SSL context.
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+ TLScontext->level = props->tls_level;
+
+ if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Per session cipher selection for sessions with mandatory encryption
+ *
+ * The cipherlist is applied to the global SSL context, since it is likely
+ * to stay the same between connections, so we make use of a 1-element
+ * cache to return the same result for identical inputs.
+ */
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ /* Alias DANE digest info from props */
+ TLScontext->dane = props->dane;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+#define CARP_VERSION(which) do { \
+ if (which##_proto != 0) \
+ msg_warn("%s: error setting %simum TLS version to: 0x%04x", \
+ TLScontext->namaddr, #which, which##_proto); \
+ else \
+ msg_warn("%s: error clearing %simum TLS version", \
+ TLScontext->namaddr, #which); \
+ } while (0)
+
+ /*
+ * Apply session protocol restrictions.
+ */
+ if (protomask != 0)
+ SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask));
+ if (!SSL_set_min_proto_version(TLScontext->con, min_proto))
+ CARP_VERSION(min);
+ if (!SSL_set_max_proto_version(TLScontext->con, max_proto))
+ CARP_VERSION(max);
+
+ /*
+ * When applicable, configure DNS-based or synthetic (fingerprint or
+ * local trust anchor) DANE authentication, enable an appropriate SNI
+ * name and peer name matching.
+ *
+ * NOTE, this can change the effective security level, and needs to happen
+ * early.
+ */
+ if (!tls_auth_enable(TLScontext, props)) {
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Try to convey the configured TLSA records for this connection to the
+ * OpenSSL library. If none are "usable", we'll fall back to "encrypt"
+ * when authentication is not mandatory, otherwise we must arrange to
+ * ensure authentication failure.
+ */
+ if (TLScontext->dane && TLScontext->dane->tlsa) {
+ int usable = tls_dane_enable(TLScontext);
+ int must_fail = usable <= 0;
+
+ if (usable == 0) {
+ switch (TLScontext->level) {
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ msg_warn("%s: all TLSA records unusable, fallback to "
+ "unauthenticated TLS", TLScontext->namaddr);
+ must_fail = 0;
+ TLScontext->level = TLS_LEV_ENCRYPT;
+ break;
+
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: all fingerprints unusable", TLScontext->namaddr);
+ break;
+ case TLS_LEV_DANE_ONLY:
+ msg_warn("%s: all TLSA records unusable", TLScontext->namaddr);
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ msg_warn("%s: all trust anchors unusable", TLScontext->namaddr);
+ break;
+ }
+ }
+ TLScontext->must_fail |= must_fail;
+ }
+
+ /*
+ * We compute the policy digest after we compute the SNI name in
+ * tls_auth_enable() and possibly update the TLScontext security level.
+ *
+ * OpenSSL will ignore cached sessions that use the wrong protocol. So we do
+ * not need to filter out cached sessions with the "wrong" protocol,
+ * rather OpenSSL will simply negotiate a new session.
+ *
+ * We salt the session lookup key with the protocol list, so that sessions
+ * found in the cache are plausibly acceptable.
+ *
+ * By the time a TLS client is negotiating ciphers it has already offered to
+ * re-use a session, it is too late to renege on the offer. So we must
+ * not attempt to re-use sessions whose ciphers are too weak. We salt the
+ * session lookup key with the cipher list, so that sessions found in the
+ * cache are always acceptable.
+ *
+ * With DANE, (more generally any TLScontext where we specified explicit
+ * trust-anchor or end-entity certificates) the verification status of
+ * the SSL session depends on the specified list. Since we verify the
+ * certificate only during the initial handshake, we must segregate
+ * sessions with different TA lists. Note, that TA re-verification is
+ * not possible with cached sessions, since these don't hold the complete
+ * peer trust chain. Therefore, we compute a digest of the sorted TA
+ * parameters and append it to the serverid.
+ */
+ TLScontext->serverid =
+ tls_serverid_digest(TLScontext, props, cipher_list);
+
+ /*
+ * When authenticating the peer, use 80-bit plus OpenSSL security level
+ *
+ * XXX: We should perhaps use security level 1 also for mandatory
+ * encryption, with only "may" tolerating weaker algorithms. But that
+ * could mean no TLS 1.0 with OpenSSL >= 3.0 and encrypt, unless I get my
+ * patch in on time to conditionally re-enable SHA1 at security level 1,
+ * and we add code to make it so.
+ *
+ * That said, with "encrypt", we could reasonably require TLS 1.2?
+ */
+ if (TLS_MUST_MATCH(TLScontext->level))
+ SSL_set_security_level(TLScontext->con, 1);
+
+ /*
+ * XXX To avoid memory leaks we must always call SSL_SESSION_free() after
+ * calling SSL_set_session(), regardless of whether or not the session
+ * will be reused.
+ */
+ if (TLScontext->cache_type) {
+ session = load_clnt_session(TLScontext);
+ if (session) {
+ SSL_set_session(TLScontext->con, session);
+ SSL_SESSION_free(session); /* 200411 */
+ }
+ }
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_connect/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_connect error to %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_connect error to %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_connect error to %s: lost connection",
+ props->namaddr);
+ }
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_client_post_connect(TLScontext, props));
+}
+
+/* tls_client_post_connect - post-handshake processing */
+
+TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peercert;
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session", TLScontext->namaddr);
+
+ /*
+ * Do peername verification if requested and extract useful information
+ * from the certificate for later use.
+ */
+ if ((peercert = TLS_PEEK_PEER_CERT(TLScontext->con)) != 0) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+
+ /*
+ * Peer name or fingerprint verification as requested.
+ * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
+ * fingerprint first, and avoid logging verified as untrusted in the
+ * call to verify_extract_name().
+ */
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg);
+ verify_extract_name(TLScontext, peercert, props);
+
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ } else {
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * The TLS engine is active. Switch to the tls_timed_read/write()
+ * functions and make the TLScontext available to those functions.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(props->stream, TLScontext);
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c
new file mode 100644
index 0000000..a2b9b80
--- /dev/null
+++ b/src/tls/tls_dane.c
@@ -0,0 +1,1357 @@
+/*++
+/* NAME
+/* tls_dane 3
+/* SUMMARY
+/* Support for RFC 6698, 7671, 7672 (DANE) certificate matching
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* void tls_dane_loglevel(log_param, log_level);
+/* const char *log_param;
+/* const char *log_level;
+/*
+/* int tls_dane_avail()
+/*
+/* void tls_dane_flush()
+/*
+/* TLS_DANE *tls_dane_alloc()
+/*
+/* void tls_tlsa_free(tlsa)
+/* TLS_TLSA *tlsa;
+/*
+/* void tls_dane_free(dane)
+/* TLS_DANE *dane;
+/*
+/* void tls_dane_add_fpt_digests(dane, digest, delim, smtp_mode)
+/* TLS_DANE *dane;
+/* const char *digest;
+/* const char *delim;
+/* int smtp_mode;
+/*
+/* TLS_TLSA *tlsa_prepend(tlsa, usage, selector, mtype, data, len)
+/* TLS_TLSA *tlsa;
+/* uint8_t usage;
+/* uint8_t selector;
+/* uint8_t mtype;
+/* const unsigned char *data;
+/* uint16_t length;
+/*
+/* int tls_dane_load_trustfile(dane, tafile)
+/* TLS_DANE *dane;
+/* const char *tafile;
+/*
+/* TLS_DANE *tls_dane_resolve(port, proto, hostrr, forcetlsa)
+/* unsigned port;
+/* const char *proto;
+/* DNS_RR *hostrr;
+/* int forcetlsa;
+/*
+/* void tls_dane_digest_init(ctx, fpt_alg)
+/* SSL_CTX *ctx;
+/* const EVP_MD *fpt_alg;
+/*
+/* void tls_dane_enable(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_dane_log(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* int tls_dane_unusable(dane)
+/* const TLS_DANE *dane;
+/*
+/* int tls_dane_notfound(dane)
+/* const TLS_DANE *dane;
+/* DESCRIPTION
+/* tls_dane_loglevel() allows the policy lookup functions in the DANE
+/* library to examine the application's TLS loglevel in and possibly
+/* produce a more detailed activity log.
+/*
+/* tls_dane_avail() returns true if the features required to support DANE
+/* are present in libresolv.
+/*
+/* tls_dane_flush() flushes all entries from the cache, and deletes
+/* the cache.
+/*
+/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE
+/* structure with null ta and ee digest sublists.
+/*
+/* tls_tlsa_free() frees a TLSA record linked list.
+/*
+/* tls_dane_free() frees the structure allocated by tls_dane_alloc().
+/*
+/* tls_dane_digest_init() configures OpenSSL to support the configured
+/* DANE TLSA digests and private-use fingerprint digest.
+/*
+/* tlsa_prepend() prepends a TLSA record to the head of a linked list
+/* which may be null when the list is empty. The result value is the
+/* new list head.
+/*
+/* tls_dane_add_fpt_digests() splits "digest" using the characters in
+/* "delim" as delimiters and generates corresponding synthetic DANE TLSA
+/* records with matching type 255 (private-use), which we associated with
+/* the configured fingerprint digest algorithm. This is an incremental
+/* interface, that builds a TLS_DANE structure outside the cache by
+/* manually adding entries.
+/*
+/* tls_dane_load_trustfile() imports trust-anchor certificates and
+/* public keys from a file (rather than DNS TLSA records).
+/*
+/* tls_dane_resolve() maps a (port, protocol, hostrr) tuple to a
+/* corresponding TLS_DANE policy structure found in the DNS. The port
+/* argument is in network byte order. A null pointer is returned when
+/* the DNS query for the TLSA record tempfailed. In all other cases the
+/* return value is a pointer to the corresponding TLS_DANE structure.
+/* The caller must free the structure via tls_dane_free().
+/*
+/* tls_dane_enable() enables DANE-style certificate checks for connections
+/* that are configured with TLSA records. The TLSA records may be from
+/* DNS (at the "dane", "dane-only" and "half-dane" security levels), or be
+/* synthetic in support of either the "fingerprint" level or local trust
+/* anchor based validation with the "secure" and "verify" levels. The
+/* return value is the number of "usable" TLSA records loaded, or negative
+/* if a record failed to load due to an internal OpenSSL problems, rather
+/* than an issue with the record making that record "unusable".
+/*
+/* tls_dane_log() logs successful verification via DNS-based or
+/* synthetic DANE TLSA RRs (fingerprint or "tafile").
+/*
+/* tls_dane_unusable() checks whether a cached TLS_DANE record is
+/* the result of a validated RRset, with no usable elements. In
+/* this case, TLS is mandatory, but certificate verification is
+/* not DANE-based.
+/*
+/* tls_dane_notfound() checks whether a cached TLS_DANE record is
+/* the result of a validated DNS lookup returning NODATA. In
+/* this case, TLS is not required by RFC, though users may elect
+/* a mandatory TLS fallback policy.
+/*
+/* Arguments:
+/* .IP ctx
+/* SSL context to be configured with the chosen digest algorithms.
+/* .IP fpt_alg
+/* The OpenSSL EVP digest algorithm handle for the fingerprint digest.
+/* .IP tlsa
+/* TLSA record linked list head, initially NULL.
+/* .IP usage
+/* DANE TLSA certificate usage field.
+/* .IP selector
+/* DANE TLSA selector field.
+/* .IP mtype
+/* DANE TLSA matching type field
+/* .IP data
+/* DANE TLSA associated data field (raw binary form), copied for internal
+/* use. The caller is responsible for freeing his own copy.
+/* .IP length
+/* Length of DANE TLSA associated DATA field.
+/* .IP dane
+/* Pointer to a TLS_DANE structure that lists the valid trust-anchor
+/* and end-entity full-certificate and/or public-key digests.
+/* .IP port
+/* The TCP port in network byte order.
+/* .IP proto
+/* Almost certainly "tcp".
+/* .IP hostrr
+/* DNS_RR pointer to TLSA base domain data.
+/* .IP forcetlsa
+/* When true, TLSA lookups are performed even when the qname and rname
+/* are insecure. This is only useful in the unlikely case that DLV is
+/* used to secure the TLSA RRset in an otherwise insecure zone.
+/* .IP log_param
+/* The TLS log level parameter name whose value is the log_level argument.
+/* .IP log_level
+/* The application TLS log level, which may affect dane lookup verbosity.
+/* .IP digest
+/* The digest (or list of digests concatenated with characters from
+/* "delim") to be added to the TLS_DANE record.
+/* .IP delim
+/* The set of delimiter characters used above.
+/* .IP smtp_mode
+/* Is the caller an SMTP client or an LMTP client?
+/* .IP tafile;
+/* A file with trust anchor certificates or public keys in PEM format.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <midna_domain.h>
+#include <vstring.h>
+#include <events.h> /* event_time() */
+#include <timecmp.h>
+#include <ctable.h>
+#include <hex_code.h>
+#include <safe_ultostr.h>
+#include <split_at.h>
+#include <name_code.h>
+
+#define STR(x) vstring_str(x)
+
+/* Global library */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#undef DANE_TLSA_SUPPORT
+
+#if RES_USE_DNSSEC && RES_USE_EDNS0
+#define DANE_TLSA_SUPPORT
+static int dane_tlsa_support = 1;
+
+#else
+static int dane_tlsa_support = 0;
+
+#endif
+
+/*
+ * A NULL alg field disables the algorithm at the codepoint passed to the
+ * SSL_CTX_dane_mtype_set(3) function. The ordinals are used for digest
+ * agility, higher is "better" (presumed stronger).
+ */
+typedef struct dane_mtype {
+ const EVP_MD *alg;
+ uint8_t ord;
+} dane_mtype;
+
+/*
+ * This is not intended to be a long-term cache of pre-parsed TLSA data,
+ * rather we primarily want to avoid fetching and parsing the TLSA records
+ * for a single multi-homed MX host more than once per delivery. Therefore,
+ * we keep the table reasonably small.
+ */
+#define CACHE_SIZE 20
+static CTABLE *dane_cache;
+
+static int log_mask;
+
+/* tls_dane_logmask - configure policy lookup logging */
+
+void tls_dane_loglevel(const char *log_param, const char *log_level)
+{
+ log_mask = tls_log_mask(log_param, log_level);
+}
+
+/* tls_dane_avail - check for availability of dane required digests */
+
+int tls_dane_avail(void)
+{
+ return (dane_tlsa_support);
+}
+
+/* tls_dane_alloc - allocate a TLS_DANE structure */
+
+TLS_DANE *tls_dane_alloc(void)
+{
+ TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane));
+
+ dane->tlsa = 0;
+ dane->base_domain = 0;
+ dane->flags = 0;
+ dane->expires = 0;
+ dane->refs = 1;
+ return (dane);
+}
+
+/* tls_tlsa_free - free a TLSA RR linked list */
+
+void tls_tlsa_free(TLS_TLSA *tlsa)
+{
+ TLS_TLSA *next;
+
+ for (; tlsa; tlsa = next) {
+ next = tlsa->next;
+ myfree(tlsa->data);
+ myfree(tlsa);
+ }
+}
+
+/* tls_dane_free - free a TLS_DANE structure */
+
+void tls_dane_free(TLS_DANE *dane)
+{
+ if (--dane->refs > 0)
+ return;
+ if (dane->base_domain)
+ myfree(dane->base_domain);
+ if (dane->tlsa)
+ tls_tlsa_free(dane->tlsa);
+ myfree((void *) dane);
+}
+
+/* tlsa_prepend - Prepend internal-form TLSA record to the RRset linked list */
+
+TLS_TLSA *tlsa_prepend(TLS_TLSA *tlsa, uint8_t usage, uint8_t selector,
+ uint8_t mtype, const unsigned char *data,
+ uint16_t data_len)
+{
+ TLS_TLSA *head;
+
+ head = (TLS_TLSA *) mymalloc(sizeof(*head));
+ head->usage = usage;
+ head->selector = selector;
+ head->mtype = mtype;
+ head->length = data_len;
+ head->data = (unsigned char *) mymemdup(data, data_len);
+ head->next = tlsa;
+ return (head);
+}
+
+#define MAX_HEAD_BYTES 32
+#define MAX_TAIL_BYTES 32
+#define MAX_DUMP_BYTES (MAX_HEAD_BYTES + MAX_TAIL_BYTES)
+
+/* tlsa_info - log import of a particular TLSA record */
+
+static void tlsa_info(const char *tag, const char *msg,
+ uint8_t u, uint8_t s, uint8_t m,
+ const unsigned char *data, ssize_t dlen)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else if (dlen > 0) {
+ hex_encode(top, (char *) data, dlen);
+ } else {
+ vstring_sprintf(top, "...");
+ }
+
+ msg_info("%s: %s: %u %u %u %s%s%s", tag, msg, u, s, m, STR(top),
+ dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+}
+
+/* tlsa_carp - carp about a particular TLSA record */
+
+static void tlsa_carp(const char *s1, const char *s2, const char *s3,
+ const char *s4, uint8_t u, uint8_t s, uint8_t m,
+ const unsigned char *data, ssize_t dlen)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else if (dlen > 0) {
+ hex_encode(top, (char *) data, dlen);
+ } else {
+ vstring_sprintf(top, "...");
+ }
+
+ msg_warn("%s%s%s %s: %u %u %u %s%s%s", s1, s2, s3, s4, u, s, m, STR(top),
+ dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+}
+
+/* tls_dane_flush - flush the cache */
+
+void tls_dane_flush(void)
+{
+ if (dane_cache)
+ ctable_free(dane_cache);
+ dane_cache = 0;
+}
+
+/* dane_free - ctable style */
+
+static void dane_free(void *dane, void *unused_context)
+{
+ tls_dane_free((TLS_DANE *) dane);
+}
+
+/* tls_dane_add_fpt_digests - map fingerprint list to DANE TLSA RRset */
+
+void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
+ const char *delim, int smtp_mode)
+{
+ ARGV *values = argv_split(digest, delim);
+ ssize_t i;
+
+ if (smtp_mode) {
+ if (warn_compat_break_smtp_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTP_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ } else {
+ if (warn_compat_break_lmtp_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_LMTP_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ }
+
+ for (i = 0; i < values->argc; ++i) {
+ const char *cp = values->argv[i];
+ size_t ilen = strlen(cp);
+ VSTRING *raw;
+
+ /*
+ * Decode optionally colon-separated hex-encoded string, the input
+ * value requires at most 3 bytes per byte of payload, which must not
+ * exceed the size of the widest supported hash function.
+ */
+ if (ilen > 3 * EVP_MAX_MD_SIZE) {
+ msg_warn("malformed fingerprint value: %.100s...",
+ values->argv[i]);
+ continue;
+ }
+ raw = vstring_alloc(ilen / 2);
+ if (hex_decode_opt(raw, cp, ilen, HEX_DECODE_FLAG_ALLOW_COLON) == 0) {
+ myfree(raw);
+ msg_warn("malformed fingerprint value: %.384s", values->argv[i]);
+ continue;
+ }
+
+ /*
+ * At the "fingerprint" security level certificate digests and public
+ * key digests are interchangeable. Each leaf certificate is matched
+ * via either the public key digest or full certificate digest. The
+ * DER encoding of a certificate is not a valid public key, and
+ * conversely, the DER encoding of a public key is not a valid
+ * certificate. An attacker would need a 2nd-preimage that is
+ * feasible across types (given cert digest == some pkey digest) and
+ * yet presumably difficult within a type (e.g. given cert digest ==
+ * some other cert digest). No such attacks are known at this time,
+ * and it is expected that if any are found they would work within as
+ * well as across the cert/pkey data types.
+ *
+ * The private-use matching type "255" is mapped to the configured
+ * fingerprint digest, which may (harmlessly) coincide with one of
+ * the standard DANE digest algorithms. The private code point is
+ * however unconditionally enabled.
+ */
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("fingerprint", "digest as private-use TLSA record",
+ 3, 0, 255, (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255,
+ (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255,
+ (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ vstring_free(raw);
+ }
+ argv_free(values);
+}
+
+/* parse_tlsa_rr - parse a validated TLSA RRset */
+
+static int parse_tlsa_rr(TLS_DANE *dane, DNS_RR *rr)
+{
+ const uint8_t *ip;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t mtype;
+ ssize_t dlen;
+ unsigned const char *data;
+ int iscname = strcasecmp(rr->rname, rr->qname);
+ const char *q = iscname ? rr->qname : "";
+ const char *a = iscname ? " -> " : "";
+ const char *r = rr->rname;
+
+ if (rr->type != T_TLSA)
+ msg_panic("%s%s%s: unexpected non-TLSA RR type: %u",
+ q, a, r, rr->type);
+
+ /* Drop truncated records */
+ if ((dlen = rr->data_len - 3) < 0) {
+ msg_warn("%s%s%s: truncated TLSA RR length == %u",
+ q, a, r, (unsigned) rr->data_len);
+ return (0);
+ }
+ ip = (const uint8_t *) rr->data;
+ usage = *ip++;
+ selector = *ip++;
+ mtype = *ip++;
+ data = (const unsigned char *) ip;
+
+ /*-
+ * Drop unsupported usages.
+ * Note: NO SUPPORT for usages 0/1 which do not apply to SMTP.
+ */
+ switch (usage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ tlsa_carp(q, a, r, "unsupported TLSA certificate usage",
+ usage, selector, mtype, data, dlen);
+ return (0);
+ }
+
+ /*
+ * Drop private-use matching type, reserved for fingerprint matching.
+ */
+ if (mtype == 255) {
+ tlsa_carp(q, a, r, "reserved private-use matching type",
+ usage, selector, mtype, data, dlen);
+ return (0);
+ }
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("DNSSEC-signed TLSA record", r,
+ usage, selector, mtype, data, dlen);
+ dane->tlsa = tlsa_prepend(dane->tlsa, usage, selector, mtype, data, dlen);
+ return (1);
+}
+
+/* dane_lookup - TLSA record lookup, ctable style */
+
+static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx)
+{
+ static VSTRING *why = 0;
+ DNS_RR *rrs = 0;
+ DNS_RR *rr;
+ TLS_DANE *dane = tls_dane_alloc();
+ int ret;
+
+ if (why == 0)
+ why = vstring_alloc(10);
+
+ ret = dns_lookup(tlsa_fqdn, T_TLSA, RES_USE_DNSSEC, &rrs, 0, why);
+
+ switch (ret) {
+ case DNS_OK:
+ if (TLS_DANE_CACHE_TTL_MIN && rrs->ttl < TLS_DANE_CACHE_TTL_MIN)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MIN;
+ if (TLS_DANE_CACHE_TTL_MAX && rrs->ttl > TLS_DANE_CACHE_TTL_MAX)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MAX;
+
+ /* One more second to account for discrete time */
+ dane->expires = 1 + event_time() + rrs->ttl;
+
+ if (rrs->dnssec_valid) {
+ int n = 0;
+
+ for (rr = rrs; rr != 0; rr = rr->next)
+ n += parse_tlsa_rr(dane, rr);
+ if (n == 0)
+ dane->flags |= TLS_DANE_FLAG_EMPTY;
+ } else
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+
+ if (rrs)
+ dns_rr_free(rrs);
+ break;
+
+ case DNS_NOTFOUND:
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+ dane->expires = 1 + event_time() + TLS_DANE_CACHE_TTL_MIN;
+ break;
+
+ default:
+ msg_warn("DANE TLSA lookup problem: %s", STR(why));
+ dane->flags |= TLS_DANE_FLAG_ERROR;
+ break;
+ }
+
+ return (void *) dane;
+}
+
+/* resolve_host - resolve TLSA RRs for hostname (rname or qname) */
+
+static TLS_DANE *resolve_host(const char *host, const char *proto,
+ unsigned port)
+{
+ static VSTRING *query_domain;
+ TLS_DANE *dane;
+
+ if (query_domain == 0)
+ query_domain = vstring_alloc(64);
+
+ vstring_sprintf(query_domain, "_%u._%s.%s", ntohs(port), proto, host);
+ dane = (TLS_DANE *) ctable_locate(dane_cache, STR(query_domain));
+ if (timecmp(event_time(), dane->expires) > 0)
+ dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(query_domain));
+ if (dane->base_domain == 0)
+ dane->base_domain = mystrdup(host);
+ /* Increment ref-count of cached entry */
+ ++dane->refs;
+ return (dane);
+}
+
+/* qname_secure - Lookup qname DNSSEC status */
+
+static int qname_secure(const char *qname)
+{
+ static VSTRING *why;
+ int ret = 0;
+ DNS_RR *rrs;
+
+ if (!why)
+ why = vstring_alloc(10);
+
+ /*
+ * We assume that qname is already an fqdn, and does not need any
+ * suffixes from RES_DEFNAME or RES_DNSRCH. This is typically the name
+ * of an MX host, and must be a complete DNS name. DANE initialization
+ * code in the SMTP client is responsible for checking that the default
+ * resolver flags do not include RES_DEFNAME and RES_DNSRCH.
+ */
+ ret = dns_lookup(qname, T_CNAME, RES_USE_DNSSEC, &rrs, 0, why);
+ if (ret == DNS_OK) {
+ ret = rrs->dnssec_valid;
+ dns_rr_free(rrs);
+ return (ret);
+ }
+ if (ret == DNS_NOTFOUND)
+ vstring_sprintf(why, "no longer a CNAME");
+ msg_warn("DNSSEC status lookup error for %s: %s", qname, STR(why));
+ return (-1);
+}
+
+/* tls_dane_resolve - cached map: (name, proto, port) -> TLS_DANE */
+
+TLS_DANE *tls_dane_resolve(unsigned port, const char *proto, DNS_RR *hostrr,
+ int forcetlsa)
+{
+ TLS_DANE *dane = 0;
+ int iscname = strcasecmp(hostrr->rname, hostrr->qname);
+ int isvalid = 1;
+
+ if (!tls_dane_avail())
+ return (0); /* Error */
+
+ /*
+ * By default suppress TLSA lookups for hosts in non-DNSSEC zones. If
+ * the host zone is not DNSSEC validated, the TLSA qname sub-domain is
+ * safely assumed to not be in a DNSSEC Look-aside Validation child zone.
+ */
+ if (!forcetlsa && !hostrr->dnssec_valid) {
+ isvalid = iscname ? qname_secure(hostrr->qname) : 0;
+ if (isvalid < 0)
+ return (0); /* Error */
+ }
+ if (!isvalid) {
+ dane = tls_dane_alloc();
+ dane->flags = TLS_DANE_FLAG_NORRS;
+ } else {
+ if (!dane_cache)
+ dane_cache = ctable_create(CACHE_SIZE, dane_lookup, dane_free, 0);
+
+ /*
+ * Try the rname first if secure, if nothing there, try the qname if
+ * different. Note, lookup errors are distinct from success with
+ * nothing found. If the rname lookup fails we don't try the qname.
+ */
+ if (hostrr->dnssec_valid) {
+ dane = resolve_host(hostrr->rname, proto, port);
+ if (tls_dane_notfound(dane) && iscname) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ if (!dane)
+ dane = resolve_host(hostrr->qname, proto, port);
+ if (dane->flags & TLS_DANE_FLAG_ERROR) {
+ /* We don't return this object. */
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+
+ return (dane);
+}
+
+/* tls_dane_load_trustfile - load trust anchor certs or keys from file */
+
+int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int tacount;
+ char *errtype = 0; /* if error: cert or pkey? */
+
+ /* nop */
+ if (tafile == 0 || *tafile == 0)
+ return (1);
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(tafile, "r")) == NULL) {
+ msg_warn("error opening trust anchor file: %s: %m", tafile);
+ return (0);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * OpenSSL implements DANE strictly, with DANE-TA(2) only matching issuer
+ * certificates, and never the leaf cert. We also allow the
+ * trust-anchors to directly match the leaf certificate or public key.
+ */
+ for (tacount = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++tacount) {
+ uint8_t daneta = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION;
+ uint8_t daneee = DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE;
+ uint8_t mtype = DNS_TLSA_MATCHING_TYPE_NO_HASH_USED;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ uint8_t selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE;
+
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("TA cert as TLSA record", tafile,
+ daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len);
+ } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) {
+ uint8_t selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO;
+
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("TA pkey as TLSA record", tafile,
+ daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len);
+ dane->tlsa = tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ msg_warn("error reading: %s: malformed trust-anchor %s",
+ tafile, errtype);
+ return (0);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ return (tacount > 0);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ return (0);
+}
+
+int tls_dane_enable(TLS_SESS_STATE *TLScontext)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ TLS_TLSA *tp;
+ SSL *ssl = TLScontext->con;
+ int usable = 0;
+ int ret;
+
+ for (tp = dane->tlsa; tp != 0; tp = tp->next) {
+ ret = SSL_dane_tlsa_add(ssl, tp->usage, tp->selector,
+ tp->mtype, tp->data, tp->length);
+ if (ret > 0) {
+ ++usable;
+ continue;
+ }
+ if (ret == 0) {
+ tlsa_carp(TLScontext->namaddr, ":", "", "unusable TLSA RR",
+ tp->usage, tp->selector, tp->mtype, tp->data,
+ tp->length);
+ continue;
+ }
+ /* Internal problem in OpenSSL */
+ tlsa_carp(TLScontext->namaddr, ":", "", "error loading trust settings",
+ tp->usage, tp->selector, tp->mtype, tp->data, tp->length);
+ tls_print_errors();
+ return (-1);
+ }
+ return (usable);
+}
+
+/* tls_dane_digest_init - configure supported DANE digests */
+
+void tls_dane_digest_init(SSL_CTX *ctx, const EVP_MD *fpt_alg)
+{
+ dane_mtype mtypes[256];
+ char *cp;
+ char *save;
+ char *algname;
+ uint8_t m;
+ uint8_t ord = 0;
+ uint8_t maxtype;
+
+ memset((char *) mtypes, 0, sizeof(mtypes));
+
+ /*
+ * The DANE SHA2-256(1) and SHA2-512(2) algorithms are disabled, unless
+ * explicitly enabled. Other codepoints can be disabled explicitly by
+ * giving them an empty digest name, which also implicitly disables all
+ * smaller codepoints that are not explicitly assigned.
+ *
+ * We reserve the private-use code point (255) for use with fingerprint
+ * matching. It MUST NOT be accepted in DNS replies.
+ */
+ mtypes[1].alg = NULL;
+ mtypes[2].alg = NULL;
+ mtypes[255].alg = fpt_alg;
+ maxtype = 2;
+
+ save = cp = mystrdup(var_tls_dane_digests);
+ while ((algname = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ char *algcode = split_at(algname, '=');
+ int codepoint = -1;
+
+ if (algcode && *algcode) {
+ unsigned long l;
+ char *endcp;
+
+ /*
+ * XXX: safe_strtoul() does not flag empty or white-space only
+ * input. Since we get algcode by splitting white-space/comma
+ * delimited tokens, this is not a problem here.
+ */
+ l = safe_strtoul(algcode, &endcp, 10);
+ if ((l == 0 && (errno == EINVAL || endcp == algcode))
+ || l >= 255 || *endcp) {
+ msg_warn("Invalid matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, algname, algcode);
+ continue;
+ }
+ if (l == 0 || l == 255) {
+ msg_warn("Reserved matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, algname, algcode);
+ continue;
+ }
+ codepoint = l;
+ }
+ /* Disable any codepoint gaps */
+ if (codepoint > maxtype) {
+ while (++maxtype < codepoint)
+ mtypes[codepoint].alg = NULL;
+ maxtype = codepoint;
+ }
+ /* Handle explicitly disabled codepoints */
+ if (*algname == 0) {
+ /* Skip empty specifiers */
+ if (codepoint < 0)
+ continue;
+ mtypes[codepoint].alg = NULL;
+ continue;
+ }
+ switch (codepoint) {
+ case -1:
+ if (strcasecmp(algname, LN_sha256) == 0)
+ codepoint = 1; /* SHA2-256(1) */
+ else if (strcasecmp(algname, LN_sha512) == 0)
+ codepoint = 2; /* SHA2-512(2) */
+ else {
+ msg_warn("%s: digest algorithm %s needs an explicit number",
+ VAR_TLS_DANE_DIGESTS, algname);
+ continue;
+ }
+ break;
+ case 1:
+ if (strcasecmp(algname, LN_sha256) != 0) {
+ msg_warn("%s: matching type 1 can only be %s",
+ VAR_TLS_DANE_DIGESTS, LN_sha256);
+ continue;
+ }
+ algname = LN_sha256;
+ break;
+ case 2:
+ if (strcasecmp(algname, LN_sha512) != 0) {
+ msg_warn("%s: matching type 2 can only be %s",
+ VAR_TLS_DANE_DIGESTS, LN_sha512);
+ continue;
+ }
+ algname = LN_sha512;
+ break;
+ default:
+ break;
+ }
+
+ if (mtypes[codepoint].ord != 0) {
+ msg_warn("%s: matching type %d specified more than once",
+ VAR_TLS_DANE_DIGESTS, codepoint);
+ continue;
+ }
+ mtypes[codepoint].ord = ++ord;
+
+ if ((mtypes[codepoint].alg = tls_digest_byname(algname, NULL)) == 0) {
+ msg_warn("%s: digest algorithm \"%s\"(%d) unknown",
+ VAR_TLS_DANE_DIGESTS, algname, codepoint);
+ continue;
+ }
+ }
+ myfree(save);
+
+ for (m = 1; m != 0; m = m != maxtype ? m + 1 : 255) {
+
+ /*
+ * In OpenSSL higher order ordinals are preferred, but we list the
+ * most preferred algorithms first, so the last ordinal becomes 1,
+ * next-to-last, 2, ...
+ *
+ * The ordinals of non-disabled algorithms are always positive, and the
+ * computed value cannot overflow 254 (the largest possible value of
+ * 'ord' after loading each valid codepoint at most once).
+ */
+ if (SSL_CTX_dane_mtype_set(ctx, mtypes[m].alg, m,
+ ord - mtypes[m].ord + 1) <= 0) {
+ msg_warn("%s: error configuring matching type %d",
+ VAR_TLS_DANE_DIGESTS, m);
+ tls_print_errors();
+ }
+ }
+}
+
+/* tls_dane_log - log DANE-based verification success */
+
+void tls_dane_log(TLS_SESS_STATE *TLScontext)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+ EVP_PKEY *mspki = 0;
+ int depth = SSL_get0_dane_authority(TLScontext->con, NULL, &mspki);
+ uint8_t u, s, m;
+ unsigned const char *data;
+ size_t dlen;
+
+ if (depth < 0)
+ return; /* No DANE auth */
+
+ switch (TLScontext->level) {
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ msg_info("%s: Matched trust anchor at depth %d",
+ TLScontext->namaddr, depth);
+ return;
+ }
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ (void) SSL_get0_dane_tlsa(TLScontext->con, &u, &s, &m, &data, &dlen);
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else {
+ hex_encode(top, (char *) data, dlen);
+ }
+
+ switch (TLScontext->level) {
+ case TLS_LEV_FPRINT:
+ msg_info("%s: Matched fingerprint: %s%s%s", TLScontext->namaddr,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+ return;
+
+ default:
+ msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
+ TLScontext->namaddr, mspki ?
+ "TA public key verified certificate" : depth ?
+ "TA certificate" : "EE certificate", depth, u, s, m,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+ return;
+ }
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <msg_vstream.h>
+
+static int verify_chain(SSL *ssl, x509_stack_t *chain, TLS_SESS_STATE *tctx)
+{
+ int ret;
+ X509 *cert;
+ X509_STORE_CTX *store_ctx;
+ SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl);
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
+ int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+
+ cert = sk_X509_value(chain, 0);
+ if ((store_ctx = X509_STORE_CTX_new()) == NULL) {
+ SSLerr(SSL_F_SSL_VERIFY_CERT_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ if (!X509_STORE_CTX_init(store_ctx, store, cert, chain)) {
+ X509_STORE_CTX_free(store_ctx);
+ return 0;
+ }
+ X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl);
+
+ /* We're *verifying* a server chain */
+ X509_STORE_CTX_set_default(store_ctx, "ssl_server");
+ X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+ SSL_get0_param(ssl));
+ X509_STORE_CTX_set0_dane(store_ctx, SSL_get0_dane(ssl));
+
+ ret = X509_verify_cert(store_ctx);
+
+ SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx));
+ X509_STORE_CTX_free(store_ctx);
+
+ return (ret);
+}
+
+static void load_tlsa_args(SSL *ssl, char *argv[])
+{
+ const EVP_MD *md = 0;
+ X509 *cert = 0;
+ BIO *bp;
+ unsigned char *buf;
+ unsigned char *buf2;
+ int len;
+ uint8_t u = atoi(argv[1]);
+ uint8_t s = atoi(argv[2]);
+ uint8_t m = atoi(argv[3]);
+ EVP_PKEY *pkey;
+
+ /* Unsupported usages are fatal */
+ switch (u) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_fatal("unsupported certificate usage %u", u);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_fatal("unsupported selector %u", s);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (m) {
+ case DNS_TLSA_MATCHING_TYPE_NO_HASH_USED:
+ case DNS_TLSA_MATCHING_TYPE_SHA256:
+ case DNS_TLSA_MATCHING_TYPE_SHA512:
+ break;
+ default:
+ msg_fatal("unsupported matching type %u", m);
+ }
+
+ if ((bp = BIO_new_file(argv[4], "r")) == NULL)
+ msg_fatal("error opening %s: %m", argv[4]);
+ if (!PEM_read_bio_X509(bp, &cert, 0, 0)) {
+ tls_print_errors();
+ msg_fatal("error loading certificate from %s: %m", argv[4]);
+ }
+ BIO_free(bp);
+
+ /*
+ * Extract ASN.1 DER form of certificate or public key.
+ */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ len = i2d_X509(cert, NULL);
+ if (len > 0xffff)
+ msg_fatal("certificate too long: %d", len);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_X509(cert, &buf2);
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ pkey = X509_get_pubkey(cert);
+ len = i2d_PUBKEY(pkey, NULL);
+ if (len > 0xffff)
+ msg_fatal("public key too long: %d", len);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_PUBKEY(pkey, &buf2);
+ EVP_PKEY_free(pkey);
+ break;
+ }
+ X509_free(cert);
+ OPENSSL_assert(buf2 - buf == len);
+
+ switch (m) {
+ case 0:
+ break;
+ case 1:
+ if ((md = tls_digest_byname(LN_sha256, NULL)) == 0)
+ msg_fatal("Digest %s not found", LN_sha256);
+ break;
+ case 2:
+ if ((md = tls_digest_byname(LN_sha512, NULL)) == 0)
+ msg_fatal("Digest %s not found", LN_sha512);
+ break;
+ default:
+ msg_fatal("Unsupported DANE mtype: %d", m);
+ }
+
+ if (md != 0) {
+ unsigned char mdbuf[EVP_MAX_MD_SIZE];
+ unsigned int mdlen = sizeof(mdbuf);
+
+ if (!EVP_Digest(buf, len, mdbuf, &mdlen, md, 0))
+ msg_fatal("Digest failure for mtype: %d", m);
+ myfree(buf);
+ buf = (unsigned char *) mymemdup(mdbuf, len = mdlen);
+ }
+ SSL_dane_tlsa_add(ssl, u, s, m, buf, len);
+ myfree((void *) buf);
+}
+
+static x509_stack_t *load_chain(const char *chainfile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int count;
+ char *errtype = 0; /* if error: cert or pkey? */
+ x509_stack_t *chain;
+ typedef X509 *(*d2i_X509_t) (X509 **, const unsigned char **, long);
+
+ if ((chain = sk_X509_new_null()) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(chainfile, "r")) == NULL) {
+ fprintf(stderr, "error opening chainfile: %s: %m\n", chainfile);
+ exit(1);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (count = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++count) {
+ const unsigned char *p = data;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_TRUSTED) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ d2i_X509_t d;
+ X509 *cert;
+
+ d = strcmp(name, PEM_STRING_X509_TRUSTED) ? d2i_X509_AUX : d2i_X509;
+ if ((cert = d(0, &p, len)) == 0 || (p - data) != len)
+ errtype = "certificate";
+ else if (sk_X509_push(chain, cert) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "unexpected chain file object: %s\n", name);
+ exit(1);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s: malformed %s", chainfile, errtype);
+ exit(1);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ if (count > 0)
+ return chain;
+ fprintf(stderr, "no certificates found in: %s\n", chainfile);
+ exit(1);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s\n", chainfile);
+ exit(1);
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s certificate-usage selector matching-type"
+ " certfile \\\n\t\tCAfile chainfile hostname [certname ...]\n",
+ progname);
+ fprintf(stderr, " where, certificate-usage = TLSA certificate usage,\n");
+ fprintf(stderr, "\t selector = TLSA selector,\n");
+ fprintf(stderr, "\t matching-type = empty string or OpenSSL digest algorithm name,\n");
+ fprintf(stderr, "\t PEM certfile provides certificate association data,\n");
+ fprintf(stderr, "\t PEM CAfile contains any usage 0/1 trusted roots,\n");
+ fprintf(stderr, "\t PEM chainfile = server chain file to verify\n");
+ fprintf(stderr, "\t hostname = destination hostname,\n");
+ fprintf(stderr, "\t each certname augments the hostname for name checks.\n");
+ exit(1);
+}
+
+static SSL_CTX *ctx_init(const char *CAfile)
+{
+ SSL_CTX *client_ctx;
+
+ tls_param_init();
+ tls_check_version();
+
+ if (!tls_validate_digest(LN_sha1))
+ msg_fatal("%s digest algorithm not available", LN_sha1);
+
+ if (TLScontext_index < 0)
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0)
+ msg_fatal("Cannot allocate SSL application data index");
+
+ ERR_clear_error();
+ if ((client_ctx = SSL_CTX_new(TLS_client_method())) == 0)
+ msg_fatal("cannot allocate client SSL_CTX");
+ SSL_CTX_set_verify_depth(client_ctx, 5);
+
+ /* Enable DANE support in OpenSSL */
+ if (SSL_CTX_dane_enable(client_ctx) <= 0) {
+ tls_print_errors();
+ msg_fatal("OpenSSL DANE initialization failed");
+ }
+ if (tls_set_ca_certificate_info(client_ctx, CAfile, "") < 0) {
+ tls_print_errors();
+ msg_fatal("cannot load CAfile: %s", CAfile);
+ }
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+ return (client_ctx);
+}
+
+int main(int argc, char *argv[])
+{
+ SSL_CTX *ssl_ctx;
+ const EVP_MD *fpt_alg;
+ TLS_SESS_STATE *tctx;
+ x509_stack_t *chain;
+ int i;
+
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ msg_vstream_init(var_procname, VSTREAM_OUT);
+
+ if (argc < 8)
+ usage(argv[0]);
+
+ ssl_ctx = ctx_init(argv[5]);
+ if (!tls_dane_avail())
+ msg_fatal("DANE TLSA support not available");
+
+ tctx = tls_alloc_sess_context(TLS_LOG_NONE, argv[7]);
+ tctx->namaddr = argv[7];
+ tctx->mdalg = LN_sha256;
+ tctx->dane = tls_dane_alloc();
+
+ if ((fpt_alg = tls_validate_digest(tctx->mdalg)) == 0)
+ msg_fatal("fingerprint digest algorithm %s not found",
+ tctx->mdalg);
+ tls_dane_digest_init(ssl_ctx, fpt_alg);
+
+ if ((tctx->con = SSL_new(ssl_ctx)) == 0
+ || !SSL_set_ex_data(tctx->con, TLScontext_index, tctx)) {
+ tls_print_errors();
+ msg_fatal("Error allocating SSL connection");
+ }
+ if (SSL_dane_enable(tctx->con, 0) <= 0) {
+ tls_print_errors();
+ msg_fatal("Error enabling DANE for SSL handle");
+ }
+ SSL_dane_set_flags(tctx->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ SSL_dane_set_flags(tctx->con, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+ for (i = 7; i < argc; ++i)
+ if (!SSL_add1_host(tctx->con, argv[i]))
+ msg_fatal("error adding hostname: %s", argv[i]);
+ load_tlsa_args(tctx->con, argv);
+ SSL_set_connect_state(tctx->con);
+
+ /* Verify saved server chain */
+ chain = load_chain(argv[6]);
+ i = verify_chain(tctx->con, chain, tctx);
+ tls_print_errors();
+
+ if (i > 0) {
+ const char *peername = SSL_get0_peername(tctx->con);
+
+ if (peername == 0)
+ peername = argv[7];
+ msg_info("Verified %s", peername);
+ } else {
+ i = SSL_get_verify_result(tctx->con);
+ msg_info("certificate verification failed for %s:%s: num=%d:%s",
+ argv[6], argv[7], i, X509_verify_cert_error_string(i));
+ }
+
+ return (i <= 0);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.sh b/src/tls/tls_dane.sh
new file mode 100644
index 0000000..a950857
--- /dev/null
+++ b/src/tls/tls_dane.sh
@@ -0,0 +1,211 @@
+#! /bin/bash
+
+set -e
+
+DOMAIN=example.com
+HOST=mail.${DOMAIN}
+TEST=./tls_dane
+
+key() {
+ local key=$1; shift
+
+ if [ ! -f "${key}.pem" ]; then
+ openssl genpkey 2>/dev/null \
+ -paramfile <(openssl ecparam -name prime256v1) \
+ -out "${key}.pem"
+ fi
+}
+
+req() {
+ local key=$1; shift
+ local cn=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n%s\n[dn]\nCN=%s\n" \
+ "prompt = no" "distinguished_name = dn" "${cn}")
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -subj / -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ openssl x509 -req -sha256 -out "${cert}.pem" 2>/dev/null \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days 30
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genee() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:true" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -set_serial 1 -days 30 -signkey "${key}.pem" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ req_nocn "$key" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+runtest() {
+ local desc=$1; shift
+ local usage=$1; shift
+ local selector=$1; shift
+ local mtype=$1; shift
+ local tlsa=$1; shift
+ local ca=$1; shift
+ local chain=$1; shift
+ local digest
+
+ case $mtype in
+ 0) digest="";;
+ 1) digest=sha256;;
+ 2) digest=sha512;;
+ *) echo "bad mtype: $mtype"; exit 1;;
+ esac
+
+ printf "%d %d %d %-24s %s: " "$usage" "$selector" "$mtype" "$tlsa" "$desc"
+
+ if [ -n "$ca" ]; then ca="$ca.pem"; fi
+ "$TEST" "$usage" "$selector" "$digest" "$tlsa.pem" "$ca" "$chain.pem" \
+ "$@" > /dev/null
+}
+
+checkpass() { runtest "$@" && { echo pass; } || { echo fail; exit 1; }; }
+checkfail() { runtest "$@" && { echo fail; exit 1; } || { echo pass; }; }
+
+#---------
+
+genss "$HOST" sskey sscert
+gennocn akey acert
+
+# Tests that might depend on akid/skid chaining
+#
+for rakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for cakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for rskid in "" "subjectKeyIdentifier = hash"
+do
+for caskid in "" "subjectKeyIdentifier = hash"
+do
+
+genroot "Root CA" rootkey rootcert "$rskid" "$rakid"
+genca "CA 1" cakey1 cacert1 "$caskid" "$cakid" rootcert rootkey
+genca "CA 2" cakey2 cacert2 "$caskid" "$cakid" cacert1 cakey1
+genee "$HOST" eekey eecert cacert2 cakey2
+cat eecert.pem cacert2.pem cacert1.pem rootcert.pem > chain.pem
+cat eecert.pem cacert2.pem cacert1.pem > chain1.pem
+
+for s in 0 1
+do
+ checkpass "OOB root TA" 2 "$s" 0 rootcert "" chain1 "$HOST"
+ checkpass "OOB TA" 2 "$s" 0 cacert2 "" eecert "$HOST"
+ checkpass "in-chain root TA" 2 "$s" 1 rootcert "" chain "$HOST"
+
+ for m in 0 1 2
+ do
+ checkpass "valid TA" 2 "$s" "$m" rootcert "" chain "$HOST"
+ for ca in "cacert1" "cacert2"; do
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain "$HOST"
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain1 "$HOST"
+ checkpass "valid TA+CA" 2 "$s" "$m" "$ca" rootcert chain1 "$HOST"
+ checkpass "sub-domain" 2 "$s" "$m" "$ca" "" chain1 whatever ".$DOMAIN"
+ checkfail "wrong name" 2 "$s" "$m" "$ca" "" chain1 "whatever"
+ done
+ done
+done
+
+done
+done
+done
+done
+
+# These tests don't depend in the akid/skid chaining:
+#
+for s in 0 1
+do
+ checkfail "missing TA" 2 "$s" 1 rootcert "" chain1 "$HOST"
+ for m in 0 1 2
+ do
+ checkpass "depth 0 TA" 2 "$s" "$m" sscert "" sscert "$HOST"
+ checkfail "non-TA" 2 "$s" "$m" eecert rootcert chain "$HOST"
+ checkfail "depth 0 TA namecheck" 2 "$s" "$m" sscert sscert sscert whatever
+
+ checkpass "valid EE" 3 "$s" "$m" eecert "" chain whatever
+ checkpass "key-only EE" 3 "$s" "$m" acert "" acert whatever
+ checkfail "wrong EE" 3 "$s" "$m" cacert2 "" chain whatever
+ done
+done
+
+rm -f *.pem
diff --git a/src/tls/tls_dh.c b/src/tls/tls_dh.c
new file mode 100644
index 0000000..9a6bd67
--- /dev/null
+++ b/src/tls/tls_dh.c
@@ -0,0 +1,384 @@
+/*++
+/* NAME
+/* tls_dh
+/* SUMMARY
+/* Diffie-Hellman parameter support
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_set_dh_from_file(path)
+/* const char *path;
+/*
+/* void tls_auto_eecdh_curves(ctx, configured)
+/* SSL_CTX *ctx;
+/* char *configured;
+/*
+/* void tls_tmp_dh(ctx, useauto)
+/* SSL_CTX *ctx;
+/* int useauto;
+/* DESCRIPTION
+/* This module maintains parameters for Diffie-Hellman key generation.
+/*
+/* tls_tmp_dh() returns the configured or compiled-in FFDHE
+/* group parameters.
+/*
+/* tls_set_dh_from_file() overrides compiled-in DH parameters
+/* with those specified in the named files. The file format
+/* is as expected by the PEM_read_DHparams() routine.
+/*
+/* tls_auto_eecdh_curves() enables negotiation of the most preferred curve
+/* among the curves specified by the "configured" argument. The useauto
+/* argument enables OpenSSL-builtin group selection in preference to our
+/* own compiled-in group. This may interoperate better with overly strict
+/* peers that accept only "standard" groups (bogus threat model).
+/* DIAGNOSTICS
+/* In case of error, tls_set_dh_from_file() logs a warning and
+/* ignores the request.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_ECDH
+#include <openssl/ec.h>
+#endif
+#if OPENSSL_VERSION_PREREQ(3,0)
+#include <openssl/decoder.h>
+#endif
+
+/* Application-specific. */
+
+ /*
+ * Compiled-in FFDHE (finite-field ephemeral Diffie-Hellman) parameters.
+ * Used when no parameters are explicitly loaded from a site-specific file.
+ *
+ * With OpenSSL 3.0 and later when no explicit parameter file is specified by
+ * the administrator (or the setting is "auto"), we delegate group selection
+ * to OpenSSL via SSL_CTX_set_dh_auto(3).
+ *
+ * Using an ASN.1 DER encoding avoids the need to explicitly manipulate the
+ * internal representation of DH parameter objects.
+ *
+ * The FFDHE group is now 2048-bit, as 1024 bits is increasingly considered to
+ * weak by clients. When greater security is required, use EECDH.
+ */
+
+ /*-
+ * Generated via:
+ * $ openssl dhparam -2 -outform DER 2048 2>/dev/null |
+ * hexdump -ve '/1 "0x%02x, "' | fmt -73
+ * TODO: generate at compile-time. But that is no good for the majority of
+ * sites that install pre-compiled binaries, and breaks reproducible builds.
+ * Instead, generate at installation time and use main.cf configuration.
+ */
+static unsigned char builtin_der[] = {
+ 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xec, 0x02, 0x7b,
+ 0x74, 0xc6, 0xd4, 0xb4, 0x89, 0x68, 0xfd, 0xbc, 0xe0, 0x82, 0xae, 0xd6,
+ 0xf1, 0x4d, 0x93, 0xaa, 0x47, 0x07, 0x84, 0x3d, 0x86, 0xf8, 0x47, 0xf7,
+ 0xdf, 0x08, 0x7b, 0xca, 0x04, 0xa4, 0x72, 0xec, 0x11, 0xe2, 0x38, 0x43,
+ 0xb7, 0x94, 0xab, 0xaf, 0xe2, 0x85, 0x59, 0x43, 0x4e, 0x71, 0x85, 0xfe,
+ 0x52, 0x0c, 0xe0, 0x1c, 0xb6, 0xc7, 0xb0, 0x1b, 0x06, 0xb3, 0x4d, 0x1b,
+ 0x4f, 0xf6, 0x4b, 0x45, 0xbd, 0x1d, 0xb8, 0xe4, 0xa4, 0x48, 0x09, 0x28,
+ 0x19, 0xd7, 0xce, 0xb1, 0xe5, 0x9a, 0xc4, 0x94, 0x55, 0xde, 0x4d, 0x86,
+ 0x0f, 0x4c, 0x5e, 0x25, 0x51, 0x6c, 0x96, 0xca, 0xfa, 0xe3, 0x01, 0x69,
+ 0x82, 0x6c, 0x8f, 0xf5, 0xe7, 0x0e, 0xb7, 0x8e, 0x52, 0xf1, 0xcf, 0x0b,
+ 0x67, 0x10, 0xd0, 0xb3, 0x77, 0x79, 0xa4, 0xc1, 0xd0, 0x0f, 0x3f, 0xf5,
+ 0x5c, 0x35, 0xf9, 0x46, 0xd2, 0xc7, 0xfb, 0x97, 0x6d, 0xd5, 0xbe, 0xe4,
+ 0x8b, 0x5a, 0xf2, 0x88, 0xfa, 0x47, 0xdc, 0xc2, 0x4a, 0x4d, 0x69, 0xd3,
+ 0x2a, 0xdf, 0x55, 0x6c, 0x5f, 0x71, 0x11, 0x1e, 0x87, 0x03, 0x68, 0xe1,
+ 0xf4, 0x21, 0x06, 0x63, 0xd9, 0x65, 0xd4, 0x0c, 0x4d, 0xa7, 0x1f, 0x15,
+ 0x53, 0x3a, 0x50, 0x1a, 0xf5, 0x9b, 0x50, 0x35, 0xe0, 0x16, 0xa1, 0xd7,
+ 0xe6, 0xbf, 0xd7, 0xd9, 0xd9, 0x53, 0xe5, 0x8b, 0xf8, 0x7b, 0x45, 0x46,
+ 0xb6, 0xac, 0x50, 0x16, 0x46, 0x42, 0xca, 0x76, 0x38, 0x4b, 0x8e, 0x83,
+ 0xc6, 0x73, 0x13, 0x9c, 0x03, 0xd1, 0x7a, 0x3d, 0x8d, 0x99, 0x34, 0x10,
+ 0x79, 0x67, 0x21, 0x23, 0xf9, 0x6f, 0x48, 0x9a, 0xa6, 0xde, 0xbf, 0x7f,
+ 0x9c, 0x16, 0x53, 0xff, 0xf7, 0x20, 0x96, 0xeb, 0x34, 0xcb, 0x5b, 0x85,
+ 0x2b, 0x7c, 0x98, 0x00, 0x23, 0x47, 0xce, 0xc2, 0x58, 0x12, 0x86, 0x2c,
+ 0x57, 0x02, 0x01, 0x02,
+};
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+
+/* ------------------------------------- 3.0 API */
+
+static EVP_PKEY *dhp = 0;
+
+/* load_builtin - load compile-time FFDHE group */
+
+static void load_builtin(void)
+{
+ EVP_PKEY *tmp = 0;
+ OSSL_DECODER_CTX *d;
+ const unsigned char *endp = builtin_der;
+ size_t dlen = sizeof(builtin_der);
+
+ d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "DER", NULL, "DH",
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ NULL, NULL);
+ /* Check decode succeeds and consumes all data (final dlen == 0) */
+ if (d && OSSL_DECODER_from_data(d, &endp, &dlen) && tmp && !dlen) {
+ dhp = tmp;
+ } else {
+ EVP_PKEY_free(tmp);
+ msg_warn("error loading compiled-in DH parameters");
+ tls_print_errors();
+ }
+ OSSL_DECODER_CTX_free(d);
+}
+
+/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
+
+void tls_set_dh_from_file(const char *path)
+{
+ FILE *fp;
+ EVP_PKEY *tmp = 0;
+ OSSL_DECODER_CTX *d;
+
+ /*
+ * This function is the first to set the DH parameters, but free any
+ * prior value just in case the call sequence changes some day.
+ */
+ if (dhp) {
+ EVP_PKEY_free(dhp);
+ dhp = 0;
+ }
+ if (strcmp(path, "auto") == 0)
+ return;
+
+ if ((fp = fopen(path, "r")) == 0) {
+ msg_warn("error opening DH parameter file \"%s\": %m"
+ " -- using compiled-in defaults", path);
+ return;
+ }
+ d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "PEM", NULL, "DH",
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ NULL, NULL);
+ if (!d || !OSSL_DECODER_from_fp(d, fp) || !tmp) {
+ msg_warn("error decoding DH parameters from file \"%s\""
+ " -- using compiled-in defaults", path);
+ tls_print_errors();
+ } else {
+ dhp = tmp;
+ }
+ OSSL_DECODER_CTX_free(d);
+ (void) fclose(fp);
+}
+
+/* tls_tmp_dh - configure FFDHE group */
+
+void tls_tmp_dh(SSL_CTX *ctx, int useauto)
+{
+ if (!dhp && !useauto)
+ load_builtin();
+ if (!ctx)
+ return;
+ if (dhp) {
+ EVP_PKEY *tmp = EVP_PKEY_dup(dhp);
+
+ if (tmp && SSL_CTX_set0_tmp_dh_pkey(ctx, tmp) > 0)
+ return;
+ EVP_PKEY_free(tmp);
+ msg_warn("error configuring explicit DH parameters");
+ tls_print_errors();
+ } else {
+ if (SSL_CTX_set_dh_auto(ctx, 1) > 0)
+ return;
+ msg_warn("error configuring auto DH parameters");
+ tls_print_errors();
+ }
+}
+
+#else /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ------------------------------------- 1.1.1 API */
+
+static DH *dhp = 0;
+
+static void load_builtin(void)
+{
+ DH *tmp = 0;
+ const unsigned char *endp = builtin_der;
+
+ if (d2i_DHparams(&tmp, &endp, sizeof(builtin_der))
+ && sizeof(builtin_der) == endp - builtin_der) {
+ dhp = tmp;
+ } else {
+ DH_free(tmp);
+ msg_warn("error loading compiled-in DH parameters");
+ tls_print_errors();
+ }
+}
+
+/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
+
+void tls_set_dh_from_file(const char *path)
+{
+ FILE *fp;
+
+ /*
+ * This function is the first to set the DH parameters, but free any
+ * prior value just in case the call sequence changes some day.
+ */
+ if (dhp) {
+ DH_free(dhp);
+ dhp = 0;
+ }
+
+ /*
+ * Forwards compatibility, support "auto" by using the builtin group when
+ * OpenSSL is < 3.0 and does not support automatic FFDHE group selection.
+ */
+ if (strcmp(path, "auto") == 0)
+ return;
+
+ if ((fp = fopen(path, "r")) == 0) {
+ msg_warn("cannot load DH parameters from file %s: %m"
+ " -- using compiled-in defaults", path);
+ return;
+ }
+ if ((dhp = PEM_read_DHparams(fp, 0, 0, 0)) == 0) {
+ msg_warn("cannot load DH parameters from file %s"
+ " -- using compiled-in defaults", path);
+ tls_print_errors();
+ }
+ (void) fclose(fp);
+}
+
+/* tls_tmp_dh - configure FFDHE group */
+
+void tls_tmp_dh(SSL_CTX *ctx, int useauto)
+{
+ if (!dhp)
+ load_builtin();
+ if (!ctx || !dhp || SSL_CTX_set_tmp_dh(ctx, dhp) > 0)
+ return;
+ msg_warn("error configuring explicit DH parameters");
+ tls_print_errors();
+}
+
+#endif /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ------------------------------------- Common API */
+
+void tls_auto_eecdh_curves(SSL_CTX *ctx, const char *configured)
+{
+#ifndef OPENSSL_NO_ECDH
+ SSL_CTX *tmpctx;
+ int *nids;
+ int space = 5;
+ int n = 0;
+ int unknown = 0;
+ char *save;
+ char *curves;
+ char *curve;
+
+ if ((tmpctx = SSL_CTX_new(TLS_method())) == 0) {
+ msg_warn("cannot allocate temp SSL_CTX, using default ECDHE curves");
+ tls_print_errors();
+ return;
+ }
+ nids = mymalloc(space * sizeof(int));
+ curves = save = mystrdup(configured);
+#define RETURN do { \
+ myfree(save); \
+ myfree(nids); \
+ SSL_CTX_free(tmpctx); \
+ return; \
+ } while (0)
+
+ while ((curve = mystrtok(&curves, CHARS_COMMA_SP)) != 0) {
+ int nid = EC_curve_nist2nid(curve);
+
+ if (nid == NID_undef)
+ nid = OBJ_sn2nid(curve);
+ if (nid == NID_undef)
+ nid = OBJ_ln2nid(curve);
+ if (nid == NID_undef) {
+ msg_warn("ignoring unknown ECDHE curve \"%s\"",
+ curve);
+ continue;
+ }
+
+ /*
+ * Validate the NID by trying it as the sole EC curve for a
+ * throw-away SSL context. Silently skip unsupported code points.
+ * This way, we can list X25519 and X448 as soon as the nids are
+ * assigned, and before the supporting code is implemented. They'll
+ * be silently skipped when not yet supported.
+ */
+ if (SSL_CTX_set1_curves(tmpctx, &nid, 1) <= 0) {
+ ++unknown;
+ continue;
+ }
+ if (++n > space) {
+ space *= 2;
+ nids = myrealloc(nids, space * sizeof(int));
+ }
+ nids[n - 1] = nid;
+ }
+
+ if (n == 0) {
+ if (unknown > 0)
+ msg_warn("none of the configured ECDHE curves are supported");
+ RETURN;
+ }
+ if (SSL_CTX_set1_curves(ctx, nids, n) <= 0) {
+ msg_warn("failed to configure ECDHE curves");
+ tls_print_errors();
+ RETURN;
+ }
+ RETURN;
+#endif
+}
+
+#ifdef TEST
+
+int main(int unused_argc, char **unused_argv)
+{
+ tls_tmp_dh(0, 0);
+ return (dhp == 0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_fprint.c b/src/tls/tls_fprint.c
new file mode 100644
index 0000000..8021570
--- /dev/null
+++ b/src/tls/tls_fprint.c
@@ -0,0 +1,435 @@
+/*++
+/* NAME
+/* tls_fprint 3
+/* SUMMARY
+/* Digests fingerprints and all that.
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+/* const char *mdalg;
+/* EVP_MD_CTX **mdctxPtr;
+/*
+/* char *tls_serverid_digest(TLScontext, props, ciphers)
+/* TLS_SESS_STATE *TLScontext;
+/* const TLS_CLIENT_START_PROPS *props;
+/* const char *ciphers;
+/*
+/* char *tls_digest_encode(md_buf, md_len)
+/* const unsigned char *md_buf;
+/* const char *md_len;
+/*
+/* char *tls_cert_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/*
+/* char *tls_pkey_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/* DESCRIPTION
+/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
+/* handle for performing digest operations with the algorithm named by the
+/* mdalg parameter. The return value is non-null on success, and holds a
+/* digest algorithm handle. If the mdctxPtr argument is non-null the
+/* created context is returned to the caller, who is then responsible for
+/* deleting it by calling EVP_MD_ctx_free() once it is no longer needed.
+/*
+/* tls_digest_encode() converts a binary message digest to a hex ASCII
+/* format with ':' separators between each pair of hex digits.
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_cert_fprint() returns a fingerprint of the given
+/* certificate using the requested message digest, formatted
+/* with tls_digest_encode(). Panics if the
+/* (previously verified) digest algorithm is not found. The return
+/* value is dynamically allocated with mymalloc(), and the caller
+/* must eventually free it with myfree().
+/*
+/* tls_pkey_fprint() returns a public-key fingerprint; in all
+/* other respects the function behaves as tls_cert_fprint().
+/* The var_tls_bc_pkey_fprint variable enables an incorrect
+/* algorithm that was used in Postfix versions 2.9.[0-5].
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_serverid_digest() suffixes props->serverid computed by the SMTP
+/* client with "&" plus a digest of additional parameters needed to ensure
+/* that re-used sessions are more likely to be reused and that they will
+/* satisfy all protocol and security requirements. The return value is
+/* dynamically allocated with mymalloc(), and the caller must eventually
+/* free it with myfree().
+/*
+/* Arguments:
+/* .IP mdalg
+/* A digest algorithm name, such as "sha256".
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP md_buf
+/* The raw binary digest.
+/* .IP md_len
+/* The digest length in bytes.
+/* .IP mdalg
+/* Name of a message digest algorithm suitable for computing secure
+/* (1st pre-image resistant) message digests of certificates. For now,
+/* md5, sha1, or member of SHA-2 family if supported by OpenSSL.
+/* .IP mdctxPtr
+/* Pointer to an (EVP_MD_CTX *) handle, or NULL if only probing for
+/* algorithm support without immediate use in mind.
+/* .IP buf
+/* Input data for the message digest algorithm mdalg.
+/* .IP len
+/* The length of the input data.
+/* .IP props
+/* The client start properties for the session, which contains the
+/* initial serverid from the SMTP client and the DANE verification
+/* parameters.
+/* .IP protomask
+/* The mask of protocol exclusions.
+/* .IP ciphers
+/* The SSL client cipherlist.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+static const char hexcodes[] = "0123456789ABCDEF";
+
+#define checkok(stillok) (ok = ok && (stillok))
+#define digest_object(p) digest_data((unsigned char *)(p), sizeof(*(p)))
+#define digest_data(p, l) checkok(digest_bytes(mdctx, (p), (l)))
+#define digest_string(s) checkok(digest_chars(mdctx, (s)))
+#define digest_dane(tlsa) checkok(tls_digest_tlsa(mdctx, tlsa))
+
+/* digest_bytes - hash octet string of given length */
+
+static int digest_bytes(EVP_MD_CTX *ctx, const unsigned char *buf, size_t len)
+{
+ return (EVP_DigestUpdate(ctx, buf, len));
+}
+
+/* digest_chars - hash string including trailing NUL */
+
+static int digest_chars(EVP_MD_CTX *ctx, const char *s)
+{
+ return (EVP_DigestUpdate(ctx, s, strlen(s) + 1));
+}
+
+/* tlsa_cmp - compare TLSA RRs for sorting to canonical order */
+
+static int tlsa_cmp(const void *a, const void *b)
+{
+ TLS_TLSA *p = *(TLS_TLSA **) a;
+ TLS_TLSA *q = *(TLS_TLSA **) b;
+ int d;
+
+ if ((d = (int) p->usage - (int) q->usage) != 0)
+ return d;
+ if ((d = (int) p->selector - (int) q->selector) != 0)
+ return d;
+ if ((d = (int) p->mtype - (int) q->mtype) != 0)
+ return d;
+ if ((d = (int) p->length - (int) q->length) != 0)
+ return d;
+ return (memcmp(p->data, q->data, p->length));
+}
+
+/* tls_digest_tlsa - fold in digest of TLSA records */
+
+static int tls_digest_tlsa(EVP_MD_CTX *mdctx, TLS_TLSA *tlsa)
+{
+ TLS_TLSA *p;
+ TLS_TLSA **arr;
+ int ok = 1;
+ int n;
+ int i;
+
+ for (n = 0, p = tlsa; p != 0; p = p->next)
+ ++n;
+ arr = (TLS_TLSA **) mymalloc(n * sizeof(*arr));
+ for (i = 0, p = tlsa; p; p = p->next)
+ arr[i++] = (void *) p;
+ qsort(arr, n, sizeof(arr[0]), tlsa_cmp);
+
+ digest_object(&n);
+ for (i = 0; i < n; ++i) {
+ digest_object(&arr[i]->usage);
+ digest_object(&arr[i]->selector);
+ digest_object(&arr[i]->mtype);
+ digest_object(&arr[i]->length);
+ digest_data(arr[i]->data, arr[i]->length);
+ }
+ myfree((void *) arr);
+ return (ok);
+}
+
+/* tls_digest_byname - test availability or prepare to use digest */
+
+const EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+{
+ const EVP_MD *md;
+ EVP_MD_CTX *mdctx = NULL;
+ int ok = 1;
+
+ /*
+ * In OpenSSL 3.0, because of dynamically variable algorithm providers,
+ * there is a time-of-check/time-of-use issue that means that abstract
+ * algorithm handles returned by EVP_get_digestbyname() can (and not
+ * infrequently do) return ultimately unusable algorithms, to check for
+ * actual availability, one needs to use the new EVP_MD_fetch() API, or
+ * indirectly check usability by creating a concrete context. We take the
+ * latter approach here (works for 1.1.1 without #ifdef).
+ *
+ * Note that EVP_MD_CTX_{create,destroy} were renamed to, respectively,
+ * EVP_MD_CTX_{new,free} in OpenSSL 1.1.0.
+ */
+ checkok(md = EVP_get_digestbyname(mdalg));
+
+ /*
+ * Sanity check: Newer shared libraries could (hypothetical ABI break)
+ * allow larger digests, we avoid such poison algorithms.
+ */
+ checkok(EVP_MD_size(md) <= EVP_MAX_MD_SIZE);
+ checkok(mdctx = EVP_MD_CTX_new());
+ checkok(EVP_DigestInit_ex(mdctx, md, NULL));
+
+
+ if (ok && mdctxPtr != 0)
+ *mdctxPtr = mdctx;
+ else
+ EVP_MD_CTX_free(mdctx);
+ return (ok ? md : 0);
+}
+
+/* tls_serverid_digest - suffix props->serverid with parameter digest */
+
+char *tls_serverid_digest(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props,
+ const char *ciphers)
+{
+ EVP_MD_CTX *mdctx;
+ const char *mdalg;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+ int i;
+ long sslversion;
+ VSTRING *result;
+
+ /*
+ * Try to use sha256: our serverid choice should be strong enough to
+ * resist 2nd-preimage attacks with a difficulty comparable to that of
+ * DANE TLSA digests. Failing that, we compute serverid digests with the
+ * default digest, but DANE requires sha256 and sha512, so if we must
+ * fall back to our default digest, DANE support won't be available. We
+ * panic if the fallback algorithm is not available, as it was verified
+ * available in tls_client_init() and must not simply vanish. Our
+ * provider set is not expected to change once the OpenSSL library is
+ * initialized.
+ */
+ if (tls_digest_byname(mdalg = LN_sha256, &mdctx) == 0
+ && tls_digest_byname(mdalg = props->mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", props->mdalg);
+
+ /* Salt the session lookup key with the OpenSSL runtime version. */
+ sslversion = OpenSSL_version_num();
+
+ digest_string(props->helo ? props->helo : "");
+ digest_object(&sslversion);
+ digest_string(props->protocols);
+ digest_string(ciphers);
+
+ /*
+ * Ensure separation of caches for sessions where DANE trust
+ * configuration succeeded from those where it did not. The latter
+ * should always see a certificate validation failure, both on initial
+ * handshake and on resumption.
+ */
+ digest_object(&TLScontext->must_fail);
+
+ /*
+ * DNS-based or synthetic DANE trust settings are potentially used at all
+ * levels above "encrypt".
+ */
+ if (TLScontext->level > TLS_LEV_ENCRYPT
+ && props->dane && props->dane->tlsa) {
+ digest_dane(props->dane->tlsa);
+ } else {
+ int none = 0; /* Record a TLSA RR count of zero */
+
+ digest_object(&none);
+ }
+
+ /*
+ * Include the chosen SNI name, which can affect server certificate
+ * selection.
+ */
+ if (TLScontext->level > TLS_LEV_ENCRYPT && TLScontext->peer_sni)
+ digest_string(TLScontext->peer_sni);
+ else
+ digest_string("");
+
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ /* Check for OpenSSL contract violation */
+ if (md_len > EVP_MAX_MD_SIZE)
+ msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len);
+
+ /*
+ * Append the digest to the serverid. We don't compare this digest to
+ * any user-specified fingerprints. Therefore, we don't need to use a
+ * colon-separated format, which saves space in the TLS session cache and
+ * makes logging of session cache lookup keys more readable.
+ *
+ * This does however duplicate a few lines of code from the digest encoder
+ * for colon-separated cert and pkey fingerprints. If that is a
+ * compelling reason to consolidate, we could use that and append the
+ * result.
+ */
+ result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len);
+ vstring_strcpy(result, props->serverid);
+ VSTRING_ADDCH(result, '&');
+ for (i = 0; i < md_len; i++) {
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]);
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]);
+ }
+ VSTRING_TERMINATE(result);
+ return (vstring_export(result));
+}
+
+/* tls_digest_encode - encode message digest binary blob as xx:xx:... */
+
+char *tls_digest_encode(const unsigned char *md_buf, int md_len)
+{
+ int i;
+ char *result = mymalloc(md_len * 3);
+
+ /* Check for contract violation */
+ if (md_len > EVP_MAX_MD_SIZE || md_len >= INT_MAX / 3)
+ msg_panic("unexpectedly large message digest size: %u", md_len);
+
+ /* No risk of overruns, len is bounded by OpenSSL digest length */
+ for (i = 0; i < md_len; i++) {
+ result[i * 3] = hexcodes[(md_buf[i] & 0xf0) >> 4U];
+ result[(i * 3) + 1] = hexcodes[(md_buf[i] & 0x0f)];
+ result[(i * 3) + 2] = (i + 1 != md_len) ? ':' : '\0';
+ }
+ return (result);
+}
+
+/* tls_data_fprint - compute and encode digest of binary object */
+
+static char *tls_data_fprint(const unsigned char *buf, int len, const char *mdalg)
+{
+ EVP_MD_CTX *mdctx = NULL;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+
+ /* Previously available in "init" routine. */
+ if (tls_digest_byname(mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", mdalg);
+
+ digest_data(buf, len);
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ return (tls_digest_encode(md_buf, md_len));
+}
+
+/* tls_cert_fprint - extract certificate fingerprint */
+
+char *tls_cert_fprint(X509 *peercert, const char *mdalg)
+{
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_X509(peercert, NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509(peercert, &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509 invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+
+ return (result);
+}
+
+/* tls_pkey_fprint - extract public key fingerprint from certificate */
+
+char *tls_pkey_fprint(X509 *peercert, const char *mdalg)
+{
+ if (var_tls_bc_pkey_fprint) {
+ const char *myname = "tls_pkey_fprint";
+ ASN1_BIT_STRING *key;
+ char *result;
+
+ key = X509_get0_pubkey_bitstr(peercert);
+ if (key == 0)
+ msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
+ myname);
+
+ result = tls_data_fprint(key->data, key->length, mdalg);
+ return (result);
+ } else {
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509_PUBKEY invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+ return (result);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_level.c b/src/tls/tls_level.c
new file mode 100644
index 0000000..eec15fd
--- /dev/null
+++ b/src/tls/tls_level.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* tls_level 3
+/* SUMMARY
+/* TLS security level conversion
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* int tls_level_lookup(name)
+/* const char *name;
+/*
+/* const char *str_tls_level(level)
+/* int level;
+/* DESCRIPTION
+/* The functions in this module convert TLS levels from symbolic
+/* name to internal form and vice versa.
+/*
+/* tls_level_lookup() converts a TLS level from symbolic name
+/* to internal form. When an unknown level is specified,
+/* tls_level_lookup() logs no warning, and returns TLS_LEV_INVALID.
+/*
+/* str_tls_level() converts a TLS level from internal form to
+/* symbolic name. The result is a null pointer for an unknown
+/* level. The "halfdane" level is not a valid user-selected TLS level,
+/* it is generated internally and is only valid output for the
+/* str_tls_level() function.
+/* SEE ALSO
+/* name_code(3) name to number mapping
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <name_code.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+ /*
+ * Numerical order of levels is critical (see tls.h):
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS is required.
+ *
+ * - With "fingerprint" and higher, the peer certificate must match.
+ *
+ * - With "dane" and higher, the peer certificate must also be trusted,
+ * possibly via TLSA RRs that make it its own authority.
+ *
+ * The smtp(8) client will report trust failure in preference to reporting
+ * failure to match, so we make "dane" larger than "fingerprint".
+ */
+static const NAME_CODE tls_level_table[] = {
+ "none", TLS_LEV_NONE,
+ "may", TLS_LEV_MAY,
+ "encrypt", TLS_LEV_ENCRYPT,
+ "fingerprint", TLS_LEV_FPRINT,
+ "halfdane", TLS_LEV_HALF_DANE, /* output only */
+ "dane", TLS_LEV_DANE,
+ "dane-only", TLS_LEV_DANE_ONLY,
+ "verify", TLS_LEV_VERIFY,
+ "secure", TLS_LEV_SECURE,
+ 0, TLS_LEV_INVALID,
+};
+
+int tls_level_lookup(const char *name)
+{
+ int level = name_code(tls_level_table, NAME_CODE_FLAG_NONE, name);
+
+ return ((level != TLS_LEV_HALF_DANE) ? level : TLS_LEV_INVALID);
+}
+
+const char *str_tls_level(int level)
+{
+ return (str_name_code(tls_level_table, level));
+}
diff --git a/src/tls/tls_mgr.c b/src/tls/tls_mgr.c
new file mode 100644
index 0000000..b69ddf6
--- /dev/null
+++ b/src/tls/tls_mgr.c
@@ -0,0 +1,486 @@
+/*++
+/* NAME
+/* tls_mgr 3
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/*
+/* int tls_mgr_seed(buf, len)
+/* VSTRING *buf;
+/* int len;
+/*
+/* int tls_mgr_policy(cache_type, cachable, timeout)
+/* const char *cache_type;
+/* int *cachable;
+/* int *timeout;
+/*
+/* int tls_mgr_update(cache_type, cache_id, buf, len)
+/* const char *cache_type;
+/* const char *cache_id;
+/* const char *buf;
+/* ssize_t len;
+/*
+/* int tls_mgr_lookup(cache_type, cache_id, buf)
+/* const char *cache_type;
+/* const char *cache_id;
+/* VSTRING *buf;
+/*
+/* int tls_mgr_delete(cache_type, cache_id)
+/* const char *cache_type;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_mgr_key(keyname, timeout)
+/* unsigned char *keyname;
+/* int timeout;
+/* DESCRIPTION
+/* These routines communicate with the tlsmgr(8) server for
+/* entropy and session cache management. Since these are
+/* non-critical services, requests are allowed to fail without
+/* disrupting Postfix.
+/*
+/* tls_mgr_seed() requests entropy from the tlsmgr(8)
+/* Pseudo Random Number Generator (PRNG) pool.
+/*
+/* tls_mgr_policy() requests the session caching policy.
+/*
+/* tls_mgr_lookup() loads the specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_update() saves the specified session to
+/* the specified session cache.
+/*
+/* tls_mgr_delete() removes specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_key() is used to retrieve the current TLS session ticket
+/* encryption or decryption keys.
+/*
+/* Arguments:
+/* .IP cache_type
+/* One of TLS_MGR_SCACHE_SMTPD, TLS_MGR_SCACHE_SMTP or
+/* TLS_MGR_SCACHE_LMTP.
+/* .IP cachable
+/* Pointer to int, set non-zero if the requested cache_type
+/* is enabled.
+/* .IP timeout
+/* Pointer to int, returns the cache entry timeout.
+/* .IP cache_id
+/* The session cache lookup key.
+/* .IP buf
+/* The result or input buffer.
+/* .IP len
+/* The length of the input buffer, or the amount of data requested.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket. When encrypting
+/* a null result indicates that session tickets are not supported, when
+/* decrypting it indicates that no matching keys were found.
+/* .IP timeout
+/* The encryption key timeout. Once a key has been active for this many
+/* seconds it is retired and used only for decrypting previously issued
+/* session tickets for another timeout seconds, and is then destroyed.
+/* The timeout must not be longer than half the SSL session lifetime.
+/* DIAGNOSTICS
+/* All client functions return one of the following status codes:
+/* .IP TLS_MGR_STAT_OK
+/* The request completed, and the requested operation was
+/* successful (for example, the requested session was found,
+/* or the specified session was saved or removed).
+/* .IP TLS_MGR_STAT_ERR
+/* The request completed, but the requested operation failed
+/* (for example, the requested object was not found or the
+/* specified session was not saved or removed).
+/* .IP TLS_MGR_STAT_FAIL
+/* The request could not complete (the client could not
+/* communicate with the tlsmgr(8) server).
+/* SEE ALSO
+/* tlsmgr(8) TLS session and PRNG management
+/* 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>
+
+#ifdef USE_TLS
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <attr.h>
+#include <attr_clnt.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* TLS library. */
+#include <tls_mgr.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+static ATTR_CLNT *tls_mgr;
+
+/* tls_mgr_handshake - receive server protocol announcement */
+
+static int tls_mgr_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR),
+ ATTR_TYPE_END));
+}
+
+/* tls_mgr_open - create client handle */
+
+static void tls_mgr_open(void)
+{
+ char *service;
+
+ /*
+ * Sanity check.
+ */
+ if (tls_mgr != 0)
+ msg_panic("tls_mgr_open: multiple initialization");
+
+ /*
+ * Use whatever IPC is preferred for internal use: UNIX-domain sockets or
+ * Solaris streams.
+ */
+ service = concatenate("local:" TLS_MGR_CLASS "/", var_tls_mgr_service,
+ (char *) 0);
+ tls_mgr = attr_clnt_create(service, var_ipc_timeout,
+ var_ipc_idle_limit, var_ipc_ttl_limit);
+ myfree(service);
+
+ attr_clnt_control(tls_mgr,
+ ATTR_CLNT_CTL_PROTO, attr_vprint, attr_vscan,
+ ATTR_CLNT_CTL_HANDSHAKE, tls_mgr_handshake,
+ ATTR_CLNT_CTL_END);
+}
+
+/* tls_mgr_seed - request PRNG seed */
+
+int tls_mgr_seed(VSTRING *buf, int len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request seed.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_SEED),
+ SEND_ATTR_INT(TLS_MGR_ATTR_SIZE, len),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SEED, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_policy - request caching policy */
+
+int tls_mgr_policy(const char *cache_type, int *cachable, int *timeout)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request policy.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_POLICY),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
+ RECV_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
+ ATTR_TYPE_END) != 3)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_lookup - request cached session */
+
+int tls_mgr_lookup(const char *cache_type, const char *cache_id,
+ VSTRING *buf)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_LOOKUP),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_update - save session to cache */
+
+int tls_mgr_update(const char *cache_type, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_UPDATE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, len, buf),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_delete - remove cached session */
+
+int tls_mgr_delete(const char *cache_type, const char *cache_id)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_DELETE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* request_scache_key - ask tlsmgr(8) for matching key */
+
+static TLS_TICKET_KEY *request_scache_key(unsigned char *keyname)
+{
+ TLS_TICKET_KEY tmp;
+ static VSTRING *keybuf;
+ char *name;
+ size_t len;
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ if (keybuf == 0)
+ keybuf = vstring_alloc(sizeof(tmp));
+
+ /* In tlsmgr requests we encode null key names as empty strings. */
+ name = keyname ? (char *) keyname : "";
+ len = keyname ? TLS_TICKET_NAMELEN : 0;
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_TKTKEY),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, len, name),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, keybuf),
+ ATTR_TYPE_END) != 2
+ || status != TLS_MGR_STAT_OK
+ || LEN(keybuf) != sizeof(tmp))
+ return (0);
+
+ memcpy((void *) &tmp, STR(keybuf), sizeof(tmp));
+ return (tls_scache_key_rotate(&tmp));
+}
+
+/* tls_mgr_key - session ticket key lookup, local cache, then tlsmgr(8) */
+
+TLS_TICKET_KEY *tls_mgr_key(unsigned char *keyname, int timeout)
+{
+ TLS_TICKET_KEY *key = 0;
+ time_t now = time((time_t *) 0);
+
+ /* A zero timeout disables session tickets. */
+ if (timeout <= 0)
+ return (0);
+
+ if ((key = tls_scache_key(keyname, now, timeout)) == 0)
+ key = request_scache_key(keyname);
+ return (key);
+}
+
+#ifdef TEST
+
+/* System library. */
+
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <hex_code.h>
+
+/* Global library. */
+
+#include <config.h>
+
+/* Application-specific. */
+
+int main(int unused_ac, char **av)
+{
+ VSTRING *inbuf = vstring_alloc(10);
+ int status;
+ ARGV *argv = 0;
+
+ msg_vstream_init(av[0], VSTREAM_ERR);
+
+ msg_verbose = 3;
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ argv = argv_split(STR(inbuf), CHARS_SPACE);
+ if (argv->argc == 0) {
+ argv_free(argv);
+ continue;
+ }
+#define COMMAND(argv, str, len) \
+ (strcasecmp(argv->argv[0], str) == 0 && argv->argc == len)
+
+ if (COMMAND(argv, "policy", 2)) {
+ int cachable;
+ int timeout;
+
+ status = tls_mgr_policy(argv->argv[1], &cachable, &timeout);
+ vstream_printf("status=%d cachable=%d timeout=%d\n",
+ status, cachable, timeout);
+ } else if (COMMAND(argv, "seed", 2)) {
+ VSTRING *buf = vstring_alloc(10);
+ VSTRING *hex = vstring_alloc(10);
+ int len = atoi(argv->argv[1]);
+
+ status = tls_mgr_seed(buf, len);
+ hex_encode(hex, STR(buf), LEN(buf));
+ vstream_printf("status=%d seed=%s\n", status, STR(hex));
+ vstring_free(hex);
+ vstring_free(buf);
+ } else if (COMMAND(argv, "lookup", 3)) {
+ VSTRING *buf = vstring_alloc(10);
+
+ status = tls_mgr_lookup(argv->argv[1], argv->argv[2], buf);
+ vstream_printf("status=%d session=%.*s\n",
+ status, (int) LEN(buf), STR(buf));
+ vstring_free(buf);
+ } else if (COMMAND(argv, "update", 4)) {
+ status = tls_mgr_update(argv->argv[1], argv->argv[2],
+ argv->argv[3], strlen(argv->argv[3]));
+ vstream_printf("status=%d\n", status);
+ } else if (COMMAND(argv, "delete", 3)) {
+ status = tls_mgr_delete(argv->argv[1], argv->argv[2]);
+ vstream_printf("status=%d\n", status);
+ } else {
+ vstream_printf("usage:\n"
+ "seed byte_count\n"
+ "policy smtpd|smtp|lmtp\n"
+ "lookup smtpd|smtp|lmtp cache_id\n"
+ "update smtpd|smtp|lmtp cache_id session\n"
+ "delete smtpd|smtp|lmtp cache_id\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(argv);
+ }
+
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_mgr.h b/src/tls/tls_mgr.h
new file mode 100644
index 0000000..c584175
--- /dev/null
+++ b/src/tls/tls_mgr.h
@@ -0,0 +1,71 @@
+#ifndef _TLS_MGR_CLNT_H_INCLUDED_
+#define _TLS_MGR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_mgr 3h
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * TLS library
+ */
+#include <tls_scache.h> /* Session ticket keys */
+
+ /*
+ * TLS manager protocol.
+ */
+#define TLS_MGR_SERVICE "tlsmgr"
+#define TLS_MGR_CLASS "private"
+
+#define TLS_MGR_ATTR_REQ "request"
+#define TLS_MGR_REQ_SEED "seed"
+#define TLS_MGR_REQ_POLICY "policy"
+#define TLS_MGR_REQ_LOOKUP "lookup"
+#define TLS_MGR_REQ_UPDATE "update"
+#define TLS_MGR_REQ_DELETE "delete"
+#define TLS_MGR_REQ_TKTKEY "tktkey"
+#define TLS_MGR_ATTR_CACHABLE "cachable"
+#define TLS_MGR_ATTR_CACHE_TYPE "cache_type"
+#define TLS_MGR_ATTR_SEED "seed"
+#define TLS_MGR_ATTR_CACHE_ID "cache_id"
+#define TLS_MGR_ATTR_SESSION "session"
+#define TLS_MGR_ATTR_SIZE "size"
+#define TLS_MGR_ATTR_STATUS "status"
+#define TLS_MGR_ATTR_KEYNAME "keyname"
+#define TLS_MGR_ATTR_KEYBUF "keybuf"
+#define TLS_MGR_ATTR_SESSTOUT "timeout"
+
+ /*
+ * TLS manager request status codes.
+ */
+#define TLS_MGR_STAT_OK 0 /* success */
+#define TLS_MGR_STAT_ERR (-1) /* object not found */
+#define TLS_MGR_STAT_FAIL (-2) /* protocol error */
+
+ /*
+ * Functional interface.
+ */
+extern int tls_mgr_seed(VSTRING *, int);
+extern int tls_mgr_policy(const char *, int *, int *);
+extern int tls_mgr_lookup(const char *, const char *, VSTRING *);
+extern int tls_mgr_update(const char *, const char *, const char *, ssize_t);
+extern int tls_mgr_delete(const char *, const char *);
+extern TLS_TICKET_KEY *tls_mgr_key(unsigned char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_misc.c b/src/tls/tls_misc.c
new file mode 100644
index 0000000..00ce10f
--- /dev/null
+++ b/src/tls/tls_misc.c
@@ -0,0 +1,1712 @@
+/*++
+/* NAME
+/* tls_misc 3
+/* SUMMARY
+/* miscellaneous TLS support routines
+/* SYNOPSIS
+/* .SH Public functions
+/* .nf
+/* .na
+/* #include <tls.h>
+/*
+/* void tls_log_summary(role, usage, TLScontext)
+/* TLS_ROLE role;
+/* TLS_USAGE usage;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* const char *tls_compile_version(void)
+/*
+/* const char *tls_run_version(void)
+/*
+/* const char **tls_pkey_algorithms(void)
+/*
+/* void tls_pre_jail_init(TLS_ROLE)
+/* TLS_ROLE role;
+/*
+/* .SH Internal functions
+/* .nf
+/* .na
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* char *var_tls_cnf_file;
+/* char *var_tls_cnf_name;
+/* char *var_tls_high_clist;
+/* char *var_tls_medium_clist;
+/* char *var_tls_low_clist;
+/* char *var_tls_export_clist;
+/* char *var_tls_null_clist;
+/* char *var_tls_eecdh_auto;
+/* char *var_tls_eecdh_strong;
+/* char *var_tls_eecdh_ultra;
+/* char *var_tls_dane_digests;
+/* int var_tls_daemon_rand_bytes;
+/* bool var_tls_append_def_CA;
+/* bool var_tls_preempt_clist;
+/* bool var_tls_bc_pkey_fprint;
+/* bool var_tls_multi_wildcard;
+/* char *var_tls_mgr_service;
+/* char *var_tls_tkt_cipher;
+/* char *var_openssl_path;
+/* char *var_tls_server_sni_maps;
+/* bool var_tls_fast_shutdown;
+/*
+/* TLS_APPL_STATE *tls_alloc_app_context(ssl_ctx, log_mask)
+/* SSL_CTX *ssl_ctx;
+/* int log_mask;
+/*
+/* void tls_free_app_context(app_ctx)
+/* void *app_ctx;
+/*
+/* TLS_SESS_STATE *tls_alloc_sess_context(log_mask, namaddr)
+/* int log_mask;
+/* const char *namaddr;
+/*
+/* void tls_free_context(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_check_version()
+/*
+/* long tls_bug_bits()
+/*
+/* void tls_param_init()
+/*
+/* int tls_library_init(void)
+/*
+/* int tls_proto_mask_lims(plist, floor, ceiling)
+/* const char *plist;
+/* int *floor;
+/* int *ceiling;
+/*
+/* int tls_cipher_grade(name)
+/* const char *name;
+/*
+/* const char *str_tls_cipher_grade(grade)
+/* int grade;
+/*
+/* const char *tls_set_ciphers(TLScontext, grade, exclusions)
+/* TLS_SESS_STATE *TLScontext;
+/* int grade;
+/* const char *exclusions;
+/*
+/* void tls_get_signature_params(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_print_errors()
+/*
+/* void tls_info_callback(ssl, where, ret)
+/* const SSL *ssl; /* unused */
+/* int where;
+/* int ret;
+/*
+/* long tls_bio_dump_cb(bio, cmd, argp, len, argi, argl, ret, processed)
+/* BIO *bio;
+/* int cmd;
+/* const char *argp;
+/* size_t len;
+/* int argi;
+/* long argl; /* unused */
+/* int ret;
+/* size_t *processed;
+/*
+/* int tls_log_mask(log_param, log_level)
+/* const char *log_param;
+/* const char *log_level;
+/*
+/* void tls_update_app_logmask(app_ctx, log_mask)
+/* TLS_APPL_STATE *app_ctx;
+/* int log_mask;
+/*
+/* const EVP_MD *tls_validate_digest(dgst)
+/* const char *dgst;
+/* DESCRIPTION
+/* This module implements public and internal routines that
+/* support the TLS client and server.
+/*
+/* tls_log_summary() logs a summary of a completed TLS connection.
+/* The "role" argument must be TLS_ROLE_CLIENT for outgoing client
+/* connections, or TLS_ROLE_SERVER for incoming server connections,
+/* and the "usage" must be TLS_USAGE_NEW or TLS_USAGE_USED.
+/*
+/* tls_compile_version() returns a text string description of
+/* the compile-time TLS library.
+/*
+/* tls_run_version() is just tls_compile_version() but with the runtime
+/* version instead of the compile-time version.
+/*
+/* tls_pkey_algorithms() returns a pointer to null-terminated
+/* array of string constants with the names of the supported
+/* public-key algorithms.
+/*
+/* tls_alloc_app_context() creates an application context that
+/* holds the SSL context for the application and related cached state.
+/*
+/* tls_free_app_context() deallocates the application context and its
+/* contents (the application context is stored outside the TLS library).
+/*
+/* tls_alloc_sess_context() creates an initialized TLS session context
+/* structure with the specified log mask and peer name[addr].
+/*
+/* tls_free_context() destroys a TLScontext structure
+/* together with OpenSSL structures that are attached to it.
+/*
+/* tls_check_version() logs a warning when the run-time OpenSSL
+/* library differs in its major, minor or micro number from
+/* the compile-time OpenSSL headers.
+/*
+/* tls_bug_bits() returns the bug compatibility mask appropriate
+/* for the run-time library. Some of the bug work-arounds are
+/* not appropriate for some library versions.
+/*
+/* tls_param_init() loads main.cf parameters used internally in
+/* TLS library. Any errors are fatal.
+/*
+/* tls_library_init() initializes the OpenSSL library, optionally
+/* loading an OpenSSL configuration file.
+/*
+/* tls_pre_jail_init() opens any tables that need to be opened before
+/* entering a chroot jail. The "role" parameter must be TLS_ROLE_CLIENT
+/* for clients and TLS_ROLE_SERVER for servers. Any errors are fatal.
+/*
+/* tls_proto_mask_lims() returns a bitmask of excluded protocols, and
+/* and the protocol version floor/ceiling, given a list (plist) of
+/* protocols to include or (preceded by a '!') exclude, or constraints
+/* of the form '>=name', '<=name', '>=hexvalue', '<=hexvalue'. If "plist"
+/* contains invalid protocol names, TLS_PROTOCOL_INVALID is returned and
+/* no warning is logged.
+/*
+/* tls_cipher_grade() converts a case-insensitive cipher grade
+/* name (high, medium, low, export, null) to the corresponding
+/* TLS_CIPHER_ constant. When the input specifies an unrecognized
+/* grade, tls_cipher_grade() logs no warning, and returns
+/* TLS_CIPHER_NONE.
+/*
+/* str_tls_cipher_grade() converts a cipher grade to a name.
+/* When the input specifies an undefined grade, str_tls_cipher_grade()
+/* logs no warning, returns a null pointer.
+/*
+/* tls_set_ciphers() applies the requested cipher grade and exclusions
+/* to the provided TLS session context, returning the resulting cipher
+/* list string. The return value is the cipherlist used and is
+/* overwritten upon each call. When the input is invalid,
+/* tls_set_ciphers() logs a warning, and returns a null result.
+/*
+/* tls_get_signature_params() updates the "TLScontext" with handshake
+/* signature parameters pertaining to TLS 1.3, where the ciphersuite
+/* no longer describes the asymmetric algorithms employed in the
+/* handshake, which are negotiated separately. This function
+/* has no effect for TLS 1.2 and earlier.
+/*
+/* tls_print_errors() queries the OpenSSL error stack,
+/* logs the error messages, and clears the error stack.
+/*
+/* tls_info_callback() is a call-back routine for the
+/* SSL_CTX_set_info_callback() routine. It logs SSL events
+/* to the Postfix logfile.
+/*
+/* tls_bio_dump_cb() is a call-back routine for the
+/* BIO_set_callback() routine. It logs SSL content to the
+/* Postfix logfile.
+/*
+/* tls_log_mask() converts a TLS log_level value from string
+/* to mask. The main.cf parameter name is passed along for
+/* diagnostics.
+/*
+/* tls_update_app_logmask() changes the log mask of the
+/* application TLS context to the new setting.
+/*
+/* tls_validate_digest() returns a static handle for the named
+/* digest algorithm, or NULL on error.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <dict.h>
+#include <valid_hostname.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <maps.h>
+
+ /*
+ * TLS library.
+ */
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+char *var_tls_cnf_file;
+char *var_tls_cnf_name;
+char *var_tls_high_clist;
+char *var_tls_medium_clist;
+char *var_tls_low_clist;
+char *var_tls_export_clist;
+char *var_tls_null_clist;
+int var_tls_daemon_rand_bytes;
+char *var_tls_eecdh_auto;
+char *var_tls_eecdh_strong;
+char *var_tls_eecdh_ultra;
+char *var_tls_dane_digests;
+bool var_tls_append_def_CA;
+char *var_tls_bug_tweaks;
+char *var_tls_ssl_options;
+bool var_tls_bc_pkey_fprint;
+bool var_tls_multi_wildcard;
+char *var_tls_mgr_service;
+char *var_tls_tkt_cipher;
+char *var_openssl_path;
+char *var_tls_server_sni_maps;
+bool var_tls_fast_shutdown;
+bool var_tls_preempt_clist;
+
+#ifdef USE_TLS
+
+static MAPS *tls_server_sni_maps;
+
+ /*
+ * Index to attach TLScontext pointers to SSL objects, so that they can be
+ * accessed by call-back routines.
+ */
+int TLScontext_index = -1;
+
+ /*
+ * Protocol name <=> mask conversion.
+ */
+static const NAME_CODE protocol_table[] = {
+ SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2,
+ SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3,
+ SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1,
+ SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1,
+ SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2,
+ TLS_PROTOCOL_TXT_TLSV1_3, TLS_PROTOCOL_TLSv1_3,
+ 0, TLS_PROTOCOL_INVALID,
+};
+
+/*
+ * Protocol name => numeric version, for MinProtocol and MaxProtocol
+ */
+static const NAME_CODE tls_version_table[] = {
+ "None", 0,
+ SSL_TXT_SSLV3, SSL3_VERSION,
+ SSL_TXT_TLSV1, TLS1_VERSION,
+ SSL_TXT_TLSV1_1, TLS1_1_VERSION,
+ SSL_TXT_TLSV1_2, TLS1_2_VERSION,
+ TLS_PROTOCOL_TXT_TLSV1_3, TLS1_3_VERSION,
+ 0, -1,
+};
+
+ /*
+ * SSL_OP_MUMBLE bug work-around name <=> mask conversion.
+ */
+#define NAMEBUG(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_bug_tweaks[] = {
+
+#ifndef SSL_OP_MICROSOFT_SESS_ID_BUG
+#define SSL_OP_MICROSOFT_SESS_ID_BUG 0
+#endif
+ NAMEBUG(MICROSOFT_SESS_ID_BUG),
+
+#ifndef SSL_OP_NETSCAPE_CHALLENGE_BUG
+#define SSL_OP_NETSCAPE_CHALLENGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_CHALLENGE_BUG),
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAMEBUG(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+#define SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_REUSE_CIPHER_CHANGE_BUG),
+ "CVE-2010-4180", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG,
+
+#ifndef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+#define SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0
+#endif
+ NAMEBUG(SSLREF2_REUSE_CERT_TYPE_BUG),
+
+#ifndef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+#define SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0
+#endif
+ NAMEBUG(MICROSOFT_BIG_SSLV3_BUFFER),
+
+#ifndef SSL_OP_MSIE_SSLV2_RSA_PADDING
+#define SSL_OP_MSIE_SSLV2_RSA_PADDING 0
+#endif
+ NAMEBUG(MSIE_SSLV2_RSA_PADDING),
+ "CVE-2005-2969", SSL_OP_MSIE_SSLV2_RSA_PADDING,
+
+#ifndef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+#define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0
+#endif
+ NAMEBUG(SSLEAY_080_CLIENT_DH_BUG),
+
+#ifndef SSL_OP_TLS_D5_BUG
+#define SSL_OP_TLS_D5_BUG 0
+#endif
+ NAMEBUG(TLS_D5_BUG),
+
+#ifndef SSL_OP_TLS_BLOCK_PADDING_BUG
+#define SSL_OP_TLS_BLOCK_PADDING_BUG 0
+#endif
+ NAMEBUG(TLS_BLOCK_PADDING_BUG),
+
+#ifndef SSL_OP_TLS_ROLLBACK_BUG
+#define SSL_OP_TLS_ROLLBACK_BUG 0
+#endif
+ NAMEBUG(TLS_ROLLBACK_BUG),
+
+#ifndef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+#define SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0
+#endif
+ NAMEBUG(DONT_INSERT_EMPTY_FRAGMENTS),
+
+#ifndef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+#define SSL_OP_CRYPTOPRO_TLSEXT_BUG 0
+#endif
+ NAMEBUG(CRYPTOPRO_TLSEXT_BUG),
+
+#ifndef SSL_OP_TLSEXT_PADDING
+#define SSL_OP_TLSEXT_PADDING 0
+#endif
+ NAMEBUG(TLSEXT_PADDING),
+
+#if 0
+
+ /*
+ * XXX: New with OpenSSL 1.1.1, this is turned on implicitly in
+ * SSL_CTX_new() and is not included in SSL_OP_ALL. Allowing users to
+ * disable this would thus be a code change that would require clearing
+ * bug work-around bits in SSL_CTX, after setting SSL_OP_ALL. Since this
+ * is presumably required for TLS 1.3 on today's Internet, the code
+ * change will be done separately later. For now this implicit bug
+ * work-around cannot be disabled via supported Postfix mechanisms.
+ */
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAMEBUG(ENABLE_MIDDLEBOX_COMPAT),
+#endif
+
+ 0, 0,
+};
+
+ /*
+ * SSL_OP_MUMBLE option name <=> mask conversion for options that are not
+ * (or may in the future not be) in SSL_OP_ALL. These enable optional
+ * behavior, rather than bug interoperability work-arounds.
+ */
+#define NAME_SSL_OP(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_op_tweaks[] = {
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAME_SSL_OP(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NO_TICKET
+#define SSL_OP_NO_TICKET 0
+#endif
+ NAME_SSL_OP(NO_TICKET),
+
+#ifndef SSL_OP_NO_COMPRESSION
+#define SSL_OP_NO_COMPRESSION 0
+#endif
+ NAME_SSL_OP(NO_COMPRESSION),
+
+#ifndef SSL_OP_NO_RENEGOTIATION
+#define SSL_OP_NO_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_RENEGOTIATION),
+
+#ifndef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+#define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_SESSION_RESUMPTION_ON_RENEGOTIATION),
+
+#ifndef SSL_OP_PRIORITIZE_CHACHA
+#define SSL_OP_PRIORITIZE_CHACHA 0
+#endif
+ NAME_SSL_OP(PRIORITIZE_CHACHA),
+
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAME_SSL_OP(ENABLE_MIDDLEBOX_COMPAT),
+
+ 0, 0,
+};
+
+ /*
+ * Once these have been a NOOP long enough, they might some day be removed
+ * from OpenSSL. The defines below will avoid bitrot issues if/when that
+ * happens.
+ */
+#ifndef SSL_OP_SINGLE_DH_USE
+#define SSL_OP_SINGLE_DH_USE 0
+#endif
+#ifndef SSL_OP_SINGLE_ECDH_USE
+#define SSL_OP_SINGLE_ECDH_USE 0
+#endif
+
+ /*
+ * Ciphersuite name <=> code conversion.
+ */
+const NAME_CODE tls_cipher_grade_table[] = {
+ "high", TLS_CIPHER_HIGH,
+ "medium", TLS_CIPHER_MEDIUM,
+ "low", TLS_CIPHER_LOW,
+ "export", TLS_CIPHER_EXPORT,
+ "null", TLS_CIPHER_NULL,
+ "invalid", TLS_CIPHER_NONE,
+ 0, TLS_CIPHER_NONE,
+};
+
+ /*
+ * Log keyword <=> mask conversion.
+ */
+#define TLS_LOG_0 TLS_LOG_NONE
+#define TLS_LOG_1 TLS_LOG_SUMMARY
+#define TLS_LOG_2 (TLS_LOG_1 | TLS_LOG_VERBOSE | TLS_LOG_CACHE | TLS_LOG_DEBUG)
+#define TLS_LOG_3 (TLS_LOG_2 | TLS_LOG_TLSPKTS)
+#define TLS_LOG_4 (TLS_LOG_3 | TLS_LOG_ALLPKTS)
+
+static const NAME_MASK tls_log_table[] = {
+ "0", TLS_LOG_0,
+ "none", TLS_LOG_NONE,
+ "1", TLS_LOG_1,
+ "routine", TLS_LOG_1,
+ "2", TLS_LOG_2,
+ "debug", TLS_LOG_2,
+ "3", TLS_LOG_3,
+ "ssl-expert", TLS_LOG_3,
+ "4", TLS_LOG_4,
+ "ssl-developer", TLS_LOG_4,
+ "5", TLS_LOG_4, /* for good measure */
+ "6", TLS_LOG_4, /* for good measure */
+ "7", TLS_LOG_4, /* for good measure */
+ "8", TLS_LOG_4, /* for good measure */
+ "9", TLS_LOG_4, /* for good measure */
+ "summary", TLS_LOG_SUMMARY,
+ "untrusted", TLS_LOG_UNTRUSTED,
+ "peercert", TLS_LOG_PEERCERT,
+ "certmatch", TLS_LOG_CERTMATCH,
+ "verbose", TLS_LOG_VERBOSE, /* Postfix TLS library verbose */
+ "cache", TLS_LOG_CACHE,
+ "dane", TLS_LOG_DANE, /* DANE policy construction */
+ "ssl-debug", TLS_LOG_DEBUG, /* SSL library debug/verbose */
+ "ssl-handshake-packet-dump", TLS_LOG_TLSPKTS,
+ "ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS,
+ 0, 0,
+};
+
+ /*
+ * Parsed OpenSSL version number.
+ */
+typedef struct {
+ int major;
+ int minor;
+ int micro;
+ int patch;
+ int status;
+} TLS_VINFO;
+
+/* tls_log_mask - Convert user TLS loglevel to internal log feature mask */
+
+int tls_log_mask(const char *log_param, const char *log_level)
+{
+ int mask;
+
+ mask = name_mask_opt(log_param, tls_log_table, log_level,
+ NAME_MASK_ANY_CASE | NAME_MASK_RETURN);
+ return (mask);
+}
+
+/* tls_update_app_logmask - update log level after init */
+
+void tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask)
+{
+ app_ctx->log_mask = log_mask;
+}
+
+/* parse_version - parse TLS protocol version name or hex number */
+
+static int parse_tls_version(const char *tok, int *version)
+{
+ int code = name_code(tls_version_table, NAME_CODE_FLAG_NONE, tok);
+ char *_end;
+ unsigned long ulval;
+
+ if (code != -1) {
+ *version = code;
+ return (0);
+ }
+ errno = 0;
+ ulval = strtoul(tok, &_end, 16);
+ if (*_end != 0
+ || (ulval == ULONG_MAX && errno == ERANGE)
+ || ulval > INT_MAX)
+ return TLS_PROTOCOL_INVALID;
+
+ *version = (int) ulval;
+ return (0);
+}
+
+/* tls_proto_mask_lims - protocols to exclude and floor/ceiling */
+
+int tls_proto_mask_lims(const char *plist, int *floor, int *ceiling)
+{
+ char *save;
+ char *tok;
+ char *cp;
+ int code;
+ int exclude = 0;
+ int include = 0;
+
+#define FREE_AND_RETURN(ptr, res) do { \
+ myfree(ptr); \
+ return (res); \
+ } while (0)
+
+ *floor = *ceiling = 0;
+
+ save = cp = mystrdup(plist);
+ while ((tok = mystrtok(&cp, CHARS_COMMA_SP ":")) != 0) {
+ if (strncmp(tok, ">=", 2) == 0)
+ code = parse_tls_version(tok + 2, floor);
+ else if (strncmp(tok, "<=", 2) == 0)
+ code = parse_tls_version(tok + 2, ceiling);
+ else if (*tok == '!')
+ exclude |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok);
+ else
+ include |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, tok);
+ if (code == TLS_PROTOCOL_INVALID)
+ FREE_AND_RETURN(save, TLS_PROTOCOL_INVALID);
+ }
+
+ /*
+ * When the include list is empty, use only the explicit exclusions.
+ * Otherwise, also exclude the complement of the include list from the
+ * built-in list of known protocols. There is no way to exclude protocols
+ * we don't know about at compile time, and this is unavoidable because
+ * the OpenSSL API works with compile-time *exclusion* bit-masks.
+ */
+ FREE_AND_RETURN(save,
+ (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude));
+}
+
+/* tls_param_init - Load TLS related config parameters */
+
+void tls_param_init(void)
+{
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_CNF_FILE, DEF_TLS_CNF_FILE, &var_tls_cnf_file, 0, 0,
+ VAR_TLS_CNF_NAME, DEF_TLS_CNF_NAME, &var_tls_cnf_name, 0, 0,
+ VAR_TLS_HIGH_CLIST, DEF_TLS_HIGH_CLIST, &var_tls_high_clist, 1, 0,
+ VAR_TLS_MEDIUM_CLIST, DEF_TLS_MEDIUM_CLIST, &var_tls_medium_clist, 1, 0,
+ VAR_TLS_LOW_CLIST, DEF_TLS_LOW_CLIST, &var_tls_low_clist, 1, 0,
+ VAR_TLS_EXPORT_CLIST, DEF_TLS_EXPORT_CLIST, &var_tls_export_clist, 1, 0,
+ VAR_TLS_NULL_CLIST, DEF_TLS_NULL_CLIST, &var_tls_null_clist, 1, 0,
+ VAR_TLS_EECDH_AUTO, DEF_TLS_EECDH_AUTO, &var_tls_eecdh_auto, 1, 0,
+ VAR_TLS_EECDH_STRONG, DEF_TLS_EECDH_STRONG, &var_tls_eecdh_strong, 1, 0,
+ VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0,
+ VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0,
+ VAR_TLS_SSL_OPTIONS, DEF_TLS_SSL_OPTIONS, &var_tls_ssl_options, 0, 0,
+ VAR_TLS_DANE_DIGESTS, DEF_TLS_DANE_DIGESTS, &var_tls_dane_digests, 1, 0,
+ VAR_TLS_MGR_SERVICE, DEF_TLS_MGR_SERVICE, &var_tls_mgr_service, 1, 0,
+ VAR_TLS_TKT_CIPHER, DEF_TLS_TKT_CIPHER, &var_tls_tkt_cipher, 0, 0,
+ VAR_OPENSSL_PATH, DEF_OPENSSL_PATH, &var_openssl_path, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_TLS_APPEND_DEF_CA, DEF_TLS_APPEND_DEF_CA, &var_tls_append_def_CA,
+ VAR_TLS_BC_PKEY_FPRINT, DEF_TLS_BC_PKEY_FPRINT, &var_tls_bc_pkey_fprint,
+ VAR_TLS_PREEMPT_CLIST, DEF_TLS_PREEMPT_CLIST, &var_tls_preempt_clist,
+ VAR_TLS_MULTI_WILDCARD, DEF_TLS_MULTI_WILDCARD, &var_tls_multi_wildcard,
+ VAR_TLS_FAST_SHUTDOWN, DEF_TLS_FAST_SHUTDOWN, &var_tls_fast_shutdown,
+ 0,
+ };
+ static int init_done;
+
+ if (init_done)
+ return;
+ init_done = 1;
+
+ get_mail_conf_str_table(str_table);
+ get_mail_conf_int_table(int_table);
+ get_mail_conf_bool_table(bool_table);
+}
+
+/* tls_library_init - perform OpenSSL library initialization */
+
+int tls_library_init(void)
+{
+ OPENSSL_INIT_SETTINGS *init_settings;
+ char *conf_name = *var_tls_cnf_name ? var_tls_cnf_name : 0;
+ char *conf_file = 0;
+ unsigned long init_opts = 0;
+
+#define TLS_LIB_INIT_TODO (-1)
+#define TLS_LIB_INIT_ERR (0)
+#define TLS_LIB_INIT_OK (1)
+
+ static int init_res = TLS_LIB_INIT_TODO;
+
+ if (init_res != TLS_LIB_INIT_TODO)
+ return (init_res);
+
+ /*
+ * Backwards compatibility: skip this function unless the Postfix
+ * configuration actually has non-default tls_config_xxx settings.
+ */
+ if (strcmp(var_tls_cnf_file, DEF_TLS_CNF_FILE) == 0
+ && strcmp(var_tls_cnf_name, DEF_TLS_CNF_NAME) == 0) {
+ if (msg_verbose)
+ msg_info("tls_library_init: using backwards-compatible defaults");
+ return (init_res = TLS_LIB_INIT_OK);
+ }
+ if ((init_settings = OPENSSL_INIT_new()) == 0) {
+ msg_warn("error allocating OpenSSL init settings, "
+ "disabling TLS support");
+ return (init_res = TLS_LIB_INIT_ERR);
+ }
+#define TLS_LIB_INIT_RETURN(x) \
+ do { OPENSSL_INIT_free(init_settings); return (init_res = (x)); } while(0)
+
+#if OPENSSL_VERSION_NUMBER < 0x1010102fL
+
+ /*
+ * OpenSSL 1.1.0 through 1.1.1a, no support for custom configuration
+ * files, disabling loading of the file, or getting strict error
+ * handling. Thus, the only supported configuration file is "default".
+ */
+ if (strcmp(var_tls_cnf_file, "default") != 0) {
+ msg_warn("non-default %s = %s requires OpenSSL 1.1.1b or later, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+#else
+ {
+ unsigned long file_flags = 0;
+
+ /*-
+ * OpenSSL 1.1.1b or later:
+ * We can now use a non-default configuration file, or
+ * use none at all. We can also request strict error
+ * reporting.
+ */
+ if (strcmp(var_tls_cnf_file, "none") == 0) {
+ init_opts |= OPENSSL_INIT_NO_LOAD_CONFIG;
+ } else if (strcmp(var_tls_cnf_file, "default") == 0) {
+
+ /*
+ * The default global config file is optional. With "default"
+ * initialisation we don't insist on a match for the requested
+ * application name, allowing fallback to the default application
+ * name, even when a non-default application name is specified.
+ * Errors in loading the default configuration are ignored.
+ */
+ conf_file = 0;
+ file_flags |= CONF_MFLAGS_IGNORE_MISSING_FILE;
+ file_flags |= CONF_MFLAGS_DEFAULT_SECTION;
+ file_flags |= CONF_MFLAGS_IGNORE_RETURN_CODES | CONF_MFLAGS_SILENT;
+ } else if (*var_tls_cnf_file == '/') {
+
+ /*
+ * A custom config file must be present, error reporting is
+ * strict and the configuration section for the requested
+ * application name does not fall back to "openssl_conf" when
+ * missing.
+ */
+ conf_file = var_tls_cnf_file;
+ } else {
+ msg_warn("non-default %s = %s is not an absolute pathname, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+
+ OPENSSL_INIT_set_config_file_flags(init_settings, file_flags);
+ }
+#endif
+
+ if (conf_file)
+ OPENSSL_INIT_set_config_filename(init_settings, conf_file);
+ if (conf_name)
+ OPENSSL_INIT_set_config_appname(init_settings, conf_name);
+
+ if (OPENSSL_init_ssl(init_opts, init_settings) <= 0) {
+ if ((init_opts & OPENSSL_INIT_NO_LOAD_CONFIG) == 0)
+ msg_warn("error loading the '%s' settings from the %s OpenSSL "
+ "configuration file, disabling TLS support",
+ conf_name ? conf_name : "global",
+ conf_file ? conf_file : "default");
+ else
+ msg_warn("error initializing the OpenSSL library, "
+ "disabling TLS support");
+ tls_print_errors();
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_OK);
+}
+
+/* tls_pre_jail_init - Load TLS related pre-jail tables */
+
+void tls_pre_jail_init(TLS_ROLE role)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_SERVER_SNI_MAPS, DEF_TLS_SERVER_SNI_MAPS, &var_tls_server_sni_maps, 0, 0,
+ 0,
+ };
+ int flags;
+
+ tls_param_init();
+
+ /* Nothing for clients at this time */
+ if (role != TLS_ROLE_SERVER)
+ return;
+
+ get_mail_conf_str_table(str_table);
+ if (*var_tls_server_sni_maps == 0)
+ return;
+
+ flags = DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX | DICT_FLAG_SRC_RHS_IS_FILE;
+ tls_server_sni_maps =
+ maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags);
+}
+
+/* server_sni_callback - process client's SNI extension */
+
+static int server_sni_callback(SSL *ssl, int *alert, void *arg)
+{
+ SSL_CTX *sni_ctx = (SSL_CTX *) arg;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
+ const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ const char *cp = sni;
+ const char *pem;
+
+ /* SNI is silently ignored when we don't care or is NULL or empty */
+ if (!sni_ctx || !tls_server_sni_maps || !sni || !*sni)
+ return SSL_TLSEXT_ERR_NOACK;
+
+ if (!valid_hostname(sni, DONT_GRIPE)) {
+ msg_warn("TLS SNI from %s is invalid: %s",
+ TLScontext->namaddr, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /*
+ * With TLS 1.3, when the client's proposed key share is not supported by
+ * the server, the server may issue a HelloRetryRequest (HRR), and the
+ * client will then retry with a new key share on a curve supported by
+ * the server. This results in the SNI callback running twice for the
+ * same connection.
+ *
+ * When that happens, The client MUST send the essentially the same hello
+ * message, including the SNI name, and since we've already loaded our
+ * certificate chain, we don't need to do it again! Therefore, if we've
+ * already recorded the peer SNI name, just check that it has not
+ * changed, and return success.
+ */
+ if (TLScontext->peer_sni) {
+ if (strcmp(sni, TLScontext->peer_sni) == 0)
+ return SSL_TLSEXT_ERR_OK;
+ msg_warn("TLS SNI changed from %s initially %s, %s after hello retry",
+ TLScontext->namaddr, TLScontext->peer_sni, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ do {
+ /* Don't silently skip maps opened with the wrong flags. */
+ pem = maps_file_find(tls_server_sni_maps, cp, 0);
+ } while (!pem
+ && !tls_server_sni_maps->error
+ && (cp = strchr(cp + 1, '.')) != 0);
+
+ if (!pem) {
+ if (tls_server_sni_maps->error) {
+ msg_warn("%s: %s map lookup problem",
+ tls_server_sni_maps->title, sni);
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ msg_info("TLS SNI %s from %s not matched, using default chain",
+ sni, TLScontext->namaddr);
+
+ /*
+ * XXX: We could lie and pretend to accept the name, but since we've
+ * previously not implemented the callback (with OpenSSL then
+ * declining the extension), and nothing bad happened, declining it
+ * explicitly should be safe.
+ */
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ SSL_set_SSL_CTX(ssl, sni_ctx);
+ if (tls_load_pem_chain(ssl, pem, sni) != 0) {
+ /* errors already logged */
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ TLScontext->peer_sni = mystrdup(sni);
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/* tls_set_ciphers - Set SSL context cipher list */
+
+const char *tls_set_ciphers(TLS_SESS_STATE *TLScontext, const char *grade,
+ const char *exclusions)
+{
+ const char *myname = "tls_set_ciphers";
+ static VSTRING *buf;
+ char *save;
+ char *cp;
+ char *tok;
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ VSTRING_RESET(buf);
+
+ switch (tls_cipher_grade(grade)) {
+ case TLS_CIPHER_NONE:
+ msg_warn("%s: invalid cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ return (0);
+ case TLS_CIPHER_HIGH:
+ vstring_strcpy(buf, var_tls_high_clist);
+ break;
+ case TLS_CIPHER_MEDIUM:
+ vstring_strcpy(buf, var_tls_medium_clist);
+ break;
+ case TLS_CIPHER_LOW:
+ vstring_strcpy(buf, var_tls_low_clist);
+ break;
+ case TLS_CIPHER_EXPORT:
+ vstring_strcpy(buf, var_tls_export_clist);
+ break;
+ case TLS_CIPHER_NULL:
+ vstring_strcpy(buf, var_tls_null_clist);
+ break;
+ default:
+ /* Internal error, valid grade, but missing case label. */
+ msg_panic("%s: unexpected cipher grade: %s", myname, grade);
+ }
+
+ /*
+ * The base lists for each grade can't be empty.
+ */
+ if (VSTRING_LEN(buf) == 0)
+ msg_panic("%s: empty \"%s\" cipherlist", myname, grade);
+
+ /*
+ * Apply locally-specified exclusions.
+ */
+#define CIPHER_SEP CHARS_COMMA_SP ":"
+ if (exclusions != 0) {
+ cp = save = mystrdup(exclusions);
+ while ((tok = mystrtok(&cp, CIPHER_SEP)) != 0) {
+
+ /*
+ * Can't exclude ciphers that start with modifiers.
+ */
+ if (strchr("!+-@", *tok)) {
+ msg_warn("%s: invalid unary '!+-@' in cipher exclusion: %s",
+ TLScontext->namaddr, tok);
+ return (0);
+ }
+ vstring_sprintf_append(buf, ":!%s", tok);
+ }
+ myfree(save);
+ }
+ ERR_clear_error();
+ if (SSL_set_cipher_list(TLScontext->con, vstring_str(buf)) == 0) {
+ msg_warn("%s: error setting cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ tls_print_errors();
+ return (0);
+ }
+ return (vstring_str(buf));
+}
+
+/* ec_curve_name - copy EC key curve group name */
+
+#ifndef OPENSSL_NO_EC
+static char *ec_curve_name(EVP_PKEY *pkey)
+{
+ char *curve = 0;
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+ size_t namelen;
+
+ if (EVP_PKEY_get_group_name(pkey, 0, 0, &namelen)) {
+ curve = mymalloc(++namelen);
+ if (!EVP_PKEY_get_group_name(pkey, curve, namelen, 0)) {
+ myfree(curve);
+ curve = 0;
+ }
+ }
+#else
+ EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey);
+ int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+ const char *tmp = EC_curve_nid2nist(nid);
+
+ if (!tmp)
+ tmp = OBJ_nid2sn(nid);
+ if (tmp)
+ curve = mystrdup(tmp);
+#endif
+ return (curve);
+}
+
+#endif
+
+/* tls_get_signature_params - TLS 1.3 signature details */
+
+void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
+{
+ const char *kex_name = 0;
+ const char *locl_sig_name = 0;
+ const char *locl_sig_dgst = 0;
+ const char *peer_sig_name = 0;
+ const char *peer_sig_dgst = 0;
+ char *kex_curve = 0;
+ char *locl_sig_curve = 0;
+ char *peer_sig_curve = 0;
+ int nid;
+ SSL *ssl = TLScontext->con;
+ int srvr = SSL_is_server(ssl);
+ EVP_PKEY *dh_pkey = 0;
+ X509 *local_cert;
+ EVP_PKEY *local_pkey = 0;
+ X509 *peer_cert;
+ EVP_PKEY *peer_pkey = 0;
+
+#define SIG_PROP(c, s, p) (*((s) ? &c->srvr_sig_##p : &c->clnt_sig_##p))
+
+ if (SSL_version(ssl) < TLS1_3_VERSION)
+ return;
+
+ if (tls_get_peer_dh_pubkey(ssl, &dh_pkey)) {
+ switch (nid = EVP_PKEY_id(dh_pkey)) {
+ default:
+ kex_name = OBJ_nid2sn(EVP_PKEY_type(nid));
+ break;
+
+ case EVP_PKEY_DH:
+ kex_name = "DHE";
+ TLScontext->kex_bits = EVP_PKEY_bits(dh_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ kex_name = "ECDHE";
+ kex_curve = ec_curve_name(dh_pkey);
+ break;
+#endif
+ }
+ EVP_PKEY_free(dh_pkey);
+ }
+
+ /*
+ * On the client end, the certificate may be preset, but not used, so we
+ * check via SSL_get_signature_nid(). This means that local signature
+ * data on clients requires at least 1.1.1a.
+ */
+ if (srvr || SSL_get_signature_nid(ssl, &nid))
+ local_cert = SSL_get_certificate(ssl);
+ else
+ local_cert = 0;
+
+ /* Signature algorithms for the local end of the connection */
+ if (local_cert) {
+ local_pkey = X509_get0_pubkey(local_cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(local_pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ locl_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ locl_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, srvr, bits) = EVP_PKEY_bits(local_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ locl_sig_name = "ECDSA";
+ locl_sig_curve = ec_curve_name(local_pkey);
+ break;
+#endif
+ }
+ /* No X509_free(local_cert) */
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef)
+ locl_sig_dgst = OBJ_nid2sn(nid);
+ }
+ /* Signature algorithms for the peer end of the connection */
+ if ((peer_cert = TLS_PEEK_PEER_CERT(ssl)) != 0) {
+ peer_pkey = X509_get0_pubkey(peer_cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(peer_pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ peer_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ peer_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, !srvr, bits) = EVP_PKEY_bits(peer_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ peer_sig_name = "ECDSA";
+ peer_sig_curve = ec_curve_name(peer_pkey);
+ break;
+#endif
+ }
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef)
+ peer_sig_dgst = OBJ_nid2sn(nid);
+
+ TLS_FREE_PEER_CERT(peer_cert);
+ }
+ if (kex_name) {
+ TLScontext->kex_name = mystrdup(kex_name);
+ TLScontext->kex_curve = kex_curve;
+ }
+ if (locl_sig_name) {
+ SIG_PROP(TLScontext, srvr, name) = mystrdup(locl_sig_name);
+ SIG_PROP(TLScontext, srvr, curve) = locl_sig_curve;
+ if (locl_sig_dgst)
+ SIG_PROP(TLScontext, srvr, dgst) = mystrdup(locl_sig_dgst);
+ }
+ if (peer_sig_name) {
+ SIG_PROP(TLScontext, !srvr, name) = mystrdup(peer_sig_name);
+ SIG_PROP(TLScontext, !srvr, curve) = peer_sig_curve;
+ if (peer_sig_dgst)
+ SIG_PROP(TLScontext, !srvr, dgst) = mystrdup(peer_sig_dgst);
+ }
+}
+
+/* tls_log_summary - TLS loglevel 1 one-liner, embellished with TLS 1.3 details */
+
+void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
+{
+ VSTRING *msg = vstring_alloc(100);
+ const char *direction = (role == TLS_ROLE_CLIENT) ? "to" : "from";
+ const char *sni = (role == TLS_ROLE_CLIENT) ? 0 : ctx->peer_sni;
+
+ /*
+ * When SNI was sent and accepted, the server-side log message now
+ * includes a "to <sni-name>" detail after the "from <namaddr>" detail
+ * identifying the remote client. We don't presently log (purportedly)
+ * accepted SNI on the client side.
+ */
+ vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s"
+ " with cipher %s (%d/%d bits)",
+ !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" :
+ TLS_CERT_IS_SECURED(ctx) ? "Verified" :
+ TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted",
+ usage == TLS_USAGE_NEW ? "established" : "reused",
+ direction, ctx->namaddr, sni ? " to " : "", sni ? sni : "",
+ ctx->protocol, ctx->cipher_name, ctx->cipher_usebits,
+ ctx->cipher_algbits);
+
+ if (ctx->kex_name && *ctx->kex_name) {
+ vstring_sprintf_append(msg, " key-exchange %s", ctx->kex_name);
+ if (ctx->kex_curve && *ctx->kex_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->kex_curve);
+ else if (ctx->kex_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->kex_bits);
+ }
+ if (ctx->srvr_sig_name && *ctx->srvr_sig_name) {
+ vstring_sprintf_append(msg, " server-signature %s",
+ ctx->srvr_sig_name);
+ if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve);
+ else if (ctx->srvr_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits);
+ if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst)
+ vstring_sprintf_append(msg, " server-digest %s",
+ ctx->srvr_sig_dgst);
+ }
+ if (ctx->clnt_sig_name && *ctx->clnt_sig_name) {
+ vstring_sprintf_append(msg, " client-signature %s",
+ ctx->clnt_sig_name);
+ if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve);
+ else if (ctx->clnt_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits);
+ if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst)
+ vstring_sprintf_append(msg, " client-digest %s",
+ ctx->clnt_sig_dgst);
+ }
+ msg_info("%s", vstring_str(msg));
+ vstring_free(msg);
+}
+
+/* tls_alloc_app_context - allocate TLS application context */
+
+TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx,
+ int log_mask)
+{
+ TLS_APPL_STATE *app_ctx;
+
+ app_ctx = (TLS_APPL_STATE *) mymalloc(sizeof(*app_ctx));
+
+ /* See portability note below with other memset() call. */
+ memset((void *) app_ctx, 0, sizeof(*app_ctx));
+ app_ctx->ssl_ctx = ssl_ctx;
+ app_ctx->sni_ctx = sni_ctx;
+ app_ctx->log_mask = log_mask;
+
+ /* See also: cache purging code in tls_set_ciphers(). */
+ app_ctx->cache_type = 0;
+
+ if (tls_server_sni_maps) {
+ SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback);
+ SSL_CTX_set_tlsext_servername_arg(ssl_ctx, (void *) sni_ctx);
+ }
+ return (app_ctx);
+}
+
+/* tls_free_app_context - Free TLS application context */
+
+void tls_free_app_context(TLS_APPL_STATE *app_ctx)
+{
+ if (app_ctx->ssl_ctx)
+ SSL_CTX_free(app_ctx->ssl_ctx);
+ if (app_ctx->sni_ctx)
+ SSL_CTX_free(app_ctx->sni_ctx);
+ if (app_ctx->cache_type)
+ myfree(app_ctx->cache_type);
+ myfree((void *) app_ctx);
+}
+
+/* tls_alloc_sess_context - allocate TLS session context */
+
+TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
+{
+ TLS_SESS_STATE *TLScontext;
+
+ /*
+ * PORTABILITY: Do not assume that null pointers are all-zero bits. Use
+ * explicit assignments to initialize pointers.
+ *
+ * See the C language FAQ item 5.17, or if you have time to burn,
+ * http://www.google.com/search?q=zero+bit+null+pointer
+ *
+ * However, it's OK to use memset() to zero integer values.
+ */
+ TLScontext = (TLS_SESS_STATE *) mymalloc(sizeof(TLS_SESS_STATE));
+ memset((void *) TLScontext, 0, sizeof(*TLScontext));
+ TLScontext->con = 0;
+ TLScontext->cache_type = 0;
+ TLScontext->serverid = 0;
+ TLScontext->peer_CN = 0;
+ TLScontext->issuer_CN = 0;
+ TLScontext->peer_sni = 0;
+ TLScontext->peer_cert_fprint = 0;
+ TLScontext->peer_pkey_fprint = 0;
+ TLScontext->protocol = 0;
+ TLScontext->cipher_name = 0;
+ TLScontext->kex_name = 0;
+ TLScontext->kex_curve = 0;
+ TLScontext->clnt_sig_name = 0;
+ TLScontext->clnt_sig_curve = 0;
+ TLScontext->clnt_sig_dgst = 0;
+ TLScontext->srvr_sig_name = 0;
+ TLScontext->srvr_sig_curve = 0;
+ TLScontext->srvr_sig_dgst = 0;
+ TLScontext->log_mask = log_mask;
+ TLScontext->namaddr = lowercase(mystrdup(namaddr));
+ TLScontext->mdalg = 0; /* Alias for props->mdalg */
+ TLScontext->dane = 0; /* Alias for props->dane */
+ TLScontext->errordepth = -1;
+ TLScontext->errorcode = X509_V_OK;
+ TLScontext->errorcert = 0;
+
+ return (TLScontext);
+}
+
+/* tls_free_context - deallocate TLScontext and members */
+
+void tls_free_context(TLS_SESS_STATE *TLScontext)
+{
+
+ /*
+ * Free the SSL structure and the BIOs. Warning: the internal_bio is
+ * connected to the SSL structure and is automatically freed with it. Do
+ * not free it again (core dump)!! Only free the network_bio.
+ */
+ if (TLScontext->con != 0)
+ SSL_free(TLScontext->con);
+
+ if (TLScontext->namaddr)
+ myfree(TLScontext->namaddr);
+ if (TLScontext->serverid)
+ myfree(TLScontext->serverid);
+
+ if (TLScontext->peer_CN)
+ myfree(TLScontext->peer_CN);
+ if (TLScontext->issuer_CN)
+ myfree(TLScontext->issuer_CN);
+ if (TLScontext->peer_sni)
+ myfree(TLScontext->peer_sni);
+ if (TLScontext->peer_cert_fprint)
+ myfree(TLScontext->peer_cert_fprint);
+ if (TLScontext->peer_pkey_fprint)
+ myfree(TLScontext->peer_pkey_fprint);
+ if (TLScontext->kex_name)
+ myfree((void *) TLScontext->kex_name);
+ if (TLScontext->kex_curve)
+ myfree((void *) TLScontext->kex_curve);
+ if (TLScontext->clnt_sig_name)
+ myfree((void *) TLScontext->clnt_sig_name);
+ if (TLScontext->clnt_sig_curve)
+ myfree((void *) TLScontext->clnt_sig_curve);
+ if (TLScontext->clnt_sig_dgst)
+ myfree((void *) TLScontext->clnt_sig_dgst);
+ if (TLScontext->srvr_sig_name)
+ myfree((void *) TLScontext->srvr_sig_name);
+ if (TLScontext->srvr_sig_curve)
+ myfree((void *) TLScontext->srvr_sig_curve);
+ if (TLScontext->srvr_sig_dgst)
+ myfree((void *) TLScontext->srvr_sig_dgst);
+ if (TLScontext->errorcert)
+ X509_free(TLScontext->errorcert);
+
+ myfree((void *) TLScontext);
+}
+
+/* tls_version_split - Split OpenSSL version number into major, minor, ... */
+
+static void tls_version_split(unsigned long version, TLS_VINFO *info)
+{
+
+ /*
+ * OPENSSL_VERSION_NUMBER(3):
+ *
+ * OPENSSL_VERSION_NUMBER is a numeric release version identifier:
+ *
+ * MMNNFFPPS: major minor fix patch status
+ *
+ * The status nibble has one of the values 0 for development, 1 to e for
+ * betas 1 to 14, and f for release. Parsed OpenSSL version number. for
+ * example: 0x1010103f == 1.1.1c.
+ */
+ info->status = version & 0xf;
+ version >>= 4;
+ info->patch = version & 0xff;
+ version >>= 8;
+ info->micro = version & 0xff;
+ version >>= 8;
+ info->minor = version & 0xff;
+ version >>= 8;
+ info->major = version & 0xff;
+}
+
+/* tls_check_version - Detect mismatch between headers and library. */
+
+void tls_check_version(void)
+{
+ TLS_VINFO hdr_info;
+ TLS_VINFO lib_info;
+
+ tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info);
+ tls_version_split(OpenSSL_version_num(), &lib_info);
+
+ /*
+ * Warn if run-time library is different from compile-time library,
+ * allowing later run-time "micro" versions starting with 1.1.0.
+ */
+ if (lib_info.major != hdr_info.major
+ || lib_info.minor != hdr_info.minor
+ || (lib_info.micro != hdr_info.micro
+ && (lib_info.micro < hdr_info.micro
+ || hdr_info.major == 0
+ || (hdr_info.major == 1 && hdr_info.minor == 0))))
+ msg_warn("run-time library vs. compile-time header version mismatch: "
+ "OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d",
+ lib_info.major, lib_info.minor, lib_info.micro,
+ hdr_info.major, hdr_info.minor, hdr_info.micro);
+}
+
+/* tls_compile_version - compile-time OpenSSL version */
+
+const char *tls_compile_version(void)
+{
+ return (OPENSSL_VERSION_TEXT);
+}
+
+/* tls_run_version - run-time version "major.minor.micro" */
+
+const char *tls_run_version(void)
+{
+ return (OpenSSL_version(OPENSSL_VERSION));
+}
+
+const char **tls_pkey_algorithms(void)
+{
+
+ /*
+ * Return an array, not string, so that the result can be inspected
+ * without parsing. Sort the result alphabetically, not chronologically.
+ */
+ static const char *algs[] = {
+#ifndef OPENSSL_NO_DSA
+ "dsa",
+#endif
+#ifndef OPENSSL_NO_ECDSA
+ "ecdsa",
+#endif
+#ifndef OPENSSL_NO_RSA
+ "rsa",
+#endif
+ 0,
+ };
+
+ return (algs);
+}
+
+/* tls_bug_bits - SSL bug compatibility bits for this OpenSSL version */
+
+long tls_bug_bits(void)
+{
+ long bits = SSL_OP_ALL; /* Work around all known bugs */
+
+ /*
+ * Silently ignore any strings that don't appear in the tweaks table, or
+ * hex bits that are not in SSL_OP_ALL.
+ */
+ if (*var_tls_bug_tweaks) {
+ bits &= ~long_name_mask_opt(VAR_TLS_BUG_TWEAKS, ssl_bug_tweaks,
+ var_tls_bug_tweaks, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ /* Not relevant to SMTP */
+ bits &= ~SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
+#endif
+ }
+
+ /*
+ * Allow users to set options not in SSL_OP_ALL, and not already managed
+ * via other Postfix parameters.
+ */
+ if (*var_tls_ssl_options) {
+ long enable;
+
+ enable = long_name_mask_opt(VAR_TLS_SSL_OPTIONS, ssl_op_tweaks,
+ var_tls_ssl_options, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+ enable &= ~(SSL_OP_ALL | TLS_SSL_OP_MANAGED_BITS);
+ bits |= enable;
+ }
+
+ /*
+ * We unconditionally avoid re-use of ephemeral keys, note that we set DH
+ * keys via a callback, so reuse was never possible, but the ECDH key is
+ * set statically, so that is potentially subject to reuse. Set both
+ * options just in case.
+ */
+ bits |= SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE;
+ return (bits);
+}
+
+/* tls_print_errors - print and clear the error stack */
+
+void tls_print_errors(void)
+{
+ unsigned long err;
+ char buffer[1024]; /* XXX */
+ const char *file;
+ const char *data;
+ int line;
+ int flags;
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+/* XXX: We're ignoring the function name, do we want to log it? */
+#define ERRGET(fi, l, d, fl) ERR_get_error_all(fi, l, 0, d, fl)
+#else
+#define ERRGET(fi, l, d, fl) ERR_get_error_line_data(fi, l, d, fl)
+#endif
+
+ while ((err = ERRGET(&file, &line, &data, &flags)) != 0) {
+ ERR_error_string_n(err, buffer, sizeof(buffer));
+ if (flags & ERR_TXT_STRING)
+ msg_warn("TLS library problem: %s:%s:%d:%s:",
+ buffer, file, line, data);
+ else
+ msg_warn("TLS library problem: %s:%s:%d:", buffer, file, line);
+ }
+}
+
+/* tls_info_callback - callback for logging SSL events via Postfix */
+
+void tls_info_callback(const SSL *s, int where, int ret)
+{
+ char *str;
+ int w;
+
+ /* Adapted from OpenSSL apps/s_cb.c. */
+
+ w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT)
+ str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT)
+ str = "SSL_accept";
+ else
+ str = "unknown";
+
+ if (where & SSL_CB_LOOP) {
+ msg_info("%s:%s", str, SSL_state_string_long((SSL *) s));
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
+ msg_info("SSL3 alert %s:%s:%s", str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0)
+ msg_info("%s:failed in %s",
+ str, SSL_state_string_long((SSL *) s));
+ else if (ret < 0) {
+#ifndef LOG_NON_ERROR_STATES
+ switch (SSL_get_error((SSL *) s, ret)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* Don't log non-error states. */
+ break;
+ default:
+#endif
+ msg_info("%s:error in %s",
+ str, SSL_state_string_long((SSL *) s));
+#ifndef LOG_NON_ERROR_STATES
+ }
+#endif
+ }
+ }
+}
+
+ /*
+ * taken from OpenSSL crypto/bio/b_dump.c.
+ *
+ * Modified to save a lot of strcpy and strcat by Matti Aarnio.
+ *
+ * Rewritten by Wietse to eliminate fixed-size stack buffer, array index
+ * multiplication and division, sprintf() and strcpy(), and lots of strlen()
+ * calls. We could make it a little faster by using a fixed-size stack-based
+ * buffer.
+ *
+ * 200412 - use %lx to print pointers, after casting them to unsigned long.
+ */
+
+#define TRUNCATE_SPACE_NULL
+#define DUMP_WIDTH 16
+#define VERT_SPLIT 7
+
+static void tls_dump_buffer(const unsigned char *start, int len)
+{
+ VSTRING *buf = vstring_alloc(100);
+ const unsigned char *last = start + len - 1;
+ const unsigned char *row;
+ const unsigned char *col;
+ int ch;
+
+#ifdef TRUNCATE_SPACE_NULL
+ while (last >= start && (*last == ' ' || *last == 0))
+ last--;
+#endif
+
+ for (row = start; row <= last; row += DUMP_WIDTH) {
+ VSTRING_RESET(buf);
+ vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start));
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last) {
+ vstring_strcat(buf, " ");
+ } else {
+ ch = *col;
+ vstring_sprintf_append(buf, "%02x%c",
+ ch, col - row == VERT_SPLIT ? '|' : ' ');
+ }
+ }
+ VSTRING_ADDCH(buf, ' ');
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last)
+ break;
+ ch = *col;
+ if (!ISPRINT(ch))
+ ch = '.';
+ VSTRING_ADDCH(buf, ch);
+ if (col - row == VERT_SPLIT)
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ msg_info("%s", vstring_str(buf));
+ }
+#ifdef TRUNCATE_SPACE_NULL
+ if ((last + 1) - start < len)
+ msg_info("%04lx - <SPACES/NULLS>",
+ (unsigned long) ((last + 1) - start));
+#endif
+ vstring_free(buf);
+}
+
+/* taken from OpenSSL apps/s_cb.c */
+
+#if !OPENSSL_VERSION_PREREQ(3,0)
+long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi,
+ long unused_argl, long ret)
+{
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ }
+ return (ret);
+}
+
+#else
+long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, size_t len,
+ int argi, long unused_argl, int ret, size_t *processed)
+{
+ size_t bytes = (ret > 0 && processed != NULL) ? *processed : len;
+
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ if (ret > 0) {
+ msg_info("read from %08lX [%08lX] (%ld bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, (long) len,
+ (long) bytes, (long) bytes);
+ tls_dump_buffer((unsigned char *) argp, (int) bytes);
+ } else {
+ msg_info("read from %08lX [%08lX] (%ld bytes => %d)",
+ (unsigned long) bio, (unsigned long) argp,
+ (long) len, ret);
+ }
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ if (ret > 0) {
+ msg_info("write to %08lX [%08lX] (%ld bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, (long) len,
+ (long) bytes, (long) bytes);
+ tls_dump_buffer((unsigned char *) argp, (int) bytes);
+ } else {
+ msg_info("write to %08lX [%08lX] (%ld bytes => %d)",
+ (unsigned long) bio, (unsigned long) argp,
+ (long) len, ret);
+ }
+ }
+ return ret;
+}
+
+#endif
+
+const EVP_MD *tls_validate_digest(const char *dgst)
+{
+ const EVP_MD *md_alg;
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if ((md_alg = tls_digest_byname(dgst, NULL)) == 0)
+ msg_warn("Digest algorithm \"%s\" not found", dgst);
+ return md_alg;
+}
+
+#else
+
+ /*
+ * Broken linker workaround.
+ */
+int tls_dummy_for_broken_linkers;
+
+#endif
diff --git a/src/tls/tls_prng.h b/src/tls/tls_prng.h
new file mode 100644
index 0000000..df7fad9
--- /dev/null
+++ b/src/tls/tls_prng.h
@@ -0,0 +1,50 @@
+#ifndef _TLS_PRNG_SRC_H_INCLUDED_
+#define _TLS_PRNG_SRC_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_prng_src 3h
+/* SUMMARY
+/* OpenSSL PRNG maintenance routines
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct TLS_PRNG_SRC {
+ int fd; /* file handle */
+ char *name; /* resource name */
+ int timeout; /* time limit of applicable */
+} TLS_PRNG_SRC;
+
+extern TLS_PRNG_SRC *tls_prng_egd_open(const char *, int);
+extern ssize_t tls_prng_egd_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_egd_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_dev_open(const char *, int);
+extern ssize_t tls_prng_dev_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_dev_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_file_open(const char *, int);
+extern ssize_t tls_prng_file_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_file_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_exch_open(const char *);
+extern void tls_prng_exch_update(TLS_PRNG_SRC *);
+extern void tls_prng_exch_close(TLS_PRNG_SRC *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_prng_dev.c b/src/tls/tls_prng_dev.c
new file mode 100644
index 0000000..47abf47
--- /dev/null
+++ b/src/tls/tls_prng_dev.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_dev 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy device
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_dev_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_dev_read(dev, length)
+/* TLS_PRNG_SRC *dev;
+/* size_t length;
+/*
+/* int tls_prng_dev_close(dev)
+/* TLS_PRNG_SRC *dev;
+/* DESCRIPTION
+/* tls_prng_dev_open() opens the specified entropy device
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_dev_read() reads the requested number of bytes from
+/* the entropy device and updates the OpenSSL PRNG.
+/*
+/* tls_prng_dev_close() closes the specified entropy device
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy device.
+/* .IP length
+/* The number of bytes to read from the entropy device.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_dev_open() returns a null pointer on error.
+/*
+/* tls_prng_dev_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_dev_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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 <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_dev_open - open entropy device */
+
+TLS_PRNG_SRC *tls_prng_dev_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_dev_open";
+ TLS_PRNG_SRC *dev;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy device %s: %m", myname, name);
+ return (0);
+ } else {
+ dev = (TLS_PRNG_SRC *) mymalloc(sizeof(*dev));
+ dev->fd = fd;
+ dev->name = mystrdup(name);
+ dev->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy device %s", myname, name);
+ return (dev);
+ }
+}
+
+/* tls_prng_dev_read - update internal PRNG from device */
+
+ssize_t tls_prng_dev_read(TLS_PRNG_SRC *dev, size_t len)
+{
+ const char *myname = "tls_prng_dev_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+ size_t rand_bytes;
+
+ if (len <= 0)
+ msg_panic("%s: bad read length: %ld", myname, (long) len);
+
+ if (len > sizeof(buffer))
+ rand_bytes = sizeof(buffer);
+ else
+ rand_bytes = len;
+ errno = 0;
+ count = timed_read(dev->fd, buffer, rand_bytes, dev->timeout, (void *) 0);
+ if (count > 0) {
+ if (msg_verbose)
+ msg_info("%s: read %ld bytes from entropy device %s",
+ myname, (long) count, dev->name);
+ RAND_seed(buffer, count);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: cannot read %ld bytes from entropy device %s: %m",
+ myname, (long) rand_bytes, dev->name);
+ }
+ return (count);
+}
+
+/* tls_prng_dev_close - disconnect from EGD server */
+
+int tls_prng_dev_close(TLS_PRNG_SRC *dev)
+{
+ const char *myname = "tls_prng_dev_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy device %s", myname, dev->name);
+ err = close(dev->fd);
+ myfree(dev->name);
+ myfree((void *) dev);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_egd.c b/src/tls/tls_prng_egd.c
new file mode 100644
index 0000000..e4a4cd5
--- /dev/null
+++ b/src/tls/tls_prng_egd.c
@@ -0,0 +1,166 @@
+/*++
+/* NAME
+/* tls_prng_egd 3
+/* SUMMARY
+/* seed OpenSSL PRNG from EGD server
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_egd_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_egd_read(egd, length)
+/* TLS_PRNG_SRC *egd;
+/* size_t length;
+/*
+/* int tls_prng_egd_close(egd)
+/* TLS_PRNG_SRC *egd;
+/* DESCRIPTION
+/* tls_prng_egd_open() connect to the specified UNIX-domain service
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_egd_read() reads the requested number of bytes from
+/* the EGD server and updates the OpenSSL PRNG.
+/*
+/* tls_prng_egd_close() disconnects from the specified EGD server
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The UNIX-domain pathname of the EGD service.
+/* .IP length
+/* The number of bytes to read from the EGD server.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_egd_open() returns a null pointer on error.
+/*
+/* tls_prng_egd_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_egd_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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 <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_egd_open - connect to EGD server */
+
+TLS_PRNG_SRC *tls_prng_egd_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_egd_open";
+ TLS_PRNG_SRC *egd;
+ int fd;
+
+ if (msg_verbose)
+ msg_info("%s: connect to EGD server %s", myname, name);
+
+ if ((fd = unix_connect(name, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot connect to EGD server %s: %m", myname, name);
+ return (0);
+ } else {
+ egd = (TLS_PRNG_SRC *) mymalloc(sizeof(*egd));
+ egd->fd = fd;
+ egd->name = mystrdup(name);
+ egd->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: connected to EGD server %s", myname, name);
+ return (egd);
+ }
+}
+
+/* tls_prng_egd_read - update internal PRNG from EGD server */
+
+ssize_t tls_prng_egd_read(TLS_PRNG_SRC *egd, size_t len)
+{
+ const char *myname = "tls_prng_egd_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+
+ if (len <= 0)
+ msg_panic("%s: bad length %ld", myname, (long) len);
+
+ buffer[0] = 1;
+ buffer[1] = (len > UCHAR_MAX ? UCHAR_MAX : len);
+
+ if (timed_write(egd->fd, buffer, 2, egd->timeout, (void *) 0) != 2) {
+ msg_info("cannot write to EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, 1, egd->timeout, (void *) 0) != 1) {
+ msg_info("cannot read from EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ count = buffer[0];
+ if (count > sizeof(buffer))
+ count = sizeof(buffer);
+ if (count == 0) {
+ msg_info("EGD server %s reports zero bytes available", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, count, egd->timeout, (void *) 0) != count) {
+ msg_info("cannot read %ld bytes from EGD server %s: %m",
+ (long) count, egd->name);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: got %ld bytes from EGD server %s", myname,
+ (long) count, egd->name);
+ RAND_seed(buffer, count);
+ return (count);
+}
+
+/* tls_prng_egd_close - disconnect from EGD server */
+
+int tls_prng_egd_close(TLS_PRNG_SRC *egd)
+{
+ const char *myname = "tls_prng_egd_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close EGD server %s", myname, egd->name);
+ err = close(egd->fd);
+ myfree(egd->name);
+ myfree((void *) egd);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_exch.c b/src/tls/tls_prng_exch.c
new file mode 100644
index 0000000..31523a3
--- /dev/null
+++ b/src/tls/tls_prng_exch.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* tls_prng_exch 3
+/* SUMMARY
+/* maintain PRNG exchange file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_exch_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* void tls_prng_exch_update(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* void tls_prng_exch_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_exch_open() opens the specified PRNG exchange file
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_exch_update() reads the requested number of bytes
+/* from the PRNG exchange file, updates the OpenSSL PRNG, and
+/* writes the requested number of bytes to the exchange file.
+/* The file is locked for exclusive access.
+/*
+/* tls_prng_exch_close() closes the specified PRNG exchange
+/* file and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The name of the PRNG exchange file.
+/* .IP length
+/* The number of bytes to read from/write to the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <myflock.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* Application specific. */
+
+#define TLS_PRNG_EXCH_SIZE 1024 /* XXX Why not configurable? */
+
+/* tls_prng_exch_open - open PRNG exchange file */
+
+TLS_PRNG_SRC *tls_prng_exch_open(const char *name)
+{
+ const char *myname = "tls_prng_exch_open";
+ TLS_PRNG_SRC *eh;
+ int fd;
+
+ if ((fd = open(name, O_RDWR | O_CREAT, 0600)) < 0)
+ msg_fatal("%s: cannot open PRNG exchange file %s: %m", myname, name);
+ eh = (TLS_PRNG_SRC *) mymalloc(sizeof(*eh));
+ eh->fd = fd;
+ eh->name = mystrdup(name);
+ eh->timeout = 0;
+ if (msg_verbose)
+ msg_info("%s: opened PRNG exchange file %s", myname, name);
+ return (eh);
+}
+
+/* tls_prng_exch_update - update PRNG exchange file */
+
+void tls_prng_exch_update(TLS_PRNG_SRC *eh)
+{
+ unsigned char buffer[TLS_PRNG_EXCH_SIZE];
+ ssize_t count;
+
+ /*
+ * Update the PRNG exchange file. Since other processes may have added
+ * entropy, we use a read-stir-write cycle.
+ */
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0)
+ msg_fatal("cannot lock PRNG exchange file %s: %m", eh->name);
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if ((count = read(eh->fd, buffer, sizeof(buffer))) < 0)
+ msg_fatal("cannot read PRNG exchange file %s: %m", eh->name);
+
+ if (count > 0)
+ RAND_seed(buffer, count);
+ RAND_bytes(buffer, sizeof(buffer));
+
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if (write(eh->fd, buffer, sizeof(buffer)) != sizeof(buffer))
+ msg_fatal("cannot write PRNG exchange file %s: %m", eh->name);
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0)
+ msg_fatal("cannot unlock PRNG exchange file %s: %m", eh->name);
+}
+
+/* tls_prng_exch_close - close PRNG exchange file */
+
+void tls_prng_exch_close(TLS_PRNG_SRC *eh)
+{
+ const char *myname = "tls_prng_exch_close";
+
+ if (close(eh->fd) < 0)
+ msg_fatal("close PRNG exchange file %s: %m", eh->name);
+ if (msg_verbose)
+ msg_info("%s: closed PRNG exchange file %s", myname, eh->name);
+ myfree(eh->name);
+ myfree((void *) eh);
+}
+
+#endif
diff --git a/src/tls/tls_prng_file.c b/src/tls/tls_prng_file.c
new file mode 100644
index 0000000..23865be
--- /dev/null
+++ b/src/tls/tls_prng_file.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_file 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_file_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_file_read(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* int tls_prng_file_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_file_open() open the specified file and returns
+/* a handle that should be used with all subsequent access.
+/*
+/* tls_prng_file_read() reads the requested number of bytes from
+/* the entropy file and updates the OpenSSL PRNG. The file is not
+/* locked for shared or exclusive access.
+/*
+/* tls_prng_file_close() closes the specified entropy file
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy file.
+/* .IP length
+/* The number of bytes to read from the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_file_open() returns a null pointer on error.
+/*
+/* tls_prng_file_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_file_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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 <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_file_open - open entropy file */
+
+TLS_PRNG_SRC *tls_prng_file_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_file_open";
+ TLS_PRNG_SRC *fh;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy file %s: %m", myname, name);
+ return (0);
+ } else {
+ fh = (TLS_PRNG_SRC *) mymalloc(sizeof(*fh));
+ fh->fd = fd;
+ fh->name = mystrdup(name);
+ fh->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy file %s", myname, name);
+ return (fh);
+ }
+}
+
+/* tls_prng_file_read - update internal PRNG from entropy file */
+
+ssize_t tls_prng_file_read(TLS_PRNG_SRC *fh, size_t len)
+{
+ const char *myname = "tls_prng_file_read";
+ char buffer[8192];
+ ssize_t to_read;
+ ssize_t count;
+
+ if (msg_verbose)
+ msg_info("%s: seed internal pool from file %s", myname, fh->name);
+
+ if (lseek(fh->fd, 0, SEEK_SET) < 0) {
+ if (msg_verbose)
+ msg_info("cannot seek entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ errno = 0;
+ for (to_read = len; to_read > 0; to_read -= count) {
+ if ((count = timed_read(fh->fd, buffer, to_read > sizeof(buffer) ?
+ sizeof(buffer) : to_read,
+ fh->timeout, (void *) 0)) < 0) {
+ if (msg_verbose)
+ msg_info("cannot read entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ if (count == 0)
+ break;
+ RAND_seed(buffer, count);
+ }
+ if (msg_verbose)
+ msg_info("read %ld bytes from entropy file %s: %m",
+ (long) (len - to_read), fh->name);
+ return (len - to_read);
+}
+
+/* tls_prng_file_close - close entropy file */
+
+int tls_prng_file_close(TLS_PRNG_SRC *fh)
+{
+ const char *myname = "tls_prng_file_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy file %s", myname, fh->name);
+ err = close(fh->fd);
+ myfree(fh->name);
+ myfree((void *) fh);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_proxy.h b/src/tls/tls_proxy.h
new file mode 100644
index 0000000..1d96a8b
--- /dev/null
+++ b/src/tls/tls_proxy.h
@@ -0,0 +1,287 @@
+#ifndef _TLS_PROXY_H_INCLUDED_
+#define _TLS_PROXY_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_proxy_clnt 3h
+/* SUMMARY
+/* postscreen TLS proxy support
+/* SYNOPSIS
+/* #include <tls_proxy_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * External interface.
+ */
+#define TLS_PROXY_FLAG_ROLE_SERVER (1<<0) /* request server role */
+#define TLS_PROXY_FLAG_ROLE_CLIENT (1<<1) /* request client role */
+#define TLS_PROXY_FLAG_SEND_CONTEXT (1<<2) /* send TLS context */
+
+#ifdef USE_TLS
+
+ /*
+ * TLS_CLIENT_PARAMS structure. If this changes, update all
+ * TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c.
+ *
+ * In the serialization these attributes are identified by their configuration
+ * parameter names.
+ *
+ * NOTE: this does not include openssl_path.
+ *
+ * TODO: TLS_SERVER_PARAM structure, like TLS_CLIENT_PARAMS plus
+ * VAR_TLS_SERVER_SNI_MAPS.
+ */
+typedef struct TLS_CLIENT_PARAMS {
+ char *tls_cnf_file;
+ char *tls_cnf_name;
+ char *tls_high_clist;
+ char *tls_medium_clist;
+ char *tls_low_clist;
+ char *tls_export_clist;
+ char *tls_null_clist;
+ char *tls_eecdh_auto;
+ char *tls_eecdh_strong;
+ char *tls_eecdh_ultra;
+ char *tls_bug_tweaks;
+ char *tls_ssl_options;
+ char *tls_dane_digests;
+ char *tls_mgr_service;
+ char *tls_tkt_cipher;
+ int tls_daemon_rand_bytes;
+ int tls_append_def_CA;
+ int tls_bc_pkey_fprint;
+ int tls_preempt_clist;
+ int tls_multi_wildcard;
+} TLS_CLIENT_PARAMS;
+
+#define TLS_PROXY_PARAMS(params, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ (((params)->a1), ((params)->a2), ((params)->a3), \
+ ((params)->a4), ((params)->a5), ((params)->a6), ((params)->a7), \
+ ((params)->a8), ((params)->a9), ((params)->a10), ((params)->a11), \
+ ((params)->a12), ((params)->a13), ((params)->a14), ((params)->a15), \
+ ((params)->a16), ((params)->a17), ((params)->a18), ((params)->a19), \
+ ((params)->a20))
+
+ /*
+ * tls_proxy_client_param_misc.c, tls_proxy_client_param_print.c, and
+ * tls_proxy_client_param_scan.c.
+ */
+extern TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *);
+extern char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+ /*
+ * Functions that handle TLS_XXX_INIT_PROPS and TLS_XXX_START_PROPS. These
+ * data structures are defined elsewhere, because they are also used in
+ * non-proxied requests.
+ */
+#define tls_proxy_legacy_open(service, flags, peer_stream, peer_addr, \
+ peer_port, timeout, serverid) \
+ tls_proxy_open((service), (flags), (peer_stream), (peer_addr), \
+ (peer_port), (timeout), (timeout), (serverid), \
+ (void *) 0, (void *) 0, (void *) 0)
+
+extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *,
+ const char *, int, int, const char *,
+ void *, void *, void *);
+
+#define TLS_PROXY_CLIENT_INIT_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *);
+extern void tls_proxy_context_free(TLS_SESS_STATE *);
+extern int tls_proxy_context_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+extern int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *);
+extern char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_INIT_PROPS *);
+
+extern int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *);
+
+extern int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *);
+
+extern int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
+
+#endif /* USE_TLS */
+
+ /*
+ * TLSPROXY attributes, unconditionally exposed.
+ */
+#define TLS_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */
+#define TLS_ATTR_FLAGS "flags"
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_SERVERID "serverid"
+
+#ifdef USE_TLS
+
+ /*
+ * Misc attributes.
+ */
+#define TLS_ATTR_COUNT "count"
+
+ /*
+ * TLS_SESS_STATE attributes.
+ */
+#define TLS_ATTR_PEER_CN "peer_CN"
+#define TLS_ATTR_ISSUER_CN "issuer_CN"
+#define TLS_ATTR_PEER_CERT_FPT "peer_fingerprint"
+#define TLS_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint"
+#define TLS_ATTR_SEC_LEVEL "level"
+#define TLS_ATTR_PEER_STATUS "peer_status"
+#define TLS_ATTR_CIPHER_PROTOCOL "cipher_protocol"
+#define TLS_ATTR_CIPHER_NAME "cipher_name"
+#define TLS_ATTR_CIPHER_USEBITS "cipher_usebits"
+#define TLS_ATTR_CIPHER_ALGBITS "cipher_algbits"
+#define TLS_ATTR_KEX_NAME "key_exchange"
+#define TLS_ATTR_KEX_CURVE "key_exchange_curve"
+#define TLS_ATTR_KEX_BITS "key_exchange_bits"
+#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature"
+#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve"
+#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits"
+#define TLS_ATTR_CLNT_SIG_DGST "clnt_signature_digest"
+#define TLS_ATTR_SRVR_SIG_NAME "srvr_signature"
+#define TLS_ATTR_SRVR_SIG_CURVE "srvr_signature_curve"
+#define TLS_ATTR_SRVR_SIG_BITS "srvr_signature_bits"
+#define TLS_ATTR_SRVR_SIG_DGST "srvr_signature_digest"
+#define TLS_ATTR_NAMADDR "namaddr"
+
+ /*
+ * TLS_SERVER_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_SET_SESSID "set_sessid"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_EECDH_GRADE "eecdh_grade"
+#define TLS_ATTR_DH1K_PARAM_FILE "dh1024_param_file"
+#define TLS_ATTR_DH512_PARAM_FILE "dh512_param_file"
+#define TLS_ATTR_ASK_CCERT "ask_ccert"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_SERVER_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_REQUIRECERT "requirecert"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_CNF_FILE "config_file"
+#define TLS_ATTR_CNF_NAME "config_name"
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_TLS_LEVEL "tls_level"
+#define TLS_ATTR_NEXTHOP "nexthop"
+#define TLS_ATTR_HOST "host"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_SNI "sni"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_HELO "helo"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MATCHARGV "matchargv"
+#define TLS_ATTR_MDALG "mdalg"
+#define TLS_ATTR_DANE "dane"
+
+ /*
+ * TLS_TLSA attributes.
+ */
+#define TLS_ATTR_USAGE "usage"
+#define TLS_ATTR_SELECTOR "selector"
+#define TLS_ATTR_MTYPE "mtype"
+#define TLS_ATTR_DATA "data"
+
+ /*
+ * TLS_DANE attributes.
+ */
+#define TLS_ATTR_DOMAIN "domain"
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_proxy_client_misc.c b/src/tls/tls_proxy_client_misc.c
new file mode 100644
index 0000000..2191dce
--- /dev/null
+++ b/src/tls/tls_proxy_client_misc.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* tls_proxy_client_misc 3
+/* SUMMARY
+/* TLS_CLIENT_XXX structure support
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_param_serialize(print_fn, buf, params)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTRING *buf;
+/* const TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_init_serialize(print_fn, buf, init_props)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTRING *buf;
+/* const TLS_CLIENT_INIT_PROPS *init_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_from_config() initializes a TLS_CLIENT_PARAMS
+/* structure from configuration parameters and returns its
+/* argument. Strings are not copied. The result must therefore
+/* not be passed to tls_proxy_client_param_free().
+/*
+/* tls_proxy_client_param_serialize() and
+/* tls_proxy_client_init_serialize() serialize the specified
+/* object to a memory buffer, using the specified print function
+/* (typically, attr_print_plain). The result can be used
+/* determine whether there are any differences between instances
+/* of the same object type.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_client_param_from_config - initialize TLS_CLIENT_PARAMS from configuration */
+
+TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *params)
+{
+ TLS_PROXY_PARAMS(params,
+ tls_cnf_file = var_tls_cnf_file,
+ tls_cnf_name = var_tls_cnf_name,
+ tls_high_clist = var_tls_high_clist,
+ tls_medium_clist = var_tls_medium_clist,
+ tls_low_clist = var_tls_low_clist,
+ tls_export_clist = var_tls_export_clist,
+ tls_null_clist = var_tls_null_clist,
+ tls_eecdh_auto = var_tls_eecdh_auto,
+ tls_eecdh_strong = var_tls_eecdh_strong,
+ tls_eecdh_ultra = var_tls_eecdh_ultra,
+ tls_bug_tweaks = var_tls_bug_tweaks,
+ tls_ssl_options = var_tls_ssl_options,
+ tls_dane_digests = var_tls_dane_digests,
+ tls_mgr_service = var_tls_mgr_service,
+ tls_tkt_cipher = var_tls_tkt_cipher,
+ tls_daemon_rand_bytes = var_tls_daemon_rand_bytes,
+ tls_append_def_CA = var_tls_append_def_CA,
+ tls_bc_pkey_fprint = var_tls_bc_pkey_fprint,
+ tls_preempt_clist = var_tls_preempt_clist,
+ tls_multi_wildcard = var_tls_multi_wildcard);
+ return (params);
+}
+
+/* tls_proxy_client_param_serialize - serialize TLS_CLIENT_PARAMS to string */
+
+char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN print_fn,
+ VSTRING *buf,
+ const TLS_CLIENT_PARAMS *params)
+{
+ const char myname[] = "tls_proxy_client_param_serialize";
+ VSTREAM *mp;
+
+ if ((mp = vstream_memopen(buf, O_WRONLY)) == 0
+ || print_fn(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_param_print,
+ (const void *) params),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("%s: can't serialize properties: %m", myname);
+ return (vstring_str(buf));
+}
+
+/* tls_proxy_client_init_serialize - serialize to string */
+
+char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN print_fn,
+ VSTRING *buf,
+ const TLS_CLIENT_INIT_PROPS *props)
+{
+ const char myname[] = "tls_proxy_client_init_serialize";
+ VSTREAM *mp;
+
+ if ((mp = vstream_memopen(buf, O_WRONLY)) == 0
+ || print_fn(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_init_print,
+ (const void *) props),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("%s: can't serialize properties: %m", myname);
+ return (vstring_str(buf));
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_print.c b/src/tls/tls_proxy_client_print.c
new file mode 100644
index 0000000..fe0b397
--- /dev/null
+++ b/src/tls/tls_proxy_client_print.c
@@ -0,0 +1,294 @@
+/*++
+/* NAME
+/* tls_proxy_client_print 3
+/* SUMMARY
+/* write TLS_CLIENT_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/*
+/* int tls_proxy_client_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/*
+/* int tls_proxy_client_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/* DESCRIPTION
+/* tls_proxy_client_param_print() writes a TLS_CLIENT_PARAMS structure to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_param_print() is meant to be passed as a call-back to
+/* attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_param_print, (const void *) param), ...
+/*
+/* tls_proxy_client_init_print() writes a full TLS_CLIENT_INIT_PROPS
+/* structure to the named stream using the specified attribute
+/* print routine. tls_proxy_client_init_print() is meant to
+/* be passed as a call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_init_print, (const void *) init_props), ...
+/*
+/* tls_proxy_client_start_print() writes a TLS_CLIENT_START_PROPS
+/* structure, without stream or file descriptor members, to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_start_print() is meant to be passed as a
+/* call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_start_print, (const void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_print - send TLS_CLIENT_PARAMS over stream */
+
+int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_CLIENT_PARAMS *params = (const TLS_CLIENT_PARAMS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_print");
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_CNF_FILE, params->tls_cnf_file),
+ SEND_ATTR_STR(TLS_ATTR_CNF_NAME, params->tls_cnf_name),
+ SEND_ATTR_STR(VAR_TLS_HIGH_CLIST, params->tls_high_clist),
+ SEND_ATTR_STR(VAR_TLS_MEDIUM_CLIST,
+ params->tls_medium_clist),
+ SEND_ATTR_STR(VAR_TLS_LOW_CLIST, params->tls_low_clist),
+ SEND_ATTR_STR(VAR_TLS_EXPORT_CLIST,
+ params->tls_export_clist),
+ SEND_ATTR_STR(VAR_TLS_NULL_CLIST, params->tls_null_clist),
+ SEND_ATTR_STR(VAR_TLS_EECDH_AUTO, params->tls_eecdh_auto),
+ SEND_ATTR_STR(VAR_TLS_EECDH_STRONG,
+ params->tls_eecdh_strong),
+ SEND_ATTR_STR(VAR_TLS_EECDH_ULTRA,
+ params->tls_eecdh_ultra),
+ SEND_ATTR_STR(VAR_TLS_BUG_TWEAKS, params->tls_bug_tweaks),
+ SEND_ATTR_STR(VAR_TLS_SSL_OPTIONS,
+ params->tls_ssl_options),
+ SEND_ATTR_STR(VAR_TLS_DANE_DIGESTS,
+ params->tls_dane_digests),
+ SEND_ATTR_STR(VAR_TLS_MGR_SERVICE,
+ params->tls_mgr_service),
+ SEND_ATTR_STR(VAR_TLS_TKT_CIPHER, params->tls_tkt_cipher),
+ SEND_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ params->tls_daemon_rand_bytes),
+ SEND_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ params->tls_append_def_CA),
+ SEND_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ params->tls_bc_pkey_fprint),
+ SEND_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ params->tls_preempt_clist),
+ SEND_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_print - send TLS_CLIENT_INIT_PROPS over stream */
+
+int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_CLIENT_INIT_PROPS *props = (const TLS_CLIENT_INIT_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_tlsa_print - send TLS_TLSA over stream */
+
+static int tls_proxy_client_tlsa_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_TLSA *head = (const TLS_TLSA *) ptr;
+ const TLS_TLSA *tp;
+ int count;
+ int ret;
+
+ for (tp = head, count = 0; tp != 0; tp = tp->next)
+ ++count;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print count=%d", count);
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_COUNT, count),
+ ATTR_TYPE_END);
+
+ for (tp = head; ret == 0 && tp != 0; tp = tp->next)
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_USAGE, tp->usage),
+ SEND_ATTR_INT(TLS_ATTR_SELECTOR, tp->selector),
+ SEND_ATTR_INT(TLS_ATTR_MTYPE, tp->mtype),
+ SEND_ATTR_DATA(TLS_ATTR_DATA, tp->length, tp->data),
+ ATTR_TYPE_END);
+
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print ret=%d", count);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_print - send TLS_DANE over stream */
+
+static int tls_proxy_client_dane_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_DANE *dane = (const TLS_DANE *) ptr;
+ int ret;
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_DANE, dane != 0),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print dane=%d", dane != 0);
+
+ if (ret == 0 && dane != 0) {
+ /* Send the base_domain and RRs, we don't need the other fields */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_DOMAIN,
+ STRING_OR_EMPTY(dane->base_domain)),
+ SEND_ATTR_FUNC(tls_proxy_client_tlsa_print,
+ (const void *) dane->tlsa),
+ ATTR_TYPE_END);
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_print - send TLS_CLIENT_START_PROPS over stream */
+
+int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_CLIENT_START_PROPS *props = (const TLS_CLIENT_START_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level),
+ SEND_ATTR_STR(TLS_ATTR_NEXTHOP,
+ STRING_OR_EMPTY(props->nexthop)),
+ SEND_ATTR_STR(TLS_ATTR_HOST,
+ STRING_OR_EMPTY(props->host)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_SNI,
+ STRING_OR_EMPTY(props->sni)),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_HELO,
+ STRING_OR_EMPTY(props->helo)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_FUNC(argv_attr_print,
+ (const void *) props->matchargv),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ SEND_ATTR_FUNC(tls_proxy_client_dane_print,
+ (const void *) props->dane),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_print ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_scan.c b/src/tls/tls_proxy_client_scan.c
new file mode 100644
index 0000000..7083353
--- /dev/null
+++ b/src/tls/tls_proxy_client_scan.c
@@ -0,0 +1,496 @@
+/*++
+/* NAME
+/* tls_proxy_client_scan 3
+/* SUMMARY
+/* read TLS_CLIENT_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_param_free(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* int tls_proxy_client_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_init_free(init_props)
+/* TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_client_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_start_free(start_props)
+/* TLS_CLIENT_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_scan() reads a TLS_CLIENT_PARAMS structure from
+/* the named stream using the specified attribute scan routine.
+/* tls_proxy_client_param_scan() is meant to be passed as a call-back
+/* function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_param_free() destroys a TLS_CLIENT_PARAMS structure
+/* that was created by tls_proxy_client_param_scan().
+/*
+/* TLS_CLIENT_PARAMS *param = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_param_scan, (void *) &param)
+/* ...
+/* if (param != 0)
+/* tls_proxy_client_param_free(param);
+/*
+/* tls_proxy_client_init_scan() reads a full TLS_CLIENT_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_client_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_init_free() destroys a TLS_CLIENT_INIT_PROPS
+/* structure that was created by tls_proxy_client_init_scan().
+/*
+/* TLS_CLIENT_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props != 0)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_client_start_scan() reads a TLS_CLIENT_START_PROPS
+/* structure, without the stream of file descriptor members,
+/* from the named stream using the specified attribute scan
+/* routine. tls_proxy_client_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_start_free() destroys a TLS_CLIENT_START_PROPS
+/* structure that was created by tls_proxy_client_start_scan().
+/*
+/* TLS_CLIENT_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props != 0)
+/* tls_proxy_client_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_free - destroy TLS_CLIENT_PARAMS structure */
+
+void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *params)
+{
+ myfree(params->tls_cnf_file);
+ myfree(params->tls_cnf_name);
+ myfree(params->tls_high_clist);
+ myfree(params->tls_medium_clist);
+ myfree(params->tls_low_clist);
+ myfree(params->tls_export_clist);
+ myfree(params->tls_null_clist);
+ myfree(params->tls_eecdh_auto);
+ myfree(params->tls_eecdh_strong);
+ myfree(params->tls_eecdh_ultra);
+ myfree(params->tls_bug_tweaks);
+ myfree(params->tls_ssl_options);
+ myfree(params->tls_dane_digests);
+ myfree(params->tls_mgr_service);
+ myfree(params->tls_tkt_cipher);
+ myfree((void *) params);
+}
+
+/* tls_proxy_client_param_scan - receive TLS_CLIENT_PARAMS from stream */
+
+int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_PARAMS *params
+ = (TLS_CLIENT_PARAMS *) mymalloc(sizeof(*params));
+ int ret;
+ VSTRING *cnf_file = vstring_alloc(25);
+ VSTRING *cnf_name = vstring_alloc(25);
+ VSTRING *tls_high_clist = vstring_alloc(25);
+ VSTRING *tls_medium_clist = vstring_alloc(25);
+ VSTRING *tls_low_clist = vstring_alloc(25);
+ VSTRING *tls_export_clist = vstring_alloc(25);
+ VSTRING *tls_null_clist = vstring_alloc(25);
+ VSTRING *tls_eecdh_auto = vstring_alloc(25);
+ VSTRING *tls_eecdh_strong = vstring_alloc(25);
+ VSTRING *tls_eecdh_ultra = vstring_alloc(25);
+ VSTRING *tls_bug_tweaks = vstring_alloc(25);
+ VSTRING *tls_ssl_options = vstring_alloc(25);
+ VSTRING *tls_dane_digests = vstring_alloc(25);
+ VSTRING *tls_mgr_service = vstring_alloc(25);
+ VSTRING *tls_tkt_cipher = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(params, 0, sizeof(*params));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_CNF_FILE, cnf_file),
+ RECV_ATTR_STR(TLS_ATTR_CNF_NAME, cnf_name),
+ RECV_ATTR_STR(VAR_TLS_HIGH_CLIST, tls_high_clist),
+ RECV_ATTR_STR(VAR_TLS_MEDIUM_CLIST, tls_medium_clist),
+ RECV_ATTR_STR(VAR_TLS_LOW_CLIST, tls_low_clist),
+ RECV_ATTR_STR(VAR_TLS_EXPORT_CLIST, tls_export_clist),
+ RECV_ATTR_STR(VAR_TLS_NULL_CLIST, tls_null_clist),
+ RECV_ATTR_STR(VAR_TLS_EECDH_AUTO, tls_eecdh_auto),
+ RECV_ATTR_STR(VAR_TLS_EECDH_STRONG, tls_eecdh_strong),
+ RECV_ATTR_STR(VAR_TLS_EECDH_ULTRA, tls_eecdh_ultra),
+ RECV_ATTR_STR(VAR_TLS_BUG_TWEAKS, tls_bug_tweaks),
+ RECV_ATTR_STR(VAR_TLS_SSL_OPTIONS, tls_ssl_options),
+ RECV_ATTR_STR(VAR_TLS_DANE_DIGESTS, tls_dane_digests),
+ RECV_ATTR_STR(VAR_TLS_MGR_SERVICE, tls_mgr_service),
+ RECV_ATTR_STR(VAR_TLS_TKT_CIPHER, tls_tkt_cipher),
+ RECV_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ &params->tls_daemon_rand_bytes),
+ RECV_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ &params->tls_append_def_CA),
+ RECV_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ &params->tls_bc_pkey_fprint),
+ RECV_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ &params->tls_preempt_clist),
+ RECV_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ &params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ params->tls_cnf_file = vstring_export(cnf_file);
+ params->tls_cnf_name = vstring_export(cnf_name);
+ params->tls_high_clist = vstring_export(tls_high_clist);
+ params->tls_medium_clist = vstring_export(tls_medium_clist);
+ params->tls_low_clist = vstring_export(tls_low_clist);
+ params->tls_export_clist = vstring_export(tls_export_clist);
+ params->tls_null_clist = vstring_export(tls_null_clist);
+ params->tls_eecdh_auto = vstring_export(tls_eecdh_auto);
+ params->tls_eecdh_strong = vstring_export(tls_eecdh_strong);
+ params->tls_eecdh_ultra = vstring_export(tls_eecdh_ultra);
+ params->tls_bug_tweaks = vstring_export(tls_bug_tweaks);
+ params->tls_ssl_options = vstring_export(tls_ssl_options);
+ params->tls_dane_digests = vstring_export(tls_dane_digests);
+ params->tls_mgr_service = vstring_export(tls_mgr_service);
+ params->tls_tkt_cipher = vstring_export(tls_tkt_cipher);
+
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_param_free(params);
+ params = 0;
+ }
+ *(TLS_CLIENT_PARAMS **) ptr = params;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_free - destroy TLS_CLIENT_INIT_PROPS structure */
+
+void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_init_scan - receive TLS_CLIENT_INIT_PROPS from stream */
+
+int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_INIT_PROPS *props
+ = (TLS_CLIENT_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_init_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_INIT_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_free - destroy TLS_CLIENT_START_PROPS structure */
+
+void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props)
+{
+ myfree((void *) props->nexthop);
+ myfree((void *) props->host);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->sni);
+ myfree((void *) props->serverid);
+ myfree((void *) props->helo);
+ myfree((void *) props->protocols);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ if (props->matchargv)
+ argv_free((ARGV *) props->matchargv);
+ myfree((void *) props->mdalg);
+ if (props->dane)
+ tls_dane_free((TLS_DANE *) props->dane);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_tlsa_scan - receive TLS_TLSA from stream */
+
+static int tls_proxy_client_tlsa_scan(ATTR_SCAN_COMMON_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ static VSTRING *data;
+ TLS_TLSA *head;
+ int count;
+ int ret;
+
+ if (data == 0)
+ data = vstring_alloc(64);
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_COUNT, &count),
+ ATTR_TYPE_END);
+ if (ret == 1 && msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan count=%d", count);
+
+ for (head = 0; ret == 1 && count > 0; --count) {
+ int u, s, m;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_USAGE, &u),
+ RECV_ATTR_INT(TLS_ATTR_SELECTOR, &s),
+ RECV_ATTR_INT(TLS_ATTR_MTYPE, &m),
+ RECV_ATTR_DATA(TLS_ATTR_DATA, data),
+ ATTR_TYPE_END);
+ if (ret == 4) {
+ ret = 1;
+ /* This makes a copy of the static vstring content */
+ head = tlsa_prepend(head, u, s, m, (unsigned char *) STR(data),
+ LEN(data));
+ } else
+ ret = -1;
+ }
+
+ if (ret != 1) {
+ tls_tlsa_free(head);
+ head = 0;
+ }
+ *(TLS_TLSA **) ptr = head;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_scan - receive TLS_DANE from stream */
+
+static int tls_proxy_client_dane_scan(ATTR_SCAN_COMMON_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_DANE *dane = 0;
+ int ret;
+ int have_dane = 0;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_DANE, &have_dane),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan have_dane=%d", have_dane);
+
+ if (ret == 1 && have_dane) {
+ VSTRING *base_domain = vstring_alloc(25);
+
+ dane = tls_dane_alloc();
+ /* We only need the base domain and TLSA RRs */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_DOMAIN, base_domain),
+ RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan,
+ &dane->tlsa),
+ ATTR_TYPE_END);
+
+ /* Always construct a well-formed structure. */
+ dane->base_domain = vstring_export(base_domain);
+ ret = (ret == 2 ? 1 : -1);
+ if (ret != 1) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ *(TLS_DANE **) ptr = dane;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_scan - receive TLS_CLIENT_START_PROPS from stream */
+
+int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_START_PROPS *props
+ = (TLS_CLIENT_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *nexthop = vstring_alloc(25);
+ VSTRING *host = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *sni = vstring_alloc(25);
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *helo = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ props->fd = -1;
+ props->dane = 0; /* scan_fn may return early */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level),
+ RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop),
+ RECV_ATTR_STR(TLS_ATTR_HOST, host),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_SNI, sni),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_HELO, helo),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_FUNC(argv_attr_scan, &props->matchargv),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ RECV_ATTR_FUNC(tls_proxy_client_dane_scan,
+ &props->dane),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->nexthop = vstring_export(nexthop);
+ props->host = vstring_export(host);
+ props->namaddr = vstring_export(namaddr);
+ props->sni = vstring_export(sni);
+ props->serverid = vstring_export(serverid);
+ props->helo = vstring_export(helo);
+ props->protocols = vstring_export(protocols);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_start_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_START_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_scan ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_clnt.c b/src/tls/tls_proxy_clnt.c
new file mode 100644
index 0000000..ca6a2e4
--- /dev/null
+++ b/src/tls/tls_proxy_clnt.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* tlsproxy_clnt 3
+/* SUMMARY
+/* tlsproxy(8) client support
+/* SYNOPSIS
+/* #include <tlsproxy_clnt.h>
+/*
+/* VSTREAM *tls_proxy_open(service, flags, peer_stream, peer_addr,
+/* peer_port, handshake_timeout, session_timeout,
+/* serverid, tls_params, init_props, start_props)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int handshake_timeout;
+/* int session_timeout;
+/* const char *serverid;
+/* void *tls_params;
+/* void *init_props;
+/* void *start_props;
+/*
+/* TLS_SESS_STATE *tls_proxy_context_receive(proxy_stream)
+/* VSTREAM *proxy_stream;
+/* AUXILIARY FUNCTIONS
+/* VSTREAM *tls_proxy_legacy_open(service, flags, peer_stream,
+/* peer_addr, peer_port,
+/* timeout, serverid)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int timeout;
+/* const char *serverid;
+/* DESCRIPTION
+/* tls_proxy_open() prepares for inserting the tlsproxy(8)
+/* daemon between the current process and a remote peer (the
+/* actual insert operation is described in the next paragraph).
+/* The result value is a null pointer on failure. The peer_stream
+/* is not closed. The resulting proxy stream is single-buffered.
+/*
+/* After this, it is a good idea to use the CA_VSTREAM_CTL_SWAP_FD
+/* request to swap the file descriptors between the plaintext
+/* peer_stream and the proxy stream from tls_proxy_open().
+/* This avoids the loss of application-configurable VSTREAM
+/* attributes on the plaintext peer_stream (such as longjmp
+/* buffer, timeout, etc.). Once the file descriptors are
+/* swapped, the proxy stream should be closed.
+/*
+/* tls_proxy_context_receive() receives the TLS context object
+/* for the named proxy stream. This function must be called
+/* only if the TLS_PROXY_SEND_CONTEXT flag was specified in
+/* the tls_proxy_open() call. Note that this TLS context object
+/* is not compatible with tls_session_free(). It must be given
+/* to tls_proxy_context_free() instead.
+/*
+/* After this, the proxy_stream is ready for plain-text I/O.
+/*
+/* tls_proxy_legacy_open() is a backwards-compatibility feature
+/* that provides a historical interface.
+/*
+/* Arguments:
+/* .IP service
+/* The (base) name of the tlsproxy service.
+/* .IP flags
+/* Bit-wise OR of:
+/* .RS
+/* .IP TLS_PROXY_FLAG_ROLE_SERVER
+/* Request the TLS server proxy role.
+/* .IP TLS_PROXY_FLAG_ROLE_CLIENT
+/* Request the TLS client proxy role.
+/* .IP TLS_PROXY_FLAG_SEND_CONTEXT
+/* Send the TLS context object.
+/* .RE
+/* .IP peer_stream
+/* Stream that connects the current process to a remote peer.
+/* .IP peer_addr
+/* Printable IP address of the remote peer_stream endpoint.
+/* .IP peer_port
+/* Printable TCP port of the remote peer_stream endpoint.
+/* .IP handshake_timeout
+/* Time limit that the tlsproxy(8) daemon should use during
+/* the TLS handshake.
+/* .IP session_timeout
+/* Time limit that the tlsproxy(8) daemon should use after the
+/* TLS handshake.
+/* .IP serverid
+/* Unique service identifier.
+/* .IP tls_params
+/* Pointer to TLS_CLIENT_PARAMS or TLS_SERVER_PARAMS.
+/* .IP init_props
+/* Pointer to TLS_CLIENT_INIT_PROPS or TLS_SERVER_INIT_PROPS.
+/* .IP start_props
+/* Pointer to TLS_CLIENT_START_PROPS or TLS_SERVER_START_PROPS.
+/* .IP proxy_stream
+/* Stream from tls_proxy_open().
+/* .IP tls_context
+/* TLS session object from tls_proxy_context_receive().
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <stringops.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+
+/* TLS library-specific. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define TLSPROXY_INIT_TIMEOUT 10
+
+/* SLMs. */
+
+#define STR vstring_str
+
+/* tls_proxy_open - open negotiations with TLS proxy */
+
+VSTREAM *tls_proxy_open(const char *service, int flags,
+ VSTREAM *peer_stream,
+ const char *peer_addr,
+ const char *peer_port,
+ int handshake_timeout,
+ int session_timeout,
+ const char *serverid,
+ void *tls_params,
+ void *init_props,
+ void *start_props)
+{
+ const char myname[] = "tls_proxy_open";
+ VSTREAM *tlsproxy_stream;
+ int status;
+ int fd;
+ static VSTRING *tlsproxy_service = 0;
+ static VSTRING *remote_endpt = 0;
+
+ /*
+ * Initialize.
+ */
+ if (tlsproxy_service == 0) {
+ tlsproxy_service = vstring_alloc(20);
+ remote_endpt = vstring_alloc(20);
+ }
+
+ /*
+ * Connect to the tlsproxy(8) daemon.
+ */
+ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service);
+ if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING,
+ TLSPROXY_INIT_TIMEOUT)) < 0) {
+ msg_warn("connect to %s service: %m", STR(tlsproxy_service));
+ return (0);
+ }
+
+ /*
+ * Initial handshake. Send common data attributes now, and send the
+ * remote peer file descriptor in a later transaction.
+ */
+ tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
+ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("error receiving %s service initial response",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port);
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, flags),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, handshake_timeout),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, session_timeout),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ ATTR_TYPE_END);
+ /* Do not flush the stream yet. */
+ if (vstream_ferror(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ switch (flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) {
+ case TLS_PROXY_FLAG_ROLE_CLIENT:
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_client_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_client_start_print, start_props),
+ ATTR_TYPE_END);
+ break;
+ case TLS_PROXY_FLAG_ROLE_SERVER:
+#if 0
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_server_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_server_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_server_start_print, start_props),
+ ATTR_TYPE_END);
+#endif
+ break;
+ default:
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+ }
+ if (vstream_fflush(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Receive the "TLS is available" indication.
+ *
+ * This may seem out of order, but we must have a read transaction between
+ * sending the request attributes and sending the plaintext file
+ * descriptor. We can't assume UNIX-domain socket semantics here.
+ */
+ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ /* TODO: informative message. */
+ ATTR_TYPE_END) != 1 || status == 0) {
+
+ /*
+ * The TLS proxy reports that the TLS engine is not available (due to
+ * configuration error, or other causes).
+ */
+ msg_warn("%s service role \"%s\" is not available",
+ STR(tlsproxy_service),
+ (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" :
+ (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" :
+ "bogus role");
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Send the remote peer file descriptor.
+ */
+ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
+ vstream_fileno(peer_stream)) < 0) {
+
+ /*
+ * Some error: drop the TLS proxy stream.
+ */
+ msg_warn("sending file handle to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ return (tlsproxy_stream);
+}
+
+
+/* tls_proxy_context_receive - receive TLS session object from tlsproxy(8) */
+
+TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream)
+{
+ TLS_SESS_STATE *tls_context = 0;
+
+ if (attr_scan(proxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context),
+ ATTR_TYPE_END) != 1) {
+ if (tls_context)
+ tls_proxy_context_free(tls_context);
+ return (0);
+ } else {
+ return (tls_context);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_print.c b/src/tls/tls_proxy_context_print.c
new file mode 100644
index 0000000..04123cb
--- /dev/null
+++ b/src/tls/tls_proxy_context_print.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* tls_proxy_context_print
+/* SUMMARY
+/* write TLS_ATTR_STATE structure to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/* DESCRIPTION
+/* tls_proxy_context_print() writes the public members of a
+/* TLS_ATTR_STATE structure to the named stream using the
+/* specified attribute print routine. tls_proxy_context_print()
+/* is meant to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_context_print, (const void *) tls_context), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_print - send TLS session state over stream */
+
+int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SESS_STATE *tp = (const TLS_SESS_STATE *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_PEER_CN,
+ STRING_OR_EMPTY(tp->peer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_ISSUER_CN,
+ STRING_OR_EMPTY(tp->issuer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_CERT_FPT,
+ STRING_OR_EMPTY(tp->peer_cert_fprint)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT,
+ STRING_OR_EMPTY(tp->peer_pkey_fprint)),
+ SEND_ATTR_INT(TLS_ATTR_SEC_LEVEL,
+ tp->level),
+ SEND_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ tp->peer_status),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL,
+ STRING_OR_EMPTY(tp->protocol)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_NAME,
+ STRING_OR_EMPTY(tp->cipher_name)),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ tp->cipher_usebits),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ tp->cipher_algbits),
+ SEND_ATTR_STR(TLS_ATTR_KEX_NAME,
+ STRING_OR_EMPTY(tp->kex_name)),
+ SEND_ATTR_STR(TLS_ATTR_KEX_CURVE,
+ STRING_OR_EMPTY(tp->kex_curve)),
+ SEND_ATTR_INT(TLS_ATTR_KEX_BITS,
+ tp->kex_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME,
+ STRING_OR_EMPTY(tp->clnt_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE,
+ STRING_OR_EMPTY(tp->clnt_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS,
+ tp->clnt_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST,
+ STRING_OR_EMPTY(tp->clnt_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME,
+ STRING_OR_EMPTY(tp->srvr_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE,
+ STRING_OR_EMPTY(tp->srvr_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS,
+ tp->srvr_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST,
+ STRING_OR_EMPTY(tp->srvr_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(tp->namaddr)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_scan.c b/src/tls/tls_proxy_context_scan.c
new file mode 100644
index 0000000..1d463ad
--- /dev/null
+++ b/src/tls/tls_proxy_context_scan.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* tls_proxy_context_scan
+/* SUMMARY
+/* read TLS session state from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_context_free(tls_context)
+/* TLS_SESS_STATE *tls_context;
+/* DESCRIPTION
+/* tls_proxy_context_scan() reads the public members of a
+/* TLS_ATTR_STATE structure from the named stream using the
+/* specified attribute scan routine. tls_proxy_context_scan()
+/* is meant to be passed as a call-back to attr_scan() as shown
+/* below.
+/*
+/* tls_proxy_context_free() destroys a TLS context object that
+/* was received with tls_proxy_context_scan().
+/*
+/* TLS_ATTR_STATE *tls_context = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ...
+/* ...
+/* if (tls_context)
+/* tls_proxy_context_free(tls_context);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_scan - receive TLS session state from stream */
+
+int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SESS_STATE *tls_context
+ = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));;
+ int ret;
+ VSTRING *peer_CN = vstring_alloc(25);
+ VSTRING *issuer_CN = vstring_alloc(25);
+ VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *protocol = vstring_alloc(25);
+ VSTRING *cipher_name = vstring_alloc(25);
+ VSTRING *kex_name = vstring_alloc(25);
+ VSTRING *kex_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_name = vstring_alloc(25);
+ VSTRING *clnt_sig_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_dgst = vstring_alloc(25);
+ VSTRING *srvr_sig_name = vstring_alloc(25);
+ VSTRING *srvr_sig_curve = vstring_alloc(25);
+ VSTRING *srvr_sig_dgst = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(100);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_context_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(tls_context, 0, sizeof(*tls_context));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_PEER_CN, peer_CN),
+ RECV_ATTR_STR(TLS_ATTR_ISSUER_CN, issuer_CN),
+ RECV_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, peer_cert_fprint),
+ RECV_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, peer_pkey_fprint),
+ RECV_ATTR_INT(TLS_ATTR_SEC_LEVEL,
+ &tls_context->level),
+ RECV_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ &tls_context->peer_status),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, protocol),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_NAME, cipher_name),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ &tls_context->cipher_usebits),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ &tls_context->cipher_algbits),
+ RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name),
+ RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve),
+ RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST, clnt_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME, srvr_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE, srvr_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS, &tls_context->srvr_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST, srvr_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ tls_context->peer_CN = vstring_export(peer_CN);
+ tls_context->issuer_CN = vstring_export(issuer_CN);
+ tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint);
+ tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint);
+ tls_context->protocol = vstring_export(protocol);
+ tls_context->cipher_name = vstring_export(cipher_name);
+ tls_context->kex_name = vstring_export(kex_name);
+ tls_context->kex_curve = vstring_export(kex_curve);
+ tls_context->clnt_sig_name = vstring_export(clnt_sig_name);
+ tls_context->clnt_sig_curve = vstring_export(clnt_sig_curve);
+ tls_context->clnt_sig_dgst = vstring_export(clnt_sig_dgst);
+ tls_context->srvr_sig_name = vstring_export(srvr_sig_name);
+ tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve);
+ tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst);
+ tls_context->namaddr = vstring_export(namaddr);
+ ret = (ret == 22 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_context_free(tls_context);
+ tls_context = 0;
+ }
+ *(TLS_SESS_STATE **) ptr = tls_context;
+ if (msg_verbose)
+ msg_info("tls_proxy_context_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */
+
+void tls_proxy_context_free(TLS_SESS_STATE *tls_context)
+{
+ if (tls_context->peer_CN)
+ myfree(tls_context->peer_CN);
+ if (tls_context->issuer_CN)
+ myfree(tls_context->issuer_CN);
+ if (tls_context->peer_cert_fprint)
+ myfree(tls_context->peer_cert_fprint);
+ if (tls_context->peer_pkey_fprint)
+ myfree(tls_context->peer_pkey_fprint);
+ if (tls_context->protocol)
+ myfree((void *) tls_context->protocol);
+ if (tls_context->cipher_name)
+ myfree((void *) tls_context->cipher_name);
+ if (tls_context->kex_name)
+ myfree((void *) tls_context->kex_name);
+ if (tls_context->kex_curve)
+ myfree((void *) tls_context->kex_curve);
+ if (tls_context->clnt_sig_name)
+ myfree((void *) tls_context->clnt_sig_name);
+ if (tls_context->clnt_sig_curve)
+ myfree((void *) tls_context->clnt_sig_curve);
+ if (tls_context->clnt_sig_dgst)
+ myfree((void *) tls_context->clnt_sig_dgst);
+ if (tls_context->srvr_sig_name)
+ myfree((void *) tls_context->srvr_sig_name);
+ if (tls_context->srvr_sig_curve)
+ myfree((void *) tls_context->srvr_sig_curve);
+ if (tls_context->srvr_sig_dgst)
+ myfree((void *) tls_context->srvr_sig_dgst);
+ if (tls_context->namaddr)
+ myfree((void *) tls_context->namaddr);
+ myfree((void *) tls_context);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_print.c b/src/tls/tls_proxy_server_print.c
new file mode 100644
index 0000000..8d51422
--- /dev/null
+++ b/src/tls/tls_proxy_server_print.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* tls_proxy_server_print 3
+/* SUMMARY
+/* write TLS_SERVER_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int tls_proxy_server_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* tls_proxy_server_init_print() writes a TLS_SERVER_INIT_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_init_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_init_print, (const void *) init_props), ...
+/*
+/* tls_proxy_server_start_print() writes a TLS_SERVER_START_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_start_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_start_print, (const void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_print - send TLS_SERVER_INIT_PROPS over stream */
+
+int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SERVER_INIT_PROPS *props = (const TLS_SERVER_INIT_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_INT(TLS_ATTR_SET_SESSID, props->set_sessid),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_EECDH_GRADE,
+ STRING_OR_EMPTY(props->eecdh_grade)),
+ SEND_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh1024_param_file)),
+ SEND_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh512_param_file)),
+ SEND_ATTR_INT(TLS_ATTR_ASK_CCERT, props->ask_ccert),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+/* tls_proxy_server_start_print - send TLS_SERVER_START_PROPS over stream */
+
+int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SERVER_START_PROPS *props = (const TLS_SERVER_START_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_REQUIRECERT, props->requirecert),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_scan.c b/src/tls/tls_proxy_server_scan.c
new file mode 100644
index 0000000..92da66c
--- /dev/null
+++ b/src/tls/tls_proxy_server_scan.c
@@ -0,0 +1,245 @@
+/*++
+/* NAME
+/* tls_proxy_server_scan 3
+/* SUMMARY
+/* read TLS_SERVER_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* tls_proxy_server_init_free(init_props)
+/* TLS_SERVER_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_server_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_server_start_free(start_props)
+/* TLS_SERVER_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_server_init_scan() reads a TLS_SERVER_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_init_free() destroys a TLS_SERVER_INIT_PROPS
+/* structure that was created by tls_proxy_server_init_scan().
+/*
+/* TLS_SERVER_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_server_start_scan() reads a TLS_SERVER_START_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_start_free() destroys a TLS_SERVER_START_PROPS
+/* structure that was created by tls_proxy_server_start_scan().
+/*
+/* TLS_SERVER_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props)
+/* tls_proxy_server_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_scan - receive TLS_SERVER_INIT_PROPS from stream */
+
+int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_INIT_PROPS *props
+ = (TLS_SERVER_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *eecdh_grade = vstring_alloc(25);
+ VSTRING *dh1024_param_file = vstring_alloc(25);
+ VSTRING *dh512_param_file = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_INT(TLS_ATTR_SET_SESSID, &props->set_sessid),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_EECDH_GRADE, eecdh_grade),
+ RECV_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, dh1024_param_file),
+ RECV_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, dh512_param_file),
+ RECV_ATTR_INT(TLS_ATTR_ASK_CCERT, &props->ask_ccert),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->protocols = vstring_export(protocols);
+ props->eecdh_grade = vstring_export(eecdh_grade);
+ props->dh1024_param_file = vstring_export(dh1024_param_file);
+ props->dh512_param_file = vstring_export(dh512_param_file);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_init_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_INIT_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_init_free - destroy TLS_SERVER_INIT_PROPS structure */
+
+void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->protocols);
+ myfree((void *) props->eecdh_grade);
+ myfree((void *) props->dh1024_param_file);
+ myfree((void *) props->dh512_param_file);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_server_start_scan - receive TLS_SERVER_START_PROPS from stream */
+
+int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_START_PROPS *props
+ = (TLS_SERVER_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ /* XXX Caller sets fd. */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_REQUIRECERT, &props->requirecert),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ props->serverid = vstring_export(serverid);
+ props->namaddr = vstring_export(namaddr);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 7 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_start_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_START_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_start_free - destroy TLS_SERVER_START_PROPS structure */
+
+void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props)
+{
+ /* XXX Caller closes fd. */
+ myfree((void *) props->serverid);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+#endif
diff --git a/src/tls/tls_rsa.c b/src/tls/tls_rsa.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tls/tls_rsa.c
diff --git a/src/tls/tls_scache.c b/src/tls/tls_scache.c
new file mode 100644
index 0000000..cf722ee
--- /dev/null
+++ b/src/tls/tls_scache.c
@@ -0,0 +1,591 @@
+/*++
+/* NAME
+/* tls_scache 3
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/*
+/* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout)
+/* const char *dbname
+/* const char *cache_label;
+/* int verbose;
+/* int timeout;
+/*
+/* void tls_scache_close(cache)
+/* TLS_SCACHE *cache;
+/*
+/* int tls_scache_lookup(cache, cache_id, out_session)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_update(cache, cache_id, session, session_len)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* const char *session;
+/* ssize_t session_len;
+/*
+/* int tls_scache_sequence(cache, first_next, out_cache_id,
+/* VSTRING *out_session)
+/* TLS_SCACHE *cache;
+/* int first_next;
+/* char **out_cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_delete(cache, cache_id)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_scache_key(keyname, now, timeout)
+/* unsigned char *keyname;
+/* time_t now;
+/* int timeout;
+/*
+/* TLS_TICKET_KEY *tls_scache_key_rotate(newkey)
+/* TLS_TICKET_KEY *newkey;
+/* DESCRIPTION
+/* This module maintains Postfix TLS session cache files.
+/* each session is stored under a lookup key (hostname or
+/* session ID).
+/*
+/* tls_scache_open() opens the specified TLS session cache
+/* and returns a handle that must be used for subsequent
+/* access.
+/*
+/* tls_scache_close() closes the specified TLS session cache
+/* and releases memory that was allocated by tls_scache_open().
+/*
+/* tls_scache_lookup() looks up the specified session in the
+/* specified cache, and applies session timeout restrictions.
+/* Entries that are too old are silently deleted.
+/*
+/* tls_scache_update() updates the specified TLS session cache
+/* with the specified session information.
+/*
+/* tls_scache_sequence() iterates over the specified TLS session
+/* cache and either returns the first or next entry that has not
+/* timed out, or returns no data. Entries that are too old are
+/* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the
+/* third and last argument to disable saving of cache entry
+/* content or cache entry ID information. This is useful when
+/* purging expired entries. A result value of zero means that
+/* the end of the cache was reached.
+/*
+/* tls_scache_delete() removes the specified cache entry from
+/* the specified TLS session cache.
+/*
+/* tls_scache_key() locates a TLS session ticket key in a 2-element
+/* in-memory cache. A null result is returned if no unexpired matching
+/* key is found.
+/*
+/* tls_scache_key_rotate() saves a TLS session tickets key in the
+/* in-memory cache.
+/*
+/* Arguments:
+/* .IP dbname
+/* The base name of the session cache file.
+/* .IP cache_label
+/* A string that is used in logging and error messages.
+/* .IP verbose
+/* Do verbose logging of cache operations? (zero == no)
+/* .IP timeout
+/* The time after which a session cache entry is considered too old.
+/* .IP first_next
+/* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT
+/* (next cache element).
+/* .IP cache_id
+/* Session cache lookup key.
+/* .IP session
+/* Storage for session information.
+/* .IP session_len
+/* The size of the session information in bytes.
+/* .IP out_cache_id
+/* .IP out_session
+/* Storage for saving the cache_id or session information of the
+/* current cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving
+/* the session cache ID of the cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid
+/* saving the session information in the cache entry.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket.
+/* .IP now
+/* Current epoch time passed by caller.
+/* .IP timeout
+/* TLS session ticket encryption lifetime.
+/* .IP newkey
+/* TLS session ticket key obtained from tlsmgr(8) to be added to
+ * internal cache.
+/* DIAGNOSTICS
+/* These routines terminate with a fatal run-time error
+/* for unrecoverable database errors. This allows the
+/* program to restart and reset the database to an
+/* empty initial state.
+/*
+/* tls_scache_open() never returns on failure. All other
+/* functions return non-zero on success, zero when the
+/* operation could not be completed.
+/* 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>
+
+#ifdef USE_TLS
+
+#include <string.h>
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <hex_code.h>
+#include <myflock.h>
+#include <vstring.h>
+#include <timecmp.h>
+
+/* Global library. */
+
+/* TLS library. */
+
+#include <tls_scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Session cache entry format.
+ */
+typedef struct {
+ time_t timestamp; /* time when saved */
+ char session[1]; /* actually a bunch of bytes */
+} TLS_SCACHE_ENTRY;
+
+static TLS_TICKET_KEY *keys[2];
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_scache_encode - encode TLS session cache entry */
+
+static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id,
+ const char *session,
+ ssize_t session_len)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *hex_data;
+ ssize_t binary_data_len;
+
+ /*
+ * Assemble the TLS session cache entry.
+ *
+ * We could eliminate some copying by using incremental encoding, but
+ * sessions are so small that it really does not matter.
+ */
+ binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session);
+ entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len);
+ entry->timestamp = time((time_t *) 0);
+ memcpy(entry->session, session, session_len);
+
+ /*
+ * Encode the TLS session cache entry.
+ */
+ hex_data = vstring_alloc(2 * binary_data_len + 1);
+ hex_encode(hex_data, (char *) entry, binary_data_len);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) session_len);
+
+ /*
+ * Clean up.
+ */
+ myfree((void *) entry);
+
+ return (hex_data);
+}
+
+/* tls_scache_decode - decode TLS session cache entry */
+
+static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id,
+ const char *hex_data, ssize_t hex_data_len,
+ VSTRING *out_session)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *bin_data;
+
+ /*
+ * Sanity check.
+ */
+ if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) {
+ msg_warn("%s TLS cache: truncated entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ return (0);
+ }
+
+ /*
+ * Disassemble the TLS session cache entry.
+ *
+ * No early returns or we have a memory leak.
+ */
+#define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); }
+
+ bin_data = vstring_alloc(hex_data_len / 2 + 1);
+ if (hex_decode_opt(bin_data, hex_data, hex_data_len,
+ HEX_DECODE_FLAG_ALLOW_COLON) == 0) {
+ msg_warn("%s TLS cache: malformed entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ FREE_AND_RETURN(bin_data, 0);
+ }
+ entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)));
+
+ /*
+ * Other mandatory restrictions.
+ */
+ if (entry->timestamp + cp->timeout < time((time_t *) 0))
+ FREE_AND_RETURN(bin_data, 0);
+
+ /*
+ * Optional output.
+ */
+ if (out_session != 0)
+ vstring_memcpy(out_session, entry->session,
+ LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
+
+ /*
+ * Clean up.
+ */
+ FREE_AND_RETURN(bin_data, 1);
+}
+
+/* tls_scache_lookup - load session from cache */
+
+int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
+ VSTRING *session)
+{
+ const char *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Initialize. Don't leak data.
+ */
+ if (session)
+ VSTRING_RESET(session);
+
+ /*
+ * Search the cache database.
+ */
+ if ((hex_data = dict_get(cp->db, cache_id)) == 0)
+ return (0);
+
+ /*
+ * Decode entry and delete if expired or malformed.
+ */
+ if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
+ session) == 0) {
+ tls_scache_delete(cp, cache_id);
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+/* tls_scache_update - save session to cache */
+
+int tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ VSTRING *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("put %s session id=%s [data %ld bytes]",
+ cp->cache_label, cache_id, (long) len);
+
+ /*
+ * Encode the cache entry.
+ */
+ hex_data = tls_scache_encode(cp, cache_id, buf, len);
+
+ /*
+ * Store the cache entry.
+ *
+ * XXX Berkeley DB supports huge database keys and values. SDBM seems to
+ * have a finite limit, and DBM simply can't be used at all.
+ */
+ dict_put(cp->db, cache_id, STR(hex_data));
+
+ /*
+ * Clean up.
+ */
+ vstring_free(hex_data);
+
+ return (1);
+}
+
+/* tls_scache_sequence - get first/next TLS session cache entry */
+
+int tls_scache_sequence(TLS_SCACHE *cp, int first_next,
+ char **out_cache_id,
+ VSTRING *out_session)
+{
+ const char *member;
+ const char *value;
+ char *saved_cursor;
+ int found_entry;
+ int keep_entry;
+ char *saved_member;
+
+ /*
+ * XXX Deleting entries while enumerating a map can he tricky. Some map
+ * types have a concept of cursor and support a "delete the current
+ * element" operation. Some map types without cursors don't behave well
+ * when the current first/next entry is deleted (example: with Berkeley
+ * DB < 2, the "next" operation produces garbage). To avoid trouble, we
+ * delete an expired entry after advancing the current first/next
+ * position beyond it, and ignore client requests to delete the current
+ * entry.
+ */
+
+ /*
+ * Find the first or next database entry. Activate the passivated entry
+ * and check the time stamp. Schedule the entry for deletion if it is too
+ * old.
+ *
+ * Save the member (cache id) so that it will not be clobbered by the
+ * tls_scache_lookup() call below.
+ */
+ found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
+ if (found_entry) {
+ keep_entry = tls_scache_decode(cp, member, value, strlen(value),
+ out_session);
+ if (keep_entry && out_cache_id)
+ *out_cache_id = mystrdup(member);
+ saved_member = mystrdup(member);
+ }
+
+ /*
+ * Delete behind. This is a no-op if an expired cache entry was updated
+ * in the mean time. Use the saved lookup criteria so that the "delete
+ * behind" operation works as promised.
+ *
+ * The delete-behind strategy assumes that all updates are made by a single
+ * process. Otherwise, delete-behind may remove an entry that was updated
+ * after it was scheduled for deletion.
+ */
+ if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) {
+ cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ saved_cursor = cp->saved_cursor;
+ cp->saved_cursor = 0;
+ tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
+ myfree(saved_cursor);
+ }
+
+ /*
+ * Otherwise, clean up if this is not the first iteration.
+ */
+ else {
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ cp->saved_cursor = 0;
+ }
+
+ /*
+ * Protect the current first/next entry against explicit or implied
+ * client delete requests, and schedule a bad or expired entry for
+ * deletion. Save the lookup criteria so that the "delete behind"
+ * operation will work as promised.
+ */
+ if (found_entry) {
+ cp->saved_cursor = saved_member;
+ if (keep_entry == 0)
+ cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ }
+ return (found_entry);
+}
+
+/* tls_scache_delete - delete session from cache */
+
+int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("delete %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Do it, unless we would delete the current first/next entry. Some map
+ * types don't have cursors, and some of those don't behave when the
+ * "current" entry is deleted.
+ */
+ return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0)
+ || dict_del(cp->db, cache_id) == 0);
+}
+
+/* tls_scache_open - open TLS session cache file */
+
+TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label,
+ int verbose, int timeout)
+{
+ TLS_SCACHE *cp;
+ DICT *dict;
+
+ /*
+ * Logging.
+ */
+ if (verbose)
+ msg_info("open %s TLS cache %s", cache_label, dbname);
+
+ /*
+ * Open the dictionary with O_TRUNC, so that we never have to worry about
+ * opening a damaged file after some process terminated abnormally.
+ */
+#define DICT_FLAGS \
+ (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK | DICT_FLAG_SYNC_UPDATE \
+ | DICT_FLAG_UTF8_REQUEST)
+
+ dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS);
+
+ /*
+ * Sanity checks.
+ */
+ if (dict->update == 0)
+ msg_fatal("dictionary %s does not support update operations", dbname);
+ if (dict->delete == 0)
+ msg_fatal("dictionary %s does not support delete operations", dbname);
+ if (dict->sequence == 0)
+ msg_fatal("dictionary %s does not support sequence operations", dbname);
+
+ /*
+ * Create the TLS_SCACHE object.
+ */
+ cp = (TLS_SCACHE *) mymalloc(sizeof(*cp));
+ cp->flags = 0;
+ cp->db = dict;
+ cp->cache_label = mystrdup(cache_label);
+ cp->verbose = verbose;
+ cp->timeout = timeout;
+ cp->saved_cursor = 0;
+
+ return (cp);
+}
+
+/* tls_scache_close - close TLS session cache file */
+
+void tls_scache_close(TLS_SCACHE *cp)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name);
+
+ /*
+ * Destroy the TLS_SCACHE object.
+ */
+ dict_close(cp->db);
+ myfree(cp->cache_label);
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ myfree((void *) cp);
+}
+
+/* tls_scache_key - find session ticket key for given key name */
+
+TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout)
+{
+ int i;
+
+ /*
+ * The keys array contains 2 elements, the current signing key and the
+ * previous key.
+ *
+ * When name == 0 we are issuing a ticket, otherwise decrypting an existing
+ * ticket with the given key name. For new tickets we always use the
+ * current key if unexpired. For existing tickets, we use either the
+ * current or previous key with a validation expiration that is timeout
+ * longer than the signing expiration.
+ */
+ if (keyname) {
+ for (i = 0; i < 2 && keys[i]; ++i) {
+ if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) {
+ if (timecmp(keys[i]->tout + timeout, now) > 0)
+ return (keys[i]);
+ break;
+ }
+ }
+ } else if (keys[0]) {
+ if (timecmp(keys[0]->tout, now) > 0)
+ return (keys[0]);
+ }
+ return (0);
+}
+
+/* tls_scache_key_rotate - rotate session ticket keys */
+
+TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey)
+{
+
+ /*
+ * Allocate or re-use storage of retired key, then overwrite it, since
+ * caller's key data is ephemeral.
+ */
+ if (keys[1] == 0)
+ keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey));
+ *keys[1] = *newkey;
+ newkey = keys[1];
+
+ /*
+ * Rotate if required, ensuring that the keys are sorted by expiration
+ * time with keys[0] expiring last.
+ */
+ if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) {
+ keys[1] = keys[0];
+ keys[0] = newkey;
+ }
+ return (newkey);
+}
+
+#endif
diff --git a/src/tls/tls_scache.h b/src/tls/tls_scache.h
new file mode 100644
index 0000000..06c727a
--- /dev/null
+++ b/src/tls/tls_scache.h
@@ -0,0 +1,73 @@
+#ifndef _TLS_SCACHE_H_INCLUDED_
+#define _TLS_SCACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_scache 3h
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int flags; /* see below */
+ DICT *db; /* database handle */
+ char *cache_label; /* "smtpd", "smtp" or "lmtp" */
+ int verbose; /* enable verbose logging */
+ int timeout; /* smtp(d)_tls_session_cache_timeout */
+ char *saved_cursor; /* cursor cache ID */
+} TLS_SCACHE;
+
+#define TLS_TICKET_NAMELEN 16 /* RFC 5077 ticket key name length */
+#define TLS_TICKET_IVLEN 16 /* RFC 5077 ticket IV length */
+#define TLS_TICKET_KEYLEN 32 /* AES-256-CBC key size */
+#define TLS_TICKET_MACLEN 32 /* RFC 5077 HMAC key size */
+#define TLS_SESSION_LIFEMIN 120 /* May you live to 120! */
+
+typedef struct TLS_TICKET_KEY {
+ unsigned char name[TLS_TICKET_NAMELEN];
+ unsigned char bits[TLS_TICKET_KEYLEN];
+ unsigned char hmac[TLS_TICKET_MACLEN];
+ time_t tout;
+} TLS_TICKET_KEY;
+
+#define TLS_SCACHE_FLAG_DEL_SAVED_CURSOR (1<<0)
+
+extern TLS_SCACHE *tls_scache_open(const char *, const char *, int, int);
+extern void tls_scache_close(TLS_SCACHE *);
+extern int tls_scache_lookup(TLS_SCACHE *, const char *, VSTRING *);
+extern int tls_scache_update(TLS_SCACHE *, const char *, const char *, ssize_t);
+extern int tls_scache_delete(TLS_SCACHE *, const char *);
+extern int tls_scache_sequence(TLS_SCACHE *, int, char **, VSTRING *);
+extern TLS_TICKET_KEY *tls_scache_key(unsigned char *, time_t, int);
+extern TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *);
+
+#define TLS_SCACHE_DONT_NEED_CACHE_ID ((char **) 0)
+#define TLS_SCACHE_DONT_NEED_SESSION ((VSTRING *) 0)
+
+#define TLS_SCACHE_SEQUENCE_NOTHING \
+ TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_SESSION
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_seed.c b/src/tls/tls_seed.c
new file mode 100644
index 0000000..edb7cd9
--- /dev/null
+++ b/src/tls/tls_seed.c
@@ -0,0 +1,88 @@
+/*++
+/* NAME
+/* tls_seed 3
+/* SUMMARY
+/* TLS PRNG seeding routines
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_ext_seed(nbytes)
+/* int nbytes;
+/*
+/* void tls_int_seed()
+/* DESCRIPTION
+/* tls_ext_seed() requests the specified number of bytes
+/* from the tlsmgr(8) PRNG pool and updates the local PRNG.
+/* The result is zero in case of success, -1 otherwise.
+/*
+/* tls_int_seed() mixes the process ID and time of day into
+/* the PRNG pool. This adds a few bits of entropy with each
+/* call, provided that the calls aren't made frequently.
+/* 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/time.h> /* gettimeofday() */
+#include <unistd.h> /* getpid() */
+
+#ifdef USE_TLS
+
+/* OpenSSL library. */
+
+#include <openssl/rand.h> /* RAND_seed() */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+/* tls_int_seed - add entropy to the pool by adding the time and PID */
+
+void tls_int_seed(void)
+{
+ static struct {
+ pid_t pid;
+ struct timeval tv;
+ } randseed;
+
+ if (randseed.pid == 0)
+ randseed.pid = getpid();
+ GETTIMEOFDAY(&randseed.tv);
+ RAND_seed(&randseed, sizeof(randseed));
+}
+
+/* tls_ext_seed - request entropy from tlsmgr(8) server */
+
+int tls_ext_seed(int nbytes)
+{
+ VSTRING *buf;
+ int status;
+
+ buf = vstring_alloc(nbytes);
+ status = tls_mgr_seed(buf, nbytes);
+ RAND_seed(vstring_str(buf), VSTRING_LEN(buf));
+ vstring_free(buf);
+ return (status == TLS_MGR_STAT_OK ? 0 : -1);
+}
+
+#endif
diff --git a/src/tls/tls_server.c b/src/tls/tls_server.c
new file mode 100644
index 0000000..fb76f06
--- /dev/null
+++ b/src/tls/tls_server.c
@@ -0,0 +1,1051 @@
+/*++
+/* NAME
+/* tls_server 3
+/* SUMMARY
+/* server-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_server_init(props)
+/* const TLS_SERVER_INIT_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_start(props)
+/* const TLS_SERVER_START_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_post_accept(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_server_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS servers,
+/* the OpenSSL library, and the TLS entropy and cache manager.
+/*
+/* See "EVENT_DRIVEN APPLICATIONS" below for using this code
+/* in event-driven programs.
+/*
+/* tls_server_init() is called once when the SMTP server
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so that peer-specific behavior is not possible.
+/*
+/* tls_server_start() activates the TLS feature for the VSTREAM
+/* passed as argument. We assume that network buffers are flushed
+/* and the TLS handshake can begin immediately.
+/*
+/* tls_server_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. One or more of TLS_CERT_FLAG_PRESENT and
+/* TLS_CERT_FLAG_TRUSTED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* Fingerprint of the certificate, or zero-length string when no peer
+/* certificate is available.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the current TLS library provides,
+/* including tls_server_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that the application is responsible for calling and retrying
+/* SSL_accept(), SSL_read(), SSL_write() and SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven server
+/* invokes tls_server_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_server_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_accept(), and if successful,
+/* for invoking tls_server_post_accept() to finish the work
+/* that was started by tls_server_start(). In case of unrecoverable
+/* failure, tls_server_post_accept() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <dict.h>
+#include <stringops.h>
+#include <msg.h>
+#include <hex_code.h>
+#include <iostuff.h> /* non-blocking */
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+#if OPENSSL_VERSION_PREREQ(3,0)
+#include <openssl/core_names.h> /* EVP_MAC parameters */
+#endif
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* Application-specific. */
+
+ /*
+ * The session_id_context identifies the service that created a session.
+ * This information is used to distinguish between multiple TLS-based
+ * servers running on the same server. We use the name of the mail system.
+ */
+static const char server_session_id_context[] = "Postfix/TLS";
+
+#ifndef OPENSSL_NO_TLSEXT
+ /*
+ * We retain the cipher handle for the lifetime of the process.
+ */
+static const EVP_CIPHER *tkt_cipher;
+#endif
+
+#define GET_SID(s, v, lptr) ((v) = SSL_SESSION_get_id((s), (lptr)))
+
+typedef const unsigned char *session_id_t;
+
+/* get_server_session_cb - callback to retrieve session from server cache */
+
+static SSL_SESSION *get_server_session_cb(SSL *ssl, session_id_t session_id,
+ int session_id_length,
+ int *unused_copy)
+{
+ const char *myname = "get_server_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *cache_id;
+ VSTRING *session_data = vstring_alloc(2048);
+ SSL_SESSION *session = 0;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in session lookup callback", myname);
+
+#define GEN_CACHE_ID(buf, id, len, service) \
+ do { \
+ buf = vstring_alloc(2 * (len + strlen(service))); \
+ hex_encode(buf, (char *) (id), (len)); \
+ vstring_sprintf_append(buf, "&s=%s", (service)); \
+ vstring_sprintf_append(buf, "&l=%ld", (long) OpenSSL_version_num()); \
+ } while (0)
+
+
+ GEN_CACHE_ID(cache_id, session_id, session_id_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: looking up session %s in %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Load the session from cache and decode it.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, STR(cache_id),
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session && (TLScontext->log_mask & TLS_LOG_CACHE))
+ msg_info("%s: reloaded session %s from %s cache",
+ TLScontext->namaddr, STR(cache_id),
+ TLScontext->cache_type);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(cache_id);
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* uncache_session - remove session from internal & external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ VSTRING *cache_id;
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ SSL_CTX_remove_session(ctx, session);
+
+ if (TLScontext->cache_type == 0)
+ return;
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: remove session %s from %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ tls_mgr_delete(TLScontext->cache_type, STR(cache_id));
+ vstring_free(cache_id);
+}
+
+/* new_server_session_cb - callback to save session to server cache */
+
+static int new_server_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_server_session_cb";
+ VSTRING *cache_id;
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: save session %s to %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session state.
+ */
+ session_data = tls_session_passivate(session);
+ if (session_data)
+ tls_mgr_update(TLScontext->cache_type, STR(cache_id),
+ STR(session_data), LEN(session_data));
+
+ /*
+ * Clean up.
+ */
+ if (session_data)
+ vstring_free(session_data);
+ vstring_free(cache_id);
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+#define NOENGINE ((ENGINE *) 0)
+#define TLS_TKT_NOKEYS -1 /* No keys for encryption */
+#define TLS_TKT_STALE 0 /* No matching keys for decryption */
+#define TLS_TKT_ACCEPT 1 /* Ticket decryptable and re-usable */
+#define TLS_TKT_REISSUE 2 /* Ticket decryptable, not re-usable */
+
+#if !defined(OPENSSL_NO_TLSEXT)
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+
+/* ticket_cb - configure tls session ticket encrypt/decrypt context */
+
+static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[],
+ EVP_CIPHER_CTX *ctx, EVP_MAC_CTX *hctx, int create)
+{
+ OSSL_PARAM params[3];
+ TLS_TICKET_KEY *key;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
+
+ if ((key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ LN_sha256, 0);
+ params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY,
+ (char *) key->hmac,
+ TLS_TICKET_MACLEN);
+ params[2] = OSSL_PARAM_construct_end();
+ if (!EVP_MAC_CTX_set_params(hctx, params))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ if (create) {
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Issuing session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ } else {
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Decrypting session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ }
+ TLScontext->ticketed = 1;
+ return (TLS_TKT_ACCEPT);
+}
+
+#else /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ticket_cb - configure tls session ticket encrypt/decrypt context */
+
+static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[],
+ EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int create)
+{
+ static const EVP_MD *sha256;
+ TLS_TICKET_KEY *key;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
+
+ if ((!sha256 && (sha256 = EVP_sha256()) == 0)
+ || (key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ HMAC_Init_ex(hctx, key->hmac, TLS_TICKET_MACLEN, sha256, NOENGINE);
+
+ if (create) {
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Issuing session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ } else {
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Decrypting session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ }
+ TLScontext->ticketed = 1;
+ return (TLS_TKT_ACCEPT);
+}
+
+#endif /* OPENSSL_VERSION_PREREQ(3,0) */
+
+#endif /* defined(SSL_OP_NO_TICKET) &&
+ * !defined(OPENSSL_NO_TLSEXT) */
+
+/* tls_server_init - initialize the server-side TLS engine */
+
+TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
+{
+ SSL_CTX *server_ctx;
+ SSL_CTX *sni_ctx;
+ X509_STORE *cert_store;
+ long off = 0;
+ int verify_flags = SSL_VERIFY_NONE;
+ int cachable;
+ int scache_timeout;
+ int ticketable = 0;
+ int protomask;
+ int min_proto;
+ int max_proto;
+ TLS_APPL_STATE *app_ctx;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the server-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * First validate the protocols. If these are invalid, we can't continue.
+ */
+ protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("Invalid TLS protocol list \"%s\": disabling TLS support",
+ props->protocols);
+ return (0);
+ }
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if (!tls_validate_digest(props->mdalg)) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. Netscape communicator can still
+ * communicate with SSLv2 servers, so it sends out a SSLv2 client hello.
+ * To deal with it, our server must be SSLv2 aware (even if we don't like
+ * SSLv2), so we need to have the SSLv23 server here. If we want to limit
+ * the protocol level, we can add an option to not use SSLv2/v3/TLSv1
+ * later.
+ */
+ ERR_clear_error();
+ server_ctx = SSL_CTX_new(TLS_server_method());
+ if (server_ctx == 0) {
+ msg_warn("cannot allocate server SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+ sni_ctx = SSL_CTX_new(TLS_server_method());
+ if (sni_ctx == 0) {
+ SSL_CTX_free(server_ctx);
+ msg_warn("cannot allocate server SNI SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(server_ctx, 0);
+ SSL_CTX_set_security_level(sni_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1);
+ SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1);
+
+ /*
+ * The session cache is implemented by the tlsmgr(8) server.
+ *
+ * XXX 200502 Surprise: when OpenSSL purges an entry from the in-memory
+ * cache, it also attempts to purge the entry from the on-disk cache.
+ * This is undesirable, especially when we set the in-memory cache size
+ * to 1. For this reason we don't allow OpenSSL to purge on-disk cache
+ * entries, and leave it up to the tlsmgr process instead. Found by
+ * Victor Duchovni.
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol work-arounds, OpenSSL version dependent.
+ */
+ off |= tls_bug_bits();
+
+ /*
+ * Add SSL_OP_NO_TICKET when the timeout is zero or library support is
+ * incomplete.
+ */
+#ifndef OPENSSL_NO_TLSEXT
+ ticketable = (*var_tls_tkt_cipher && scache_timeout > 0
+ && !(off & SSL_OP_NO_TICKET));
+ if (ticketable) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ tkt_cipher = EVP_CIPHER_fetch(NULL, var_tls_tkt_cipher, NULL);
+#else
+ tkt_cipher = EVP_get_cipherbyname(var_tls_tkt_cipher);
+#endif
+ if (tkt_cipher == 0
+ || EVP_CIPHER_mode(tkt_cipher) != EVP_CIPH_CBC_MODE
+ || EVP_CIPHER_iv_length(tkt_cipher) != TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) < TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) > TLS_TICKET_KEYLEN) {
+ msg_warn("%s: invalid value: %s; session tickets disabled",
+ VAR_TLS_TKT_CIPHER, var_tls_tkt_cipher);
+ ticketable = 0;
+ }
+ }
+ if (ticketable) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(server_ctx, ticket_cb);
+#else
+ SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_cb);
+#endif
+
+ /*
+ * OpenSSL 1.1.1 introduces support for TLS 1.3, which can issue more
+ * than one ticket per handshake. While this may be appropriate for
+ * communication between browsers and webservers, it is not terribly
+ * useful for MTAs, many of which other than Postfix don't do TLS
+ * session caching at all, and Postfix has no mechanism for storing
+ * multiple session tickets, if more than one sent, the second
+ * clobbers the first. OpenSSL 1.1.1 servers default to issuing two
+ * tickets for non-resumption handshakes, we reduce this to one. Our
+ * ticket decryption callback already (since 2.11) asks OpenSSL to
+ * avoid issuing new tickets when the presented ticket is re-usable.
+ */
+ SSL_CTX_set_num_tickets(server_ctx, 1);
+ }
+#endif
+ if (!ticketable)
+ off |= SSL_OP_NO_TICKET;
+
+ SSL_CTX_set_options(server_ctx, off);
+
+ /*
+ * Global protocol selection.
+ */
+ if (protomask != 0)
+ SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask));
+ SSL_CTX_set_min_proto_version(server_ctx, min_proto);
+ SSL_CTX_set_max_proto_version(server_ctx, max_proto);
+ SSL_CTX_set_min_proto_version(sni_ctx, min_proto);
+ SSL_CTX_set_max_proto_version(sni_ctx, max_proto);
+
+ /*
+ * Some sites may want to give the client less rope. On the other hand,
+ * this could trigger inter-operability issues, the client should not
+ * offer ciphers it implements poorly, but this hasn't stopped some
+ * vendors from getting it wrong.
+ */
+ if (var_tls_preempt_clist)
+ SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ /* Done with server_ctx options, clone to sni_ctx */
+ SSL_CTX_clear_options(sni_ctx, ~0);
+ SSL_CTX_set_options(sni_ctx, SSL_CTX_get_options(server_ctx));
+
+ /*
+ * Set the call-back routine to debug handshake progress.
+ */
+ if (log_mask & TLS_LOG_DEBUG) {
+ SSL_CTX_set_info_callback(server_ctx, tls_info_callback);
+ SSL_CTX_set_info_callback(sni_ctx, tls_info_callback);
+ }
+
+ /*
+ * Load the CA public key certificates for both the server cert and for
+ * the verification of client certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(server_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * Upref and share the cert store. Sadly we can't yet use
+ * SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
+ */
+ cert_store = SSL_CTX_get_cert_store(server_ctx);
+ X509_STORE_up_ref(cert_store);
+ SSL_CTX_set_cert_store(sni_ctx, cert_store);
+
+ /*
+ * Load the server public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). A client
+ * with RSA only (e.g. Netscape) will use the RSA certificate only. A
+ * client with openssl-library will use RSA first if not especially
+ * changed in the cipher setup.
+ */
+ if (tls_set_my_certificate_key_info(server_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * Diffie-Hellman key generation parameters can either be loaded from
+ * files (preferred) or taken from compiled in values. First, set the
+ * callback that will select the values when requested, then load the
+ * (possibly) available DH parameters from files. We are generous with
+ * the error handling, since we do have default values compiled in, so we
+ * will not abort but just log the error message.
+ */
+ if (*props->dh1024_param_file != 0)
+ tls_set_dh_from_file(props->dh1024_param_file);
+ tls_tmp_dh(server_ctx, 1);
+ tls_tmp_dh(sni_ctx, 1);
+
+ /*
+ * Enable EECDH if available, errors are not fatal, we just keep going
+ * with any remaining key-exchange algorithms.
+ */
+ tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_auto);
+ tls_auto_eecdh_curves(sni_ctx, var_tls_eecdh_auto);
+
+ /*
+ * If we want to check client certificates, we have to indicate it in
+ * advance. By now we only allow to decide on a global basis. If we want
+ * to allow certificate based relaying, we must ask the client to provide
+ * one with SSL_VERIFY_PEER. The client now can decide, whether it
+ * provides one or not. We can enforce a failure of the negotiation with
+ * SSL_VERIFY_FAIL_IF_NO_PEER_CERT, if we do not allow a connection
+ * without one. In the "server hello" following the initialization by the
+ * "client hello" the server must provide a list of CAs it is willing to
+ * accept. Some clever clients will then select one from the list of
+ * available certificates matching these CAs. Netscape Communicator will
+ * present the list of certificates for selecting the one to be sent, or
+ * it will issue a warning, if there is no certificate matching the
+ * available CAs.
+ *
+ * With regard to the purpose of the certificate for relaying, we might like
+ * a later negotiation, maybe relaying would already be allowed for other
+ * reasons, but this would involve severe changes in the internal postfix
+ * logic, so we have to live with it the way it is.
+ */
+ if (props->ask_ccert)
+ verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ SSL_CTX_set_verify(server_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ SSL_CTX_set_verify(sni_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ if (props->ask_ccert && *props->CAfile) {
+ STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile);
+
+ if (calist == 0) {
+ /* Not generally critical */
+ msg_warn("error loading client CA names from: %s",
+ props->CAfile);
+ tls_print_errors();
+ }
+ SSL_CTX_set_client_CA_list(server_ctx, calist);
+
+ if (calist != 0 && sk_X509_NAME_num(calist) > 0) {
+ calist = SSL_dup_CA_list(calist);
+
+ if (calist == 0) {
+ msg_warn("error duplicating client CA names for SNI");
+ tls_print_errors();
+ } else {
+ SSL_CTX_set_client_CA_list(sni_ctx, calist);
+ }
+ }
+ }
+
+ /*
+ * Initialize our own TLS server handle, before diving into the details
+ * of TLS session cache management.
+ */
+ app_ctx = tls_alloc_app_context(server_ctx, sni_ctx, log_mask);
+
+ if (cachable || ticketable || props->set_sessid) {
+
+ /*
+ * Initialize the session cache.
+ *
+ * With a large number of concurrent smtpd(8) processes, it is not a
+ * good idea to cache multiple large session objects in each process.
+ * We set the internal cache size to 1, and don't register a
+ * "remove_cb" so as to avoid deleting good sessions from the
+ * external cache prematurely (when the internal cache is full,
+ * OpenSSL removes sessions from the external cache also)!
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly
+ * (not via a callback).
+ *
+ * Set a session id context to identify to what type of server process
+ * created a session. In our case, the context is simply the name of
+ * the mail system: "Postfix/TLS".
+ */
+ SSL_CTX_sess_set_cache_size(server_ctx, 1);
+ SSL_CTX_set_session_id_context(server_ctx,
+ (void *) &server_session_id_context,
+ sizeof(server_session_id_context));
+ SSL_CTX_set_session_cache_mode(server_ctx,
+ SSL_SESS_CACHE_SERVER |
+ SSL_SESS_CACHE_NO_INTERNAL |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ if (cachable) {
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ SSL_CTX_sess_set_get_cb(server_ctx, get_server_session_cb);
+ SSL_CTX_sess_set_new_cb(server_ctx, new_server_session_cb);
+ }
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session
+ * lifetime to twice the cache lifetime, which is also the issuing
+ * and retired key validation lifetime of session tickets keys. This
+ * way a session always lasts longer than the server's ability to
+ * decrypt its session ticket. Otherwise, a bug in OpenSSL may fail
+ * to re-issue tickets when sessions decrypt, but are expired.
+ */
+ SSL_CTX_set_timeout(server_ctx, 2 * scache_timeout);
+ } else {
+
+ /*
+ * If we have no external cache, disable all caching. No use wasting
+ * server memory resources with sessions they are unlikely to be able
+ * to reuse.
+ */
+ SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF);
+ }
+
+ return (app_ctx);
+}
+
+ /*
+ * This is the actual startup routine for a new connection. We expect that
+ * the SMTP buffers are flushed and the "220 Ready to start TLS" was sent to
+ * the client, so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
+{
+ int sts;
+ TLS_SESS_STATE *TLScontext;
+ const char *cipher_list;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * Implicitly enable logging of trust chain errors when verified certs
+ * are required.
+ */
+ if (props->requirecert)
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection from %s", props->namaddr);
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+
+ ERR_clear_error();
+ if ((TLScontext->con = (SSL *) SSL_new(app_ctx->ssl_ctx)) == 0) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ TLScontext->serverid = mystrdup(props->serverid);
+ TLScontext->am_server = 1;
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+ if (props->requirecert)
+ SSL_set_security_level(TLScontext->con, 1);
+#endif
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_accept/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for session caching and certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_accept(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_accept error from %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_accept error from %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_accept error from %s: lost connection",
+ props->namaddr);
+ }
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_server_post_accept(TLScontext));
+}
+
+/* tls_server_post_accept - post-handshake processing */
+
+TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peer;
+ char buf[CCERT_BUFSIZ];
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session%s", TLScontext->namaddr,
+ TLScontext->ticketed ? " (RFC 5077 session ticket)" : "");
+
+ /*
+ * Let's see whether a peer certificate is available and what is the
+ * actual information. We want to save it for later use.
+ */
+ peer = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peer != NULL) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ X509_NAME_oneline(X509_get_subject_name(peer),
+ buf, sizeof(buf));
+ msg_info("subject=%s", printable(buf, '?'));
+ X509_NAME_oneline(X509_get_issuer_name(peer),
+ buf, sizeof(buf));
+ msg_info("issuer=%s", printable(buf, '?'));
+ }
+ TLScontext->peer_CN = tls_peer_CN(peer, TLScontext);
+ TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext);
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg);
+
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
+ msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s"
+ ", pkey_fingerprint=%s",
+ TLScontext->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ }
+ TLS_FREE_PEER_CERT(peer);
+
+ /*
+ * Give them a clue. Problems with trust chain verification are
+ * logged when the session is first negotiated, before the session is
+ * stored into the cache. We don't want mystery failures, so log the
+ * fact the real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_TRUSTED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log",
+ TLScontext->namaddr);
+ }
+ } else {
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * If the library triggered the SSL handshake, switch to the
+ * tls_timed_read/write() functions and make the TLScontext available to
+ * those functions. Otherwise, leave control over SSL_read/write/etc.
+ * with the application.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(TLScontext->stream, TLScontext);
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ /*
+ * All the key facts in a single log entry.
+ */
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_SERVER, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_session.c b/src/tls/tls_session.c
new file mode 100644
index 0000000..a4b7a8f
--- /dev/null
+++ b/src/tls/tls_session.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* tls_session
+/* SUMMARY
+/* TLS client and server session routines
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* void tls_session_stop(ctx, stream, timeout, failure, TLScontext)
+/* TLS_APPL_STATE *ctx;
+/* VSTREAM *stream;
+/* int timeout;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* VSTRING *tls_session_passivate(session)
+/* SSL_SESSION *session;
+/*
+/* SSL_SESSION *tls_session_activate(session_data, session_data_len)
+/* char *session_data;
+/* int session_data_len;
+/* DESCRIPTION
+/* tls_session_stop() implements the tls_server_shutdown()
+/* and the tls_client_shutdown() routines.
+/*
+/* tls_session_passivate() converts an SSL_SESSION object to
+/* VSTRING. The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with vstring_free().
+/*
+/* tls_session_activate() reanimates a passivated SSL_SESSION object.
+/* The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with SSL_SESSION_free().
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+
+/* tls_session_stop - shut down the TLS connection and reset state */
+
+void tls_session_stop(TLS_APPL_STATE *unused_ctx, VSTREAM *stream, int timeout,
+ int failure, TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "tls_session_stop";
+ int retval;
+
+ /*
+ * Sanity check.
+ */
+ if (TLScontext == 0)
+ msg_panic("%s: stream has no active TLS context", myname);
+
+ /*
+ * According to RFC 2246 (TLS 1.0), there is no requirement to wait for
+ * the peer's close-notify. If the application protocol provides
+ * sufficient session termination signaling, then there's no need to
+ * duplicate that at the TLS close-notify layer.
+ *
+ * https://tools.ietf.org/html/rfc2246#section-7.2.1
+ * https://tools.ietf.org/html/rfc4346#section-7.2.1
+ * https://tools.ietf.org/html/rfc5246#section-7.2.1
+ *
+ * Specify 'tls_fast_shutdown = no' to enable the historical behavior
+ * described below.
+ *
+ * Perform SSL_shutdown() twice, as the first attempt will send out the
+ * shutdown alert but it will not wait for the peer's shutdown alert.
+ * Therefore, when we are the first party to send the alert, we must call
+ * SSL_shutdown() again. On failure we don't want to resume the session,
+ * so we will not perform SSL_shutdown() and the session will be removed
+ * as being bad.
+ */
+ if (!failure && !SSL_in_init(TLScontext->con)) {
+ retval = tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ if (!var_tls_fast_shutdown && retval == 0)
+ tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ }
+ tls_free_context(TLScontext);
+ tls_stream_stop(stream);
+}
+
+/* tls_session_passivate - passivate SSL_SESSION object */
+
+VSTRING *tls_session_passivate(SSL_SESSION *session)
+{
+ const char *myname = "tls_session_passivate";
+ int estimate;
+ int actual_size;
+ VSTRING *session_data;
+ unsigned char *ptr;
+
+ /*
+ * First, find out how much memory is needed for the passivated
+ * SSL_SESSION object.
+ */
+ estimate = i2d_SSL_SESSION(session, (unsigned char **) 0);
+ if (estimate <= 0) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ return (0);
+ }
+
+ /*
+ * Passivate the SSL_SESSION object. The use of a VSTRING is slightly
+ * wasteful but is convenient to combine data and length.
+ */
+ session_data = vstring_alloc(estimate);
+ ptr = (unsigned char *) STR(session_data);
+ actual_size = i2d_SSL_SESSION(session, &ptr);
+ if (actual_size != estimate) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ vstring_free(session_data);
+ return (0);
+ }
+ vstring_set_payload_size(session_data, actual_size);
+
+ return (session_data);
+}
+
+/* tls_session_activate - activate passivated session */
+
+SSL_SESSION *tls_session_activate(const char *session_data, int session_data_len)
+{
+ SSL_SESSION *session;
+ const unsigned char *ptr;
+
+ /*
+ * Activate the SSL_SESSION object.
+ */
+ ptr = (const unsigned char *) session_data;
+ session = d2i_SSL_SESSION((SSL_SESSION **) 0, &ptr, session_data_len);
+ if (!session)
+ tls_print_errors();
+
+ return (session);
+}
+
+#endif
diff --git a/src/tls/tls_stream.c b/src/tls/tls_stream.c
new file mode 100644
index 0000000..4cc53a7
--- /dev/null
+++ b/src/tls/tls_stream.c
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/* tls_stream
+/* SUMMARY
+/* VSTREAM over TLS
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_stream_start(stream, context)
+/* VSTREAM *stream;
+/* TLS_SESS_STATE *context;
+/*
+/* void tls_stream_stop(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the VSTREAM over TLS support user interface.
+/* The hard work is done elsewhere.
+/*
+/* tls_stream_start() enables TLS on the named stream. All read
+/* and write operations are directed through the TLS library,
+/* using the state information specified with the context argument.
+/*
+/* tls_stream_stop() replaces the VSTREAM read/write routines
+/* by dummies that have no side effects, and deletes the
+/* VSTREAM's reference to the TLS context.
+/* DIAGNOSTICS
+/* The tls_stream(3) read/write routines return the non-zero
+/* number of plaintext bytes read/written if successful; -1
+/* after TLS protocol failure, system-call failure, or for any
+/* reason described under "in addition" below; and zero when
+/* the remote party closed the connection or sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_stream(3) read/write routines the
+/* global errno value is non-zero when the requested operation
+/* did not complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when a network read/write
+/* request did not complete within the time limit.
+/* SEE ALSO
+/* dummy_read(3), placebo read routine
+/* dummy_write(3), placebo write routine
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Based on code that was originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <iostuff.h>
+#include <vstream.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /*
+ * Interface mis-match compensation. The OpenSSL read/write routines return
+ * unspecified negative values when an operation fails, while the vstream(3)
+ * plaintext timed_read/write() functions follow the convention of UNIX
+ * system calls, and return -1 upon error. The macro below makes OpenSSL
+ * read/write results consistent with the UNIX system-call convention.
+ */
+#define NORMALIZED_VSTREAM_RETURN(retval) ((retval) < 0 ? -1 : (retval))
+
+/* tls_timed_read - read content from stream, then TLS decapsulate */
+
+static ssize_t tls_timed_read(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_read";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ ret = tls_bio_read(fd, buf, len, timeout, TLScontext);
+ if (ret > 0 && (TLScontext->log_mask & TLS_LOG_ALLPKTS))
+ msg_info("Read %ld chars: %.*s",
+ (long) ret, (int) (ret > 40 ? 40 : ret), (char *) buf);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_timed_write - TLS encapsulate content, then write to stream */
+
+static ssize_t tls_timed_write(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_write";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ if (TLScontext->log_mask & TLS_LOG_ALLPKTS)
+ msg_info("Write %ld chars: %.*s",
+ (long) len, (int) (len > 40 ? 40 : len), (char *) buf);
+ ret = tls_bio_write(fd, buf, len, timeout, TLScontext);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_stream_start - start VSTREAM over TLS */
+
+void tls_stream_start(VSTREAM *stream, TLS_SESS_STATE *context)
+{
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(tls_timed_read),
+ CA_VSTREAM_CTL_WRITE_FN(tls_timed_write),
+ CA_VSTREAM_CTL_CONTEXT(context),
+ CA_VSTREAM_CTL_END);
+}
+
+/* tls_stream_stop - stop VSTREAM over TLS */
+
+void tls_stream_stop(VSTREAM *stream)
+{
+
+ /*
+ * Prevent data leakage after TLS is turned off. The Postfix/TLS patch
+ * provided null function pointers; we use dummy routines that make less
+ * noise when used.
+ */
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(dummy_read),
+ CA_VSTREAM_CTL_WRITE_FN(dummy_write),
+ CA_VSTREAM_CTL_CONTEXT((void *) 0),
+ CA_VSTREAM_CTL_END);
+}
+
+#endif
diff --git a/src/tls/tls_verify.c b/src/tls/tls_verify.c
new file mode 100644
index 0000000..f32f32b
--- /dev/null
+++ b/src/tls/tls_verify.c
@@ -0,0 +1,425 @@
+/*++
+/* NAME
+/* tls_verify 3
+/* SUMMARY
+/* peer name and peer certificate verification
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_verify_certificate_callback(ok, ctx)
+/* int ok;
+/* X509_STORE_CTX *ctx;
+/*
+/* int tls_log_verify_error(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_peer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_issuer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* tls_verify_certificate_callback() is called several times (directly
+/* or indirectly) from crypto/x509/x509_vfy.c. It collects errors
+/* and trust information at each element of the trust chain.
+/* The last call at depth 0 sets the verification status based
+/* on the cumulative winner (lowest depth) of errors vs. trust.
+/* We always return 1 (continue the handshake) and handle trust
+/* and peer-name verification problems at the application level.
+/*
+/* tls_log_verify_error() (called only when we care about the
+/* peer certificate, that is not when opportunistic) logs the
+/* reason why the certificate failed to be verified.
+/*
+/* tls_peer_CN() returns the text CommonName for the peer
+/* certificate subject, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* tls_issuer_CN() returns the text CommonName for the peer
+/* certificate issuer, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* Arguments:
+/* .IP ok
+/* Result of prior verification: non-zero means success. In
+/* order to reduce the noise level, some tests or error reports
+/* are disabled when verification failed because of some
+/* earlier problem.
+/* .IP ctx
+/* SSL application context. This links to the Postfix TLScontext
+/* with enforcement and logging options.
+/* .IP gn
+/* An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName
+/* to be decoded and checked for validity.
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP TLScontext
+/* Server or client context for warning messages.
+/* DIAGNOSTICS
+/* tls_peer_CN() and tls_issuer_CN() log a warning when 1) the requested
+/* information is not available in the specified certificate, 2) the
+/* result exceeds a fixed limit, 3) the result contains NUL characters or
+/* the result contains non-printable or non-ASCII characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* update_error_state - safely stash away error state */
+
+static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
+ X509 *errorcert, int errorcode)
+{
+ /* No news is good news */
+ if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
+ return;
+
+ /*
+ * The certificate pointer is stable during the verification callback,
+ * but may be freed after the callback returns. Since we delay error
+ * reporting till later, we bump the refcount so we can rely on it still
+ * being there until later.
+ */
+ if (TLScontext->errorcert != 0)
+ X509_free(TLScontext->errorcert);
+ if (errorcert != 0)
+ X509_up_ref(errorcert);
+ TLScontext->errorcert = errorcert;
+ TLScontext->errorcode = errorcode;
+ TLScontext->errordepth = depth;
+}
+
+/* tls_verify_certificate_callback - verify peer certificate info */
+
+int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
+{
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+ int err;
+ int depth;
+ SSL *con;
+ TLS_SESS_STATE *TLScontext;
+
+ /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
+ con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ /*
+ * Transient failures to load the (DNS or synthetic TLSA) trust settings
+ * must poison certificate verification, since otherwise the default
+ * trust store may bless a certificate that would have failed
+ * verification with the preferred trust anchors (or fingerprints).
+ *
+ * Since we unconditionally continue, or in any case if verification is
+ * about to succeed, there is eventually a final depth 0 callback, at
+ * which point we force an "unspecified" error. The failure to load the
+ * trust settings was logged earlier.
+ */
+ if (TLScontext->must_fail) {
+ if (depth == 0) {
+ X509_STORE_CTX_set_error(ctx, err = X509_V_ERR_UNSPECIFIED);
+ update_error_state(TLScontext, depth, cert, err);
+ }
+ return (1);
+ }
+ if (ok == 0)
+ update_error_state(TLScontext, depth, cert, err);
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ if (cert)
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("%s: depth=%d verify=%d subject=%s",
+ TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ }
+ return (1);
+}
+
+/* tls_log_verify_error - Report final verification error status */
+
+void tls_log_verify_error(TLS_SESS_STATE *TLScontext)
+{
+ char buf[CCERT_BUFSIZ];
+ int err = TLScontext->errorcode;
+ X509 *cert = TLScontext->errorcert;
+ int depth = TLScontext->errordepth;
+
+#define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
+
+ if (err == X509_V_OK)
+ return;
+
+ /*
+ * Specific causes for verification failure.
+ */
+ switch (err) {
+ case X509_V_ERR_CERT_UNTRUSTED:
+
+ /*
+ * We expect the error cert to be the leaf, but it is likely
+ * sufficient to omit it from the log, even less user confusion.
+ */
+ msg_info("certificate verification failed for %s: "
+ "not trusted by local or TLSA policy", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ msg_info("certificate verification failed for %s: "
+ "self-signed certificate", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+
+ /*
+ * There is no difference between issuing cert not provided and
+ * provided, but not found in CAfile/CApath. Either way, we don't
+ * trust it.
+ */
+ if (cert)
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("certificate verification failed for %s: untrusted issuer %s",
+ TLScontext->namaddr, printable(buf, '?'));
+ break;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate not"
+ " yet valid", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate has"
+ " expired", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_INVALID_PURPOSE:
+ msg_info("certificate verification failed for %s: not designated for "
+ "use as a %s certificate", TLScontext->namaddr, PURPOSE);
+ break;
+ case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+ msg_info("certificate verification failed for %s: "
+ "certificate chain longer than limit(%d)",
+ TLScontext->namaddr, depth - 1);
+ break;
+ default:
+ msg_info("%s certificate verification failed for %s: num=%d:%s",
+ PURPOSE, TLScontext->namaddr, err,
+ X509_verify_cert_error_string(err));
+ break;
+ }
+}
+
+#ifndef DONT_GRIPE
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+#endif
+
+/* tls_text_name - extract certificate property value by name */
+
+static char *tls_text_name(X509_NAME *name, int nid, const char *label,
+ const TLS_SESS_STATE *TLScontext, int gripe)
+{
+ const char *myname = "tls_text_name";
+ int pos;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *entry_str;
+ int asn1_type;
+ int utf8_length;
+ unsigned char *utf8_value;
+ int ch;
+ unsigned char *cp;
+
+ if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
+ if (gripe != DONT_GRIPE) {
+ msg_warn("%s: %s: peer certificate has no %s",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ }
+ return (0);
+ }
+#if 0
+
+ /*
+ * If the match is required unambiguous, insist that that no other values
+ * be present.
+ */
+ if (X509_NAME_get_index_by_NID(name, nid, pos) >= 0) {
+ msg_warn("%s: %s: multiple %ss in peer certificate",
+ myname, TLScontext->namaddr, label);
+ return (0);
+ }
+#endif
+
+ if ((entry = X509_NAME_get_entry(name, pos)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s entry",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+ if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s data",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * XXX Convert everything into UTF-8. This is a super-set of ASCII, so we
+ * don't have to bother with separate code paths for ASCII-like content.
+ * If the payload is ASCII then we won't waste lots of CPU cycles
+ * converting it into UTF-8. It's up to OpenSSL to do something
+ * reasonable when converting ASCII formats that contain non-ASCII
+ * content.
+ *
+ * XXX Don't bother optimizing the string length error check. It is not
+ * worth the complexity.
+ */
+ asn1_type = ASN1_STRING_type(entry_str);
+ if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
+ msg_warn("%s: %s: error decoding peer %s of ASN.1 type=%d",
+ myname, TLScontext->namaddr, label, asn1_type);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * No returns without cleaning up. A good optimizer will replace multiple
+ * blocks of identical code by jumps to just one such block.
+ */
+#define TLS_TEXT_NAME_RETURN(x) do { \
+ char *__tls_text_name_temp = (x); \
+ OPENSSL_free(utf8_value); \
+ return (__tls_text_name_temp); \
+ } while (0)
+
+ /*
+ * Remove trailing null characters. They would give false alarms with the
+ * length check and with the embedded null check.
+ */
+#define TRIM0(s, l) do { while ((l) > 0 && (s)[(l)-1] == 0) --(l); } while (0)
+
+ TRIM0(utf8_value, utf8_length);
+
+ /*
+ * Enforce the length limit, because the caller will copy the result into
+ * a fixed-length buffer.
+ */
+ if (utf8_length >= CCERT_BUFSIZ) {
+ msg_warn("%s: %s: peer %s too long: %d",
+ myname, TLScontext->namaddr, label, utf8_length);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject embedded nulls in ASCII or UTF-8 names. OpenSSL is responsible
+ * for producing properly-formatted UTF-8.
+ */
+ if (utf8_length != strlen((char *) utf8_value)) {
+ msg_warn("%s: %s: NULL character in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject non-printable ASCII characters in UTF-8 content.
+ *
+ * Note: the code below does not find control characters in illegal UTF-8
+ * sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly,
+ * it does validation.
+ */
+ for (cp = utf8_value; (ch = *cp) != 0; cp++) {
+ if (ISASCII(ch) && !ISPRINT(ch)) {
+ msg_warn("%s: %s: non-printable content in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+ }
+ TLS_TEXT_NAME_RETURN(mystrdup((char *) utf8_value));
+}
+
+/* tls_peer_CN - extract peer common name from certificate */
+
+char *tls_peer_CN(X509 *peercert, const TLS_SESS_STATE *TLScontext)
+{
+ char *cn;
+ const char *san;
+
+ /* Absent a commonName, return a validated DNS-ID SAN */
+ cn = tls_text_name(X509_get_subject_name(peercert), NID_commonName,
+ "subject CN", TLScontext, DONT_GRIPE);
+ if (cn == 0 && (san = SSL_get0_peername(TLScontext->con)) != 0)
+ cn = mystrdup(san);
+ return (cn ? cn : mystrdup(""));
+}
+
+/* tls_issuer_CN - extract issuer common name from certificate */
+
+char *tls_issuer_CN(X509 *peer, const TLS_SESS_STATE *TLScontext)
+{
+ X509_NAME *name;
+ char *cn;
+
+ name = X509_get_issuer_name(peer);
+
+ /*
+ * If no issuer CN field, use Organization instead. CA certs without a CN
+ * are common, so we only complain if the organization is also missing.
+ */
+ if ((cn = tls_text_name(name, NID_commonName,
+ "issuer CN", TLScontext, DONT_GRIPE)) == 0)
+ cn = tls_text_name(name, NID_organizationName,
+ "issuer Organization", TLScontext, DONT_GRIPE);
+ return (cn ? cn : mystrdup(""));
+}
+
+#endif
diff --git a/src/tls/warn-mixed-multi-key.pem b/src/tls/warn-mixed-multi-key.pem
new file mode 100644
index 0000000..ab21d3a
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem
@@ -0,0 +1,51 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/warn-mixed-multi-key.pem.ref b/src/tls/warn-mixed-multi-key.pem.ref
new file mode 100644
index 0000000..c9ce8db
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem.ref
@@ -0,0 +1,13 @@
+unknown: warning: ignoring 2nd key at index 4 in warn-mixed-multi-key.pem after 1st at 2
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tlsmgr/.indent.pro b/src/tlsmgr/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tlsmgr/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tlsmgr/Makefile.in b/src/tlsmgr/Makefile.in
new file mode 100644
index 0000000..8e7aab6
--- /dev/null
+++ b/src/tlsmgr/Makefile.in
@@ -0,0 +1,100 @@
+SHELL = /bin/sh
+SRCS = tlsmgr.c
+OBJS = tlsmgr.o
+HDRS =
+TESTSRC = smtpd_token_test.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = tlsmgr
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../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 *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+tests:
+
+root_tests:
+
+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'
+tlsmgr.o: ../../include/argv.h
+tlsmgr.o: ../../include/attr.h
+tlsmgr.o: ../../include/check_arg.h
+tlsmgr.o: ../../include/data_redirect.h
+tlsmgr.o: ../../include/dict.h
+tlsmgr.o: ../../include/dns.h
+tlsmgr.o: ../../include/events.h
+tlsmgr.o: ../../include/htable.h
+tlsmgr.o: ../../include/iostuff.h
+tlsmgr.o: ../../include/mail_conf.h
+tlsmgr.o: ../../include/mail_params.h
+tlsmgr.o: ../../include/mail_proto.h
+tlsmgr.o: ../../include/mail_server.h
+tlsmgr.o: ../../include/mail_version.h
+tlsmgr.o: ../../include/master_proto.h
+tlsmgr.o: ../../include/msg.h
+tlsmgr.o: ../../include/myaddrinfo.h
+tlsmgr.o: ../../include/myflock.h
+tlsmgr.o: ../../include/mymalloc.h
+tlsmgr.o: ../../include/name_code.h
+tlsmgr.o: ../../include/name_mask.h
+tlsmgr.o: ../../include/nvtable.h
+tlsmgr.o: ../../include/set_eugid.h
+tlsmgr.o: ../../include/sock_addr.h
+tlsmgr.o: ../../include/stringops.h
+tlsmgr.o: ../../include/sys_defs.h
+tlsmgr.o: ../../include/tls.h
+tlsmgr.o: ../../include/tls_mgr.h
+tlsmgr.o: ../../include/tls_prng.h
+tlsmgr.o: ../../include/tls_scache.h
+tlsmgr.o: ../../include/vbuf.h
+tlsmgr.o: ../../include/vstream.h
+tlsmgr.o: ../../include/vstring.h
+tlsmgr.o: ../../include/vstring_vstream.h
+tlsmgr.o: ../../include/warn_stat.h
+tlsmgr.o: tlsmgr.c
diff --git a/src/tlsmgr/tlsmgr.c b/src/tlsmgr/tlsmgr.c
new file mode 100644
index 0000000..28ca961
--- /dev/null
+++ b/src/tlsmgr/tlsmgr.c
@@ -0,0 +1,1115 @@
+/*++
+/* NAME
+/* tlsmgr 8
+/* SUMMARY
+/* Postfix TLS session cache and PRNG manager
+/* SYNOPSIS
+/* \fBtlsmgr\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBtlsmgr\fR(8) manages the Postfix TLS session caches.
+/* It stores and retrieves cache entries on request by
+/* \fBsmtpd\fR(8) and \fBsmtp\fR(8) processes, and periodically
+/* removes entries that have expired.
+/*
+/* The \fBtlsmgr\fR(8) also manages the PRNG (pseudo random number
+/* generator) pool. It answers queries by the \fBsmtpd\fR(8)
+/* and \fBsmtp\fR(8)
+/* processes to seed their internal PRNG pools.
+/*
+/* The \fBtlsmgr\fR(8)'s PRNG pool is initially seeded from
+/* an external source (EGD, /dev/urandom, or regular file).
+/* It is updated at configurable pseudo-random intervals with
+/* data from the external source. It is updated periodically
+/* with data from TLS session cache entries and with the time
+/* of day, and is updated with the time of day whenever a
+/* process requests \fBtlsmgr\fR(8) service.
+/*
+/* The \fBtlsmgr\fR(8) saves the PRNG state to an exchange file
+/* periodically and when the process terminates, and reads
+/* the exchange file when initializing its PRNG.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBtlsmgr\fR(8) is not security-sensitive. The code that maintains
+/* the external and internal PRNG pools does not "trust" the
+/* data that it manipulates, and the code that maintains the
+/* TLS session cache does not touch the contents of the cached
+/* entries, except for seeding its internal PRNG pool.
+/*
+/* The \fBtlsmgr\fR(8) can be run chrooted and with reduced privileges.
+/* At process startup it connects to the entropy source and
+/* exchange file, and creates or truncates the optional TLS
+/* session cache files.
+/*
+/* With Postfix version 2.5 and later, the \fBtlsmgr\fR(8) no
+/* longer uses root privileges when opening cache files. These
+/* files should now be stored under the Postfix-owned
+/* \fBdata_directory\fR. As a migration aid, an attempt to
+/* open a cache file under a non-Postfix directory is redirected
+/* to the Postfix-owned \fBdata_directory\fR, and a warning
+/* is logged.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* There is no automatic means to limit the number of entries in the
+/* TLS session caches and/or the size of the TLS cache files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* because \fBtlsmgr\fR(8) is a persistent processes. Use the
+/* command "\fBpostfix reload\fR" after a configuration change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* TLS SESSION CACHE
+/* .ad
+/* .fi
+/* .IP "\fBlmtp_tls_loglevel (0)\fR"
+/* The LMTP-specific version of the smtp_tls_loglevel
+/* configuration parameter.
+/* .IP "\fBlmtp_tls_session_cache_database (empty)\fR"
+/* The LMTP-specific version of the smtp_tls_session_cache_database
+/* configuration parameter.
+/* .IP "\fBlmtp_tls_session_cache_timeout (3600s)\fR"
+/* The LMTP-specific version of the smtp_tls_session_cache_timeout
+/* configuration parameter.
+/* .IP "\fBsmtp_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP client logging of TLS activity.
+/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP client
+/* TLS session cache.
+/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP client TLS session cache
+/* information.
+/* .IP "\fBsmtpd_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP server logging of TLS activity.
+/* .IP "\fBsmtpd_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP server
+/* TLS session cache.
+/* .IP "\fBsmtpd_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP server TLS session cache
+/* information.
+/* PSEUDO RANDOM NUMBER GENERATOR
+/* .ad
+/* .fi
+/* .IP "\fBtls_random_source (see 'postconf -d' output)\fR"
+/* The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo
+/* random number generator (PRNG) pool.
+/* .IP "\fBtls_random_bytes (32)\fR"
+/* The number of bytes that \fBtlsmgr\fR(8) reads from $tls_random_source
+/* when (re)seeding the in-memory pseudo random number generator (PRNG)
+/* pool.
+/* .IP "\fBtls_random_exchange_name (see 'postconf -d' output)\fR"
+/* Name of the pseudo random number generator (PRNG) state file
+/* that is maintained by \fBtlsmgr\fR(8).
+/* .IP "\fBtls_random_prng_update_period (3600s)\fR"
+/* The time between attempts by \fBtlsmgr\fR(8) to save the state of
+/* the pseudo random number generator (PRNG) to the file specified
+/* with $tls_random_exchange_name.
+/* .IP "\fBtls_random_reseed_period (3600s)\fR"
+/* The maximal time between attempts by \fBtlsmgr\fR(8) to re-seed the
+/* in-memory pseudo random number generator (PRNG) pool from external
+/* sources.
+/* 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 "\fBdata_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix-writable data files (for example:
+/* caches, pseudo-random numbers).
+/* .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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtp(8), Postfix SMTP client
+/* smtpd(8), Postfix SMTP server
+/* 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
+/* TLS_README, Postfix TLS configuration and operation
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 2.2.
+/* AUTHOR(S)
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Adapted by:
+/* 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/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/time.h> /* gettimeofday, not POSIX */
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <attr.h>
+#include <set_eugid.h>
+#include <htable.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <data_redirect.h>
+
+/* Master process interface. */
+
+#include <master_proto.h>
+#include <mail_server.h>
+
+/* TLS library. */
+
+#ifdef USE_TLS
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h> /* TLS_MGR_SCACHE_<type> */
+#include <tls_prng.h>
+#include <tls_scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunables.
+ */
+char *var_tls_rand_source;
+int var_tls_rand_bytes;
+int var_tls_reseed_period;
+int var_tls_prng_exch_period;
+char *var_smtpd_tls_loglevel;
+char *var_smtpd_tls_scache_db;
+int var_smtpd_tls_scache_timeout;
+char *var_smtp_tls_loglevel;
+char *var_smtp_tls_scache_db;
+int var_smtp_tls_scache_timeout;
+char *var_lmtp_tls_loglevel;
+char *var_lmtp_tls_scache_db;
+int var_lmtp_tls_scache_timeout;
+char *var_tls_rand_exch_name;
+
+ /*
+ * Bound the time that we are willing to wait for an I/O operation. This
+ * produces better error messages than waiting until the watchdog timer
+ * kills the process.
+ */
+#define TLS_MGR_TIMEOUT 10
+
+ /*
+ * State for updating the PRNG exchange file.
+ */
+static TLS_PRNG_SRC *rand_exch;
+
+ /*
+ * State for seeding the internal PRNG from external source.
+ */
+static TLS_PRNG_SRC *rand_source_dev;
+static TLS_PRNG_SRC *rand_source_egd;
+static TLS_PRNG_SRC *rand_source_file;
+
+ /*
+ * The external entropy source type is encoded in the source name. The
+ * obvious alternative is to have separate configuration parameters per
+ * source type, so that one process can query multiple external sources.
+ */
+#define DEV_PREF "dev:"
+#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1)
+#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN)
+
+#define EGD_PREF "egd:"
+#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1)
+#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN)
+
+ /*
+ * State for TLS session caches.
+ */
+typedef struct {
+ char *cache_label; /* cache short-hand name */
+ TLS_SCACHE *cache_info; /* cache handle */
+ int cache_active; /* cache status */
+ char **cache_db; /* main.cf parameter value */
+ const char *log_param; /* main.cf parameter name */
+ char **log_level; /* main.cf parameter value */
+ int *cache_timeout; /* main.cf parameter value */
+} TLSMGR_SCACHE;
+
+static TLSMGR_SCACHE cache_table[] = {
+ TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db,
+ VAR_SMTPD_TLS_LOGLEVEL,
+ &var_smtpd_tls_loglevel, &var_smtpd_tls_scache_timeout,
+ TLS_MGR_SCACHE_SMTP, 0, 0, &var_smtp_tls_scache_db,
+ VAR_SMTP_TLS_LOGLEVEL,
+ &var_smtp_tls_loglevel, &var_smtp_tls_scache_timeout,
+ TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db,
+ VAR_LMTP_TLS_LOGLEVEL,
+ &var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout,
+ 0,
+};
+
+#define smtpd_cache (cache_table[0])
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* tlsmgr_prng_exch_event - update PRNG exchange file */
+
+static void tlsmgr_prng_exch_event(int unused_event, void *dummy)
+{
+ const char *myname = "tlsmgr_prng_exch_event";
+ unsigned char randbyte;
+ int next_period;
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: update PRNG exchange file", myname);
+
+ /*
+ * Sanity check. If the PRNG exchange file was removed, there is no point
+ * updating it further. Restart the process and update the new file.
+ */
+ if (fstat(rand_exch->fd, &st) < 0)
+ msg_fatal("cannot fstat() the PRNG exchange file: %m");
+ if (st.st_nlink == 0) {
+ msg_warn("PRNG exchange file was removed -- exiting to reopen");
+ sleep(1);
+ exit(0);
+ }
+ tls_prng_exch_update(rand_exch);
+
+ /*
+ * Make prediction difficult for outsiders and calculate the time for the
+ * next execution randomly.
+ */
+ RAND_bytes(&randbyte, 1);
+ next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX;
+ event_request_timer(tlsmgr_prng_exch_event, dummy, next_period);
+}
+
+/* tlsmgr_reseed_event - re-seed the internal PRNG pool */
+
+static void tlsmgr_reseed_event(int unused_event, void *dummy)
+{
+ int next_period;
+ unsigned char randbyte;
+ int must_exit = 0;
+
+ /*
+ * Reseed the internal PRNG from external source. Errors are recoverable.
+ * We simply restart and reconnect without making a fuss. This is OK
+ * because we do require that exchange file updates succeed. The exchange
+ * file is the only entropy source that really matters in the long term.
+ *
+ * If the administrator specifies an external randomness source that we
+ * could not open upon start-up, restart to see if we can open it now
+ * (and log a nagging warning if we can't).
+ */
+ if (*var_tls_rand_source) {
+
+ /*
+ * Source is a random device.
+ */
+ if (rand_source_dev) {
+ if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) {
+ msg_info("cannot read from entropy device %s: %m -- "
+ "exiting to reopen", DEV_PATH(var_tls_rand_source));
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Source is an EGD compatible socket.
+ */
+ else if (rand_source_egd) {
+ if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) {
+ msg_info("lost connection to EGD server %s -- "
+ "exiting to reconnect", EGD_PATH(var_tls_rand_source));
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Source is a regular file. Read the content once and close the
+ * file.
+ */
+ else if (rand_source_file) {
+ if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0)
+ msg_warn("cannot read from entropy file %s: %m",
+ var_tls_rand_source);
+ tls_prng_file_close(rand_source_file);
+ rand_source_file = 0;
+ var_tls_rand_source[0] = 0;
+ }
+
+ /*
+ * Could not open the external source upon start-up. See if we can
+ * open it this time. Save PRNG state before we exit.
+ */
+ else {
+ msg_info("exiting to reopen external entropy source %s",
+ var_tls_rand_source);
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Save PRNG state in case we must exit.
+ */
+ if (must_exit) {
+ if (rand_exch)
+ tls_prng_exch_update(rand_exch);
+ sleep(1);
+ exit(0);
+ }
+
+ /*
+ * Make prediction difficult for outsiders and calculate the time for the
+ * next execution randomly.
+ */
+ RAND_bytes(&randbyte, 1);
+ next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX;
+ event_request_timer(tlsmgr_reseed_event, dummy, next_period);
+}
+
+/* tlsmgr_cache_run_event - start TLS session cache scan */
+
+static void tlsmgr_cache_run_event(int unused_event, void *ctx)
+{
+ const char *myname = "tlsmgr_cache_run_event";
+ TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx;
+
+ /*
+ * This routine runs when it is time for another TLS session cache scan.
+ * Make sure this routine gets called again in the future.
+ *
+ * Don't start a new scan when the timer goes off while cache cleanup is
+ * still in progress.
+ */
+ if (cache->cache_info->verbose)
+ msg_info("%s: start TLS %s session cache cleanup",
+ myname, cache->cache_label);
+
+ if (cache->cache_active == 0)
+ cache->cache_active =
+ tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST,
+ TLS_SCACHE_SEQUENCE_NOTHING);
+
+ event_request_timer(tlsmgr_cache_run_event, (void *) cache,
+ cache->cache_info->timeout);
+}
+
+/* tlsmgr_key - return matching or current RFC 5077 session ticket keys */
+
+static int tlsmgr_key(VSTRING *buffer, int timeout)
+{
+ TLS_TICKET_KEY *key;
+ TLS_TICKET_KEY tmp;
+ unsigned char *name;
+ time_t now = time((time_t *) 0);
+
+ /* In tlsmgr requests we encode null key names as empty strings. */
+ name = LEN(buffer) ? (unsigned char *) STR(buffer) : 0;
+
+ /*
+ * Each key's encrypt and subsequent decrypt-only timeout is half of the
+ * total session timeout.
+ */
+ timeout /= 2;
+
+ /* Attempt to locate existing key */
+ if ((key = tls_scache_key(name, now, timeout)) == 0) {
+ if (name == 0) {
+ /* Create new encryption key */
+ if (RAND_bytes(tmp.name, TLS_TICKET_NAMELEN) <= 0
+ || RAND_bytes(tmp.bits, TLS_TICKET_KEYLEN) <= 0
+ || RAND_bytes(tmp.hmac, TLS_TICKET_MACLEN) <= 0)
+ return (TLS_MGR_STAT_ERR);
+ tmp.tout = now + timeout - 1;
+ key = tls_scache_key_rotate(&tmp);
+ } else {
+ /* No matching decryption key found */
+ return (TLS_MGR_STAT_ERR);
+ }
+ }
+ /* Return value overwrites name buffer */
+ vstring_memcpy(buffer, (char *) key, sizeof(*key));
+ return (TLS_MGR_STAT_OK);
+}
+
+/* tlsmgr_loop - TLS manager main loop */
+
+static int tlsmgr_loop(char *unused_name, char **unused_argv)
+{
+ struct timeval tv;
+ int active = 0;
+ TLSMGR_SCACHE *ent;
+
+ /*
+ * Update the PRNG pool with the time of day. We do it here after every
+ * event (including internal timer events and external client request
+ * events), instead of doing it in individual event call-back routines.
+ */
+ GETTIMEOFDAY(&tv);
+ RAND_seed(&tv, sizeof(struct timeval));
+
+ /*
+ * This routine runs as part of the event handling loop, after the event
+ * manager has delivered a timer or I/O event, or after it has waited for
+ * a specified amount of time. The result value of tlsmgr_loop()
+ * specifies how long the event manager should wait for the next event.
+ *
+ * We use this loop to interleave TLS session cache cleanup with other
+ * activity. Interleaved processing is needed when we use a client-server
+ * protocol for entropy and session state exchange with smtp(8) and
+ * smtpd(8) processes.
+ */
+#define DONT_WAIT 0
+#define WAIT_FOR_EVENT (-1)
+
+ for (ent = cache_table; ent->cache_label; ++ent) {
+ if (ent->cache_info && ent->cache_active)
+ active |= ent->cache_active =
+ tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT,
+ TLS_SCACHE_SEQUENCE_NOTHING);
+ }
+
+ return (active ? DONT_WAIT : WAIT_FOR_EVENT);
+}
+
+/* tlsmgr_request_receive - receive request */
+
+static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request)
+{
+ int count;
+
+ /*
+ * Kluge: choose the protocol depending on the request size.
+ */
+ if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
+ msg_warn("timeout while waiting for data from %s",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
+ msg_warn("cannot examine read buffer of %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+
+ /*
+ * Short request: master trigger. Use the string+null protocol.
+ */
+ if (count <= 2) {
+ if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
+ msg_warn("end-of-input while reading request from %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ }
+
+ /*
+ * Long request: real tlsmgr client. Use the attribute list protocol.
+ */
+ else {
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_REQ, request),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* tlsmgr_service - respond to external request */
+
+static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+ static VSTRING *request = 0;
+ static VSTRING *cache_type = 0;
+ static VSTRING *cache_id = 0;
+ static VSTRING *buffer = 0;
+ int len;
+ static char wakeup[] = { /* master wakeup request */
+ TRIGGER_REQ_WAKEUP,
+ 0,
+ };
+ TLSMGR_SCACHE *ent;
+ int status = TLS_MGR_STAT_FAIL;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Initialize. We're select threaded, so we can use static buffers.
+ */
+ if (request == 0) {
+ request = vstring_alloc(10);
+ cache_type = vstring_alloc(10);
+ cache_id = vstring_alloc(10);
+ buffer = vstring_alloc(10);
+ }
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the tlsmgr service (including wake up events sent by the master).
+ * All connection-management stuff is handled by the common code in
+ * multi_server.c.
+ */
+ if (tlsmgr_request_receive(client_stream, request) == 0) {
+
+ /*
+ * Load session from cache.
+ */
+ if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END) == 2) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_LOOKUP);
+ VSTRING_RESET(buffer);
+ } else if (ent->cache_info == 0) {
+
+ /*
+ * Cache type valid, but not enabled
+ */
+ VSTRING_RESET(buffer);
+ } else {
+ status = tls_scache_lookup(ent->cache_info,
+ STR(cache_id), buffer) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Save session to cache.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buffer),
+ ATTR_TYPE_END) == 3) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_UPDATE);
+ } else if (ent->cache_info != 0) {
+ status =
+ tls_scache_update(ent->cache_info, STR(cache_id),
+ STR(buffer), LEN(buffer)) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Delete session from cache.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END) == 2) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_DELETE);
+ } else if (ent->cache_info != 0) {
+ status = tls_scache_delete(ent->cache_info,
+ STR(cache_id)) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * RFC 5077 TLS session ticket keys
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer),
+ ATTR_TYPE_END) == 1) {
+ if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) {
+ msg_warn("invalid session ticket key name length: %ld",
+ (long) LEN(buffer));
+ VSTRING_RESET(buffer);
+ } else if (*smtpd_cache.cache_timeout <= 0) {
+ status = TLS_MGR_STAT_ERR;
+ VSTRING_RESET(buffer);
+ } else {
+ status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout);
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_KEYBUF,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Entropy request.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len),
+ ATTR_TYPE_END) == 1) {
+ VSTRING_RESET(buffer);
+ if (len <= 0 || len > 255) {
+ msg_warn("bogus seed length \"%d\" in \"%s\" request",
+ len, TLS_MGR_REQ_SEED);
+ } else {
+ VSTRING_SPACE(buffer, len);
+ RAND_bytes((unsigned char *) STR(buffer), len);
+ vstring_set_payload_size(buffer, len);
+ status = TLS_MGR_STAT_OK;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SEED,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Caching policy request.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
+ int cachable = 0;
+ int timeout = 0;
+
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ ATTR_TYPE_END) == 1) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_POLICY);
+ } else {
+ cachable = (ent->cache_info != 0) ? 1 : 0;
+ timeout = *ent->cache_timeout;
+ status = TLS_MGR_STAT_OK;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
+ SEND_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Master trigger. Normally, these triggers arrive only after some
+ * other process requested the tlsmgr's service. The purpose is to
+ * restart the tlsmgr after it aborted due to a fatal run-time error,
+ * so that it can continue its housekeeping even while nothing is
+ * using TLS.
+ *
+ * XXX Which begs the question, if TLS isn't used often, do we need a
+ * tlsmgr background process? It could terminate when the session
+ * caches are empty.
+ */
+ else if (STREQ(STR(request), wakeup)) {
+ if (msg_verbose)
+ msg_info("received master trigger");
+ multi_server_disconnect(client_stream);
+ return; /* NOT: vstream_fflush */
+ }
+ }
+
+ /*
+ * Protocol error.
+ */
+ else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL),
+ ATTR_TYPE_END);
+ }
+ vstream_fflush(client_stream);
+}
+
+/* tlsmgr_pre_init - pre-jail initialization */
+
+static void tlsmgr_pre_init(char *unused_name, char **unused_argv)
+{
+ char *path;
+ struct timeval tv;
+ TLSMGR_SCACHE *ent;
+ VSTRING *redirect;
+ HTABLE *dup_filter;
+ const char *dup_label;
+
+ /*
+ * If nothing else works then at least this will get us a few bits of
+ * entropy.
+ *
+ * XXX This is our first call into the OpenSSL library. We should find out
+ * if this can be moved to the post-jail initialization phase, without
+ * breaking compatibility with existing installations.
+ */
+ GETTIMEOFDAY(&tv);
+ tv.tv_sec ^= getpid();
+ RAND_seed(&tv, sizeof(struct timeval));
+
+ /*
+ * Open the external entropy source. We will not be able to open it again
+ * after we are sent to chroot jail, so we keep it open. Errors are not
+ * fatal. The exchange file (see below) is the only entropy source that
+ * really matters in the long run.
+ *
+ * Security note: we open the entropy source while privileged, but we don't
+ * access the source until after we release privileges. This way, none of
+ * the OpenSSL code gets to execute while we are privileged.
+ */
+ if (*var_tls_rand_source) {
+
+ /*
+ * Source is a random device.
+ */
+ if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) {
+ path = DEV_PATH(var_tls_rand_source);
+ rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT);
+ if (rand_source_dev == 0)
+ msg_warn("cannot open entropy device %s: %m", path);
+ }
+
+ /*
+ * Source is an EGD compatible socket.
+ */
+ else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) {
+ path = EGD_PATH(var_tls_rand_source);
+ rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT);
+ if (rand_source_egd == 0)
+ msg_warn("cannot connect to EGD server %s: %m", path);
+ }
+
+ /*
+ * Source is regular file. We read this only once.
+ */
+ else {
+ rand_source_file =
+ tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT);
+ }
+ } else {
+ msg_warn("no entropy source specified with parameter %s",
+ VAR_TLS_RAND_SOURCE);
+ msg_warn("encryption keys etc. may be predictable");
+ }
+
+ /*
+ * Security: don't create root-owned files that contain untrusted data.
+ * And don't create Postfix-owned files in root-owned directories,
+ * either. We want a correct relationship between (file/directory)
+ * ownership and (file/directory) content.
+ */
+ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
+ redirect = vstring_alloc(100);
+
+ /*
+ * Open the PRNG exchange file before going to jail, but don't use root
+ * privileges. Start the exchange file read/update pseudo thread after
+ * dropping privileges.
+ */
+ if (*var_tls_rand_exch_name) {
+ rand_exch =
+ tls_prng_exch_open(data_redirect_file(redirect,
+ var_tls_rand_exch_name));
+ if (rand_exch == 0)
+ msg_fatal("cannot open PRNG exchange file %s: %m",
+ var_tls_rand_exch_name);
+ }
+
+ /*
+ * Open the session cache files and discard old information before going
+ * to jail, but don't use root privilege. Start the cache maintenance
+ * pseudo threads after dropping privileges.
+ */
+ dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0]));
+ for (ent = cache_table; ent->cache_label; ++ent) {
+ /* Sanitize session timeout */
+ if (*ent->cache_timeout > 0) {
+ if (*ent->cache_timeout < TLS_SESSION_LIFEMIN)
+ *ent->cache_timeout = TLS_SESSION_LIFEMIN;
+ } else {
+ *ent->cache_timeout = 0;
+ }
+ /* External cache database disabled if timeout is non-positive */
+ if (*ent->cache_timeout > 0 && **ent->cache_db) {
+ if ((dup_label = htable_find(dup_filter, *ent->cache_db)) != 0)
+ msg_fatal("do not use the same TLS cache file %s for %s and %s",
+ *ent->cache_db, dup_label, ent->cache_label);
+ htable_enter(dup_filter, *ent->cache_db, ent->cache_label);
+ ent->cache_info =
+ tls_scache_open(data_redirect_map(redirect, *ent->cache_db),
+ ent->cache_label,
+ tls_log_mask(ent->log_param,
+ *ent->log_level) & TLS_LOG_CACHE,
+ *ent->cache_timeout);
+ }
+ }
+ htable_free(dup_filter, (void (*) (void *)) 0);
+
+ /*
+ * Clean up and restore privilege.
+ */
+ vstring_free(redirect);
+ RESTORE_SAVED_EUGID();
+}
+
+/* tlsmgr_post_init - post-jail initialization */
+
+static void tlsmgr_post_init(char *unused_name, char **unused_argv)
+{
+ TLSMGR_SCACHE *ent;
+
+#define NULL_EVENT (0)
+#define NULL_CONTEXT ((char *) 0)
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail,
+ * but before any client requests are serviced. Prevent automatic process
+ * suicide after a limited number of client requests or after a limited
+ * amount of idle time.
+ */
+ var_use_limit = 0;
+ var_idle_limit = 0;
+
+ /*
+ * Start the internal PRNG re-seeding pseudo thread first.
+ */
+ if (*var_tls_rand_source) {
+ if (var_tls_reseed_period > INT_MAX / UCHAR_MAX)
+ var_tls_reseed_period = INT_MAX / UCHAR_MAX;
+ tlsmgr_reseed_event(NULL_EVENT, NULL_CONTEXT);
+ }
+
+ /*
+ * Start the exchange file read/update pseudo thread.
+ */
+ if (*var_tls_rand_exch_name) {
+ if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX)
+ var_tls_prng_exch_period = INT_MAX / UCHAR_MAX;
+ tlsmgr_prng_exch_event(NULL_EVENT, NULL_CONTEXT);
+ }
+
+ /*
+ * Start the cache maintenance pseudo threads last. Strictly speaking
+ * there is nothing to clean up after we truncate the database to zero
+ * length, but early cleanup makes verbose logging more informative (we
+ * get positive confirmation that the cleanup threads are running).
+ */
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (ent->cache_info)
+ tlsmgr_cache_run_event(NULL_EVENT, (void *) ent);
+}
+
+/* tlsmgr_post_accept - announce our protocol */
+
+static void tlsmgr_post_accept(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+
+/* tlsmgr_before_exit - save PRNG state before exit */
+
+static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv)
+{
+
+ /*
+ * Save state before we exit after "postfix reload".
+ */
+ if (rand_exch)
+ tls_prng_exch_update(rand_exch);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0,
+ VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0,
+ VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0,
+ VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0,
+ VAR_LMTP_TLS_SCACHE_DB, DEF_LMTP_TLS_SCACHE_DB, &var_lmtp_tls_scache_db, 0, 0,
+ VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
+ VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_lmtp_tls_loglevel, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0,
+ VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0,
+ VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, MAX_SMTPD_TLS_SCACHETIME,
+ VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, MAX_SMTP_TLS_SCACHETIME,
+ VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, MAX_LMTP_TLS_SCACHETIME,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Use the multi service skeleton, and require that no-one else is
+ * monitoring our service port while this process runs.
+ */
+ multi_server_main(argc, argv, tlsmgr_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_PRE_INIT(tlsmgr_pre_init),
+ CA_MAIL_SERVER_POST_INIT(tlsmgr_post_init),
+ CA_MAIL_SERVER_POST_ACCEPT(tlsmgr_post_accept),
+ CA_MAIL_SERVER_EXIT(tlsmgr_before_exit),
+ CA_MAIL_SERVER_LOOP(tlsmgr_loop),
+ CA_MAIL_SERVER_SOLITARY,
+ 0);
+}
+
+#else
+
+/* tlsmgr_service - respond to external trigger(s), non-TLS version */
+
+static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service,
+ char **unused_argv)
+{
+ msg_info("TLS support is not compiled in -- exiting");
+}
+
+/* main - the main program, non-TLS version */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * 200411 We can't simply use msg_fatal() here, because the logging
+ * hasn't been initialized. The text would disappear because stderr is
+ * redirected to /dev/null.
+ *
+ * We invoke multi_server_main() to complete program initialization
+ * (including logging) and then invoke the tlsmgr_service() routine to
+ * log the message that says why this program will not run.
+ */
+ multi_server_main(argc, argv, tlsmgr_service,
+ 0);
+}
+
+#endif
diff --git a/src/tlsproxy/.indent.pro b/src/tlsproxy/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tlsproxy/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tlsproxy/Makefile.in b/src/tlsproxy/Makefile.in
new file mode 100644
index 0000000..c72aa81
--- /dev/null
+++ b/src/tlsproxy/Makefile.in
@@ -0,0 +1,117 @@
+SHELL = /bin/sh
+SRCS = tlsproxy.c tlsproxy_state.c
+OBJS = tlsproxy.o tlsproxy_state.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = tlsproxy
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../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)
+
+tests: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+tlsproxy.o: ../../include/argv.h
+tlsproxy.o: ../../include/attr.h
+tlsproxy.o: ../../include/been_here.h
+tlsproxy.o: ../../include/check_arg.h
+tlsproxy.o: ../../include/dns.h
+tlsproxy.o: ../../include/events.h
+tlsproxy.o: ../../include/htable.h
+tlsproxy.o: ../../include/iostuff.h
+tlsproxy.o: ../../include/mail_conf.h
+tlsproxy.o: ../../include/mail_params.h
+tlsproxy.o: ../../include/mail_proto.h
+tlsproxy.o: ../../include/mail_server.h
+tlsproxy.o: ../../include/mail_version.h
+tlsproxy.o: ../../include/msg.h
+tlsproxy.o: ../../include/myaddrinfo.h
+tlsproxy.o: ../../include/mymalloc.h
+tlsproxy.o: ../../include/name_code.h
+tlsproxy.o: ../../include/name_mask.h
+tlsproxy.o: ../../include/nbbio.h
+tlsproxy.o: ../../include/nvtable.h
+tlsproxy.o: ../../include/sock_addr.h
+tlsproxy.o: ../../include/split_at.h
+tlsproxy.o: ../../include/sys_defs.h
+tlsproxy.o: ../../include/tls.h
+tlsproxy.o: ../../include/tls_proxy.h
+tlsproxy.o: ../../include/vbuf.h
+tlsproxy.o: ../../include/vstream.h
+tlsproxy.o: ../../include/vstring.h
+tlsproxy.o: tlsproxy.c
+tlsproxy.o: tlsproxy.h
+tlsproxy_state.o: ../../include/argv.h
+tlsproxy_state.o: ../../include/attr.h
+tlsproxy_state.o: ../../include/check_arg.h
+tlsproxy_state.o: ../../include/dns.h
+tlsproxy_state.o: ../../include/events.h
+tlsproxy_state.o: ../../include/htable.h
+tlsproxy_state.o: ../../include/mail_conf.h
+tlsproxy_state.o: ../../include/mail_server.h
+tlsproxy_state.o: ../../include/msg.h
+tlsproxy_state.o: ../../include/myaddrinfo.h
+tlsproxy_state.o: ../../include/mymalloc.h
+tlsproxy_state.o: ../../include/name_code.h
+tlsproxy_state.o: ../../include/name_mask.h
+tlsproxy_state.o: ../../include/nbbio.h
+tlsproxy_state.o: ../../include/nvtable.h
+tlsproxy_state.o: ../../include/sock_addr.h
+tlsproxy_state.o: ../../include/sys_defs.h
+tlsproxy_state.o: ../../include/tls.h
+tlsproxy_state.o: ../../include/tls_proxy.h
+tlsproxy_state.o: ../../include/vbuf.h
+tlsproxy_state.o: ../../include/vstream.h
+tlsproxy_state.o: ../../include/vstring.h
+tlsproxy_state.o: tlsproxy.h
+tlsproxy_state.o: tlsproxy_state.c
diff --git a/src/tlsproxy/tlsproxy.c b/src/tlsproxy/tlsproxy.c
new file mode 100644
index 0000000..ccb3804
--- /dev/null
+++ b/src/tlsproxy/tlsproxy.c
@@ -0,0 +1,1968 @@
+/*++
+/* NAME
+/* tlsproxy 8
+/* SUMMARY
+/* Postfix TLS proxy
+/* SYNOPSIS
+/* \fBtlsproxy\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBtlsproxy\fR(8) server implements a two-way TLS proxy. It
+/* is used by the \fBpostscreen\fR(8) server to talk SMTP-over-TLS
+/* with remote SMTP clients that are not allowlisted (including
+/* clients whose allowlist status has expired), and by the
+/* \fBsmtp\fR(8) client to support TLS connection reuse, but it
+/* should also work for non-SMTP protocols.
+/*
+/* Although one \fBtlsproxy\fR(8) process can serve multiple
+/* sessions at the same time, it is a good idea to allow the
+/* number of processes to increase with load, so that the
+/* service remains responsive.
+/* PROTOCOL EXAMPLE
+/* .ad
+/* .fi
+/* The example below concerns \fBpostscreen\fR(8). However,
+/* the \fBtlsproxy\fR(8) server is agnostic of the application
+/* protocol, and the example is easily adapted to other
+/* applications.
+/*
+/* After receiving a valid remote SMTP client STARTTLS command,
+/* the \fBpostscreen\fR(8) server sends the remote SMTP client
+/* endpoint string, the requested role (server), and the
+/* requested timeout to \fBtlsproxy\fR(8). \fBpostscreen\fR(8)
+/* then receives a "TLS available" indication from \fBtlsproxy\fR(8).
+/* If the TLS service is available, \fBpostscreen\fR(8) sends
+/* the remote SMTP client file descriptor to \fBtlsproxy\fR(8),
+/* and sends the plaintext 220 greeting to the remote SMTP
+/* client. This triggers TLS negotiations between the remote
+/* SMTP client and \fBtlsproxy\fR(8). Upon completion of the
+/* TLS-level handshake, \fBtlsproxy\fR(8) translates between
+/* plaintext from/to \fBpostscreen\fR(8) and ciphertext to/from
+/* the remote SMTP client.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBtlsproxy\fR(8) server is moderately security-sensitive.
+/* It talks to untrusted clients on the network. The process
+/* can be run chrooted at fixed low privilege.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* as \fBtlsproxy\fR(8) processes may run for a long time
+/* depending on mail server load. 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.
+/* STARTTLS GLOBAL CONTROLS
+/* .ad
+/* .fi
+/* The following settings are global and therefore cannot be
+/* overruled by information specified in a \fBtlsproxy\fR(8)
+/* client request.
+/* .IP "\fBtls_append_default_CA (no)\fR"
+/* Append the system-supplied default Certification Authority
+/* certificates to the ones specified with *_tls_CApath or *_tls_CAfile.
+/* .IP "\fBtls_daemon_random_bytes (32)\fR"
+/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8)
+/* process requests from the \fBtlsmgr\fR(8) server in order to seed its
+/* internal pseudo random number generator (PRNG).
+/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "high" grade ciphers.
+/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "medium" or higher grade ciphers.
+/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "low" or higher grade ciphers.
+/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "export" or higher grade ciphers.
+/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR"
+/* The OpenSSL cipherlist for "NULL" grade ciphers that provide
+/* authentication without encryption.
+/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for sensibly
+/* strong
+/* ephemeral ECDH key exchange.
+/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for maximally
+/* strong
+/* ephemeral ECDH key exchange.
+/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR"
+/* List or bit-mask of OpenSSL bug work-arounds to disable.
+/* .IP "\fBtls_preempt_cipherlist (no)\fR"
+/* With SSLv3 and later, use the Postfix SMTP server's cipher
+/* preference order instead of the remote client's cipher preference
+/* order.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBtls_legacy_public_key_fingerprints (no)\fR"
+/* A temporary migration aid for sites that use certificate
+/* \fIpublic-key\fR fingerprints with Postfix 2.9.0..2.9.5, which use
+/* an incorrect algorithm.
+/* .PP
+/* Available in Postfix version 2.11-3.1:
+/* .IP "\fBtls_dane_digest_agility (on)\fR"
+/* Configure RFC7671 DANE TLSA digest algorithm agility.
+/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR"
+/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain
+/* digests of trust-anchors with certificate usage "2".
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR"
+/* The name of the \fBtlsmgr\fR(8) service entry in master.cf.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR"
+/* Algorithm used to encrypt RFC5077 TLS session tickets.
+/* .IP "\fBopenssl_path (openssl)\fR"
+/* The location of the OpenSSL command line program \fBopenssl\fR(1).
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR"
+/* The prioritized list of elliptic curves supported by the Postfix
+/* SMTP client and server.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBtls_server_sni_maps (empty)\fR"
+/* Optional lookup tables that map names received from remote SMTP
+/* clients via the TLS Server Name Indication (SNI) extension to the
+/* appropriate keys and certificate chains.
+/* .PP
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* STARTTLS SERVER CONTROLS
+/* .ad
+/* .fi
+/* These settings are clones of Postfix SMTP server settings.
+/* They allow \fBtlsproxy\fR(8) to load the same certificate
+/* and private key information as the Postfix SMTP server,
+/* before dropping privileges, so that the key files can be
+/* kept read-only for root. These settings can currently not
+/* be overruled by information in a \fBtlsproxy\fR(8) client
+/* request, but that limitation may be removed in a future
+/* version.
+/* .IP "\fBtlsproxy_tls_CAfile ($smtpd_tls_CAfile)\fR"
+/* A file containing (PEM format) CA certificates of root CAs
+/* trusted to sign either remote SMTP client certificates or intermediate
+/* CA certificates.
+/* .IP "\fBtlsproxy_tls_CApath ($smtpd_tls_CApath)\fR"
+/* A directory containing (PEM format) CA certificates of root CAs
+/* trusted to sign either remote SMTP client certificates or intermediate
+/* CA certificates.
+/* .IP "\fBtlsproxy_tls_always_issue_session_ids ($smtpd_tls_always_issue_session_ids)\fR"
+/* Force the Postfix \fBtlsproxy\fR(8) server to issue a TLS session id,
+/* even when TLS session caching is turned off.
+/* .IP "\fBtlsproxy_tls_ask_ccert ($smtpd_tls_ask_ccert)\fR"
+/* Ask a remote SMTP client for a client certificate.
+/* .IP "\fBtlsproxy_tls_ccert_verifydepth ($smtpd_tls_ccert_verifydepth)\fR"
+/* The verification depth for remote SMTP client certificates.
+/* .IP "\fBtlsproxy_tls_cert_file ($smtpd_tls_cert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server RSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_ciphers ($smtpd_tls_ciphers)\fR"
+/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server
+/* will use with opportunistic TLS encryption.
+/* .IP "\fBtlsproxy_tls_dcert_file ($smtpd_tls_dcert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server DSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_dh1024_param_file ($smtpd_tls_dh1024_param_file)\fR"
+/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server
+/* should use with non-export EDH ciphers.
+/* .IP "\fBtlsproxy_tls_dh512_param_file ($smtpd_tls_dh512_param_file)\fR"
+/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server
+/* should use with export-grade EDH ciphers.
+/* .IP "\fBtlsproxy_tls_dkey_file ($smtpd_tls_dkey_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server DSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_eccert_file ($smtpd_tls_eccert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_eckey_file ($smtpd_tls_eckey_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_eecdh_grade ($smtpd_tls_eecdh_grade)\fR"
+/* The Postfix \fBtlsproxy\fR(8) server security grade for ephemeral
+/* elliptic-curve Diffie-Hellman (EECDH) key exchange.
+/* .IP "\fBtlsproxy_tls_exclude_ciphers ($smtpd_tls_exclude_ciphers)\fR"
+/* List of ciphers or cipher types to exclude from the \fBtlsproxy\fR(8)
+/* server cipher list at all TLS security levels.
+/* .IP "\fBtlsproxy_tls_fingerprint_digest ($smtpd_tls_fingerprint_digest)\fR"
+/* The message digest algorithm to construct remote SMTP
+/* client-certificate
+/* fingerprints.
+/* .IP "\fBtlsproxy_tls_key_file ($smtpd_tls_key_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) server RSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_tls_loglevel ($smtpd_tls_loglevel)\fR"
+/* Enable additional Postfix \fBtlsproxy\fR(8) server logging of TLS
+/* activity.
+/* .IP "\fBtlsproxy_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR"
+/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server
+/* will use with mandatory TLS encryption.
+/* .IP "\fBtlsproxy_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR"
+/* Additional list of ciphers or cipher types to exclude from the
+/* \fBtlsproxy\fR(8) server cipher list at mandatory TLS security levels.
+/* .IP "\fBtlsproxy_tls_mandatory_protocols ($smtpd_tls_mandatory_protocols)\fR"
+/* The SSL/TLS protocols accepted by the Postfix \fBtlsproxy\fR(8) server
+/* with mandatory TLS encryption.
+/* .IP "\fBtlsproxy_tls_protocols ($smtpd_tls_protocols)\fR"
+/* List of TLS protocols that the Postfix \fBtlsproxy\fR(8) server will
+/* exclude or include with opportunistic TLS encryption.
+/* .IP "\fBtlsproxy_tls_req_ccert ($smtpd_tls_req_ccert)\fR"
+/* With mandatory TLS encryption, require a trusted remote SMTP
+/* client certificate in order to allow TLS connections to proceed.
+/* .IP "\fBtlsproxy_tls_security_level ($smtpd_tls_security_level)\fR"
+/* The SMTP TLS security level for the Postfix \fBtlsproxy\fR(8) server;
+/* when a non-empty value is specified, this overrides the obsolete
+/* parameters smtpd_use_tls and smtpd_enforce_tls.
+/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR"
+/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate
+/* chains in PEM format.
+/* STARTTLS CLIENT CONTROLS
+/* .ad
+/* .fi
+/* These settings are clones of Postfix SMTP client settings.
+/* They allow \fBtlsproxy\fR(8) to load the same certificate
+/* and private key information as the Postfix SMTP client,
+/* before dropping privileges, so that the key files can be
+/* kept read-only for root. Some settings may be overruled by
+/* information in a \fBtlsproxy\fR(8) client request.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBtlsproxy_client_CAfile ($smtp_tls_CAfile)\fR"
+/* A file containing CA certificates of root CAs trusted to sign
+/* either remote TLS server certificates or intermediate CA certificates.
+/* .IP "\fBtlsproxy_client_CApath ($smtp_tls_CApath)\fR"
+/* Directory with PEM format Certification Authority certificates
+/* that the Postfix \fBtlsproxy\fR(8) client uses to verify a remote TLS
+/* server certificate.
+/* .IP "\fBtlsproxy_client_chain_files ($smtp_tls_chain_files)\fR"
+/* Files with the Postfix \fBtlsproxy\fR(8) client keys and certificate
+/* chains in PEM format.
+/* .IP "\fBtlsproxy_client_cert_file ($smtp_tls_cert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client RSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_key_file ($smtp_tls_key_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client RSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_dcert_file ($smtp_tls_dcert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client DSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_dkey_file ($smtp_tls_dkey_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client DSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_eccert_file ($smtp_tls_eccert_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA certificate in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_eckey_file ($smtp_tls_eckey_file)\fR"
+/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA private key in PEM
+/* format.
+/* .IP "\fBtlsproxy_client_fingerprint_digest ($smtp_tls_fingerprint_digest)\fR"
+/* The message digest algorithm used to construct remote TLS server
+/* certificate fingerprints.
+/* .IP "\fBtlsproxy_client_loglevel ($smtp_tls_loglevel)\fR"
+/* Enable additional Postfix \fBtlsproxy\fR(8) client logging of TLS
+/* activity.
+/* .IP "\fBtlsproxy_client_loglevel_parameter (smtp_tls_loglevel)\fR"
+/* The name of the parameter that provides the tlsproxy_client_loglevel
+/* value.
+/* .IP "\fBtlsproxy_client_scert_verifydepth ($smtp_tls_scert_verifydepth)\fR"
+/* The verification depth for remote TLS server certificates.
+/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR"
+/* Opportunistic mode: use TLS when a remote server announces TLS
+/* support.
+/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR"
+/* Enforcement mode: require that SMTP servers use TLS encryption.
+/* .IP "\fBtlsproxy_client_per_site ($smtp_tls_per_site)\fR"
+/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+/* usage policy by next-hop destination and by remote TLS server
+/* hostname.
+/* .PP
+/* Available in Postfix version 3.4-3.6:
+/* .IP "\fBtlsproxy_client_level ($smtp_tls_security_level)\fR"
+/* The default TLS security level for the Postfix \fBtlsproxy\fR(8)
+/* client.
+/* .IP "\fBtlsproxy_client_policy ($smtp_tls_policy_maps)\fR"
+/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+/* security policy by next-hop destination.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBtlsproxy_client_security_level ($smtp_tls_security_level)\fR"
+/* The default TLS security level for the Postfix \fBtlsproxy\fR(8)
+/* client.
+/* .IP "\fBtlsproxy_client_policy_maps ($smtp_tls_policy_maps)\fR"
+/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+/* security policy by next-hop destination.
+/* OBSOLETE STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* These parameters are supported for compatibility with
+/* \fBsmtpd\fR(8) legacy parameters.
+/* .IP "\fBtlsproxy_use_tls ($smtpd_use_tls)\fR"
+/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+/* but do not require that clients use TLS encryption.
+/* .IP "\fBtlsproxy_enforce_tls ($smtpd_enforce_tls)\fR"
+/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+/* require that clients use TLS encryption.
+/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR"
+/* Opportunistic mode: use TLS when a remote server announces TLS
+/* support.
+/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR"
+/* Enforcement mode: require that SMTP servers use TLS encryption.
+/* RESOURCE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBtlsproxy_watchdog_timeout (10s)\fR"
+/* How much time a \fBtlsproxy\fR(8) process may take to process local
+/* or remote I/O before it is terminated by a built-in watchdog timer.
+/* 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 "\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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* postscreen(8), Postfix zombie blocker
+/* smtpd(8), Postfix SMTP server
+/* postconf(5), configuration parameters
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 2.8.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <nbbio.h>
+#include <mymalloc.h>
+#include <split_at.h>
+
+ /*
+ * Global library.
+ */
+#include <been_here.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_version.h>
+
+ /*
+ * Master library.
+ */
+#include <mail_server.h>
+
+ /*
+ * TLS library.
+ */
+#ifdef USE_TLS
+#define TLS_INTERNAL /* XXX */
+#include <tls.h>
+#include <tls_proxy.h>
+
+ /*
+ * Application-specific.
+ */
+#include <tlsproxy.h>
+
+ /*
+ * Tunable parameters. We define our clones of the smtpd(8) parameters to
+ * avoid any confusion about which parameters are used by this program.
+ */
+int var_smtpd_tls_ccert_vd;
+char *var_smtpd_tls_loglevel;
+bool var_smtpd_use_tls;
+bool var_smtpd_enforce_tls;
+bool var_smtpd_tls_ask_ccert;
+bool var_smtpd_tls_req_ccert;
+bool var_smtpd_tls_set_sessid;
+char *var_smtpd_relay_ccerts;
+char *var_smtpd_tls_chain_files;
+char *var_smtpd_tls_cert_file;
+char *var_smtpd_tls_key_file;
+char *var_smtpd_tls_dcert_file;
+char *var_smtpd_tls_dkey_file;
+char *var_smtpd_tls_eccert_file;
+char *var_smtpd_tls_eckey_file;
+char *var_smtpd_tls_CAfile;
+char *var_smtpd_tls_CApath;
+char *var_smtpd_tls_ciph;
+char *var_smtpd_tls_mand_ciph;
+char *var_smtpd_tls_excl_ciph;
+char *var_smtpd_tls_mand_excl;
+char *var_smtpd_tls_proto;
+char *var_smtpd_tls_mand_proto;
+char *var_smtpd_tls_dh512_param_file;
+char *var_smtpd_tls_dh1024_param_file;
+char *var_smtpd_tls_eecdh;
+char *var_smtpd_tls_fpt_dgst;
+char *var_smtpd_tls_level;
+
+int var_tlsp_tls_ccert_vd;
+char *var_tlsp_tls_loglevel;
+bool var_tlsp_use_tls;
+bool var_tlsp_enforce_tls;
+bool var_tlsp_tls_ask_ccert;
+bool var_tlsp_tls_req_ccert;
+bool var_tlsp_tls_set_sessid;
+char *var_tlsp_tls_chain_files;
+char *var_tlsp_tls_cert_file;
+char *var_tlsp_tls_key_file;
+char *var_tlsp_tls_dcert_file;
+char *var_tlsp_tls_dkey_file;
+char *var_tlsp_tls_eccert_file;
+char *var_tlsp_tls_eckey_file;
+char *var_tlsp_tls_CAfile;
+char *var_tlsp_tls_CApath;
+char *var_tlsp_tls_ciph;
+char *var_tlsp_tls_mand_ciph;
+char *var_tlsp_tls_excl_ciph;
+char *var_tlsp_tls_mand_excl;
+char *var_tlsp_tls_proto;
+char *var_tlsp_tls_mand_proto;
+char *var_tlsp_tls_dh512_param_file;
+char *var_tlsp_tls_dh1024_param_file;
+char *var_tlsp_tls_eecdh;
+char *var_tlsp_tls_fpt_dgst;
+char *var_tlsp_tls_level;
+
+int var_tlsp_watchdog;
+
+ /*
+ * Defaults for tlsp_clnt_*.
+ */
+char *var_smtp_tls_loglevel;
+int var_smtp_tls_scert_vd;
+char *var_smtp_tls_chain_files;
+char *var_smtp_tls_cert_file;
+char *var_smtp_tls_key_file;
+char *var_smtp_tls_dcert_file;
+char *var_smtp_tls_dkey_file;
+char *var_smtp_tls_eccert_file;
+char *var_smtp_tls_eckey_file;
+char *var_smtp_tls_CAfile;
+char *var_smtp_tls_CApath;
+char *var_smtp_tls_fpt_dgst;
+char *var_smtp_tls_level;
+bool var_smtp_use_tls;
+bool var_smtp_enforce_tls;
+char *var_smtp_tls_per_site;
+char *var_smtp_tls_policy;
+
+char *var_tlsp_clnt_loglevel;
+char *var_tlsp_clnt_logparam;
+int var_tlsp_clnt_scert_vd;
+char *var_tlsp_clnt_chain_files;
+char *var_tlsp_clnt_cert_file;
+char *var_tlsp_clnt_key_file;
+char *var_tlsp_clnt_dcert_file;
+char *var_tlsp_clnt_dkey_file;
+char *var_tlsp_clnt_eccert_file;
+char *var_tlsp_clnt_eckey_file;
+char *var_tlsp_clnt_CAfile;
+char *var_tlsp_clnt_CApath;
+char *var_tlsp_clnt_fpt_dgst;
+char *var_tlsp_clnt_level;
+bool var_tlsp_clnt_use_tls;
+bool var_tlsp_clnt_enforce_tls;
+char *var_tlsp_clnt_per_site;
+char *var_tlsp_clnt_policy;
+
+ /*
+ * TLS per-process status.
+ */
+static TLS_APPL_STATE *tlsp_server_ctx;
+static bool tlsp_pre_jail_done;
+static int ask_client_cert;
+static char *tlsp_pre_jail_client_param_key; /* pre-jail global params */
+static char *tlsp_pre_jail_client_init_key; /* pre-jail init props */
+
+ /*
+ * TLS per-client status.
+ */
+static HTABLE *tlsp_client_app_cache; /* per-client init props */
+static BH_TABLE *tlsp_params_mismatch_filter; /* per-client nag filter */
+
+ /*
+ * Error handling: if a function detects an error, then that function is
+ * responsible for destroying TLSP_STATE. Exceptions to this principle are
+ * indicated in the code.
+ */
+
+ /*
+ * Internal status API.
+ */
+#define TLSP_STAT_OK 0
+#define TLSP_STAT_ERR (-1)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * The code that implements the TLS engine looks simpler than expected. That
+ * is the result of a great deal of effort, mainly in design and analysis.
+ *
+ * The initial use case was to provide TLS support for postscreen(8).
+ *
+ * By design, postscreen(8) is an event-driven server that must scale up to a
+ * large number of clients. This means that postscreen(8) must avoid doing
+ * CPU-intensive operations such as those in OpenSSL.
+ *
+ * tlsproxy(8) runs the OpenSSL code on behalf of postscreen(8), translating
+ * plaintext SMTP messages from postscreen(8) into SMTP-over-TLS messages to
+ * the remote SMTP client, and vice versa. As long as postscreen(8) does not
+ * receive email messages, the cost of doing TLS operations will be modest.
+ *
+ * Like postscreen(8), one tlsproxy(8) process services multiple remote SMTP
+ * clients. Unlike postscreen(8), there can be more than one tlsproxy(8)
+ * process, although their number is meant to be much smaller than the
+ * number of remote SMTP clients that talk TLS.
+ *
+ * As with postscreen(8), all I/O must be event-driven: encrypted traffic
+ * between tlsproxy(8) and remote SMTP clients, and plaintext traffic
+ * between tlsproxy(8) and postscreen(8). Event-driven plaintext I/O is
+ * straightforward enough that it could be abstracted away with the nbbio(3)
+ * module.
+ *
+ * The event-driven TLS I/O implementation is founded on on-line OpenSSL
+ * documentation, supplemented by statements from OpenSSL developers on
+ * public mailing lists. After some field experience with this code, we may
+ * be able to factor it out as a library module, like nbbio(3), that can
+ * become part of the TLS library.
+ *
+ * Later in the life cycle, tlsproxy(8) has also become an enabler for TLS
+ * connection reuse across different SMTP client processes.
+ */
+
+static void tlsp_ciphertext_event(int, void *);
+
+#define TLSP_INIT_TIMEOUT 100
+
+static void tlsp_plaintext_event(int event, void *context);
+
+/* tlsp_drain - delayed exit after "postfix reload" */
+
+static void tlsp_drain(char *unused_service, char **unused_argv)
+{
+ int count;
+
+ /*
+ * After "postfix reload", complete work-in-progress in the background,
+ * instead of dropping already-accepted connections on the floor.
+ *
+ * All error retry counts shall be limited. Instead of blocking here, we
+ * could retry failed fork() operations in the event call-back routines,
+ * but we don't need perfection. The host system is severely overloaded
+ * and service levels are already way down.
+ */
+ for (count = 0; /* see below */ ; count++) {
+ if (count >= 5) {
+ msg_fatal("fork: %m");
+ } else if (event_server_drain() != 0) {
+ msg_warn("fork: %m");
+ sleep(1);
+ continue;
+ } else {
+ return;
+ }
+ }
+}
+
+/* tlsp_eval_tls_error - translate TLS "error" result into action */
+
+static int tlsp_eval_tls_error(TLSP_STATE *state, int err)
+{
+ int ciphertext_fd = state->ciphertext_fd;
+
+ /*
+ * The ciphertext file descriptor is in non-blocking mode, meaning that
+ * each SSL_accept/connect/read/write/shutdown request may return an
+ * "error" indication that it needs to read or write more ciphertext. The
+ * purpose of this routine is to translate those "error" indications into
+ * the appropriate read/write/timeout event requests.
+ */
+ switch (err) {
+
+ /*
+ * No error means a successful SSL_accept/connect/shutdown request or
+ * sequence of SSL_read/write requests. Disable read/write events on
+ * the ciphertext stream. Keep the ciphertext stream timer alive as a
+ * safety mechanism for the case that the plaintext pseudothreads get
+ * stuck.
+ */
+ case SSL_ERROR_NONE:
+ if (state->ssl_last_err != SSL_ERROR_NONE) {
+ event_disable_readwrite(ciphertext_fd);
+ event_request_timer(tlsp_ciphertext_event, (void *) state,
+ state->timeout);
+ state->ssl_last_err = SSL_ERROR_NONE;
+ }
+ return (TLSP_STAT_OK);
+
+ /*
+ * The TLS engine wants to write to the network. Turn on
+ * write/timeout events on the ciphertext stream.
+ */
+ case SSL_ERROR_WANT_WRITE:
+ if (state->ssl_last_err == SSL_ERROR_WANT_READ)
+ event_disable_readwrite(ciphertext_fd);
+ if (state->ssl_last_err != SSL_ERROR_WANT_WRITE) {
+ event_enable_write(ciphertext_fd, tlsp_ciphertext_event,
+ (void *) state);
+ state->ssl_last_err = SSL_ERROR_WANT_WRITE;
+ }
+ event_request_timer(tlsp_ciphertext_event, (void *) state,
+ state->timeout);
+ return (TLSP_STAT_OK);
+
+ /*
+ * The TLS engine wants to read from the network. Turn on
+ * read/timeout events on the ciphertext stream.
+ */
+ case SSL_ERROR_WANT_READ:
+ if (state->ssl_last_err == SSL_ERROR_WANT_WRITE)
+ event_disable_readwrite(ciphertext_fd);
+ if (state->ssl_last_err != SSL_ERROR_WANT_READ) {
+ event_enable_read(ciphertext_fd, tlsp_ciphertext_event,
+ (void *) state);
+ state->ssl_last_err = SSL_ERROR_WANT_READ;
+ }
+ event_request_timer(tlsp_ciphertext_event, (void *) state,
+ state->timeout);
+ return (TLSP_STAT_OK);
+
+ /*
+ * Some error. Self-destruct. This automagically cleans up all
+ * pending read/write and timeout event requests, making state a
+ * dangling pointer.
+ */
+ case SSL_ERROR_SSL:
+ tls_print_errors();
+ /* FALLTHROUGH */
+ default:
+
+ /*
+ * Allow buffered-up plaintext output to trickle out. Permanently
+ * disable read/write activity on the ciphertext stream, so that this
+ * function will no longer be called. Keep the ciphertext stream
+ * timer alive as a safety mechanism for the case that the plaintext
+ * pseudothreads get stuck. Return into tlsp_strategy(), which will
+ * enable plaintext write events.
+ */
+#define TLSP_CAN_TRICKLE_OUT_PLAINTEXT(buf) \
+ ((buf) && !NBBIO_ERROR_FLAGS(buf) && NBBIO_WRITE_PEND(buf))
+
+ if (TLSP_CAN_TRICKLE_OUT_PLAINTEXT(state->plaintext_buf)) {
+ event_disable_readwrite(ciphertext_fd);
+ event_request_timer(tlsp_ciphertext_event, (void *) state,
+ state->timeout);
+ state->flags |= TLSP_FLAG_NO_MORE_CIPHERTEXT_IO;
+ return (TLSP_STAT_OK);
+ }
+ tlsp_state_free(state);
+ return (TLSP_STAT_ERR);
+ }
+}
+
+/* tlsp_post_handshake - post-handshake processing */
+
+static int tlsp_post_handshake(TLSP_STATE *state)
+{
+
+ /*
+ * Do not assume that tls_server_post_accept() and
+ * tls_client_post_connect() will always succeed.
+ */
+ if (state->is_server_role)
+ state->tls_context = tls_server_post_accept(state->tls_context);
+ else
+ state->tls_context = tls_client_post_connect(state->tls_context,
+ state->client_start_props);
+ if (state->tls_context == 0) {
+ tlsp_state_free(state);
+ return (TLSP_STAT_ERR);
+ }
+
+ /*
+ * Report TLS handshake results to the tlsproxy client.
+ *
+ * Security: this sends internal data over the same local plaintext stream
+ * that will also be used for sending decrypted remote content from an
+ * arbitrary remote peer. For this reason we enable decrypted I/O only
+ * after reporting the TLS handshake results. The Postfix attribute
+ * protocol is robust enough that an attacker cannot append content.
+ */
+ if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0
+ && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_context_print,
+ (void *) state->tls_context),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(state->plaintext_stream) != 0)) {
+ msg_warn("cannot send TLS context: %m");
+ tlsp_state_free(state);
+ return (TLSP_STAT_ERR);
+ }
+
+ /*
+ * Initialize plaintext-related session state. Once we have this behind
+ * us, the TLSP_STATE destructor will automagically clean up requests for
+ * plaintext read/write/timeout events, which makes error recovery
+ * easier.
+ */
+ state->plaintext_buf =
+ nbbio_create(vstream_fileno(state->plaintext_stream),
+ VSTREAM_BUFSIZE, state->server_id,
+ tlsp_plaintext_event,
+ (void *) state);
+ return (TLSP_STAT_OK);
+}
+
+/* tlsp_strategy - decide what to read or write next. */
+
+static void tlsp_strategy(TLSP_STATE *state)
+{
+ TLS_SESS_STATE *tls_context = state->tls_context;
+ NBBIO *plaintext_buf;
+ int ssl_stat;
+ int ssl_read_err;
+ int ssl_write_err;
+ int handshake_err;
+
+ /*
+ * This function is called after every ciphertext or plaintext event, to
+ * schedule new ciphertext or plaintext I/O.
+ */
+
+ /*
+ * Try to make an SSL I/O request. If this fails with SSL_ERROR_WANT_READ
+ * or SSL_ERROR_WANT_WRITE, enable ciphertext read or write events, and
+ * retry the SSL I/O request in a later tlsp_strategy() call.
+ */
+ if ((state->flags & TLSP_FLAG_NO_MORE_CIPHERTEXT_IO) == 0) {
+
+ /*
+ * Do not enable plain-text I/O before completing the TLS handshake.
+ * Otherwise the remote peer can prepend plaintext to the optional
+ * TLS_SESS_STATE object.
+ */
+ if (state->flags & TLSP_FLAG_DO_HANDSHAKE) {
+ state->timeout = state->handshake_timeout;
+ ERR_clear_error();
+ if (state->is_server_role)
+ ssl_stat = SSL_accept(tls_context->con);
+ else
+ ssl_stat = SSL_connect(tls_context->con);
+ if (ssl_stat != 1) {
+ handshake_err = SSL_get_error(tls_context->con, ssl_stat);
+ tlsp_eval_tls_error(state, handshake_err);
+ /* At this point, state could be a dangling pointer. */
+ return;
+ }
+ state->flags &= ~TLSP_FLAG_DO_HANDSHAKE;
+ state->timeout = state->session_timeout;
+ if (tlsp_post_handshake(state) != TLSP_STAT_OK) {
+ /* At this point, state is a dangling pointer. */
+ return;
+ }
+ }
+
+ /*
+ * Shutdown and self-destruct after NBBIO error. This automagically
+ * cleans up all pending read/write and timeout event requests.
+ * Before shutting down TLS, we stop all plain-text I/O events but
+ * keep the NBBIO error flags.
+ */
+ plaintext_buf = state->plaintext_buf;
+ if (NBBIO_ERROR_FLAGS(plaintext_buf)) {
+ if (NBBIO_ACTIVE_FLAGS(plaintext_buf))
+ nbbio_disable_readwrite(state->plaintext_buf);
+ ERR_clear_error();
+ if (!SSL_in_init(tls_context->con)
+ && (ssl_stat = SSL_shutdown(tls_context->con)) < 0) {
+ handshake_err = SSL_get_error(tls_context->con, ssl_stat);
+ tlsp_eval_tls_error(state, handshake_err);
+ /* At this point, state could be a dangling pointer. */
+ return;
+ }
+ tlsp_state_free(state);
+ return;
+ }
+
+ /*
+ * Try to move data from the plaintext input buffer to the TLS
+ * engine.
+ *
+ * XXX We're supposed to repeat the exact same SSL_write() call
+ * arguments after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
+ * result. Rumor has it that this is because each SSL_write() call
+ * reads from the buffer incrementally, and returns > 0 only after
+ * the final byte is processed. Rumor also has it that setting
+ * SSL_MODE_ENABLE_PARTIAL_WRITE and
+ * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and
+ * that repeating the request with an increased request size is OK.
+ * Unfortunately all this is not or poorly documented, and one has to
+ * rely on statements from OpenSSL developers in public mailing
+ * archives.
+ */
+ ssl_write_err = SSL_ERROR_NONE;
+ while (NBBIO_READ_PEND(plaintext_buf) > 0) {
+ ERR_clear_error();
+ ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf),
+ NBBIO_READ_PEND(plaintext_buf));
+ ssl_write_err = SSL_get_error(tls_context->con, ssl_stat);
+ if (ssl_write_err != SSL_ERROR_NONE)
+ break;
+ /* Allow the plaintext pseudothread to read more data. */
+ NBBIO_READ_PEND(plaintext_buf) -= ssl_stat;
+ if (NBBIO_READ_PEND(plaintext_buf) > 0)
+ memmove(NBBIO_READ_BUF(plaintext_buf),
+ NBBIO_READ_BUF(plaintext_buf) + ssl_stat,
+ NBBIO_READ_PEND(plaintext_buf));
+ }
+
+ /*
+ * Try to move data from the TLS engine to the plaintext output
+ * buffer. Note: data may arrive as a side effect of calling
+ * SSL_write(), therefore we call SSL_read() after calling
+ * SSL_write().
+ *
+ * XXX We're supposed to repeat the exact same SSL_read() call arguments
+ * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This
+ * supposedly means that our plaintext writer must not memmove() the
+ * plaintext output buffer until after the SSL_read() call succeeds.
+ * For now I'll ignore this, because 1) SSL_read() is documented to
+ * return the bytes available, instead of returning > 0 only after
+ * the entire buffer is processed like SSL_write() does; and 2) there
+ * is no "read" equivalent of the SSL_R_BAD_WRITE_RETRY,
+ * SSL_MODE_ENABLE_PARTIAL_WRITE or
+ * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features.
+ */
+ ssl_read_err = SSL_ERROR_NONE;
+ while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) {
+ ERR_clear_error();
+ ssl_stat = SSL_read(tls_context->con,
+ NBBIO_WRITE_BUF(plaintext_buf)
+ + NBBIO_WRITE_PEND(state->plaintext_buf),
+ NBBIO_BUFSIZE(plaintext_buf)
+ - NBBIO_WRITE_PEND(state->plaintext_buf));
+ ssl_read_err = SSL_get_error(tls_context->con, ssl_stat);
+ if (ssl_read_err != SSL_ERROR_NONE)
+ break;
+ NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat;
+ }
+
+ /*
+ * Try to enable/disable ciphertext read/write events. If SSL_write()
+ * was satisfied, see if SSL_read() wants to do some work. In case of
+ * an unrecoverable error, this automagically destroys the session
+ * state after cleaning up all pending read/write and timeout event
+ * requests.
+ */
+ if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ?
+ ssl_write_err : ssl_read_err) < 0)
+ /* At this point, state is a dangling pointer. */
+ return;
+ }
+
+ /*
+ * Destroy state when the ciphertext I/O was permanently disabled and we
+ * can no longer trickle out plaintext.
+ */
+ else {
+ plaintext_buf = state->plaintext_buf;
+ if (!TLSP_CAN_TRICKLE_OUT_PLAINTEXT(plaintext_buf)) {
+ tlsp_state_free(state);
+ return;
+ }
+ }
+
+ /*
+ * Try to enable/disable plaintext read/write events. Basically, if we
+ * have nothing to write to the plaintext stream, see if there is
+ * something to read. If the write buffer is empty and the read buffer is
+ * full, suspend plaintext I/O until conditions change (but keep the
+ * timer active, as a safety mechanism in case ciphertext I/O gets
+ * stuck).
+ *
+ * XXX In theory, if the ciphertext peer keeps writing fast enough then we
+ * would never read from the plaintext stream and cause the latter to
+ * block. In practice, postscreen(8) limits the number of client
+ * commands, and thus postscreen(8)'s output will fit in a kernel buffer.
+ * A remote SMTP server is not supposed to flood the local SMTP client
+ * with massive replies; if it does, then the local SMTP client should
+ * deal with it.
+ */
+ if (NBBIO_WRITE_PEND(plaintext_buf) > 0) {
+ if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ)
+ nbbio_disable_readwrite(plaintext_buf);
+ nbbio_enable_write(plaintext_buf, state->timeout);
+ } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) {
+ if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE)
+ nbbio_disable_readwrite(plaintext_buf);
+ nbbio_enable_read(plaintext_buf, state->timeout);
+ } else {
+ if (NBBIO_ACTIVE_FLAGS(plaintext_buf))
+ nbbio_slumber(plaintext_buf, state->timeout);
+ }
+}
+
+/* tlsp_plaintext_event - plaintext was read/written */
+
+static void tlsp_plaintext_event(int event, void *context)
+{
+ TLSP_STATE *state = (TLSP_STATE *) context;
+
+ /*
+ * Safety alert: the plaintext pseudothreads have "slumbered" for too
+ * long (see code above). This means that the ciphertext pseudothreads
+ * are stuck.
+ */
+ if ((NBBIO_ERROR_FLAGS(state->plaintext_buf) & NBBIO_FLAG_TIMEOUT) != 0
+ && NBBIO_ACTIVE_FLAGS(state->plaintext_buf) == 0)
+ msg_warn("deadlock on ciphertext stream for %s", state->remote_endpt);
+
+ /*
+ * This is easy, because the NBBIO layer has already done the event
+ * decoding and plaintext I/O for us. All we need to do is decide if we
+ * want to read or write more plaintext.
+ */
+ tlsp_strategy(state);
+ /* At this point, state could be a dangling pointer. */
+}
+
+/* tlsp_ciphertext_event - ciphertext is ready to read/write */
+
+static void tlsp_ciphertext_event(int event, void *context)
+{
+ TLSP_STATE *state = (TLSP_STATE *) context;
+
+ /*
+ * Without a TLS equivalent of the NBBIO layer, we must decode the events
+ * ourselves and do the ciphertext I/O. Then, we can decide if we want to
+ * read or write more ciphertext.
+ */
+ if (event == EVENT_READ || event == EVENT_WRITE) {
+ tlsp_strategy(state);
+ /* At this point, state could be a dangling pointer. */
+ } else {
+ if (event == EVENT_TIME && state->ssl_last_err == SSL_ERROR_NONE)
+ msg_warn("deadlock on plaintext stream for %s",
+ state->remote_endpt);
+ else
+ msg_warn("ciphertext read/write %s for %s",
+ event == EVENT_TIME ? "timeout" : "error",
+ state->remote_endpt);
+ tlsp_state_free(state);
+ }
+}
+
+/* tlsp_client_start_pre_handshake - turn on TLS or force disconnect */
+
+static int tlsp_client_start_pre_handshake(TLSP_STATE *state)
+{
+ state->client_start_props->ctx = state->appl_state;
+ state->client_start_props->fd = state->ciphertext_fd;
+ state->tls_context = tls_client_start(state->client_start_props);
+ if (state->tls_context != 0)
+ return (TLSP_STAT_OK);
+
+ tlsp_state_free(state);
+ return (TLSP_STAT_ERR);
+}
+
+/* tlsp_server_start_pre_handshake - turn on TLS or force disconnect */
+
+static int tlsp_server_start_pre_handshake(TLSP_STATE *state)
+{
+ TLS_SERVER_START_PROPS props;
+ static char *cipher_grade;
+ static VSTRING *cipher_exclusions;
+
+ /*
+ * The code in this routine is pasted literally from smtpd(8). I am not
+ * going to sanitize this because doing so surely will break things in
+ * unexpected ways.
+ */
+
+ /*
+ * Perform the before-handshake portion of per-session initialization.
+ * Pass a null VSTREAM to indicate that this program will do the
+ * ciphertext I/O, not libtls.
+ *
+ * The cipher grade and exclusions don't change between sessions. Compute
+ * just once and cache.
+ */
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ if (cipher_grade == 0) {
+ cipher_grade =
+ var_tlsp_enforce_tls ? var_tlsp_tls_mand_ciph : var_tlsp_tls_ciph;
+ cipher_exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_excl_ciph);
+ if (var_tlsp_enforce_tls)
+ ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_mand_excl);
+ if (ask_client_cert)
+ ADD_EXCLUDE(cipher_exclusions, "aNULL");
+ }
+ state->tls_context =
+ TLS_SERVER_START(&props,
+ ctx = tlsp_server_ctx,
+ stream = (VSTREAM *) 0,/* unused */
+ fd = state->ciphertext_fd,
+ timeout = 0, /* unused */
+ requirecert = (var_tlsp_tls_req_ccert
+ && var_tlsp_enforce_tls),
+ serverid = state->server_id,
+ namaddr = state->remote_endpt,
+ cipher_grade = cipher_grade,
+ cipher_exclusions = STR(cipher_exclusions),
+ mdalg = var_tlsp_tls_fpt_dgst);
+
+ if (state->tls_context == 0) {
+ tlsp_state_free(state);
+ return (TLSP_STAT_ERR);
+ }
+
+ /*
+ * XXX Do we care about TLS session rate limits? Good postscreen(8)
+ * clients will occasionally require the tlsproxy to renew their
+ * allowlist status, but bad clients hammering the server can suck up
+ * lots of CPU cycles. Per-client concurrency limits in postscreen(8)
+ * will divert only naive security "researchers".
+ */
+ return (TLSP_STAT_OK);
+}
+
+ /*
+ * From here on down is low-level code that sets up the plumbing before
+ * passing control to the TLS engine above.
+ */
+
+/* tlsp_request_read_event - pre-handshake event boiler plate */
+
+static void tlsp_request_read_event(int fd, EVENT_NOTIFY_FN handler,
+ int timeout, void *context)
+{
+ event_enable_read(fd, handler, context);
+ event_request_timer(handler, context, timeout);
+}
+
+/* tlsp_accept_event - pre-handshake event boiler plate */
+
+static void tlsp_accept_event(int event, EVENT_NOTIFY_FN handler,
+ void *context)
+{
+ if (event != EVENT_TIME)
+ event_cancel_timer(handler, context);
+ else
+ errno = ETIMEDOUT;
+ /* tlsp_state_free() disables pre-handshake plaintext I/O events. */
+}
+
+/* tlsp_get_fd_event - receive final connection hand-off information */
+
+static void tlsp_get_fd_event(int event, void *context)
+{
+ const char *myname = "tlsp_get_fd_event";
+ TLSP_STATE *state = (TLSP_STATE *) context;
+ int plaintext_fd = vstream_fileno(state->plaintext_stream);
+ int status;
+
+ /*
+ * At this point we still manually manage plaintext read/write/timeout
+ * events. Disable I/O events on the plaintext stream until the TLS
+ * handshake is completed. Every code path must either destroy state, or
+ * request the next event, otherwise we have a file and memory leak.
+ */
+ tlsp_accept_event(event, tlsp_get_fd_event, (void *) state);
+ event_disable_readwrite(plaintext_fd);
+
+ if (event != EVENT_READ
+ || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) {
+ msg_warn("%s: receive remote SMTP peer file descriptor: %m", myname);
+ tlsp_state_free(state);
+ return;
+ }
+
+ /*
+ * This is a bit early, to ensure that timer events for this file handle
+ * are guaranteed to be turned off by the TLSP_STATE destructor.
+ */
+ state->ciphertext_timer = tlsp_ciphertext_event;
+ non_blocking(state->ciphertext_fd, NON_BLOCKING);
+
+ /*
+ * Perform the TLS layer before-handshake initialization. We perform the
+ * remainder after the actual TLS handshake completes.
+ */
+ if (state->is_server_role)
+ status = tlsp_server_start_pre_handshake(state);
+ else
+ status = tlsp_client_start_pre_handshake(state);
+ if (status != TLSP_STAT_OK)
+ /* At this point, state is a dangling pointer. */
+ return;
+
+ /*
+ * Trigger the initial proxy server I/Os.
+ */
+ tlsp_strategy(state);
+ /* At this point, state could be a dangling pointer. */
+}
+
+/* tlsp_config_diff - report server-client config differences */
+
+static void tlsp_log_config_diff(const char *server_cfg, const char *client_cfg)
+{
+ VSTRING *diff_summary = vstring_alloc(100);
+ char *saved_server = mystrdup(server_cfg);
+ char *saved_client = mystrdup(client_cfg);
+ char *server_field;
+ char *client_field;
+ char *server_next;
+ char *client_next;
+
+ /*
+ * Not using argv_split(), because it would treat multiple consecutive
+ * newline characters as one.
+ */
+ for (server_field = saved_server, client_field = saved_client;
+ server_field && client_field;
+ server_field = server_next, client_field = client_next) {
+ server_next = split_at(server_field, '\n');
+ client_next = split_at(client_field, '\n');
+ if (strcmp(server_field, client_field) != 0) {
+ if (LEN(diff_summary) > 0)
+ vstring_sprintf_append(diff_summary, "; ");
+ vstring_sprintf_append(diff_summary,
+ "(server) '%s' != (client) '%s'",
+ server_field, client_field);
+ }
+ }
+ msg_warn("%s", STR(diff_summary));
+
+ vstring_free(diff_summary);
+ myfree(saved_client);
+ myfree(saved_server);
+}
+
+/* tlsp_client_init - initialize a TLS client engine */
+
+static TLS_APPL_STATE *tlsp_client_init(TLS_CLIENT_PARAMS *tls_params,
+ TLS_CLIENT_INIT_PROPS *init_props)
+{
+ TLS_APPL_STATE *appl_state;
+ VSTRING *param_buf;
+ char *param_key;
+ VSTRING *init_buf;
+ char *init_key;
+ int log_hints = 0;
+
+ /*
+ * Use one TLS_APPL_STATE object for all requests that specify the same
+ * TLS_CLIENT_INIT_PROPS. Each TLS_APPL_STATE owns an SSL_CTX, which is
+ * expensive to create. Bug: TLS_CLIENT_PARAMS are not used when creating
+ * a TLS_APPL_STATE instance.
+ *
+ * First, compute the TLS_APPL_STATE cache lookup key. Save a copy of the
+ * pre-jail request TLS_CLIENT_PARAMS and TLSPROXY_CLIENT_INIT_PROPS
+ * settings, so that we can detect post-jail requests that do not match.
+ */
+ param_buf = vstring_alloc(100);
+ param_key = tls_proxy_client_param_serialize(attr_print_plain, param_buf,
+ tls_params);
+ init_buf = vstring_alloc(100);
+ init_key = tls_proxy_client_init_serialize(attr_print_plain, init_buf,
+ init_props);
+ if (tlsp_pre_jail_done == 0) {
+ if (tlsp_pre_jail_client_param_key == 0
+ || tlsp_pre_jail_client_init_key == 0) {
+ tlsp_pre_jail_client_param_key = mystrdup(param_key);
+ tlsp_pre_jail_client_init_key = mystrdup(init_key);
+ } else if (strcmp(tlsp_pre_jail_client_param_key, param_key) != 0
+ || strcmp(tlsp_pre_jail_client_init_key, init_key) != 0) {
+ msg_panic("tlsp_client_init: too many pre-jail calls");
+ }
+ }
+
+ /*
+ * Log a warning if a post-jail request uses unexpected TLS_CLIENT_PARAMS
+ * settings. Bug: TLS_CLIENT_PARAMS settings are not used when creating a
+ * TLS_APPL_STATE instance; this makes a mismatch of TLS_CLIENT_PARAMS
+ * settings problematic.
+ */
+ if (tlsp_pre_jail_done
+ && !been_here_fixed(tlsp_params_mismatch_filter, param_key)
+ && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) {
+ msg_warn("request from tlsproxy client with unexpected settings");
+ tlsp_log_config_diff(tlsp_pre_jail_client_param_key, param_key);
+ log_hints = 1;
+ }
+
+ /*
+ * Look up the cached TLS_APPL_STATE for this tls_client_init request.
+ */
+ if ((appl_state = (TLS_APPL_STATE *)
+ htable_find(tlsp_client_app_cache, init_key)) == 0) {
+
+ /*
+ * Before creating a TLS_APPL_STATE instance, log a warning if a
+ * post-jail request differs from the saved pre-jail request AND the
+ * post-jail request specifies file/directory pathname arguments.
+ * Unexpected requests containing pathnames are problematic after
+ * chroot (pathname resolution) and after dropping privileges (key
+ * files must be root read-only). Unexpected requests are not a
+ * problem as long as they contain no pathnames (for example a
+ * tls_loglevel change).
+ *
+ * We could eliminate some of this complication by adding code that
+ * opens a cert/key lookup table at pre-jail time, and by reading
+ * cert/key info on-the-fly from that table. But then all requests
+ * would still have to specify the same table.
+ */
+#define NOT_EMPTY(x) ((x) && *(x))
+
+ if (tlsp_pre_jail_done
+ && strcmp(tlsp_pre_jail_client_init_key, init_key) != 0
+ && (NOT_EMPTY(init_props->chain_files)
+ || NOT_EMPTY(init_props->cert_file)
+ || NOT_EMPTY(init_props->key_file)
+ || NOT_EMPTY(init_props->dcert_file)
+ || NOT_EMPTY(init_props->dkey_file)
+ || NOT_EMPTY(init_props->eccert_file)
+ || NOT_EMPTY(init_props->eckey_file)
+ || NOT_EMPTY(init_props->CAfile)
+ || NOT_EMPTY(init_props->CApath))) {
+ msg_warn("request from tlsproxy client with unexpected settings");
+ tlsp_log_config_diff(tlsp_pre_jail_client_init_key, init_key);
+ log_hints = 1;
+ }
+ }
+ if (log_hints)
+ msg_warn("to avoid this warning, 1) identify the tlsproxy "
+ "client that is making this request, 2) configure "
+ "a custom tlsproxy service with settings that "
+ "match that tlsproxy client, and 3) configure "
+ "that tlsproxy client with a tlsproxy_service_name "
+ "setting that resolves to that custom tlsproxy "
+ "service");
+
+ /*
+ * TLS_APPL_STATE creation may fail when a post-jail request specifies
+ * unexpected cert/key information, but that is OK because we already
+ * logged a warning with configuration suggestions.
+ */
+ if (appl_state == 0
+ && (appl_state = tls_client_init(init_props)) != 0) {
+ (void) htable_enter(tlsp_client_app_cache, init_key,
+ (void *) appl_state);
+
+ /*
+ * To maintain sanity, allow partial SSL_write() operations, and
+ * allow SSL_write() buffer pointers to change after a WANT_READ or
+ * WANT_WRITE result. This is based on OpenSSL developers talking on
+ * a mailing list, but is not supported by documentation. If this
+ * code stops working then no-one can be held responsible.
+ */
+ SSL_CTX_set_mode(appl_state->ssl_ctx,
+ SSL_MODE_ENABLE_PARTIAL_WRITE
+ | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ }
+ vstring_free(init_buf);
+ vstring_free(param_buf);
+ return (appl_state);
+}
+
+/* tlsp_close_event - pre-handshake plaintext-client close event */
+
+static void tlsp_close_event(int event, void *context)
+{
+ TLSP_STATE *state = (TLSP_STATE *) context;
+
+ tlsp_accept_event(event, tlsp_close_event, (void *) state);
+ tlsp_state_free(state);
+}
+
+/* tlsp_get_request_event - receive initial hand-off info */
+
+static void tlsp_get_request_event(int event, void *context)
+{
+ const char *myname = "tlsp_get_request_event";
+ TLSP_STATE *state = (TLSP_STATE *) context;
+ VSTREAM *plaintext_stream = state->plaintext_stream;
+ int plaintext_fd = vstream_fileno(plaintext_stream);
+ static VSTRING *remote_endpt;
+ static VSTRING *server_id;
+ int req_flags;
+ int handshake_timeout;
+ int session_timeout;
+ int ready = 0;
+
+ /*
+ * At this point we still manually manage plaintext read/write/timeout
+ * events. Every code path must either destroy state or request the next
+ * event, otherwise this pseudo-thread is idle until the client goes
+ * away.
+ */
+ tlsp_accept_event(event, tlsp_get_request_event, (void *) state);
+
+ /*
+ * One-time initialization.
+ */
+ if (remote_endpt == 0) {
+ remote_endpt = vstring_alloc(10);
+ server_id = vstring_alloc(10);
+ }
+
+ /*
+ * Receive the initial request attributes. Receive the remainder after we
+ * figure out what role we are expected to play.
+ *
+ * The tlsproxy server does not enforce per-request read/write deadlines or
+ * minimal data rates. Instead, the tlsproxy server relies on the
+ * tlsproxy client to enforce these context-dependent limits. When a
+ * tlsproxy client decides to time out, it will close its end of the
+ * tlsproxy stream, and the tlsproxy server will handle that immediately.
+ */
+ if (event != EVENT_READ
+ || attr_scan(plaintext_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, remote_endpt),
+ RECV_ATTR_INT(TLS_ATTR_FLAGS, &req_flags),
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &handshake_timeout),
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &session_timeout),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, server_id),
+ ATTR_TYPE_END) != 5) {
+ msg_warn("%s: receive request attributes: %m", myname);
+ tlsp_state_free(state);
+ return;
+ }
+
+ /*
+ * XXX We use the same fixed timeout throughout the entire session for
+ * both plaintext and ciphertext communication. This timeout is just a
+ * safety feature; the real timeout will be enforced by our plaintext
+ * peer (except during TLS the handshake, when we intentionally disable
+ * plaintext I/O).
+ */
+ state->remote_endpt = mystrdup(STR(remote_endpt));
+ state->server_id = mystrdup(STR(server_id));
+ msg_info("CONNECT %s %s",
+ (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" :
+ (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" :
+ "(bogus_direction)", state->remote_endpt);
+ state->req_flags = req_flags;
+ /* state->is_server_role is set below. */
+ state->handshake_timeout = handshake_timeout;
+ state->session_timeout = session_timeout + 10; /* XXX */
+
+ /*
+ * Receive the TLS preferences now, to reduce the number of protocol
+ * roundtrips.
+ */
+ switch (req_flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) {
+ case TLS_PROXY_FLAG_ROLE_CLIENT:
+ state->is_server_role = 0;
+ if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(tls_proxy_client_param_scan,
+ (void *) &state->tls_params),
+ RECV_ATTR_FUNC(tls_proxy_client_init_scan,
+ (void *) &state->client_init_props),
+ RECV_ATTR_FUNC(tls_proxy_client_start_scan,
+ (void *) &state->client_start_props),
+ ATTR_TYPE_END) != 3) {
+ msg_warn("%s: receive client TLS settings: %m", myname);
+ tlsp_state_free(state);
+ return;
+ }
+ state->appl_state = tlsp_client_init(state->tls_params,
+ state->client_init_props);
+ ready = state->appl_state != 0;
+ break;
+ case TLS_PROXY_FLAG_ROLE_SERVER:
+ state->is_server_role = 1;
+ ready = (tlsp_server_ctx != 0);
+ break;
+ default:
+ state->is_server_role = 0;
+ msg_warn("%s: bad request flags: 0x%x", myname, req_flags);
+ ready = 0;
+ }
+
+ /*
+ * For portability we must send some data, after receiving the request
+ * attributes and before receiving the remote file descriptor.
+ *
+ * If the requested TLS engine is unavailable, hang up after making sure
+ * that the plaintext peer has received our "sorry" indication.
+ */
+ if (attr_print(plaintext_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, ready),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(plaintext_stream) != 0
+ || ready == 0) {
+ tlsp_request_read_event(plaintext_fd, tlsp_close_event,
+ TLSP_INIT_TIMEOUT, (void *) state);
+ return;
+ } else {
+ tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event,
+ TLSP_INIT_TIMEOUT, (void *) state);
+ return;
+ }
+}
+
+/* tlsp_service - handle new client connection */
+
+static void tlsp_service(VSTREAM *plaintext_stream,
+ char *service,
+ char **argv)
+{
+ TLSP_STATE *state;
+ int plaintext_fd = vstream_fileno(plaintext_stream);
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This program handles multiple connections, so it must not block. We
+ * use event-driven code for all operations that introduce latency.
+ * Except that attribute lists are sent/received synchronously, once the
+ * socket is found to be ready for transmission.
+ */
+ non_blocking(plaintext_fd, NON_BLOCKING);
+ vstream_control(plaintext_stream,
+ CA_VSTREAM_CTL_PATH("plaintext"),
+ CA_VSTREAM_CTL_TIMEOUT(5),
+ CA_VSTREAM_CTL_END);
+
+ (void) attr_print(plaintext_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END);
+ if (vstream_fflush(plaintext_stream) != 0)
+ msg_warn("write %s attribute: %m", MAIL_ATTR_PROTO);
+
+ /*
+ * Receive postscreen's remote SMTP client address/port and socket.
+ */
+ state = tlsp_state_create(service, plaintext_stream);
+ tlsp_request_read_event(plaintext_fd, tlsp_get_request_event,
+ TLSP_INIT_TIMEOUT, (void *) state);
+}
+
+/* pre_jail_init_server - pre-jail initialization */
+
+static void pre_jail_init_server(void)
+{
+ TLS_SERVER_INIT_PROPS props;
+ const char *cert_file;
+ int have_server_cert;
+ int no_server_cert_ok;
+ int require_server_cert;
+
+ /*
+ * The code in this routine is pasted literally from smtpd(8). I am not
+ * going to sanitize this because doing so surely will break things in
+ * unexpected ways.
+ */
+ if (*var_tlsp_tls_level) {
+ switch (tls_level_lookup(var_tlsp_tls_level)) {
+ default:
+ msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level);
+ /* NOTREACHED */
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
+ VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level);
+ /* FALLTHROUGH */
+ case TLS_LEV_ENCRYPT:
+ var_tlsp_enforce_tls = var_tlsp_use_tls = 1;
+ break;
+ case TLS_LEV_MAY:
+ var_tlsp_enforce_tls = 0;
+ var_tlsp_use_tls = 1;
+ break;
+ case TLS_LEV_NONE:
+ var_tlsp_enforce_tls = var_tlsp_use_tls = 0;
+ break;
+ }
+ }
+ var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls;
+ if (!var_tlsp_use_tls) {
+ msg_warn("TLS server role is disabled with %s or %s",
+ VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS);
+ return;
+ }
+
+ /*
+ * Load TLS keys before dropping privileges.
+ *
+ * Can't use anonymous ciphers if we want client certificates. Must use
+ * anonymous ciphers if we have no certificates.
+ */
+ ask_client_cert = require_server_cert =
+ (var_tlsp_tls_ask_ccert
+ || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert));
+ if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) {
+ no_server_cert_ok = 1;
+ cert_file = "";
+ } else {
+ no_server_cert_ok = 0;
+ cert_file = var_tlsp_tls_cert_file;
+ }
+ have_server_cert =
+ (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file);
+
+ if (*var_tlsp_tls_chain_files != 0) {
+ if (!have_server_cert)
+ have_server_cert = 1;
+ else
+ msg_warn("Both %s and one or more of the legacy "
+ " %s, %s or %s are non-empty; the legacy "
+ " parameters will be ignored",
+ VAR_TLSP_TLS_CHAIN_FILES,
+ VAR_TLSP_TLS_CERT_FILE,
+ VAR_TLSP_TLS_ECCERT_FILE,
+ VAR_TLSP_TLS_DCERT_FILE);
+ }
+ /* Some TLS configuration errors are not show stoppers. */
+ if (!have_server_cert && require_server_cert)
+ msg_warn("Need a server cert to request client certs");
+ if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)
+ msg_warn("Can't require client certs unless TLS is required");
+ /* After a show-stopper error, log a warning. */
+ if (have_server_cert || (no_server_cert_ok && !require_server_cert)) {
+
+ tls_pre_jail_init(TLS_ROLE_SERVER);
+
+ /*
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ */
+ tlsp_server_ctx =
+ TLS_SERVER_INIT(&props,
+ log_param = VAR_TLSP_TLS_LOGLEVEL,
+ log_level = var_tlsp_tls_loglevel,
+ verifydepth = var_tlsp_tls_ccert_vd,
+ cache_type = TLS_MGR_SCACHE_SMTPD,
+ set_sessid = var_tlsp_tls_set_sessid,
+ chain_files = var_tlsp_tls_chain_files,
+ cert_file = cert_file,
+ key_file = var_tlsp_tls_key_file,
+ dcert_file = var_tlsp_tls_dcert_file,
+ dkey_file = var_tlsp_tls_dkey_file,
+ eccert_file = var_tlsp_tls_eccert_file,
+ eckey_file = var_tlsp_tls_eckey_file,
+ CAfile = var_tlsp_tls_CAfile,
+ CApath = var_tlsp_tls_CApath,
+ dh1024_param_file
+ = var_tlsp_tls_dh1024_param_file,
+ dh512_param_file
+ = var_tlsp_tls_dh512_param_file,
+ eecdh_grade = var_tlsp_tls_eecdh,
+ protocols = var_tlsp_enforce_tls ?
+ var_tlsp_tls_mand_proto :
+ var_tlsp_tls_proto,
+ ask_ccert = ask_client_cert,
+ mdalg = var_tlsp_tls_fpt_dgst);
+ } else {
+ msg_warn("No server certs available. TLS can't be enabled");
+ }
+
+ /*
+ * To maintain sanity, allow partial SSL_write() operations, and allow
+ * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE
+ * result. This is based on OpenSSL developers talking on a mailing list,
+ * but is not supported by documentation. If this code stops working then
+ * no-one can be held responsible.
+ */
+ if (tlsp_server_ctx)
+ SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx,
+ SSL_MODE_ENABLE_PARTIAL_WRITE
+ | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+}
+
+/* pre_jail_init_client - pre-jail initialization */
+
+static void pre_jail_init_client(void)
+{
+ int clnt_use_tls;
+
+ /*
+ * The cache with TLS_APPL_STATE instances for different TLS_CLIENT_INIT
+ * configurations.
+ */
+ tlsp_client_app_cache = htable_create(10);
+
+ /*
+ * Most sites don't use TLS client certs/keys. In that case, enabling
+ * tlsproxy-based connection caching is trivial.
+ *
+ * But some sites do use TLS client certs/keys, and that is challenging when
+ * tlsproxy runs in a post-jail environment: chroot breaks pathname
+ * resolution, and an unprivileged process should not be able to open
+ * files with secrets. The workaround: assume that most of those sites
+ * will use a fixed TLS client identity. In that case, tlsproxy can load
+ * the corresponding certs/keys at pre-jail time, so that secrets can
+ * remain read-only for root. As long as the tlsproxy pre-jail TLS client
+ * configuration with cert or key pathnames is the same as the one used
+ * in the Postfix SMTP client, sites can selectively or globally enable
+ * tlsproxy-based connection caching without additional TLS
+ * configuration.
+ *
+ * Loading one TLS client configuration at pre-jail time is not sufficient
+ * for the minority of sites that want to use TLS connection caching with
+ * multiple TLS client identities. To alert the operator, tlsproxy will
+ * log a warning when a TLS_CLIENT_INIT message specifies a different
+ * configuration than the tlsproxy pre-jail client configuration, and
+ * that different configuration specifies file/directory pathname
+ * arguments. The workaround is to have one tlsproxy process per TLS
+ * client identity.
+ *
+ * The general solution for single-identity or multi-identity clients is to
+ * stop loading certs and keys from individual files. Instead, have a
+ * cert/key map, indexed by client identity, read-only by root. After
+ * opening the map as root at pre-jail time, tlsproxy can read certs/keys
+ * on-the-fly as an unprivileged process at post-jail time. This is the
+ * approach that was already proposed for server-side SNI support, and it
+ * could be reused here. It would also end the proliferation of RSA
+ * cert/key parameters, DSA cert/key parameters, EC cert/key parameters,
+ * and so on.
+ *
+ * Horror: In order to create the same pre-jail TLS client context as the
+ * one used in the Postfix SMTP client, we have to duplicate intricate
+ * SMTP client code, including a handful configuration parameters that
+ * tlsproxy does not need. We must duplicate the logic, so that we only
+ * load certs and keys when the SMTP client would load them.
+ */
+ if (*var_tlsp_clnt_level != 0)
+ switch (tls_level_lookup(var_tlsp_clnt_level)) {
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_DANE_ONLY:
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_ENCRYPT:
+ var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 1;
+ break;
+ case TLS_LEV_DANE:
+ case TLS_LEV_MAY:
+ var_tlsp_clnt_use_tls = 1;
+ var_tlsp_clnt_enforce_tls = 0;
+ break;
+ case TLS_LEV_NONE:
+ var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 0;
+ break;
+ default:
+ /* tls_level_lookup() logs no warning. */
+ /* session_tls_init() assumes that var_tlsp_clnt_level is sane. */
+ msg_fatal("Invalid TLS level \"%s\"", var_tlsp_clnt_level);
+ }
+ clnt_use_tls = (var_tlsp_clnt_use_tls || var_tlsp_clnt_enforce_tls);
+
+ /*
+ * Initialize the TLS data before entering the chroot jail.
+ */
+ if (clnt_use_tls || var_tlsp_clnt_per_site[0] || var_tlsp_clnt_policy[0]) {
+ TLS_CLIENT_PARAMS tls_params;
+ TLS_CLIENT_INIT_PROPS init_props;
+
+ tls_pre_jail_init(TLS_ROLE_CLIENT);
+
+ /*
+ * We get stronger type safety and a cleaner interface by combining
+ * the various parameters into a single tls_client_props structure.
+ *
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ */
+ (void) tls_proxy_client_param_from_config(&tls_params);
+ (void) TLS_CLIENT_INIT_ARGS(&init_props,
+ log_param = var_tlsp_clnt_logparam,
+ log_level = var_tlsp_clnt_loglevel,
+ verifydepth = var_tlsp_clnt_scert_vd,
+ cache_type = TLS_MGR_SCACHE_SMTP,
+ chain_files = var_tlsp_clnt_chain_files,
+ cert_file = var_tlsp_clnt_cert_file,
+ key_file = var_tlsp_clnt_key_file,
+ dcert_file = var_tlsp_clnt_dcert_file,
+ dkey_file = var_tlsp_clnt_dkey_file,
+ eccert_file = var_tlsp_clnt_eccert_file,
+ eckey_file = var_tlsp_clnt_eckey_file,
+ CAfile = var_tlsp_clnt_CAfile,
+ CApath = var_tlsp_clnt_CApath,
+ mdalg = var_tlsp_clnt_fpt_dgst);
+ if (tlsp_client_init(&tls_params, &init_props) == 0)
+ msg_warn("TLS client initialization failed");
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize roles separately.
+ */
+ pre_jail_init_server();
+ pre_jail_init_client();
+
+ /*
+ * tlsp_client_init() needs to know if it is called pre-jail or
+ * post-jail.
+ */
+ tlsp_pre_jail_done = 1;
+
+ /*
+ * Bug: TLS_CLIENT_PARAMS attributes are not used when creating a
+ * TLS_APPL_STATE instance; we can only warn about attribute mismatches.
+ */
+ tlsp_params_mismatch_filter = been_here_init(BH_BOUND_NONE, BH_FLAG_NONE);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * Each table below initializes the named variables to their implicit
+ * default value, or to the explicit value in main.cf or master.cf. Here,
+ * "compat" means that a table initializes a variable "smtpd_blah" or
+ * "smtp_blah" that provides the implicit default value for variable
+ * "tlsproxy_blah" which is initialized by a different table. To make
+ * this work, the variables in a "compat" table must be initialized
+ * before the variables in the corresponding non-compat table.
+ */
+ static const CONFIG_INT_TABLE compat_int_table[] = {
+ VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0,
+ VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0,
+ 0,
+ };
+ static const CONFIG_NINT_TABLE nint_table[] = {
+ VAR_TLSP_TLS_CCERT_VD, DEF_TLSP_TLS_CCERT_VD, &var_tlsp_tls_ccert_vd, 0, 0,
+ VAR_TLSP_CLNT_SCERT_VD, DEF_TLSP_CLNT_SCERT_VD, &var_tlsp_clnt_scert_vd, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_TLSP_WATCHDOG, DEF_TLSP_WATCHDOG, &var_tlsp_watchdog, 10, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE compat_bool_table[] = {
+ VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls,
+ VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
+ VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
+ VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
+ VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls,
+ VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_TLSP_USE_TLS, DEF_TLSP_USE_TLS, &var_tlsp_use_tls,
+ VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls,
+ VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert,
+ VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert,
+ VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid,
+ VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls,
+ VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls,
+ 0,
+ };
+ static const CONFIG_STR_TABLE compat_str_table[] = {
+ VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0,
+ VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0,
+ VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0,
+ VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0,
+ VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0,
+ VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0,
+ VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0,
+ VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0,
+ VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0,
+ VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0,
+ VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0,
+ VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0,
+ VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0,
+ VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0,
+ VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0,
+ VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0,
+ VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0,
+ VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0,
+ VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0,
+ VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
+ VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0,
+ VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0,
+ VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0,
+ VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0,
+ VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0,
+ VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0,
+ VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0,
+ VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0,
+ VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+ VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
+ VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
+ VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0,
+ VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0,
+ VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLSP_TLS_CHAIN_FILES, DEF_TLSP_TLS_CHAIN_FILES, &var_tlsp_tls_chain_files, 0, 0,
+ VAR_TLSP_TLS_CERT_FILE, DEF_TLSP_TLS_CERT_FILE, &var_tlsp_tls_cert_file, 0, 0,
+ VAR_TLSP_TLS_KEY_FILE, DEF_TLSP_TLS_KEY_FILE, &var_tlsp_tls_key_file, 0, 0,
+ VAR_TLSP_TLS_DCERT_FILE, DEF_TLSP_TLS_DCERT_FILE, &var_tlsp_tls_dcert_file, 0, 0,
+ VAR_TLSP_TLS_DKEY_FILE, DEF_TLSP_TLS_DKEY_FILE, &var_tlsp_tls_dkey_file, 0, 0,
+ VAR_TLSP_TLS_ECCERT_FILE, DEF_TLSP_TLS_ECCERT_FILE, &var_tlsp_tls_eccert_file, 0, 0,
+ VAR_TLSP_TLS_ECKEY_FILE, DEF_TLSP_TLS_ECKEY_FILE, &var_tlsp_tls_eckey_file, 0, 0,
+ VAR_TLSP_TLS_CA_FILE, DEF_TLSP_TLS_CA_FILE, &var_tlsp_tls_CAfile, 0, 0,
+ VAR_TLSP_TLS_CA_PATH, DEF_TLSP_TLS_CA_PATH, &var_tlsp_tls_CApath, 0, 0,
+ VAR_TLSP_TLS_CIPH, DEF_TLSP_TLS_CIPH, &var_tlsp_tls_ciph, 1, 0,
+ VAR_TLSP_TLS_MAND_CIPH, DEF_TLSP_TLS_MAND_CIPH, &var_tlsp_tls_mand_ciph, 1, 0,
+ VAR_TLSP_TLS_EXCL_CIPH, DEF_TLSP_TLS_EXCL_CIPH, &var_tlsp_tls_excl_ciph, 0, 0,
+ VAR_TLSP_TLS_MAND_EXCL, DEF_TLSP_TLS_MAND_EXCL, &var_tlsp_tls_mand_excl, 0, 0,
+ VAR_TLSP_TLS_PROTO, DEF_TLSP_TLS_PROTO, &var_tlsp_tls_proto, 0, 0,
+ VAR_TLSP_TLS_MAND_PROTO, DEF_TLSP_TLS_MAND_PROTO, &var_tlsp_tls_mand_proto, 0, 0,
+ VAR_TLSP_TLS_512_FILE, DEF_TLSP_TLS_512_FILE, &var_tlsp_tls_dh512_param_file, 0, 0,
+ VAR_TLSP_TLS_1024_FILE, DEF_TLSP_TLS_1024_FILE, &var_tlsp_tls_dh1024_param_file, 0, 0,
+ VAR_TLSP_TLS_EECDH, DEF_TLSP_TLS_EECDH, &var_tlsp_tls_eecdh, 1, 0,
+ VAR_TLSP_TLS_FPT_DGST, DEF_TLSP_TLS_FPT_DGST, &var_tlsp_tls_fpt_dgst, 1, 0,
+ VAR_TLSP_TLS_LOGLEVEL, DEF_TLSP_TLS_LOGLEVEL, &var_tlsp_tls_loglevel, 0, 0,
+ VAR_TLSP_TLS_LEVEL, DEF_TLSP_TLS_LEVEL, &var_tlsp_tls_level, 0, 0,
+ VAR_TLSP_CLNT_LOGLEVEL, DEF_TLSP_CLNT_LOGLEVEL, &var_tlsp_clnt_loglevel, 0, 0,
+ VAR_TLSP_CLNT_LOGPARAM, DEF_TLSP_CLNT_LOGPARAM, &var_tlsp_clnt_logparam, 0, 0,
+ VAR_TLSP_CLNT_CHAIN_FILES, DEF_TLSP_CLNT_CHAIN_FILES, &var_tlsp_clnt_chain_files, 0, 0,
+ VAR_TLSP_CLNT_CERT_FILE, DEF_TLSP_CLNT_CERT_FILE, &var_tlsp_clnt_cert_file, 0, 0,
+ VAR_TLSP_CLNT_KEY_FILE, DEF_TLSP_CLNT_KEY_FILE, &var_tlsp_clnt_key_file, 0, 0,
+ VAR_TLSP_CLNT_DCERT_FILE, DEF_TLSP_CLNT_DCERT_FILE, &var_tlsp_clnt_dcert_file, 0, 0,
+ VAR_TLSP_CLNT_DKEY_FILE, DEF_TLSP_CLNT_DKEY_FILE, &var_tlsp_clnt_dkey_file, 0, 0,
+ VAR_TLSP_CLNT_ECCERT_FILE, DEF_TLSP_CLNT_ECCERT_FILE, &var_tlsp_clnt_eccert_file, 0, 0,
+ VAR_TLSP_CLNT_ECKEY_FILE, DEF_TLSP_CLNT_ECKEY_FILE, &var_tlsp_clnt_eckey_file, 0, 0,
+ VAR_TLSP_CLNT_CAFILE, DEF_TLSP_CLNT_CAFILE, &var_tlsp_clnt_CAfile, 0, 0,
+ VAR_TLSP_CLNT_CAPATH, DEF_TLSP_CLNT_CAPATH, &var_tlsp_clnt_CApath, 0, 0,
+ VAR_TLSP_CLNT_FPT_DGST, DEF_TLSP_CLNT_FPT_DGST, &var_tlsp_clnt_fpt_dgst, 1, 0,
+ VAR_TLSP_CLNT_LEVEL, DEF_TLSP_CLNT_LEVEL, &var_tlsp_clnt_level, 0, 0,
+ VAR_TLSP_CLNT_PER_SITE, DEF_TLSP_CLNT_PER_SITE, &var_tlsp_clnt_per_site, 0, 0,
+ VAR_TLSP_CLNT_POLICY, DEF_TLSP_CLNT_POLICY, &var_tlsp_clnt_policy, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the event-driven service skeleton.
+ */
+ event_server_main(argc, argv, tlsp_service,
+ CA_MAIL_SERVER_INT_TABLE(compat_int_table),
+ CA_MAIL_SERVER_NINT_TABLE(nint_table),
+ CA_MAIL_SERVER_STR_TABLE(compat_str_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(compat_bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_SLOW_EXIT(tlsp_drain),
+ CA_MAIL_SERVER_RETIRE_ME,
+ CA_MAIL_SERVER_WATCHDOG(&var_tlsp_watchdog),
+ CA_MAIL_SERVER_UNLIMITED,
+ 0);
+}
+
+#else
+
+/* tlsp_service - respond to external trigger(s), non-TLS version */
+
+static void tlsp_service(VSTREAM *stream, char *unused_service,
+ char **unused_argv)
+{
+ msg_info("TLS support is not compiled in -- exiting");
+ event_server_disconnect(stream);
+}
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * We can't simply use msg_fatal() here, because the logging hasn't been
+ * initialized. The text would disappear because stderr is redirected to
+ * /dev/null.
+ *
+ * We invoke event_server_main() to complete program initialization
+ * (including logging) and then invoke the tlsp_service() routine to log
+ * the message that says why this program will not run.
+ */
+ event_server_main(argc, argv, tlsp_service,
+ 0);
+}
+
+#endif
diff --git a/src/tlsproxy/tlsproxy.h b/src/tlsproxy/tlsproxy.h
new file mode 100644
index 0000000..eacbb1f
--- /dev/null
+++ b/src/tlsproxy/tlsproxy.h
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* tlsproxy 3h
+/* SUMMARY
+/* tlsproxy internal interfaces
+/* SYNOPSIS
+/* #include <tlsproxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <nbbio.h>
+
+ /*
+ * TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * Internal interface.
+ */
+typedef struct {
+ int flags; /* see below */
+ int req_flags; /* request flags, see tls_proxy.h */
+ int is_server_role; /* avoid clumsy handler code */
+ char *service; /* argv[0] */
+ VSTREAM *plaintext_stream; /* local peer: postscreen(8), etc. */
+ NBBIO *plaintext_buf; /* plaintext buffer */
+ int ciphertext_fd; /* remote peer */
+ EVENT_NOTIFY_FN ciphertext_timer; /* kludge */
+ int timeout; /* read/write time limit */
+ int handshake_timeout; /* in-handshake time limit */
+ int session_timeout; /* post-handshake time limit */
+ char *remote_endpt; /* printable remote endpoint */
+ char *server_id; /* cache management */
+ TLS_APPL_STATE *appl_state; /* libtls state */
+ TLS_SESS_STATE *tls_context; /* libtls state */
+ int ssl_last_err; /* TLS I/O state */
+ TLS_CLIENT_PARAMS *tls_params; /* globals not part of init_props */
+ TLS_SERVER_INIT_PROPS *server_init_props;
+ TLS_SERVER_START_PROPS *server_start_props;
+ TLS_CLIENT_INIT_PROPS *client_init_props;
+ TLS_CLIENT_START_PROPS *client_start_props;
+} TLSP_STATE;
+
+#define TLSP_FLAG_DO_HANDSHAKE (1<<0)
+#define TLSP_FLAG_NO_MORE_CIPHERTEXT_IO (1<<1) /* overrides DO_HANDSHAKE */
+
+extern TLSP_STATE *tlsp_state_create(const char *, VSTREAM *);
+extern void tlsp_state_free(TLSP_STATE *);
+
+/* 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/tlsproxy/tlsproxy_state.c b/src/tlsproxy/tlsproxy_state.c
new file mode 100644
index 0000000..df6cbda
--- /dev/null
+++ b/src/tlsproxy/tlsproxy_state.c
@@ -0,0 +1,169 @@
+/*++
+/* NAME
+/* tlsproxy_state 3
+/* SUMMARY
+/* Postfix SMTP server
+/* SYNOPSIS
+/* #include <tlsproxy.h>
+/*
+/* TLSP_STATE *tlsp_state_create(service, plaintext_stream)
+/* const char *service;
+/* VSTREAM *plaintext_stream;
+/*
+/* void tlsp_state_free(state)
+/* TLSP_STATE *state;
+/* DESCRIPTION
+/* This module provides TLSP_STATE constructor and destructor
+/* routines.
+/*
+/* tlsp_state_create() initializes session context.
+/*
+/* tlsp_state_free() destroys session context. If the handshake
+/* was in progress, it logs a 'handshake failed' message.
+/*
+/* Arguments:
+/* .IP service
+/* The service name for the TLS library. This argument is copied.
+/* The destructor will automatically destroy the string.
+/* .IP plaintext_stream
+/* The VSTREAM between postscreen(8) and tlsproxy(8).
+/* The destructor will automatically close the stream.
+/* .PP
+/* Other structure members are set by the application. The
+/* text below describes how the TLSP_STATE destructor
+/* disposes of them.
+/* .IP plaintext_buf
+/* NBBIO for plaintext I/O.
+/* The destructor will automatically turn off read/write/timeout
+/* events and destroy the NBBIO.
+/* .IP ciphertext_fd
+/* The file handle for the remote SMTP client socket.
+/* The destructor will automatically turn off read/write events
+/* and close the file handle.
+/* .IP ciphertext_timer
+/* The destructor will automatically turn off this time event.
+/* .IP timeout
+/* Time limit for plaintext and ciphertext I/O.
+/* .IP remote_endpt
+/* Printable remote endpoint name.
+/* The destructor will automatically destroy the string.
+/* .IP server_id
+/* TLS session cache identifier.
+/* The destructor will automatically destroy the string.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 <nbbio.h>
+
+ /*
+ * Master library.
+ */
+#include <mail_server.h>
+
+ /*
+ * TLS library.
+ */
+#ifdef USE_TLS
+#define TLS_INTERNAL /* XXX */
+#include <tls.h>
+#include <tls_proxy.h>
+
+ /*
+ * Application-specific.
+ */
+#include <tlsproxy.h>
+
+/* tlsp_state_create - create TLS proxy state object */
+
+TLSP_STATE *tlsp_state_create(const char *service,
+ VSTREAM *plaintext_stream)
+{
+ TLSP_STATE *state = (TLSP_STATE *) mymalloc(sizeof(*state));
+
+ state->flags = TLSP_FLAG_DO_HANDSHAKE;
+ state->service = mystrdup(service);
+ state->plaintext_stream = plaintext_stream;
+ state->plaintext_buf = 0;
+ state->ciphertext_fd = -1;
+ state->ciphertext_timer = 0;
+ state->timeout = -1;
+ state->remote_endpt = 0;
+ state->server_id = 0;
+ state->tls_context = 0;
+ state->tls_params = 0;
+ state->server_init_props = 0;
+ state->server_start_props = 0;
+ state->client_init_props = 0;
+ state->client_start_props = 0;
+
+ return (state);
+}
+
+/* tlsp_state_free - destroy state objects, connection and events */
+
+void tlsp_state_free(TLSP_STATE *state)
+{
+ /* Don't log failure after plaintext EOF. */
+ if (state->remote_endpt && state->server_id
+ && (state->flags & TLSP_FLAG_DO_HANDSHAKE))
+ msg_info("TLS handshake failed for service=%s peer=%s",
+ state->server_id, state->remote_endpt);
+ myfree(state->service);
+ if (state->plaintext_buf) /* turns off plaintext events */
+ nbbio_free(state->plaintext_buf);
+ else
+ event_disable_readwrite(vstream_fileno(state->plaintext_stream));
+ event_server_disconnect(state->plaintext_stream);
+ if (state->ciphertext_fd >= 0) {
+ event_disable_readwrite(state->ciphertext_fd);
+ (void) close(state->ciphertext_fd);
+ }
+ if (state->ciphertext_timer)
+ event_cancel_timer(state->ciphertext_timer, (void *) state);
+ if (state->remote_endpt) {
+ msg_info("DISCONNECT %s", state->remote_endpt);
+ myfree(state->remote_endpt);
+ }
+ if (state->server_id)
+ myfree(state->server_id);
+ if (state->tls_context)
+ tls_free_context(state->tls_context);
+ if (state->tls_params)
+ tls_proxy_client_param_free(state->tls_params);
+ if (state->server_init_props)
+ tls_proxy_server_init_free(state->server_init_props);
+ if (state->server_start_props)
+ tls_proxy_server_start_free(state->server_start_props);
+ if (state->client_init_props)
+ tls_proxy_client_init_free(state->client_init_props);
+ if (state->client_start_props)
+ tls_proxy_client_start_free(state->client_start_props);
+ myfree((void *) state);
+}
+
+#endif
diff --git a/src/trivial-rewrite/.indent.pro b/src/trivial-rewrite/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/trivial-rewrite/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/trivial-rewrite/.printfck b/src/trivial-rewrite/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/trivial-rewrite/.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/trivial-rewrite/Makefile.in b/src/trivial-rewrite/Makefile.in
new file mode 100644
index 0000000..92fa666
--- /dev/null
+++ b/src/trivial-rewrite/Makefile.in
@@ -0,0 +1,199 @@
+SHELL = /bin/sh
+SRCS = trivial-rewrite.c rewrite.c resolve.c transport.c
+OBJS = trivial-rewrite.o rewrite.o resolve.o transport.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+LIB =
+TESTPROG= transport
+PROG = trivial-rewrite
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+BIN_DIR = ../../libexec
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG) $(LIB)
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: transport_test
+
+root_tests:
+
+$(BIN_DIR)/$(PROG): $(PROG)
+ cp $(PROG) $@
+
+update: $(BIN_DIR)/$(PROG)
+
+transport: transport.c $(LIB) $(LIBS)
+ -mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+transport_test: transport transport.in transport.ref
+ $(SHLIB_ENV) sh transport.in >transport.tmp 2>&1
+ diff transport.ref transport.tmp
+ rm -f transport.tmp
+
+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 trivial-rewrite $(TESTPROG) junk $(LIB)
+ rm -rf printfck
+
+tidy: clean
+
+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'
+resolve.o: ../../include/argv.h
+resolve.o: ../../include/attr.h
+resolve.o: ../../include/check_arg.h
+resolve.o: ../../include/dict.h
+resolve.o: ../../include/domain_list.h
+resolve.o: ../../include/htable.h
+resolve.o: ../../include/iostuff.h
+resolve.o: ../../include/mail_addr_find.h
+resolve.o: ../../include/mail_addr_form.h
+resolve.o: ../../include/mail_conf.h
+resolve.o: ../../include/mail_params.h
+resolve.o: ../../include/mail_proto.h
+resolve.o: ../../include/maps.h
+resolve.o: ../../include/match_list.h
+resolve.o: ../../include/match_parent_style.h
+resolve.o: ../../include/msg.h
+resolve.o: ../../include/myflock.h
+resolve.o: ../../include/mymalloc.h
+resolve.o: ../../include/nvtable.h
+resolve.o: ../../include/quote_822_local.h
+resolve.o: ../../include/quote_flags.h
+resolve.o: ../../include/resolve_clnt.h
+resolve.o: ../../include/resolve_local.h
+resolve.o: ../../include/split_at.h
+resolve.o: ../../include/string_list.h
+resolve.o: ../../include/stringops.h
+resolve.o: ../../include/sys_defs.h
+resolve.o: ../../include/tok822.h
+resolve.o: ../../include/valid_hostname.h
+resolve.o: ../../include/valid_mailhost_addr.h
+resolve.o: ../../include/valid_utf8_hostname.h
+resolve.o: ../../include/vbuf.h
+resolve.o: ../../include/vstream.h
+resolve.o: ../../include/vstring.h
+resolve.o: ../../include/vstring_vstream.h
+resolve.o: resolve.c
+resolve.o: transport.h
+resolve.o: trivial-rewrite.h
+rewrite.o: ../../include/argv.h
+rewrite.o: ../../include/attr.h
+rewrite.o: ../../include/check_arg.h
+rewrite.o: ../../include/dict.h
+rewrite.o: ../../include/htable.h
+rewrite.o: ../../include/iostuff.h
+rewrite.o: ../../include/mail_conf.h
+rewrite.o: ../../include/mail_params.h
+rewrite.o: ../../include/mail_proto.h
+rewrite.o: ../../include/maps.h
+rewrite.o: ../../include/msg.h
+rewrite.o: ../../include/myflock.h
+rewrite.o: ../../include/mymalloc.h
+rewrite.o: ../../include/nvtable.h
+rewrite.o: ../../include/resolve_clnt.h
+rewrite.o: ../../include/resolve_local.h
+rewrite.o: ../../include/split_at.h
+rewrite.o: ../../include/sys_defs.h
+rewrite.o: ../../include/tok822.h
+rewrite.o: ../../include/vbuf.h
+rewrite.o: ../../include/vstream.h
+rewrite.o: ../../include/vstring.h
+rewrite.o: ../../include/vstring_vstream.h
+rewrite.o: rewrite.c
+rewrite.o: trivial-rewrite.h
+transport.o: ../../include/argv.h
+transport.o: ../../include/attr.h
+transport.o: ../../include/check_arg.h
+transport.o: ../../include/dict.h
+transport.o: ../../include/events.h
+transport.o: ../../include/htable.h
+transport.o: ../../include/iostuff.h
+transport.o: ../../include/mail_addr_find.h
+transport.o: ../../include/mail_addr_form.h
+transport.o: ../../include/mail_params.h
+transport.o: ../../include/mail_proto.h
+transport.o: ../../include/maps.h
+transport.o: ../../include/match_list.h
+transport.o: ../../include/match_parent_style.h
+transport.o: ../../include/msg.h
+transport.o: ../../include/myflock.h
+transport.o: ../../include/mymalloc.h
+transport.o: ../../include/nvtable.h
+transport.o: ../../include/split_at.h
+transport.o: ../../include/stringops.h
+transport.o: ../../include/strip_addr.h
+transport.o: ../../include/sys_defs.h
+transport.o: ../../include/vbuf.h
+transport.o: ../../include/vstream.h
+transport.o: ../../include/vstring.h
+transport.o: transport.c
+transport.o: transport.h
+trivial-rewrite.o: ../../include/argv.h
+trivial-rewrite.o: ../../include/attr.h
+trivial-rewrite.o: ../../include/check_arg.h
+trivial-rewrite.o: ../../include/dict.h
+trivial-rewrite.o: ../../include/events.h
+trivial-rewrite.o: ../../include/htable.h
+trivial-rewrite.o: ../../include/iostuff.h
+trivial-rewrite.o: ../../include/mail_addr.h
+trivial-rewrite.o: ../../include/mail_conf.h
+trivial-rewrite.o: ../../include/mail_params.h
+trivial-rewrite.o: ../../include/mail_proto.h
+trivial-rewrite.o: ../../include/mail_server.h
+trivial-rewrite.o: ../../include/mail_version.h
+trivial-rewrite.o: ../../include/maps.h
+trivial-rewrite.o: ../../include/msg.h
+trivial-rewrite.o: ../../include/myflock.h
+trivial-rewrite.o: ../../include/mymalloc.h
+trivial-rewrite.o: ../../include/nvtable.h
+trivial-rewrite.o: ../../include/resolve_clnt.h
+trivial-rewrite.o: ../../include/resolve_local.h
+trivial-rewrite.o: ../../include/rewrite_clnt.h
+trivial-rewrite.o: ../../include/split_at.h
+trivial-rewrite.o: ../../include/stringops.h
+trivial-rewrite.o: ../../include/sys_defs.h
+trivial-rewrite.o: ../../include/tok822.h
+trivial-rewrite.o: ../../include/vbuf.h
+trivial-rewrite.o: ../../include/vstream.h
+trivial-rewrite.o: ../../include/vstring.h
+trivial-rewrite.o: ../../include/vstring_vstream.h
+trivial-rewrite.o: transport.h
+trivial-rewrite.o: trivial-rewrite.c
+trivial-rewrite.o: trivial-rewrite.h
diff --git a/src/trivial-rewrite/resolve.c b/src/trivial-rewrite/resolve.c
new file mode 100644
index 0000000..40e6aa5
--- /dev/null
+++ b/src/trivial-rewrite/resolve.c
@@ -0,0 +1,828 @@
+/*++
+/* NAME
+/* resolve 3
+/* SUMMARY
+/* mail address resolver
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/*
+/* void resolve_init(void)
+/*
+/* int resolve_class(domain)
+/* const char *domain;
+/*
+/* void resolve_proto(context, stream)
+/* RES_CONTEXT *context;
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the trivial address resolving engine.
+/* It distinguishes between local and remote mail, and optionally
+/* consults one or more transport tables that map a destination
+/* to a transport, nexthop pair.
+/*
+/* resolve_init() initializes data structures that are private
+/* to this module. It should be called once before using the
+/* actual resolver routines.
+/*
+/* resolve_class() returns the address class for the specified
+/* domain, or -1 in case of error.
+/*
+/* resolve_proto() implements the client-server protocol:
+/* read one address in FQDN form, reply with a (transport,
+/* nexthop, internalized recipient) triple.
+/* STANDARDS
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <valid_utf8_hostname.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <mail_conf.h>
+#include <quote_822_local.h>
+#include <tok822.h>
+#include <domain_list.h>
+#include <string_list.h>
+#include <match_parent_style.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <valid_mailhost_addr.h>
+
+/* Application-specific. */
+
+#include "trivial-rewrite.h"
+#include "transport.h"
+
+ /*
+ * The job of the address resolver is to map one recipient address to a
+ * triple of (channel, nexthop, recipient). The channel is the name of the
+ * delivery service specified in master.cf, the nexthop is (usually) a
+ * description of the next host to deliver to, and recipient is the final
+ * recipient address. The latter may differ from the input address as the
+ * result of stripping multiple layers of sender-specified routing.
+ *
+ * Addresses are resolved by their domain name. Known domain names are
+ * categorized into classes: local, virtual alias, virtual mailbox, relay,
+ * and everything else. Finding the address domain class is a matter of
+ * table lookups.
+ *
+ * Different address domain classes generally use different delivery channels,
+ * and may use class dependent ways to arrive at the corresponding nexthop
+ * information. With classes that do final delivery, the nexthop is
+ * typically the local machine hostname.
+ *
+ * The transport lookup table provides a means to override the domain class
+ * channel and/or nexhop information for specific recipients or for entire
+ * domain hierarchies.
+ *
+ * This works well in the general case. The only bug in this approach is that
+ * the structure of the nexthop information is transport dependent.
+ * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the
+ * pathname of a UNIX-domain socket. However, with the error transport the
+ * nexthop field contains free text with the reason for non-delivery.
+ *
+ * Therefore, a transport map entry that overrides the channel but not the
+ * nexthop information (or vice versa) may produce surprising results. In
+ * particular, the free text nexthop information for the error transport is
+ * likely to confuse regular delivery agents; and conversely, a hostname or
+ * socket pathname is not an adequate text as reason for non-delivery.
+ *
+ * In the code below, rcpt_domain specifies the domain name that we will use
+ * when the transport table specifies a non-default channel but no nexthop
+ * information (we use a generic text when that non-default channel is the
+ * error transport).
+ */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+ /*
+ * Some of the lists that define the address domain classes.
+ */
+static DOMAIN_LIST *relay_domains;
+static STRING_LIST *virt_alias_doms;
+static STRING_LIST *virt_mailbox_doms;
+
+static MAPS *relocated_maps;
+
+/* resolve_class - determine domain address class */
+
+int resolve_class(const char *domain)
+{
+ int ret;
+
+ /*
+ * Same order as in resolve_addr().
+ */
+ if ((ret = resolve_local(domain)) != 0)
+ return (ret > 0 ? RESOLVE_CLASS_LOCAL : -1);
+ if (virt_alias_doms) {
+ if (string_list_match(virt_alias_doms, domain))
+ return (RESOLVE_CLASS_ALIAS);
+ if (virt_alias_doms->error)
+ return (-1);
+ }
+ if (virt_mailbox_doms) {
+ if (string_list_match(virt_mailbox_doms, domain))
+ return (RESOLVE_CLASS_VIRTUAL);
+ if (virt_mailbox_doms->error)
+ return (-1);
+ }
+ if (relay_domains) {
+ if (string_list_match(relay_domains, domain))
+ return (RESOLVE_CLASS_RELAY);
+ if (relay_domains->error)
+ return (-1);
+ }
+ return (RESOLVE_CLASS_DEFAULT);
+}
+
+/* resolve_addr - resolve address according to rule set */
+
+static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
+ VSTRING *channel, VSTRING *nexthop,
+ VSTRING *nextrcpt, int *flags)
+{
+ const char *myname = "resolve_addr";
+ VSTRING *addr_buf = vstring_alloc(100);
+ TOK822 *tree = 0;
+ TOK822 *saved_domain = 0;
+ TOK822 *domain = 0;
+ char *destination;
+ const char *blame = 0;
+ const char *rcpt_domain;
+ ssize_t addr_len;
+ ssize_t loop_count;
+ ssize_t loop_max;
+ char *local;
+ char *oper;
+ char *junk;
+ const char *relay;
+ const char *xport;
+ const char *sender_key;
+ int rc;
+
+ *flags = 0;
+ vstring_strcpy(channel, "CHANNEL NOT UPDATED");
+ vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED");
+ vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED");
+
+ /*
+ * The address is in internalized (unquoted) form.
+ *
+ * In an ideal world we would parse the externalized address form as given
+ * to us by the sender.
+ *
+ * However, in the real world we have to look for routing characters like
+ * %@! in the address local-part, even when that information is quoted
+ * due to the presence of special characters or whitespace. Although
+ * technically incorrect, this is needed to stop user@domain@domain relay
+ * attempts when forwarding mail to a Sendmail MX host.
+ *
+ * This suggests that we parse the address in internalized (unquoted) form.
+ * Unfortunately, if we do that, the unparser generates incorrect white
+ * space between adjacent non-operator tokens. Example: ``first last''
+ * needs white space, but ``stuff[stuff]'' does not. This is not a
+ * problem when unparsing the result from parsing externalized forms,
+ * because the parser/unparser were designed for valid externalized forms
+ * where ``stuff[stuff]'' does not happen.
+ *
+ * As a workaround we start with the quoted form and then dequote the
+ * local-part only where needed. This will do the right thing in most
+ * (but not all) cases.
+ */
+ addr_len = strlen(addr);
+ quote_822_local(addr_buf, addr);
+ tree = tok822_scan_addr(vstring_str(addr_buf));
+
+ /*
+ * The optimizer will eliminate tests that always fail, and will replace
+ * multiple expansions of this macro by a GOTO to a single instance.
+ */
+#define FREE_MEMORY_AND_RETURN { \
+ if (saved_domain) \
+ tok822_free_tree(saved_domain); \
+ if(tree) \
+ tok822_free_tree(tree); \
+ if (addr_buf) \
+ vstring_free(addr_buf); \
+ return; \
+ }
+
+ /*
+ * Preliminary resolver: strip off all instances of the local domain.
+ * Terminate when no destination domain is left over, or when the
+ * destination domain is remote.
+ *
+ * XXX To whom it may concern. If you change the resolver loop below, or
+ * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
+ * under "make resolve_clnt_test" in the global directory.
+ */
+#define RESOLVE_LOCAL(domain) \
+ resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
+
+ for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) {
+
+ /*
+ * XXX Should never happen, but if this happens with some
+ * pathological address, then that is not sufficient reason to
+ * disrupt the operation of an MTA.
+ */
+ if (loop_count > loop_max) {
+ msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
+ addr, (long) loop_count);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ break;
+ }
+
+ /*
+ * Strip trailing dot at end of domain, but not dot-dot or at-dot.
+ * This merely makes diagnostics more accurate by leaving bogus
+ * addresses alone.
+ */
+ if (tree->tail
+ && tree->tail->type == '.'
+ && tok822_rfind_type(tree->tail, '@') != 0
+ && tree->tail->prev->type != '.'
+ && tree->tail->prev->type != '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip trailing @.
+ */
+ if (var_resolve_nulldom
+ && tree->tail
+ && tree->tail->type == '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip (and save) @domain if local.
+ *
+ * Grr. resolve_local() table lookups may fail. It may be OK for local
+ * file lookup code to abort upon failure, but with network-based
+ * tables it is preferable to return an error indication to the
+ * requestor.
+ */
+ if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) {
+ if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) {
+ if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ break;
+ }
+ tok822_sub_keep_before(tree, domain);
+ if (saved_domain)
+ tok822_free_tree(saved_domain);
+ saved_domain = domain;
+ domain = 0; /* safety for future change */
+ }
+
+ /*
+ * After stripping the local domain, if any, replace foo%bar by
+ * foo@bar, site!user by user@site, rewrite to canonical form, and
+ * retry.
+ */
+ if (tok822_rfind_type(tree->tail, '@')
+ || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!'))
+ || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) {
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * If the local-part is a quoted string, crack it open when we're
+ * permitted to do so and look for routing operators. This is
+ * technically incorrect, but is needed to stop relaying problems.
+ *
+ * XXX Do another feeble attempt to keep local-part info quoted.
+ */
+ if (var_resolve_dequoted
+ && tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0
+ || (var_percent_hack && (oper = strrchr(local, '%')) != 0)
+ || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) {
+ if (*oper == '%')
+ *oper = '@';
+ tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL);
+ if (*oper == '@') {
+ junk = mystrdup(STR(addr_buf));
+ quote_822_local(addr_buf, junk);
+ myfree(junk);
+ }
+ tok822_free(tree->head);
+ tree->head = tok822_scan(STR(addr_buf), &tree->tail);
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * An empty local-part or an empty quoted string local-part becomes
+ * the local MAILER-DAEMON, for consistency with our own From:
+ * message headers.
+ */
+ if (tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && VSTRING_LEN(tree->head->vstr) == 0) {
+ tok822_free(tree->head);
+ tree->head = 0;
+ }
+ /* XXX Re-resolve the surrogate, in case already in user@domain form. */
+ if (tree->head == 0) {
+ tree->head = tok822_scan(var_empty_addr, &tree->tail);
+ continue;
+ }
+ /* XXX Re-resolve with @$myhostname for backwards compatibility. */
+ if (domain == 0 && saved_domain == 0) {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ continue;
+ }
+
+ /*
+ * We're done. There are no domains left to strip off the address,
+ * and all null local-part information is sanitized.
+ */
+ domain = 0;
+ break;
+ }
+
+ vstring_free(addr_buf);
+ addr_buf = 0;
+
+ /*
+ * Make sure the resolved envelope recipient has the user@domain form. If
+ * no domain was specified in the address, assume the local machine. See
+ * above for what happens with an empty address.
+ */
+ if (domain == 0) {
+ if (saved_domain) {
+ tok822_sub_append(tree, saved_domain);
+ saved_domain = 0;
+ } else {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ }
+ }
+
+ /*
+ * Transform the recipient address back to internal form.
+ *
+ * XXX This may produce incorrect results if we cracked open a quoted
+ * local-part with routing operators; see discussion above at the top of
+ * the big loop.
+ *
+ * XXX We explicitly disallow domain names in bare network address form. A
+ * network address destination should be formatted according to RFC 2821:
+ * it should be enclosed in [], and an IPv6 address should have an IPv6:
+ * prefix.
+ */
+ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if (rcpt_domain == (char *) 1)
+ msg_panic("no @ in address: \"%s\"", STR(nextrcpt));
+ if (*rcpt_domain == '[') {
+ if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (var_smtputf8_enable
+ && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) {
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain,
+ DONT_GRIPE)) {
+ if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) {
+ vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1);
+ vstring_strcat(nextrcpt, "]");
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if ((rc = resolve_local(rcpt_domain)) > 0) /* XXX */
+ domain = 0;
+ else if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ } else {
+ *flags |= RESOLVE_FLAG_ERROR;
+ }
+ }
+ tok822_free_tree(tree);
+ tree = 0;
+
+ /*
+ * XXX Short-cut invalid address forms.
+ */
+ if (*flags & RESOLVE_FLAG_ERROR) {
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Recognize routing operators in the local-part, even when we do not
+ * recognize ! or % as valid routing operators locally. This is needed to
+ * prevent backup MX hosts from relaying third-party destinations through
+ * primary MX hosts, otherwise the backup host could end up on black
+ * lists. Ignore local swap_bangpath and percent_hack settings because we
+ * can't know how the next MX host is set up.
+ */
+ if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain))
+ *flags |= RESOLVE_FLAG_ROUTED;
+
+ /*
+ * With local, virtual, relay, or other non-local destinations, give the
+ * highest precedence to transport associated nexthop information.
+ *
+ * Otherwise, with relay or other non-local destinations, the relayhost
+ * setting overrides the recipient domain name, and the sender-dependent
+ * relayhost overrides both.
+ *
+ * XXX Nag if the recipient domain is listed in multiple domain lists. The
+ * result is implementation defined, and may break when internals change.
+ *
+ * For now, we distinguish only a fixed number of address classes.
+ * Eventually this may become extensible, so that new classes can be
+ * configured with their own domain list, delivery transport, and
+ * recipient table.
+ */
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+ if (domain != 0) {
+
+ /*
+ * Virtual alias domain.
+ */
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_VIRT_MAILBOX_DOMS);
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_RELAY_DOMAINS);
+#if 0
+ if (strcasecmp_utf8(rcpt_domain, var_myorigin) == 0)
+ msg_warn("do not list $%s (%s) in %s",
+ VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS);
+#endif
+ }
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ vstring_sprintf(nexthop, "5.1.1 User unknown%s",
+ var_show_unk_rcpt_table ?
+ " in virtual alias table" : "");
+ *flags |= RESOLVE_CLASS_ALIAS;
+ } else if (virt_alias_doms && virt_alias_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Virtual mailbox domain.
+ */
+ else if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_MAILBOX_DOMS,
+ VAR_RELAY_DOMAINS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->virt_transport_name;
+ *flags |= RESOLVE_CLASS_VIRTUAL;
+ } else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+
+ /*
+ * Off-host relay destination.
+ */
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain)) {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport));
+ blame = rp->relay_transport_name;
+ *flags |= RESOLVE_CLASS_RELAY;
+ } else if (relay_domains && relay_domains->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELAY_DOMAINS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Other off-host destination.
+ */
+ else {
+ if (rp->snd_def_xp_info
+ && (xport = mail_addr_find(rp->snd_def_xp_info,
+ sender_key = (*sender ? sender :
+ var_null_def_xport_maps_key),
+ (char **) 0)) != 0) {
+ if (*xport == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_def_xp_maps_name, sender_key);
+ xport = "DUNNO";
+ }
+ vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ?
+ RES_PARAM_VALUE(rp->def_transport) : xport);
+ blame = rp->snd_def_xp_maps_name;
+ } else if (rp->snd_def_xp_info
+ && rp->snd_def_xp_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_def_xp_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport));
+ blame = rp->def_transport_name;
+ }
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ }
+
+ /*
+ * With off-host delivery, sender-dependent or global relayhost
+ * override the recipient domain.
+ */
+ if (rp->snd_relay_info
+ && (relay = mail_addr_find(rp->snd_relay_info,
+ sender_key = (*sender ? sender :
+ var_null_relay_maps_key),
+ (char **) 0)) != 0) {
+ if (*relay == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_relay_maps_name, sender_key);
+ relay = 0;
+ } else if (strcasecmp_utf8(relay, "DUNNO") == 0)
+ relay = 0;
+ } else if (rp->snd_relay_info
+ && rp->snd_relay_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_relay_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ relay = 0;
+ }
+ /* Enforce all the relayhost precedences in one place. */
+ if (relay != 0) {
+ vstring_strcpy(nexthop, relay);
+ } else if (*RES_PARAM_VALUE(rp->relayhost))
+ vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost));
+ else
+ vstring_strcpy(nexthop, rcpt_domain);
+ }
+ }
+
+ /*
+ * Local delivery.
+ *
+ * XXX Nag if the domain is listed in multiple domain lists. The effect is
+ * implementation defined, and may break when internals change.
+ */
+ else {
+ if (var_helpful_warnings) {
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->local_transport_name;
+ *flags |= RESOLVE_CLASS_LOCAL;
+ }
+
+ /*
+ * An explicit main.cf transport:nexthop setting overrides the nexthop.
+ *
+ * XXX We depend on this mechanism to enforce per-recipient concurrencies
+ * for local recipients. With "local_transport = local:$myhostname" we
+ * force mail for any domain in $mydestination/${proxy,inet}_interfaces
+ * to share the same queue.
+ */
+ if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
+ vstring_strcpy(nexthop, destination);
+
+ /*
+ * Sanity checks.
+ */
+ if (*STR(channel) == 0) {
+ if (blame == 0)
+ msg_panic("%s: null blame", myname);
+ msg_warn("file %s/%s: parameter %s: null transport is not allowed",
+ var_config_dir, MAIN_CONF_FILE, blame);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ if (*STR(nexthop) == 0)
+ msg_panic("%s: null nexthop", myname);
+
+ /*
+ * The transport map can selectively override any transport and/or
+ * nexthop host info that is set up above. Unfortunately, the syntax for
+ * nexthop information is transport specific. We therefore need sane and
+ * intuitive semantics for transport map entries that specify a channel
+ * but no nexthop.
+ *
+ * With non-error transports, the initial nexthop information is the
+ * recipient domain. However, specific main.cf transport definitions may
+ * specify a transport-specific destination, such as a host + TCP socket,
+ * or the pathname of a UNIX-domain socket. With less precedence than
+ * main.cf transport definitions, a main.cf relayhost definition may also
+ * override nexthop information for off-host deliveries.
+ *
+ * With the error transport, the nexthop information is free text that
+ * specifies the reason for non-delivery.
+ *
+ * Because nexthop syntax is transport specific we reset the nexthop
+ * information to the recipient domain when the transport table specifies
+ * a transport without also specifying the nexthop information.
+ *
+ * Subtle note: reset nexthop even when the transport table does not change
+ * the transport. Otherwise it is hard to get rid of main.cf specified
+ * nexthop information.
+ *
+ * XXX Don't override the virtual alias class (error:User unknown) result.
+ */
+ if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) {
+ if (transport_lookup(rp->transport_info, STR(nextrcpt),
+ rcpt_domain, channel, nexthop) == 0
+ && rp->transport_info->transport_path->error != 0) {
+ msg_warn("%s lookup failure", rp->transport_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipients that have moved, regardless of domain address class.
+ * We do this last, in anticipation of transport maps that can override
+ * the recipient address.
+ *
+ * The downside of not doing this in delivery agents is that this table has
+ * no effect on local alias expansion results. Such mail will have to
+ * make almost an entire iteration through the mail system.
+ */
+#define IGNORE_ADDR_EXTENSION ((char **) 0)
+
+ if (relocated_maps != 0) {
+ const char *newloc;
+
+ if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
+ IGNORE_ADDR_EXTENSION)) != 0) {
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ /* 5.1.6 is the closest match, but not perfect. */
+ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
+ } else if (relocated_maps->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipient addresses that start with `-'. External commands may
+ * misinterpret such addresses as command-line options.
+ *
+ * In theory I could say people should always carefully set up their
+ * master.cf pipe mailer entries with `--' before the first non-option
+ * argument, but mistakes will happen regardless.
+ *
+ * Therefore the protection is put in place here, where it cannot be
+ * bypassed.
+ */
+ if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') {
+ *flags |= RESOLVE_FLAG_ERROR;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Clean up.
+ */
+ FREE_MEMORY_AND_RETURN;
+}
+
+/* Static, so they can be used by the network protocol interface only. */
+
+static VSTRING *channel;
+static VSTRING *nexthop;
+static VSTRING *nextrcpt;
+static VSTRING *query;
+static VSTRING *sender;
+
+/* resolve_proto - read request and send reply */
+
+int resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
+{
+ int flags;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, query),
+ ATTR_TYPE_END) != 2)
+ return (-1);
+
+ resolve_addr(context, STR(sender), STR(query),
+ channel, nexthop, nextrcpt, &flags);
+
+ if (msg_verbose)
+ msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
+ STR(sender), STR(query), STR(channel),
+ STR(nexthop), STR(nextrcpt), flags);
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
+ SEND_ATTR_STR(MAIL_ATTR_TRANSPORT, STR(channel)),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, STR(nexthop)),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, STR(nextrcpt)),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write resolver reply: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+/* resolve_init - module initializations */
+
+void resolve_init(void)
+{
+ sender = vstring_alloc(100);
+ query = vstring_alloc(100);
+ channel = vstring_alloc(100);
+ nexthop = vstring_alloc(100);
+ nextrcpt = vstring_alloc(100);
+
+ if (*var_virt_alias_doms)
+ virt_alias_doms =
+ string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_RETURN,
+ var_virt_alias_doms);
+
+ if (*var_virt_mailbox_doms)
+ virt_mailbox_doms =
+ string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_RETURN,
+ var_virt_mailbox_doms);
+
+ if (*var_relay_domains)
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_RELAY_DOMAINS),
+ var_relay_domains);
+
+ if (*var_relocated_maps)
+ relocated_maps =
+ maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+}
diff --git a/src/trivial-rewrite/rewrite.c b/src/trivial-rewrite/rewrite.c
new file mode 100644
index 0000000..483463c
--- /dev/null
+++ b/src/trivial-rewrite/rewrite.c
@@ -0,0 +1,303 @@
+/*++
+/* NAME
+/* rewrite 3
+/* SUMMARY
+/* mail address rewriter
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/*
+/* void rewrite_init(void)
+/*
+/* void rewrite_proto(stream)
+/* VSTREAM *stream;
+/*
+/* void rewrite_addr(context, addr, result)
+/* RWR_CONTEXT *context;
+/* char *addr;
+/* VSTRING *result;
+/*
+/* void rewrite_tree(context, tree)
+/* RWR_CONTEXT *context;
+/* TOK822 *tree;
+/*
+/* RWR_CONTEXT local_context;
+/* RWR_CONTEXT remote_context;
+/* DESCRIPTION
+/* This module implements the trivial address rewriting engine.
+/*
+/* rewrite_init() initializes data structures that are private
+/* to this module. It should be called once before using the
+/* actual rewriting routines.
+/*
+/* rewrite_proto() implements the client-server protocol: read
+/* one rule set name and one address in external (quoted) form,
+/* reply with the rewritten address in external form.
+/*
+/* rewrite_addr() rewrites an address string to another string.
+/* Both input and output are in external (quoted) form.
+/*
+/* rewrite_tree() rewrites a parse tree with a single address to
+/* another tree. A tree is a dummy node on top of a token list.
+/*
+/* local_context and remote_context provide domain names for
+/* completing incomplete address forms.
+/* STANDARDS
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <tok822.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include "trivial-rewrite.h"
+
+RWR_CONTEXT local_context = {
+ VAR_MYORIGIN, &var_myorigin,
+ VAR_MYDOMAIN, &var_mydomain,
+};
+
+RWR_CONTEXT remote_context = {
+ VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
+ VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
+};
+
+static VSTRING *ruleset;
+static VSTRING *address;
+static VSTRING *result;
+
+/* rewrite_tree - rewrite address according to rule set */
+
+void rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
+{
+ TOK822 *colon;
+ TOK822 *domain;
+ TOK822 *bang;
+ TOK822 *local;
+ VSTRING *vstringval;
+
+ /*
+ * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
+ * be sure to re-run the tests under "make rewrite_clnt_test" and "make
+ * resolve_clnt_test" in the global directory.
+ */
+
+ /*
+ * Sanity check.
+ */
+ if (tree->head == 0)
+ msg_panic("rewrite_tree: empty tree");
+
+ /*
+ * An empty address is a special case.
+ */
+ if (tree->head == tree->tail
+ && tree->tail->type == TOK822_QSTRING
+ && VSTRING_LEN(tree->tail->vstr) == 0)
+ return;
+
+ /*
+ * Treat a lone @ as if it were an empty address.
+ */
+ if (tree->head == tree->tail
+ && tree->tail->type == '@') {
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+ tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
+ return;
+ }
+
+ /*
+ * Strip source route.
+ */
+ if (tree->head->type == '@'
+ && (colon = tok822_find_type(tree->head, ':')) != 0
+ && colon != tree->tail)
+ tok822_free_tree(tok822_sub_keep_after(tree, colon));
+
+ /*
+ * Optionally, transform address forms without @.
+ */
+ if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {
+
+ /*
+ * Swap domain!user to user@domain.
+ */
+ if (var_swap_bangpath != 0
+ && (bang = tok822_find_type(tree->head, '!')) != 0) {
+ tok822_sub_keep_before(tree, bang);
+ local = tok822_cut_after(bang);
+ tok822_free(bang);
+ tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
+ if (local)
+ tok822_sub_prepend(tree, local);
+ }
+
+ /*
+ * Promote user%domain to user@domain.
+ */
+ else if (var_percent_hack != 0
+ && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
+ domain->type = '@';
+ }
+
+ /*
+ * Append missing @origin
+ */
+ else if (var_append_at_myorigin != 0
+ && REW_PARAM_VALUE(context->origin) != 0
+ && REW_PARAM_VALUE(context->origin)[0] != 0) {
+ domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
+ (TOK822 **) 0));
+ }
+ }
+
+ /*
+ * Append missing .domain, but leave broken forms ending in @ alone. This
+ * merely makes diagnostics more accurate by leaving bogus addresses
+ * alone.
+ *
+ * Backwards-compatibility warning: warn for "user@localhost" when there is
+ * no "localhost" in mydestination or in any other address class with an
+ * explicit domain list.
+ */
+ if (var_append_dot_mydomain != 0
+ && REW_PARAM_VALUE(context->domain) != 0
+ && REW_PARAM_VALUE(context->domain)[0] != 0
+ && (domain = tok822_rfind_type(tree->tail, '@')) != 0
+ && domain != tree->tail
+ && tok822_find_type(domain, TOK822_DOMLIT) == 0
+ && tok822_find_type(domain, '.') == 0) {
+ if (warn_compat_break_app_dot_mydomain
+ && (vstringval = domain->next->vstr) != 0) {
+ if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
+ msg_info("using backwards-compatible default setting "
+ VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
+ "\"%s.%s\"", vstring_str(vstringval),
+ vstring_str(vstringval), var_mydomain);
+ } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
+ msg_info("using backwards-compatible default setting "
+ VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
+ "\"%s.%s\"; please add \"localhost\" to "
+ "mydestination or other address class",
+ vstring_str(vstringval), vstring_str(vstringval),
+ var_mydomain);
+ }
+ }
+ tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
+ (TOK822 **) 0));
+ }
+
+ /*
+ * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
+ * merely makes diagnostics more accurate by leaving bogus addresses
+ * alone.
+ */
+ if (tree->tail->type == '.'
+ && tree->tail->prev
+ && tree->tail->prev->type != '.'
+ && tree->tail->prev->type != '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+}
+
+/* rewrite_proto - read request and send reply */
+
+int rewrite_proto(VSTREAM *stream)
+{
+ RWR_CONTEXT *context;
+ TOK822 *tree;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, address),
+ ATTR_TYPE_END) != 2)
+ return (-1);
+
+ if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
+ context = &local_context;
+ else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
+ context = &remote_context;
+ else {
+ msg_warn("unknown context: %s", vstring_str(ruleset));
+ return (-1);
+ }
+
+ /*
+ * Sanity check. An address is supposed to be in externalized form.
+ */
+ if (*vstring_str(address) == 0) {
+ msg_warn("rewrite_addr: null address");
+ vstring_strcpy(result, vstring_str(address));
+ }
+
+ /*
+ * Convert the address from externalized (quoted) form to token list,
+ * rewrite it, and convert back.
+ */
+ else {
+ tree = tok822_scan_addr(vstring_str(address));
+ rewrite_tree(context, tree);
+ tok822_externalize(result, tree, TOK822_STR_DEFL);
+ tok822_free_tree(tree);
+ }
+ if (msg_verbose)
+ msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
+ vstring_str(address), vstring_str(result));
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write rewrite reply: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+/* rewrite_init - module initializations */
+
+void rewrite_init(void)
+{
+ ruleset = vstring_alloc(100);
+ address = vstring_alloc(100);
+ result = vstring_alloc(100);
+}
diff --git a/src/trivial-rewrite/transport.c b/src/trivial-rewrite/transport.c
new file mode 100644
index 0000000..90a2c4f
--- /dev/null
+++ b/src/trivial-rewrite/transport.c
@@ -0,0 +1,438 @@
+/*++
+/* NAME
+/* transport 3
+/* SUMMARY
+/* transport mapping
+/* SYNOPSIS
+/* #include "transport.h"
+/*
+/* TRANSPORT_INFO *transport_pre_init(maps_name, maps)
+/* const char *maps_name;
+/* const char *maps;
+/*
+/* void transport_post_init(info)
+/* TRANSPORT_INFO *info;
+/*
+/* int transport_lookup(info, address, rcpt_domain, channel, nexthop)
+/* TRANSPORT_INFO *info;
+/* const char *address;
+/* const char *rcpt_domain;
+/* VSTRING *channel;
+/* VSTRING *nexthop;
+/*
+/* void transport_free(info);
+/* TRANSPORT_INFO * info;
+/* DESCRIPTION
+/* This module implements access to the table that maps transport
+/* user@domain addresses to (channel, nexthop) tuples.
+/*
+/* transport_pre_init() performs initializations that should be
+/* done before the process enters the chroot jail, and
+/* before calling transport_lookup().
+/*
+/* transport_post_init() can be invoked after entering the chroot
+/* jail, and must be called before calling transport_lookup().
+/*
+/* transport_lookup() finds the channel and nexthop for the given
+/* domain, and returns 1 if something was found. Otherwise, 0
+/* is returned.
+/* DIAGNOSTICS
+/* info->transport_path->error is non-zero when the lookup
+/* should be tried again.
+/* SEE ALSO
+/* maps(3), multi-dictionary search
+/* strip_addr(3), strip extension from address
+/* transport(5), format of transport map
+/* CONFIGURATION PARAMETERS
+/* transport_maps, names of maps to be searched.
+/* 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 <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <dict.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <strip_addr.h>
+#include <mail_params.h>
+#include <mail_addr_find.h>
+#include <match_parent_style.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "transport.h"
+
+static int transport_match_parent_style;
+
+#define STR(x) vstring_str(x)
+
+static void transport_wildcard_init(TRANSPORT_INFO *);
+
+/* transport_pre_init - pre-jail initialization */
+
+TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name,
+ const char *transport_maps)
+{
+ TRANSPORT_INFO *tp;
+
+ tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp));
+ tp->transport_path = maps_create(transport_maps_name, transport_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB
+ | DICT_FLAG_UTF8_REQUEST);
+ tp->wildcard_channel = tp->wildcard_nexthop = 0;
+ tp->wildcard_errno = 0;
+ tp->expire = 0;
+ return (tp);
+}
+
+/* transport_post_init - post-jail initialization */
+
+void transport_post_init(TRANSPORT_INFO *tp)
+{
+ transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
+ transport_wildcard_init(tp);
+}
+
+/* transport_free - destroy transport info */
+
+void transport_free(TRANSPORT_INFO *tp)
+{
+ if (tp->transport_path)
+ maps_free(tp->transport_path);
+ if (tp->wildcard_channel)
+ vstring_free(tp->wildcard_channel);
+ if (tp->wildcard_nexthop)
+ vstring_free(tp->wildcard_nexthop);
+ myfree((void *) tp);
+}
+
+/* update_entry - update from transport table entry */
+
+static void update_entry(const char *new_channel, const char *new_nexthop,
+ const char *rcpt_domain, VSTRING *channel,
+ VSTRING *nexthop)
+{
+
+ /*
+ * :[nexthop] means don't change the channel, and don't change the
+ * nexthop unless a non-default nexthop is specified. Thus, a right-hand
+ * side of ":" is the transport table equivalent of a NOOP.
+ */
+ if (*new_channel == 0) { /* :[nexthop] */
+ if (*new_nexthop != 0)
+ vstring_strcpy(nexthop, new_nexthop);
+ }
+
+ /*
+ * transport[:[nexthop]] means change the channel, and reset the nexthop
+ * to the default unless a non-default nexthop is specified.
+ */
+ else {
+ vstring_strcpy(channel, new_channel);
+ if (*new_nexthop != 0)
+ vstring_strcpy(nexthop, new_nexthop);
+ else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0
+ && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0)
+ vstring_strcpy(nexthop, rcpt_domain);
+ else
+ vstring_strcpy(nexthop, "Address is undeliverable");
+ }
+}
+
+/* parse_transport_entry - parse transport table entry */
+
+static void parse_transport_entry(const char *value, const char *rcpt_domain,
+ VSTRING *channel, VSTRING *nexthop)
+{
+ char *saved_value;
+ const char *host;
+
+#define FOUND 1
+#define NOTFOUND 0
+
+ /*
+ * It would be great if we could specify a recipient address in the
+ * lookup result. Unfortunately, we cannot simply run the result through
+ * a parser that recognizes "transport:user@domain" because the lookup
+ * result can have arbitrary content (especially in the case of the error
+ * mailer).
+ */
+ saved_value = mystrdup(value);
+ host = split_at(saved_value, ':');
+ update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop);
+ myfree(saved_value);
+}
+
+/* transport_wildcard_init - (re) initialize wild-card lookup result */
+
+static void transport_wildcard_init(TRANSPORT_INFO *tp)
+{
+ VSTRING *channel = vstring_alloc(10);
+ VSTRING *nexthop = vstring_alloc(10);
+ const char *value;
+
+ /*
+ * Both channel and nexthop may be zero-length strings. Therefore we must
+ * use something else to represent "wild-card does not exist". We use
+ * null VSTRING pointers, for historical reasons.
+ */
+ if (tp->wildcard_channel)
+ vstring_free(tp->wildcard_channel);
+ if (tp->wildcard_nexthop)
+ vstring_free(tp->wildcard_nexthop);
+
+ /*
+ * Technically, the wildcard lookup pattern is redundant. A static map
+ * (keys always match, result is fixed string) could achieve the same:
+ *
+ * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
+ *
+ * But the user interface of such an approach would be less intuitive. We
+ * tolerate the continued existence of wildcard lookup patterns because
+ * of human interface considerations.
+ */
+#define WILDCARD "*"
+#define FULL 0
+#define PARTIAL DICT_FLAG_FIXED
+
+ if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) {
+ parse_transport_entry(value, "", channel, nexthop);
+ tp->wildcard_errno = 0;
+ tp->wildcard_channel = channel;
+ tp->wildcard_nexthop = nexthop;
+ if (msg_verbose)
+ msg_info("wildcard_{chan:hop}={%s:%s}",
+ vstring_str(channel), vstring_str(nexthop));
+ } else {
+ tp->wildcard_errno = tp->transport_path->error;
+ vstring_free(channel);
+ vstring_free(nexthop);
+ tp->wildcard_channel = 0;
+ tp->wildcard_nexthop = 0;
+ }
+ tp->expire = event_time() + 30; /* XXX make configurable */
+}
+
+/* transport_lookup - map a transport domain */
+
+int transport_lookup(TRANSPORT_INFO *tp, const char *addr,
+ const char *rcpt_domain,
+ VSTRING *channel, VSTRING *nexthop)
+{
+ char *ratsign = 0;
+ const char *value;
+
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+#define DISCARD_EXTENSION ((char **) 0)
+
+ /*
+ * The null recipient is rewritten to the local mailer daemon address.
+ */
+ if (*addr == 0) {
+ msg_warn("transport_lookup: null address - skipping table lookup");
+ return (NOTFOUND);
+ }
+
+ /*
+ * Look up the full and extension-stripped address, then match the domain
+ * and subdomains. Try the external form before the backwards-compatible
+ * internal form.
+ */
+#define LOOKUP_STRATEGY \
+ (MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \
+ (transport_match_parent_style == MATCH_FLAG_PARENT ? \
+ MA_FIND_PDMS : MA_FIND_PDDMDS))
+
+ if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
+ msg_panic("transport_lookup: bad address: \"%s\"", addr);
+
+ if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0,
+ LOOKUP_STRATEGY)) != 0) {
+ parse_transport_entry(value, rcpt_domain, channel, nexthop);
+ return (FOUND);
+ }
+ if (tp->transport_path->error != 0)
+ return (NOTFOUND);
+
+ /*
+ * Fall back to the wild-card entry.
+ */
+ if (tp->wildcard_errno || event_time() > tp->expire)
+ transport_wildcard_init(tp);
+ if (tp->wildcard_errno) {
+ tp->transport_path->error = tp->wildcard_errno;
+ return (NOTFOUND);
+ } else if (tp->wildcard_channel) {
+ update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
+ rcpt_domain, channel, nexthop);
+ return (FOUND);
+ }
+
+ /*
+ * We really did not find it.
+ */
+ return (NOTFOUND);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read an address from stdin, and spit out
+ * the lookup result.
+ */
+
+#include <string.h>
+
+#include <mail_conf.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s [-v] database", progname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(100);
+ VSTRING *channel = vstring_alloc(100);
+ VSTRING *nexthop = vstring_alloc(100);
+ TRANSPORT_INFO *tp;
+ char *bp;
+ char *addr_field;
+ char *rcpt_domain;
+ char *expect_channel;
+ char *expect_nexthop;
+ int status;
+ int ch;
+ int errs = 0;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 1)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
+
+ mail_conf_read(); /* XXX eliminate dependency. */
+ UPDATE(var_rcpt_delim, "+");
+ UPDATE(var_mydomain, "localdomain");
+ UPDATE(var_myorigin, "localhost.localdomain");
+ UPDATE(var_mydest, "localhost.localdomain");
+
+ tp = transport_pre_init("transport map", argv[optind]);
+ transport_post_init(tp);
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+
+ /*
+ * Parse the input and expectations. XXX We can't expect empty
+ * fields, so require '-' instead.
+ */
+ if ((addr_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no address field");
+ if ((rcpt_domain = strrchr(addr_field, '@')) == 0)
+ msg_fatal("no recipient domain");
+ rcpt_domain += 1;
+ expect_channel = mystrtok(&bp, ":");
+ expect_nexthop = mystrtok(&bp, ":");
+ if ((expect_channel != 0) != (expect_nexthop != 0))
+ msg_fatal("specify both channel and nexthop, or specify neither");
+ if (expect_channel) {
+ if (strcmp(expect_channel, "-") == 0)
+ *expect_channel = 0;
+ if (strcmp(expect_nexthop, "-") == 0)
+ *expect_nexthop = 0;
+ vstring_strcpy(channel, "DEFAULT");
+ vstring_strcpy(nexthop, rcpt_domain);
+ }
+ if (mystrtok(&bp, ":") != 0)
+ msg_fatal("garbage after nexthop field");
+
+ /*
+ * Lookups.
+ */
+ status = transport_lookup(tp, addr_field, rcpt_domain,
+ channel, nexthop);
+
+ /*
+ * Enforce expectations.
+ */
+ if (expect_nexthop && status) {
+ vstream_printf("%s:%s -> %s:%s \n",
+ addr_field, rcpt_domain,
+ STR(channel), STR(nexthop));
+ vstream_fflush(VSTREAM_OUT);
+ if (strcmp(expect_channel, STR(channel)) != 0) {
+ msg_warn("expect channel '%s' but got '%s'",
+ expect_channel, STR(channel));
+ errs = 1;
+ }
+ if (strcmp(expect_nexthop, STR(nexthop)) != 0) {
+ msg_warn("expect nexthop '%s' but got '%s'",
+ expect_nexthop, STR(nexthop));
+ errs = 1;
+ }
+ } else if (expect_nexthop && !status) {
+ vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
+ tp->transport_path->error ?
+ "(try again)" : "(not found)");
+ vstream_fflush(VSTREAM_OUT);
+ msg_warn("expect channel '%s' but got none", expect_channel);
+ msg_warn("expect nexthop '%s' but got none", expect_nexthop);
+ errs = 1;
+ } else if (!status) {
+ vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
+ tp->transport_path->error ?
+ "(try again)" : "(not found)");
+ }
+ }
+ transport_free(tp);
+ vstring_free(nexthop);
+ vstring_free(channel);
+ vstring_free(buffer);
+ exit(errs != 0);
+}
+
+#endif
diff --git a/src/trivial-rewrite/transport.h b/src/trivial-rewrite/transport.h
new file mode 100644
index 0000000..7db0b50
--- /dev/null
+++ b/src/trivial-rewrite/transport.h
@@ -0,0 +1,51 @@
+/*++
+/* NAME
+/* transport 3h
+/* SUMMARY
+/* transport mapping
+/* SYNOPSIS
+/* #include "transport.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+
+ /*
+ * External interface.
+ */
+typedef struct TRANSPORT_INFO {
+ MAPS *transport_path;
+ VSTRING *wildcard_channel;
+ VSTRING *wildcard_nexthop;
+ int wildcard_errno;
+ time_t expire;
+} TRANSPORT_INFO;
+
+extern TRANSPORT_INFO *transport_pre_init(const char *, const char *);
+extern void transport_post_init(TRANSPORT_INFO *);
+extern int transport_lookup(TRANSPORT_INFO *, const char *, const char *, VSTRING *, VSTRING *);
+extern void transport_free(TRANSPORT_INFO *);
+
+/* 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
+/*--*/
diff --git a/src/trivial-rewrite/transport.in b/src/trivial-rewrite/transport.in
new file mode 100644
index 0000000..1ed3a53
--- /dev/null
+++ b/src/trivial-rewrite/transport.in
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Format: address:expected_channel:expected_nexthop
+# No expectation means no match is expected.
+# Specify "-" to expect an empty string.
+
+echo ==== no wildcard
+${VALGRIND} ./transport 'inline:{rcpt1@example1.com=channel1:nexthop1, rcpt2@example2=channel2:, example3=channel3}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt1+ext1@example1.com:channel1:nexthop1
+rcpt2@example2:channel2:example2
+rcpt@example3:channel3:example3
+EOF
+
+echo ==== with wildcard channel and nexthop
+${VALGRIND} ./transport 'inline:{*=channel0:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:channel0:nexthop0
+EOF
+
+echo ==== with wildcard channel only
+${VALGRIND} ./transport 'inline:{*=channel0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:channel0:example2
+EOF
+
+echo ==== with wildcard nexthop only
+${VALGRIND} ./transport 'inline:{*=:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:DEFAULT:nexthop0
+EOF
+
+echo ==== with wildcard empty fields.
+${VALGRIND} ./transport 'inline:{*=:, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:DEFAULT:example2
+EOF
+
+echo === subdomain test
+${VALGRIND} ./transport 'inline:{example=:example-result,.example=:dot-example-result}' <<'EOF'
+plain1+ext@other-example:
+foo@example:DEFAULT:example-result
+foo@sub.example:DEFAULT:dot-example-result
+foo@sub.sub.example:DEFAULT:dot-example-result
+EOF
diff --git a/src/trivial-rewrite/transport.ref b/src/trivial-rewrite/transport.ref
new file mode 100644
index 0000000..47ab07b
--- /dev/null
+++ b/src/trivial-rewrite/transport.ref
@@ -0,0 +1,22 @@
+==== no wildcard
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt1+ext1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel2:example2
+rcpt@example3:example3 -> channel3:example3
+==== with wildcard channel and nexthop
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel0:nexthop0
+==== with wildcard channel only
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel0:example2
+==== with wildcard nexthop only
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> DEFAULT:nexthop0
+==== with wildcard empty fields.
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> DEFAULT:example2
+=== subdomain test
+plain1+ext@other-example:other-example -> (not found)
+foo@example:example -> DEFAULT:example-result
+foo@sub.example:sub.example -> DEFAULT:dot-example-result
+foo@sub.sub.example:sub.sub.example -> DEFAULT:dot-example-result
diff --git a/src/trivial-rewrite/trivial-rewrite.c b/src/trivial-rewrite/trivial-rewrite.c
new file mode 100644
index 0000000..675af80
--- /dev/null
+++ b/src/trivial-rewrite/trivial-rewrite.c
@@ -0,0 +1,667 @@
+/*++
+/* NAME
+/* trivial-rewrite 8
+/* SUMMARY
+/* Postfix address rewriting and resolving daemon
+/* SYNOPSIS
+/* \fBtrivial-rewrite\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBtrivial-rewrite\fR(8) daemon processes three types of client
+/* service requests:
+/* .IP "\fBrewrite \fIcontext address\fR"
+/* Rewrite an address to standard form, according to the
+/* address rewriting context:
+/* .RS
+/* .IP \fBlocal\fR
+/* Append the domain names specified with \fB$myorigin\fR or
+/* \fB$mydomain\fR to incomplete addresses; do \fBswap_bangpath\fR
+/* and \fBallow_percent_hack\fR processing as described below, and
+/* strip source routed addresses (\fI@site,@site:user@domain\fR)
+/* to \fIuser@domain\fR form.
+/* .IP \fBremote\fR
+/* Append the domain name specified with
+/* \fB$remote_header_rewrite_domain\fR to incomplete
+/* addresses. Otherwise the result is identical to that of
+/* the \fBlocal\fR address rewriting context. This prevents
+/* Postfix from appending the local domain to spam from poorly
+/* written remote clients.
+/* .RE
+/* .IP "\fBresolve \fIsender\fR \fIaddress\fR"
+/* Resolve the address to a (\fItransport\fR, \fInexthop\fR,
+/* \fIrecipient\fR, \fIflags\fR) quadruple. The meaning of
+/* the results is as follows:
+/* .RS
+/* .IP \fItransport\fR
+/* The delivery agent to use. This is the first field of an entry
+/* in the \fBmaster.cf\fR file.
+/* .IP \fInexthop\fR
+/* The host to send to and optional delivery method information.
+/* .IP \fIrecipient\fR
+/* The envelope recipient address that is passed on to \fInexthop\fR.
+/* .IP \fIflags\fR
+/* The address class, whether the address requires relaying,
+/* whether the address has problems, and whether the request failed.
+/* .RE
+/* .IP "\fBverify \fIsender\fR \fIaddress\fR"
+/* Resolve the address for address verification purposes.
+/* SERVER PROCESS MANAGEMENT
+/* .ad
+/* .fi
+/* The \fBtrivial-rewrite\fR(8) servers run under control by
+/* the Postfix master(8)
+/* server. Each server can handle multiple simultaneous connections.
+/* When all servers are busy while a client connects, the master
+/* creates a new server process, provided that the trivial-rewrite
+/* server process limit is not exceeded.
+/* Each trivial-rewrite server terminates after
+/* serving at least \fB$max_use\fR clients of after \fB$max_idle\fR
+/* seconds of idle time.
+/* STANDARDS
+/* .ad
+/* .fi
+/* None. The command does not interact with the outside world.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBtrivial-rewrite\fR(8) daemon is not security sensitive.
+/* By default, this daemon does not talk to remote or local users.
+/* It can run at a fixed low privilege in a chrooted environment.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* On busy mail systems a long time may pass before a \fBmain.cf\fR
+/* change affecting \fBtrivial-rewrite\fR(8) is picked up. 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 "\fBresolve_dequoted_address (yes)\fR"
+/* Resolve a recipient address safely instead of correctly, by
+/* looking inside quotes.
+/* .PP
+/* Available with Postfix version 2.1 and later:
+/* .IP "\fBresolve_null_domain (no)\fR"
+/* Resolve an address that ends in the "@" null domain as if the
+/* local hostname were specified, instead of rejecting the address as
+/* invalid.
+/* .PP
+/* Available with Postfix version 2.3 and later:
+/* .IP "\fBresolve_numeric_domain (no)\fR"
+/* Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+/* rejecting the address as invalid.
+/* .PP
+/* Available with Postfix version 2.5 and later:
+/* .IP "\fBallow_min_user (no)\fR"
+/* Allow a sender or recipient address to have `-' as the first
+/* character.
+/* ADDRESS REWRITING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBmyorigin ($myhostname)\fR"
+/* The domain name that locally-posted mail appears to come
+/* from, and that locally posted mail is delivered to.
+/* .IP "\fBallow_percent_hack (yes)\fR"
+/* Enable the rewriting of the form "user%domain" to "user@domain".
+/* .IP "\fBappend_at_myorigin (yes)\fR"
+/* With locally submitted mail, append the string "@$myorigin" to mail
+/* addresses without domain information.
+/* .IP "\fBappend_dot_mydomain (Postfix >= 3.0: no, Postfix < 3.0: yes)\fR"
+/* With locally submitted mail, append the string ".$mydomain" to
+/* addresses that have no ".domain" information.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* .IP "\fBswap_bangpath (yes)\fR"
+/* Enable the rewriting of "site!user" into "user@site".
+/* .PP
+/* Available in Postfix 2.2 and later:
+/* .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.
+/* ROUTING CONTROLS
+/* .ad
+/* .fi
+/* The following is applicable to Postfix version 2.0 and later.
+/* Earlier versions do not have support for: virtual_transport,
+/* relay_transport, virtual_alias_domains, virtual_mailbox_domains
+/* or proxy_interfaces.
+/* .IP "\fBlocal_transport (local:$myhostname)\fR"
+/* The default mail delivery transport and next-hop destination
+/* for final delivery to domains listed with mydestination, and for
+/* [ipaddress] destinations that match $inet_interfaces or $proxy_interfaces.
+/* .IP "\fBvirtual_transport (virtual)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* final delivery to domains listed with $virtual_mailbox_domains.
+/* .IP "\fBrelay_transport (relay)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* remote delivery to domains listed with $relay_domains.
+/* .IP "\fBdefault_transport (smtp)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* destinations that do not match $mydestination, $inet_interfaces,
+/* $proxy_interfaces, $virtual_alias_domains, $virtual_mailbox_domains,
+/* or $relay_domains.
+/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
+/* A list of Postfix features where the pattern "example.com" also
+/* matches subdomains of example.com,
+/* instead of requiring an explicit ".example.com" pattern.
+/* .IP "\fBrelayhost (empty)\fR"
+/* The next-hop destination(s) for non-local mail; overrides non-local
+/* domains in recipient addresses.
+/* .IP "\fBtransport_maps (empty)\fR"
+/* Optional lookup tables with mappings from recipient address to
+/* (message delivery transport, next-hop destination).
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsender_dependent_relayhost_maps (empty)\fR"
+/* A sender-dependent override for the global relayhost parameter
+/* setting.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBempty_address_relayhost_maps_lookup_key (<>)\fR"
+/* The sender_dependent_relayhost_maps search string that will be
+/* used instead of the null sender address.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBempty_address_default_transport_maps_lookup_key (<>)\fR"
+/* The sender_dependent_default_transport_maps search string that
+/* will be used instead of the null sender address.
+/* .IP "\fBsender_dependent_default_transport_maps (empty)\fR"
+/* A sender-dependent override for the global default_transport
+/* parameter setting.
+/* ADDRESS VERIFICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix version 2.1 introduces sender and recipient address verification.
+/* This feature is implemented by sending probe email messages that
+/* are not actually delivered.
+/* By default, address verification probes use the same route
+/* as regular mail. To override specific aspects of message
+/* routing for address verification probes, specify one or more
+/* of the following:
+/* .IP "\fBaddress_verify_local_transport ($local_transport)\fR"
+/* Overrides the local_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_virtual_transport ($virtual_transport)\fR"
+/* Overrides the virtual_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_relay_transport ($relay_transport)\fR"
+/* Overrides the relay_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_default_transport ($default_transport)\fR"
+/* Overrides the default_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_relayhost ($relayhost)\fR"
+/* Overrides the relayhost parameter setting for address verification
+/* probes.
+/* .IP "\fBaddress_verify_transport_maps ($transport_maps)\fR"
+/* Overrides the transport_maps parameter setting for address verification
+/* probes.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBaddress_verify_sender_dependent_relayhost_maps ($sender_dependent_relayhost_maps)\fR"
+/* Overrides the sender_dependent_relayhost_maps parameter setting for address
+/* verification probes.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBaddress_verify_sender_dependent_default_transport_maps ($sender_dependent_default_transport_maps)\fR"
+/* Overrides the sender_dependent_default_transport_maps parameter
+/* setting for address verification probes.
+/* 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 "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+/* The recipient of mail addressed to the null address.
+/* .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 "\fBrelocated_maps (empty)\fR"
+/* Optional lookup tables with new contact information for users or
+/* domains that no longer exist.
+/* .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 "\fBshow_user_unknown_table_name (yes)\fR"
+/* Display the name of the recipient table in the "User unknown"
+/* responses.
+/* .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.0 and later:
+/* .IP "\fBhelpful_warnings (yes)\fR"
+/* Log warnings about problematic configuration settings, and provide
+/* helpful suggestions.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* postconf(5), configuration parameters
+/* transport(5), transport table format
+/* relocated(5), format of the "user has moved" table
+/* 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_CLASS_README, Postfix address classes howto
+/* ADDRESS_VERIFICATION_README, Postfix address verification
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <dict.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <mail_conf.h>
+#include <resolve_clnt.h>
+#include <rewrite_clnt.h>
+#include <tok822.h>
+#include <mail_addr.h>
+
+/* Multi server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <trivial-rewrite.h>
+#include <transport.h>
+
+static VSTRING *command;
+
+ /*
+ * Tunable parameters.
+ */
+char *var_transport_maps;
+bool var_swap_bangpath;
+bool var_append_dot_mydomain;
+bool var_append_at_myorigin;
+bool var_percent_hack;
+char *var_local_transport;
+char *var_virt_transport;
+char *var_relay_transport;
+int var_resolve_dequoted;
+char *var_virt_alias_maps; /* XXX virtual_alias_domains */
+char *var_virt_mailbox_maps; /* XXX virtual_mailbox_domains */
+char *var_virt_alias_doms;
+char *var_virt_mailbox_doms;
+char *var_relocated_maps;
+char *var_def_transport;
+char *var_snd_def_xport_maps;
+char *var_empty_addr;
+int var_show_unk_rcpt_table;
+int var_resolve_nulldom;
+char *var_remote_rwr_domain;
+char *var_snd_relay_maps;
+char *var_null_relay_maps_key;
+char *var_null_def_xport_maps_key;
+int var_resolve_num_dom;
+bool var_allow_min_user;
+
+ /*
+ * Shadow personality for address verification.
+ */
+char *var_vrfy_xport_maps;
+char *var_vrfy_local_xport;
+char *var_vrfy_virt_xport;
+char *var_vrfy_relay_xport;
+char *var_vrfy_def_xport;
+char *var_vrfy_snd_def_xport_maps;
+char *var_vrfy_relayhost;
+char *var_vrfy_relay_maps;
+
+ /*
+ * Different resolver personalities depending on the kind of request.
+ */
+RES_CONTEXT resolve_regular = {
+ VAR_LOCAL_TRANSPORT, &var_local_transport,
+ VAR_VIRT_TRANSPORT, &var_virt_transport,
+ VAR_RELAY_TRANSPORT, &var_relay_transport,
+ VAR_DEF_TRANSPORT, &var_def_transport,
+ VAR_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0,
+ VAR_RELAYHOST, &var_relayhost,
+ VAR_SND_RELAY_MAPS, &var_snd_relay_maps, 0,
+ VAR_TRANSPORT_MAPS, &var_transport_maps, 0
+};
+
+RES_CONTEXT resolve_verify = {
+ VAR_VRFY_LOCAL_XPORT, &var_vrfy_local_xport,
+ VAR_VRFY_VIRT_XPORT, &var_vrfy_virt_xport,
+ VAR_VRFY_RELAY_XPORT, &var_vrfy_relay_xport,
+ VAR_VRFY_DEF_XPORT, &var_vrfy_def_xport,
+ VAR_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0,
+ VAR_VRFY_RELAYHOST, &var_vrfy_relayhost,
+ VAR_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0,
+ VAR_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0
+};
+
+ /*
+ * Connection management. When file-based lookup tables change we should
+ * restart at our convenience, but avoid client read errors. We restart
+ * rather than reopen, because the process may be chrooted (and if it isn't
+ * we still need code that handles the chrooted case anyway).
+ *
+ * Three variants are implemented. Only one should be used.
+ *
+ * ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ *
+ * This code detaches the trivial-rewrite process from the master, stops
+ * accepting new clients, and handles established clients in the background,
+ * asking them to reconnect the next time they send a request. The master
+ * creates a new process that accepts connections. This is reasonably safe
+ * because the number of trivial-rewrite server processes is small compared
+ * to the number of trivial-rewrite client processes. The few extra
+ * background processes should not make a difference in Postfix's footprint.
+ * However, once a daemon detaches from the master, its exit status will be
+ * lost, and abnormal termination may remain undetected. Timely restart is
+ * achieved by checking the table changed status every 10 seconds or so
+ * before responding to a client request.
+ *
+ * ifdef CHECK_TABLE_STATS_PERIODICALLY
+ *
+ * This code runs every 10 seconds and terminates the process when lookup
+ * tables have changed. This is subject to race conditions when established
+ * clients send a request while the server exits; those clients may read EOF
+ * instead of a server reply. If the experience with the oldest option
+ * (below) is anything to go by, however, then this is unlikely to be a
+ * problem during real deployment.
+ *
+ * ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ *
+ * This is the old code. It checks the table changed status when a new client
+ * connects (i.e. before the server calls accept()), and terminates
+ * immediately. This is invisible for the connecting client, but is subject
+ * to race conditions when established clients send a request while the
+ * server exits; those clients may read EOF instead of a server reply. This
+ * has, however, not been a problem in real deployment. With the old code,
+ * timely restart is achieved by setting the ipc_ttl parameter to 60
+ * seconds, so that the table change status is checked several times a
+ * minute.
+ */
+int server_flags;
+
+ /*
+ * Define exactly one of these.
+ */
+/* #define DETACH_AND_ASK_CLIENTS_TO_RECONNECT /* correct and complex */
+#define CHECK_TABLE_STATS_PERIODICALLY /* quick */
+/* #define CHECK_TABLE_STATS_BEFORE_ACCEPT /* slow */
+
+/* rewrite_service - read request and send reply */
+
+static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv)
+{
+ int status = -1;
+
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ static time_t last;
+ time_t now;
+ const char *table;
+
+#endif
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Client connections are long-lived. Be sure to refesh timely.
+ */
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ if (server_flags == 0 && (now = event_time()) - last > 10) {
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ if (multi_server_drain() == 0)
+ server_flags = 1;
+ }
+ last = now;
+ }
+#endif
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to address rewriting. All connection-management stuff is
+ * handled by the common code in multi_server.c.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, command),
+ ATTR_TYPE_END) == 1) {
+ if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) {
+ status = rewrite_proto(stream);
+ } else if (strcmp(vstring_str(command), RESOLVE_REGULAR) == 0) {
+ status = resolve_proto(&resolve_regular, stream);
+ } else if (strcmp(vstring_str(command), RESOLVE_VERIFY) == 0) {
+ status = resolve_proto(&resolve_verify, stream);
+ } else {
+ msg_warn("bad command %.30s", printable(vstring_str(command), '?'));
+ }
+ }
+ if (status < 0)
+ multi_server_disconnect(stream);
+}
+
+/* pre_accept - see if tables have changed */
+
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+
+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);
+ }
+}
+
+#endif
+
+/* post_accept - announce our protocol name */
+
+static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv,
+ HTABLE *unused_attr)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+static void check_table_stats(int unused_event, void *unused_context)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+ event_request_timer(check_table_stats, (void *) 0, 10);
+}
+
+/* pre_jail_init - initialize before entering chroot jail */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ command = vstring_alloc(100);
+ rewrite_init();
+ resolve_init();
+ if (*RES_PARAM_VALUE(resolve_regular.transport_maps))
+ resolve_regular.transport_info =
+ transport_pre_init(resolve_regular.transport_maps_name,
+ RES_PARAM_VALUE(resolve_regular.transport_maps));
+ if (*RES_PARAM_VALUE(resolve_verify.transport_maps))
+ resolve_verify.transport_info =
+ transport_pre_init(resolve_verify.transport_maps_name,
+ RES_PARAM_VALUE(resolve_verify.transport_maps));
+ if (*RES_PARAM_VALUE(resolve_regular.snd_relay_maps))
+ resolve_regular.snd_relay_info =
+ maps_create(resolve_regular.snd_relay_maps_name,
+ RES_PARAM_VALUE(resolve_regular.snd_relay_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_verify.snd_relay_maps))
+ resolve_verify.snd_relay_info =
+ maps_create(resolve_verify.snd_relay_maps_name,
+ RES_PARAM_VALUE(resolve_verify.snd_relay_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps))
+ resolve_regular.snd_def_xp_info =
+ maps_create(resolve_regular.snd_def_xp_maps_name,
+ RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps))
+ resolve_verify.snd_def_xp_info =
+ maps_create(resolve_verify.snd_def_xp_maps_name,
+ RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+}
+
+/* post_jail_init - initialize after entering chroot jail */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+ if (resolve_regular.transport_info)
+ transport_post_init(resolve_regular.transport_info);
+ if (resolve_verify.transport_info)
+ transport_post_init(resolve_verify.transport_info);
+ check_table_stats(0, (void *) 0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton code */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
+ VAR_LOCAL_TRANSPORT, DEF_LOCAL_TRANSPORT, &var_local_transport, 1, 0,
+ VAR_VIRT_TRANSPORT, DEF_VIRT_TRANSPORT, &var_virt_transport, 1, 0,
+ VAR_RELAY_TRANSPORT, DEF_RELAY_TRANSPORT, &var_relay_transport, 1, 0,
+ VAR_DEF_TRANSPORT, DEF_DEF_TRANSPORT, &var_def_transport, 1, 0,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
+ VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
+ VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
+ VAR_VRFY_XPORT_MAPS, DEF_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0, 0,
+ VAR_VRFY_LOCAL_XPORT, DEF_VRFY_LOCAL_XPORT, &var_vrfy_local_xport, 1, 0,
+ VAR_VRFY_VIRT_XPORT, DEF_VRFY_VIRT_XPORT, &var_vrfy_virt_xport, 1, 0,
+ VAR_VRFY_RELAY_XPORT, DEF_VRFY_RELAY_XPORT, &var_vrfy_relay_xport, 1, 0,
+ VAR_VRFY_DEF_XPORT, DEF_VRFY_DEF_XPORT, &var_vrfy_def_xport, 1, 0,
+ VAR_VRFY_RELAYHOST, DEF_VRFY_RELAYHOST, &var_vrfy_relayhost, 0, 0,
+ VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0,
+ VAR_SND_RELAY_MAPS, DEF_SND_RELAY_MAPS, &var_snd_relay_maps, 0, 0,
+ VAR_NULL_RELAY_MAPS_KEY, DEF_NULL_RELAY_MAPS_KEY, &var_null_relay_maps_key, 1, 0,
+ VAR_VRFY_RELAY_MAPS, DEF_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0, 0,
+ VAR_SND_DEF_XPORT_MAPS, DEF_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0, 0,
+ VAR_NULL_DEF_XPORT_MAPS_KEY, DEF_NULL_DEF_XPORT_MAPS_KEY, &var_null_def_xport_maps_key, 1, 0,
+ VAR_VRFY_SND_DEF_XPORT_MAPS, DEF_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_SWAP_BANGPATH, DEF_SWAP_BANGPATH, &var_swap_bangpath,
+ VAR_APP_AT_MYORIGIN, DEF_APP_AT_MYORIGIN, &var_append_at_myorigin,
+ VAR_PERCENT_HACK, DEF_PERCENT_HACK, &var_percent_hack,
+ VAR_RESOLVE_DEQUOTED, DEF_RESOLVE_DEQUOTED, &var_resolve_dequoted,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_RESOLVE_NULLDOM, DEF_RESOLVE_NULLDOM, &var_resolve_nulldom,
+ VAR_RESOLVE_NUM_DOM, DEF_RESOLVE_NUM_DOM, &var_resolve_num_dom,
+ VAR_ALLOW_MIN_USER, DEF_ALLOW_MIN_USER, &var_allow_min_user,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, rewrite_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+#endif
+ CA_MAIL_SERVER_POST_ACCEPT(post_accept),
+ 0);
+}
diff --git a/src/trivial-rewrite/trivial-rewrite.h b/src/trivial-rewrite/trivial-rewrite.h
new file mode 100644
index 0000000..e27dd00
--- /dev/null
+++ b/src/trivial-rewrite/trivial-rewrite.h
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* trivial-rewrite 3h
+/* SUMMARY
+/* mail address rewriter and resolver
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <tok822.h>
+#include <maps.h>
+
+ /*
+ * Connection management.
+ */
+extern int server_flags;
+
+ /*
+ * rewrite.c
+ */
+typedef struct {
+ const char *origin_name; /* name of variable */
+ char **origin; /* default origin */
+ const char *domain_name; /* name of variable */
+ char **domain; /* default domain */
+} RWR_CONTEXT;
+
+#define REW_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */
+
+extern void rewrite_init(void);
+extern int rewrite_proto(VSTREAM *);
+extern void rewrite_addr(RWR_CONTEXT *, char *, VSTRING *);
+extern void rewrite_tree(RWR_CONTEXT *, TOK822 *);
+extern RWR_CONTEXT local_context;
+extern RWR_CONTEXT inval_context;
+
+ /*
+ * resolve.c
+ */
+typedef struct {
+ const char *local_transport_name; /* name of variable */
+ char **local_transport; /* local transport:nexthop */
+ const char *virt_transport_name; /* name of variable */
+ char **virt_transport; /* virtual mailbox transport:nexthop */
+ const char *relay_transport_name; /* name of variable */
+ char **relay_transport; /* relay transport:nexthop */
+ const char *def_transport_name; /* name of variable */
+ char **def_transport; /* default transport:nexthop */
+ const char *snd_def_xp_maps_name; /* name of variable */
+ char **snd_def_xp_maps; /* maptype:mapname */
+ MAPS *snd_def_xp_info; /* handle */
+ const char *relayhost_name; /* name of variable */
+ char **relayhost; /* for relay and default transport */
+ const char *snd_relay_maps_name; /* name of variable */
+ char **snd_relay_maps; /* maptype:mapname */
+ MAPS *snd_relay_info; /* handle */
+ const char *transport_maps_name; /* name of variable */
+ char **transport_maps; /* maptype:mapname */
+ struct TRANSPORT_INFO *transport_info; /* handle */
+} RES_CONTEXT;
+
+#define RES_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */
+
+extern void resolve_init(void);
+extern int resolve_proto(RES_CONTEXT *, VSTREAM *);
+extern int resolve_class(const char *);
+
+/* 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/util/.indent.pro b/src/util/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/util/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/util/.printfck b/src/util/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/util/.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/util/Makefile.in b/src/util/Makefile.in
new file mode 100644
index 0000000..c59cdf9
--- /dev/null
+++ b/src/util/Makefile.in
@@ -0,0 +1,2756 @@
+SHELL = /bin/sh
+SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
+ attr_print64.c attr_print_plain.c attr_scan0.c attr_scan64.c \
+ attr_scan_plain.c auto_clnt.c base64_code.c basename.c binhash.c \
+ chroot_uid.c cidr_match.c clean_env.c close_on_exec.c concatenate.c \
+ ctable.c dict.c dict_alloc.c dict_cdb.c dict_cidr.c dict_db.c \
+ dict_dbm.c dict_debug.c dict_env.c dict_ht.c dict_lmdb.c dict_ni.c dict_nis.c \
+ dict_nisplus.c dict_open.c dict_pcre.c dict_regexp.c dict_sdbm.c \
+ dict_static.c dict_tcp.c dict_unix.c dir_forest.c doze.c dummy_read.c \
+ dummy_write.c duplex_pipe.c environ.c events.c exec_command.c \
+ fifo_listen.c fifo_trigger.c file_limit.c find_inet.c fsspace.c \
+ fullname.c get_domainname.c get_hostname.c hex_code.c hex_quote.c \
+ host_port.c htable.c inet_addr_host.c inet_addr_list.c \
+ inet_addr_local.c inet_connect.c inet_listen.c inet_proto.c \
+ inet_trigger.c line_wrap.c lowercase.c lstat_as.c mac_expand.c \
+ mac_parse.c make_dirs.c mask_addr.c match_list.c match_ops.c msg.c \
+ msg_output.c msg_syslog.c msg_vstream.c mvect.c myaddrinfo.c myflock.c \
+ mymalloc.c myrand.c mystrtok.c name_code.c name_mask.c netstring.c \
+ neuter.c non_blocking.c nvtable.c open_as.c open_limit.c open_lock.c \
+ peekfd.c posix_signals.c printable.c rand_sleep.c \
+ readlline.c ring.c safe_getenv.c safe_open.c \
+ sane_accept.c sane_connect.c sane_link.c sane_rename.c \
+ sane_socketpair.c sane_time.c scan_dir.c set_eugid.c set_ugid.c \
+ load_lib.c \
+ sigdelay.c skipblanks.c sock_addr.c spawn_command.c split_at.c \
+ split_nameval.c stat_as.c strcasecmp.c stream_connect.c \
+ stream_listen.c stream_recv_fd.c stream_send_fd.c stream_trigger.c \
+ sys_compat.c timed_connect.c timed_read.c timed_wait.c timed_write.c \
+ translit.c trimblanks.c unescape.c unix_connect.c unix_listen.c \
+ unix_recv_fd.c unix_send_fd.c unix_trigger.c unsafe.c uppercase.c \
+ username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \
+ vstream_popen.c vstring.c vstring_vstream.c watchdog.c \
+ write_buf.c sane_basename.c format_tv.c allspace.c \
+ allascii.c load_file.c killme_after.c vstream_tweak.c \
+ pass_trigger.c edit_file.c inet_windowsize.c \
+ unix_pass_fd_fix.c dict_cache.c valid_utf8_string.c dict_thash.c \
+ ip_match.c nbbio.c base32_code.c dict_test.c \
+ dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \
+ dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \
+ poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \
+ valid_utf8_hostname.c midna_domain.c argv_splitq.c balpar.c dict_union.c \
+ extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \
+ split_qnameval.c argv_attr_print.c argv_attr_scan.c dict_file.c \
+ msg_logger.c logwriter.c unix_dgram_connect.c unix_dgram_listen.c \
+ byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \
+ sane_strtol.c hash_fnv.c ldseed.c
+OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
+ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
+ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
+ chroot_uid.o cidr_match.o clean_env.o close_on_exec.o concatenate.o \
+ ctable.o dict.o dict_alloc.o dict_cidr.o dict_db.o \
+ dict_dbm.o dict_debug.o dict_env.o dict_ht.o dict_ni.o dict_nis.o \
+ dict_nisplus.o dict_open.o dict_regexp.o \
+ dict_static.o dict_tcp.o dict_unix.o dir_forest.o doze.o dummy_read.o \
+ dummy_write.o duplex_pipe.o environ.o events.o exec_command.o \
+ fifo_listen.o fifo_trigger.o file_limit.o find_inet.o fsspace.o \
+ fullname.o get_domainname.o get_hostname.o hex_code.o hex_quote.o \
+ host_port.o htable.o inet_addr_host.o inet_addr_list.o \
+ inet_addr_local.o inet_connect.o inet_listen.o inet_proto.o \
+ inet_trigger.o line_wrap.o lowercase.o lstat_as.o mac_expand.o \
+ load_lib.o \
+ mac_parse.o make_dirs.o mask_addr.o match_list.o match_ops.o msg.o \
+ msg_output.o msg_syslog.o msg_vstream.o mvect.o myaddrinfo.o myflock.o \
+ mymalloc.o myrand.o mystrtok.o name_code.o name_mask.o netstring.o \
+ neuter.o non_blocking.o nvtable.o open_as.o open_limit.o open_lock.o \
+ peekfd.o posix_signals.o printable.o rand_sleep.o \
+ readlline.o ring.o safe_getenv.o safe_open.o \
+ sane_accept.o sane_connect.o sane_link.o sane_rename.o \
+ sane_socketpair.o sane_time.o scan_dir.o set_eugid.o set_ugid.o \
+ sigdelay.o skipblanks.o sock_addr.o spawn_command.o split_at.o \
+ split_nameval.o stat_as.o $(STRCASE) stream_connect.o \
+ stream_listen.o stream_recv_fd.o stream_send_fd.o stream_trigger.o \
+ sys_compat.o timed_connect.o timed_read.o timed_wait.o timed_write.o \
+ translit.o trimblanks.o unescape.o unix_connect.o unix_listen.o \
+ unix_recv_fd.o unix_send_fd.o unix_trigger.o unsafe.o uppercase.o \
+ username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \
+ vstream_popen.o vstring.o vstring_vstream.o watchdog.o \
+ write_buf.o sane_basename.o format_tv.o allspace.o \
+ allascii.o load_file.o killme_after.o vstream_tweak.o \
+ pass_trigger.o edit_file.o inet_windowsize.o \
+ unix_pass_fd_fix.o dict_cache.o valid_utf8_string.o dict_thash.o \
+ ip_match.o nbbio.o base32_code.o dict_test.o \
+ dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \
+ dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \
+ poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \
+ valid_utf8_hostname.o midna_domain.o argv_splitq.o balpar.o dict_union.o \
+ extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \
+ split_qnameval.o argv_attr_print.o argv_attr_scan.o dict_file.o \
+ msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \
+ byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o \
+ sane_strtol.o hash_fnv.o ldseed.o
+# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
+# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
+# otherwise it sets the PLUGIN_* macros.
+MAP_OBJ = dict_pcre.o $(LIB_MAP_OBJ)
+LIB_MAP_OBJ = dict_cdb.o dict_lmdb.o dict_sdbm.o slmdb.o
+HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
+ chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
+ dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
+ dict_lmdb.h dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h dict_regexp.h \
+ dict_sdbm.h dict_static.h dict_tcp.h dict_unix.h dir_forest.h \
+ events.h exec_command.h find_inet.h fsspace.h fullname.h \
+ get_domainname.h get_hostname.h hex_code.h hex_quote.h host_port.h \
+ htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \
+ inet_proto.h iostuff.h line_wrap.h listen.h lstat_as.h mac_expand.h \
+ mac_parse.h make_dirs.h mask_addr.h match_list.h msg.h \
+ msg_output.h msg_syslog.h msg_vstream.h mvect.h myaddrinfo.h myflock.h \
+ mymalloc.h myrand.h name_code.h name_mask.h netstring.h nvtable.h \
+ open_as.h open_lock.h posix_signals.h readlline.h ring.h \
+ safe.h safe_open.h sane_accept.h sane_connect.h sane_fsops.h \
+ load_lib.h \
+ sane_socketpair.h sane_time.h scan_dir.h set_eugid.h set_ugid.h \
+ sigdelay.h sock_addr.h spawn_command.h split_at.h stat_as.h \
+ stringops.h sys_defs.h timed_connect.h timed_wait.h trigger.h \
+ username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \
+ vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \
+ edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \
+ dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \
+ slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \
+ valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \
+ check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \
+ known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h
+TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
+ stream_test.c dup2_pass_on_exec.c
+DEFS = -I. -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+FILES = Makefile $(SRCS) $(HDRS)
+INCL =
+LIB = lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
+ fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \
+ inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \
+ mystrtok sigdelay translit valid_hostname vstream_popen \
+ vstring vstring_vstream doze select_bug stream_test mac_expand \
+ watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \
+ inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \
+ attr_scan0 host_port attr_scan_plain attr_print_plain htable \
+ unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \
+ myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \
+ valid_utf8_string ip_match base32_code msg_rate_delay netstring \
+ vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
+ vbuf_print split_qnameval vstream msg_logger byte_mask \
+ known_tcp_ports dict_stream find_inet binhash
+PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX)
+HTABLE_FIX = NORANDOMIZE=1
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ)
+
+$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+plugin_map_so_make: $(PLUGIN_MAP_SO)
+
+$(LIB_PREFIX)pcre$(LIB_SUFFIX): dict_pcre.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_pcre.o $(AUXLIBS_PCRE)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE) \
+ $(PLUGIN_MAP_OBJ_UPDATE)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+plugin_map_so_update: $(PLUGIN_MAP_SO)
+ -for i in $(PLUGIN_MAP_SO); \
+ do \
+ for type in $(DEFINED_MAP_TYPES); do \
+ case $$i in $(LIB_PREFIX)$$type$(LIB_SUFFIX)) \
+ cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+ continue 2;; \
+ esac; \
+ done; \
+ rm -f $(LIB_DIR)/$$i; \
+ done
+
+plugin_map_obj_update: $(LIB_MAP_OBJ)
+ -for i in $(LIB_MAP_OBJ); \
+ do \
+ cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+ done
+
+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`
+
+shar:
+ @shar $(FILES)
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+dup2_pass_on_exec: dup2_pass_on_exec.c
+ $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS)
+
+vstring: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+msg_logger: msg_logger.c $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+msg_syslog: msg_syslog.c $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+vstring_vstream: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+valid_hostname: valid_hostname.c $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+events: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+dict_open: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+fullname: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+inet_addr_local: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+inet_addr_host: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+fifo_open: fifo_open.c
+ $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS)
+
+sigdelay: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+mystrtok: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+fifo_rdwr_bug: fifo_rdwr_bug.c $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+
+fifo_rdonly_bug: fifo_rdonly_bug.c $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+
+select_bug: select_bug.c $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+
+translit: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+fsspace: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+exec_command: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+make_dirs: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+mac_parse: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+vstream_popen: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+fifo_trigger: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+doze: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+mac_expand: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+watchdog: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+unescape: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+hex_quote: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+name_mask: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+byte_mask: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+rand_sleep: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+sane_time: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+ctable: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+inet_addr_list: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_print64: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_scan64: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+base64_code: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_print0: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_scan0: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+host_port: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_scan_plain: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+attr_print_plain: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+htable: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+binhash: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+unix_recv_fd: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+unix_send_fd: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+stream_recv_fd: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+stream_send_fd: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+hex_code: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+myaddrinfo: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+myaddrinfo4: $(LIB)
+ mv myaddrinfo.o junk
+ $(CC) $(CFLAGS) -DTEST -DEMULATE_IPV4_ADDRINFO -o $@ myaddrinfo.c $(LIB) $(SYSLIBS)
+ mv junk myaddrinfo.o
+
+inet_proto: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+sane_basename: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+find_inet: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+stream_test: stream_test.c $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+
+gcctest: gccw.c gccw.ref
+ rm -f gccw.o
+ make gccw.o 2>&1 | sed "s/\`/'/g; s/return-/return /" | sort >gccw.tmp
+ diff gccw.ref gccw.tmp
+ rm -f gccw.o gccw.tmp
+
+format_tv: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+valid_utf8_string: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+ip_match: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+base32_code: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+msg_rate_delay: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+netstring: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+vstream: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+timecmp: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+dict_cache: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+midna_domain: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+casefold: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+strcasecmp_utf8: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+vbuf_print: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+split_qnameval: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+known_tcp_ports: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+dict_stream: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
+ hex_quote_test ctable_test inet_addr_list_test base64_code_test \
+ attr_scan64_test attr_scan0_test dict_pcre_tests host_port_test \
+ dict_cidr_test attr_scan_plain_test htable_test hex_code_test \
+ myaddrinfo_test format_tv_test ip_match_test name_mask_tests \
+ base32_code_test dict_thash_test surrogate_test timecmp_test \
+ dict_static_test dict_inline_test midna_domain_test casefold_test \
+ dict_utf8_test strcasecmp_utf8_test vbuf_print_test dict_regexp_test \
+ dict_union_test dict_pipe_test miss_endif_cidr_test \
+ miss_endif_regexp_test split_qnameval_test vstring_test \
+ vstream_test dict_regexp_file_test dict_cidr_file_test \
+ dict_static_file_test dict_random_test dict_random_file_test \
+ dict_inline_file_test byte_mask_tests mystrtok_test \
+ known_tcp_ports_test dict_stream_test dict_inline_regexp_test \
+ dict_inline_cidr_test binhash_test
+
+dict_pcre_tests: dict_pcre_test miss_endif_pcre_test dict_pcre_file_test \
+ dict_inline_pcre_test
+
+root_tests:
+
+valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref
+ $(SHLIB_ENV) ${VALGRIND} ./valid_hostname <valid_hostname.in 2>valid_hostname.tmp
+ diff valid_hostname.ref valid_hostname.tmp
+ rm -f valid_hostname.tmp
+
+mac_expand_test: mac_expand mac_expand.in mac_expand.ref
+ $(SHLIB_ENV) ${VALGRIND} ./mac_expand <mac_expand.in >mac_expand.tmp 2>&1
+ diff mac_expand.ref mac_expand.tmp
+ rm -f mac_expand.tmp
+
+unescape_test: unescape unescape.in unescape.ref
+ $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | od -cb >unescape.tmp
+ diff -b unescape.ref unescape.tmp
+# $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | $(SHLIB_ENV) ./unescape -e >unescape.tmp
+# diff unescape.in unescape.tmp
+ rm -f unescape.tmp
+
+hex_quote_test: hex_quote
+ $(SHLIB_ENV) ${VALGRIND} ./hex_quote <hex_quote.c | od -cb >hex_quote.tmp
+ od -cb <hex_quote.c >hex_quote.ref
+ cmp hex_quote.ref hex_quote.tmp
+ rm -f hex_quote.ref hex_quote.tmp
+
+ctable_test: ctable
+ $(SHLIB_ENV) ${VALGRIND} ./ctable <ctable.in >ctable.tmp 2>&1
+ diff ctable.ref ctable.tmp
+ rm -f ctable.tmp
+
+# On Linux, following test may require "modprobe ipv6" to enable IPv6.
+
+inet_addr_list_test: inet_addr_list
+ $(SHLIB_ENV) ${VALGRIND} ./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1
+ diff inet_addr_list.ref inet_addr_list.tmp
+ rm -f inet_addr_list.tmp
+
+sane_basename_test: sane_basename
+ $(SHLIB_ENV) ${VALGRIND} ./sane_basename <sane_basename.in >sane_basename.tmp 2>&1
+ diff sane_basename.ref sane_basename.tmp
+ rm -f sane_basename.tmp
+
+base64_code_test: base64_code
+ $(SHLIB_ENV) ${VALGRIND} ./base64_code
+
+attr_scan64_test: attr_print64 attr_scan64 attr_scan64.ref
+ ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print64 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan64)) >attr_scan64.tmp 2>&1 3>&1
+ diff attr_scan64.ref attr_scan64.tmp
+ rm -f attr_scan64.tmp
+
+attr_scan0_test: attr_print0 attr_scan0 attr_scan0.ref
+ ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print0 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan0)) >attr_scan0.tmp 2>&1 3>&1
+ diff attr_scan0.ref attr_scan0.tmp
+ rm -f attr_scan0.tmp
+
+dict_test: dict_open testdb dict_test.in dict_test.ref
+ rm -f testdb.db testdb.dir testdb.pag
+ $(SHLIB_ENV) ../postmap/postmap -N hash:testdb
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
+ diff dict_test.ref dict_test.tmp
+ $(SHLIB_ENV) ../postmap/postmap -n hash:testdb
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
+ diff dict_test.ref dict_test.tmp
+ rm -f testdb.db testdb.dir testdb.pag dict_test.tmp
+
+dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \
+ <dict_pcre.in 2>&1 | sed -e 's/uid=[0-9][0-9][0-9]*/uid=USER/' \
+ -e 's/missing )/missing closing parenthesis/' >dict_pcre.tmp
+ diff dict_pcre.ref dict_pcre.tmp
+ rm -f dict_pcre.tmp
+
+dict_pcre_file_test: dict_open dict_pcre_file.in dict_pcre_file.map dict_pcre_file.ref
+ echo this-is-file1 > dict_pcre_file1
+ echo this-is-file2 > dict_pcre_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre_file.map \
+ read src_rhs_is_file <dict_pcre_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre_file.tmp
+ diff dict_pcre_file.ref dict_pcre_file.tmp
+ rm -f dict_pcre_file.tmp dict_pcre_file1 dict_pcre_file2
+
+dict_regexp_test: dict_open dict_regexp.in dict_regexp.map dict_regexp.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp.map read <dict_regexp.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp
+ diff dict_regexp.ref dict_regexp.tmp
+ rm -f dict_regexp.tmp
+
+dict_regexp_file_test: dict_open dict_regexp_file.in dict_regexp_file.map dict_regexp_file.ref
+ echo this-is-file1 > dict_regexp_file1
+ echo this-is-file2 > dict_regexp_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp_file.map \
+ read src_rhs_is_file <dict_regexp_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp_file.tmp
+ diff dict_regexp_file.ref dict_regexp_file.tmp
+ rm -f dict_regexp_file.tmp dict_regexp_file1 dict_regexp_file2
+
+dict_cidr_test: dict_open dict_cidr.in dict_cidr.map dict_cidr.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr.map read <dict_cidr.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp
+ diff dict_cidr.ref dict_cidr.tmp
+ rm -f dict_cidr.tmp
+
+dict_cidr_file_test: dict_open dict_cidr_file.in dict_cidr_file.map dict_cidr_file.ref
+ echo this-is-file1 > dict_cidr_file1
+ echo this-is-file2 > dict_cidr_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr_file.map \
+ read src_rhs_is_file <dict_cidr_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr_file.tmp
+ diff dict_cidr_file.ref dict_cidr_file.tmp
+ rm -f dict_cidr_file.tmp dict_cidr_file1 dict_cidr_file2
+
+miss_endif_cidr_test: dict_open miss_endif_cidr.map miss_endif_cidr.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:miss_endif_cidr.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp
+ diff miss_endif_cidr.ref dict_cidr.tmp
+ rm -f dict_cidr.tmp
+
+miss_endif_pcre_test: dict_open miss_endif_re.map miss_endif_pcre.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre.tmp
+ diff miss_endif_pcre.ref dict_pcre.tmp
+ rm -f dict_pcre.tmp
+
+miss_endif_regexp_test: dict_open miss_endif_re.map miss_endif_regexp.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp
+ diff miss_endif_regexp.ref dict_regexp.tmp
+ rm -f dict_regexp.tmp
+
+split_qnameval_test: split_qnameval update
+ $(SHLIB_ENV) ${VALGRIND} ./split_qnameval
+
+dict_seq_test: dict_open testdb dict_seq.in dict_seq.ref
+ rm -f testdb.db testdb.dir testdb.pag
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb create sync < dict_seq.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' > dict_seq.tmp
+ diff dict_seq.ref dict_seq.tmp
+ rm -f testdb.db testdb.dir testdb.pag dict_seq.tmp
+
+host_port_test: host_port host_port.in host_port.ref
+ $(SHLIB_ENV) ${VALGRIND} ./host_port <host_port.in >host_port.tmp 2>&1
+ diff host_port.ref host_port.tmp
+ rm -f host_port.tmp
+
+attr_scan_plain_test: attr_print_plain attr_scan_plain attr_scan_plain.ref
+ ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print_plain 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan_plain)) >attr_scan_plain.tmp 2>&1 3>&1
+ diff attr_scan_plain.ref attr_scan_plain.tmp
+ rm -f attr_scan_plain.tmp
+
+htable_test: htable /usr/share/dict/words
+ $(SHLIB_ENV) ${VALGRIND} ./htable < /usr/share/dict/words
+
+binhash_test: binhash /usr/share/dict/words
+ $(SHLIB_ENV) ${VALGRIND} ./binhash < /usr/share/dict/words
+
+hex_code_test: hex_code
+ $(SHLIB_ENV) ${VALGRIND} ./hex_code
+
+timecmp_test: timecmp
+ $(SHLIB_ENV) ${VALGRIND} ./timecmp
+
+myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1
+ diff myaddrinfo.ref myaddrinfo.tmp
+ rm -f myaddrinfo.tmp
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1
+ diff myaddrinfo.ref2 myaddrinfo.tmp
+ rm -f myaddrinfo.tmp
+
+myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1
+ diff myaddrinfo4.ref myaddrinfo4.tmp
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1
+ diff myaddrinfo4.ref2 myaddrinfo4.tmp
+ rm -f myaddrinfo4.tmp
+
+format_tv_test: format_tv format_tv.in format_tv.ref
+ $(SHLIB_ENV) ${VALGRIND} ./format_tv <format_tv.in >format_tv.tmp
+ diff format_tv.ref format_tv.tmp
+ rm -f format_tv.tmp
+
+ip_match_test: ip_match ip_match.in ip_match.ref
+ $(SHLIB_ENV) ${VALGRIND} ./ip_match <ip_match.in >ip_match.tmp
+ diff ip_match.ref ip_match.tmp
+ rm -f ip_match.tmp
+
+name_mask_tests: name_mask_test0 name_mask_test1 name_mask_test2 \
+ name_mask_test3 name_mask_test4 name_mask_test5 name_mask_test6 \
+ name_mask_test7 name_mask_test8 name_mask_test9
+
+name_mask_test0: name_mask name_mask.in name_mask.ref0
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask IGNORE IGNORE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref0 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test1: name_mask name_mask.in name_mask.ref1
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref1 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test2: name_mask name_mask.in name_mask.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref2 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test3: name_mask name_mask.in name_mask.ref3
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask WARN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref3 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test4: name_mask name_mask.in name_mask.ref4
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref4 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test5: name_mask name_mask.in name_mask.ref5
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN RETURN < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref5 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test6: name_mask name_mask.in name_mask.ref6
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN WARN < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref6 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test7: name_mask name_mask.in name_mask.ref7
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN IGNORE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref7 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test8: name_mask name_mask.in name_mask.ref8
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,COMMA < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref8 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test9: name_mask name_mask.in name_mask.ref9
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,PIPE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref9 name_mask.tmp
+ rm -f name_mask.tmp
+
+byte_mask_tests: byte_mask_test0 byte_mask_test1 byte_mask_test2
+
+byte_mask_test0: byte_mask byte_mask.in byte_mask.ref0
+ while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask IGNORE IGNORE; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref0 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+byte_mask_test1: byte_mask byte_mask.in byte_mask.ref1
+ while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask WARN WARN; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref1 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+byte_mask_test2: byte_mask byte_mask.in byte_mask.ref2
+ -while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask FATAL FATAL; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref2 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+base32_code_test: base32_code
+ $(SHLIB_ENV) ${VALGRIND} ./base32_code
+
+dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref
+ $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
+ LANG=C sort | diff dict_thash.map -
+ NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1
+ diff dict_thash.ref dict_thash.tmp
+ rm -f dict_thash.tmp
+
+surrogate_test: dict_open surrogate.ref
+ cp /dev/null surrogate.tmp
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open hash:/xx read >>surrogate.tmp 2>&1
+ diff surrogate.ref surrogate.tmp
+ rm -f surrogate.tmp
+
+dict_static_test: dict_open dict_static.ref
+ (set -e; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:fooxx read; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx ' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx }x' read </dev/null; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:'{ foo xx }' read; \
+ ) >dict_static.tmp 2>&1
+ diff dict_static.ref dict_static.tmp
+ rm -f dict_static.tmp
+
+dict_static_file_test: dict_open dict_static_file.ref
+ echo this-is-file1 > dict_static_file1
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:fooxx read src_rhs_is_file; \
+ (echo get file1; echo get file2) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:'{ dict_static_file1 }' read src_rhs_is_file; \
+ ) >dict_static_file.tmp 2>&1
+ diff dict_static_file.ref dict_static_file.tmp
+ rm -f dict_static_file.tmp dict_static_file1
+
+dict_random_test: dict_open dict_random.ref
+ (set -e; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{123 123}' read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:123 read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 ' read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 }x' read; \
+ ) >dict_random.tmp 2>&1
+ diff dict_random.ref dict_random.tmp
+ rm -f dict_random.tmp
+
+dict_random_file_test: dict_open dict_random_file.ref
+ echo this-is-file1 > dict_random_file1
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{fooxx}' read src_rhs_is_file; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{ dict_random_file1 }' read src_rhs_is_file; \
+ ) >dict_random_file.tmp 2>&1
+ diff dict_random_file.ref dict_random_file.tmp
+ rm -f dict_random_file.tmp dict_random_file1
+
+dict_inline_test: dict_open dict_inline.ref
+ (set -e; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ }' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo = xx }' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx }x' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx x' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx {x=y}x}' read </dev/null; \
+ (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read fold_fix; \
+ (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read 'fold_fix,utf8_request'; \
+ ) >dict_inline.tmp 2>&1
+ diff dict_inline.ref dict_inline.tmp
+ rm -f dict_inline.tmp
+
+dict_inline_file_test: dict_open dict_inline_file.ref
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} \
+ ./dict_open inline:'{ foo=xx, bar=yy }' read src_rhs_is_file; \
+ echo this-is-file1 > dict_inline_file1; \
+ echo this-is-file2 > dict_inline_file2; \
+ (echo get file1; echo get file2; echo get file3) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ file1=dict_inline_file1, file2=dict_inline_file2}' read src_rhs_is_file; \
+ ) >dict_inline_file.tmp 2>&1
+ diff dict_inline_file.ref dict_inline_file.tmp
+ rm -f dict_inline_file.tmp dict_inline_file1 dict_inline_file2
+
+midna_domain_test: midna_domain midna_domain_test.in midna_domain_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./midna_domain <midna_domain_test.in >midna_domain_test.tmp 2>&1
+ diff midna_domain_test.ref midna_domain_test.tmp
+ rm -f midna_domain_test.tmp
+
+casefold_test: casefold casefold_test.in casefold_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./casefold <casefold_test.in >casefold_test.tmp 2>&1
+ diff casefold_test.ref casefold_test.tmp
+ rm -f casefold_test.tmp
+
+dict_utf8_test: dict_open dict_utf8_test.in dict_utf8_test.ref
+ $(SHLIB_ENV) sh dict_utf8_test.in >dict_utf8_test.tmp 2>&1
+ diff dict_utf8_test.ref dict_utf8_test.tmp
+ rm -f dict_utf8_test.tmp
+
+strcasecmp_utf8_test: strcasecmp_utf8 strcasecmp_utf8_test.in \
+ strcasecmp_utf8_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./strcasecmp_utf8 <strcasecmp_utf8_test.in >strcasecmp_utf8_test.tmp 2>&1
+ diff strcasecmp_utf8_test.ref strcasecmp_utf8_test.tmp
+ rm -f strcasecmp_utf8_test.tmp
+
+vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vbuf_print <vbuf_print_test.in >vbuf_print_test.tmp 2>&1
+ diff vbuf_print_test.ref vbuf_print_test.tmp
+ rm -f vbuf_print_test.tmp
+
+dict_union_test: dict_open dict_union_test.in dict_union_test.ref
+ $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1
+ diff dict_union_test.ref dict_union_test.tmp
+ rm -f dict_union_test.tmp
+
+dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref
+ $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1
+ diff dict_pipe_test.ref dict_pipe_test.tmp
+ rm -f dict_pipe_test.tmp
+
+vstring_test: dict_open vstring vstring_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1
+ diff vstring_test.ref vstring_test.tmp
+ rm -f vstring_test.tmp
+
+vstream_test: vstream vstream_test.in vstream_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vstream <vstream_test.in >vstream_test.tmp 2>&1
+ diff vstream_test.ref vstream_test.tmp
+ rm -f vstream_test.tmp
+
+mystrtok_test: mystrtok mystrtok.ref
+ $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1
+ diff mystrtok.ref mystrtok.tmp
+ rm -f mystrtok.tmp
+
+known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref
+ $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1
+ diff known_tcp_ports.ref known_tcp_ports.tmp
+ rm -f known_tcp_ports.tmp
+
+dict_stream_test: dict_stream dict_stream.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1
+ diff dict_stream.ref dict_stream.tmp
+ rm -f dict_stream.tmp
+
+dict_inline_pcre_test: dict_open dict_inline_pcre.ref
+ (echo get foo; echo get bar) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open 'pcre:{{/foo/ got foo}}' \
+ read 2>&1 | grep -v uid= >dict_inline_pcre.tmp
+ diff dict_inline_pcre.ref dict_inline_pcre.tmp
+ rm -f dict_inline_pcre.tmp
+
+dict_inline_regexp_test: dict_open dict_inline_regexp.ref
+ (echo get foo; echo get bar) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open 'regexp:{{/foo/ got foo}}' \
+ read 2>&1 | grep -v uid= >dict_inline_regexp.tmp
+ diff dict_inline_regexp.ref dict_inline_regexp.tmp
+ rm -f dict_inline_regexp.tmp
+
+dict_inline_cidr_test: dict_open dict_inline_cidr.ref
+ (echo get 1.2.3.4; echo get 4.3.2.1) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open \
+ 'cidr:{{1.2.3.4 got 1.2.3.4}}' \
+ read 2>&1 | grep -v uid= >dict_inline_cidr.tmp
+ diff dict_inline_cidr.ref dict_inline_cidr.tmp
+ rm -f dict_inline_cidr.tmp
+
+find_inet_test: find_inet find_inet.ref
+ $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1
+ diff find_inet.ref find_inet.tmp
+ rm -f find_inet.tmp
+
+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'
+allascii.o: allascii.c
+allascii.o: check_arg.h
+allascii.o: stringops.h
+allascii.o: sys_defs.h
+allascii.o: vbuf.h
+allascii.o: vstring.h
+alldig.o: alldig.c
+alldig.o: check_arg.h
+alldig.o: stringops.h
+alldig.o: sys_defs.h
+alldig.o: vbuf.h
+alldig.o: vstring.h
+allprint.o: allprint.c
+allprint.o: check_arg.h
+allprint.o: stringops.h
+allprint.o: sys_defs.h
+allprint.o: vbuf.h
+allprint.o: vstring.h
+allspace.o: allspace.c
+allspace.o: check_arg.h
+allspace.o: stringops.h
+allspace.o: sys_defs.h
+allspace.o: vbuf.h
+allspace.o: vstring.h
+argv.o: argv.c
+argv.o: argv.h
+argv.o: msg.h
+argv.o: mymalloc.h
+argv.o: sys_defs.h
+argv_attr_print.o: argv.h
+argv_attr_print.o: argv_attr.h
+argv_attr_print.o: argv_attr_print.c
+argv_attr_print.o: attr.h
+argv_attr_print.o: check_arg.h
+argv_attr_print.o: htable.h
+argv_attr_print.o: msg.h
+argv_attr_print.o: mymalloc.h
+argv_attr_print.o: nvtable.h
+argv_attr_print.o: sys_defs.h
+argv_attr_print.o: vbuf.h
+argv_attr_print.o: vstream.h
+argv_attr_print.o: vstring.h
+argv_attr_scan.o: argv.h
+argv_attr_scan.o: argv_attr.h
+argv_attr_scan.o: argv_attr_scan.c
+argv_attr_scan.o: attr.h
+argv_attr_scan.o: check_arg.h
+argv_attr_scan.o: htable.h
+argv_attr_scan.o: msg.h
+argv_attr_scan.o: mymalloc.h
+argv_attr_scan.o: nvtable.h
+argv_attr_scan.o: sys_defs.h
+argv_attr_scan.o: vbuf.h
+argv_attr_scan.o: vstream.h
+argv_attr_scan.o: vstring.h
+argv_split.o: argv.h
+argv_split.o: argv_split.c
+argv_split.o: check_arg.h
+argv_split.o: msg.h
+argv_split.o: mymalloc.h
+argv_split.o: stringops.h
+argv_split.o: sys_defs.h
+argv_split.o: vbuf.h
+argv_split.o: vstring.h
+argv_split_at.o: argv.h
+argv_split_at.o: argv_split_at.c
+argv_split_at.o: check_arg.h
+argv_split_at.o: msg.h
+argv_split_at.o: mymalloc.h
+argv_split_at.o: split_at.h
+argv_split_at.o: stringops.h
+argv_split_at.o: sys_defs.h
+argv_split_at.o: vbuf.h
+argv_split_at.o: vstring.h
+argv_splitq.o: argv.h
+argv_splitq.o: argv_splitq.c
+argv_splitq.o: check_arg.h
+argv_splitq.o: msg.h
+argv_splitq.o: mymalloc.h
+argv_splitq.o: stringops.h
+argv_splitq.o: sys_defs.h
+argv_splitq.o: vbuf.h
+argv_splitq.o: vstring.h
+attr_clnt.o: attr.h
+attr_clnt.o: attr_clnt.c
+attr_clnt.o: attr_clnt.h
+attr_clnt.o: auto_clnt.h
+attr_clnt.o: check_arg.h
+attr_clnt.o: compat_va_copy.h
+attr_clnt.o: htable.h
+attr_clnt.o: iostuff.h
+attr_clnt.o: msg.h
+attr_clnt.o: mymalloc.h
+attr_clnt.o: nvtable.h
+attr_clnt.o: sys_defs.h
+attr_clnt.o: vbuf.h
+attr_clnt.o: vstream.h
+attr_clnt.o: vstring.h
+attr_print0.o: attr.h
+attr_print0.o: attr_print0.c
+attr_print0.o: base64_code.h
+attr_print0.o: check_arg.h
+attr_print0.o: htable.h
+attr_print0.o: msg.h
+attr_print0.o: mymalloc.h
+attr_print0.o: nvtable.h
+attr_print0.o: sys_defs.h
+attr_print0.o: vbuf.h
+attr_print0.o: vstream.h
+attr_print0.o: vstring.h
+attr_print64.o: attr.h
+attr_print64.o: attr_print64.c
+attr_print64.o: base64_code.h
+attr_print64.o: check_arg.h
+attr_print64.o: htable.h
+attr_print64.o: msg.h
+attr_print64.o: mymalloc.h
+attr_print64.o: nvtable.h
+attr_print64.o: sys_defs.h
+attr_print64.o: vbuf.h
+attr_print64.o: vstream.h
+attr_print64.o: vstring.h
+attr_print_plain.o: attr.h
+attr_print_plain.o: attr_print_plain.c
+attr_print_plain.o: base64_code.h
+attr_print_plain.o: check_arg.h
+attr_print_plain.o: htable.h
+attr_print_plain.o: msg.h
+attr_print_plain.o: mymalloc.h
+attr_print_plain.o: nvtable.h
+attr_print_plain.o: sys_defs.h
+attr_print_plain.o: vbuf.h
+attr_print_plain.o: vstream.h
+attr_print_plain.o: vstring.h
+attr_scan0.o: attr.h
+attr_scan0.o: attr_scan0.c
+attr_scan0.o: base64_code.h
+attr_scan0.o: check_arg.h
+attr_scan0.o: htable.h
+attr_scan0.o: msg.h
+attr_scan0.o: mymalloc.h
+attr_scan0.o: nvtable.h
+attr_scan0.o: stringops.h
+attr_scan0.o: sys_defs.h
+attr_scan0.o: vbuf.h
+attr_scan0.o: vstream.h
+attr_scan0.o: vstring.h
+attr_scan0.o: vstring_vstream.h
+attr_scan64.o: attr.h
+attr_scan64.o: attr_scan64.c
+attr_scan64.o: base64_code.h
+attr_scan64.o: check_arg.h
+attr_scan64.o: htable.h
+attr_scan64.o: msg.h
+attr_scan64.o: mymalloc.h
+attr_scan64.o: nvtable.h
+attr_scan64.o: stringops.h
+attr_scan64.o: sys_defs.h
+attr_scan64.o: vbuf.h
+attr_scan64.o: vstream.h
+attr_scan64.o: vstring.h
+attr_scan_plain.o: attr.h
+attr_scan_plain.o: attr_scan_plain.c
+attr_scan_plain.o: base64_code.h
+attr_scan_plain.o: check_arg.h
+attr_scan_plain.o: htable.h
+attr_scan_plain.o: msg.h
+attr_scan_plain.o: mymalloc.h
+attr_scan_plain.o: nvtable.h
+attr_scan_plain.o: stringops.h
+attr_scan_plain.o: sys_defs.h
+attr_scan_plain.o: vbuf.h
+attr_scan_plain.o: vstream.h
+attr_scan_plain.o: vstring.h
+auto_clnt.o: auto_clnt.c
+auto_clnt.o: auto_clnt.h
+auto_clnt.o: check_arg.h
+auto_clnt.o: connect.h
+auto_clnt.o: events.h
+auto_clnt.o: iostuff.h
+auto_clnt.o: msg.h
+auto_clnt.o: mymalloc.h
+auto_clnt.o: split_at.h
+auto_clnt.o: sys_defs.h
+auto_clnt.o: vbuf.h
+auto_clnt.o: vstream.h
+balpar.o: balpar.c
+balpar.o: check_arg.h
+balpar.o: stringops.h
+balpar.o: sys_defs.h
+balpar.o: vbuf.h
+balpar.o: vstring.h
+base32_code.o: base32_code.c
+base32_code.o: base32_code.h
+base32_code.o: check_arg.h
+base32_code.o: msg.h
+base32_code.o: mymalloc.h
+base32_code.o: sys_defs.h
+base32_code.o: vbuf.h
+base32_code.o: vstring.h
+base64_code.o: base64_code.c
+base64_code.o: base64_code.h
+base64_code.o: check_arg.h
+base64_code.o: msg.h
+base64_code.o: mymalloc.h
+base64_code.o: sys_defs.h
+base64_code.o: vbuf.h
+base64_code.o: vstring.h
+basename.o: basename.c
+basename.o: check_arg.h
+basename.o: stringops.h
+basename.o: sys_defs.h
+basename.o: vbuf.h
+basename.o: vstring.h
+binhash.o: binhash.c
+binhash.o: binhash.h
+binhash.o: hash_fnv.h
+binhash.o: msg.h
+binhash.o: mymalloc.h
+binhash.o: sys_defs.h
+byte_mask.o: byte_mask.c
+byte_mask.o: byte_mask.h
+byte_mask.o: check_arg.h
+byte_mask.o: msg.h
+byte_mask.o: mymalloc.h
+byte_mask.o: stringops.h
+byte_mask.o: sys_defs.h
+byte_mask.o: vbuf.h
+byte_mask.o: vstring.h
+casefold.o: casefold.c
+casefold.o: check_arg.h
+casefold.o: msg.h
+casefold.o: stringops.h
+casefold.o: sys_defs.h
+casefold.o: vbuf.h
+casefold.o: vstring.h
+chroot_uid.o: chroot_uid.c
+chroot_uid.o: chroot_uid.h
+chroot_uid.o: msg.h
+chroot_uid.o: sys_defs.h
+cidr_match.o: check_arg.h
+cidr_match.o: cidr_match.c
+cidr_match.o: cidr_match.h
+cidr_match.o: mask_addr.h
+cidr_match.o: msg.h
+cidr_match.o: myaddrinfo.h
+cidr_match.o: split_at.h
+cidr_match.o: stringops.h
+cidr_match.o: sys_defs.h
+cidr_match.o: vbuf.h
+cidr_match.o: vstring.h
+clean_env.o: argv.h
+clean_env.o: clean_env.c
+clean_env.o: clean_env.h
+clean_env.o: msg.h
+clean_env.o: safe.h
+clean_env.o: sys_defs.h
+close_on_exec.o: close_on_exec.c
+close_on_exec.o: iostuff.h
+close_on_exec.o: msg.h
+close_on_exec.o: sys_defs.h
+concatenate.o: check_arg.h
+concatenate.o: compat_va_copy.h
+concatenate.o: concatenate.c
+concatenate.o: mymalloc.h
+concatenate.o: stringops.h
+concatenate.o: sys_defs.h
+concatenate.o: vbuf.h
+concatenate.o: vstring.h
+ctable.o: ctable.c
+ctable.o: ctable.h
+ctable.o: htable.h
+ctable.o: msg.h
+ctable.o: mymalloc.h
+ctable.o: ring.h
+ctable.o: sys_defs.h
+dict.o: argv.h
+dict.o: check_arg.h
+dict.o: dict.c
+dict.o: dict.h
+dict.o: dict_ht.h
+dict.o: htable.h
+dict.o: iostuff.h
+dict.o: line_number.h
+dict.o: mac_expand.h
+dict.o: mac_parse.h
+dict.o: msg.h
+dict.o: myflock.h
+dict.o: mymalloc.h
+dict.o: name_mask.h
+dict.o: readlline.h
+dict.o: stringops.h
+dict.o: sys_defs.h
+dict.o: vbuf.h
+dict.o: vstream.h
+dict.o: vstring.h
+dict.o: warn_stat.h
+dict_alloc.o: argv.h
+dict_alloc.o: check_arg.h
+dict_alloc.o: dict.h
+dict_alloc.o: dict_alloc.c
+dict_alloc.o: msg.h
+dict_alloc.o: myflock.h
+dict_alloc.o: mymalloc.h
+dict_alloc.o: sys_defs.h
+dict_alloc.o: vbuf.h
+dict_alloc.o: vstream.h
+dict_alloc.o: vstring.h
+dict_cache.o: argv.h
+dict_cache.o: check_arg.h
+dict_cache.o: dict.h
+dict_cache.o: dict_cache.c
+dict_cache.o: dict_cache.h
+dict_cache.o: events.h
+dict_cache.o: msg.h
+dict_cache.o: myflock.h
+dict_cache.o: mymalloc.h
+dict_cache.o: sys_defs.h
+dict_cache.o: vbuf.h
+dict_cache.o: vstream.h
+dict_cache.o: vstring.h
+dict_cdb.o: argv.h
+dict_cdb.o: check_arg.h
+dict_cdb.o: dict.h
+dict_cdb.o: dict_cdb.c
+dict_cdb.o: dict_cdb.h
+dict_cdb.o: iostuff.h
+dict_cdb.o: msg.h
+dict_cdb.o: myflock.h
+dict_cdb.o: mymalloc.h
+dict_cdb.o: stringops.h
+dict_cdb.o: sys_defs.h
+dict_cdb.o: vbuf.h
+dict_cdb.o: vstream.h
+dict_cdb.o: vstring.h
+dict_cdb.o: warn_stat.h
+dict_cidr.o: argv.h
+dict_cidr.o: check_arg.h
+dict_cidr.o: cidr_match.h
+dict_cidr.o: dict.h
+dict_cidr.o: dict_cidr.c
+dict_cidr.o: dict_cidr.h
+dict_cidr.o: msg.h
+dict_cidr.o: mvect.h
+dict_cidr.o: myaddrinfo.h
+dict_cidr.o: myflock.h
+dict_cidr.o: mymalloc.h
+dict_cidr.o: readlline.h
+dict_cidr.o: stringops.h
+dict_cidr.o: sys_defs.h
+dict_cidr.o: vbuf.h
+dict_cidr.o: vstream.h
+dict_cidr.o: vstring.h
+dict_cidr.o: warn_stat.h
+dict_db.o: argv.h
+dict_db.o: check_arg.h
+dict_db.o: dict.h
+dict_db.o: dict_db.c
+dict_db.o: dict_db.h
+dict_db.o: iostuff.h
+dict_db.o: msg.h
+dict_db.o: myflock.h
+dict_db.o: mymalloc.h
+dict_db.o: stringops.h
+dict_db.o: sys_defs.h
+dict_db.o: vbuf.h
+dict_db.o: vstream.h
+dict_db.o: vstring.h
+dict_db.o: warn_stat.h
+dict_dbm.o: dict_dbm.c
+dict_dbm.o: sys_defs.h
+dict_debug.o: argv.h
+dict_debug.o: check_arg.h
+dict_debug.o: dict.h
+dict_debug.o: dict_debug.c
+dict_debug.o: msg.h
+dict_debug.o: myflock.h
+dict_debug.o: mymalloc.h
+dict_debug.o: sys_defs.h
+dict_debug.o: vbuf.h
+dict_debug.o: vstream.h
+dict_debug.o: vstring.h
+dict_env.o: argv.h
+dict_env.o: check_arg.h
+dict_env.o: dict.h
+dict_env.o: dict_env.c
+dict_env.o: dict_env.h
+dict_env.o: msg.h
+dict_env.o: myflock.h
+dict_env.o: mymalloc.h
+dict_env.o: safe.h
+dict_env.o: stringops.h
+dict_env.o: sys_defs.h
+dict_env.o: vbuf.h
+dict_env.o: vstream.h
+dict_env.o: vstring.h
+dict_fail.o: argv.h
+dict_fail.o: check_arg.h
+dict_fail.o: dict.h
+dict_fail.o: dict_fail.c
+dict_fail.o: dict_fail.h
+dict_fail.o: msg.h
+dict_fail.o: myflock.h
+dict_fail.o: mymalloc.h
+dict_fail.o: sys_defs.h
+dict_fail.o: vbuf.h
+dict_fail.o: vstream.h
+dict_fail.o: vstring.h
+dict_file.o: argv.h
+dict_file.o: base64_code.h
+dict_file.o: check_arg.h
+dict_file.o: dict.h
+dict_file.o: dict_file.c
+dict_file.o: msg.h
+dict_file.o: myflock.h
+dict_file.o: mymalloc.h
+dict_file.o: sys_defs.h
+dict_file.o: vbuf.h
+dict_file.o: vstream.h
+dict_file.o: vstring.h
+dict_ht.o: argv.h
+dict_ht.o: check_arg.h
+dict_ht.o: dict.h
+dict_ht.o: dict_ht.c
+dict_ht.o: dict_ht.h
+dict_ht.o: htable.h
+dict_ht.o: myflock.h
+dict_ht.o: mymalloc.h
+dict_ht.o: stringops.h
+dict_ht.o: sys_defs.h
+dict_ht.o: vbuf.h
+dict_ht.o: vstream.h
+dict_ht.o: vstring.h
+dict_inline.o: argv.h
+dict_inline.o: check_arg.h
+dict_inline.o: dict.h
+dict_inline.o: dict_ht.h
+dict_inline.o: dict_inline.c
+dict_inline.o: dict_inline.h
+dict_inline.o: htable.h
+dict_inline.o: msg.h
+dict_inline.o: myflock.h
+dict_inline.o: mymalloc.h
+dict_inline.o: stringops.h
+dict_inline.o: sys_defs.h
+dict_inline.o: vbuf.h
+dict_inline.o: vstream.h
+dict_inline.o: vstring.h
+dict_lmdb.o: argv.h
+dict_lmdb.o: check_arg.h
+dict_lmdb.o: dict.h
+dict_lmdb.o: dict_lmdb.c
+dict_lmdb.o: dict_lmdb.h
+dict_lmdb.o: htable.h
+dict_lmdb.o: iostuff.h
+dict_lmdb.o: msg.h
+dict_lmdb.o: myflock.h
+dict_lmdb.o: mymalloc.h
+dict_lmdb.o: slmdb.h
+dict_lmdb.o: stringops.h
+dict_lmdb.o: sys_defs.h
+dict_lmdb.o: vbuf.h
+dict_lmdb.o: vstream.h
+dict_lmdb.o: vstring.h
+dict_lmdb.o: warn_stat.h
+dict_ni.o: dict_ni.c
+dict_ni.o: sys_defs.h
+dict_nis.o: argv.h
+dict_nis.o: check_arg.h
+dict_nis.o: dict.h
+dict_nis.o: dict_nis.c
+dict_nis.o: dict_nis.h
+dict_nis.o: msg.h
+dict_nis.o: myflock.h
+dict_nis.o: mymalloc.h
+dict_nis.o: stringops.h
+dict_nis.o: sys_defs.h
+dict_nis.o: vbuf.h
+dict_nis.o: vstream.h
+dict_nis.o: vstring.h
+dict_nisplus.o: argv.h
+dict_nisplus.o: check_arg.h
+dict_nisplus.o: dict.h
+dict_nisplus.o: dict_nisplus.c
+dict_nisplus.o: dict_nisplus.h
+dict_nisplus.o: msg.h
+dict_nisplus.o: myflock.h
+dict_nisplus.o: mymalloc.h
+dict_nisplus.o: stringops.h
+dict_nisplus.o: sys_defs.h
+dict_nisplus.o: vbuf.h
+dict_nisplus.o: vstream.h
+dict_nisplus.o: vstring.h
+dict_open.o: argv.h
+dict_open.o: check_arg.h
+dict_open.o: dict.h
+dict_open.o: dict_cdb.h
+dict_open.o: dict_cidr.h
+dict_open.o: dict_db.h
+dict_open.o: dict_dbm.h
+dict_open.o: dict_env.h
+dict_open.o: dict_fail.h
+dict_open.o: dict_ht.h
+dict_open.o: dict_inline.h
+dict_open.o: dict_lmdb.h
+dict_open.o: dict_ni.h
+dict_open.o: dict_nis.h
+dict_open.o: dict_nisplus.h
+dict_open.o: dict_open.c
+dict_open.o: dict_pcre.h
+dict_open.o: dict_pipe.h
+dict_open.o: dict_random.h
+dict_open.o: dict_regexp.h
+dict_open.o: dict_sdbm.h
+dict_open.o: dict_sockmap.h
+dict_open.o: dict_static.h
+dict_open.o: dict_tcp.h
+dict_open.o: dict_thash.h
+dict_open.o: dict_union.h
+dict_open.o: dict_unix.h
+dict_open.o: htable.h
+dict_open.o: msg.h
+dict_open.o: myflock.h
+dict_open.o: mymalloc.h
+dict_open.o: split_at.h
+dict_open.o: stringops.h
+dict_open.o: sys_defs.h
+dict_open.o: vbuf.h
+dict_open.o: vstream.h
+dict_open.o: vstring.h
+dict_pcre.o: argv.h
+dict_pcre.o: check_arg.h
+dict_pcre.o: dict.h
+dict_pcre.o: dict_pcre.c
+dict_pcre.o: dict_pcre.h
+dict_pcre.o: mac_parse.h
+dict_pcre.o: msg.h
+dict_pcre.o: mvect.h
+dict_pcre.o: myflock.h
+dict_pcre.o: mymalloc.h
+dict_pcre.o: readlline.h
+dict_pcre.o: safe.h
+dict_pcre.o: stringops.h
+dict_pcre.o: sys_defs.h
+dict_pcre.o: vbuf.h
+dict_pcre.o: vstream.h
+dict_pcre.o: vstring.h
+dict_pcre.o: warn_stat.h
+dict_pipe.o: argv.h
+dict_pipe.o: check_arg.h
+dict_pipe.o: dict.h
+dict_pipe.o: dict_pipe.c
+dict_pipe.o: dict_pipe.h
+dict_pipe.o: htable.h
+dict_pipe.o: msg.h
+dict_pipe.o: myflock.h
+dict_pipe.o: mymalloc.h
+dict_pipe.o: stringops.h
+dict_pipe.o: sys_defs.h
+dict_pipe.o: vbuf.h
+dict_pipe.o: vstream.h
+dict_pipe.o: vstring.h
+dict_random.o: argv.h
+dict_random.o: check_arg.h
+dict_random.o: dict.h
+dict_random.o: dict_random.c
+dict_random.o: dict_random.h
+dict_random.o: msg.h
+dict_random.o: myflock.h
+dict_random.o: mymalloc.h
+dict_random.o: myrand.h
+dict_random.o: stringops.h
+dict_random.o: sys_defs.h
+dict_random.o: vbuf.h
+dict_random.o: vstream.h
+dict_random.o: vstring.h
+dict_regexp.o: argv.h
+dict_regexp.o: check_arg.h
+dict_regexp.o: dict.h
+dict_regexp.o: dict_regexp.c
+dict_regexp.o: dict_regexp.h
+dict_regexp.o: mac_parse.h
+dict_regexp.o: msg.h
+dict_regexp.o: mvect.h
+dict_regexp.o: myflock.h
+dict_regexp.o: mymalloc.h
+dict_regexp.o: readlline.h
+dict_regexp.o: safe.h
+dict_regexp.o: stringops.h
+dict_regexp.o: sys_defs.h
+dict_regexp.o: vbuf.h
+dict_regexp.o: vstream.h
+dict_regexp.o: vstring.h
+dict_regexp.o: warn_stat.h
+dict_sdbm.o: argv.h
+dict_sdbm.o: check_arg.h
+dict_sdbm.o: dict.h
+dict_sdbm.o: dict_sdbm.c
+dict_sdbm.o: dict_sdbm.h
+dict_sdbm.o: htable.h
+dict_sdbm.o: iostuff.h
+dict_sdbm.o: msg.h
+dict_sdbm.o: myflock.h
+dict_sdbm.o: mymalloc.h
+dict_sdbm.o: stringops.h
+dict_sdbm.o: sys_defs.h
+dict_sdbm.o: vbuf.h
+dict_sdbm.o: vstream.h
+dict_sdbm.o: vstring.h
+dict_sdbm.o: warn_stat.h
+dict_sockmap.o: argv.h
+dict_sockmap.o: auto_clnt.h
+dict_sockmap.o: check_arg.h
+dict_sockmap.o: dict.h
+dict_sockmap.o: dict_sockmap.c
+dict_sockmap.o: dict_sockmap.h
+dict_sockmap.o: htable.h
+dict_sockmap.o: msg.h
+dict_sockmap.o: myflock.h
+dict_sockmap.o: mymalloc.h
+dict_sockmap.o: netstring.h
+dict_sockmap.o: split_at.h
+dict_sockmap.o: stringops.h
+dict_sockmap.o: sys_defs.h
+dict_sockmap.o: vbuf.h
+dict_sockmap.o: vstream.h
+dict_sockmap.o: vstring.h
+dict_static.o: argv.h
+dict_static.o: check_arg.h
+dict_static.o: dict.h
+dict_static.o: dict_static.c
+dict_static.o: dict_static.h
+dict_static.o: msg.h
+dict_static.o: myflock.h
+dict_static.o: mymalloc.h
+dict_static.o: stringops.h
+dict_static.o: sys_defs.h
+dict_static.o: vbuf.h
+dict_static.o: vstream.h
+dict_static.o: vstring.h
+dict_stream.o: argv.h
+dict_stream.o: check_arg.h
+dict_stream.o: dict.h
+dict_stream.o: dict_stream.c
+dict_stream.o: msg.h
+dict_stream.o: myflock.h
+dict_stream.o: mymalloc.h
+dict_stream.o: stringops.h
+dict_stream.o: sys_defs.h
+dict_stream.o: vbuf.h
+dict_stream.o: vstream.h
+dict_stream.o: vstring.h
+dict_surrogate.o: argv.h
+dict_surrogate.o: check_arg.h
+dict_surrogate.o: compat_va_copy.h
+dict_surrogate.o: dict.h
+dict_surrogate.o: dict_surrogate.c
+dict_surrogate.o: msg.h
+dict_surrogate.o: myflock.h
+dict_surrogate.o: mymalloc.h
+dict_surrogate.o: sys_defs.h
+dict_surrogate.o: vbuf.h
+dict_surrogate.o: vstream.h
+dict_surrogate.o: vstring.h
+dict_tcp.o: argv.h
+dict_tcp.o: check_arg.h
+dict_tcp.o: connect.h
+dict_tcp.o: dict.h
+dict_tcp.o: dict_tcp.c
+dict_tcp.o: dict_tcp.h
+dict_tcp.o: hex_quote.h
+dict_tcp.o: iostuff.h
+dict_tcp.o: msg.h
+dict_tcp.o: myflock.h
+dict_tcp.o: mymalloc.h
+dict_tcp.o: stringops.h
+dict_tcp.o: sys_defs.h
+dict_tcp.o: vbuf.h
+dict_tcp.o: vstream.h
+dict_tcp.o: vstring.h
+dict_tcp.o: vstring_vstream.h
+dict_test.o: argv.h
+dict_test.o: check_arg.h
+dict_test.o: dict.h
+dict_test.o: dict_db.h
+dict_test.o: dict_lmdb.h
+dict_test.o: dict_test.c
+dict_test.o: msg.h
+dict_test.o: msg_vstream.h
+dict_test.o: myflock.h
+dict_test.o: stringops.h
+dict_test.o: sys_defs.h
+dict_test.o: vbuf.h
+dict_test.o: vstream.h
+dict_test.o: vstring.h
+dict_test.o: vstring_vstream.h
+dict_thash.o: argv.h
+dict_thash.o: check_arg.h
+dict_thash.o: dict.h
+dict_thash.o: dict_ht.h
+dict_thash.o: dict_thash.c
+dict_thash.o: dict_thash.h
+dict_thash.o: htable.h
+dict_thash.o: iostuff.h
+dict_thash.o: msg.h
+dict_thash.o: myflock.h
+dict_thash.o: mymalloc.h
+dict_thash.o: readlline.h
+dict_thash.o: stringops.h
+dict_thash.o: sys_defs.h
+dict_thash.o: vbuf.h
+dict_thash.o: vstream.h
+dict_thash.o: vstring.h
+dict_union.o: argv.h
+dict_union.o: check_arg.h
+dict_union.o: dict.h
+dict_union.o: dict_union.c
+dict_union.o: dict_union.h
+dict_union.o: htable.h
+dict_union.o: msg.h
+dict_union.o: myflock.h
+dict_union.o: mymalloc.h
+dict_union.o: stringops.h
+dict_union.o: sys_defs.h
+dict_union.o: vbuf.h
+dict_union.o: vstream.h
+dict_union.o: vstring.h
+dict_unix.o: argv.h
+dict_unix.o: check_arg.h
+dict_unix.o: dict.h
+dict_unix.o: dict_unix.c
+dict_unix.o: dict_unix.h
+dict_unix.o: msg.h
+dict_unix.o: myflock.h
+dict_unix.o: mymalloc.h
+dict_unix.o: stringops.h
+dict_unix.o: sys_defs.h
+dict_unix.o: vbuf.h
+dict_unix.o: vstream.h
+dict_unix.o: vstring.h
+dict_utf8.o: argv.h
+dict_utf8.o: check_arg.h
+dict_utf8.o: dict.h
+dict_utf8.o: dict_utf8.c
+dict_utf8.o: msg.h
+dict_utf8.o: myflock.h
+dict_utf8.o: mymalloc.h
+dict_utf8.o: stringops.h
+dict_utf8.o: sys_defs.h
+dict_utf8.o: vbuf.h
+dict_utf8.o: vstream.h
+dict_utf8.o: vstring.h
+dir_forest.o: check_arg.h
+dir_forest.o: dir_forest.c
+dir_forest.o: dir_forest.h
+dir_forest.o: msg.h
+dir_forest.o: sys_defs.h
+dir_forest.o: vbuf.h
+dir_forest.o: vstring.h
+doze.o: doze.c
+doze.o: iostuff.h
+doze.o: msg.h
+doze.o: sys_defs.h
+dummy_read.o: dummy_read.c
+dummy_read.o: iostuff.h
+dummy_read.o: msg.h
+dummy_read.o: sys_defs.h
+dummy_write.o: dummy_write.c
+dummy_write.o: iostuff.h
+dummy_write.o: msg.h
+dummy_write.o: sys_defs.h
+dup2_pass_on_exec.o: dup2_pass_on_exec.c
+duplex_pipe.o: duplex_pipe.c
+duplex_pipe.o: iostuff.h
+duplex_pipe.o: sane_socketpair.h
+duplex_pipe.o: sys_defs.h
+edit_file.o: check_arg.h
+edit_file.o: edit_file.c
+edit_file.o: edit_file.h
+edit_file.o: msg.h
+edit_file.o: myflock.h
+edit_file.o: mymalloc.h
+edit_file.o: stringops.h
+edit_file.o: sys_defs.h
+edit_file.o: vbuf.h
+edit_file.o: vstream.h
+edit_file.o: vstring.h
+edit_file.o: warn_stat.h
+environ.o: environ.c
+environ.o: sys_defs.h
+events.o: events.c
+events.o: events.h
+events.o: iostuff.h
+events.o: msg.h
+events.o: mymalloc.h
+events.o: ring.h
+events.o: sys_defs.h
+exec_command.o: argv.h
+exec_command.o: exec_command.c
+exec_command.o: exec_command.h
+exec_command.o: msg.h
+exec_command.o: sys_defs.h
+extpar.o: check_arg.h
+extpar.o: extpar.c
+extpar.o: stringops.h
+extpar.o: sys_defs.h
+extpar.o: vbuf.h
+extpar.o: vstring.h
+fifo_listen.o: fifo_listen.c
+fifo_listen.o: htable.h
+fifo_listen.o: iostuff.h
+fifo_listen.o: listen.h
+fifo_listen.o: msg.h
+fifo_listen.o: sys_defs.h
+fifo_listen.o: warn_stat.h
+fifo_open.o: fifo_open.c
+fifo_rdonly_bug.o: fifo_rdonly_bug.c
+fifo_rdonly_bug.o: sys_defs.h
+fifo_rdwr_bug.o: fifo_rdwr_bug.c
+fifo_rdwr_bug.o: sys_defs.h
+fifo_trigger.o: check_arg.h
+fifo_trigger.o: fifo_trigger.c
+fifo_trigger.o: iostuff.h
+fifo_trigger.o: msg.h
+fifo_trigger.o: safe_open.h
+fifo_trigger.o: sys_defs.h
+fifo_trigger.o: trigger.h
+fifo_trigger.o: vbuf.h
+fifo_trigger.o: vstream.h
+fifo_trigger.o: vstring.h
+file_limit.o: file_limit.c
+file_limit.o: iostuff.h
+file_limit.o: msg.h
+file_limit.o: sys_defs.h
+find_inet.o: check_arg.h
+find_inet.o: find_inet.c
+find_inet.o: find_inet.h
+find_inet.o: known_tcp_ports.h
+find_inet.o: msg.h
+find_inet.o: stringops.h
+find_inet.o: sys_defs.h
+find_inet.o: vbuf.h
+find_inet.o: vstring.h
+format_tv.o: check_arg.h
+format_tv.o: format_tv.c
+format_tv.o: format_tv.h
+format_tv.o: msg.h
+format_tv.o: sys_defs.h
+format_tv.o: vbuf.h
+format_tv.o: vstring.h
+fsspace.o: fsspace.c
+fsspace.o: fsspace.h
+fsspace.o: msg.h
+fsspace.o: sys_defs.h
+fullname.o: check_arg.h
+fullname.o: fullname.c
+fullname.o: fullname.h
+fullname.o: safe.h
+fullname.o: sys_defs.h
+fullname.o: vbuf.h
+fullname.o: vstring.h
+gccw.o: gccw.c
+get_domainname.o: get_domainname.c
+get_domainname.o: get_domainname.h
+get_domainname.o: get_hostname.h
+get_domainname.o: mymalloc.h
+get_domainname.o: sys_defs.h
+get_hostname.o: get_hostname.c
+get_hostname.o: get_hostname.h
+get_hostname.o: msg.h
+get_hostname.o: mymalloc.h
+get_hostname.o: sys_defs.h
+get_hostname.o: valid_hostname.h
+hash_fnv.o: hash_fnv.c
+hash_fnv.o: hash_fnv.h
+hash_fnv.o: ldseed.h
+hash_fnv.o: msg.h
+hash_fnv.o: sys_defs.h
+hex_code.o: check_arg.h
+hex_code.o: hex_code.c
+hex_code.o: hex_code.h
+hex_code.o: msg.h
+hex_code.o: mymalloc.h
+hex_code.o: sys_defs.h
+hex_code.o: vbuf.h
+hex_code.o: vstring.h
+hex_quote.o: check_arg.h
+hex_quote.o: hex_quote.c
+hex_quote.o: hex_quote.h
+hex_quote.o: msg.h
+hex_quote.o: sys_defs.h
+hex_quote.o: vbuf.h
+hex_quote.o: vstring.h
+host_port.o: check_arg.h
+host_port.o: host_port.c
+host_port.o: host_port.h
+host_port.o: msg.h
+host_port.o: split_at.h
+host_port.o: stringops.h
+host_port.o: sys_defs.h
+host_port.o: valid_hostname.h
+host_port.o: valid_utf8_hostname.h
+host_port.o: vbuf.h
+host_port.o: vstring.h
+htable.o: hash_fnv.h
+htable.o: htable.c
+htable.o: htable.h
+htable.o: msg.h
+htable.o: mymalloc.h
+htable.o: sys_defs.h
+inet_addr_host.o: inet_addr_host.c
+inet_addr_host.o: inet_addr_host.h
+inet_addr_host.o: inet_addr_list.h
+inet_addr_host.o: inet_proto.h
+inet_addr_host.o: msg.h
+inet_addr_host.o: myaddrinfo.h
+inet_addr_host.o: mymalloc.h
+inet_addr_host.o: sock_addr.h
+inet_addr_host.o: sys_defs.h
+inet_addr_list.o: inet_addr_list.c
+inet_addr_list.o: inet_addr_list.h
+inet_addr_list.o: msg.h
+inet_addr_list.o: myaddrinfo.h
+inet_addr_list.o: mymalloc.h
+inet_addr_list.o: sock_addr.h
+inet_addr_list.o: sys_defs.h
+inet_addr_local.o: check_arg.h
+inet_addr_local.o: hex_code.h
+inet_addr_local.o: inet_addr_list.h
+inet_addr_local.o: inet_addr_local.c
+inet_addr_local.o: inet_addr_local.h
+inet_addr_local.o: mask_addr.h
+inet_addr_local.o: msg.h
+inet_addr_local.o: myaddrinfo.h
+inet_addr_local.o: mymalloc.h
+inet_addr_local.o: sock_addr.h
+inet_addr_local.o: sys_defs.h
+inet_addr_local.o: vbuf.h
+inet_addr_local.o: vstring.h
+inet_connect.o: connect.h
+inet_connect.o: host_port.h
+inet_connect.o: inet_connect.c
+inet_connect.o: inet_proto.h
+inet_connect.o: iostuff.h
+inet_connect.o: msg.h
+inet_connect.o: myaddrinfo.h
+inet_connect.o: mymalloc.h
+inet_connect.o: sane_connect.h
+inet_connect.o: sock_addr.h
+inet_connect.o: sys_defs.h
+inet_connect.o: timed_connect.h
+inet_listen.o: host_port.h
+inet_listen.o: htable.h
+inet_listen.o: inet_listen.c
+inet_listen.o: inet_proto.h
+inet_listen.o: iostuff.h
+inet_listen.o: listen.h
+inet_listen.o: msg.h
+inet_listen.o: myaddrinfo.h
+inet_listen.o: mymalloc.h
+inet_listen.o: sane_accept.h
+inet_listen.o: sock_addr.h
+inet_listen.o: sys_defs.h
+inet_proto.o: check_arg.h
+inet_proto.o: inet_proto.c
+inet_proto.o: inet_proto.h
+inet_proto.o: msg.h
+inet_proto.o: myaddrinfo.h
+inet_proto.o: mymalloc.h
+inet_proto.o: name_mask.h
+inet_proto.o: sys_defs.h
+inet_proto.o: vbuf.h
+inet_proto.o: vstring.h
+inet_trigger.o: connect.h
+inet_trigger.o: events.h
+inet_trigger.o: inet_trigger.c
+inet_trigger.o: iostuff.h
+inet_trigger.o: msg.h
+inet_trigger.o: mymalloc.h
+inet_trigger.o: sys_defs.h
+inet_trigger.o: trigger.h
+inet_windowsize.o: inet_windowsize.c
+inet_windowsize.o: iostuff.h
+inet_windowsize.o: msg.h
+inet_windowsize.o: sys_defs.h
+ip_match.o: check_arg.h
+ip_match.o: ip_match.c
+ip_match.o: ip_match.h
+ip_match.o: msg.h
+ip_match.o: mymalloc.h
+ip_match.o: sys_defs.h
+ip_match.o: vbuf.h
+ip_match.o: vstring.h
+killme_after.o: killme_after.c
+killme_after.o: killme_after.h
+killme_after.o: sys_defs.h
+known_tcp_ports.o: check_arg.h
+known_tcp_ports.o: htable.h
+known_tcp_ports.o: known_tcp_ports.c
+known_tcp_ports.o: known_tcp_ports.h
+known_tcp_ports.o: mymalloc.h
+known_tcp_ports.o: stringops.h
+known_tcp_ports.o: sys_defs.h
+known_tcp_ports.o: vbuf.h
+known_tcp_ports.o: vstring.h
+ldseed.o: iostuff.h
+ldseed.o: ldseed.c
+ldseed.o: ldseed.h
+ldseed.o: msg.h
+ldseed.o: sys_defs.h
+line_number.o: check_arg.h
+line_number.o: line_number.c
+line_number.o: line_number.h
+line_number.o: sys_defs.h
+line_number.o: vbuf.h
+line_number.o: vstring.h
+line_wrap.o: line_wrap.c
+line_wrap.o: line_wrap.h
+line_wrap.o: sys_defs.h
+load_file.o: check_arg.h
+load_file.o: iostuff.h
+load_file.o: load_file.c
+load_file.o: load_file.h
+load_file.o: msg.h
+load_file.o: sys_defs.h
+load_file.o: vbuf.h
+load_file.o: vstream.h
+load_file.o: warn_stat.h
+load_lib.o: load_lib.c
+load_lib.o: load_lib.h
+load_lib.o: msg.h
+load_lib.o: sys_defs.h
+logwriter.o: check_arg.h
+logwriter.o: iostuff.h
+logwriter.o: logwriter.c
+logwriter.o: logwriter.h
+logwriter.o: msg.h
+logwriter.o: mymalloc.h
+logwriter.o: safe_open.h
+logwriter.o: sys_defs.h
+logwriter.o: vbuf.h
+logwriter.o: vstream.h
+logwriter.o: vstring.h
+lowercase.o: check_arg.h
+lowercase.o: lowercase.c
+lowercase.o: stringops.h
+lowercase.o: sys_defs.h
+lowercase.o: vbuf.h
+lowercase.o: vstring.h
+lstat_as.o: lstat_as.c
+lstat_as.o: lstat_as.h
+lstat_as.o: msg.h
+lstat_as.o: set_eugid.h
+lstat_as.o: sys_defs.h
+lstat_as.o: warn_stat.h
+mac_expand.o: check_arg.h
+mac_expand.o: htable.h
+mac_expand.o: mac_expand.c
+mac_expand.o: mac_expand.h
+mac_expand.o: mac_parse.h
+mac_expand.o: msg.h
+mac_expand.o: mymalloc.h
+mac_expand.o: name_code.h
+mac_expand.o: sane_strtol.h
+mac_expand.o: stringops.h
+mac_expand.o: sys_defs.h
+mac_expand.o: vbuf.h
+mac_expand.o: vstring.h
+mac_parse.o: check_arg.h
+mac_parse.o: mac_parse.c
+mac_parse.o: mac_parse.h
+mac_parse.o: msg.h
+mac_parse.o: sys_defs.h
+mac_parse.o: vbuf.h
+mac_parse.o: vstring.h
+make_dirs.o: check_arg.h
+make_dirs.o: make_dirs.c
+make_dirs.o: make_dirs.h
+make_dirs.o: msg.h
+make_dirs.o: mymalloc.h
+make_dirs.o: stringops.h
+make_dirs.o: sys_defs.h
+make_dirs.o: vbuf.h
+make_dirs.o: vstring.h
+make_dirs.o: warn_stat.h
+mask_addr.o: mask_addr.c
+mask_addr.o: mask_addr.h
+mask_addr.o: msg.h
+mask_addr.o: sys_defs.h
+match_list.o: argv.h
+match_list.o: check_arg.h
+match_list.o: dict.h
+match_list.o: match_list.c
+match_list.o: match_list.h
+match_list.o: msg.h
+match_list.o: myflock.h
+match_list.o: mymalloc.h
+match_list.o: stringops.h
+match_list.o: sys_defs.h
+match_list.o: vbuf.h
+match_list.o: vstream.h
+match_list.o: vstring.h
+match_list.o: vstring_vstream.h
+match_ops.o: argv.h
+match_ops.o: check_arg.h
+match_ops.o: cidr_match.h
+match_ops.o: dict.h
+match_ops.o: match_list.h
+match_ops.o: match_ops.c
+match_ops.o: msg.h
+match_ops.o: myaddrinfo.h
+match_ops.o: myflock.h
+match_ops.o: mymalloc.h
+match_ops.o: split_at.h
+match_ops.o: stringops.h
+match_ops.o: sys_defs.h
+match_ops.o: vbuf.h
+match_ops.o: vstream.h
+match_ops.o: vstring.h
+midna_domain.o: check_arg.h
+midna_domain.o: ctable.h
+midna_domain.o: midna_domain.c
+midna_domain.o: midna_domain.h
+midna_domain.o: msg.h
+midna_domain.o: mymalloc.h
+midna_domain.o: name_mask.h
+midna_domain.o: stringops.h
+midna_domain.o: sys_defs.h
+midna_domain.o: valid_hostname.h
+midna_domain.o: vbuf.h
+midna_domain.o: vstring.h
+msg.o: msg.c
+msg.o: msg.h
+msg.o: msg_output.h
+msg.o: sys_defs.h
+msg_logger.o: check_arg.h
+msg_logger.o: connect.h
+msg_logger.o: iostuff.h
+msg_logger.o: logwriter.h
+msg_logger.o: msg.h
+msg_logger.o: msg_logger.c
+msg_logger.o: msg_logger.h
+msg_logger.o: msg_output.h
+msg_logger.o: mymalloc.h
+msg_logger.o: safe.h
+msg_logger.o: sys_defs.h
+msg_logger.o: vbuf.h
+msg_logger.o: vstream.h
+msg_logger.o: vstring.h
+msg_output.o: check_arg.h
+msg_output.o: msg_output.c
+msg_output.o: msg_output.h
+msg_output.o: msg_vstream.h
+msg_output.o: mymalloc.h
+msg_output.o: stringops.h
+msg_output.o: sys_defs.h
+msg_output.o: vbuf.h
+msg_output.o: vstream.h
+msg_output.o: vstring.h
+msg_rate_delay.o: check_arg.h
+msg_rate_delay.o: events.h
+msg_rate_delay.o: msg.h
+msg_rate_delay.o: msg_rate_delay.c
+msg_rate_delay.o: sys_defs.h
+msg_rate_delay.o: vbuf.h
+msg_rate_delay.o: vstring.h
+msg_syslog.o: check_arg.h
+msg_syslog.o: msg.h
+msg_syslog.o: msg_output.h
+msg_syslog.o: msg_syslog.c
+msg_syslog.o: msg_syslog.h
+msg_syslog.o: mymalloc.h
+msg_syslog.o: safe.h
+msg_syslog.o: stringops.h
+msg_syslog.o: sys_defs.h
+msg_syslog.o: vbuf.h
+msg_syslog.o: vstring.h
+msg_vstream.o: check_arg.h
+msg_vstream.o: msg.h
+msg_vstream.o: msg_output.h
+msg_vstream.o: msg_vstream.c
+msg_vstream.o: msg_vstream.h
+msg_vstream.o: sys_defs.h
+msg_vstream.o: vbuf.h
+msg_vstream.o: vstream.h
+mvect.o: mvect.c
+mvect.o: mvect.h
+mvect.o: mymalloc.h
+mvect.o: sys_defs.h
+myaddrinfo.o: check_arg.h
+myaddrinfo.o: inet_proto.h
+myaddrinfo.o: known_tcp_ports.h
+myaddrinfo.o: msg.h
+myaddrinfo.o: myaddrinfo.c
+myaddrinfo.o: myaddrinfo.h
+myaddrinfo.o: mymalloc.h
+myaddrinfo.o: sock_addr.h
+myaddrinfo.o: split_at.h
+myaddrinfo.o: stringops.h
+myaddrinfo.o: sys_defs.h
+myaddrinfo.o: valid_hostname.h
+myaddrinfo.o: vbuf.h
+myaddrinfo.o: vstring.h
+myflock.o: msg.h
+myflock.o: myflock.c
+myflock.o: myflock.h
+myflock.o: sys_defs.h
+mymalloc.o: msg.h
+mymalloc.o: mymalloc.c
+mymalloc.o: mymalloc.h
+mymalloc.o: sys_defs.h
+myrand.o: myrand.c
+myrand.o: myrand.h
+myrand.o: sys_defs.h
+mystrtok.o: check_arg.h
+mystrtok.o: mystrtok.c
+mystrtok.o: stringops.h
+mystrtok.o: sys_defs.h
+mystrtok.o: vbuf.h
+mystrtok.o: vstring.h
+name_code.o: name_code.c
+name_code.o: name_code.h
+name_code.o: sys_defs.h
+name_mask.o: check_arg.h
+name_mask.o: msg.h
+name_mask.o: mymalloc.h
+name_mask.o: name_mask.c
+name_mask.o: name_mask.h
+name_mask.o: stringops.h
+name_mask.o: sys_defs.h
+name_mask.o: vbuf.h
+name_mask.o: vstring.h
+nbbio.o: events.h
+nbbio.o: msg.h
+nbbio.o: mymalloc.h
+nbbio.o: nbbio.c
+nbbio.o: nbbio.h
+nbbio.o: sys_defs.h
+netstring.o: check_arg.h
+netstring.o: compat_va_copy.h
+netstring.o: msg.h
+netstring.o: netstring.c
+netstring.o: netstring.h
+netstring.o: sys_defs.h
+netstring.o: vbuf.h
+netstring.o: vstream.h
+netstring.o: vstring.h
+neuter.o: check_arg.h
+neuter.o: neuter.c
+neuter.o: stringops.h
+neuter.o: sys_defs.h
+neuter.o: vbuf.h
+neuter.o: vstring.h
+non_blocking.o: iostuff.h
+non_blocking.o: msg.h
+non_blocking.o: non_blocking.c
+non_blocking.o: sys_defs.h
+nvtable.o: htable.h
+nvtable.o: mymalloc.h
+nvtable.o: nvtable.c
+nvtable.o: nvtable.h
+nvtable.o: sys_defs.h
+open_as.o: msg.h
+open_as.o: open_as.c
+open_as.o: open_as.h
+open_as.o: set_eugid.h
+open_as.o: sys_defs.h
+open_limit.o: iostuff.h
+open_limit.o: open_limit.c
+open_limit.o: sys_defs.h
+open_lock.o: check_arg.h
+open_lock.o: msg.h
+open_lock.o: myflock.h
+open_lock.o: open_lock.c
+open_lock.o: open_lock.h
+open_lock.o: safe_open.h
+open_lock.o: sys_defs.h
+open_lock.o: vbuf.h
+open_lock.o: vstream.h
+open_lock.o: vstring.h
+pass_accept.o: attr.h
+pass_accept.o: check_arg.h
+pass_accept.o: htable.h
+pass_accept.o: iostuff.h
+pass_accept.o: listen.h
+pass_accept.o: msg.h
+pass_accept.o: mymalloc.h
+pass_accept.o: nvtable.h
+pass_accept.o: pass_accept.c
+pass_accept.o: sys_defs.h
+pass_accept.o: vbuf.h
+pass_accept.o: vstream.h
+pass_accept.o: vstring.h
+pass_trigger.o: connect.h
+pass_trigger.o: events.h
+pass_trigger.o: iostuff.h
+pass_trigger.o: msg.h
+pass_trigger.o: mymalloc.h
+pass_trigger.o: pass_trigger.c
+pass_trigger.o: sys_defs.h
+pass_trigger.o: trigger.h
+peekfd.o: iostuff.h
+peekfd.o: peekfd.c
+peekfd.o: sys_defs.h
+poll_fd.o: iostuff.h
+poll_fd.o: msg.h
+poll_fd.o: poll_fd.c
+poll_fd.o: sys_defs.h
+posix_signals.o: posix_signals.c
+posix_signals.o: posix_signals.h
+posix_signals.o: sys_defs.h
+printable.o: check_arg.h
+printable.o: printable.c
+printable.o: stringops.h
+printable.o: sys_defs.h
+printable.o: vbuf.h
+printable.o: vstring.h
+rand_sleep.o: iostuff.h
+rand_sleep.o: msg.h
+rand_sleep.o: myrand.h
+rand_sleep.o: rand_sleep.c
+rand_sleep.o: sys_defs.h
+readlline.o: check_arg.h
+readlline.o: msg.h
+readlline.o: readlline.c
+readlline.o: readlline.h
+readlline.o: sys_defs.h
+readlline.o: vbuf.h
+readlline.o: vstream.h
+readlline.o: vstring.h
+recv_pass_attr.o: attr.h
+recv_pass_attr.o: check_arg.h
+recv_pass_attr.o: htable.h
+recv_pass_attr.o: iostuff.h
+recv_pass_attr.o: listen.h
+recv_pass_attr.o: mymalloc.h
+recv_pass_attr.o: nvtable.h
+recv_pass_attr.o: recv_pass_attr.c
+recv_pass_attr.o: sys_defs.h
+recv_pass_attr.o: vbuf.h
+recv_pass_attr.o: vstream.h
+recv_pass_attr.o: vstring.h
+ring.o: ring.c
+ring.o: ring.h
+safe_getenv.o: safe.h
+safe_getenv.o: safe_getenv.c
+safe_getenv.o: sys_defs.h
+safe_open.o: check_arg.h
+safe_open.o: msg.h
+safe_open.o: safe_open.c
+safe_open.o: safe_open.h
+safe_open.o: stringops.h
+safe_open.o: sys_defs.h
+safe_open.o: vbuf.h
+safe_open.o: vstream.h
+safe_open.o: vstring.h
+safe_open.o: warn_stat.h
+sane_accept.o: msg.h
+sane_accept.o: sane_accept.c
+sane_accept.o: sane_accept.h
+sane_accept.o: sys_defs.h
+sane_basename.o: check_arg.h
+sane_basename.o: sane_basename.c
+sane_basename.o: stringops.h
+sane_basename.o: sys_defs.h
+sane_basename.o: vbuf.h
+sane_basename.o: vstring.h
+sane_connect.o: msg.h
+sane_connect.o: sane_connect.c
+sane_connect.o: sane_connect.h
+sane_connect.o: sys_defs.h
+sane_link.o: msg.h
+sane_link.o: sane_fsops.h
+sane_link.o: sane_link.c
+sane_link.o: sys_defs.h
+sane_link.o: warn_stat.h
+sane_rename.o: msg.h
+sane_rename.o: sane_fsops.h
+sane_rename.o: sane_rename.c
+sane_rename.o: sys_defs.h
+sane_rename.o: warn_stat.h
+sane_socketpair.o: msg.h
+sane_socketpair.o: sane_socketpair.c
+sane_socketpair.o: sane_socketpair.h
+sane_socketpair.o: sys_defs.h
+sane_strtol.o: sane_strtol.c
+sane_strtol.o: sane_strtol.h
+sane_strtol.o: sys_defs.h
+sane_time.o: msg.h
+sane_time.o: sane_time.c
+sane_time.o: sane_time.h
+sane_time.o: sys_defs.h
+scan_dir.o: check_arg.h
+scan_dir.o: msg.h
+scan_dir.o: mymalloc.h
+scan_dir.o: scan_dir.c
+scan_dir.o: scan_dir.h
+scan_dir.o: stringops.h
+scan_dir.o: sys_defs.h
+scan_dir.o: vbuf.h
+scan_dir.o: vstring.h
+select_bug.o: check_arg.h
+select_bug.o: msg.h
+select_bug.o: msg_vstream.h
+select_bug.o: select_bug.c
+select_bug.o: sys_defs.h
+select_bug.o: vbuf.h
+select_bug.o: vstream.h
+set_eugid.o: msg.h
+set_eugid.o: set_eugid.c
+set_eugid.o: set_eugid.h
+set_eugid.o: sys_defs.h
+set_ugid.o: msg.h
+set_ugid.o: set_ugid.c
+set_ugid.o: set_ugid.h
+set_ugid.o: sys_defs.h
+sigdelay.o: msg.h
+sigdelay.o: posix_signals.h
+sigdelay.o: sigdelay.c
+sigdelay.o: sigdelay.h
+sigdelay.o: sys_defs.h
+skipblanks.o: check_arg.h
+skipblanks.o: skipblanks.c
+skipblanks.o: stringops.h
+skipblanks.o: sys_defs.h
+skipblanks.o: vbuf.h
+skipblanks.o: vstring.h
+slmdb.o: check_arg.h
+slmdb.o: slmdb.c
+slmdb.o: slmdb.h
+sock_addr.o: msg.h
+sock_addr.o: sock_addr.c
+sock_addr.o: sock_addr.h
+sock_addr.o: sys_defs.h
+spawn_command.o: argv.h
+spawn_command.o: check_arg.h
+spawn_command.o: clean_env.h
+spawn_command.o: exec_command.h
+spawn_command.o: msg.h
+spawn_command.o: set_ugid.h
+spawn_command.o: spawn_command.c
+spawn_command.o: spawn_command.h
+spawn_command.o: sys_defs.h
+spawn_command.o: timed_wait.h
+split_at.o: split_at.c
+split_at.o: split_at.h
+split_at.o: sys_defs.h
+split_nameval.o: check_arg.h
+split_nameval.o: msg.h
+split_nameval.o: split_nameval.c
+split_nameval.o: stringops.h
+split_nameval.o: sys_defs.h
+split_nameval.o: vbuf.h
+split_nameval.o: vstring.h
+split_qnameval.o: check_arg.h
+split_qnameval.o: msg.h
+split_qnameval.o: split_qnameval.c
+split_qnameval.o: stringops.h
+split_qnameval.o: sys_defs.h
+split_qnameval.o: vbuf.h
+split_qnameval.o: vstring.h
+stat_as.o: msg.h
+stat_as.o: set_eugid.h
+stat_as.o: stat_as.c
+stat_as.o: stat_as.h
+stat_as.o: sys_defs.h
+stat_as.o: warn_stat.h
+strcasecmp.o: strcasecmp.c
+strcasecmp.o: sys_defs.h
+strcasecmp_utf8.o: check_arg.h
+strcasecmp_utf8.o: strcasecmp_utf8.c
+strcasecmp_utf8.o: stringops.h
+strcasecmp_utf8.o: sys_defs.h
+strcasecmp_utf8.o: vbuf.h
+strcasecmp_utf8.o: vstring.h
+stream_connect.o: connect.h
+stream_connect.o: iostuff.h
+stream_connect.o: msg.h
+stream_connect.o: stream_connect.c
+stream_connect.o: sys_defs.h
+stream_listen.o: htable.h
+stream_listen.o: iostuff.h
+stream_listen.o: listen.h
+stream_listen.o: msg.h
+stream_listen.o: stream_listen.c
+stream_listen.o: sys_defs.h
+stream_recv_fd.o: iostuff.h
+stream_recv_fd.o: msg.h
+stream_recv_fd.o: stream_recv_fd.c
+stream_recv_fd.o: sys_defs.h
+stream_send_fd.o: iostuff.h
+stream_send_fd.o: msg.h
+stream_send_fd.o: stream_send_fd.c
+stream_send_fd.o: sys_defs.h
+stream_test.o: check_arg.h
+stream_test.o: connect.h
+stream_test.o: htable.h
+stream_test.o: iostuff.h
+stream_test.o: listen.h
+stream_test.o: msg.h
+stream_test.o: msg_vstream.h
+stream_test.o: stream_test.c
+stream_test.o: sys_defs.h
+stream_test.o: vbuf.h
+stream_test.o: vstream.h
+stream_trigger.o: connect.h
+stream_trigger.o: events.h
+stream_trigger.o: iostuff.h
+stream_trigger.o: msg.h
+stream_trigger.o: mymalloc.h
+stream_trigger.o: stream_trigger.c
+stream_trigger.o: sys_defs.h
+stream_trigger.o: trigger.h
+sys_compat.o: sys_compat.c
+sys_compat.o: sys_defs.h
+timecmp.o: timecmp.c
+timecmp.o: timecmp.h
+timed_connect.o: iostuff.h
+timed_connect.o: msg.h
+timed_connect.o: sane_connect.h
+timed_connect.o: sys_defs.h
+timed_connect.o: timed_connect.c
+timed_connect.o: timed_connect.h
+timed_read.o: iostuff.h
+timed_read.o: msg.h
+timed_read.o: sys_defs.h
+timed_read.o: timed_read.c
+timed_wait.o: msg.h
+timed_wait.o: posix_signals.h
+timed_wait.o: sys_defs.h
+timed_wait.o: timed_wait.c
+timed_wait.o: timed_wait.h
+timed_write.o: iostuff.h
+timed_write.o: msg.h
+timed_write.o: sys_defs.h
+timed_write.o: timed_write.c
+translit.o: check_arg.h
+translit.o: stringops.h
+translit.o: sys_defs.h
+translit.o: translit.c
+translit.o: vbuf.h
+translit.o: vstring.h
+trimblanks.o: check_arg.h
+trimblanks.o: stringops.h
+trimblanks.o: sys_defs.h
+trimblanks.o: trimblanks.c
+trimblanks.o: vbuf.h
+trimblanks.o: vstring.h
+unescape.o: check_arg.h
+unescape.o: stringops.h
+unescape.o: sys_defs.h
+unescape.o: unescape.c
+unescape.o: vbuf.h
+unescape.o: vstring.h
+unix_connect.o: connect.h
+unix_connect.o: iostuff.h
+unix_connect.o: msg.h
+unix_connect.o: sane_connect.h
+unix_connect.o: sys_defs.h
+unix_connect.o: timed_connect.h
+unix_connect.o: unix_connect.c
+unix_dgram_connect.o: connect.h
+unix_dgram_connect.o: iostuff.h
+unix_dgram_connect.o: msg.h
+unix_dgram_connect.o: sys_defs.h
+unix_dgram_connect.o: unix_dgram_connect.c
+unix_dgram_listen.o: htable.h
+unix_dgram_listen.o: iostuff.h
+unix_dgram_listen.o: listen.h
+unix_dgram_listen.o: msg.h
+unix_dgram_listen.o: sys_defs.h
+unix_dgram_listen.o: unix_dgram_listen.c
+unix_listen.o: htable.h
+unix_listen.o: iostuff.h
+unix_listen.o: listen.h
+unix_listen.o: msg.h
+unix_listen.o: sane_accept.h
+unix_listen.o: sys_defs.h
+unix_listen.o: unix_listen.c
+unix_pass_fd_fix.o: check_arg.h
+unix_pass_fd_fix.o: iostuff.h
+unix_pass_fd_fix.o: name_mask.h
+unix_pass_fd_fix.o: sys_defs.h
+unix_pass_fd_fix.o: unix_pass_fd_fix.c
+unix_pass_fd_fix.o: vbuf.h
+unix_pass_fd_fix.o: vstring.h
+unix_recv_fd.o: iostuff.h
+unix_recv_fd.o: msg.h
+unix_recv_fd.o: sys_defs.h
+unix_recv_fd.o: unix_recv_fd.c
+unix_send_fd.o: iostuff.h
+unix_send_fd.o: msg.h
+unix_send_fd.o: sys_defs.h
+unix_send_fd.o: unix_send_fd.c
+unix_trigger.o: connect.h
+unix_trigger.o: events.h
+unix_trigger.o: iostuff.h
+unix_trigger.o: msg.h
+unix_trigger.o: mymalloc.h
+unix_trigger.o: sys_defs.h
+unix_trigger.o: trigger.h
+unix_trigger.o: unix_trigger.c
+unsafe.o: safe.h
+unsafe.o: sys_defs.h
+unsafe.o: unsafe.c
+uppercase.o: check_arg.h
+uppercase.o: stringops.h
+uppercase.o: sys_defs.h
+uppercase.o: uppercase.c
+uppercase.o: vbuf.h
+uppercase.o: vstring.h
+username.o: sys_defs.h
+username.o: username.c
+username.o: username.h
+valid_hostname.o: check_arg.h
+valid_hostname.o: msg.h
+valid_hostname.o: mymalloc.h
+valid_hostname.o: stringops.h
+valid_hostname.o: sys_defs.h
+valid_hostname.o: valid_hostname.c
+valid_hostname.o: valid_hostname.h
+valid_hostname.o: vbuf.h
+valid_hostname.o: vstring.h
+valid_utf8_hostname.o: check_arg.h
+valid_utf8_hostname.o: midna_domain.h
+valid_utf8_hostname.o: msg.h
+valid_utf8_hostname.o: mymalloc.h
+valid_utf8_hostname.o: stringops.h
+valid_utf8_hostname.o: sys_defs.h
+valid_utf8_hostname.o: valid_hostname.h
+valid_utf8_hostname.o: valid_utf8_hostname.c
+valid_utf8_hostname.o: valid_utf8_hostname.h
+valid_utf8_hostname.o: vbuf.h
+valid_utf8_hostname.o: vstring.h
+valid_utf8_string.o: check_arg.h
+valid_utf8_string.o: stringops.h
+valid_utf8_string.o: sys_defs.h
+valid_utf8_string.o: valid_utf8_string.c
+valid_utf8_string.o: vbuf.h
+valid_utf8_string.o: vstring.h
+vbuf.o: sys_defs.h
+vbuf.o: vbuf.c
+vbuf.o: vbuf.h
+vbuf_print.o: check_arg.h
+vbuf_print.o: msg.h
+vbuf_print.o: mymalloc.h
+vbuf_print.o: sys_defs.h
+vbuf_print.o: vbuf.h
+vbuf_print.o: vbuf_print.c
+vbuf_print.o: vbuf_print.h
+vbuf_print.o: vstring.h
+vstream.o: check_arg.h
+vstream.o: iostuff.h
+vstream.o: msg.h
+vstream.o: mymalloc.h
+vstream.o: sys_defs.h
+vstream.o: vbuf.h
+vstream.o: vbuf_print.h
+vstream.o: vstream.c
+vstream.o: vstream.h
+vstream.o: vstring.h
+vstream_popen.o: argv.h
+vstream_popen.o: check_arg.h
+vstream_popen.o: clean_env.h
+vstream_popen.o: exec_command.h
+vstream_popen.o: iostuff.h
+vstream_popen.o: msg.h
+vstream_popen.o: set_ugid.h
+vstream_popen.o: sys_defs.h
+vstream_popen.o: vbuf.h
+vstream_popen.o: vstream.h
+vstream_popen.o: vstream_popen.c
+vstream_tweak.o: check_arg.h
+vstream_tweak.o: msg.h
+vstream_tweak.o: sys_defs.h
+vstream_tweak.o: vbuf.h
+vstream_tweak.o: vstream.h
+vstream_tweak.o: vstream_tweak.c
+vstring.o: check_arg.h
+vstring.o: msg.h
+vstring.o: mymalloc.h
+vstring.o: sys_defs.h
+vstring.o: vbuf.h
+vstring.o: vbuf_print.h
+vstring.o: vstring.c
+vstring.o: vstring.h
+vstring_vstream.o: check_arg.h
+vstring_vstream.o: msg.h
+vstring_vstream.o: sys_defs.h
+vstring_vstream.o: vbuf.h
+vstring_vstream.o: vstream.h
+vstring_vstream.o: vstring.h
+vstring_vstream.o: vstring_vstream.c
+vstring_vstream.o: vstring_vstream.h
+warn_stat.o: msg.h
+warn_stat.o: sys_defs.h
+warn_stat.o: warn_stat.c
+warn_stat.o: warn_stat.h
+watchdog.o: events.h
+watchdog.o: iostuff.h
+watchdog.o: killme_after.h
+watchdog.o: msg.h
+watchdog.o: mymalloc.h
+watchdog.o: posix_signals.h
+watchdog.o: sys_defs.h
+watchdog.o: watchdog.c
+watchdog.o: watchdog.h
+write_buf.o: iostuff.h
+write_buf.o: msg.h
+write_buf.o: sys_defs.h
+write_buf.o: write_buf.c
diff --git a/src/util/allascii.c b/src/util/allascii.c
new file mode 100644
index 0000000..4ac820e
--- /dev/null
+++ b/src/util/allascii.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* allascii 3
+/* SUMMARY
+/* predicate if string is all ASCII
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allascii(buffer)
+/* const char *buffer;
+/*
+/* int allascii_len(buffer, len)
+/* const char *buffer;
+/* ssize_t len;
+/* DESCRIPTION
+/* allascii() determines if its argument is an all-ASCII string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP len
+/* The string length, -1 to determine the length dynamically.
+/* 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>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allascii_len - return true if string is all ASCII */
+
+int allascii_len(const char *string, ssize_t len)
+{
+ const char *cp;
+ int ch;
+
+ if (len < 0)
+ len = strlen(string);
+ if (len == 0)
+ return (0);
+ for (cp = string; cp < string + len
+ && (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/alldig.c b/src/util/alldig.c
new file mode 100644
index 0000000..cabe5c3
--- /dev/null
+++ b/src/util/alldig.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* alldig 3
+/* SUMMARY
+/* predicate if string is all numerical
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int alldig(string)
+/* const char *string;
+/*
+/* int allalnum(string)
+/* const char *string;
+/* DESCRIPTION
+/* alldig() determines if its argument is an all-numerical string.
+/*
+/* allalnum() determines if its argument is an all-alphanumerical
+/* string.
+/* SEE ALSO
+/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger:
+/* "Software Tools", Addison-Wesley 1976.
+/* 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>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* alldig - return true if string is all digits */
+
+int alldig(const char *string)
+{
+ const char *cp;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; *cp != 0; cp++)
+ if (!ISDIGIT(*cp))
+ return (0);
+ return (1);
+}
+
+/* allalnum - return true if string is all alphanum */
+
+int allalnum(const char *string)
+{
+ const char *cp;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; *cp != 0; cp++)
+ if (!ISALNUM(*cp))
+ return (0);
+ return (1);
+}
diff --git a/src/util/allprint.c b/src/util/allprint.c
new file mode 100644
index 0000000..6e9c519
--- /dev/null
+++ b/src/util/allprint.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* allprint 3
+/* SUMMARY
+/* predicate if string is all printable
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allprint(buffer)
+/* const char *buffer;
+/* DESCRIPTION
+/* allprint() determines if its argument is an all-printable string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allprint - return true if string is all printable */
+
+int allprint(const char *string)
+{
+ const char *cp;
+ int ch;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch) || !ISPRINT(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/allspace.c b/src/util/allspace.c
new file mode 100644
index 0000000..9a05347
--- /dev/null
+++ b/src/util/allspace.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* allspace 3
+/* SUMMARY
+/* predicate if string is all space
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allspace(buffer)
+/* const char *buffer;
+/* DESCRIPTION
+/* allspace() determines if its argument is an all-space string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allspace - return true if string is all space */
+
+int allspace(const char *string)
+{
+ const char *cp;
+ int ch;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch) || !ISSPACE(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/argv.c b/src/util/argv.c
new file mode 100644
index 0000000..a364e24
--- /dev/null
+++ b/src/util/argv.c
@@ -0,0 +1,326 @@
+/*++
+/* NAME
+/* argv 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_alloc(len)
+/* ssize_t len;
+/*
+/* ARGV *argv_sort(argvp)
+/* ARGV *argvp;
+/*
+/* ARGV *argv_free(argvp)
+/* ARGV *argvp;
+/*
+/* void argv_add(argvp, arg, ..., ARGV_END)
+/* ARGV *argvp;
+/* char *arg;
+/*
+/* void argv_addn(argvp, arg, arg_len, ..., ARGV_END)
+/* ARGV *argvp;
+/* char *arg;
+/* ssize_t arg_len;
+/*
+/* void argv_terminate(argvp);
+/* ARGV *argvp;
+/*
+/* void argv_truncate(argvp, len);
+/* ARGV *argvp;
+/* ssize_t len;
+/*
+/* void argv_insert_one(argvp, pos, arg)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* const char *arg;
+/*
+/* void argv_replace_one(argvp, pos, arg)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* const char *arg;
+/*
+/* void argv_delete(argvp, pos, how_many)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* ssize_t how_many;
+/*
+/* void ARGV_FAKE_BEGIN(argv, arg)
+/* const char *arg;
+/*
+/* void ARGV_FAKE_END
+/* DESCRIPTION
+/* The functions in this module manipulate arrays of string
+/* pointers. An ARGV structure contains the following members:
+/* .IP len
+/* The length of the \fIargv\fR array member.
+/* .IP argc
+/* The number of \fIargv\fR elements used.
+/* .IP argv
+/* An array of pointers to null-terminated strings.
+/* .PP
+/* argv_alloc() returns an empty string array of the requested
+/* length. The result is ready for use by argv_add(). The array
+/* is null terminated.
+/*
+/* argv_sort() sorts the elements of argvp in place returning
+/* the original array.
+/*
+/* argv_add() copies zero or more strings and adds them to the
+/* specified string array. The array is null terminated.
+/* Terminate the argument list with a null pointer. The manifest
+/* constant ARGV_END provides a convenient notation for this.
+/*
+/* argv_addn() is like argv_add(), but each string is followed
+/* by a string length argument.
+/*
+/* argv_free() releases storage for a string array, and conveniently
+/* returns a null pointer.
+/*
+/* argv_terminate() null-terminates its string array argument.
+/*
+/* argv_truncate() truncates its argument to the specified
+/* number of entries, but does not reallocate memory. The
+/* result is null-terminated.
+/*
+/* argv_insert_one() inserts one string at the specified array
+/* position.
+/*
+/* argv_replace_one() replaces one string at the specified
+/* position. The old string is destroyed after the update is
+/* made.
+/*
+/* argv_delete() deletes the specified number of elements
+/* starting at the specified array position. The result is
+/* null-terminated.
+/*
+/* ARGV_FAKE_BEGIN/END are an optimization for the case where
+/* a single string needs to be passed into an ARGV-based
+/* interface. ARGV_FAKE_BEGIN() opens a statement block and
+/* allocates a stack-based ARGV structure named after the first
+/* argument, that encapsulates the second argument. This
+/* implementation allocates no heap memory and creates no copy
+/* of the second argument. ARGV_FAKE_END closes the statement
+/* block and thereby releases storage.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "argv.h"
+
+/* argv_free - destroy string array */
+
+ARGV *argv_free(ARGV *argvp)
+{
+ char **cpp;
+
+ for (cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++)
+ myfree(*cpp);
+ myfree((void *) argvp->argv);
+ myfree((void *) argvp);
+ return (0);
+}
+
+/* argv_alloc - initialize string array */
+
+ARGV *argv_alloc(ssize_t len)
+{
+ ARGV *argvp;
+ ssize_t sane_len;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+ argvp = (ARGV *) mymalloc(sizeof(*argvp));
+ argvp->len = 0;
+ sane_len = (len < 2 ? 2 : len);
+ argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *));
+ argvp->len = sane_len;
+ argvp->argc = 0;
+ argvp->argv[0] = 0;
+ return (argvp);
+}
+
+static int argv_cmp(const void *e1, const void *e2)
+{
+ const char *s1 = *(const char **) e1;
+ const char *s2 = *(const char **) e2;
+
+ return strcmp(s1, s2);
+}
+
+/* argv_sort - sort array in place */
+
+ARGV *argv_sort(ARGV *argvp)
+{
+ qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp);
+ return (argvp);
+}
+
+/* argv_extend - extend array */
+
+static void argv_extend(ARGV *argvp)
+{
+ ssize_t new_len;
+
+ new_len = argvp->len * 2;
+ argvp->argv = (char **)
+ myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *));
+ argvp->len = new_len;
+}
+
+/* argv_add - add string to vector */
+
+void argv_add(ARGV *argvp,...)
+{
+ char *arg;
+ va_list ap;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+#define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1)
+
+ va_start(ap, argvp);
+ while ((arg = va_arg(ap, char *)) != 0) {
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ argvp->argv[argvp->argc++] = mystrdup(arg);
+ }
+ va_end(ap);
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_addn - add string to vector */
+
+void argv_addn(ARGV *argvp,...)
+{
+ char *arg;
+ ssize_t len;
+ va_list ap;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+ va_start(ap, argvp);
+ while ((arg = va_arg(ap, char *)) != 0) {
+ if ((len = va_arg(ap, ssize_t)) < 0)
+ msg_panic("argv_addn: bad string length %ld", (long) len);
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ argvp->argv[argvp->argc++] = mystrndup(arg, len);
+ }
+ va_end(ap);
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_terminate - terminate string array */
+
+void argv_terminate(ARGV *argvp)
+{
+
+ /*
+ * Trust that argvp->argc < argvp->len.
+ */
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_truncate - truncate string array */
+
+void argv_truncate(ARGV *argvp, ssize_t len)
+{
+ char **cpp;
+
+ /*
+ * Sanity check.
+ */
+ if (len < 0)
+ msg_panic("argv_truncate: bad length %ld", (long) len);
+
+ if (len < argvp->argc) {
+ for (cpp = argvp->argv + len; cpp < argvp->argv + argvp->argc; cpp++)
+ myfree(*cpp);
+ argvp->argc = len;
+ argvp->argv[argvp->argc] = 0;
+ }
+}
+
+/* argv_insert_one - insert one string into array */
+
+void argv_insert_one(ARGV *argvp, ssize_t where, const char *arg)
+{
+ ssize_t pos;
+
+ /*
+ * Sanity check.
+ */
+ if (where < 0 || where > argvp->argc)
+ msg_panic("argv_insert_one bad position: %ld", (long) where);
+
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ for (pos = argvp->argc; pos >= where; pos--)
+ argvp->argv[pos + 1] = argvp->argv[pos];
+ argvp->argv[where] = mystrdup(arg);
+ argvp->argc += 1;
+}
+
+/* argv_replace_one - replace one string in array */
+
+void argv_replace_one(ARGV *argvp, ssize_t where, const char *arg)
+{
+ char *temp;
+
+ /*
+ * Sanity check.
+ */
+ if (where < 0 || where >= argvp->argc)
+ msg_panic("argv_replace_one bad position: %ld", (long) where);
+
+ temp = argvp->argv[where];
+ argvp->argv[where] = mystrdup(arg);
+ myfree(temp);
+}
+
+/* argv_delete - remove string(s) from array */
+
+void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many)
+{
+ ssize_t pos;
+
+ /*
+ * Sanity check.
+ */
+ if (first < 0 || how_many < 0 || first + how_many > argvp->argc)
+ msg_panic("argv_delete bad range: (start=%ld count=%ld)",
+ (long) first, (long) how_many);
+
+ for (pos = first; pos < first + how_many; pos++)
+ myfree(argvp->argv[pos]);
+ for (pos = first; pos <= argvp->argc - how_many; pos++)
+ argvp->argv[pos] = argvp->argv[pos + how_many];
+ argvp->argc -= how_many;
+}
diff --git a/src/util/argv.h b/src/util/argv.h
new file mode 100644
index 0000000..1e3b467
--- /dev/null
+++ b/src/util/argv.h
@@ -0,0 +1,69 @@
+#ifndef _ARGV_H_INCLUDED_
+#define _ARGV_H_INCLUDED_
+
+/*++
+/* NAME
+/* argv 3h
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include "argv.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct ARGV {
+ ssize_t len; /* number of array elements */
+ ssize_t argc; /* array elements in use */
+ char **argv; /* string array */
+} ARGV;
+
+extern ARGV *argv_alloc(ssize_t);
+extern ARGV *argv_sort(ARGV *);
+extern void argv_add(ARGV *,...);
+extern void argv_addn(ARGV *,...);
+extern void argv_terminate(ARGV *);
+extern void argv_truncate(ARGV *, ssize_t);
+extern void argv_insert_one(ARGV *, ssize_t, const char *);
+extern void argv_replace_one(ARGV *, ssize_t, const char *);
+extern void argv_delete(ARGV *, ssize_t, ssize_t);
+extern ARGV *argv_free(ARGV *);
+
+extern ARGV *argv_split(const char *, const char *);
+extern ARGV *argv_split_count(const char *, const char *, ssize_t);
+extern ARGV *argv_split_append(ARGV *, const char *, const char *);
+
+extern ARGV *argv_splitq(const char *, const char *, const char *);
+extern ARGV *argv_splitq_count(const char *, const char *, const char *, ssize_t);
+extern ARGV *argv_splitq_append(ARGV *, const char *, const char *, const char *);
+
+extern ARGV *argv_split_at(const char *, int);
+extern ARGV *argv_split_at_count(const char *, int, ssize_t);
+extern ARGV *argv_split_at_append(ARGV *, const char *, int);
+
+#define ARGV_FAKE_BEGIN(fake_argv, arg) { \
+ ARGV fake_argv; \
+ char *__fake_argv_args__[2]; \
+ __fake_argv_args__[0] = (char *) (arg); \
+ __fake_argv_args__[1] = 0; \
+ fake_argv.argv = __fake_argv_args__; \
+ fake_argv.argc = fake_argv.len = 1;
+
+#define ARGV_FAKE_END }
+
+#define ARGV_END ((char *) 0)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/argv_attr.h b/src/util/argv_attr.h
new file mode 100644
index 0000000..53c587f
--- /dev/null
+++ b/src/util/argv_attr.h
@@ -0,0 +1,43 @@
+#ifndef _ARGV_ATTR_H_INCLUDED_
+#define _ARGV_ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* argv_attr 3h
+/* SUMMARY
+/* argv serialization/deserialization
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <attr.h>
+#include <check_arg.h>
+#include <vstream.h>
+
+ /*
+ * External API.
+ */
+#define ARGV_ATTR_SIZE "argv_size"
+#define ARGV_ATTR_VALUE "argv_value"
+#define ARGV_ATTR_MAX 1024
+
+extern int argv_attr_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int argv_attr_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/argv_attr_print.c b/src/util/argv_attr_print.c
new file mode 100644
index 0000000..78e3315
--- /dev/null
+++ b/src/util/argv_attr_print.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* argv_attr_print
+/* SUMMARY
+/* write ARGV to stream
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/*
+/* int argv_attr_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* argv_attr_print() writes an ARGV to the named stream using
+/* the specified attribute print routine. argv_attr_print() is meant
+/* to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(argv_attr_print, (const void *) argv), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* The result value is zero in case of success, non-zero
+/* otherwise.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <argv_attr.h>
+#include <attr.h>
+#include <vstream.h>
+#include <msg.h>
+
+/* argv_attr_print - write ARGV to stream */
+
+int argv_attr_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ ARGV *argv = (ARGV *) ptr;
+ int n;
+ int ret;
+ int argc = argv ? argv->argc : 0;
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(ARGV_ATTR_SIZE, argc),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("argv_attr_print count=%d", argc);
+ for (n = 0; ret == 0 && n < argc; n++)
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(ARGV_ATTR_VALUE, argv->argv[n]),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("argv_attr_print ret=%d", ret);
+ /* Do not flush the stream. */
+ return (ret);
+}
diff --git a/src/util/argv_attr_scan.c b/src/util/argv_attr_scan.c
new file mode 100644
index 0000000..0987bf8
--- /dev/null
+++ b/src/util/argv_attr_scan.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* argv_attr_scan
+/* SUMMARY
+/* read ARGV from stream
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/*
+/* int argv_attr_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* argv_attr_scan() creates an ARGV and reads its contents
+/* from the named stream using the specified attribute scan
+/* routine. argv_attr_scan() is meant to be passed as a call-back
+/* to attr_scan(), thusly:
+/*
+/* ARGV *argv = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(argv_attr_scan, (void *) &argv), ...
+/* ...
+/* if (argv)
+/* argv_free(argv);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* In case of error, this function returns non-zero and creates
+/* an ARGV null pointer.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* argv_attr_scan - write ARGV to stream */
+
+int argv_attr_scan(ATTR_PRINT_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ ARGV *argv = 0;
+ int size;
+ int ret;
+
+ if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(ARGV_ATTR_SIZE, &size),
+ ATTR_TYPE_END)) == 1) {
+ if (msg_verbose)
+ msg_info("argv_attr_scan count=%d", size);
+ if (size < 0 || size > ARGV_ATTR_MAX) {
+ msg_warn("invalid size %d from %s while reading ARGV",
+ size, VSTREAM_PATH(fp));
+ ret = -1;
+ } else if (size > 0) {
+ VSTRING *buffer = vstring_alloc(100);
+
+ argv = argv_alloc(size);
+ while (ret == 1 && size-- > 0) {
+ if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(ARGV_ATTR_VALUE, buffer),
+ ATTR_TYPE_END)) == 1)
+ argv_add(argv, vstring_str(buffer), ARGV_END);
+ }
+ argv_terminate(argv);
+ vstring_free(buffer);
+ }
+ }
+ *(ARGV **) ptr = argv;
+ if (msg_verbose)
+ msg_info("argv_attr_scan ret=%d", ret);
+ return (ret);
+}
diff --git a/src/util/argv_split.c b/src/util/argv_split.c
new file mode 100644
index 0000000..a9f3afb
--- /dev/null
+++ b/src/util/argv_split.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* argv_split 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_split(string, delim)
+/* const char *string;
+/* const char *delim;
+/*
+/* ARGV *argv_split_count(string, delim, count)
+/* const char *string;
+/* const char *delim;
+/* ssize_t count;
+/*
+/* ARGV *argv_split_append(argv, string, delim)
+/* ARGV *argv;
+/* const char *string;
+/* const char *delim;
+/* DESCRIPTION
+/* argv_split() breaks up \fIstring\fR into tokens according
+/* to the delimiters specified in \fIdelim\fR. The result is
+/* a null-terminated string array.
+/*
+/* argv_split_count() is like argv_split() but stops splitting
+/* input after at most \fIcount\fR -1 times and leaves the
+/* remainder, if any, in the last array element. It is an error
+/* to specify a count < 1.
+/*
+/* argv_split_append() performs the same operation as argv_split(),
+/* but appends the result to an existing string array.
+/* SEE ALSO
+/* mystrtok(), safe string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "argv.h"
+#include "msg.h"
+
+/* argv_split - split string into token array */
+
+ARGV *argv_split(const char *string, const char *delim)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_count - split string into token array */
+
+ARGV *argv_split_count(const char *string, const char *delim, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_split_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ if (*bp)
+ bp += strspn(bp, delim);
+ if (*bp)
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_append - split string into token array, append to array */
+
+ARGV *argv_split_append(ARGV *argvp, const char *string, const char *delim)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/argv_split_at.c b/src/util/argv_split_at.c
new file mode 100644
index 0000000..fcdb4ff
--- /dev/null
+++ b/src/util/argv_split_at.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* argv_split_at 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_split_at(string, sep)
+/* const char *string;
+/* int sep;
+/*
+/* ARGV *argv_split_at_count(string, sep, count)
+/* const char *string;
+/* int sep;
+/* ssize_t count;
+/*
+/* ARGV *argv_split_at_append(argv, string, sep)
+/* ARGV *argv;
+/* const char *string;
+/* int sep;
+/* DESCRIPTION
+/* argv_split_at() splits \fIstring\fR into fields using a
+/* single separator specified in \fIsep\fR. The result is a
+/* null-terminated string array.
+/*
+/* argv_split_at_count() is like argv_split_at() but stops
+/* splitting input after at most \fIcount\fR -1 times and
+/* leaves the remainder, if any, in the last array element.
+/* It is an error to specify a count < 1.
+/*
+/* argv_split_at_append() performs the same operation as
+/* argv_split_at(), but appends the result to an existing
+/* string array.
+/* SEE ALSO
+/* split_at(), trivial string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include <mymalloc.h>
+#include <stringops.h>
+#include <argv.h>
+#include <msg.h>
+#include <split_at.h>
+
+/* argv_split_at - split string into field array */
+
+ARGV *argv_split_at(const char *string, int sep)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_at_count - split string into field array */
+
+ARGV *argv_split_at_count(const char *string, int sep, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_split_at_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_at_append - split string into field array, append to array */
+
+ARGV *argv_split_at_append(ARGV *argvp, const char *string, int sep)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/argv_splitq.c b/src/util/argv_splitq.c
new file mode 100644
index 0000000..3900ee1
--- /dev/null
+++ b/src/util/argv_splitq.c
@@ -0,0 +1,118 @@
+/*++
+/* NAME
+/* argv_splitq 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_splitq(string, delim, parens)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/*
+/* ARGV *argv_splitq_count(string, delim, parens, count)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* ssize_t count;
+/*
+/* ARGV *argv_splitq_append(argv, string, delim, parens)
+/* ARGV *argv;
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* DESCRIPTION
+/* argv_splitq() breaks up \fIstring\fR into tokens according
+/* to the delimiters specified in \fIdelim\fR, while avoiding
+/* splitting text between matching parentheses. The result is
+/* a null-terminated string array.
+/*
+/* argv_splitq_count() is like argv_splitq() but stops splitting
+/* input after at most \fIcount\fR -1 times and leaves the
+/* remainder, if any, in the last array element. It is an error
+/* to specify a count < 1.
+/*
+/* argv_splitq_append() performs the same operation as argv_splitq(),
+/* but appends the result to an existing string array.
+/* SEE ALSO
+/* mystrtokq(), safe string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "argv.h"
+#include "msg.h"
+
+/* argv_splitq - split string into token array */
+
+ARGV *argv_splitq(const char *string, const char *delim, const char *parens)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_count - split string into token array */
+
+ARGV *argv_splitq_count(const char *string, const char *delim,
+ const char *parens, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_splitq_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ if (*bp)
+ bp += strspn(bp, delim);
+ if (*bp)
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_append - split string into token array, append to array */
+
+ARGV *argv_splitq_append(ARGV *argvp, const char *string, const char *delim,
+ const char *parens)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/attr.h b/src/util/attr.h
new file mode 100644
index 0000000..067405f
--- /dev/null
+++ b/src/util/attr.h
@@ -0,0 +1,184 @@
+#ifndef _ATTR_H_INCLUDED_
+#define _ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr 3h
+/* SUMMARY
+/* attribute list manipulations
+/* SYNOPSIS
+/* #include "attr.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <nvtable.h>
+#include <check_arg.h>
+
+ /*
+ * Delegation for better data abstraction.
+ */
+typedef int (*ATTR_SCAN_COMMON_FN) (VSTREAM *, int,...);
+typedef int (*ATTR_SCAN_CUSTOM_FN) (ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+typedef int (*ATTR_PRINT_COMMON_FN) (VSTREAM *, int,...);
+typedef int (*ATTR_PRINT_CUSTOM_FN) (ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+
+ /*
+ * Attribute types. See attr_scan(3) for documentation.
+ */
+#define ATTR_TYPE_END 0 /* end of data */
+#define ATTR_TYPE_INT 1 /* Unsigned integer */
+#define ATTR_TYPE_NUM ATTR_TYPE_INT
+#define ATTR_TYPE_STR 2 /* Character string */
+#define ATTR_TYPE_HASH 3 /* Hash table */
+#define ATTR_TYPE_NV 3 /* Name-value table */
+#define ATTR_TYPE_LONG 4 /* Unsigned long */
+#define ATTR_TYPE_DATA 5 /* Binary data */
+#define ATTR_TYPE_FUNC 6 /* Function pointer */
+#define ATTR_TYPE_STREQ 7 /* Requires (name, value) match */
+
+ /*
+ * Optional sender-specified grouping for hash or nameval tables.
+ */
+#define ATTR_TYPE_OPEN '{'
+#define ATTR_TYPE_CLOSE '}'
+#define ATTR_NAME_OPEN "{"
+#define ATTR_NAME_CLOSE "}"
+
+#define ATTR_HASH_LIMIT 1024 /* Size of hash table */
+
+ /*
+ * Typechecking support for variadic function arguments. See check_arg(3h)
+ * for documentation.
+ */
+#define SEND_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, int, (val))
+#define SEND_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val))
+#define SEND_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_CPTR(ATTR, HTABLE, (val))
+#define SEND_ATTR_NV(val) ATTR_TYPE_NV, CHECK_CPTR(ATTR, NVTABLE, (val))
+#define SEND_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, long, (val))
+#define SEND_ATTR_DATA(name, len, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, ssize_t, (len)), CHECK_CPTR(ATTR, void, (val))
+#define SEND_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_PRINT_CUSTOM_FN, (func)), CHECK_CPTR(ATTR, void, (val))
+
+#define RECV_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, int, (val))
+#define RECV_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val))
+#define RECV_ATTR_STREQ(name, val) ATTR_TYPE_STREQ, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val))
+#define RECV_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_PTR(ATTR, HTABLE, (val))
+#define RECV_ATTR_NV(val) ATTR_TYPE_NV, CHECK_PTR(ATTR, NVTABLE, (val))
+#define RECV_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, long, (val))
+#define RECV_ATTR_DATA(name, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val))
+#define RECV_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_SCAN_CUSTOM_FN, (func)), CHECK_PTR(ATTR, void, (val))
+
+CHECK_VAL_HELPER_DCL(ATTR, ssize_t);
+CHECK_VAL_HELPER_DCL(ATTR, long);
+CHECK_VAL_HELPER_DCL(ATTR, int);
+CHECK_PTR_HELPER_DCL(ATTR, void);
+CHECK_PTR_HELPER_DCL(ATTR, long);
+CHECK_PTR_HELPER_DCL(ATTR, int);
+CHECK_PTR_HELPER_DCL(ATTR, VSTRING);
+CHECK_PTR_HELPER_DCL(ATTR, NVTABLE);
+CHECK_PTR_HELPER_DCL(ATTR, HTABLE);
+CHECK_CPTR_HELPER_DCL(ATTR, void);
+CHECK_CPTR_HELPER_DCL(ATTR, char);
+CHECK_CPTR_HELPER_DCL(ATTR, NVTABLE);
+CHECK_CPTR_HELPER_DCL(ATTR, HTABLE);
+CHECK_VAL_HELPER_DCL(ATTR, ATTR_PRINT_CUSTOM_FN);
+CHECK_VAL_HELPER_DCL(ATTR, ATTR_SCAN_CUSTOM_FN);
+
+ /*
+ * Flags that control processing. See attr_scan(3) for documentation.
+ */
+#define ATTR_FLAG_NONE 0
+#define ATTR_FLAG_MISSING (1<<0) /* Flag missing attribute */
+#define ATTR_FLAG_EXTRA (1<<1) /* Flag spurious attribute */
+#define ATTR_FLAG_MORE (1<<2) /* Don't skip or terminate */
+#define ATTR_FLAG_PRINTABLE (1<<3) /* Sanitize received strings */
+
+#define ATTR_FLAG_STRICT (ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA)
+#define ATTR_FLAG_ALL (017)
+
+ /*
+ * Default to null-terminated, as opposed to base64-encoded.
+ */
+#define attr_print attr_print0
+#define attr_vprint attr_vprint0
+#define attr_scan attr_scan0
+#define attr_vscan attr_vscan0
+#define attr_scan_more attr_scan_more0
+
+ /*
+ * attr_print64.c.
+ */
+extern int attr_print64(VSTREAM *, int,...);
+extern int attr_vprint64(VSTREAM *, int, va_list);
+
+ /*
+ * attr_scan64.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan64(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan64(VSTREAM *, int, va_list);
+extern int WARN_UNUSED_RESULT attr_scan_more64(VSTREAM *);
+
+ /*
+ * attr_print0.c.
+ */
+extern int attr_print0(VSTREAM *, int,...);
+extern int attr_vprint0(VSTREAM *, int, va_list);
+
+ /*
+ * attr_scan0.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan0(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan0(VSTREAM *, int, va_list);
+extern int WARN_UNUSED_RESULT attr_scan_more0(VSTREAM *);
+
+ /*
+ * attr_scan_plain.c.
+ */
+extern int attr_print_plain(VSTREAM *, int,...);
+extern int attr_vprint_plain(VSTREAM *, int, va_list);
+extern int attr_scan_more_plain(VSTREAM *);
+
+ /*
+ * attr_print_plain.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan_plain(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan_plain(VSTREAM *, int, va_list);
+
+ /*
+ * Attribute names for testing the compatibility of the read and write
+ * routines.
+ */
+#ifdef TEST
+#define ATTR_NAME_INT "number"
+#define ATTR_NAME_STR "string"
+#define ATTR_NAME_LONG "long_number"
+#define ATTR_NAME_DATA "data"
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/attr_clnt.c b/src/util/attr_clnt.c
new file mode 100644
index 0000000..d944be5
--- /dev/null
+++ b/src/util/attr_clnt.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* attr_clnt 3
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/*
+/* typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list);
+/* typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list);
+/* typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *);
+/*
+/* ATTR_CLNT *attr_clnt_create(server, timeout, max_idle, max_ttl)
+/* const char *server;
+/* int timeout;
+/* int max_idle;
+/* int max_ttl;
+/*
+/* int attr_clnt_request(client,
+/* send_flags, send_type, send_name, ..., ATTR_TYPE_END,
+/* recv_flags, recv_type, recv_name, ..., ATTR_TYPE_END)
+/* ATTR_CLNT *client;
+/* int send_flags;
+/* int send_type;
+/* const char *send_name;
+/* int recv_flags;
+/* int recv_type;
+/* const char *recv_name;
+/*
+/* void attr_clnt_free(client)
+/* ATTR_CLNT *client;
+/*
+/* void attr_clnt_control(client, name, value, ... ATTR_CLNT_CTL_END)
+/* ATTR_CLNT *client;
+/* int name;
+/* DESCRIPTION
+/* This module implements a client for a simple attribute-based
+/* protocol. The default protocol is described in attr_scan_plain(3).
+/*
+/* attr_clnt_create() creates a client handle. See auto_clnt(3) for
+/* a description of the arguments.
+/*
+/* attr_clnt_request() sends the specified request attributes and
+/* receives a reply. The reply argument specifies a name-value table.
+/* The other arguments are as described in attr_print_plain(3). The
+/* result is the number of attributes received or -1 in case of trouble.
+/*
+/* attr_clnt_free() destroys a client handle and closes its connection.
+/*
+/* attr_clnt_control() allows the user to fine tune the behavior of
+/* the specified client. The arguments are a list of (name, value)
+/* terminated with ATTR_CLNT_CTL_END.
+/* The following lists the names and the types of the corresponding
+/* value arguments.
+/* .IP "ATTR_CLNT_CTL_PROTO(ATTR_CLNT_PRINT_FN, ATTR_CLNT_SCAN_FN)"
+/* Specifies alternatives for the attr_plain_print() and
+/* attr_plain_scan() functions.
+/* .IP "ATTR_CLNT_CTL_REQ_LIMIT(int)"
+/* The maximal number of requests per connection (default: 0,
+/* i.e. no limit). To enable the limit, specify a value greater
+/* than zero.
+/* .IP "ATTR_CLNT_CTL_TRY_LIMIT(int)"
+/* The maximal number of attempts to send a request before
+/* giving up (default: 2). To disable the limit, specify a
+/* value equal to zero.
+/* .IP "ATTR_CLNT_CTL_TRY_DELAY(int)"
+/* The time in seconds between attempts to send a request
+/* (default: 1). Specify a value greater than zero.
+/* .IP "ATTR_CLNT_CTL_HANDSHAKE(VSTREAM *)"
+/* A pointer to function that will be called at the start of a
+/* new connection, and that returns 0 in case of success.
+/* DIAGNOSTICS
+/* Warnings: communication failure.
+/* SEE ALSO
+/* auto_clnt(3), client endpoint management
+/* attr_scan_plain(3), attribute protocol
+/* attr_print_plain(3), attribute protocol
+/* 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 <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <attr.h>
+#include <iostuff.h>
+#include <compat_va_copy.h>
+#include <auto_clnt.h>
+#include <attr_clnt.h>
+
+/* Application-specific. */
+
+struct ATTR_CLNT {
+ AUTO_CLNT *auto_clnt;
+ /* Remaining properties are set with attr_clnt_control(). */
+ ATTR_CLNT_PRINT_FN print;
+ ATTR_CLNT_SCAN_FN scan;
+ int req_limit;
+ int req_count;
+ int try_limit;
+ int try_delay;
+};
+
+#define ATTR_CLNT_DEF_REQ_LIMIT (0) /* default per-session request limit */
+#define ATTR_CLNT_DEF_TRY_LIMIT (2) /* default request (re)try limit */
+#define ATTR_CLNT_DEF_TRY_DELAY (1) /* default request (re)try delay */
+
+/* attr_clnt_free - destroy attribute client */
+
+void attr_clnt_free(ATTR_CLNT *client)
+{
+ auto_clnt_free(client->auto_clnt);
+ myfree((void *) client);
+}
+
+/* attr_clnt_create - create attribute client */
+
+ATTR_CLNT *attr_clnt_create(const char *service, int timeout,
+ int max_idle, int max_ttl)
+{
+ ATTR_CLNT *client;
+
+ client = (ATTR_CLNT *) mymalloc(sizeof(*client));
+ client->auto_clnt = auto_clnt_create(service, timeout, max_idle, max_ttl);
+ client->scan = attr_vscan_plain;
+ client->print = attr_vprint_plain;
+ client->req_limit = ATTR_CLNT_DEF_REQ_LIMIT;
+ client->req_count = 0;
+ client->try_limit = ATTR_CLNT_DEF_TRY_LIMIT;
+ client->try_delay = ATTR_CLNT_DEF_TRY_DELAY;
+ return (client);
+}
+
+/* attr_clnt_request - send query, receive reply */
+
+int attr_clnt_request(ATTR_CLNT *client, int send_flags,...)
+{
+ const char *myname = "attr_clnt_request";
+ VSTREAM *stream;
+ int count = 0;
+ va_list saved_ap;
+ va_list ap;
+ int type;
+ int recv_flags;
+ int err;
+ int ret;
+
+ /*
+ * XXX If the stream is readable before we send anything, then assume the
+ * remote end disconnected.
+ *
+ * XXX For some reason we can't simply call the scan routine after the print
+ * routine, that messes up the argument list.
+ */
+#define SKIP_ARG(ap, type) { \
+ (void) va_arg(ap, char *); \
+ (void) va_arg(ap, type); \
+ }
+#define SKIP_ARG2(ap, t1, t2) { \
+ SKIP_ARG(ap, t1); \
+ (void) va_arg(ap, t2); \
+ }
+
+ /* Finalize argument lists before returning. */
+ va_start(saved_ap, send_flags);
+ for (;;) {
+ errno = 0;
+ if ((stream = auto_clnt_access(client->auto_clnt)) != 0
+ && readable(vstream_fileno(stream)) == 0) {
+ errno = 0;
+ VA_COPY(ap, saved_ap);
+ err = (client->print(stream, send_flags, ap) != 0
+ || vstream_fflush(stream) != 0);
+ va_end(ap);
+ if (err == 0) {
+ VA_COPY(ap, saved_ap);
+ while ((type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (type) {
+ case ATTR_TYPE_STR:
+ SKIP_ARG(ap, char *);
+ break;
+ case ATTR_TYPE_DATA:
+ SKIP_ARG2(ap, ssize_t, char *);
+ break;
+ case ATTR_TYPE_INT:
+ SKIP_ARG(ap, int);
+ break;
+ case ATTR_TYPE_LONG:
+ SKIP_ARG(ap, long);
+ break;
+ case ATTR_TYPE_HASH:
+ (void) va_arg(ap, HTABLE *);
+ break;
+ default:
+ msg_panic("%s: unexpected attribute type %d",
+ myname, type);
+ }
+ }
+ recv_flags = va_arg(ap, int);
+ ret = client->scan(stream, recv_flags, ap);
+ va_end(ap);
+ /* Finalize argument lists before returning. */
+ if (ret > 0) {
+ if (client->req_limit > 0
+ && (client->req_count += 1) >= client->req_limit) {
+ auto_clnt_recover(client->auto_clnt);
+ client->req_count = 0;
+ }
+ break;
+ }
+ }
+ }
+ if ((++count >= client->try_limit && client->try_limit > 0)
+ || msg_verbose
+ || (errno && errno != EPIPE && errno != ENOENT && errno != ECONNRESET))
+ msg_warn("problem talking to server %s: %m",
+ auto_clnt_name(client->auto_clnt));
+ /* Finalize argument lists before returning. */
+ if (count >= client->try_limit && client->try_limit > 0) {
+ ret = -1;
+ break;
+ }
+ sleep(client->try_delay);
+ auto_clnt_recover(client->auto_clnt);
+ client->req_count = 0;
+ }
+ /* Finalize argument lists before returning. */
+ va_end(saved_ap);
+ return (ret);
+}
+
+/* attr_clnt_control - fine control */
+
+void attr_clnt_control(ATTR_CLNT *client, int name,...)
+{
+ const char *myname = "attr_clnt_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != ATTR_CLNT_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case ATTR_CLNT_CTL_PROTO:
+ client->print = va_arg(ap, ATTR_CLNT_PRINT_FN);
+ client->scan = va_arg(ap, ATTR_CLNT_SCAN_FN);
+ break;
+ case ATTR_CLNT_CTL_HANDSHAKE:
+ auto_clnt_control(client->auto_clnt,
+ AUTO_CLNT_CTL_HANDSHAKE,
+ va_arg(ap, ATTR_CLNT_HANDSHAKE_FN),
+ AUTO_CLNT_CTL_END);
+ break;
+ case ATTR_CLNT_CTL_REQ_LIMIT:
+ client->req_limit = va_arg(ap, int);
+ if (client->req_limit < 0)
+ msg_panic("%s: bad request limit: %d",
+ myname, client->req_limit);
+ if (msg_verbose)
+ msg_info("%s: new request limit %d",
+ myname, client->req_limit);
+ break;
+ case ATTR_CLNT_CTL_TRY_LIMIT:
+ client->try_limit = va_arg(ap, int);
+ if (client->try_limit < 0)
+ msg_panic("%s: bad retry limit: %d", myname, client->try_limit);
+ if (msg_verbose)
+ msg_info("%s: new retry limit %d", myname, client->try_limit);
+ break;
+ case ATTR_CLNT_CTL_TRY_DELAY:
+ client->try_delay = va_arg(ap, int);
+ if (client->try_delay <= 0)
+ msg_panic("%s: bad retry delay: %d", myname, client->try_delay);
+ if (msg_verbose)
+ msg_info("%s: new retry delay %d", myname, client->try_delay);
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
diff --git a/src/util/attr_clnt.h b/src/util/attr_clnt.h
new file mode 100644
index 0000000..ca630cd
--- /dev/null
+++ b/src/util/attr_clnt.h
@@ -0,0 +1,60 @@
+#ifndef _ATTR_CLNT_H_INCLUDED_
+#define _ATTR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr_clnt 3h
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <attr.h>
+
+ /*
+ * External interface.
+ */
+typedef struct ATTR_CLNT ATTR_CLNT;
+typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list);
+typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list);
+typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *);
+
+extern ATTR_CLNT *attr_clnt_create(const char *, int, int, int);
+extern int attr_clnt_request(ATTR_CLNT *, int,...);
+extern void attr_clnt_free(ATTR_CLNT *);
+extern void attr_clnt_control(ATTR_CLNT *, int,...);
+
+#define ATTR_CLNT_CTL_END 0
+#define ATTR_CLNT_CTL_PROTO 1 /* print/scan functions */
+#define ATTR_CLNT_CTL_REQ_LIMIT 2 /* requests per connection */
+#define ATTR_CLNT_CTL_TRY_LIMIT 3 /* attempts per request */
+#define ATTR_CLNT_CTL_TRY_DELAY 4 /* pause between requests */
+#define ATTR_CLNT_CTL_HANDSHAKE 5 /* handshake before first request */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/attr_print0.c b/src/util/attr_print0.c
new file mode 100644
index 0000000..98c5118
--- /dev/null
+++ b/src/util/attr_print0.c
@@ -0,0 +1,256 @@
+/*++
+/* NAME
+/* attr_print0 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print0(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint0(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print0() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan0(). The stream is not flushed.
+/*
+/* attr_vprint0() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print0() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan0(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print0() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan0(3) recover attributes from byte stream
+/* 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 <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <attr.h>
+#include <base64_code.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_vprint0 - send attribute list to stream */
+
+int attr_vprint0(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print0";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ ssize_t len_val;
+ static VSTRING *base64_buf;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ int_val = va_arg(ap, int);
+ vstream_fprintf(fp, "%u", (unsigned) int_val);
+ VSTREAM_PUTC('\0', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ long_val = va_arg(ap, unsigned long);
+ vstream_fprintf(fp, "%lu", (unsigned long) long_val);
+ VSTREAM_PUTC('\0', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ str_val = va_arg(ap, char *);
+ vstream_fwrite(fp, str_val, strlen(str_val) + 1);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ base64_encode(base64_buf, str_val, len_val);
+ vstream_fwrite(fp, STR(base64_buf), LEN(base64_buf) + 1);
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print0, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1);
+ vstream_fwrite(fp, ht[0]->value, strlen(ht[0]->value) + 1);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\0', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print0(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint0(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan0 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_print64.c b/src/util/attr_print64.c
new file mode 100644
index 0000000..085ba33
--- /dev/null
+++ b/src/util/attr_print64.c
@@ -0,0 +1,297 @@
+/*++
+/* NAME
+/* attr_print64 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print64(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint64(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print64() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan64(). The stream is not flushed.
+/*
+/* attr_vprint64() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print64() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan64(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print64() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan64(3) recover attributes from byte stream
+/* 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 <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <attr.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_print64_str - encode and send attribute information */
+
+static void attr_print64_str(VSTREAM *fp, const char *str, ssize_t len)
+{
+ static VSTRING *base64_buf;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+
+ base64_encode(base64_buf, str, len);
+ vstream_fputs(STR(base64_buf), fp);
+}
+
+static void attr_print64_num(VSTREAM *fp, unsigned num)
+{
+ static VSTRING *plain;
+
+ if (plain == 0)
+ plain = vstring_alloc(10);
+
+ vstring_sprintf(plain, "%u", num);
+ attr_print64_str(fp, STR(plain), LEN(plain));
+}
+
+static void attr_print64_long_num(VSTREAM *fp, unsigned long long_num)
+{
+ static VSTRING *plain;
+
+ if (plain == 0)
+ plain = vstring_alloc(10);
+
+ vstring_sprintf(plain, "%lu", long_num);
+ attr_print64_str(fp, STR(plain), LEN(plain));
+}
+
+/* attr_vprint64 - send attribute list to stream */
+
+int attr_vprint64(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print64";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ ssize_t len_val;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ int_val = va_arg(ap, int);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_num(fp, (unsigned) int_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ long_val = va_arg(ap, long);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_long_num(fp, (unsigned long) long_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ str_val = va_arg(ap, char *);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, str_val, strlen(str_val));
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, str_val, len_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print64, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ attr_print64_str(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN) - 1);
+ VSTREAM_PUTC('\n', fp);
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key));
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value));
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ attr_print64_str(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE) - 1);
+ VSTREAM_PUTC('\n', fp);
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print64(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint64(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan64 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_print_plain.c b/src/util/attr_print_plain.c
new file mode 100644
index 0000000..7d2d02f
--- /dev/null
+++ b/src/util/attr_print_plain.c
@@ -0,0 +1,252 @@
+/*++
+/* NAME
+/* attr_print_plain 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint_plain(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print_plain() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan_plain(). The stream is not flushed.
+/*
+/* attr_vprint_plain() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print_plain() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan_plain(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print_plain() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan_plain(3) recover attributes from byte stream
+/* 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 <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <vstring.h>
+#include <attr.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_vprint_plain - send attribute list to stream */
+
+int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print_plain";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ static VSTRING *base64_buf;
+ ssize_t len_val;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ int_val = va_arg(ap, int);
+ vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, (unsigned) int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ long_val = va_arg(ap, long);
+ vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ str_val = va_arg(ap, char *);
+ vstream_fprintf(fp, "%s=%s\n", attr_name, str_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ base64_encode(base64_buf, str_val, len_val);
+ vstream_fprintf(fp, "%s=%s\n", attr_name, STR(base64_buf));
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
+ VSTREAM_PUTC('\n', fp);
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ vstream_fprintf(fp, "%s=%s\n", ht[0]->key, (char *) ht[0]->value);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
+ VSTREAM_PUTC('\n', fp);
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan.ref b/src/util/attr_scan.ref
new file mode 100644
index 0000000..cd06a27
--- /dev/null
+++ b/src/util/attr_scan.ref
@@ -0,0 +1,36 @@
+./attr_print: send attr number = 4711
+./attr_print: send attr string = whoopee
+./attr_print: send attr name foo-name value foo-value
+./attr_print: send attr name bar-name value bar-value
+./attr_print: send attr number = 4711
+./attr_print: send attr string = whoopee
+./attr_scan: unknown_stream: wanted attribute: number
+./attr_scan: input attribute name: number
+./attr_scan: input attribute value: 4711
+./attr_scan: unknown_stream: wanted attribute: string
+./attr_scan: input attribute name: string
+./attr_scan: input attribute value: whoopee
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: foo-name
+./attr_scan: input attribute value: foo-value
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: bar-name
+./attr_scan: input attribute value: bar-value
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: (end)
+./attr_scan: unknown_stream: wanted attribute: number
+./attr_scan: input attribute name: number
+./attr_scan: input attribute value: 4711
+./attr_scan: unknown_stream: wanted attribute: string
+./attr_scan: input attribute name: string
+./attr_scan: input attribute value: whoopee
+./attr_scan: unknown_stream: wanted attribute: (list terminator)
+./attr_scan: input attribute name: (end)
+number 4711
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
+number 4711
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
diff --git a/src/util/attr_scan0.c b/src/util/attr_scan0.c
new file mode 100644
index 0000000..13aa125
--- /dev/null
+++ b/src/util/attr_scan0.c
@@ -0,0 +1,596 @@
+/*++
+/* NAME
+/* attr_scan0 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan0(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more0(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan0() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print0().
+/*
+/* attr_vscan0() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more0() returns 0 when a terminator is found (and
+/* consumes that terminator), returns 1 when more input is
+/* expected (without consuming input), and returns -1 otherwise
+/* (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* null
+/* .br
+/* multi-attr :== "{" null simple-attr* "}" null
+/* .br
+/* simple-attr :== attr-name null attr-value null
+/* .br
+/* attr-name :== any string not containing null
+/* .br
+/* attr-value :== any string not containing null
+/* .br
+/* null :== the ASCII null character
+/* .in
+/*
+/* All attribute names and attribute values are sent as null terminated
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters including the terminator.
+/* These formatting rules favor implementations in C.
+/*
+/* Normally, attributes must be received in the sequence as specified with
+/* the attr_scan0() argument list. The input stream may contain additional
+/* attributes at any point in the input stream, including additional
+/* instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan0() operations from the
+/* same input attribute list.
+/* By default, attr_scan0() skips forward past the input attribute list
+/* terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan0() and attr_vscan0() return -1 when malformed input is
+/* detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print0(3) send attributes over byte stream.
+/* 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 <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan0_string - pull a string from the input stream */
+
+static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
+{
+ int ch;
+
+ if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ if (ch != 0) {
+ msg_warn("unexpected end-of-input from %s while reading %s",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan0_data - pull a data blob from the input stream */
+
+static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf,
+ const char *context)
+{
+ static VSTRING *base64_buf = 0;
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0)
+ return (-1);
+ if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(base64_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan0_number - pull a number from the input stream */
+
+static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan0_long_number - pull a number from the input stream */
+
+static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan0 - receive attribute list from stream */
+
+int attr_vscan0(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan0";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan0_string(fp, name_buf,
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ (void) attr_scan0_string(fp, str_buf, "input attribute value");
+ }
+
+ /*
+ * Do the requested conversion.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan0_number(fp, number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_LONG:
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan0_long_number(fp, long_number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STR:
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan0_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan0_data(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan0_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if ((ch = attr_scan0_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan0 - read attribute list from stream */
+
+int attr_scan0(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan0(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more0 - look ahead for more */
+
+int attr_scan_more0(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case 0:
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan0 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan0.ref b/src/util/attr_scan0.ref
new file mode 100644
index 0000000..9055d79
--- /dev/null
+++ b/src/util/attr_scan0.ref
@@ -0,0 +1,79 @@
+./attr_print0: send attr protocol = test
+./attr_print0: send attr number = 4711
+./attr_print0: send attr long_number = 1234
+./attr_print0: send attr string = whoopee
+./attr_print0: send attr data = [data 7 bytes]
+./attr_print0: send attr name bar-name value bar-value
+./attr_print0: send attr name foo-name value foo-value
+./attr_print0: send attr long_number = 4321
+./attr_print0: send attr protocol = test
+./attr_print0: send attr number = 4711
+./attr_print0: send attr long_number = 1234
+./attr_print0: send attr string = whoopee
+./attr_print0: send attr data = [data 7 bytes]
+./attr_print0: send attr protocol = not-test
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: test
+./attr_scan0: unknown_stream: wanted attribute: number
+./attr_scan0: input attribute name: number
+./attr_scan0: input attribute value: 4711
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 1234
+./attr_scan0: unknown_stream: wanted attribute: string
+./attr_scan0: input attribute name: string
+./attr_scan0: input attribute value: whoopee
+./attr_scan0: unknown_stream: wanted attribute: data
+./attr_scan0: input attribute name: data
+./attr_scan0: input attribute value: d2hvb3BlZQ==
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan0: input attribute name: {
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: bar-name
+./attr_scan0: input attribute value: bar-value
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: foo-name
+./attr_scan0: input attribute value: foo-value
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: }
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 4321
+./attr_scan0: unknown_stream: wanted attribute: (list terminator)
+./attr_scan0: input attribute name: (end)
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: test
+./attr_scan0: unknown_stream: wanted attribute: number
+./attr_scan0: input attribute name: number
+./attr_scan0: input attribute value: 4711
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 1234
+./attr_scan0: unknown_stream: wanted attribute: string
+./attr_scan0: input attribute name: string
+./attr_scan0: input attribute value: whoopee
+./attr_scan0: unknown_stream: wanted attribute: data
+./attr_scan0: input attribute name: data
+./attr_scan0: input attribute value: d2hvb3BlZQ==
+./attr_scan0: unknown_stream: wanted attribute: (list terminator)
+./attr_scan0: input attribute name: (end)
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: not-test
+./attr_scan0: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/attr_scan64.c b/src/util/attr_scan64.c
new file mode 100644
index 0000000..0d9b114
--- /dev/null
+++ b/src/util/attr_scan64.c
@@ -0,0 +1,665 @@
+/*++
+/* NAME
+/* attr_scan64 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan64(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more64(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan64() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print64().
+/*
+/* attr_vscan64() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more64() returns 0 when a terminator is found
+/* (and consumes that terminator), returns 1 when more input
+/* is expected (without consuming input), and returns -1
+/* otherwise (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
+/* .br
+/* simple-attr :== attr-name colon attr-value newline
+/* .br
+/* attr-name :== any base64 encoded string
+/* .br
+/* attr-value :== any base64 encoded string
+/* .br
+/* colon :== the ASCII colon character
+/* .br
+/* newline :== the ASCII newline character
+/* .in
+/*
+/* All attribute names and attribute values are sent as base64-encoded
+/* strings. Each base64 encoding must be no longer than 4*var_line_limit
+/* characters. The formatting rules aim to make implementations in PERL
+/* and other languages easy.
+/*
+/* Normally, attributes must be received in the sequence as specified with
+/* the attr_scan64() argument list. The input stream may contain additional
+/* attributes at any point in the input stream, including additional
+/* instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan64() operations from the
+/* same input attribute list.
+/* By default, attr_scan64() skips forward past the input attribute list
+/* terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan64() and attr_vscan64() return -1 when malformed input is
+/* detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print64(3) send attributes over byte stream.
+/* 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 <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan64_string - pull a string from the input stream */
+
+static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
+{
+ static VSTRING *base64_buf = 0;
+
+#if 0
+ extern int var_line_limit; /* XXX */
+ int limit = var_line_limit * 4;
+
+#endif
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+
+ VSTRING_RESET(base64_buf);
+ while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') {
+ if (ch == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ VSTRING_ADDCH(base64_buf, ch);
+#if 0
+ if (LEN(base64_buf) > limit) {
+ msg_warn("string length > %d characters from %s while reading %s",
+ limit, VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+#endif
+ }
+ VSTRING_TERMINATE(base64_buf);
+ if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s: %.100s",
+ VSTREAM_PATH(fp), STR(base64_buf));
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan64_number - pull a number from the input stream */
+
+static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan64_long_number - pull a number from the input stream */
+
+static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan64 - receive attribute list from stream */
+
+int attr_vscan64(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan64";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan64_string(fp, name_buf,
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (ch == '\n' && LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ /* void */ ;
+ }
+
+ /*
+ * Do the requested conversion. If the target attribute is a
+ * non-array type, disallow sending a multi-valued attribute, and
+ * disallow sending no value. If the target attribute is an array
+ * type, allow the sender to send a zero-element array (i.e. no value
+ * at all). XXX Need to impose a bound on the number of array
+ * elements.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ if (ch != ':') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan64_number(fp, number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_LONG:
+ if (ch != ':') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan64_long_number(fp, long_number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_STR:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan64_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ if (ch != ':') {
+ msg_warn("missing value for data attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan64_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan64_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if ((ch = attr_scan64_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan64 - read attribute list from stream */
+
+int attr_scan64(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan64(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more64 - look ahead for more */
+
+int attr_scan_more64(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case '\n':
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan64 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan64.ref b/src/util/attr_scan64.ref
new file mode 100644
index 0000000..ccf27f1
--- /dev/null
+++ b/src/util/attr_scan64.ref
@@ -0,0 +1,79 @@
+./attr_print64: send attr protocol = test
+./attr_print64: send attr number = 4711
+./attr_print64: send attr long_number = 1234
+./attr_print64: send attr string = whoopee
+./attr_print64: send attr data = [data 7 bytes]
+./attr_print64: send attr name bar-name value bar-value
+./attr_print64: send attr name foo-name value foo-value
+./attr_print64: send attr long_number = 4321
+./attr_print64: send attr protocol = test
+./attr_print64: send attr number = 4711
+./attr_print64: send attr long_number = 1234
+./attr_print64: send attr string = whoopee
+./attr_print64: send attr data = [data 7 bytes]
+./attr_print64: send attr protocol = not-test
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: test
+./attr_scan64: unknown_stream: wanted attribute: number
+./attr_scan64: input attribute name: number
+./attr_scan64: input attribute value: 4711
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 1234
+./attr_scan64: unknown_stream: wanted attribute: string
+./attr_scan64: input attribute name: string
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: data
+./attr_scan64: input attribute name: data
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan64: input attribute name: {
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: bar-name
+./attr_scan64: input attribute value: bar-value
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: foo-name
+./attr_scan64: input attribute value: foo-value
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: }
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 4321
+./attr_scan64: unknown_stream: wanted attribute: (list terminator)
+./attr_scan64: input attribute name: (end)
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: test
+./attr_scan64: unknown_stream: wanted attribute: number
+./attr_scan64: input attribute name: number
+./attr_scan64: input attribute value: 4711
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 1234
+./attr_scan64: unknown_stream: wanted attribute: string
+./attr_scan64: input attribute name: string
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: data
+./attr_scan64: input attribute name: data
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: (list terminator)
+./attr_scan64: input attribute name: (end)
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: not-test
+./attr_scan64: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/attr_scan_plain.c b/src/util/attr_scan_plain.c
new file mode 100644
index 0000000..d7e2f66
--- /dev/null
+++ b/src/util/attr_scan_plain.c
@@ -0,0 +1,643 @@
+/*++
+/* NAME
+/* attr_scan_plain 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan_plain(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more_plain(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan_plain() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print_plain().
+/*
+/* attr_vscan_plain() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more_plain() returns 0 when a terminator is found
+/* (and consumes that terminator), returns 1 when more input
+/* is expected (without consuming input), and returns -1
+/* otherwise (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
+/* .br
+/* simple-attr :== attr-name "=" attr-value newline
+/* .br
+/* attr-name :== any string without null or "=" or newline.
+/* .br
+/* attr-value :== any string without null or newline.
+/* .br
+/* newline :== the ASCII newline character
+/* .in
+/*
+/* All attribute names and attribute values are sent as plain
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters. The formatting rules aim to make implementations in PERL
+/* and other languages easy.
+/*
+/* Normally, attributes must be received in the sequence as specified
+/* with the attr_scan_plain() argument list. The input stream may
+/* contain additional attributes at any point in the input stream,
+/* including additional instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan_plain() operations
+/* from the same input attribute list.
+/* By default, attr_scan_plain() skips forward past the input attribute
+/* list terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input
+/* is detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print_plain(3) send attributes over byte stream.
+/* 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 <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan_plain_string - pull a string from the input stream */
+
+static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf,
+ int terminator, const char *context)
+{
+#if 0
+ extern int var_line_limit; /* XXX */
+ int limit = var_line_limit * 4;
+
+#endif
+ int ch;
+
+ VSTRING_RESET(plain_buf);
+ while ((ch = VSTREAM_GETC(fp)) != '\n'
+ && (terminator == 0 || ch != terminator)) {
+ if (ch == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ VSTRING_ADDCH(plain_buf, ch);
+#if 0
+ if (LEN(plain_buf) > limit) {
+ msg_warn("string length > %d characters from %s while reading %s",
+ limit, VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+#endif
+ }
+ VSTRING_TERMINATE(plain_buf);
+
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan_plain_data - pull a data blob from the input stream */
+
+static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf,
+ int terminator,
+ const char *context)
+{
+ static VSTRING *base64_buf = 0;
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0)
+ return (-1);
+ if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(base64_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan_plain_number - pull a number from the input stream */
+
+static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ int terminator, const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan_plain_long_number - pull a number from the input stream */
+
+static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ int terminator,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan_plain - receive attribute list from stream */
+
+int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan_plain";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan_plain_string(fp, name_buf, '=',
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (ch == '\n' && LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ /* void */ ;
+ }
+
+ /*
+ * Do the requested conversion.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan_plain_number(fp, number, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_LONG:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf,
+ 0, "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STR:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan_plain_string(fp, string, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ if (ch != '=') {
+ msg_warn("missing value for data attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan_plain_data(fp, string, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan_plain_string(fp, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if ((ch = attr_scan_plain_string(fp, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan_plain - read attribute list from stream */
+
+int attr_scan_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more_plain - look ahead for more */
+
+int attr_scan_more_plain(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case '\n':
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan_plain.ref b/src/util/attr_scan_plain.ref
new file mode 100644
index 0000000..1c0f358
--- /dev/null
+++ b/src/util/attr_scan_plain.ref
@@ -0,0 +1,79 @@
+./attr_print_plain: send attr protocol = test
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_print_plain: send attr data = [data 7 bytes]
+./attr_print_plain: send attr name bar-name value bar-value
+./attr_print_plain: send attr name foo-name value foo-value
+./attr_print_plain: send attr long_number = 4321
+./attr_print_plain: send attr protocol = test
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_print_plain: send attr data = [data 7 bytes]
+./attr_print_plain: send attr protocol = not-test
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: test
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: data
+./attr_scan_plain: input attribute name: data
+./attr_scan_plain: input attribute value: d2hvb3BlZQ==
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: {
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: bar-name
+./attr_scan_plain: input attribute value: bar-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: foo-name
+./attr_scan_plain: input attribute value: foo-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: }
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 4321
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
+./attr_scan_plain: input attribute name: (end)
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: test
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: data
+./attr_scan_plain: input attribute name: data
+./attr_scan_plain: input attribute value: d2hvb3BlZQ==
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
+./attr_scan_plain: input attribute name: (end)
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: not-test
+./attr_scan_plain: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/auto_clnt.c b/src/util/auto_clnt.c
new file mode 100644
index 0000000..cdbbe22
--- /dev/null
+++ b/src/util/auto_clnt.c
@@ -0,0 +1,372 @@
+/*++
+/* NAME
+/* auto_clnt 3
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/*
+/* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *);
+/*
+/* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl)
+/* const char *service;
+/* int timeout;
+/* int max_idle;
+/* int max_ttl;
+/*
+/* VSTREAM *auto_clnt_access(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_recover(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* const char *auto_clnt_name(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_free(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END)
+/* AUTO_CLNT *auto_clnt;
+/* int name;
+/* DESCRIPTION
+/* This module maintains IPC client endpoints that automatically
+/* disconnect after a being idle for a configurable amount of time,
+/* that disconnect after a configurable time to live,
+/* and that transparently handle most server-initiated disconnects.
+/*
+/* This module tries each operation only a limited number of
+/* times and then reports an error. This is unlike the
+/* clnt_stream(3) module which will retry forever, so that
+/* the application never experiences an error.
+/*
+/* auto_clnt_create() instantiates a client endpoint.
+/*
+/* auto_clnt_access() returns an open stream to the service specified
+/* to auto_clnt_create(). The stream instance may change between calls.
+/* The result is a null pointer in case of failure.
+/*
+/* auto_clnt_recover() recovers from a server-initiated disconnect
+/* that happened in the middle of an I/O operation.
+/*
+/* auto_clnt_name() returns the name of the specified client endpoint.
+/*
+/* auto_clnt_free() destroys of the specified client endpoint.
+/*
+/* auto_clnt_control() allows the user to fine tune the behavior of
+/* the specified client. The arguments are a list of (name, value)
+/* terminated with AUTO_CLNT_CTL_END.
+/* The following lists the names and the types of the corresponding
+/* value arguments.
+/* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)"
+/* A pointer to function that will be called at the start of a
+/* new connection, and that returns 0 in case of success.
+/* .PP
+/* Arguments:
+/* .IP service
+/* The service argument specifies "transport:servername" where
+/* transport is currently limited to one of the following:
+/* .RS
+/* .IP inet
+/* servername has the form "inet:host:port".
+/* .IP local
+/* servername has the form "local:private/servicename" or
+/* "local:public/servicename". This is the preferred way to
+/* specify Postfix daemons that are configured as "unix" in
+/* master.cf.
+/* .IP unix
+/* servername has the form "unix:private/servicename" or
+/* "unix:public/servicename". This does not work on Solaris,
+/* where Postfix uses STREAMS instead of UNIX-domain sockets.
+/* .RE
+/* .IP timeout
+/* The time limit for sending, receiving, or for connecting
+/* to a server. Specify a value <=0 to disable the time limit.
+/* .IP max_idle
+/* Idle time after which the client disconnects. Specify 0 to
+/* disable the limit.
+/* .IP max_ttl
+/* Upper bound on the time that a connection is allowed to persist.
+/* Specify 0 to disable the limit.
+/* .IP open_action
+/* Application call-back routine that opens a stream or returns a
+/* null pointer upon failure. In case of success, the call-back routine
+/* is expected to set the stream pathname to the server endpoint name.
+/* .IP context
+/* Application context that is passed to the open_action routine.
+/* .IP handshake
+/* A null pointer, or a pointer to function that will be called
+/* at the start of a new connection and that returns 0 in case
+/* of success.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: out of memory.
+/* 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 <mymalloc.h>
+#include <vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <connect.h>
+#include <split_at.h>
+#include <auto_clnt.h>
+
+/* Application-specific. */
+
+ /*
+ * AUTO_CLNT is an opaque structure. None of the access methods can easily
+ * be implemented as a macro, and access is not performance critical anyway.
+ */
+struct AUTO_CLNT {
+ VSTREAM *vstream; /* buffered I/O */
+ char *endpoint; /* host:port or pathname */
+ int timeout; /* I/O time limit */
+ int max_idle; /* time before client disconnect */
+ int max_ttl; /* time before client disconnect */
+ AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */
+ int (*connect) (const char *, int, int); /* unix, local, inet */
+};
+
+static void auto_clnt_close(AUTO_CLNT *);
+
+/* auto_clnt_event - server-initiated disconnect or client-side max_idle */
+
+static void auto_clnt_event(int unused_event, void *context)
+{
+ AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context;
+
+ /*
+ * Sanity check. This routine causes the stream to be closed, so it
+ * cannot be called when the stream is already closed.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("auto_clnt_event: stream is closed");
+
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_ttl_event - client-side expiration */
+
+static void auto_clnt_ttl_event(int event, void *context)
+{
+
+ /*
+ * XXX This function is needed only because event_request_timer() cannot
+ * distinguish between requests that specify the same call-back routine
+ * and call-back context. The fix is obvious: specify a request ID along
+ * with the call-back routine, but there is too much code that would have
+ * to be changed.
+ *
+ * XXX Should we be concerned that an overly aggressive optimizer will
+ * eliminate this function and replace calls to auto_clnt_ttl_event() by
+ * direct calls to auto_clnt_event()? It should not, because there exists
+ * code that takes the address of both functions.
+ */
+ auto_clnt_event(event, context);
+}
+
+/* auto_clnt_open - connect to service */
+
+static void auto_clnt_open(AUTO_CLNT *auto_clnt)
+{
+ const char *myname = "auto_clnt_open";
+ int fd;
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream)
+ msg_panic("auto_clnt_open: stream is open");
+
+ /*
+ * Schedule a read event so that we can clean up when the remote side
+ * disconnects, and schedule a timer event so that we can cleanup an idle
+ * connection. Note that both events are handled by the same routine.
+ *
+ * Finally, schedule an event to force disconnection even when the
+ * connection is not idle. This is to prevent one client from clinging on
+ * to a server forever.
+ */
+ fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout);
+ if (fd < 0) {
+ msg_warn("connect to %s: %m", auto_clnt->endpoint);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: connected to %s", myname, auto_clnt->endpoint);
+ auto_clnt->vstream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(auto_clnt->vstream,
+ CA_VSTREAM_CTL_PATH(auto_clnt->endpoint),
+ CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout),
+ CA_VSTREAM_CTL_END);
+ }
+
+ if (auto_clnt->vstream != 0) {
+ close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC);
+ event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event,
+ (void *) auto_clnt);
+ if (auto_clnt->max_idle > 0)
+ event_request_timer(auto_clnt_event, (void *) auto_clnt,
+ auto_clnt->max_idle);
+ if (auto_clnt->max_ttl > 0)
+ event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt,
+ auto_clnt->max_ttl);
+ }
+}
+
+/* auto_clnt_close - disconnect from service */
+
+static void auto_clnt_close(AUTO_CLNT *auto_clnt)
+{
+ const char *myname = "auto_clnt_close";
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("%s: stream is closed", myname);
+
+ /*
+ * Be sure to disable read and timer events.
+ */
+ if (msg_verbose)
+ msg_info("%s: disconnect %s stream",
+ myname, VSTREAM_PATH(auto_clnt->vstream));
+ event_disable_readwrite(vstream_fileno(auto_clnt->vstream));
+ event_cancel_timer(auto_clnt_event, (void *) auto_clnt);
+ event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt);
+ (void) vstream_fclose(auto_clnt->vstream);
+ auto_clnt->vstream = 0;
+}
+
+/* auto_clnt_recover - recover from server-initiated disconnect */
+
+void auto_clnt_recover(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Clean up. Don't re-connect until the caller needs it.
+ */
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_access - access a client stream */
+
+VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt)
+{
+ AUTO_CLNT_HANDSHAKE_FN handshake;
+
+ /*
+ * Open a stream or restart the idle timer.
+ *
+ * Important! Do not restart the TTL timer!
+ */
+ if (auto_clnt->vstream == 0) {
+ auto_clnt_open(auto_clnt);
+ handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0);
+ } else {
+ if (auto_clnt->max_idle > 0)
+ event_request_timer(auto_clnt_event, (void *) auto_clnt,
+ auto_clnt->max_idle);
+ handshake = 0;
+ }
+ if (handshake != 0 && handshake(auto_clnt->vstream) != 0)
+ return (0);
+ return (auto_clnt->vstream);
+}
+
+/* auto_clnt_create - create client stream object */
+
+AUTO_CLNT *auto_clnt_create(const char *service, int timeout,
+ int max_idle, int max_ttl)
+{
+ const char *myname = "auto_clnt_create";
+ char *transport = mystrdup(service);
+ char *endpoint;
+ AUTO_CLNT *auto_clnt;
+
+ /*
+ * Don't open the stream until the caller needs it.
+ */
+ if ((endpoint = split_at(transport, ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("need service transport:endpoint instead of \"%s\"", service);
+ if (msg_verbose)
+ msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
+ auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt));
+ auto_clnt->vstream = 0;
+ auto_clnt->endpoint = mystrdup(endpoint);
+ auto_clnt->timeout = timeout;
+ auto_clnt->max_idle = max_idle;
+ auto_clnt->max_ttl = max_ttl;
+ auto_clnt->handshake = 0;
+ if (strcmp(transport, "inet") == 0) {
+ auto_clnt->connect = inet_connect;
+ } else if (strcmp(transport, "local") == 0) {
+ auto_clnt->connect = LOCAL_CONNECT;
+ } else if (strcmp(transport, "unix") == 0) {
+ auto_clnt->connect = unix_connect;
+ } else {
+ msg_fatal("invalid transport name: %s in service: %s",
+ transport, service);
+ }
+ myfree(transport);
+ return (auto_clnt);
+}
+
+/* auto_clnt_name - return client stream name */
+
+const char *auto_clnt_name(AUTO_CLNT *auto_clnt)
+{
+ return (auto_clnt->endpoint);
+}
+
+/* auto_clnt_free - destroy client stream instance */
+
+void auto_clnt_free(AUTO_CLNT *auto_clnt)
+{
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+ myfree(auto_clnt->endpoint);
+ myfree((void *) auto_clnt);
+}
+
+/* auto_clnt_control - fine control */
+
+void auto_clnt_control(AUTO_CLNT *client, int name,...)
+{
+ const char *myname = "auto_clnt_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case AUTO_CLNT_CTL_HANDSHAKE:
+ client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN);
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
diff --git a/src/util/auto_clnt.h b/src/util/auto_clnt.h
new file mode 100644
index 0000000..61c6680
--- /dev/null
+++ b/src/util/auto_clnt.h
@@ -0,0 +1,51 @@
+#ifndef _AUTO_CLNT_H_INCLUDED_
+#define _AUTO_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* auto_clnt 3h
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct AUTO_CLNT AUTO_CLNT;
+typedef int (*AUTO_CLNT_HANDSHAKE_FN) (VSTREAM *);
+
+extern AUTO_CLNT *auto_clnt_create(const char *, int, int, int);
+extern VSTREAM *auto_clnt_access(AUTO_CLNT *);
+extern void auto_clnt_recover(AUTO_CLNT *);
+extern const char *auto_clnt_name(AUTO_CLNT *);
+extern void auto_clnt_free(AUTO_CLNT *);
+extern void auto_clnt_control(AUTO_CLNT *, int,...);
+
+#define AUTO_CLNT_CTL_END 0
+#define AUTO_CLNT_CTL_HANDSHAKE 1 /* handshake before first request */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/balpar.c b/src/util/balpar.c
new file mode 100644
index 0000000..6ff97eb
--- /dev/null
+++ b/src/util/balpar.c
@@ -0,0 +1,56 @@
+/*++
+/* NAME
+/* balpar 3
+/* SUMMARY
+/* determine length of string in parentheses
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* size_t balpar(string, parens)
+/* const char *string;
+/* const char *parens;
+/* DESCRIPTION
+/* balpar() determines the length of a string enclosed in
+/* the specified parentheses, zero in case of error.
+/* SEE ALSO
+/* A balpar() routine appears in Brian W. Kernighan, P.J. Plauger:
+/* "Software Tools", Addison-Wesley 1976. This function is different.
+/* 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 <stringops.h>
+
+/* balpar - return length of {text} */
+
+size_t balpar(const char *string, const char *parens)
+{
+ const char *cp;
+ int level;
+ int ch;
+
+ if (*string != parens[0])
+ return (0);
+ for (level = 1, cp = string + 1; (ch = *cp) != 0; cp++) {
+ if (ch == parens[1]) {
+ if (--level == 0)
+ return (cp - string + 1);
+ } else if (ch == parens[0]) {
+ level++;
+ }
+ }
+ return (0);
+}
diff --git a/src/util/base32_code.c b/src/util/base32_code.c
new file mode 100644
index 0000000..31566ea
--- /dev/null
+++ b/src/util/base32_code.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* base32_code 3
+/* SUMMARY
+/* encode/decode data, base 32 style
+/* SYNOPSIS
+/* #include <base32_code.h>
+/*
+/* VSTRING *base32_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base32_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* DESCRIPTION
+/* base32_encode() takes a block of len bytes and encodes it as one
+/* null-terminated string. The result value is the result argument.
+/*
+/* base32_decode() performs the opposite transformation. The result
+/* value is the result argument. The result is null terminated, whether
+/* or not that makes sense.
+/* DIAGNOSTICS
+/* base32_decode() returns a null pointer when the input contains
+/* characters not in the base 32 alphabet.
+/* SEE ALSO
+/* RFC 4648; padding is strictly enforced
+/* 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 <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <base32_code.h>
+
+/* Application-specific. */
+
+static unsigned char to_b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x))
+
+/* base32_encode - raw data to encoded */
+
+VSTRING *base32_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ const unsigned char *cp;
+ ssize_t count;
+ static int pad_count[] = {0, 6, 4, 3, 1};
+
+ /*
+ * Encode 5 -> 8.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 5, cp += 5) {
+ VSTRING_ADDCH(result, to_b32[cp[0] >> 3]);
+ if (count < 2) {
+ VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2 | cp[1] >> 6]);
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x3f) >> 1]);
+ if (count < 3) {
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4 | cp[2] >> 4]);
+ if (count < 4) {
+ VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1 | cp[3] >> 7]);
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x7f) >> 2]);
+ if (count < 5) {
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3 | cp[4] >> 5]);
+ VSTRING_ADDCH(result, to_b32[cp[4] & 0x1f]);
+ }
+ if (count > 0)
+ vstring_strncat(result, "======", pad_count[count]);
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* base32_decode - encoded data to raw */
+
+VSTRING *base32_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ static unsigned char *un_b32 = 0;
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int ch0;
+ unsigned int ch1;
+ unsigned int ch2;
+ unsigned int ch3;
+ unsigned int ch4;
+ unsigned int ch5;
+ unsigned int ch6;
+ unsigned int ch7;
+
+#define CHARS_PER_BYTE (UCHAR_MAX + 1)
+#define INVALID 0xff
+#if 1
+#define ENFORCE_LENGTH(x) (x)
+#define ENFORCE_PADDING(x) (x)
+#define ENFORCE_NULL_BITS(x) (x)
+#else
+#define ENFORCE_LENGTH(x) (1)
+#define ENFORCE_PADDING(x) (1)
+#define ENFORCE_NULL_BITS(x) (1)
+#endif
+
+ /*
+ * Sanity check.
+ */
+ if (ENFORCE_LENGTH(len % 8))
+ return (0);
+
+ /*
+ * Once: initialize the decoding lookup table on the fly.
+ */
+ if (un_b32 == 0) {
+ un_b32 = (unsigned char *) mymalloc(CHARS_PER_BYTE);
+ memset(un_b32, INVALID, CHARS_PER_BYTE);
+ for (cp = to_b32; cp < to_b32 + sizeof(to_b32) - 1; cp++)
+ un_b32[*cp] = cp - to_b32;
+ }
+
+ /*
+ * Decode 8 -> 5.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 8) {
+ if ((ch0 = un_b32[*cp++]) == INVALID
+ || (ch1 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch0 << 3 | ch1 >> 2);
+ if ((ch2 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "=====") == 0)
+ && ENFORCE_NULL_BITS((ch1 & 0x3) == 0))
+ break;
+ if ((ch2 = un_b32[ch2]) == INVALID)
+ return (0);
+ if ((ch3 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch1 << 6 | ch2 << 1 | ch3 >> 4);
+ if ((ch4 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "===") == 0)
+ && ENFORCE_NULL_BITS((ch3 & 0xf) == 0))
+ break;
+ if ((ch4 = un_b32[ch4]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch3 << 4 | ch4 >> 1);
+ if ((ch5 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "==") == 0)
+ && ENFORCE_NULL_BITS((ch4 & 0x1) == 0))
+ break;
+ if ((ch5 = un_b32[ch5]) == INVALID)
+ return (0);
+ if ((ch6 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch4 << 7 | ch5 << 2 | ch6 >> 3);
+ if ((ch7 = *cp++) == '='
+ && ENFORCE_NULL_BITS((ch6 & 0x7) == 0))
+ break;
+ if ((ch7 = un_b32[ch7]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch6 << 5 | ch7);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to base 32 and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ VSTRING *test = vstring_alloc(1);
+ int i, j;
+
+ /*
+ * Test all byte values (except null) on all byte positions.
+ */
+ for (j = 0; j < 256; j++)
+ for (i = 1; i < 256; i++)
+ VSTRING_ADDCH(test, i);
+ VSTRING_TERMINATE(test);
+
+#define DECODE(b,x,l) { \
+ if (base32_decode((b),(x),(l)) == 0) \
+ msg_panic("bad base32: %s", (x)); \
+ }
+#define VERIFY(b,t,l) { \
+ if (memcmp((b), (t), (l)) != 0) \
+ msg_panic("bad test: %s", (b)); \
+ }
+
+ /*
+ * Test all padding variants.
+ */
+ for (i = 1; i <= 8; i++) {
+ base32_encode(b1, STR(test), LEN(test));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+
+ base32_encode(b1, STR(test), LEN(test));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+
+ base32_encode(b1, STR(test), LEN(test));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+ vstring_truncate(test, LEN(test) - 1);
+ }
+ vstring_free(test);
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/base32_code.h b/src/util/base32_code.h
new file mode 100644
index 0000000..aa258fe
--- /dev/null
+++ b/src/util/base32_code.h
@@ -0,0 +1,41 @@
+#ifndef _BASE32_CODE_H_INCLUDED_
+#define _BASE32_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* base32_code 3h
+/* SUMMARY
+/* encode/decode data, base 32 style
+/* SYNOPSIS
+/* #include <base32_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *base32_encode(VSTRING *, const char *, ssize_t);
+extern VSTRING *WARN_UNUSED_RESULT base32_decode(VSTRING *, const char *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/base64_code.c b/src/util/base64_code.c
new file mode 100644
index 0000000..e607218
--- /dev/null
+++ b/src/util/base64_code.c
@@ -0,0 +1,228 @@
+/*++
+/* NAME
+/* base64_code 3
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/*
+/* VSTRING *base64_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base64_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base64_encode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/*
+/* VSTRING *base64_decode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/* DESCRIPTION
+/* base64_encode() takes a block of len bytes and encodes it as one
+/* null-terminated string. The result value is the result argument.
+/*
+/* base64_decode() performs the opposite transformation. The result
+/* value is the result argument. The result is null terminated, whether
+/* or not that makes sense.
+/*
+/* base64_encode_opt() and base64_decode_opt() provide extended
+/* interfaces. In both cases the flags arguments is the bit-wise
+/* OR of zero or more the following:
+/* .IP BASE64_FLAG_APPEND
+/* Append the result, instead of overwriting the result buffer.
+/* .PP
+/* For convenience, BASE64_FLAG_NONE specifies none of the above.
+/* DIAGNOSTICS
+/* base64_decode () returns a null pointer when the input contains
+/* characters not in the base 64 alphabet.
+/* 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 <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <base64_code.h>
+
+/* Application-specific. */
+
+static unsigned char to_b64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x))
+
+/* base64_encode - raw data to encoded */
+
+#undef base64_encode
+
+extern VSTRING *base64_encode(VSTRING *, const char *, ssize_t);
+
+VSTRING *base64_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (base64_encode_opt(result, in, len, BASE64_FLAG_NONE));
+}
+
+VSTRING *base64_encode_opt(VSTRING *result, const char *in, ssize_t len,
+ int flags)
+{
+ const unsigned char *cp;
+ ssize_t count;
+
+ /*
+ * Encode 3 -> 4.
+ */
+ if ((flags & BASE64_FLAG_APPEND) == 0)
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) {
+ VSTRING_ADDCH(result, to_b64[cp[0] >> 2]);
+ if (count > 1) {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]);
+ if (count > 2) {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]);
+ VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]);
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]);
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]);
+ VSTRING_ADDCH(result, '=');
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* base64_decode - encoded data to raw */
+
+#undef base64_decode
+
+extern VSTRING *base64_decode(VSTRING *, const char *, ssize_t);
+
+VSTRING *base64_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (base64_decode_opt(result, in, len, BASE64_FLAG_NONE));
+}
+
+VSTRING *base64_decode_opt(VSTRING *result, const char *in, ssize_t len,
+ int flags)
+{
+ static unsigned char *un_b64 = 0;
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int ch0;
+ unsigned int ch1;
+ unsigned int ch2;
+ unsigned int ch3;
+
+#define CHARS_PER_BYTE (UCHAR_MAX + 1)
+#define INVALID 0xff
+
+ /*
+ * Sanity check.
+ */
+ if (len % 4)
+ return (0);
+
+ /*
+ * Once: initialize the decoding lookup table on the fly.
+ */
+ if (un_b64 == 0) {
+ un_b64 = (unsigned char *) mymalloc(CHARS_PER_BYTE);
+ memset(un_b64, INVALID, CHARS_PER_BYTE);
+ for (cp = to_b64; cp < to_b64 + sizeof(to_b64) - 1; cp++)
+ un_b64[*cp] = cp - to_b64;
+ }
+
+ /*
+ * Decode 4 -> 3.
+ */
+ if ((flags & BASE64_FLAG_APPEND) == 0)
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) {
+ if ((ch0 = un_b64[*cp++]) == INVALID
+ || (ch1 = un_b64[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4);
+ if ((ch2 = *cp++) == '=')
+ break;
+ if ((ch2 = un_b64[ch2]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2);
+ if ((ch3 = *cp++) == '=')
+ break;
+ if ((ch3 = un_b64[ch3]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch2 << 6 | ch3);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to base 64 and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ char test[256];
+ int n;
+
+ for (n = 0; n < sizeof(test); n++)
+ test[n] = n;
+ base64_encode(b1, test, sizeof(test));
+ if (base64_decode(b2, STR(b1), LEN(b1)) == 0)
+ msg_panic("bad base64: %s", STR(b1));
+ if (LEN(b2) != sizeof(test))
+ msg_panic("bad decode length: %ld != %ld",
+ (long) LEN(b2), (long) sizeof(test));
+ for (n = 0; n < sizeof(test); n++)
+ if (STR(b2)[n] != test[n])
+ msg_panic("bad decode value %d != %d",
+ (unsigned char) STR(b2)[n], (unsigned char) test[n]);
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/base64_code.h b/src/util/base64_code.h
new file mode 100644
index 0000000..fefdf4e
--- /dev/null
+++ b/src/util/base64_code.h
@@ -0,0 +1,49 @@
+#ifndef _BASE64_CODE_H_INCLUDED_
+#define _BASE64_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* base64_code 3h
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *base64_encode_opt(VSTRING *, const char *, ssize_t, int);
+extern VSTRING *WARN_UNUSED_RESULT base64_decode_opt(VSTRING *, const char *, ssize_t, int);
+
+#define BASE64_FLAG_NONE 0
+#define BASE64_FLAG_APPEND (1<<0)
+
+#define base64_encode(bp, cp, ln) \
+ base64_encode_opt((bp), (cp), (ln), BASE64_FLAG_NONE)
+#define base64_decode(bp, cp, ln) \
+ base64_decode_opt((bp), (cp), (ln), BASE64_FLAG_NONE)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/basename.c b/src/util/basename.c
new file mode 100644
index 0000000..429e6cf
--- /dev/null
+++ b/src/util/basename.c
@@ -0,0 +1,49 @@
+/*++
+/* NAME
+/* basename 3
+/* SUMMARY
+/* extract file basename
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *basename(path)
+/* const char *path;
+/* DESCRIPTION
+/* The \fBbasename\fR routine skips over the last '/' in
+/* \fIpath\fR and returns a pointer to the result.
+/* 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>
+
+#ifndef HAVE_BASENAME
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* basename - skip directory prefix */
+
+char *basename(const char *path)
+{
+ char *result;
+
+ if ((result = strrchr(path, '/')) == 0)
+ result = (char *) path;
+ else
+ result += 1;
+ return (result);
+}
+
+#endif
diff --git a/src/util/binhash.c b/src/util/binhash.c
new file mode 100644
index 0000000..fd9bc87
--- /dev/null
+++ b/src/util/binhash.c
@@ -0,0 +1,447 @@
+/*++
+/* NAME
+/* binhash 3
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <binhash.h>
+/*
+/* typedef struct {
+/* .in +4
+/* void *key;
+/* ssize_t key_len;
+/* void *value;
+/* /* private fields... */
+/* .in -4
+/* } BINHASH_INFO;
+/*
+/* BINHASH *binhash_create(size)
+/* ssize_t size;
+/*
+/* BINHASH_INFO *binhash_enter(table, key, key_len, value)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/* void *value;
+/*
+/* char *binhash_find(table, key, key_len)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/*
+/* BINHASH_INFO *binhash_locate(table, key, key_len)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/*
+/* void binhash_delete(table, key, key_len, free_fn)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/* void (*free_fn)(void *);
+/*
+/* void binhash_free(table, free_fn)
+/* BINHASH *table;
+/* void (*free_fn)(void *);
+/*
+/* void binhash_walk(table, action, ptr)
+/* BINHASH *table;
+/* void (*action)(BINHASH_INFO *info, void *ptr);
+/* void *ptr;
+/*
+/* BINHASH_INFO **binhash_list(table)
+/* BINHASH *table;
+/*
+/* BINHASH_INFO *binhash_sequence(table, how)
+/* BINHASH *table;
+/* int how;
+/* DESCRIPTION
+/* This module maintains one or more hash tables. Each table entry
+/* consists of a unique binary-valued lookup key and a generic
+/* character-pointer value.
+/* The tables are automatically resized when they fill up. When the
+/* values to be remembered are not character pointers, proper casts
+/* should be used or the code will not be portable.
+/*
+/* binhash_create() creates a table of the specified size and returns a
+/* pointer to the result. The lookup keys are saved with mymemdup().
+/*
+/* binhash_enter() stores a (key, value) pair into the specified table
+/* and returns a pointer to the resulting entry. The code does not
+/* check if an entry with that key already exists: use binhash_locate()
+/* for updating an existing entry. The key is copied; the value is not.
+/*
+/* binhash_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use binhash_locate().
+/*
+/* binhash_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* binhash_delete() removes one entry that was stored under the given key.
+/* If the free_fn argument is not a null pointer, the corresponding
+/* function is called with as argument the value that was stored under
+/* the key.
+/*
+/* binhash_free() destroys a hash table, including contents. If the free_fn
+/* argument is not a null pointer, the corresponding function is called
+/* for each table entry, with as argument the value that was stored
+/* with the entry.
+/*
+/* binhash_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* binhash_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/*
+/* binhash_sequence() returns the first or next element
+/* depending on the value of the "how" argument. Specify
+/* BINHASH_SEQ_FIRST to start a new sequence, BINHASH_SEQ_NEXT
+/* to continue, and BINHASH_SEQ_STOP to terminate a sequence
+/* early. The caller must not delete an element before it is
+/* visited.
+/* RESTRICTIONS
+/* A callback function should not modify the hash table that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* hash_fnv(3) Fowler/Noll/Vo hash function
+/* 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
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Local stuff */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "binhash.h"
+
+/* binhash_hash - hash a string */
+
+#ifndef NO_HASH_FNV
+#include "hash_fnv.h"
+
+#define binhash_hash(key, len, size) (hash_fnv((key), (len)) % (size))
+
+#else
+
+static size_t binhash_hash(const void *key, ssize_t len, size_t size)
+{
+ size_t h = 0;
+ size_t g;
+
+ /*
+ * From the "Dragon" book by Aho, Sethi and Ullman.
+ */
+
+ while (len-- > 0) {
+ h = (h << 4U) + *(unsigned const char *) key++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= (g >> 24U);
+ h ^= g;
+ }
+ }
+ return (h % size);
+}
+
+#endif
+
+/* binhash_link - insert element into table */
+
+#define binhash_link(table, elm) { \
+ BINHASH_INFO **_h = table->data + binhash_hash(elm->key, elm->key_len, table->size);\
+ elm->prev = 0; \
+ if ((elm->next = *_h) != 0) \
+ (*_h)->prev = elm; \
+ *_h = elm; \
+ table->used++; \
+}
+
+/* binhash_size - allocate and initialize hash table */
+
+static void binhash_size(BINHASH *table, size_t size)
+{
+ BINHASH_INFO **h;
+
+ size |= 1;
+
+ table->data = h = (BINHASH_INFO **) mymalloc(size * sizeof(BINHASH_INFO *));
+ table->size = size;
+ table->used = 0;
+
+ while (size-- > 0)
+ *h++ = 0;
+}
+
+/* binhash_create - create initial hash table */
+
+BINHASH *binhash_create(ssize_t size)
+{
+ BINHASH *table;
+
+ table = (BINHASH *) mymalloc(sizeof(BINHASH));
+ binhash_size(table, size < 13 ? 13 : size);
+ table->seq_bucket = table->seq_element = 0;
+ return (table);
+}
+
+/* binhash_grow - extend existing table */
+
+static void binhash_grow(BINHASH *table)
+{
+ BINHASH_INFO *ht;
+ BINHASH_INFO *next;
+ ssize_t old_size = table->size;
+ BINHASH_INFO **h = table->data;
+ BINHASH_INFO **old_entries = h;
+
+ binhash_size(table, 2 * old_size);
+
+ while (old_size-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ binhash_link(table, ht);
+ }
+ }
+ myfree((void *) old_entries);
+}
+
+/* binhash_enter - enter (key, value) pair */
+
+BINHASH_INFO *binhash_enter(BINHASH *table, const void *key, ssize_t key_len, void *value)
+{
+ BINHASH_INFO *ht;
+
+ if (table->used >= table->size)
+ binhash_grow(table);
+ ht = (BINHASH_INFO *) mymalloc(sizeof(BINHASH_INFO));
+ ht->key = mymemdup(key, key_len);
+ ht->key_len = key_len;
+ ht->value = value;
+ binhash_link(table, ht);
+ return (ht);
+}
+
+/* binhash_find - lookup value */
+
+void *binhash_find(BINHASH *table, const void *key, ssize_t key_len)
+{
+ BINHASH_INFO *ht;
+
+#define KEY_EQ(x,y,l) (((unsigned char *) x)[0] == ((unsigned char *) y)[0] && memcmp(x,y,l) == 0)
+
+ if (table != 0)
+ for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next)
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len))
+ return (ht->value);
+ return (0);
+}
+
+/* binhash_locate - lookup entry */
+
+BINHASH_INFO *binhash_locate(BINHASH *table, const void *key, ssize_t key_len)
+{
+ BINHASH_INFO *ht;
+
+ if (table != 0)
+ for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next)
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len))
+ return (ht);
+ return (0);
+}
+
+/* binhash_delete - delete one entry */
+
+void binhash_delete(BINHASH *table, const void *key, ssize_t key_len, void (*free_fn) (void *))
+{
+ if (table != 0) {
+ BINHASH_INFO *ht;
+ BINHASH_INFO **h = table->data + binhash_hash(key, key_len, table->size);
+
+ for (ht = *h; ht; ht = ht->next) {
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) {
+ if (ht->next)
+ ht->next->prev = ht->prev;
+ if (ht->prev)
+ ht->prev->next = ht->next;
+ else
+ *h = ht->next;
+ table->used--;
+ myfree(ht->key);
+ if (free_fn)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ return;
+ }
+ }
+ msg_panic("binhash_delete: unknown_key: \"%s\"", (char *) key);
+ }
+}
+
+/* binhash_free - destroy hash table */
+
+void binhash_free(BINHASH *table, void (*free_fn) (void *))
+{
+ if (table != 0) {
+ ssize_t i = table->size;
+ BINHASH_INFO *ht;
+ BINHASH_INFO *next;
+ BINHASH_INFO **h = table->data;
+
+ while (i-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ myfree(ht->key);
+ if (free_fn)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ }
+ }
+ myfree((void *) table->data);
+ table->data = 0;
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = 0;
+ myfree((void *) table);
+ }
+}
+
+/* binhash_walk - iterate over hash table */
+
+void binhash_walk(BINHASH *table, void (*action) (BINHASH_INFO *, void *),
+ void *ptr) {
+ if (table != 0) {
+ ssize_t i = table->size;
+ BINHASH_INFO **h = table->data;
+ BINHASH_INFO *ht;
+
+ while (i-- > 0)
+ for (ht = *h++; ht; ht = ht->next)
+ (*action) (ht, ptr);
+ }
+}
+
+/* binhash_list - list all table members */
+
+BINHASH_INFO **binhash_list(table)
+BINHASH *table;
+{
+ BINHASH_INFO **list;
+ BINHASH_INFO *member;
+ ssize_t count = 0;
+ ssize_t i;
+
+ if (table != 0) {
+ list = (BINHASH_INFO **) mymalloc(sizeof(*list) * (table->used + 1));
+ for (i = 0; i < table->size; i++)
+ for (member = table->data[i]; member != 0; member = member->next)
+ list[count++] = member;
+ } else {
+ list = (BINHASH_INFO **) mymalloc(sizeof(*list));
+ }
+ list[count] = 0;
+ return (list);
+}
+
+/* binhash_sequence - dict(3) compatibility iterator */
+
+BINHASH_INFO *binhash_sequence(BINHASH *table, int how)
+{
+ if (table == 0)
+ return (0);
+
+ switch (how) {
+ case BINHASH_SEQ_FIRST: /* start new sequence */
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = binhash_list(table);
+ table->seq_element = table->seq_bucket;
+ return (*(table->seq_element)++);
+ case BINHASH_SEQ_NEXT: /* next element */
+ if (table->seq_element && *table->seq_element)
+ return (*(table->seq_element)++);
+ /* FALLTHROUGH */
+ default: /* terminate sequence */
+ if (table->seq_bucket) {
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = table->seq_element = 0;
+ }
+ return (0);
+ }
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+#include <myrand.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ ssize_t count = 0;
+ BINHASH *hash;
+ BINHASH_INFO **ht_info;
+ BINHASH_INFO **ht;
+ BINHASH_INFO *info;
+ ssize_t i;
+ ssize_t r;
+ int op;
+
+ /*
+ * Load a large number of strings including terminator, and delete them
+ * in a random order.
+ */
+ hash = binhash_create(10);
+ while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
+ binhash_enter(hash, vstring_str(buf), VSTRING_LEN(buf) + 1,
+ CAST_INT_TO_VOID_PTR(count++));
+ if (count != hash->used)
+ msg_panic("%ld entries stored, but %lu entries exist",
+ (long) count, (unsigned long) hash->used);
+ for (i = 0, op = BINHASH_SEQ_FIRST; (info = binhash_sequence(hash, op)) != 0;
+ i++, op = BINHASH_SEQ_NEXT)
+ if (memchr(info->key, 0, info->key_len) == 0)
+ msg_panic("no null byte in lookup key");
+ if (i != hash->used)
+ msg_panic("%ld entries found, but %lu entries exist",
+ (long) i, (unsigned long) hash->used);
+ ht_info = binhash_list(hash);
+ for (i = 0; i < hash->used; i++) {
+ r = myrand() % hash->used;
+ info = ht_info[i];
+ ht_info[i] = ht_info[r];
+ ht_info[r] = info;
+ }
+ for (ht = ht_info; *ht; ht++)
+ binhash_delete(hash, ht[0]->key, ht[0]->key_len, (void (*) (void *)) 0);
+ if (hash->used > 0)
+ msg_panic("%ld entries not deleted", (long) hash->used);
+ myfree((void *) ht_info);
+ binhash_free(hash, (void (*) (void *)) 0);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/binhash.h b/src/util/binhash.h
new file mode 100644
index 0000000..3b4e1a4
--- /dev/null
+++ b/src/util/binhash.h
@@ -0,0 +1,65 @@
+#ifndef _BINHASH_H_INCLUDED_
+#define _BINHASH_H_INCLUDED_
+
+/*++
+/* NAME
+/* binhash 3h
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <binhash.h>
+/* DESCRIPTION
+/* .nf
+
+ /* Structure of one hash table entry. */
+
+typedef struct BINHASH_INFO {
+ void *key; /* lookup key */
+ ssize_t key_len; /* key length */
+ void *value; /* associated value */
+ struct BINHASH_INFO *next; /* colliding entry */
+ struct BINHASH_INFO *prev; /* colliding entry */
+} BINHASH_INFO;
+
+ /* Structure of one hash table. */
+
+typedef struct BINHASH {
+ ssize_t size; /* length of entries array */
+ ssize_t used; /* number of entries in table */
+ BINHASH_INFO **data; /* entries array, auto-resized */
+ BINHASH_INFO **seq_bucket; /* current sequence hash bucket */
+ BINHASH_INFO **seq_element; /* current sequence element */
+} BINHASH;
+
+extern BINHASH *binhash_create(ssize_t);
+extern BINHASH_INFO *binhash_enter(BINHASH *, const void *, ssize_t, void *);
+extern BINHASH_INFO *binhash_locate(BINHASH *, const void *, ssize_t);
+extern void *binhash_find(BINHASH *, const void *, ssize_t);
+extern void binhash_delete(BINHASH *, const void *, ssize_t, void (*) (void *));
+extern void binhash_free(BINHASH *, void (*) (void *));
+extern void binhash_walk(BINHASH *, void (*) (BINHASH_INFO *, void *), void *);
+extern BINHASH_INFO **binhash_list(BINHASH *);
+extern BINHASH_INFO *binhash_sequence(BINHASH *, int);
+
+#define BINHASH_SEQ_FIRST 0
+#define BINHASH_SEQ_NEXT 1
+#define BINHASH_SEQ_STOP (-1)
+
+/* 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
+/* CREATION DATE
+/* Thu Feb 20 16:54:29 EST 1997
+/* LAST MODIFICATION
+/* %E% %U%
+/* VERSION/RELEASE
+/* %I%
+/*--*/
+
+#endif
diff --git a/src/util/byte_mask.c b/src/util/byte_mask.c
new file mode 100644
index 0000000..e818476
--- /dev/null
+++ b/src/util/byte_mask.c
@@ -0,0 +1,306 @@
+/*++
+/* NAME
+/* byte_mask 3
+/* SUMMARY
+/* map byte sequence to bit mask
+/* SYNOPSIS
+/* #include <byte_mask.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int byte_val;
+/* int mask;
+/* .in -4
+/* } BYTE_MASK;
+/*
+/* int byte_mask(
+/* const char *context,
+/* const BYTE_MASK *table,
+/* const char *bytes);
+/*
+/* const char *str_byte_mask(
+/* const char *context,
+/* const BYTE_MASK *table,
+/* int mask);
+/*
+/* int byte_mask_opt(
+/* const char *context;
+/* const BYTE_MASK *table,
+/* const char *bytes,
+/* int flags);
+/*
+/* const char *str_byte_mask_opt(
+/* VSTRING *buf,
+/* const char *context,
+/* const BYTE_MASK *table,
+/* int mask,
+/* int flags);
+/* DESCRIPTION
+/* byte_mask() takes a null-terminated \fItable\fR with (byte
+/* value, single-bit mask) pairs and computes the bit-wise OR
+/* of the masks that correspond to the byte values pointed to
+/* by the \fIbytes\fR argument.
+/*
+/* str_byte_mask() translates a bit mask into its equivalent
+/* bytes. The result is written to a static buffer that is
+/* overwritten upon each call.
+/*
+/* byte_mask_opt() and str_byte_mask_opt() are extended versions
+/* with additional fine control.
+/*
+/* Arguments:
+/* .IP buf
+/* Null pointer or pointer to buffer storage.
+/* .IP context
+/* What kind of byte values and bit masks are being manipulated,
+/* reported in error messages. Typically, this would be the
+/* name of a user-configurable parameter or command-line
+/* attribute.
+/* .IP table
+/* Table with (byte value, single-bit mask) pairs.
+/* .IP bytes
+/* A null-terminated string that is to be converted into a bit
+/* mask.
+/* .IP mask
+/* A bit mask that is to be converted into null-terminated
+/* string.
+/* .IP flags
+/* Bit-wise OR of one or more of the following. Where features
+/* would have conflicting results (e.g., FATAL versus IGNORE),
+/* the feature that takes precedence is described first.
+/*
+/* When converting from string to mask, at least one of the
+/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN,
+/* BYTE_MASK_WARN or BYTE_MASK_IGNORE.
+/*
+/* When converting from mask to string, at least one of the
+/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN,
+/* BYTE_MASK_WARN or BYTE_MASK_IGNORE.
+/* .RS
+/* .IP BYTE_MASK_FATAL
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Terminate with a fatal run-time error if this
+/* condition is not met. This feature is enabled by default
+/* when calling byte_mask() or str_name_mask().
+/* .IP BYTE_MASK_RETURN
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Log a warning, and return 0 (byte_mask()) or
+/* a null pointer (str_byte_mask()) if this condition is not
+/* met. This feature is not enabled by default when calling
+/* byte_mask() or str_name_mask().
+/* .IP BYTE_MASK_WARN
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Log a warning if this condition is not met,
+/* continue processing, and return all valid bits or bytes.
+/* This feature is not enabled by default when calling byte_mask()
+/* or str_byte_mask().
+/* .IP BYTE_MASK_IGNORE
+/* Silently ignore values in \fIbytes\fR that don't exist in
+/* \fItable\fR, and silently ignore bits listed in \fImask\fR
+/* that don't exist in \fItable\fR. This feature is not enabled
+/* by default when calling byte_mask() or str_byte_mask().
+/* .IP BYTE_MASK_ANY_CASE
+/* Enable case-insensitive matching. This feature is not
+/* enabled by default when calling byte_mask(); it has no
+/* effect with str_byte_mask().
+/* .RE
+/* The value BYTE_MASK_NONE explicitly requests no features,
+/* and BYTE_MASK_DEFAULT enables the default options.
+/* DIAGNOSTICS
+/* Fatal: the \fIbytes\fR argument specifies a name not found in
+/* \fItable\fR, or the \fImask\fR specifies a bit not found in
+/* \fItable\fR.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This code is a clone of Postfix name_mask(3).
+/* 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 <errno.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <byte_mask.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+
+/* byte_mask_opt - compute mask corresponding to byte string */
+
+int byte_mask_opt(const char *context, const BYTE_MASK *table,
+ const char *bytes, int flags)
+{
+ const char myname[] = "byte_mask";
+ const char *bp;
+ int result = 0;
+ const BYTE_MASK *np;
+
+ if ((flags & BYTE_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ /*
+ * Iterate over bytes string, and look up each byte in the table. If the
+ * byte is found, merge its mask with the result.
+ */
+ for (bp = bytes; *bp; bp++) {
+ int byte_val = *(const unsigned char *) bp;
+
+ for (np = table; /* void */ ; np++) {
+ if (np->byte_val == 0) {
+ if (flags & BYTE_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ } else if (flags & BYTE_MASK_RETURN) {
+ msg_warn("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ return (0);
+ } else if (flags & BYTE_MASK_WARN) {
+ msg_warn("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ }
+ break;
+ }
+ if ((flags & BYTE_MASK_ANY_CASE) ?
+ (TOLOWER(byte_val) == TOLOWER(np->byte_val)) :
+ (byte_val == np->byte_val)) {
+ if (msg_verbose)
+ msg_info("%s: %c", myname, byte_val);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+ return (result);
+}
+
+/* str_byte_mask_opt - mask to string */
+
+const char *str_byte_mask_opt(VSTRING *buf, const char *context,
+ const BYTE_MASK *table,
+ int mask, int flags)
+{
+ const char myname[] = "byte_mask";
+ const BYTE_MASK *np;
+ static VSTRING *my_buf = 0;
+
+ if ((flags & STR_BYTE_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->byte_val == 0) {
+ if (flags & BYTE_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ } else if (flags & BYTE_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ return (0);
+ } else if (flags & BYTE_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%c", np->byte_val);
+ }
+ }
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program.
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <name_mask.h>
+
+int main(int argc, char **argv)
+{
+ static const BYTE_MASK demo_table[] = {
+ '0', 1 << 0,
+ '1', 1 << 1,
+ '2', 1 << 2,
+ '3', 1 << 3,
+ 0, 0,
+ };
+ static const NAME_MASK feature_table[] = {
+ "DEFAULT", BYTE_MASK_DEFAULT,
+ "FATAL", BYTE_MASK_FATAL,
+ "ANY_CASE", BYTE_MASK_ANY_CASE,
+ "RETURN", BYTE_MASK_RETURN,
+ "WARN", BYTE_MASK_WARN,
+ "IGNORE", BYTE_MASK_IGNORE,
+ 0,
+ };
+ int in_feature_mask;
+ int out_feature_mask;
+ int demo_mask;
+ const char *demo_str;
+ VSTRING *out_buf = vstring_alloc(1);
+ VSTRING *in_buf = vstring_alloc(1);
+
+ if (argc != 3)
+ msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
+ in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
+ out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
+ while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
+ demo_mask = byte_mask_opt("name", demo_table,
+ STR(in_buf), in_feature_mask);
+ demo_str = str_byte_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("%s -> 0x%x -> %s\n",
+ STR(in_buf), demo_mask,
+ demo_str ? demo_str : "(null)");
+ demo_mask <<=1;
+ demo_str = str_byte_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("0x%x -> %s\n",
+ demo_mask, demo_str ? demo_str : "(null)");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in_buf);
+ vstring_free(out_buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/byte_mask.h b/src/util/byte_mask.h
new file mode 100644
index 0000000..6cdea1d
--- /dev/null
+++ b/src/util/byte_mask.h
@@ -0,0 +1,64 @@
+#ifndef _BYTE_MASK_H_INCLUDED_
+#define _BYTE_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* byte_mask 3h
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <byte_mask.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int byte_val;
+ int mask;
+} BYTE_MASK;
+
+#define BYTE_MASK_FATAL (1<<0)
+#define BYTE_MASK_ANY_CASE (1<<1)
+#define BYTE_MASK_RETURN (1<<2)
+#define BYTE_MASK_WARN (1<<6)
+#define BYTE_MASK_IGNORE (1<<7)
+
+#define BYTE_MASK_REQUIRED \
+ (BYTE_MASK_FATAL | BYTE_MASK_RETURN | BYTE_MASK_WARN | BYTE_MASK_IGNORE)
+#define STR_BYTE_MASK_REQUIRED (BYTE_MASK_REQUIRED)
+
+#define BYTE_MASK_NONE 0
+#define BYTE_MASK_DEFAULT (BYTE_MASK_FATAL)
+
+#define byte_mask(tag, table, str) \
+ byte_mask_opt((tag), (table), (str), BYTE_MASK_DEFAULT)
+#define str_byte_mask(tag, table, mask) \
+ str_byte_mask_opt(((VSTRING *) 0), (tag), (table), (mask), BYTE_MASK_DEFAULT)
+
+extern int byte_mask_opt(const char *, const BYTE_MASK *, const char *, int);
+extern const char *str_byte_mask_opt(VSTRING *, const char *, const BYTE_MASK *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/byte_mask.in b/src/util/byte_mask.in
new file mode 100644
index 0000000..6d89c5a
--- /dev/null
+++ b/src/util/byte_mask.in
@@ -0,0 +1,7 @@
+0123
+0
+1
+2
+3
+1234
+abcd
diff --git a/src/util/byte_mask.ref0 b/src/util/byte_mask.ref0
new file mode 100644
index 0000000..a6ab11b
--- /dev/null
+++ b/src/util/byte_mask.ref0
@@ -0,0 +1,14 @@
+0123 -> 0xf -> 0123
+0x1e -> 123
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+3 -> 0x8 -> 3
+0x10 ->
+1234 -> 0xe -> 123
+0x1c -> 23
+abcd -> 0x0 ->
+0x0 ->
diff --git a/src/util/byte_mask.ref1 b/src/util/byte_mask.ref1
new file mode 100644
index 0000000..92e3f4f
--- /dev/null
+++ b/src/util/byte_mask.ref1
@@ -0,0 +1,22 @@
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+0123 -> 0xf -> 0123
+0x1e -> 123
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+3 -> 0x8 -> 3
+0x10 ->
+unknown: warning: unknown name value "4" in "1234"
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+1234 -> 0xe -> 123
+0x1c -> 23
+unknown: warning: unknown name value "a" in "abcd"
+unknown: warning: unknown name value "b" in "abcd"
+unknown: warning: unknown name value "c" in "abcd"
+unknown: warning: unknown name value "d" in "abcd"
+abcd -> 0x0 ->
+0x0 ->
diff --git a/src/util/byte_mask.ref2 b/src/util/byte_mask.ref2
new file mode 100644
index 0000000..631ec53
--- /dev/null
+++ b/src/util/byte_mask.ref2
@@ -0,0 +1,10 @@
+unknown: fatal: byte_mask: unknown mask bit in mask: 0x10
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+unknown: fatal: byte_mask: unknown mask bit in mask: 0x10
+unknown: fatal: unknown name value "4" in "1234"
+unknown: fatal: unknown name value "a" in "abcd"
diff --git a/src/util/cache.in b/src/util/cache.in
new file mode 100644
index 0000000..89974bd
--- /dev/null
+++ b/src/util/cache.in
@@ -0,0 +1,26 @@
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+e
+d
+c
+b
+a
+1
+b
+c
+d
+e
+f
+6
+f
diff --git a/src/util/casefold.c b/src/util/casefold.c
new file mode 100644
index 0000000..d3ebd4b
--- /dev/null
+++ b/src/util/casefold.c
@@ -0,0 +1,359 @@
+/*++
+/* NAME
+/* casefold 3
+/* SUMMARY
+/* casefold text for caseless comparison
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *casefold(
+/* VSTRING *dst,
+/* const char *src)
+/*
+/* char *casefold_append(
+/* VSTRING *dst,
+/* const char *src)
+/*
+/* char *casefold_len(
+/* VSTRING *dst,
+/* const char *src,
+/* ssize_t src_len)
+/* AUXILIARY FUNCTIONS
+/* char *casefoldx(
+/* int flags,
+/* VSTRING *dst,
+/* const char *src,
+/* ssize_t src_len)
+/* DESCRIPTION
+/* casefold() converts text to a form that is suitable for
+/* caseless comparison, rather than presentation to humans.
+/*
+/* When compiled without EAI support or util_utf8_enable is
+/* zero, casefold() implements ASCII case folding, leaving
+/* non-ASCII byte values unchanged.
+/*
+/* When compiled with EAI support and util_utf8_enable is
+/* non-zero, casefold() implements UTF-8 case folding using
+/* the en_US locale, as recommended when the conversion result
+/* is not meant to be presented to humans.
+/*
+/* casefold_len() implements casefold() with a source length
+/* argument.
+/*
+/* casefold_append() implements casefold() without overwriting
+/* the result.
+/*
+/* casefoldx() implements a more complex API that implements
+/* all of the above and more.
+/*
+/* Arguments:
+/* .IP src
+/* Null-terminated input string.
+/* .IP dest
+/* Output buffer, null-terminated. Specify a null pointer to
+/* use an internal buffer that is overwritten upon each call.
+/* .IP src_len
+/* The string length, -1 to determine the length dynamically.
+/* .IP flags
+/* Bitwise OR of zero or more of the following:
+/* .RS
+/* .IP CASEF_FLAG_UTF8
+/* Enable UTF-8 support. This flag has no effect when compiled
+/* without EAI support.
+/* .IP CASEF_FLAG_APPEND
+/* Append the result to the buffer, instead of overwriting it.
+/* DIAGNOSTICS
+/* All errors are fatal. There appear to be no input-dependent
+/* errors.
+/*
+/* With the ICU 4.8 library, there is no casefold error for
+/* UTF-8 code points U+0000..U+10FFFF (including surrogate
+/* range), not even when running inside an empty chroot jail.
+/* Nor does malformed UTF-8 trigger errors; non-UTF-8 bytes
+/* are copied verbatim. Based on ICU 4.8 source-code review
+/* and experimentation(!) we conclude that UTF-8 casefolding
+/* has no data-dependent error cases, and that it is safe to
+/* treat all casefolding errors as fatal runtime errors.
+/* 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 <ctype.h>
+#ifndef NO_EAI
+#include <unicode/ucasemap.h>
+#include <unicode/ustring.h>
+#include <unicode/uchar.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* casefoldx - casefold an UTF-8 string */
+
+char *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len)
+{
+ size_t old_len;
+
+#ifdef NO_EAI
+
+ /*
+ * ASCII mode only.
+ */
+ if (len < 0)
+ len = strlen(src);
+ if ((flags & CASEF_FLAG_APPEND) == 0)
+ VSTRING_RESET(dest);
+ old_len = VSTRING_LEN(dest);
+ vstring_strncat(dest, src, len);
+ lowercase(STR(dest) + old_len);
+ return (STR(dest));
+#else
+
+ /*
+ * Unicode mode.
+ */
+ const char myname[] = "casefold";
+ static VSTRING *fold_buf = 0;
+ static UCaseMap *csm = 0;
+ UErrorCode error;
+ ssize_t space_needed;
+ int n;
+
+ /*
+ * Handle special cases.
+ */
+ if (len < 0)
+ len = strlen(src);
+ if (dest == 0)
+ dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100)));
+ if ((flags & CASEF_FLAG_APPEND) == 0)
+ VSTRING_RESET(dest);
+ old_len = VSTRING_LEN(dest);
+
+ /*
+ * All-ASCII input, or ASCII mode only.
+ */
+ if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) {
+ vstring_strncat(dest, src, len);
+ lowercase(STR(dest) + old_len);
+ return (STR(dest));
+ }
+
+ /*
+ * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax
+ * errors. XXX Based on source-code review we conclude that non-UTF-8
+ * bytes are copied verbatim, and experiments confirm this. Given that
+ * this behavior is intentional, we assume that it will stay that way.
+ */
+#if 0
+ if (valid_utf8_string(src, len) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+#endif
+
+ /*
+ * One-time initialization. With ICU 4.8 this works while chrooted.
+ */
+ if (csm == 0) {
+ error = U_ZERO_ERROR;
+ csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error);
+ if (U_SUCCESS(error) == 0)
+ msg_fatal("ucasemap_open error: %s", u_errorName(error));
+ }
+
+ /*
+ * Fold the input, adjusting the buffer size if needed. Safety: don't
+ * loop forever.
+ *
+ * Note: the requested amount of space for casemapped output (as reported
+ * with space_needed below) does not include storage for the null
+ * terminator. The terminator is written only when the output buffer is
+ * large enough. This is why we overallocate space when the output does
+ * not fit. But if the output fits exactly, then the output will be
+ * unterminated, and we have to terminate the output ourselves.
+ */
+ for (n = 0; n < 3; n++) {
+ error = U_ZERO_ERROR;
+ space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len,
+ vstring_avail(dest), src, len, &error);
+ if (U_SUCCESS(error)) {
+ vstring_set_payload_size(dest, old_len + space_needed);
+ if (vstring_avail(dest) == 0) /* exact fit, no terminator */
+ VSTRING_TERMINATE(dest); /* add terminator */
+ break;
+ } else if (error == U_BUFFER_OVERFLOW_ERROR) {
+ VSTRING_SPACE(dest, space_needed + 1); /* for terminator */
+ } else {
+ msg_fatal("%s: conversion error for \"%s\": %s",
+ myname, src, u_errorName(error));
+ }
+ }
+ return (STR(dest));
+#endif /* NO_EAI */
+}
+
+#ifdef TEST
+
+static void encode_utf8(VSTRING *buffer, int codepoint)
+{
+ const char myname[] = "encode_utf8";
+
+ VSTRING_RESET(buffer);
+ if (codepoint < 0x80) {
+ VSTRING_ADDCH(buffer, codepoint);
+ } else if (codepoint < 0x800) {
+ VSTRING_ADDCH(buffer, 0xc0 | (codepoint >> 6));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else if (codepoint < 0x10000) {
+ VSTRING_ADDCH(buffer, 0xe0 | (codepoint >> 12));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else if (codepoint <= 0x10FFFF) {
+ VSTRING_ADDCH(buffer, 0xf0 | (codepoint >> 18));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 12) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else {
+ msg_panic("%s: out-of-range codepoint U+%X", myname, codepoint);
+ }
+ VSTRING_TERMINATE(buffer);
+}
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <locale.h>
+
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ VSTRING *dest = vstring_alloc(1);
+ char *bp;
+ char *conv_res;
+ char *cmd;
+ int codepoint, first, last;
+ VSTREAM *fp;
+
+ if (setlocale(LC_ALL, "C") == 0)
+ msg_fatal("setlocale(LC_ALL, C) failed: %m");
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ util_utf8_enable = 1;
+
+ VSTRING_SPACE(buffer, 256); /* chroot/file pathname */
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+ vstream_printf("> %s\n", bp);
+ cmd = mystrtok(&bp, CHARS_SPACE);
+ if (cmd == 0 || *cmd == '#')
+ continue;
+ while (ISSPACE(*bp))
+ bp++;
+
+ /*
+ * Null-terminated string.
+ */
+ if (strcmp(cmd, "fold") == 0) {
+ conv_res = casefold(dest, bp);
+ vstream_printf("\"%s\" ->fold \"%s\"\n", bp, conv_res);
+ }
+
+ /*
+ * Codepoint range.
+ */
+ else if (strcmp(cmd, "range") == 0
+ && sscanf(bp, "%i %i", &first, &last) == 2
+ && first <= last) {
+ for (codepoint = first; codepoint <= last; codepoint++) {
+ if (codepoint >= 0xD800 && codepoint <= 0xDFFF) {
+ vstream_printf("skipping surrogate range\n");
+ codepoint = 0xDFFF;
+ } else {
+ encode_utf8(buffer, codepoint);
+ if (msg_verbose)
+ vstream_printf("U+%X -> %s\n", codepoint, STR(buffer));
+ if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0)
+ msg_fatal("bad utf-8 encoding for U+%X\n", codepoint);
+ casefold(dest, STR(buffer));
+ }
+ }
+ vstream_printf("range completed: 0x%x..0x%x\n", first, last);
+ }
+
+ /*
+ * Chroot directory.
+ */
+ else if (strcmp(cmd, "chroot") == 0
+ && sscanf(bp, "%255s", STR(buffer)) == 1) {
+ if (geteuid() == 0) {
+ if (chdir(STR(buffer)) < 0)
+ msg_fatal("chdir(%s): %m", STR(buffer));
+ if (chroot(STR(buffer)) < 0)
+ msg_fatal("chroot(%s): %m", STR(buffer));
+ vstream_printf("chroot %s completed\n", STR(buffer));
+ }
+ }
+
+ /*
+ * File.
+ */
+ else if (strcmp(cmd, "file") == 0
+ && sscanf(bp, "%255s", STR(buffer)) == 1) {
+ if ((fp = vstream_fopen(STR(buffer), O_RDONLY, 0)) == 0)
+ msg_fatal("open(%s): %m", STR(buffer));
+ while (vstring_fgets_nonl(buffer, fp))
+ vstream_printf("%s\n", casefold(dest, STR(buffer)));
+ vstream_fclose(fp);
+ }
+
+ /*
+ * Verbose.
+ */
+ else if (strcmp(cmd, "verbose") == 0
+ && sscanf(bp, "%i", &msg_verbose) == 1) {
+ /* void */ ;
+ }
+
+ /*
+ * Usage
+ */
+ else {
+ vstream_printf("Usage: %s chroot <path> | file <path> | fold <text> | range <first> <last> | verbose <int>\n",
+ argv[0]);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buffer);
+ vstring_free(dest);
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/src/util/casefold_test.in b/src/util/casefold_test.in
new file mode 100644
index 0000000..c1a530e
--- /dev/null
+++ b/src/util/casefold_test.in
@@ -0,0 +1,24 @@
+# Ignored when not running as root.
+chroot /tmp
+# Casefold U+0000 .. U+10FFFF excluding surrogates.
+range 0x0 0xD7FF
+range 0xD800 0xD800
+range 0xDFFF 0xDFFF
+range 0xE000 0x10FFFF
+# Demonstrate that range is not a noop.
+verbose 1
+range 0xE000 0xE007
+verbose 0
+# Upper-case greek -> lower-case greek.
+fold Δημοσθένους.example.com
+# Exact-fit null termination test.
+fold Δημοσθένους.exxample.com
+# Upper-case ASCII -> lower-case ASCII.
+fold HeLlO.ExAmPlE.CoM
+# Folding does not change aliases for '.'.
+fold x。example.com
+fold x.example.com
+fold x。example.com
+# Bad UTF-8
+fold YYY€€€
+fold €€€XXX
diff --git a/src/util/casefold_test.ref b/src/util/casefold_test.ref
new file mode 100644
index 0000000..639fe06
--- /dev/null
+++ b/src/util/casefold_test.ref
@@ -0,0 +1,47 @@
+> # Ignored when not running as root.
+> chroot /tmp
+> # Casefold U+0000 .. U+10FFFF excluding surrogates.
+> range 0x0 0xD7FF
+range completed: 0x0..0xd7ff
+> range 0xD800 0xD800
+skipping surrogate range
+range completed: 0xd800..0xd800
+> range 0xDFFF 0xDFFF
+skipping surrogate range
+range completed: 0xdfff..0xdfff
+> range 0xE000 0x10FFFF
+range completed: 0xe000..0x10ffff
+> # Demonstrate that range is not a noop.
+> verbose 1
+> range 0xE000 0xE007
+U+E000 -> 
+U+E001 -> î€
+U+E002 -> 
+U+E003 -> 
+U+E004 -> 
+U+E005 -> 
+U+E006 -> 
+U+E007 -> 
+range completed: 0xe000..0xe007
+> verbose 0
+> # Upper-case greek -> lower-case greek.
+> fold Δημοσθένους.example.com
+"Δημοσθένους.example.com" ->fold "δημοσθένουσ.example.com"
+> # Exact-fit null termination test.
+> fold Δημοσθένους.exxample.com
+"Δημοσθένους.exxample.com" ->fold "δημοσθένουσ.exxample.com"
+> # Upper-case ASCII -> lower-case ASCII.
+> fold HeLlO.ExAmPlE.CoM
+"HeLlO.ExAmPlE.CoM" ->fold "hello.example.com"
+> # Folding does not change aliases for '.'.
+> fold x。example.com
+"x。example.com" ->fold "x。example.com"
+> fold x.example.com
+"x.example.com" ->fold "x.example.com"
+> fold x。example.com
+"x。example.com" ->fold "x。example.com"
+> # Bad UTF-8
+> fold YYY€€€
+"YYY€€€" ->fold "yyy€€€"
+> fold €€€XXX
+"€€€XXX" ->fold "€€€xxx"
diff --git a/src/util/check_arg.h b/src/util/check_arg.h
new file mode 100644
index 0000000..09f0932
--- /dev/null
+++ b/src/util/check_arg.h
@@ -0,0 +1,157 @@
+#ifndef _CHECK_ARG_INCLUDED_
+#define _CHECK_ARG_INCLUDED_
+
+/*++
+/* NAME
+/* check_arg 3h
+/* SUMMARY
+/* type checking/narrowing/widening for unprototyped arguments
+/* SYNOPSIS
+/* #include <check_arg.h>
+/*
+/* /* Example checking infrastructure for int, int *, const int *. */
+/* CHECK_VAL_HELPER_DCL(tag, int);
+/* CHECK_PTR_HELPER_DCL(tag, int);
+/* CHECK_CPTR_HELPER_DCL(tag, int);
+/*
+/* /* Example variables with type int, int *, const int *. */
+/* int int_val;
+/* int *int_ptr;
+/* const int *int_cptr;
+/*
+/* /* Example variadic function with type-flag arguments. */
+/* func(FLAG_INT_VAL, CHECK_VAL(tag, int, int_val),
+/* FLAG_INT_PTR, CHECK_PTR(tag, int, int_ptr),
+/* FLAG_INT_CPTR, CHECK_CPTR(tag, int, int_cptr)
+/* FLAG_END);
+/* DESCRIPTION
+/* This module implements wrappers for unprototyped function
+/* arguments, to enable the same type checking, type narrowing,
+/* and type widening as for prototyped function arguments. The
+/* wrappers may also be useful in other contexts.
+/*
+/* Typically, these wrappers are hidden away in a per-module
+/* header file that is read by the consumers of that module.
+/* To protect consumers against name collisions between wrappers
+/* in different header files, wrappers should be called with
+/* a distinct per-module tag value. The tag syntax is that
+/* of a C identifier.
+/*
+/* Use CHECK_VAL(tag, type, argument) for arguments with a
+/* basic type: int, long, etc., and types defined with "typedef"
+/* where indirection is built into the type itself (for example,
+/* the result of "typedef int *foo" or function pointer
+/* typedefs).
+/*
+/* Use CHECK_PTR(tag, type, argument) for non-const pointer
+/* arguments, CHECK_CPTR(tag, type, argument) for const pointer
+/* arguments, and CHECK_PPTR(tag, type, argument) for pointer-
+/* to-pointer arguments.
+/*
+/* Use CHECK_*_HELPER_DCL(tag, type) to provide the
+/* checking infrastructure for all CHECK_*(tag, type, ...)
+/* instances with the same *, tag and type. Depending on
+/* the compilation environment, the infrastructure consists
+/* of an inline function definition or a dummy assignment
+/* target declaration.
+/*
+/* The compiler should report the following problems:
+/* .IP \(bu
+/* Const pointer argument where a non-const pointer is expected.
+/* .IP \(bu
+/* Pointer argument where a non-pointer is expected and
+/* vice-versa.
+/* .IP \(bu
+/* Pointer/pointer type mismatches except void/non-void pointers.
+/* Just like function prototypes, all CHECK_*PTR() wrappers
+/* cast their result to the desired type.
+/* .IP \(bu
+/* Non-constant non-pointer argument where a pointer is expected.
+/*. PP
+/* Just like function prototypes, the CHECK_*PTR() wrappers
+/* handle "bare" numerical constants by casting their argument
+/* to the desired pointer type.
+/*
+/* Just like function prototypes, the CHECK_VAL() wrapper
+/* cannot force the caller to specify a particular non-pointer
+/* type and casts its argument value to the desired type which
+/* may wider or narrower than the argument value.
+/* IMPLEMENTATION
+
+ /*
+ * Choose between an implementation based on inline functions (standardized
+ * with C99) or conditional assignment (portable to older compilers, with
+ * some caveats as discussed below).
+ */
+#ifndef NO_INLINE
+
+ /*
+ * Parameter checks expand into inline helper function calls.
+ */
+#define CHECK_VAL(tag, type, v) check_val_##tag##type(v)
+#define CHECK_PTR(tag, type, v) check_ptr_##tag##type(v)
+#define CHECK_CPTR(tag, type, v) check_cptr_##tag##type(v)
+#define CHECK_PPTR(tag, type, v) check_pptr_##tag##type(v)
+
+ /*
+ * Macros to instantiate the inline helper functions.
+ */
+#define CHECK_VAL_HELPER_DCL(tag, type) \
+ static inline type check_val_##tag##type(type v) { return v; }
+#define CHECK_PTR_HELPER_DCL(tag, type) \
+ static inline type *check_ptr_##tag##type(type *v) { return v; }
+#define CHECK_CPTR_HELPER_DCL(tag, type) \
+ static inline const type *check_cptr_##tag##type(const type *v) \
+ { return v; }
+#define CHECK_PPTR_HELPER_DCL(tag, type) \
+ static inline type **check_pptr_##tag##type(type **v) { return v; }
+
+#else /* NO_INLINE */
+
+ /*
+ * Parameter checks expand into unreachable conditional assignments.
+ * Inspired by OpenSSL's verified pointer check, our implementation also
+ * detects const/non-const pointer conflicts, and it also supports
+ * non-pointer expressions.
+ */
+#define CHECK_VAL(tag, type, v) ((type) (1 ? (v) : (CHECK_VAL_DUMMY(type) = (v))))
+#define CHECK_PTR(tag, type, v) ((type *) (1 ? (v) : (CHECK_PTR_DUMMY(type) = (v))))
+#define CHECK_CPTR(tag, type, v) \
+ ((const type *) (1 ? (v) : (CHECK_CPTR_DUMMY(type) = (v))))
+#define CHECK_PPTR(tag, type, v) ((type **) (1 ? (v) : (CHECK_PPTR_DUMMY(type) = (v))))
+
+ /*
+ * These macros instantiate assignment target declarations. Since the
+ * assignment is made in unreachable code, the compiler "should" not emit
+ * any references to those assignment targets. We use the "extern" class so
+ * that gcc will not complain about unused variables. Using "extern" breaks
+ * when a compiler does emit references to unreachable assignment targets.
+ * Hopefully, those cases will be rare.
+ */
+#define CHECK_VAL_HELPER_DCL(tag, type) extern type CHECK_VAL_DUMMY(type)
+#define CHECK_PTR_HELPER_DCL(tag, type) extern type *CHECK_PTR_DUMMY(type)
+#define CHECK_CPTR_HELPER_DCL(tag, type) extern const type *CHECK_CPTR_DUMMY(type)
+#define CHECK_PPTR_HELPER_DCL(tag, type) extern type **CHECK_PPTR_DUMMY(type)
+
+ /*
+ * The actual dummy assignment target names.
+ */
+#define CHECK_VAL_DUMMY(type) check_val_dummy_##type
+#define CHECK_PTR_DUMMY(type) check_ptr_dummy_##type
+#define CHECK_CPTR_DUMMY(type) check_cptr_dummy_##type
+#define CHECK_PPTR_DUMMY(type) check_pptr_dummy_##type
+
+#endif /* NO_INLINE */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/chroot_uid.c b/src/util/chroot_uid.c
new file mode 100644
index 0000000..4a7660f
--- /dev/null
+++ b/src/util/chroot_uid.c
@@ -0,0 +1,88 @@
+/*++
+/* NAME
+/* chroot_uid 3
+/* SUMMARY
+/* limit possible damage a process can do
+/* SYNOPSIS
+/* #include <chroot_uid.h>
+/*
+/* void chroot_uid(root_dir, user_name)
+/* const char *root_dir;
+/* const char *user_name;
+/* DESCRIPTION
+/* \fBchroot_uid\fR changes the process root to \fIroot_dir\fR and
+/* changes process privileges to those of \fIuser_name\fR.
+/* DIAGNOSTICS
+/* System call errors are reported via the msg(3) interface.
+/* All errors are fatal.
+/* 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 <pwd.h>
+#include <unistd.h>
+#include <grp.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "chroot_uid.h"
+
+/* chroot_uid - restrict the damage that this program can do */
+
+void chroot_uid(const char *root_dir, const char *user_name)
+{
+ struct passwd *pwd;
+ uid_t uid;
+ gid_t gid;
+
+ /*
+ * Look up the uid/gid before entering the jail, and save them so they
+ * can't be clobbered. Set up the primary and secondary groups.
+ */
+ if (user_name != 0) {
+ if ((pwd = getpwnam(user_name)) == 0)
+ msg_fatal("unknown user: %s", user_name);
+ uid = pwd->pw_uid;
+ gid = pwd->pw_gid;
+ if (setgid(gid) < 0)
+ msg_fatal("setgid(%ld): %m", (long) gid);
+ if (initgroups(user_name, gid) < 0)
+ msg_fatal("initgroups: %m");
+ }
+
+ /*
+ * Enter the jail.
+ */
+ if (root_dir) {
+ if (chroot(root_dir))
+ msg_fatal("chroot(%s): %m", root_dir);
+ if (chdir("/"))
+ msg_fatal("chdir(/): %m");
+ }
+
+ /*
+ * Drop the user privileges.
+ */
+ if (user_name != 0)
+ if (setuid(uid) < 0)
+ msg_fatal("setuid(%ld): %m", (long) uid);
+
+ /*
+ * Give the desperate developer a clue of what is happening.
+ */
+ if (msg_verbose > 1)
+ msg_info("chroot %s user %s",
+ root_dir ? root_dir : "(none)",
+ user_name ? user_name : "(none)");
+}
diff --git a/src/util/chroot_uid.h b/src/util/chroot_uid.h
new file mode 100644
index 0000000..f2a8399
--- /dev/null
+++ b/src/util/chroot_uid.h
@@ -0,0 +1,29 @@
+#ifndef _CHROOT_UID_H_INCLUDED_
+#define _CHROOT_UID_H_INCLUDED_
+
+/*++
+/* NAME
+/* chroot_uid 3h
+/* SUMMARY
+/* limit possible damage a process can do
+/* SYNOPSIS
+/* #include <chroot_uid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void chroot_uid(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/cidr_match.c b/src/util/cidr_match.c
new file mode 100644
index 0000000..0ae7c56
--- /dev/null
+++ b/src/util/cidr_match.c
@@ -0,0 +1,316 @@
+/*++
+/* NAME
+/* cidr_match 3
+/* SUMMARY
+/* CIDR-style pattern matching
+/* SYNOPSIS
+/* #include <cidr_match.h>
+/*
+/* VSTRING *cidr_match_parse(info, pattern, match, why)
+/* CIDR_MATCH *info;
+/* char *pattern;
+/* VSTRING *why;
+/*
+/* int cidr_match_execute(info, address)
+/* CIDR_MATCH *info;
+/* const char *address;
+/* AUXILIARY FUNCTIONS
+/* VSTRING *cidr_match_parse_if(info, pattern, match, why)
+/* CIDR_MATCH *info;
+/* char *pattern;
+/* VSTRING *why;
+/*
+/* void cidr_match_endif(info)
+/* CIDR_MATCH *info;
+/* DESCRIPTION
+/* This module parses address or address/length patterns and
+/* provides simple address matching. The implementation is
+/* such that parsing and execution can be done without dynamic
+/* memory allocation. The purpose is to minimize overhead when
+/* called by functions that parse and execute on the fly, such
+/* as match_hostaddr().
+/*
+/* cidr_match_parse() parses an address or address/mask
+/* expression and stores the result into the info argument.
+/* A non-zero (or zero) match argument requests a positive (or
+/* negative) match. The symbolic constants CIDR_MATCH_TRUE and
+/* CIDR_MATCH_FALSE may help to improve code readability.
+/* The result is non-zero in case of problems: either the
+/* value of the why argument, or a newly allocated VSTRING
+/* (the caller should give the latter to vstring_free()).
+/* The pattern argument is destroyed.
+/*
+/* cidr_match_parse_if() parses the address that follows an IF
+/* token, and stores the result into the info argument.
+/* The arguments are the same as for cidr_match_parse().
+/*
+/* cidr_match_endif() handles the occurrence of an ENDIF token,
+/* and updates the info argument.
+/*
+/* cidr_match_execute() matches the specified address against
+/* a list of parsed expressions, and returns the matching
+/* expression's data structure.
+/* SEE ALSO
+/* dict_cidr(3) CIDR-style lookup table
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <myaddrinfo.h>
+#include <mask_addr.h>
+#include <cidr_match.h>
+
+/* Application-specific. */
+
+ /*
+ * This is how we figure out the address family, address bit count and
+ * address byte count for a CIDR_MATCH entry.
+ */
+#ifdef HAS_IPV6
+#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET)
+#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
+ ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \
+ (f) == AF_INET ? MAI_V4ADDR_BITS : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
+ ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \
+ (f) == AF_INET ? MAI_V4ADDR_BYTES : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#else
+#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET)
+#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
+ ((f) == AF_INET ? MAI_V4ADDR_BITS : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
+ ((f) == AF_INET ? MAI_V4ADDR_BYTES : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#endif
+
+/* cidr_match_entry - match one entry */
+
+static inline int cidr_match_entry(CIDR_MATCH *entry,
+ unsigned char *addr_bytes)
+{
+ unsigned char *mp;
+ unsigned char *np;
+ unsigned char *ap;
+
+ /* Unoptimized case: netmask with some or all bits zero. */
+ if (entry->mask_shift < entry->addr_bit_count) {
+ for (np = entry->net_bytes, mp = entry->mask_bytes,
+ ap = addr_bytes; /* void */ ; np++, mp++, ap++) {
+ if (ap >= addr_bytes + entry->addr_byte_count)
+ return (entry->match);
+ if ((*ap & *mp) != *np)
+ break;
+ }
+ }
+ /* Optimized case: all 1 netmask (i.e. no netmask specified). */
+ else {
+ for (np = entry->net_bytes,
+ ap = addr_bytes; /* void */ ; np++, ap++) {
+ if (ap >= addr_bytes + entry->addr_byte_count)
+ return (entry->match);
+ if (*ap != *np)
+ break;
+ }
+ }
+ return (!entry->match);
+}
+
+/* cidr_match_execute - match address against compiled CIDR pattern list */
+
+CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr)
+{
+ unsigned char addr_bytes[CIDR_MATCH_ABYTES];
+ unsigned addr_family;
+ CIDR_MATCH *entry;
+
+ addr_family = CIDR_MATCH_ADDR_FAMILY(addr);
+ if (inet_pton(addr_family, addr, addr_bytes) != 1)
+ return (0);
+
+ for (entry = list; entry; entry = entry->next) {
+
+ switch (entry->op) {
+
+ case CIDR_MATCH_OP_MATCH:
+ if (entry->addr_family == addr_family)
+ if (cidr_match_entry(entry, addr_bytes))
+ return (entry);
+ break;
+
+ case CIDR_MATCH_OP_IF:
+ if (entry->addr_family == addr_family)
+ if (cidr_match_entry(entry, addr_bytes))
+ continue;
+ /* An IF without matching ENDIF has no end-of block entry. */
+ if ((entry = entry->block_end) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ case CIDR_MATCH_OP_ENDIF:
+ continue;
+ }
+ }
+ return (0);
+}
+
+/* cidr_match_parse - parse CIDR pattern */
+
+VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match,
+ VSTRING *why)
+{
+ const char *myname = "cidr_match_parse";
+ char *mask_search;
+ char *mask;
+ MAI_HOSTADDR_STR hostaddr;
+ unsigned char *np;
+ unsigned char *mp;
+
+ /*
+ * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR
+ * maps don't need [] to eliminate syntax ambiguity, but matchlists need
+ * it. While stripping [], figure out where we should start looking for
+ * /mask information.
+ */
+ if (*pattern == '[') {
+ pattern++;
+ if ((mask_search = split_at(pattern, ']')) == 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "missing ']' character after \"[%s\"", pattern);
+ return (why);
+ } else if (*mask_search != '/') {
+ if (*mask_search != 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "garbage after \"[%s]\"", pattern);
+ return (why);
+ }
+ mask_search = pattern;
+ }
+ } else
+ mask_search = pattern;
+
+ /*
+ * Parse the pattern into network and mask, destroying the pattern.
+ */
+ if ((mask = split_at(mask_search, '/')) != 0) {
+ const char *parse_error;
+
+ ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
+ ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
+ ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
+ if (!alldig(mask)) {
+ parse_error = "bad mask value";
+ } else if ((ip->mask_shift = atoi(mask)) > ip->addr_bit_count) {
+ parse_error = "bad mask length";
+ } else if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
+ parse_error = "bad network value";
+ } else {
+ parse_error = 0;
+ }
+ if (parse_error != 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "%s in \"%s/%s\"", parse_error, pattern, mask);
+ return (why);
+ }
+ if (ip->mask_shift > 0) {
+ /* Allow for bytes > 8. */
+ memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
+ mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift);
+ } else
+ memset(ip->mask_bytes, 0, ip->addr_byte_count);
+
+ /*
+ * Sanity check: all host address bits must be zero.
+ */
+ for (np = ip->net_bytes, mp = ip->mask_bytes;
+ np < ip->net_bytes + ip->addr_byte_count; np++, mp++) {
+ if (*np & ~(*mp)) {
+ mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift);
+ if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf,
+ sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "non-null host address bits in \"%s/%s\", "
+ "perhaps you should use \"%s/%d\" instead",
+ pattern, mask, hostaddr.buf, ip->mask_shift);
+ return (why);
+ }
+ }
+ }
+
+ /*
+ * No /mask specified. Treat a bare network address as /allbits.
+ */
+ else {
+ ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
+ ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
+ ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
+ if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "bad address pattern: \"%s\"", pattern);
+ return (why);
+ }
+ ip->mask_shift = ip->addr_bit_count;
+ /* Allow for bytes > 8. */
+ memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
+ }
+
+ /*
+ * Wrap up the result.
+ */
+ ip->op = CIDR_MATCH_OP_MATCH;
+ ip->match = match;
+ ip->next = 0;
+ ip->block_end = 0;
+
+ return (0);
+}
+
+/* cidr_match_parse_if - parse CIDR pattern after IF */
+
+VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match,
+ VSTRING *why)
+{
+ VSTRING *ret;
+
+ if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0)
+ ip->op = CIDR_MATCH_OP_IF;
+ return (ret);
+}
+
+/* cidr_match_endif - handle ENDIF pattern */
+
+void cidr_match_endif(CIDR_MATCH *ip)
+{
+ memset(ip, 0, sizeof(*ip));
+ ip->op = CIDR_MATCH_OP_ENDIF;
+ ip->next = 0; /* maybe not all bits 0 */
+ ip->block_end = 0;
+}
diff --git a/src/util/cidr_match.h b/src/util/cidr_match.h
new file mode 100644
index 0000000..22f16a0
--- /dev/null
+++ b/src/util/cidr_match.h
@@ -0,0 +1,82 @@
+#ifndef _CIDR_MATCH_H_INCLUDED_
+#define _CIDR_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cidr 3h
+/* SUMMARY
+/* CIDR-style pattern matching
+/* SYNOPSIS
+/* #include <cidr_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Utility library.
+ */
+#include <myaddrinfo.h> /* MAI_V6ADDR_BYTES etc. */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ *
+ * Address length is protocol dependent. Find out how large our address byte
+ * strings should be.
+ */
+#ifdef HAS_IPV6
+# define CIDR_MATCH_ABYTES MAI_V6ADDR_BYTES
+#else
+# define CIDR_MATCH_ABYTES MAI_V4ADDR_BYTES
+#endif
+
+ /*
+ * Each parsed CIDR pattern can be member of a linked list.
+ */
+typedef struct CIDR_MATCH {
+ int op; /* operation, match or control flow */
+ int match; /* positive or negative match */
+ unsigned char net_bytes[CIDR_MATCH_ABYTES]; /* network portion */
+ unsigned char mask_bytes[CIDR_MATCH_ABYTES]; /* network mask */
+ unsigned char addr_family; /* AF_XXX */
+ unsigned char addr_byte_count; /* typically, 4 or 16 */
+ unsigned char addr_bit_count; /* optimization */
+ unsigned char mask_shift; /* optimization */
+ struct CIDR_MATCH *next; /* next entry */
+ struct CIDR_MATCH *block_end; /* block terminator */
+} CIDR_MATCH;
+
+#define CIDR_MATCH_OP_MATCH 1 /* Match this pattern */
+#define CIDR_MATCH_OP_IF 2 /* Increase if/endif nesting on match */
+#define CIDR_MATCH_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+#define CIDR_MATCH_TRUE 1 /* Request positive match */
+#define CIDR_MATCH_FALSE 0 /* Request negative match */
+
+extern VSTRING *cidr_match_parse(CIDR_MATCH *, char *, int, VSTRING *);
+extern VSTRING *cidr_match_parse_if(CIDR_MATCH *, char *, int, VSTRING *);
+extern void cidr_match_endif(CIDR_MATCH *);
+
+extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/clean_env.c b/src/util/clean_env.c
new file mode 100644
index 0000000..5ae5528
--- /dev/null
+++ b/src/util/clean_env.c
@@ -0,0 +1,146 @@
+/*++
+/* NAME
+/* clean_env 3
+/* SUMMARY
+/* clean up the environment
+/* SYNOPSIS
+/* #include <clean_env.h>
+/*
+/* void clean_env(preserve_list)
+/* const char **preserve_list;
+/*
+/* void update_env(preserve_list)
+/* const char **preserve_list;
+/* DESCRIPTION
+/* clean_env() reduces the process environment to the bare minimum.
+/* The function takes a null-terminated list of arguments.
+/* Each argument specifies the name of an environment variable
+/* that should be preserved, or specifies a name=value that should
+/* be entered into the new environment.
+/*
+/* update_env() applies name=value settings, but otherwise does not
+/* change the process environment.
+/* DIAGNOSTICS
+/* Fatal error: out of memory.
+/* SEE ALSO
+/* safe_getenv(3), guarded getenv()
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <safe.h>
+#include <clean_env.h>
+#include <stringops.h>
+
+/* clean_env - clean up the environment */
+
+void clean_env(char **preserve_list)
+{
+ extern char **environ;
+ ARGV *save_list;
+ char *value;
+ char **cpp;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
+
+ /*
+ * Preserve or specify selected environment variables.
+ */
+ save_list = argv_alloc(10);
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("clean_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ } else if ((value = safe_getenv(*cpp)) != 0) {
+ argv_add(save_list, *cpp, value, (char *) 0);
+ }
+ }
+
+ /*
+ * Truncate the process environment, if available. On some systems
+ * (Ultrix!), environ can be a null pointer.
+ */
+ if (environ)
+ environ[0] = 0;
+
+ /*
+ * Restore preserved environment variables.
+ */
+ for (cpp = save_list->argv; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]);
+
+ /*
+ * Cleanup.
+ */
+ argv_free(save_list);
+}
+
+/* update_env - apply name=value settings only */
+
+void update_env(char **preserve_list)
+{
+ char **cpp;
+ ARGV *save_list;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
+
+ /*
+ * Extract name=value settings.
+ */
+ save_list = argv_alloc(10);
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("update_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ }
+ }
+
+ /*
+ * Apply name=value settings.
+ */
+ for (cpp = save_list->argv; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]);
+
+ /*
+ * Cleanup.
+ */
+ argv_free(save_list);
+}
diff --git a/src/util/clean_env.h b/src/util/clean_env.h
new file mode 100644
index 0000000..5c89723
--- /dev/null
+++ b/src/util/clean_env.h
@@ -0,0 +1,36 @@
+#ifndef _CLEAN_ENV_H_INCLUDED_
+#define _CLEAN_ENV_H_INCLUDED_
+
+/*++
+/* NAME
+/* clean_env 3h
+/* SUMMARY
+/* clean up the environment
+/* SYNOPSIS
+/* #include <clean_env.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void clean_env(char **);
+extern void update_env(char **);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/close_on_exec.c b/src/util/close_on_exec.c
new file mode 100644
index 0000000..efa3415
--- /dev/null
+++ b/src/util/close_on_exec.c
@@ -0,0 +1,60 @@
+/*++
+/* NAME
+/* close_on_exec 3
+/* SUMMARY
+/* set/clear close-on-exec flag
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int close_on_exec(int fd, int on)
+/* DESCRIPTION
+/* the \fIclose_on_exec\fR() function manipulates the close-on-exec
+/* flag for the specified open file, and returns the old setting.
+/*
+/* Arguments:
+/* .IP fd
+/* A file descriptor.
+/* .IP on
+/* Use CLOSE_ON_EXEC or PASS_ON_EXEC.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+
+/* Utility library. */
+
+#include "msg.h"
+
+/* Application-specific. */
+
+#include "iostuff.h"
+
+#define PATTERN FD_CLOEXEC
+
+/* close_on_exec - set/clear close-on-exec flag */
+
+int close_on_exec(fd, on)
+int fd;
+int on;
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+ msg_fatal("fcntl: get flags: %m");
+ if (fcntl(fd, F_SETFD, on ? flags | PATTERN : flags & ~PATTERN) < 0)
+ msg_fatal("fcntl: set close-on-exec flag %s: %m", on ? "on" : "off");
+ return ((flags & PATTERN) != 0);
+}
diff --git a/src/util/compat_va_copy.h b/src/util/compat_va_copy.h
new file mode 100644
index 0000000..6a2042b
--- /dev/null
+++ b/src/util/compat_va_copy.h
@@ -0,0 +1,44 @@
+#ifndef _COMPAT_VA_COPY_H_INCLUDED_
+#define _COMPAT_VA_COPY_H_INCLUDED_
+
+/*++
+/* NAME
+/* compat_va_copy 3h
+/* SUMMARY
+/* compatibility
+/* SYNOPSIS
+/* #include <compat_va_copy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * C99 defines va_start and va_copy as macros, so we can probe the
+ * compilation environment with #ifdef etc. Some environments define
+ * __va_copy so we probe for that, too.
+ */
+#if !defined(va_start)
+#error "include <stdarg.h> first"
+#endif
+
+#if !defined(VA_COPY)
+#if defined(va_copy)
+#define VA_COPY(dest, src) va_copy(dest, src)
+#elif defined(__va_copy)
+#define VA_COPY(dest, src) __va_copy(dest, src)
+#else
+#define VA_COPY(dest, src) (dest) = (src)
+#endif
+#endif /* VA_COPY */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/concatenate.c b/src/util/concatenate.c
new file mode 100644
index 0000000..7b6a3eb
--- /dev/null
+++ b/src/util/concatenate.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* concatenate 3
+/* SUMMARY
+/* concatenate strings
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *concatenate(str, ...)
+/* const char *str;
+/* DESCRIPTION
+/* The \fBconcatenate\fR routine concatenates a null-terminated
+/* list of pointers to null-terminated character strings.
+/* The result is dynamically allocated and should be passed to myfree()
+/* when no longer 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "compat_va_copy.h"
+
+/* concatenate - concatenate null-terminated list of strings */
+
+char *concatenate(const char *arg0,...)
+{
+ char *result;
+ va_list ap;
+ va_list ap2;
+ ssize_t len;
+ char *arg;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, arg0);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Compute the length of the resulting string.
+ */
+ len = strlen(arg0);
+ while ((arg = va_arg(ap, char *)) != 0)
+ len += strlen(arg);
+ va_end(ap);
+
+ /*
+ * Build the resulting string. Don't care about wasting a CPU cycle.
+ */
+ result = mymalloc(len + 1);
+ strcpy(result, arg0);
+ while ((arg = va_arg(ap2, char *)) != 0)
+ strcat(result, arg);
+ va_end(ap2);
+ return (result);
+}
diff --git a/src/util/connect.h b/src/util/connect.h
new file mode 100644
index 0000000..1e8de11
--- /dev/null
+++ b/src/util/connect.h
@@ -0,0 +1,43 @@
+#ifndef _CONNECT_H_INCLUDED_
+#define _CONNECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* connect 3h
+/* SUMMARY
+/* client interface file
+/* SYNOPSIS
+/* #include <connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+
+ /*
+ * Client external interface.
+ */
+extern int unix_connect(const char *, int, int);
+extern int inet_connect(const char *, int, int);
+extern int stream_connect(const char *, int, int);
+extern int unix_dgram_connect(const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/ctable.c b/src/util/ctable.c
new file mode 100644
index 0000000..4993671
--- /dev/null
+++ b/src/util/ctable.c
@@ -0,0 +1,321 @@
+/*++
+/* NAME
+/* ctable 3
+/* SUMMARY
+/* cache manager
+/* SYNOPSIS
+/* #include <ctable.h>
+/*
+/* CTABLE *ctable_create(limit, create, delete, context)
+/* ssize_t limit;
+/* void *(*create)(const char *key, void *context);
+/* void (*delete)(void *value, void *context);
+/* void *context;
+/*
+/* const void *ctable_locate(cache, key)
+/* CTABLE *cache;
+/* const char *key;
+/*
+/* const void *ctable_refresh(cache, key)
+/* CTABLE *cache;
+/* const char *key;
+/*
+/* const void *ctable_newcontext(cache, context)
+/* CTABLE *cache;
+/* void *context;
+/*
+/* void ctable_free(cache)
+/* CTABLE *cache;
+/*
+/* void ctable_walk(cache, action)
+/* CTABLE *cache;
+/* void (*action)(const char *key, const void *value);
+/* DESCRIPTION
+/* This module maintains multiple caches. Cache items are purged
+/* automatically when the number of items exceeds a configurable
+/* limit. Caches never shrink. Each cache entry consists of a
+/* string-valued lookup key and a generic data pointer value.
+/*
+/* ctable_create() creates a cache with the specified size limit, and
+/* returns a pointer to the result. The create and delete arguments
+/* specify pointers to call-back functions that create a value, given
+/* a key, and delete a given value, respectively. The context argument
+/* is passed on to the call-back routines.
+/* The create() and delete() functions must not modify the cache.
+/*
+/* ctable_locate() looks up or generates the value that corresponds to
+/* the specified key, and returns that value.
+/*
+/* ctable_refresh() flushes the value (if any) associated with
+/* the specified key, and returns the same result as ctable_locate().
+/*
+/* ctable_newcontext() updates the context that is passed on
+/* to call-back routines.
+/*
+/* ctable_free() destroys the specified cache, including its contents.
+/*
+/* ctable_walk() iterates over all elements in the cache, and invokes
+/* the action function for each cache element with the corresponding
+/* key and value as arguments. This function is useful mainly for
+/* cache performance debugging.
+/* Note: the action() function must not modify the cache.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Panic: interface violation.
+/* 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 <stdlib.h>
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <ring.h>
+#include <htable.h>
+#include <ctable.h>
+
+ /*
+ * Cache entries are kept in most-recently used order. We use a hash table
+ * to quickly locate cache entries.
+ */
+#define CTABLE_ENTRY struct ctable_entry
+
+struct ctable_entry {
+ RING ring; /* MRU linkage */
+ const char *key; /* lookup key */
+ void *value; /* corresponding value */
+};
+
+#define RING_TO_CTABLE_ENTRY(ring_ptr) \
+ RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring)
+#define RING_PTR_OF(x) (&((x)->ring))
+
+struct ctable {
+ HTABLE *table; /* table with key, ctable_entry pairs */
+ size_t limit; /* max nr of entries */
+ size_t used; /* current nr of entries */
+ CTABLE_CREATE_FN create; /* constructor */
+ CTABLE_DELETE_FN delete; /* destructor */
+ RING ring; /* MRU linkage */
+ void *context; /* application context */
+};
+
+#define CTABLE_MIN_SIZE 5
+
+/* ctable_create - create empty cache */
+
+CTABLE *ctable_create(ssize_t limit, CTABLE_CREATE_FN create,
+ CTABLE_DELETE_FN delete, void *context)
+{
+ CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE));
+ const char *myname = "ctable_create";
+
+ if (limit < 1)
+ msg_panic("%s: bad cache limit: %ld", myname, (long) limit);
+
+ cache->table = htable_create(limit);
+ cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit);
+ cache->used = 0;
+ cache->create = create;
+ cache->delete = delete;
+ ring_init(RING_PTR_OF(cache));
+ cache->context = context;
+ return (cache);
+}
+
+/* ctable_locate - look up or create cache item */
+
+const void *ctable_locate(CTABLE *cache, const char *key)
+{
+ const char *myname = "ctable_locate";
+ CTABLE_ENTRY *entry;
+
+ /*
+ * If the entry is not in the cache, make sure there is room for a new
+ * entry and install it at the front of the MRU chain. Otherwise, move
+ * the entry to the front of the MRU chain if it is not already there.
+ * All this means that the cache never shrinks.
+ */
+ if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) {
+ if (cache->used >= cache->limit) {
+ entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache)));
+ if (msg_verbose)
+ msg_info("%s: purge entry key %s", myname, entry->key);
+ ring_detach(RING_PTR_OF(entry));
+ cache->delete(entry->value, cache->context);
+ htable_delete(cache->table, entry->key, (void (*) (void *)) 0);
+ } else {
+ entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY));
+ cache->used++;
+ }
+ entry->value = cache->create(key, cache->context);
+ entry->key = htable_enter(cache->table, key, (void *) entry)->key;
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ if (msg_verbose)
+ msg_info("%s: install entry key %s", myname, entry->key);
+ } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) {
+ if (msg_verbose)
+ msg_info("%s: leave existing entry key %s", myname, entry->key);
+ } else {
+ ring_detach(RING_PTR_OF(entry));
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ if (msg_verbose)
+ msg_info("%s: move existing entry key %s", myname, entry->key);
+ }
+ return (entry->value);
+}
+
+/* ctable_refresh - page-in fresh data for given key */
+
+const void *ctable_refresh(CTABLE *cache, const char *key)
+{
+ const char *myname = "ctable_refresh";
+ CTABLE_ENTRY *entry;
+
+ /* Materialize entry if missing. */
+ if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0)
+ return ctable_locate(cache, key);
+
+ /* Otherwise, refresh its content. */
+ cache->delete(entry->value, cache->context);
+ entry->value = cache->create(key, cache->context);
+
+ /* Update its MRU linkage. */
+ if (entry != RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) {
+ ring_detach(RING_PTR_OF(entry));
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ }
+ if (msg_verbose)
+ msg_info("%s: refresh entry key %s", myname, entry->key);
+ return (entry->value);
+}
+
+/* ctable_newcontext - update call-back context */
+
+void ctable_newcontext(CTABLE *cache, void *context)
+{
+ cache->context = context;
+}
+
+static CTABLE *ctable_free_cache;
+
+/* ctable_free_callback - callback function */
+
+static void ctable_free_callback(void *ptr)
+{
+ CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr;
+
+ ctable_free_cache->delete(entry->value, ctable_free_cache->context);
+ myfree((void *) entry);
+}
+
+/* ctable_free - destroy cache and contents */
+
+void ctable_free(CTABLE *cache)
+{
+ CTABLE *saved_cache = ctable_free_cache;
+
+ /*
+ * XXX the hash table does not pass application context so we have to
+ * store it in a global variable.
+ */
+ ctable_free_cache = cache;
+ htable_free(cache->table, ctable_free_callback);
+ myfree((void *) cache);
+ ctable_free_cache = saved_cache;
+}
+
+/* ctable_walk - iterate over all cache entries */
+
+void ctable_walk(CTABLE *cache, void (*action) (const char *, const void *))
+{
+ RING *entry = RING_PTR_OF(cache);
+
+ /* Walking down the MRU chain is less work than using ht_walk(). */
+
+ while ((entry = ring_succ(entry)) != RING_PTR_OF(cache))
+ action((RING_TO_CTABLE_ENTRY(entry)->key),
+ (RING_TO_CTABLE_ENTRY(entry)->value));
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read keys from stdin, ask for values not
+ * in cache.
+ */
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+static void *ask(const char *key, void *context)
+{
+ VSTRING *data_buf = (VSTRING *) context;
+
+ vstream_printf("ask: %s = ", key);
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF)
+ vstream_longjmp(VSTREAM_IN, 1);
+ if (!isatty(0)) {
+ vstream_printf("%s\n", STR(data_buf));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ return (mystrdup(STR(data_buf)));
+}
+
+static void drop(void *data, void *unused_context)
+{
+ myfree(data);
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *key_buf;
+ VSTRING *data_buf;
+ CTABLE *cache;
+ const char *value;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ key_buf = vstring_alloc(100);
+ data_buf = vstring_alloc(100);
+ cache = ctable_create(1, ask, drop, (void *) data_buf);
+ msg_verbose = 1;
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_EXCEPT, CA_VSTREAM_CTL_END);
+
+ if (vstream_setjmp(VSTREAM_IN) == 0) {
+ for (;;) {
+ vstream_printf("key = ");
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF)
+ vstream_longjmp(VSTREAM_IN, 1);
+ if (!isatty(0)) {
+ vstream_printf("%s\n", STR(key_buf));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ value = ctable_locate(cache, STR(key_buf));
+ vstream_printf("result: %s\n", value);
+ }
+ }
+ ctable_free(cache);
+ vstring_free(key_buf);
+ vstring_free(data_buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/ctable.h b/src/util/ctable.h
new file mode 100644
index 0000000..ea16914
--- /dev/null
+++ b/src/util/ctable.h
@@ -0,0 +1,41 @@
+#ifndef _CTABLE_H_INCLUDED_
+#define _CTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* ctable 5
+/* SUMMARY
+/* cache manager
+/* SYNOPSIS
+/* #include <ctable.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Interface of the cache manager. The structure of a cache is not visible
+ * to the caller.
+ */
+
+#define CTABLE struct ctable
+typedef void *(*CTABLE_CREATE_FN) (const char *, void *);
+typedef void (*CTABLE_DELETE_FN) (void *, void *);
+
+extern CTABLE *ctable_create(ssize_t, CTABLE_CREATE_FN, CTABLE_DELETE_FN, void *);
+extern void ctable_free(CTABLE *);
+extern void ctable_walk(CTABLE *, void (*) (const char *, const void *));
+extern const void *ctable_locate(CTABLE *, const char *);
+extern const void *ctable_refresh(CTABLE *, const char *);
+extern void ctable_newcontext(CTABLE *, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/ctable.in b/src/util/ctable.in
new file mode 100644
index 0000000..78763cf
--- /dev/null
+++ b/src/util/ctable.in
@@ -0,0 +1,39 @@
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+e
+d
+c
+b
+a
+1
+b
+c
+d
+e
+f
+6
+f
diff --git a/src/util/ctable.ref b/src/util/ctable.ref
new file mode 100644
index 0000000..34e8a95
--- /dev/null
+++ b/src/util/ctable.ref
@@ -0,0 +1,99 @@
+key = a
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+ask: b = 2
+./ctable: ctable_locate: install entry key b
+result: 2
+key = c
+ask: c = 3
+./ctable: ctable_locate: install entry key c
+result: 3
+key = d
+ask: d = 4
+./ctable: ctable_locate: install entry key d
+result: 4
+key = e
+ask: e = 5
+./ctable: ctable_locate: install entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = a
+./ctable: ctable_locate: purge entry key b
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+./ctable: ctable_locate: purge entry key c
+ask: b = 2
+./ctable: ctable_locate: install entry key b
+result: 2
+key = c
+./ctable: ctable_locate: purge entry key d
+ask: c = 3
+./ctable: ctable_locate: install entry key c
+result: 3
+key = d
+./ctable: ctable_locate: purge entry key e
+ask: d = 4
+./ctable: ctable_locate: install entry key d
+result: 4
+key = e
+./ctable: ctable_locate: purge entry key f
+ask: e = 5
+./ctable: ctable_locate: install entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = e
+./ctable: ctable_locate: move existing entry key e
+result: 5
+key = d
+./ctable: ctable_locate: move existing entry key d
+result: 4
+key = c
+./ctable: ctable_locate: move existing entry key c
+result: 3
+key = b
+./ctable: ctable_locate: move existing entry key b
+result: 2
+key = a
+./ctable: ctable_locate: purge entry key f
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+./ctable: ctable_locate: move existing entry key b
+result: 2
+key = c
+./ctable: ctable_locate: move existing entry key c
+result: 3
+key = d
+./ctable: ctable_locate: move existing entry key d
+result: 4
+key = e
+./ctable: ctable_locate: move existing entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = \ No newline at end of file
diff --git a/src/util/dict.c b/src/util/dict.c
new file mode 100644
index 0000000..5d53860
--- /dev/null
+++ b/src/util/dict.c
@@ -0,0 +1,664 @@
+/*++
+/* NAME
+/* dict 3
+/* SUMMARY
+/* dictionary manager
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* void dict_register(dict_name, dict_info)
+/* const char *dict_name;
+/* DICT *dict_info;
+/*
+/* DICT *dict_handle(dict_name)
+/* const char *dict_name;
+/*
+/* void dict_unregister(dict_name)
+/* const char *dict_name;
+/*
+/* int dict_update(dict_name, member, value)
+/* const char *dict_name;
+/* const char *member;
+/* const char *value;
+/*
+/* const char *dict_lookup(dict_name, member)
+/* const char *dict_name;
+/* const char *member;
+/*
+/* int dict_delete(dict_name, member)
+/* const char *dict_name;
+/* const char *member;
+/*
+/* int dict_sequence(dict_name, func, member, value)
+/* const char *dict_name;
+/* int func;
+/* const char **member;
+/* const char **value;
+/*
+/* const char *dict_eval(dict_name, string, int recursive)
+/* const char *dict_name;
+/* const char *string;
+/* int recursive;
+/*
+/* int dict_walk(action, context)
+/* void (*action)(dict_name, dict_handle, context)
+/* void *context;
+/*
+/* int dict_error(dict_name)
+/* const char *dict_name;
+/*
+/* const char *dict_changed_name()
+/*
+/* void DICT_OWNER_AGGREGATE_INIT(aggregate)
+/* DICT_OWNER aggregate;
+/*
+/* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source)
+/* DICT_OWNER aggregate;
+/* DICT_OWNER source;
+/* AUXILIARY FUNCTIONS
+/* int dict_load_file_xt(dict_name, path)
+/* const char *dict_name;
+/* const char *path;
+/*
+/* void dict_load_fp(dict_name, fp)
+/* const char *dict_name;
+/* VSTREAM *fp;
+/*
+/* const char *dict_flags_str(dict_flags)
+/* int dict_flags;
+/*
+/* int dict_flags_mask(names)
+/* const char *names;
+/* DESCRIPTION
+/* This module maintains a collection of name-value dictionaries.
+/* Each dictionary has its own name and has its own methods to read
+/* or update members. Examples of dictionaries that can be accessed
+/* in this manner are the global UNIX-style process environment,
+/* hash tables, NIS maps, DBM files, and so on. Dictionary values
+/* are not limited to strings but can be arbitrary objects as long
+/* as they can be represented by character pointers.
+/* FEATURES
+/* .fi
+/* .ad
+/* Notable features of this module are:
+/* .IP "macro expansion (string-valued dictionaries only)"
+/* Macros of the form $\fIname\fR can be expanded to the current
+/* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are
+/* also supported.
+/* .IP "unknown names"
+/* An update request for an unknown dictionary name will trigger
+/* the instantiation of an in-memory dictionary with that name.
+/* A lookup request (including delete and sequence) for an
+/* unknown dictionary will result in a "not found" and "no
+/* error" result.
+/* .PP
+/* dict_register() adds a new dictionary, including access methods,
+/* to the list of known dictionaries, or increments the reference
+/* count for an existing (name, dictionary) pair. Otherwise, it is
+/* an error to pass an existing name (this would cause a memory leak).
+/*
+/* dict_handle() returns the generic dictionary handle of the
+/* named dictionary, or a null pointer when the named dictionary
+/* is not found.
+/*
+/* dict_unregister() decrements the reference count of the named
+/* dictionary. When the reference count reaches zero, dict_unregister()
+/* breaks the (name, dictionary) association and executes the
+/* dictionary's optional \fIremove\fR method.
+/*
+/* dict_update() updates the value of the named dictionary member.
+/* The dictionary member and the named dictionary are instantiated
+/* on the fly. The result value is zero (DICT_STAT_SUCCESS)
+/* when the update was made.
+/*
+/* dict_lookup() returns the value of the named member (i.e. without
+/* expanding macros in the member value). The \fIdict_name\fR argument
+/* specifies the dictionary to search. The result is a null pointer
+/* when no value is found, otherwise the result is owned by the
+/* underlying dictionary method. Make a copy if the result is to be
+/* modified, or if the result is to survive multiple dict_lookup() calls.
+/*
+/* dict_delete() removes the named member from the named dictionary.
+/* The result value is zero (DICT_STAT_SUCCESS) when the member
+/* was found.
+/*
+/* dict_sequence() steps through the named dictionary and returns
+/* keys and values in some implementation-defined order. The func
+/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first
+/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result
+/* is owned by the underlying dictionary method. Make a copy if the
+/* result is to be modified, or if the result is to survive multiple
+/* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS)
+/* when a member was found.
+/*
+/* dict_eval() expands macro references in the specified string.
+/* The result is owned by the dictionary manager. Make a copy if the
+/* result is to survive multiple dict_eval() calls. When the
+/* \fIrecursive\fR argument is non-zero, macro references in macro
+/* lookup results are expanded recursively.
+/*
+/* dict_walk() iterates over all registered dictionaries in some
+/* arbitrary order, and invokes the specified action routine with
+/* as arguments:
+/* .IP "const char *dict_name"
+/* Dictionary name.
+/* .IP "DICT *dict_handle"
+/* Generic dictionary handle.
+/* .IP "char *context"
+/* Application context from the caller.
+/* .PP
+/* dict_changed_name() returns non-zero when any dictionary needs to
+/* be re-opened because it has changed or because it was unlinked.
+/* A non-zero result is the name of a changed dictionary.
+/*
+/* dict_load_file_xt() reads name-value entries from the named file.
+/* Lines that begin with whitespace are concatenated to the preceding
+/* line (the newline is deleted).
+/* Each entry is stored in the dictionary named by \fIdict_name\fR.
+/* The result is zero if the file could not be opened.
+/*
+/* dict_load_fp() reads name-value entries from an open stream.
+/* It has the same semantics as the dict_load_file_xt() function.
+/*
+/* dict_flags_str() returns a printable representation of the
+/* specified dictionary flags. The result is overwritten upon
+/* each call.
+/*
+/* dict_flags_mask() returns the bitmask for the specified
+/* comma/space-separated dictionary flag names.
+/* TRUST AND PROVENANCE
+/* .ad
+/* .fi
+/* Each dictionary has an owner attribute that contains (status,
+/* uid) information about the owner of a dictionary. The
+/* status is one of the following:
+/* .IP DICT_OWNER_TRUSTED
+/* The dictionary is owned by a trusted user. The uid is zero,
+/* and specifies a UNIX user ID.
+/* .IP DICT_OWNER_UNTRUSTED
+/* The dictionary is owned by an untrusted user. The uid is
+/* non-zero, and specifies a UNIX user ID.
+/* .IP DICT_OWNER_UNKNOWN
+/* The dictionary is owned by an unspecified user. For example,
+/* the origin is unauthenticated, or different parts of a
+/* dictionary aggregate (see below) are owned by different
+/* untrusted users. The uid is non-zero and does not specify
+/* a UNIX user ID.
+/* .PP
+/* Note that dictionary ownership does not necessarily imply
+/* ownership of lookup results. For example, a PCRE table may
+/* be owned by the trusted root user, but the result of $number
+/* expansion can contain data from an arbitrary remote SMTP
+/* client. See dict_open(3) for how to disallow $number
+/* expansions with security-sensitive operations.
+/*
+/* Two macros are available to help determine the provenance
+/* and trustworthiness of a dictionary aggregate. The macros
+/* are unsafe because they may evaluate arguments more than
+/* once.
+/*
+/* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner
+/* attributes to the highest trust level.
+/*
+/* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner
+/* attributes with the attributes of the specified source, and
+/* reduces the aggregate trust level as appropriate.
+/* SEE ALSO
+/* htable(3)
+/* BUGS
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, malformed macro name.
+/*
+/* The lookup routine returns non-null when the request is
+/* satisfied. The update, delete and sequence routines return
+/* zero (DICT_STAT_SUCCESS) when the request is satisfied.
+/* The dict_error() function returns non-zero only when the
+/* last operation was not satisfied due to a dictionary access
+/* error. The result can have the following values:
+/* .IP DICT_ERR_NONE(zero)
+/* There was no dictionary access error. For example, the
+/* request was satisfied, the requested information did not
+/* exist in the dictionary, or the information already existed
+/* when it should not exist (collision).
+/* .IP DICT_ERR_RETRY(<0)
+/* The dictionary was temporarily unavailable. This can happen
+/* with network-based services.
+/* .IP DICT_ERR_CONFIG(<0)
+/* The dictionary was unavailable due to a configuration error.
+/* .PP
+/* Generally, a program is expected to test the function result
+/* value for "success" first. If the operation was not successful,
+/* a program is expected to test for a non-zero dict->error
+/* status to distinguish between a data notfound/collision
+/* condition or a dictionary access error.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "htable.h"
+#include "mymalloc.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "readlline.h"
+#include "mac_expand.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "name_mask.h"
+#include "dict.h"
+#include "dict_ht.h"
+#include "warn_stat.h"
+#include "line_number.h"
+
+static HTABLE *dict_table;
+
+ /*
+ * Each (name, dictionary) instance has a reference count. The count is part
+ * of the name, not the dictionary. The same dictionary may be registered
+ * under multiple names. The structure below keeps track of instances and
+ * reference counts.
+ */
+typedef struct {
+ DICT *dict;
+ int refcount;
+} DICT_NODE;
+
+#define dict_node(dict) \
+ (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0)
+
+/* Find a dictionary handle by name for lookup purposes. */
+
+#define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \
+ DICT_NODE *node; \
+ if ((node = dict_node(dict_name)) != 0) \
+ dict = node->dict; \
+ else \
+ dict = 0; \
+} while (0)
+
+/* Find a dictionary handle by name for update purposes. */
+
+#define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \
+ DICT_NODE *node; \
+ if ((node = dict_node(dict_name)) == 0) { \
+ dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \
+ dict_register(dict_name, dict); \
+ } else \
+ dict = node->dict; \
+} while (0)
+
+#define STR(x) vstring_str(x)
+
+/* dict_register - make association with dictionary */
+
+void dict_register(const char *dict_name, DICT *dict_info)
+{
+ const char *myname = "dict_register";
+ DICT_NODE *node;
+
+ if (dict_table == 0)
+ dict_table = htable_create(0);
+ if ((node = dict_node(dict_name)) == 0) {
+ node = (DICT_NODE *) mymalloc(sizeof(*node));
+ node->dict = dict_info;
+ node->refcount = 0;
+ htable_enter(dict_table, dict_name, (void *) node);
+ } else if (dict_info != node->dict)
+ msg_fatal("%s: dictionary name exists: %s", myname, dict_name);
+ node->refcount++;
+ if (msg_verbose > 1)
+ msg_info("%s: %s %d", myname, dict_name, node->refcount);
+}
+
+/* dict_handle - locate generic dictionary handle */
+
+DICT *dict_handle(const char *dict_name)
+{
+ DICT_NODE *node;
+
+ return ((node = dict_node(dict_name)) != 0 ? node->dict : 0);
+}
+
+/* dict_node_free - dict_unregister() callback */
+
+static void dict_node_free(void *ptr)
+{
+ DICT_NODE *node = (DICT_NODE *) ptr;
+ DICT *dict = node->dict;
+
+ if (dict->close)
+ dict->close(dict);
+ myfree((void *) node);
+}
+
+/* dict_unregister - break association with named dictionary */
+
+void dict_unregister(const char *dict_name)
+{
+ const char *myname = "dict_unregister";
+ DICT_NODE *node;
+
+ if ((node = dict_node(dict_name)) == 0)
+ msg_panic("non-existing dictionary: %s", dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: %s %d", myname, dict_name, node->refcount);
+ if (--(node->refcount) == 0)
+ htable_delete(dict_table, dict_name, dict_node_free);
+}
+
+/* dict_update - replace or add dictionary entry */
+
+int dict_update(const char *dict_name, const char *member, const char *value)
+{
+ const char *myname = "dict_update";
+ DICT *dict;
+
+ DICT_FIND_FOR_UPDATE(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, value);
+ return (dict->update(dict, member, value));
+}
+
+/* dict_lookup - look up dictionary entry */
+
+const char *dict_lookup(const char *dict_name, const char *member)
+{
+ const char *myname = "dict_lookup";
+ DICT *dict;
+ const char *ret;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (dict != 0) {
+ ret = dict->lookup(dict, member);
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, ret ? ret :
+ dict->error ? "(error)" : "(notfound)");
+ return (ret);
+ } else {
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, "(notfound)");
+ return (0);
+ }
+}
+
+/* dict_delete - delete dictionary entry */
+
+int dict_delete(const char *dict_name, const char *member)
+{
+ const char *myname = "dict_delete";
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: delete %s", myname, member);
+ return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL);
+}
+
+/* dict_sequence - traverse dictionary */
+
+int dict_sequence(const char *dict_name, const int func,
+ const char **member, const char **value)
+{
+ const char *myname = "dict_sequence";
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: sequence func %d", myname, func);
+ return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL);
+}
+
+/* dict_error - return last error */
+
+int dict_error(const char *dict_name)
+{
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ return (dict ? dict->error : DICT_ERR_NONE);
+}
+
+/* dict_load_file_xt - read entries from text file */
+
+int dict_load_file_xt(const char *dict_name, const char *path)
+{
+ VSTREAM *fp;
+ struct stat st;
+ time_t before;
+ time_t after;
+
+ /*
+ * Read the file again if it is hot. This may result in reading a partial
+ * parameter name when a file changes in the middle of a read.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
+ return (0);
+ dict_load_fp(dict_name, fp);
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_ferror(fp) || vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+ if (msg_verbose > 1)
+ msg_info("pausing to let %s cool down", path);
+ doze(300000);
+ }
+ return (1);
+}
+
+/* dict_load_fp - read entries from open stream */
+
+void dict_load_fp(const char *dict_name, VSTREAM *fp)
+{
+ const char *myname = "dict_load_fp";
+ VSTRING *buf;
+ char *member;
+ char *val;
+ const char *old;
+ int last_line;
+ int lineno;
+ const char *err;
+ struct stat st;
+ DICT *dict;
+
+ /*
+ * Instantiate the dictionary even if the file is empty.
+ */
+ DICT_FIND_FOR_UPDATE(dict, dict_name);
+ buf = vstring_alloc(100);
+ last_line = 0;
+
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", VSTREAM_PATH(fp));
+ while (readllines(buf, fp, &last_line, &lineno)) {
+ if ((err = split_nameval(STR(buf), &member, &val)) != 0)
+ msg_fatal("%s, line %d: %s: \"%s\"",
+ VSTREAM_PATH(fp),
+ lineno,
+ err, STR(buf));
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, val);
+ if ((old = dict->lookup(dict, member)) != 0
+ && strcmp(old, val) != 0)
+ msg_warn("%s, line %d: overriding earlier entry: %s=%s",
+ VSTREAM_PATH(fp), lineno, member, old);
+ if (dict->update(dict, member, val) != 0)
+ msg_fatal("%s, line %d: unable to update %s:%s",
+ VSTREAM_PATH(fp), lineno, dict->type, dict->name);
+ }
+ vstring_free(buf);
+ dict->owner.uid = st.st_uid;
+ dict->owner.status = (st.st_uid != 0);
+}
+
+/* dict_eval_lookup - macro parser call-back routine */
+
+static const char *dict_eval_lookup(const char *key, int unused_type,
+ void *context)
+{
+ char *dict_name = (char *) context;
+ const char *pp = 0;
+ DICT *dict;
+
+ /*
+ * XXX how would one recover?
+ */
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (dict != 0
+ && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0)
+ msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key);
+ return (pp);
+}
+
+/* dict_eval - expand embedded dictionary references */
+
+const char *dict_eval(const char *dict_name, const char *value, int recursive)
+{
+ const char *myname = "dict_eval";
+ static VSTRING *buf;
+ int status;
+
+ /*
+ * Initialize.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ /*
+ * Expand macros, possibly recursively.
+ */
+#define DONT_FILTER (char *) 0
+
+ status = mac_expand(buf, value,
+ recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE,
+ DONT_FILTER, dict_eval_lookup, (void *) dict_name);
+ if (status & MAC_PARSE_ERROR)
+ msg_fatal("dictionary %s: macro processing error", dict_name);
+ if (msg_verbose > 1) {
+ if (strcmp(value, STR(buf)) != 0)
+ msg_info("%s: expand %s -> %s", myname, value, STR(buf));
+ else
+ msg_info("%s: const %s", myname, value);
+ }
+ return (STR(buf));
+}
+
+/* dict_walk - iterate over all dictionaries in arbitrary order */
+
+void dict_walk(DICT_WALK_ACTION action, void *ptr)
+{
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ HTABLE_INFO *h;
+
+ ht_info_list = htable_list(dict_table);
+ for (ht = ht_info_list; (h = *ht) != 0; ht++)
+ action(h->key, (DICT *) h->value, ptr);
+ myfree((void *) ht_info_list);
+}
+
+/* dict_changed_name - see if any dictionary has changed */
+
+const char *dict_changed_name(void)
+{
+ const char *myname = "dict_changed_name";
+ struct stat st;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ HTABLE_INFO *h;
+ const char *status;
+ DICT *dict;
+
+ ht_info_list = htable_list(dict_table);
+ for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) {
+ dict = ((DICT_NODE *) h->value)->dict;
+ if (dict->stat_fd < 0) /* not file-based */
+ continue;
+ if (dict->mtime == 0) /* not bloody likely */
+ msg_warn("%s: table %s: null time stamp", myname, h->key);
+ if (fstat(dict->stat_fd, &st) < 0)
+ msg_fatal("%s: fstat: %m", myname);
+ if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0
+ && st.st_mtime != dict->mtime)
+ || st.st_nlink == 0)
+ status = h->key;
+ }
+ myfree((void *) ht_info_list);
+ return (status);
+}
+
+/* dict_changed - backwards compatibility */
+
+int dict_changed(void)
+{
+ return (dict_changed_name() != 0);
+}
+
+ /*
+ * Mapping between flag names and flag values.
+ */
+static const NAME_MASK dict_mask[] = {
+ "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */
+ "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */
+ "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */
+ "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */
+ "fixed", DICT_FLAG_FIXED, /* fixed key map */
+ "pattern", DICT_FLAG_PATTERN, /* keys are patterns */
+ "lock", DICT_FLAG_LOCK, /* lock before access */
+ "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */
+ "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */
+ "debug", DICT_FLAG_DEBUG, /* log access */
+ "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */
+ "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */
+ "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */
+ "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */
+ "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */
+ "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */
+ "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */
+ "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */
+ "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */
+ "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */
+ "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */
+ 0,
+};
+
+/* dict_flags_str - convert bitmask to symbolic flag names */
+
+const char *dict_flags_str(int dict_flags)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(1);
+
+ return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags,
+ NAME_MASK_NUMBER | NAME_MASK_PIPE));
+}
+
+/* dict_flags_mask - convert symbolic flag names to bitmask */
+
+int dict_flags_mask(const char *names)
+{
+ return (name_mask("dictionary flags", dict_mask, names));
+}
diff --git a/src/util/dict.h b/src/util/dict.h
new file mode 100644
index 0000000..4f0cab8
--- /dev/null
+++ b/src/util/dict.h
@@ -0,0 +1,340 @@
+#ifndef _DICT_H_INCLUDED_
+#define _DICT_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict 3h
+/* SUMMARY
+/* dictionary manager
+/* SYNOPSIS
+/* #include <dict.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <setjmp.h>
+
+#ifdef NO_SIGSETJMP
+#define DICT_JMP_BUF jmp_buf
+#else
+#define DICT_JMP_BUF sigjmp_buf
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <argv.h>
+#include <vstring.h>
+#include <myflock.h>
+
+ /*
+ * Provenance information.
+ */
+typedef struct DICT_OWNER {
+ int status; /* see below */
+ uid_t uid; /* use only if status == UNTRUSTED */
+} DICT_OWNER;
+
+ /*
+ * Note that trust levels are not in numerical order.
+ */
+#define DICT_OWNER_UNKNOWN (-1) /* ex: unauthenticated tcp, proxy */
+#define DICT_OWNER_TRUSTED (!1) /* ex: root-owned config file */
+#define DICT_OWNER_UNTRUSTED (!0) /* ex: non-root config file */
+
+ /*
+ * When combining tables with different provenance, we initialize to the
+ * highest trust level, and remember the lowest trust level that we find
+ * during aggregation. If we combine tables that are owned by different
+ * untrusted users, the resulting provenance is "unknown".
+ */
+#define DICT_OWNER_AGGREGATE_INIT(dst) { \
+ (dst).status = DICT_OWNER_TRUSTED; \
+ (dst).uid = 0; \
+ } while (0)
+
+ /*
+ * The following is derived from the 3x3 transition matrix.
+ */
+#define DICT_OWNER_AGGREGATE_UPDATE(dst, src) do { \
+ if ((dst).status == DICT_OWNER_TRUSTED \
+ || (src).status == DICT_OWNER_UNKNOWN) { \
+ (dst) = (src); \
+ } else if ((dst).status == (src).status \
+ && (dst).uid != (src).uid) { \
+ (dst).status = DICT_OWNER_UNKNOWN; \
+ (dst).uid = ~0; \
+ } \
+ } while (0)
+
+ /*
+ * Generic dictionary interface - in reality, a dictionary extends this
+ * structure with private members to maintain internal state.
+ */
+typedef struct DICT {
+ char *type; /* for diagnostics */
+ char *name; /* for diagnostics */
+ int flags; /* see below */
+ const char *(*lookup) (struct DICT *, const char *);
+ int (*update) (struct DICT *, const char *, const char *);
+ int (*delete) (struct DICT *, const char *);
+ int (*sequence) (struct DICT *, int, const char **, const char **);
+ int (*lock) (struct DICT *, int);
+ void (*close) (struct DICT *);
+ int lock_type; /* for read/write lock */
+ int lock_fd; /* for read/write lock */
+ int stat_fd; /* change detection */
+ time_t mtime; /* mod time at open */
+ VSTRING *fold_buf; /* key folding buffer */
+ DICT_OWNER owner; /* provenance */
+ int error; /* last operation only */
+ DICT_JMP_BUF *jbuf; /* exception handling */
+ struct DICT_UTF8_BACKUP *utf8_backup; /* see below */
+ struct VSTRING *file_buf; /* dict_file_to_buf() */
+ struct VSTRING *file_b64; /* dict_file_to_b64() */
+} DICT;
+
+extern DICT *dict_alloc(const char *, const char *, ssize_t);
+extern void dict_free(DICT *);
+
+extern DICT *dict_debug(DICT *);
+
+#define DICT_DEBUG(d) ((d)->flags & DICT_FLAG_DEBUG ? dict_debug(d) : (d))
+
+ /*
+ * See dict_open.c embedded manpage for flag definitions.
+ */
+#define DICT_FLAG_NONE (0)
+#define DICT_FLAG_DUP_WARN (1<<0) /* warn about dups if not supported */
+#define DICT_FLAG_DUP_IGNORE (1<<1) /* ignore dups if not supported */
+#define DICT_FLAG_TRY0NULL (1<<2) /* do not append 0 to key/value */
+#define DICT_FLAG_TRY1NULL (1<<3) /* append 0 to key/value */
+#define DICT_FLAG_FIXED (1<<4) /* fixed key map */
+#define DICT_FLAG_PATTERN (1<<5) /* keys are patterns */
+#define DICT_FLAG_LOCK (1<<6) /* use temp lock before access */
+#define DICT_FLAG_DUP_REPLACE (1<<7) /* replace dups if supported */
+#define DICT_FLAG_SYNC_UPDATE (1<<8) /* sync updates if supported */
+#define DICT_FLAG_DEBUG (1<<9) /* log access */
+/*#define DICT_FLAG_FOLD_KEY (1<<10) /* lowercase the lookup key */
+#define DICT_FLAG_NO_REGSUB (1<<11) /* disallow regexp substitution */
+#define DICT_FLAG_NO_PROXY (1<<12) /* disallow proxy mapping */
+#define DICT_FLAG_NO_UNAUTH (1<<13) /* disallow unauthenticated data */
+#define DICT_FLAG_FOLD_FIX (1<<14) /* case-fold key with fixed-case map */
+#define DICT_FLAG_FOLD_MUL (1<<15) /* case-fold key with multi-case map */
+#define DICT_FLAG_FOLD_ANY (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL)
+#define DICT_FLAG_OPEN_LOCK (1<<16) /* perm lock if not multi-writer safe */
+#define DICT_FLAG_BULK_UPDATE (1<<17) /* optimize for bulk updates */
+#define DICT_FLAG_MULTI_WRITER (1<<18) /* multi-writer safe map */
+#define DICT_FLAG_UTF8_REQUEST (1<<19) /* activate UTF-8 if possible */
+#define DICT_FLAG_UTF8_ACTIVE (1<<20) /* UTF-8 proxy layer is present */
+#define DICT_FLAG_SRC_RHS_IS_FILE \
+ (1<<21) /* Map source RHS is a file */
+
+#define DICT_FLAG_UTF8_MASK (DICT_FLAG_UTF8_REQUEST)
+
+ /* IMPORTANT: Update the dict_mask[] table when the above changes */
+
+ /*
+ * The subsets of flags that control how a map is used. These are relevant
+ * mainly for proxymap support. Note: some categories overlap.
+ *
+ * DICT_FLAG_IMPL_MASK - flags that are set by the map implementation itself.
+ *
+ * DICT_FLAG_PARANOID - requestor flags that forbid the use of insecure map
+ * types for security-sensitive operations. These flags are checked by the
+ * map implementation itself upon open, lookup etc. requests.
+ *
+ * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that
+ * the requestor may change between open, lookup etc. requests. These
+ * specify requestor properties, not map properties.
+ *
+ * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change
+ * these flags between open, lookup, etc. requests (although a map may make
+ * changes to its copy of some of these flags). The proxymap server opens
+ * only one map instance for all client requests with the same values of
+ * these flags, and the proxymap client uses its own saved copy of these
+ * flags. DICT_FLAG_SRC_RHS_IS_FILE is an example of such a flag.
+ */
+#define DICT_FLAG_PARANOID \
+ (DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH)
+#define DICT_FLAG_IMPL_MASK (DICT_FLAG_FIXED | DICT_FLAG_PATTERN | \
+ DICT_FLAG_MULTI_WRITER)
+#define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \
+ DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \
+ DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK)
+#define DICT_FLAG_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK)
+
+ /*
+ * Feature tests.
+ */
+#define DICT_NEED_UTF8_ACTIVATION(enable, flags) \
+ ((enable) && ((flags) & DICT_FLAG_UTF8_MASK))
+
+ /*
+ * dict->error values. Errors must be negative; smtpd_check depends on this.
+ */
+#define DICT_ERR_NONE 0 /* no error */
+#define DICT_ERR_RETRY (-1) /* soft error */
+#define DICT_ERR_CONFIG (-2) /* configuration error */
+
+ /*
+ * Result values for exposed functions except lookup. FAIL/ERROR are
+ * suggested values, not for use in comparisons for equality.
+ */
+#define DICT_STAT_FAIL 1 /* any value > 0: notfound, conflict */
+#define DICT_STAT_SUCCESS 0 /* request satisfied */
+#define DICT_STAT_ERROR (-1) /* any value < 0: database error */
+
+ /*
+ * Set an error code and return a result value.
+ */
+#define DICT_ERR_VAL_RETURN(dict, err, val) do { \
+ (dict)->error = (err); \
+ return (val); \
+ } while (0)
+
+ /*
+ * Sequence function types.
+ */
+#define DICT_SEQ_FUN_FIRST 0 /* set cursor to first record */
+#define DICT_SEQ_FUN_NEXT 1 /* set cursor to next record */
+
+ /*
+ * Interface for dictionary types.
+ */
+extern ARGV *dict_mapnames(void);
+typedef void (*DICT_MAPNAMES_EXTEND_FN) (ARGV *);
+extern DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN);
+
+
+ /*
+ * High-level interface, with logical dictionary names.
+ */
+extern void dict_register(const char *, DICT *);
+extern DICT *dict_handle(const char *);
+extern void dict_unregister(const char *);
+extern int dict_update(const char *, const char *, const char *);
+extern const char *dict_lookup(const char *, const char *);
+extern int dict_delete(const char *, const char *);
+extern int dict_sequence(const char *, const int, const char **, const char **);
+extern int dict_load_file_xt(const char *, const char *);
+extern void dict_load_fp(const char *, VSTREAM *);
+extern const char *dict_eval(const char *, const char *, int);
+extern int dict_error(const char *);
+
+ /*
+ * Low-level interface, with physical dictionary handles.
+ */
+typedef DICT *(*DICT_OPEN_FN) (const char *, int, int);
+typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN) (const char *);
+extern DICT *dict_open(const char *, int, int);
+extern DICT *dict_open3(const char *, const char *, int, int);
+extern void dict_open_register(const char *, DICT_OPEN_FN);
+extern DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN);
+
+#define dict_get(dp, key) ((const char *) (dp)->lookup((dp), (key)))
+#define dict_put(dp, key, val) (dp)->update((dp), (key), (val))
+#define dict_del(dp, key) (dp)->delete((dp), (key))
+#define dict_seq(dp, f, key, val) (dp)->sequence((dp), (f), (key), (val))
+#define dict_close(dp) (dp)->close(dp)
+typedef void (*DICT_WALK_ACTION) (const char *, DICT *, void *);
+extern void dict_walk(DICT_WALK_ACTION, void *);
+extern int dict_changed(void);
+extern const char *dict_changed_name(void);
+extern const char *dict_flags_str(int);
+extern int dict_flags_mask(const char *);
+extern void dict_type_override(DICT *, const char *);
+
+ /*
+ * Check and convert UTF-8 keys and values.
+ */
+typedef struct DICT_UTF8_BACKUP {
+ const char *(*lookup) (struct DICT *, const char *);
+ int (*update) (struct DICT *, const char *, const char *);
+ int (*delete) (struct DICT *, const char *);
+} DICT_UTF8_BACKUP;
+
+extern DICT *dict_utf8_activate(DICT *);
+
+ /*
+ * Driver for interactive or scripted tests.
+ */
+void dict_test(int, char **);
+
+ /*
+ * Behind-the-scenes support to continue execution with reduced
+ * functionality.
+ */
+extern int dict_allow_surrogate;
+extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...);
+
+ /*
+ * This name is reserved for matchlist error handling.
+ */
+#define DICT_TYPE_NOFILE "non-existent"
+#define DICT_TYPE_NOUTF8 "non-UTF-8"
+
+ /*
+ * Duplicated from vstream(3). This should probably be abstracted out.
+ *
+ * Exception handling. We use pointer to jmp_buf to avoid a lot of unused
+ * baggage for streams that don't need this functionality.
+ *
+ * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can
+ * avoid surprises in code that manipulates signals, but unfortunately some
+ * systems have bugs in their implementation.
+ */
+#ifdef NO_SIGSETJMP
+#define dict_setjmp(dict) setjmp((dict)->jbuf[0])
+#define dict_longjmp(dict, val) longjmp((dict)->jbuf[0], (val))
+#else
+#define dict_setjmp(dict) sigsetjmp((dict)->jbuf[0], 1)
+#define dict_longjmp(dict, val) siglongjmp((dict)->jbuf[0], (val))
+#endif
+#define dict_isjmp(dict) ((dict)->jbuf != 0)
+
+ /*
+ * Temporary API. If exception handling proves to be useful,
+ * dict_jmp_alloc() should be integrated into dict_alloc().
+ */
+extern void dict_jmp_alloc(DICT *);
+
+ /*
+ * dict_file(3).
+ */
+extern struct VSTRING *dict_file_to_buf(DICT *, const char *);
+extern struct VSTRING *dict_file_to_b64(DICT *, const char *);
+extern struct VSTRING *dict_file_from_b64(DICT *, const char *);
+extern char *dict_file_get_error(DICT *);
+extern void dict_file_purge_buffers(DICT *);
+extern const char *dict_file_lookup(DICT *dict, const char *);
+
+ /*
+ * dict_stream(3)
+ */
+extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+ int open_flags, int dict_flags, struct stat * st, VSTRING **why);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_alloc.c b/src/util/dict_alloc.c
new file mode 100644
index 0000000..3285a38
--- /dev/null
+++ b/src/util/dict_alloc.c
@@ -0,0 +1,196 @@
+/*++
+/* NAME
+/* dict_alloc 3
+/* SUMMARY
+/* dictionary memory manager
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_alloc(dict_type, dict_name, size)
+/* const char *dict_type;
+/* const char *dict_name;
+/* ssize_t size;
+/*
+/* void dict_free(dict)
+/* DICT *ptr;
+/*
+/* void dict_jmp_alloc(dict)
+/* DICT *ptr;
+/* DESCRIPTION
+/* dict_alloc() allocates memory for a dictionary structure of
+/* \fIsize\fR bytes, initializes all generic dictionary
+/* properties to default settings,
+/* and installs default methods that do not support any operation.
+/* The caller is supposed to override the default methods with
+/* ones that it supports.
+/* The purpose of the default methods is to trap an attempt to
+/* invoke an unsupported method.
+/*
+/* One exception is the default lock function. When the
+/* dictionary provides a file handle for locking, the default
+/* lock function returns the result from myflock with the
+/* locking method specified in the lock_type member, otherwise
+/* it returns 0. Presently, the lock function is used only to
+/* implement the DICT_FLAG_OPEN_LOCK feature (lock the database
+/* exclusively after it is opened) for databases that are not
+/* multi-writer safe.
+/*
+/* dict_free() releases memory and cleans up after dict_alloc().
+/* It is up to the caller to dispose of any memory that was allocated
+/* by the caller.
+/*
+/* dict_jmp_alloc() implements preliminary support for exception
+/* handling. This will eventually be built into dict_alloc().
+/*
+/* Arguments:
+/* .IP dict_type
+/* The official name for this type of dictionary, as used by
+/* dict_open(3) etc. This is stored under the \fBtype\fR
+/* member.
+/* .IP dict_name
+/* Dictionary name. This is stored as the \fBname\fR member.
+/* .IP size
+/* The size in bytes of the dictionary subclass structure instance.
+/* SEE ALSO
+/* dict(3)
+/* DIAGNOSTICS
+/* Fatal errors: the process invokes a default method.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "myflock.h"
+#include "dict.h"
+
+/* dict_default_lookup - trap unimplemented operation */
+
+static const char *dict_default_lookup(DICT *dict, const char *unused_key)
+{
+ msg_fatal("table %s:%s: lookup operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_update - trap unimplemented operation */
+
+static int dict_default_update(DICT *dict, const char *unused_key,
+ const char *unused_value)
+{
+ msg_fatal("table %s:%s: update operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_delete - trap unimplemented operation */
+
+static int dict_default_delete(DICT *dict, const char *unused_key)
+{
+ msg_fatal("table %s:%s: delete operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_sequence - trap unimplemented operation */
+
+static int dict_default_sequence(DICT *dict, int unused_function,
+ const char **unused_key, const char **unused_value)
+{
+ msg_fatal("table %s:%s: sequence operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_lock - default lock handler */
+
+static int dict_default_lock(DICT *dict, int operation)
+{
+ if (dict->lock_fd >= 0) {
+ return (myflock(dict->lock_fd, dict->lock_type, operation));
+ } else {
+ return (0);
+ }
+}
+
+/* dict_default_close - trap unimplemented operation */
+
+static void dict_default_close(DICT *dict)
+{
+ msg_fatal("table %s:%s: close operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_alloc - allocate dictionary object, initialize super-class */
+
+DICT *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size)
+{
+ DICT *dict = (DICT *) mymalloc(size);
+
+ dict->type = mystrdup(dict_type);
+ dict->name = mystrdup(dict_name);
+ dict->flags = DICT_FLAG_FIXED;
+ dict->lookup = dict_default_lookup;
+ dict->update = dict_default_update;
+ dict->delete = dict_default_delete;
+ dict->sequence = dict_default_sequence;
+ dict->close = dict_default_close;
+ dict->lock = dict_default_lock;
+ dict->lock_type = INTERNAL_LOCK;
+ dict->lock_fd = -1;
+ dict->stat_fd = -1;
+ dict->mtime = 0;
+ dict->fold_buf = 0;
+ dict->owner.status = DICT_OWNER_UNKNOWN;
+ dict->owner.uid = INT_MAX;
+ dict->error = DICT_ERR_NONE;
+ dict->jbuf = 0;
+ dict->utf8_backup = 0;
+ dict->file_buf = 0;
+ dict->file_b64 = 0;
+ return dict;
+}
+
+/* dict_free - super-class destructor */
+
+void dict_free(DICT *dict)
+{
+ myfree(dict->type);
+ myfree(dict->name);
+ if (dict->jbuf)
+ myfree((void *) dict->jbuf);
+ if (dict->utf8_backup)
+ myfree((void *) dict->utf8_backup);
+ if (dict->file_buf)
+ vstring_free(dict->file_buf);
+ if (dict->file_b64)
+ vstring_free(dict->file_b64);
+ myfree((void *) dict);
+}
+
+ /*
+ * TODO: add a dict_flags() argument to dict_alloc() and handle jump buffer
+ * allocation there.
+ */
+
+/* dict_jmp_alloc - enable exception handling */
+
+void dict_jmp_alloc(DICT *dict)
+{
+ if (dict->jbuf == 0)
+ dict->jbuf = (DICT_JMP_BUF *) mymalloc(sizeof(DICT_JMP_BUF));
+}
diff --git a/src/util/dict_cache.c b/src/util/dict_cache.c
new file mode 100644
index 0000000..d8e874d
--- /dev/null
+++ b/src/util/dict_cache.c
@@ -0,0 +1,1121 @@
+/*++
+/* NAME
+/* dict_cache 3
+/* SUMMARY
+/* External cache manager
+/* SYNOPSIS
+/* #include <dict_cache.h>
+/*
+/* DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags)
+/* const char *dbname;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* void dict_cache_close(cache)
+/* DICT_CACHE *cache;
+/*
+/* const char *dict_cache_lookup(cache, cache_key)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/*
+/* int dict_cache_update(cache, cache_key, cache_val)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/* const char *cache_val;
+/*
+/* int dict_cache_delete(cache, cache_key)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/*
+/* int dict_cache_sequence(cache, first_next, cache_key, cache_val)
+/* DICT_CACHE *cache;
+/* int first_next;
+/* const char **cache_key;
+/* const char **cache_val;
+/* AUXILIARY FUNCTIONS
+/* void dict_cache_control(cache, name, value, ...)
+/* DICT_CACHE *cache;
+/* int name;
+/*
+/* typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key,
+/* const char *cache_val, void *context);
+/*
+/* const char *dict_cache_name(cache)
+/* DICT_CACHE *cache;
+/* DESCRIPTION
+/* This module maintains external cache files with support
+/* for expiration. The underlying table must implement the
+/* "lookup", "update", "delete" and "sequence" operations.
+/*
+/* Although this API is similar to the one documented in
+/* dict_open(3), there are subtle differences in the interaction
+/* between the iterators that access all cache elements, and
+/* other operations that access individual cache elements.
+/*
+/* In particular, when a "sequence" or "cleanup" operation is
+/* in progress the cache intercepts requests to delete the
+/* "current" entry, as this would cause some databases to
+/* mis-behave. Instead, the cache implements a "delete behind"
+/* strategy, and deletes such an entry after the "sequence"
+/* or "cleanup" operation moves on to the next cache element.
+/* The "delete behind" strategy also affects the cache lookup
+/* and update operations as detailed below.
+/*
+/* dict_cache_open() is a wrapper around the dict_open()
+/* function. It opens the specified cache and returns a handle
+/* that must be used for subsequent access. This function does
+/* not return in case of error.
+/*
+/* dict_cache_close() closes the specified cache and releases
+/* memory that was allocated by dict_cache_open(), and terminates
+/* any thread that was started with dict_cache_control().
+/*
+/* dict_cache_lookup() looks up the specified cache entry.
+/* The result value is a null pointer when the cache entry was
+/* not found, or when the entry is scheduled for "delete
+/* behind".
+/*
+/* dict_cache_update() updates the specified cache entry. If
+/* the entry is scheduled for "delete behind", the delete
+/* operation is canceled (because of this, the cache must be
+/* opened with DICT_FLAG_DUP_REPLACE). This function does not
+/* return in case of error.
+/*
+/* dict_cache_delete() removes the specified cache entry. If
+/* this is the "current" entry of a "sequence" operation, the
+/* entry is scheduled for "delete behind". The result value
+/* is zero when the entry was found.
+/*
+/* dict_cache_sequence() iterates over the specified cache and
+/* returns each entry in an implementation-defined order. The
+/* result value is zero when a cache entry was found.
+/*
+/* Important: programs must not use both dict_cache_sequence()
+/* and the built-in cache cleanup feature.
+/*
+/* dict_cache_control() provides control over the built-in
+/* cache cleanup feature and logging. The arguments are a list
+/* of macros with zero or more arguments, terminated with
+/* CA_DICT_CACHE_CTL_END which has none. The following lists
+/* the macros and corresponding argument types.
+/* .IP "CA_DICT_CACHE_CTL_FLAGS(int flags)"
+/* The arguments to this command are the bit-wise OR of zero
+/* or more of the following:
+/* .RS
+/* .IP CA_DICT_CACHE_CTL_FLAG_VERBOSE
+/* Enable verbose logging of cache activity.
+/* .IP CA_DICT_CACHE_CTL_FLAG_EXP_SUMMARY
+/* Log cache statistics after each cache cleanup run.
+/* .RE
+/* .IP "CA_DICT_CACHE_CTL_INTERVAL(int interval)"
+/* The interval between cache cleanup runs. Specify a null
+/* validator or interval to stop cache cleanup.
+/* .IP "CA_DICT_CACHE_CTL_VALIDATOR(DICT_CACHE_VALIDATOR_FN validator)"
+/* An application call-back routine that returns non-zero when
+/* a cache entry should be kept. The call-back function should
+/* not make changes to the cache. Specify a null validator or
+/* interval to stop cache cleanup.
+/* .IP "CA_DICT_CACHE_CTL_CONTEXT(void *context)"
+/* Application context that is passed to the validator function.
+/* .RE
+/* .PP
+/* dict_cache_name() returns the name of the specified cache.
+/*
+/* Arguments:
+/* .IP "dbname, open_flags, dict_flags"
+/* These are passed unchanged to dict_open(). The cache must
+/* be opened with DICT_FLAG_DUP_REPLACE.
+/* .IP cache
+/* Cache handle created with dict_cache_open().
+/* .IP cache_key
+/* Cache lookup key.
+/* .IP cache_val
+/* Information that is stored under a cache lookup key.
+/* .IP first_next
+/* One of DICT_SEQ_FUN_FIRST (first cache element) or
+/* DICT_SEQ_FUN_NEXT (next cache element).
+/* .sp
+/* Note: there is no "stop" request. To ensure that the "delete
+/* behind" strategy does not interfere with database access,
+/* allow dict_cache_sequence() to run to completion.
+/* .IP table
+/* A bare dictionary handle.
+/* DIAGNOSTICS
+/* When a request is satisfied, the lookup routine returns
+/* non-null, and the update, delete and sequence routines
+/* return zero. The cache->error value is zero when a request
+/* could not be satisfied because an item did not exist (delete,
+/* sequence) or if it could not be updated. The cache->error
+/* value is non-zero only when a request could not be satisfied,
+/* and the cause was a database error.
+/*
+/* Cache access errors are logged with a warning message. To
+/* avoid spamming the log, each type of operation logs no more
+/* than one cache access error per second, per cache. Specify
+/* the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all
+/* warnings.
+/* BUGS
+/* There should be a way to suspend automatic program suicide
+/* until a cache cleanup run is completed. Some entries may
+/* never be removed when the process max_idle time is less
+/* than the time needed to make a full pass over the cache.
+/*
+/* The delete-behind strategy assumes that all updates are
+/* made by a single process. Otherwise, delete-behind may
+/* remove an entry that was updated after it was scheduled for
+/* deletion.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* A predecessor of this code was written first for the Postfix
+/* tlsmgr(8) daemon.
+/* 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>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <dict_cache.h>
+
+/* Application-specific. */
+
+ /*
+ * XXX Deleting entries while enumerating a map can he tricky. Some map
+ * types have a concept of cursor and support a "delete the current element"
+ * operation. Some map types without cursors don't behave well when the
+ * current first/next entry is deleted (example: with Berkeley DB < 2, the
+ * "next" operation produces garbage). To avoid trouble, we delete an entry
+ * after advancing the current first/next position beyond it; we use the
+ * same strategy with application requests to delete the current entry.
+ */
+
+ /*
+ * Opaque data structure. Use dict_cache_name() to access the name of the
+ * underlying database.
+ */
+struct DICT_CACHE {
+ char *name; /* full name including proxy: */
+ int cache_flags; /* see below */
+ int user_flags; /* logging */
+ DICT *db; /* database handle */
+ int error; /* last operation only */
+
+ /* Delete-behind support. */
+ char *saved_curr_key; /* "current" cache lookup key */
+ char *saved_curr_val; /* "current" cache lookup result */
+
+ /* Cleanup support. */
+ int exp_interval; /* time between cleanup runs */
+ DICT_CACHE_VALIDATOR_FN exp_validator; /* expiration call-back */
+ void *exp_context; /* call-back context */
+ int retained; /* entries retained in cleanup run */
+ int dropped; /* entries removed in cleanup run */
+
+ /* Rate-limited logging support. */
+ int log_delay;
+ time_t upd_log_stamp; /* last update warning */
+ time_t get_log_stamp; /* last lookup warning */
+ time_t del_log_stamp; /* last delete warning */
+ time_t seq_log_stamp; /* last sequence warning */
+};
+
+#define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0) /* delete-behind is scheduled */
+
+ /*
+ * Don't log cache access errors more than once per second.
+ */
+#define DC_DEF_LOG_DELAY 1
+
+ /*
+ * Macros to make obscure code more readable.
+ */
+#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \
+ ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY)
+
+#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \
+ ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0)
+
+#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \
+ (/* NOT: (cp)->saved_curr_key && */ \
+ ((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0)
+
+#define DC_CANCEL_DELETE_BEHIND(cp) \
+ ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY)
+
+ /*
+ * Special key to store the time of the last cache cleanup run completion.
+ */
+#define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_"
+
+/* dict_cache_lookup - load entry from cache */
+
+const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
+{
+ const char *myname = "dict_cache_lookup";
+ const char *cache_val;
+ DICT *db = cp->db;
+
+ /*
+ * Search for the cache entry. Don't return an entry that is scheduled
+ * for delete-behind.
+ */
+ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
+ && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (pretend not found - scheduled for deletion)",
+ myname, cache_key);
+ DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0);
+ } else {
+ cache_val = dict_get(db, cache_key);
+ if (cache_val == 0 && db->error != 0)
+ msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn,
+ "%s: cache lookup for '%s' failed due to error",
+ cp->name, cache_key);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname, cache_key,
+ cache_val ? cache_val : db->error ?
+ "error" : "(not found)");
+ DICT_ERR_VAL_RETURN(cp, db->error, cache_val);
+ }
+}
+
+/* dict_cache_update - save entry to cache */
+
+int dict_cache_update(DICT_CACHE *cp, const char *cache_key,
+ const char *cache_val)
+{
+ const char *myname = "dict_cache_update";
+ DICT *db = cp->db;
+ int put_res;
+
+ /*
+ * Store the cache entry and cancel the delete-behind operation.
+ */
+ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
+ && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: cancel delete-behind for key=%s", myname, cache_key);
+ DC_CANCEL_DELETE_BEHIND(cp);
+ }
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname, cache_key, cache_val);
+ put_res = dict_put(db, cache_key, cache_val);
+ if (put_res != 0)
+ msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not update entry for %s", cp->name, cache_key);
+ DICT_ERR_VAL_RETURN(cp, db->error, put_res);
+}
+
+/* dict_cache_delete - delete entry from cache */
+
+int dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
+{
+ const char *myname = "dict_cache_delete";
+ int del_res;
+ DICT *db = cp->db;
+
+ /*
+ * Delete the entry, unless we would delete the current first/next entry.
+ * In that case, schedule the "current" entry for delete-behind to avoid
+ * mis-behavior by some databases.
+ */
+ if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (current entry - schedule for delete-behind)",
+ myname, cache_key);
+ DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ } else {
+ del_res = dict_del(db, cache_key);
+ if (del_res != 0)
+ msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not delete entry for %s", cp->name, cache_key);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (%s)", myname, cache_key,
+ del_res == 0 ? "found" :
+ db->error ? "error" : "not found");
+ DICT_ERR_VAL_RETURN(cp, db->error, del_res);
+ }
+}
+
+/* dict_cache_sequence - look up the first/next cache entry */
+
+int dict_cache_sequence(DICT_CACHE *cp, int first_next,
+ const char **cache_key,
+ const char **cache_val)
+{
+ const char *myname = "dict_cache_sequence";
+ int seq_res;
+ const char *raw_cache_key;
+ const char *raw_cache_val;
+ char *previous_curr_key;
+ char *previous_curr_val;
+ DICT *db = cp->db;
+
+ /*
+ * Find the first or next database entry. Hide the record with the cache
+ * cleanup completion time stamp.
+ */
+ seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val);
+ if (seq_res == 0
+ && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0)
+ seq_res =
+ dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname,
+ seq_res == 0 ? raw_cache_key : db->error ?
+ "(error)" : "(not found)",
+ seq_res == 0 ? raw_cache_val : db->error ?
+ "(error)" : "(not found)");
+ if (db->error)
+ msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn,
+ "%s: sequence error", cp->name);
+
+ /*
+ * Save the current cache_key and cache_val before they are clobbered by
+ * our own delete operation below. This also prevents surprises when the
+ * application accesses the database after this function returns.
+ *
+ * We also use the saved cache_key to protect the current entry against
+ * application delete requests.
+ */
+ previous_curr_key = cp->saved_curr_key;
+ previous_curr_val = cp->saved_curr_val;
+ if (seq_res == 0) {
+ cp->saved_curr_key = mystrdup(raw_cache_key);
+ cp->saved_curr_val = mystrdup(raw_cache_val);
+ } else {
+ cp->saved_curr_key = 0;
+ cp->saved_curr_val = 0;
+ }
+
+ /*
+ * Delete behind.
+ */
+ if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) {
+ DC_CANCEL_DELETE_BEHIND(cp);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: delete-behind key=%s value=%s",
+ myname, previous_curr_key, previous_curr_val);
+ if (dict_del(db, previous_curr_key) != 0)
+ msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not delete entry for %s",
+ cp->name, previous_curr_key);
+ }
+
+ /*
+ * Clean up previous iteration key and value.
+ */
+ if (previous_curr_key)
+ myfree(previous_curr_key);
+ if (previous_curr_val)
+ myfree(previous_curr_val);
+
+ /*
+ * Return the result.
+ */
+ *cache_key = (cp)->saved_curr_key;
+ *cache_val = (cp)->saved_curr_val;
+ DICT_ERR_VAL_RETURN(cp, db->error, seq_res);
+}
+
+/* dict_cache_delete_behind_reset - reset "delete behind" state */
+
+static void dict_cache_delete_behind_reset(DICT_CACHE *cp)
+{
+#define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0)
+
+ DC_CANCEL_DELETE_BEHIND(cp);
+ FREE_AND_WIPE(cp->saved_curr_key);
+ FREE_AND_WIPE(cp->saved_curr_val);
+}
+
+/* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */
+
+static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp,
+ const char *full_partial)
+{
+ if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS)
+ msg_info("cache %s %s cleanup: retained=%d dropped=%d entries",
+ cp->name, full_partial, cp->retained, cp->dropped);
+ cp->retained = cp->dropped = 0;
+}
+
+/* dict_cache_clean_event - examine one cache entry */
+
+static void dict_cache_clean_event(int unused_event, void *cache_context)
+{
+ const char *myname = "dict_cache_clean_event";
+ DICT_CACHE *cp = (DICT_CACHE *) cache_context;
+ const char *cache_key;
+ const char *cache_val;
+ int next_interval;
+ VSTRING *stamp_buf;
+ int first_next;
+
+ /*
+ * We interleave cache cleanup with other processing, so that the
+ * application's service remains available, with perhaps increased
+ * latency.
+ */
+
+ /*
+ * Start a new cache cleanup run.
+ */
+ if (cp->saved_curr_key == 0) {
+ cp->retained = cp->dropped = 0;
+ first_next = DICT_SEQ_FUN_FIRST;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: start %s cache cleanup", myname, cp->name);
+ }
+
+ /*
+ * Continue a cache cleanup run in progress.
+ */
+ else {
+ first_next = DICT_SEQ_FUN_NEXT;
+ }
+
+ /*
+ * Examine one cache entry.
+ */
+ if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) {
+ if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) {
+ DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
+ cp->dropped++;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: drop %s cache entry for %s",
+ myname, cp->name, cache_key);
+ } else {
+ cp->retained++;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: keep %s cache entry for %s",
+ myname, cp->name, cache_key);
+ }
+ next_interval = 0;
+ }
+
+ /*
+ * Cache cleanup completed. Report vital statistics.
+ */
+ else if (cp->error != 0) {
+ msg_warn("%s: cache cleanup scan terminated due to error", cp->name);
+ dict_cache_clean_stat_log_reset(cp, "partial");
+ next_interval = cp->exp_interval;
+ } else {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: done %s cache cleanup scan", myname, cp->name);
+ dict_cache_clean_stat_log_reset(cp, "full");
+ stamp_buf = vstring_alloc(100);
+ vstring_sprintf(stamp_buf, "%ld", (long) event_time());
+ dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED,
+ vstring_str(stamp_buf));
+ vstring_free(stamp_buf);
+ next_interval = cp->exp_interval;
+ }
+ event_request_timer(dict_cache_clean_event, cache_context, next_interval);
+}
+
+/* dict_cache_control - schedule or stop the cache cleanup thread */
+
+void dict_cache_control(DICT_CACHE *cp,...)
+{
+ const char *myname = "dict_cache_control";
+ const char *last_done;
+ time_t next_interval;
+ int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval);
+ va_list ap;
+ int name;
+
+ /*
+ * Update the control settings.
+ */
+ va_start(ap, cp);
+ while ((name = va_arg(ap, int)) > 0) {
+ switch (name) {
+ case DICT_CACHE_CTL_END:
+ break;
+ case DICT_CACHE_CTL_FLAGS:
+ cp->user_flags = va_arg(ap, int);
+ cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ?
+ 0 : DC_DEF_LOG_DELAY;
+ break;
+ case DICT_CACHE_CTL_INTERVAL:
+ cp->exp_interval = va_arg(ap, int);
+ if (cp->exp_interval < 0)
+ msg_panic("%s: bad %s cache cleanup interval %d",
+ myname, cp->name, cp->exp_interval);
+ break;
+ case DICT_CACHE_CTL_VALIDATOR:
+ cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN);
+ break;
+ case DICT_CACHE_CTL_CONTEXT:
+ cp->exp_context = va_arg(ap, void *);
+ break;
+ default:
+ msg_panic("%s: bad command: %d", myname, name);
+ }
+ }
+ va_end(ap);
+
+ /*
+ * Schedule the cache cleanup thread.
+ */
+ if (cp->exp_interval && cp->exp_validator) {
+
+ /*
+ * Sanity checks.
+ */
+ if (cache_cleanup_is_active)
+ msg_panic("%s: %s cache cleanup is already scheduled",
+ myname, cp->name);
+
+ /*
+ * The next start time depends on the last completion time.
+ */
+#define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last))
+#define NOW (time((time_t *) 0)) /* NOT: event_time() */
+
+ if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0
+ || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0)
+ next_interval = 0;
+ if (next_interval > cp->exp_interval)
+ next_interval = cp->exp_interval;
+ if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0)
+ msg_info("%s cache cleanup will start after %ds",
+ cp->name, (int) next_interval);
+ event_request_timer(dict_cache_clean_event, (void *) cp,
+ (int) next_interval);
+ }
+
+ /*
+ * Cancel the cache cleanup thread.
+ */
+ else if (cache_cleanup_is_active) {
+ if (cp->retained || cp->dropped)
+ dict_cache_clean_stat_log_reset(cp, "partial");
+ dict_cache_delete_behind_reset(cp);
+ event_cancel_timer(dict_cache_clean_event, (void *) cp);
+ }
+}
+
+/* dict_cache_open - open cache file */
+
+DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags)
+{
+ DICT_CACHE *cp;
+ DICT *dict;
+
+ /*
+ * Open the database as requested. Don't attempt to second-guess the
+ * application.
+ */
+ dict = dict_open(dbname, open_flags, dict_flags);
+
+ /*
+ * Create the DICT_CACHE object.
+ */
+ cp = (DICT_CACHE *) mymalloc(sizeof(*cp));
+ cp->name = mystrdup(dbname);
+ cp->cache_flags = 0;
+ cp->user_flags = 0;
+ cp->db = dict;
+ cp->saved_curr_key = 0;
+ cp->saved_curr_val = 0;
+ cp->exp_interval = 0;
+ cp->exp_validator = 0;
+ cp->exp_context = 0;
+ cp->retained = 0;
+ cp->dropped = 0;
+ cp->log_delay = DC_DEF_LOG_DELAY;
+ cp->upd_log_stamp = cp->get_log_stamp =
+ cp->del_log_stamp = cp->seq_log_stamp = 0;
+
+ return (cp);
+}
+
+/* dict_cache_close - close cache file */
+
+void dict_cache_close(DICT_CACHE *cp)
+{
+
+ /*
+ * Destroy the DICT_CACHE object.
+ */
+ dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END);
+ myfree(cp->name);
+ dict_close(cp->db);
+ if (cp->saved_curr_key)
+ myfree(cp->saved_curr_key);
+ if (cp->saved_curr_val)
+ myfree(cp->saved_curr_val);
+ myfree((void *) cp);
+}
+
+/* dict_cache_name - get the cache name */
+
+const char *dict_cache_name(DICT_CACHE *cp)
+{
+
+ /*
+ * This is used for verbose logging or warning messages, so the cost of
+ * call is only made where needed (well sort off - code that does not
+ * execute still presents overhead for the processor pipeline, processor
+ * cache, etc).
+ */
+ return (cp->name);
+}
+
+ /*
+ * Test driver with support for interleaved access. First, enter a number of
+ * requests to look up, update or delete a sequence of cache entries, then
+ * interleave those sequences with the "run" command.
+ */
+#ifdef TEST
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <argv.h>
+#include <stringops.h>
+
+#define DELIMS " "
+#define USAGE "\n\tTo manage settings:" \
+ "\n\tverbose <level> (verbosity level)" \
+ "\n\telapsed <level> (0=don't show elapsed time)" \
+ "\n\tlmdb_map_size <limit> (initial LMDB size limit)" \
+ "\n\tcache <type>:<name> (switch to named database)" \
+ "\n\tstatus (show map size, cache, pending requests)" \
+ "\n\n\tTo manage pending requests:" \
+ "\n\treset (discard pending requests)" \
+ "\n\trun (execute pending requests in interleaved order)" \
+ "\n\n\tTo add a pending request:" \
+ "\n\tquery <key-suffix> <count> (negative to reverse order)" \
+ "\n\tupdate <key-suffix> <count> (negative to reverse order)" \
+ "\n\tdelete <key-suffix> <count> (negative to reverse order)" \
+ "\n\tpurge <key-suffix>" \
+ "\n\tcount <key-suffix>"
+
+ /*
+ * For realism, open the cache with the same flags as postscreen(8) and
+ * verify(8).
+ */
+#define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_OPEN_LOCK)
+
+ /*
+ * Storage for one request to access a sequence of cache entries.
+ */
+typedef struct DICT_CACHE_SREQ {
+ int flags; /* per-request: reverse, purge */
+ char *cmd; /* command for status report */
+ void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
+ char *suffix; /* key suffix */
+ int done; /* progress indicator */
+ int todo; /* number of entries to process */
+ int first_next; /* first/next */
+} DICT_CACHE_SREQ;
+
+#define DICT_CACHE_SREQ_FLAG_PURGE (1<<1) /* purge instead of count */
+#define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2) /* reverse instead of forward */
+
+#define DICT_CACHE_SREQ_LIMIT 10
+
+ /*
+ * All test requests combined.
+ */
+typedef struct DICT_CACHE_TEST {
+ int flags; /* exclusion flags */
+ int size; /* allocated slots */
+ int used; /* used slots */
+ DICT_CACHE_SREQ job_list[1]; /* actually, a bunch */
+} DICT_CACHE_TEST;
+
+#define DICT_CACHE_TEST_FLAG_ITER (1<<0) /* count or purge */
+
+#define STR(x) vstring_str(x)
+
+int show_elapsed = 1; /* show elapsed time */
+
+#ifdef HAS_LMDB
+extern size_t dict_lmdb_map_size; /* LMDB-specific */
+
+#endif
+
+/* usage - command-line usage message */
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s (no argument)", progname);
+}
+
+/* make_tagged_key - make tagged search key */
+
+static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp)
+{
+ if (cp->done < 0)
+ msg_panic("make_tagged_key: bad done count: %d", cp->done);
+ if (cp->todo < 1)
+ msg_panic("make_tagged_key: bad todo count: %d", cp->todo);
+ vstring_sprintf(bp, "%d-%s",
+ (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
+ cp->todo - cp->done - 1 : cp->done, cp->suffix);
+}
+
+/* create_requests - create request list */
+
+static DICT_CACHE_TEST *create_requests(int count)
+{
+ DICT_CACHE_TEST *tp;
+ DICT_CACHE_SREQ *cp;
+
+ tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) +
+ (count - 1) *sizeof(DICT_CACHE_SREQ));
+ tp->flags = 0;
+ tp->size = count;
+ tp->used = 0;
+ for (cp = tp->job_list; cp < tp->job_list + count; cp++) {
+ cp->flags = 0;
+ cp->cmd = 0;
+ cp->action = 0;
+ cp->suffix = 0;
+ cp->todo = 0;
+ cp->first_next = DICT_SEQ_FUN_FIRST;
+ }
+ return (tp);
+}
+
+/* reset_requests - reset request list */
+
+static void reset_requests(DICT_CACHE_TEST *tp)
+{
+ DICT_CACHE_SREQ *cp;
+
+ tp->flags = 0;
+ tp->used = 0;
+ for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) {
+ cp->flags = 0;
+ if (cp->cmd) {
+ myfree(cp->cmd);
+ cp->cmd = 0;
+ }
+ cp->action = 0;
+ if (cp->suffix) {
+ myfree(cp->suffix);
+ cp->suffix = 0;
+ }
+ cp->todo = 0;
+ cp->first_next = DICT_SEQ_FUN_FIRST;
+ }
+}
+
+/* free_requests - destroy request list */
+
+static void free_requests(DICT_CACHE_TEST *tp)
+{
+ reset_requests(tp);
+ myfree((void *) tp);
+}
+
+/* run_requests - execute pending requests in interleaved order */
+
+static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp)
+{
+ DICT_CACHE_SREQ *cp;
+ int todo;
+ struct timeval start;
+ struct timeval finish;
+ struct timeval elapsed;
+
+ if (dp == 0) {
+ msg_warn("no cache");
+ return;
+ }
+ GETTIMEOFDAY(&start);
+ do {
+ todo = 0;
+ for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) {
+ if (cp->done < cp->todo) {
+ todo = 1;
+ cp->action(cp, dp, bp);
+ }
+ }
+ } while (todo);
+ GETTIMEOFDAY(&finish);
+ timersub(&finish, &start, &elapsed);
+ if (show_elapsed)
+ vstream_printf("Elapsed: %g\n",
+ elapsed.tv_sec + elapsed.tv_usec / 1000000.0);
+
+ reset_requests(tp);
+}
+
+/* show_status - show settings and pending requests */
+
+static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
+{
+ DICT_CACHE_SREQ *cp;
+
+#ifdef HAS_LMDB
+ vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size);
+#endif
+ vstream_printf("cache\t%s\n", dp ? dp->name : "(none)");
+
+ if (tp->used == 0)
+ vstream_printf("No pending requests\n");
+ else
+ vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
+ "cmd", "dir", "suffix", "count", "done", "first/next");
+
+ for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++)
+ if (cp->todo > 0)
+ vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n",
+ cp->cmd,
+ (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
+ "reverse" : "forward",
+ cp->suffix ? cp->suffix : "(null)", cp->todo,
+ cp->done, cp->first_next);
+}
+
+/* query_action - lookup cache entry */
+
+static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ const char *lookup;
+
+ make_tagged_key(bp, cp);
+ if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) {
+ if (dp->error)
+ msg_warn("query_action: query failed: %s: %m", STR(bp));
+ else
+ msg_warn("query_action: query failed: %s", STR(bp));
+ } else if (strcmp(STR(bp), lookup) != 0) {
+ msg_warn("lookup result \"%s\" differs from key \"%s\"",
+ lookup, STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* update_action - update cache entry */
+
+static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ make_tagged_key(bp, cp);
+ if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) {
+ if (dp->error)
+ msg_warn("update_action: update failed: %s: %m", STR(bp));
+ else
+ msg_warn("update_action: update failed: %s", STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* delete_action - delete cache entry */
+
+static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ make_tagged_key(bp, cp);
+ if (dict_cache_delete(dp, STR(bp)) != 0) {
+ if (dp->error)
+ msg_warn("delete_action: delete failed: %s: %m", STR(bp));
+ else
+ msg_warn("delete_action: delete failed: %s", STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* iter_action - iterate over cache and act on entries with given suffix */
+
+static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ const char *cache_key;
+ const char *cache_val;
+ const char *what;
+ const char *suffix;
+
+ if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) {
+ if (strcmp(cache_key, cache_val) != 0)
+ msg_warn("value \"%s\" differs from key \"%s\"",
+ cache_val, cache_key);
+ suffix = cache_key + strspn(cache_key, "0123456789");
+ if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) {
+ cp->done += 1;
+ cp->todo = cp->done + 1; /* XXX */
+ if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE)
+ && dict_cache_delete(dp, cache_key) != 0) {
+ if (dp->error)
+ msg_warn("purge_action: delete failed: %s: %m", STR(bp));
+ else
+ msg_warn("purge_action: delete failed: %s", STR(bp));
+ }
+ }
+ cp->first_next = DICT_SEQ_FUN_NEXT;
+ } else {
+ what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count";
+ if (dp->error)
+ msg_warn("%s error after %d: %m", what, cp->done);
+ else
+ vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done);
+ cp->todo = 0;
+ }
+}
+
+ /*
+ * Table-driven support.
+ */
+typedef struct DICT_CACHE_SREQ_INFO {
+ const char *name;
+ int argc;
+ void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
+ int test_flags;
+ int req_flags;
+} DICT_CACHE_SREQ_INFO;
+
+static DICT_CACHE_SREQ_INFO req_info[] = {
+ {"query", 3, query_action},
+ {"update", 3, update_action},
+ {"delete", 3, delete_action},
+ {"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER},
+ {"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE},
+ 0,
+};
+
+/* add_request - add a request to the list */
+
+static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
+{
+ DICT_CACHE_SREQ_INFO *rp;
+ DICT_CACHE_SREQ *cp;
+ int req_flags;
+ int count;
+ char *cmd = argv->argv[0];
+ char *suffix = (argv->argc > 1 ? argv->argv[1] : 0);
+ char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */
+
+ if (tp->used >= tp->size) {
+ msg_warn("%s: request list is full", cmd);
+ return;
+ }
+ for (rp = req_info; /* See below */ ; rp++) {
+ if (rp->name == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ return;
+ }
+ if (strcmp(rp->name, argv->argv[0]) == 0
+ && rp->argc == argv->argc)
+ break;
+ }
+ req_flags = rp->req_flags;
+ if (todo[0] == '-') {
+ req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE;
+ todo += 1;
+ }
+ if (!alldig(todo) || (count = atoi(todo)) == 0) {
+ msg_warn("%s: bad count: %s", cmd, todo);
+ return;
+ }
+ if (tp->flags & rp->test_flags) {
+ msg_warn("%s: command conflicts with other command", cmd);
+ return;
+ }
+ tp->flags |= rp->test_flags;
+ cp = tp->job_list + tp->used;
+ cp->cmd = mystrdup(cmd);
+ cp->action = rp->action;
+ if (suffix)
+ cp->suffix = mystrdup(suffix);
+ cp->done = 0;
+ cp->flags = req_flags;
+ cp->todo = count;
+ tp->used += 1;
+}
+
+/* main - main program */
+
+int main(int argc, char **argv)
+{
+ DICT_CACHE_TEST *test_job;
+ VSTRING *inbuf = vstring_alloc(100);
+ char *bufp;
+ ARGV *args;
+ DICT_CACHE *cache = 0;
+ int stdin_is_tty;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 1)
+ usage(argv[0]);
+
+
+ test_job = create_requests(DICT_CACHE_SREQ_LIMIT);
+
+ stdin_is_tty = isatty(0);
+
+ for (;;) {
+ if (stdin_is_tty) {
+ vstream_printf("> ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
+ break;
+ bufp = vstring_str(inbuf);
+ if (!stdin_is_tty) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ args = argv_split(bufp, DELIMS);
+ if (argc == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) {
+ msg_verbose = atoi(args->argv[1]);
+ } else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) {
+ show_elapsed = atoi(args->argv[1]);
+#ifdef HAS_LMDB
+ } else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) {
+ dict_lmdb_map_size = atol(args->argv[1]);
+#endif
+ } else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) {
+ if (cache)
+ dict_cache_close(cache);
+ cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR,
+ DICT_CACHE_OPEN_FLAGS);
+ } else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) {
+ reset_requests(test_job);
+ } else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) {
+ run_requests(test_job, cache, inbuf);
+ } else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) {
+ show_status(test_job, cache);
+ } else {
+ add_request(test_job, args);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(args);
+ }
+
+ vstring_free(inbuf);
+ free_requests(test_job);
+ if (cache)
+ dict_cache_close(cache);
+ return (0);
+}
+
+#endif
diff --git a/src/util/dict_cache.h b/src/util/dict_cache.h
new file mode 100644
index 0000000..2d403b5
--- /dev/null
+++ b/src/util/dict_cache.h
@@ -0,0 +1,67 @@
+#ifndef _DICT_CACHE_H_INCLUDED_
+#define _DICT_CACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cache 3h
+/* SUMMARY
+/* External cache manager
+/* SYNOPSIS
+/* #include <dict_cache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+typedef struct DICT_CACHE DICT_CACHE;
+typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *, const char *, void *);
+
+extern DICT_CACHE *dict_cache_open(const char *, int, int);
+extern void dict_cache_close(DICT_CACHE *);
+extern const char *dict_cache_lookup(DICT_CACHE *, const char *);
+extern int dict_cache_update(DICT_CACHE *, const char *, const char *);
+extern int dict_cache_delete(DICT_CACHE *, const char *);
+extern int dict_cache_sequence(DICT_CACHE *, int, const char **, const char **);
+extern void dict_cache_control(DICT_CACHE *,...);
+extern const char *dict_cache_name(DICT_CACHE *);
+
+#define DICT_CACHE_FLAG_VERBOSE (1<<0) /* verbose operation */
+#define DICT_CACHE_FLAG_STATISTICS (1<<1) /* log cache statistics */
+
+/* Legacy API: type-unchecked argument, internal use. */
+#define DICT_CACHE_CTL_END 0 /* list terminator */
+#define DICT_CACHE_CTL_FLAGS 1 /* see above */
+#define DICT_CACHE_CTL_INTERVAL 2 /* cleanup interval */
+#define DICT_CACHE_CTL_VALIDATOR 3 /* call-back validator */
+#define DICT_CACHE_CTL_CONTEXT 4 /* call-back context */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_DICT_CACHE_CTL_END DICT_CACHE_CTL_END
+#define CA_DICT_CACHE_CTL_FLAGS(v) DICT_CACHE_CTL_FLAGS, CHECK_VAL(DICT_CACHE, int, (v))
+#define CA_DICT_CACHE_CTL_INTERVAL(v) DICT_CACHE_CTL_INTERVAL, CHECK_VAL(DICT_CACHE, int, (v))
+#define CA_DICT_CACHE_CTL_VALIDATOR(v) DICT_CACHE_CTL_VALIDATOR, CHECK_VAL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN, (v))
+#define CA_DICT_CACHE_CTL_CONTEXT(v) DICT_CACHE_CTL_CONTEXT, CHECK_PTR(DICT_CACHE, void, (v))
+
+CHECK_VAL_HELPER_DCL(DICT_CACHE, int);
+CHECK_VAL_HELPER_DCL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN);
+CHECK_PTR_HELPER_DCL(DICT_CACHE, void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_cdb.c b/src/util/dict_cdb.c
new file mode 100644
index 0000000..a9133cc
--- /dev/null
+++ b/src/util/dict_cdb.c
@@ -0,0 +1,446 @@
+/*++
+/* NAME
+/* dict_cdb 3
+/* SUMMARY
+/* dictionary manager interface to CDB files
+/* SYNOPSIS
+/* #include <dict_cdb.h>
+/*
+/* DICT *dict_cdb_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DESCRIPTION
+/* dict_cdb_open() opens the specified CDB database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/*
+/* Arguments:
+/* .IP path
+/* The database pathname, not including the ".cdb" suffix.
+/* .IP open_flags
+/* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
+/* .IP dict_flags
+/* Flags used by the dictionary interface.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, write error, out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include "sys_defs.h"
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "myflock.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_cdb.h"
+#include "warn_stat.h"
+
+#ifdef HAS_CDB
+
+#include <cdb.h>
+#ifndef TINYCDB_VERSION
+#include <cdb_make.h>
+#endif
+#ifndef cdb_fileno
+#define cdb_fileno(c) ((c)->fd)
+#endif
+
+#ifndef CDB_SUFFIX
+#define CDB_SUFFIX ".cdb"
+#endif
+#ifndef CDB_TMP_SUFFIX
+#define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
+#endif
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ struct cdb cdb; /* cdb structure */
+} DICT_CDBQ; /* query interface */
+
+typedef struct {
+ DICT dict; /* generic members */
+ struct cdb_make cdbm; /* cdb_make structure */
+ char *cdb_path; /* cdb pathname (.cdb) */
+ char *tmp_path; /* temporary pathname (.tmp) */
+} DICT_CDBM; /* rebuild interface */
+
+/* dict_cdbq_lookup - find database entry, query mode */
+
+static const char *dict_cdbq_lookup(DICT *dict, const char *name)
+{
+ DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
+ unsigned vlen;
+ int status = 0;
+ static char *buf;
+ static unsigned len;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /* CDB is constant, so do not try to acquire a lock. */
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * See if this CDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1);
+ if (status > 0)
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ }
+
+ /*
+ * See if this CDB file was written with no null byte appended to key and
+ * value.
+ */
+ if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ status = cdb_find(&dict_cdbq->cdb, name, strlen(name));
+ if (status > 0)
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ }
+ if (status < 0)
+ msg_fatal("error reading %s: %m", dict->name);
+
+ if (status) {
+ vlen = cdb_datalen(&dict_cdbq->cdb);
+ if (len < vlen) {
+ if (buf == 0)
+ buf = mymalloc(vlen + 1);
+ else
+ buf = myrealloc(buf, vlen + 1);
+ len = vlen;
+ }
+ if (cdb_read(&dict_cdbq->cdb, buf, vlen,
+ cdb_datapos(&dict_cdbq->cdb)) < 0)
+ msg_fatal("error reading %s: %m", dict->name);
+ buf[vlen] = '\0';
+ result = buf;
+ }
+ /* No locking so not release the lock. */
+
+ return (result);
+}
+
+/* dict_cdbq_close - close data base, query mode */
+
+static void dict_cdbq_close(DICT *dict)
+{
+ DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
+
+ cdb_free(&dict_cdbq->cdb);
+ close(dict->stat_fd);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_cdbq_open - open data base, query mode */
+
+static DICT *dict_cdbq_open(const char *path, int dict_flags)
+{
+ DICT_CDBQ *dict_cdbq;
+ struct stat st;
+ char *cdb_path;
+ int fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CDBQ_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ myfree(cdb_path); \
+ return (__d); \
+ } while (0)
+
+ cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
+
+ if ((fd = open(cdb_path, O_RDONLY)) < 0)
+ DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
+ O_RDONLY, dict_flags,
+ "open database %s: %m", cdb_path));
+
+ dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB,
+ cdb_path, sizeof(*dict_cdbq));
+#if defined(TINYCDB_VERSION)
+ if (cdb_init(&(dict_cdbq->cdb), fd) != 0)
+ msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path);
+#else
+ cdb_init(&(dict_cdbq->cdb), fd);
+#endif
+ dict_cdbq->dict.lookup = dict_cdbq_lookup;
+ dict_cdbq->dict.close = dict_cdbq_close;
+ dict_cdbq->dict.stat_fd = fd;
+ if (fstat(fd, &st) < 0)
+ msg_fatal("dict_dbq_open: fstat: %m");
+ dict_cdbq->dict.mtime = st.st_mtime;
+ dict_cdbq->dict.owner.uid = st.st_uid;
+ dict_cdbq->dict.owner.status = (st.st_uid != 0);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if (stat(path, &st) == 0
+ && st.st_mtime > dict_cdbq->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", cdb_path, path);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose to
+ * try both in query mode.
+ */
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL;
+ dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_cdbq->dict.fold_buf = vstring_alloc(10);
+
+ DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict));
+}
+
+/* dict_cdbm_update - add database entry, create mode */
+
+static int dict_cdbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
+ unsigned ksize, vsize;
+ int r;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ ksize = strlen(name);
+ vsize = strlen(value);
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ ksize++;
+ vsize++;
+ }
+
+ /*
+ * Do the add operation. No locking is done.
+ */
+#ifdef TINYCDB_VERSION
+#ifndef CDB_PUT_ADD
+#error please upgrate tinycdb to at least 0.5 version
+#endif
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ r = CDB_PUT_ADD;
+ else if (dict->flags & DICT_FLAG_DUP_REPLACE)
+ r = CDB_PUT_REPLACE;
+ else
+ r = CDB_PUT_INSERT;
+ r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r);
+ if (r < 0)
+ msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
+ else if (r > 0) {
+ if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE))
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"",
+ dict_cdbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"",
+ dict_cdbm->dict.name, name);
+ }
+ return (r);
+#else
+ if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0)
+ msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
+ return (0);
+#endif
+}
+
+/* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
+
+static void dict_cdbm_close(DICT *dict)
+{
+ DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
+ int fd = cdb_fileno(&dict_cdbm->cdbm);
+
+ /*
+ * Note: if FCNTL locking is used, closing any file descriptor on a
+ * locked file cancels all locks that the process may have on that file.
+ * CDB is FCNTL locking safe, because it uses the same file descriptor
+ * for database I/O and locking.
+ */
+ if (cdb_make_finish(&dict_cdbm->cdbm) < 0)
+ msg_fatal("finish database %s: %m", dict_cdbm->tmp_path);
+ if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0)
+ msg_fatal("rename database from %s to %s: %m",
+ dict_cdbm->tmp_path, dict_cdbm->cdb_path);
+ if (close(fd) < 0) /* releases a lock */
+ msg_fatal("close database %s: %m", dict_cdbm->cdb_path);
+ myfree(dict_cdbm->cdb_path);
+ myfree(dict_cdbm->tmp_path);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_cdbm_open - create database as file.tmp */
+
+static DICT *dict_cdbm_open(const char *path, int dict_flags)
+{
+ DICT_CDBM *dict_cdbm;
+ char *cdb_path;
+ char *tmp_path;
+ int fd;
+ struct stat st0, st1;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CDBM_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (cdb_path) \
+ myfree(cdb_path); \
+ if (tmp_path) \
+ myfree(tmp_path); \
+ return (__d); \
+ } while (0)
+
+ cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
+ tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0);
+
+ /*
+ * Repeat until we have opened *and* locked *existing* file. Since the
+ * new (tmp) file will be renamed to be .cdb file, locking here is
+ * somewhat funny to work around possible race conditions. Note that we
+ * can't open a file with O_TRUNC as we can't know if another process
+ * isn't creating it at the same time.
+ */
+ for (;;) {
+ if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
+ DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
+ O_RDWR, dict_flags,
+ "open database %s: %m",
+ tmp_path));
+ if (fstat(fd, &st0) < 0)
+ msg_fatal("fstat(%s): %m", tmp_path);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we
+ * can't have any spectators.
+ */
+ if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", tmp_path);
+
+ if (stat(tmp_path, &st1) < 0)
+ msg_fatal("stat(%s): %m", tmp_path);
+
+ /*
+ * Compare file's state before and after lock: should be the same,
+ * and nlinks should be >0, or else we opened non-existing file...
+ */
+ if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev
+ && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink
+ && st0.st_nlink > 0)
+ break; /* successfully opened */
+
+ close(fd);
+
+ }
+
+#ifndef NO_FTRUNCATE
+ if (st0.st_size)
+ ftruncate(fd, 0);
+#endif
+
+ dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path,
+ sizeof(*dict_cdbm));
+ if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0)
+ msg_fatal("initialize database %s: %m", tmp_path);
+ dict_cdbm->dict.close = dict_cdbm_close;
+ dict_cdbm->dict.update = dict_cdbm_update;
+ dict_cdbm->cdb_path = cdb_path;
+ dict_cdbm->tmp_path = tmp_path;
+ cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */
+ dict_cdbm->dict.owner.uid = st1.st_uid;
+ dict_cdbm->dict.owner.status = (st1.st_uid != 0);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default to not append a null byte when creating a cdb.
+ */
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_flags |= DICT_FLAG_TRY0NULL;
+ else if ((dict_flags & DICT_FLAG_TRY1NULL)
+ && (dict_flags & DICT_FLAG_TRY0NULL))
+ dict_flags &= ~DICT_FLAG_TRY0NULL;
+ dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_cdbm->dict.fold_buf = vstring_alloc(10);
+
+ DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict));
+}
+
+/* dict_cdb_open - open data base for query mode or create mode */
+
+DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags)
+{
+ switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
+ case O_RDONLY: /* query mode */
+ return dict_cdbq_open(path, dict_flags);
+ case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */
+ case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */
+ return dict_cdbm_open(path, dict_flags);
+ default:
+ msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
+ " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
+ }
+}
+
+#endif /* HAS_CDB */
diff --git a/src/util/dict_cdb.h b/src/util/dict_cdb.h
new file mode 100644
index 0000000..e2c13d4
--- /dev/null
+++ b/src/util/dict_cdb.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_CDB_H_INCLUDED_
+#define _DICT_CDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cdb 3h
+/* SUMMARY
+/* dictionary manager interface to CDB files
+/* SYNOPSIS
+/* #include <dict_cdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_CDB "cdb"
+
+extern DICT *dict_cdb_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif /* _DICT_CDB_H_INCLUDED_ */
diff --git a/src/util/dict_cidr.c b/src/util/dict_cidr.c
new file mode 100644
index 0000000..d29a2ba
--- /dev/null
+++ b/src/util/dict_cidr.c
@@ -0,0 +1,361 @@
+/*++
+/* NAME
+/* dict_cidr 3
+/* SUMMARY
+/* Dictionary interface for CIDR data
+/* SYNOPSIS
+/* #include <dict_cidr.h>
+/*
+/* DICT *dict_cidr_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_cidr_open() opens the named file and stores
+/* the key/value pairs where the key must be either a
+/* "naked" IP address or a netblock in CIDR notation.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Jozsef Kadlecsik
+/* kadlec@blackhole.kfki.hu
+/* KFKI Research Institute for Particle and Nuclear Physics
+/* POB. 49
+/* 1525 Budapest, Hungary
+/*
+/* 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/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <dict.h>
+#include <myaddrinfo.h>
+#include <cidr_match.h>
+#include <dict_cidr.h>
+#include <warn_stat.h>
+#include <mvect.h>
+
+/* Application-specific. */
+
+ /*
+ * Each rule in a CIDR table is parsed and stored in a linked list.
+ */
+typedef struct DICT_CIDR_ENTRY {
+ CIDR_MATCH cidr_info; /* must be first */
+ char *value; /* lookup result */
+ int lineno;
+} DICT_CIDR_ENTRY;
+
+typedef struct {
+ DICT dict; /* generic members */
+ DICT_CIDR_ENTRY *head; /* first entry */
+} DICT_CIDR;
+
+/* dict_cidr_lookup - CIDR table lookup */
+
+static const char *dict_cidr_lookup(DICT *dict, const char *key)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+
+ if (msg_verbose)
+ msg_info("dict_cidr_lookup: %s: %s", dict->name, key);
+
+ dict->error = 0;
+
+ if ((entry = (DICT_CIDR_ENTRY *)
+ cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0)
+ return (entry->value);
+ return (0);
+}
+
+/* dict_cidr_close - close the CIDR table */
+
+static void dict_cidr_close(DICT *dict)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+ DICT_CIDR_ENTRY *next;
+
+ for (entry = dict_cidr->head; entry; entry = next) {
+ next = (DICT_CIDR_ENTRY *) entry->cidr_info.next;
+ myfree(entry->value);
+ myfree((void *) entry);
+ }
+ dict_free(dict);
+}
+
+/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */
+
+static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno,
+ int nesting, VSTRING *why)
+{
+ DICT_CIDR_ENTRY *rule;
+ char *pattern;
+ char *value;
+ CIDR_MATCH cidr_info;
+ MAI_HOSTADDR_STR hostaddr;
+ int match = 1;
+
+ /*
+ * IF must be followed by a pattern.
+ */
+ if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ p += 2;
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+ trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */
+ if (cidr_match_parse_if(&cidr_info, p, match, why) != 0)
+ return (0);
+ value = "";
+ }
+
+ /*
+ * ENDIF must not be followed by other text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ p += 5;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ if (*p != 0) {
+ vstring_sprintf(why, "garbage after ENDIF");
+ return (0);
+ }
+ if (nesting == 0) {
+ vstring_sprintf(why, "ENDIF without IF");
+ return (0);
+ }
+ cidr_match_endif(&cidr_info);
+ value = "";
+ }
+
+ /*
+ * An address pattern.
+ */
+ else {
+
+ /*
+ * Process negation operators.
+ */
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+
+ /*
+ * Split the rule into key and value. We already eliminated leading
+ * whitespace, comments, empty lines or lines with whitespace only.
+ * This means a null key can't happen but we will handle this anyway.
+ */
+ pattern = p;
+ while (*p && !ISSPACE(*p)) /* Skip over key */
+ p++;
+ if (*p) /* Terminate key */
+ *p++ = 0;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ value = p;
+ trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */
+ if (*pattern == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+
+ /*
+ * Parse the pattern, destroying it in the process.
+ */
+ if (cidr_match_parse(&cidr_info, pattern, match, why) != 0)
+ return (0);
+
+ if (*value == 0) {
+ vstring_sprintf(why, "no lookup result");
+ return (0);
+ }
+ }
+
+ /*
+ * Optionally replace the value file the contents of a file.
+ */
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ vstring_strcpy(why, err);
+ myfree(err);
+ return (0);
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY));
+ rule->cidr_info = cidr_info;
+ rule->value = mystrdup(value);
+ rule->lineno = lineno;
+
+ if (msg_verbose) {
+ if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
+ hostaddr.buf, sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ msg_info("dict_cidr_open: add %s/%d %s",
+ hostaddr.buf, cidr_info.mask_shift, rule->value);
+ }
+ return (rule);
+}
+
+/* dict_cidr_open - parse CIDR table */
+
+DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_cidr_open";
+ DICT_CIDR *dict_cidr;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *line_buffer = 0;
+ VSTRING *why = 0;
+ DICT_CIDR_ENTRY *rule;
+ DICT_CIDR_ENTRY *last_rule = 0;
+ int last_line = 0;
+ int lineno;
+ int nesting = 0;
+ DICT_CIDR_ENTRY **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CIDR_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (map_fp != 0 && vstream_fclose(map_fp)) \
+ msg_fatal("cidr map %s: read error: %m", mapname); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_CIDR, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+ why = vstring_alloc(100);
+
+ /*
+ * XXX Eliminate unnecessary queries by setting a flag that says "this
+ * map matches network addresses only".
+ */
+ dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname,
+ sizeof(*dict_cidr));
+ dict_cidr->dict.lookup = dict_cidr_lookup;
+ dict_cidr->dict.close = dict_cidr_close;
+ dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dict_cidr->head = 0;
+
+ dict_cidr->dict.owner.uid = st.st_uid;
+ dict_cidr->dict.owner.status = (st.st_uid != 0);
+
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ rule = dict_cidr_parse_rule(&dict_cidr->dict,
+ vstring_str(line_buffer), lineno,
+ nesting, why);
+ if (rule == 0) {
+ msg_warn("cidr map %s, line %d: %s: skipping this rule",
+ mapname, lineno, vstring_str(why));
+ continue;
+ }
+ if (rule->cidr_info.op == CIDR_MATCH_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) {
+ DICT_CIDR_ENTRY *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_cidr_parse_rule(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if_rule = rule_stack[nesting];
+ if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, if_rule->cidr_info.op);
+ if_rule->cidr_info.block_end = &(rule->cidr_info);
+ }
+ if (last_rule == 0)
+ dict_cidr->head = rule;
+ else
+ last_rule->cidr_info.next = &(rule->cidr_info);
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("cidr map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ dict_file_purge_buffers(&dict_cidr->dict);
+ DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict));
+}
diff --git a/src/util/dict_cidr.h b/src/util/dict_cidr.h
new file mode 100644
index 0000000..308a765
--- /dev/null
+++ b/src/util/dict_cidr.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_CIDR_H_INCLUDED_
+#define _DICT_CIDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cidr 3h
+/* SUMMARY
+/* Dictionary manager interface to handle cidr data.
+/* SYNOPSIS
+/* #include <dict_cidr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+extern DICT *dict_cidr_open(const char *, int, int);
+
+#define DICT_TYPE_CIDR "cidr"
+
+/* 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
+/*
+/* Jozsef Kadlecsik
+/* kadlec@blackhole.kfki.hu
+/* KFKI Research Institute for Particle and Nuclear Physics
+/* POB. 49
+/* 1525 Budapest 114, Hungary
+/*--*/
+
+#endif
diff --git a/src/util/dict_cidr.in b/src/util/dict_cidr.in
new file mode 100644
index 0000000..ee20c17
--- /dev/null
+++ b/src/util/dict_cidr.in
@@ -0,0 +1,23 @@
+get 172.16.0.0
+get 172.16.0.1
+get 172.16.7.255
+get 172.16.8.1
+get 172.16.17.1
+get 172.17.1.1
+get 172.17.1.2
+get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+get 2001:240:5c7:0:2d0:b7ff:febe:ca9f
+get 1.1.1.1
+get 1:1:1:1:1:1:1:1
+get 1.2.3.3
+get 1.2.3.4
+get 1.2.3.5
+get 1.2.3.6
+get 1.2.3.7
+get 1.2.3.8
+get ::f3
+get ::f4
+get ::f5
+get ::f6
+get ::f7
+get ::f8
diff --git a/src/util/dict_cidr.map b/src/util/dict_cidr.map
new file mode 100644
index 0000000..ac0f9b5
--- /dev/null
+++ b/src/util/dict_cidr.map
@@ -0,0 +1,50 @@
+172.16.0.0/21 554 match bad netblock 172.16.0.0/21
+172.16.8.0/21 554 match bad netblock 172.16.8.0/21
+172.16.0.0/16 554 match bad netblock 172.16.0.0/16
+172.17.1.1 554 match bad naked address
+172.16.1.3/21 whatever
+172.16.1.3/33 whatever
+172.999.0.0/21 whatever
+172.16.1.999 whatever
+172.16.1.4
+if 1.2.0.0/16
+if 1.2.3.4/30
+1.2.3.3 1.2.3.3 can't happen
+1.2.3.4 1.2.3.4 can happen
+1.2.3.5 1.2.3.5 can happen
+1.2.3.6 1.2.3.6 can happen
+1.2.3.7 1.2.3.7 can happen
+1.2.3.8 1.2.3.8 can't happen
+endif
+endif
+if !1.2.3.4/30
+1.2.3.3 1.2.3.3 can happen
+1.2.3.8 1.2.3.8 can happen
+endif
+if ::f4/126
+::f3 ::f3 can't happen
+::f4 ::f4 can happen
+::f5 ::f5 can happen
+::f6 ::f6 can happen
+::f7 ::f7 can happen
+::f8 ::f8 can't happen
+endif
+if !::f4/126
+::f3 ::f3 can happen
+::f8 ::f8 can happen
+endif
+2001:240:5c7:0:2d0:b7ff:fe88:2ca7 match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+2001:240:5c7::/64 match netblock 2001:240:5c7::/64
+1.0.0.0/0 match 0.0.0.0/0
+! ! 0.0.0.0/0 match 0.0.0.0/0
+1::/0 match ::/0
+::/0 match ::/0
+[1234 can't happen
+[1234]junk can't happen
+172.16.1.3/3x can't happen
+endif
+endif
+if 1:2::3:4
+if 1:2::3:5
+if !
+!
diff --git a/src/util/dict_cidr.ref b/src/util/dict_cidr.ref
new file mode 100644
index 0000000..305e3fd
--- /dev/null
+++ b/src/util/dict_cidr.ref
@@ -0,0 +1,63 @@
+./dict_open: warning: cidr map dict_cidr.map, line 5: non-null host address bits in "172.16.1.3/21", perhaps you should use "172.16.0.0/21" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 6: bad mask length in "172.16.1.3/33": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 7: bad network value in "172.999.0.0/21": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 38: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 40: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 42: missing ']' character after "[1234": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 43: garbage after "[1234]": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 44: bad mask value in "172.16.1.3/3x": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 45: ENDIF without IF: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 46: ENDIF without IF: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 49: no address pattern: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 50: no address pattern: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 48: IF has no matching ENDIF
+./dict_open: warning: cidr map dict_cidr.map, line 47: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 172.16.0.0
+172.16.0.0=554 match bad netblock 172.16.0.0/21
+> get 172.16.0.1
+172.16.0.1=554 match bad netblock 172.16.0.0/21
+> get 172.16.7.255
+172.16.7.255=554 match bad netblock 172.16.0.0/21
+> get 172.16.8.1
+172.16.8.1=554 match bad netblock 172.16.8.0/21
+> get 172.16.17.1
+172.16.17.1=554 match bad netblock 172.16.0.0/16
+> get 172.17.1.1
+172.17.1.1=554 match bad naked address
+> get 172.17.1.2
+172.17.1.2=match 0.0.0.0/0
+> get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+2001:240:5c7:0:2d0:b7ff:fe88:2ca7=match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+> get 2001:240:5c7:0:2d0:b7ff:febe:ca9f
+2001:240:5c7:0:2d0:b7ff:febe:ca9f=match netblock 2001:240:5c7::/64
+> get 1.1.1.1
+1.1.1.1=match 0.0.0.0/0
+> get 1:1:1:1:1:1:1:1
+1:1:1:1:1:1:1:1=match ::/0
+> get 1.2.3.3
+1.2.3.3=1.2.3.3 can happen
+> get 1.2.3.4
+1.2.3.4=1.2.3.4 can happen
+> get 1.2.3.5
+1.2.3.5=1.2.3.5 can happen
+> get 1.2.3.6
+1.2.3.6=1.2.3.6 can happen
+> get 1.2.3.7
+1.2.3.7=1.2.3.7 can happen
+> get 1.2.3.8
+1.2.3.8=1.2.3.8 can happen
+> get ::f3
+::f3=::f3 can happen
+> get ::f4
+::f4=::f4 can happen
+> get ::f5
+::f5=::f5 can happen
+> get ::f6
+::f6=::f6 can happen
+> get ::f7
+::f7=::f7 can happen
+> get ::f8
+::f8=::f8 can happen
diff --git a/src/util/dict_cidr_file.in b/src/util/dict_cidr_file.in
new file mode 100644
index 0000000..531d735
--- /dev/null
+++ b/src/util/dict_cidr_file.in
@@ -0,0 +1,3 @@
+get 1.1.1.1
+get 2.2.2.2
+get 3.3.3.3
diff --git a/src/util/dict_cidr_file.map b/src/util/dict_cidr_file.map
new file mode 100644
index 0000000..e85e8ca
--- /dev/null
+++ b/src/util/dict_cidr_file.map
@@ -0,0 +1,3 @@
+1.1.1.1 dict_cidr_file1
+2.2.2.2 dict_cidr_file2
+3.3.3.3 dict_cidr_file3
diff --git a/src/util/dict_cidr_file.ref b/src/util/dict_cidr_file.ref
new file mode 100644
index 0000000..3e9e792
--- /dev/null
+++ b/src/util/dict_cidr_file.ref
@@ -0,0 +1,8 @@
+./dict_open: warning: cidr map dict_cidr_file.map, line 3: open dict_cidr_file3: No such file or directory: skipping this rule
+owner=untrusted (uid=USER)
+> get 1.1.1.1
+1.1.1.1=dGhpcy1pcy1maWxlMQo=
+> get 2.2.2.2
+2.2.2.2=dGhpcy1pcy1maWxlMgo=
+> get 3.3.3.3
+3.3.3.3: not found
diff --git a/src/util/dict_db.c b/src/util/dict_db.c
new file mode 100644
index 0000000..b2d0c33
--- /dev/null
+++ b/src/util/dict_db.c
@@ -0,0 +1,880 @@
+/*++
+/* NAME
+/* dict_db 3
+/* SUMMARY
+/* dictionary manager interface to DB files
+/* SYNOPSIS
+/* #include <dict_db.h>
+/*
+/* extern int dict_db_cache_size;
+/*
+/* DEFINE_DICT_DB_CACHE_SIZE;
+/*
+/* DICT *dict_hash_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DICT *dict_btree_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_XXX_open() opens the specified DB database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/*
+/* The dict_db_cache_size variable specifies a non-default per-table
+/* I/O buffer size. The default buffer size is adequate for reading.
+/* For better performance while creating a large table, specify a large
+/* buffer size before opening the file.
+/*
+/* This variable cannot be exported via the dict(3) API and
+/* must therefore be defined in the calling program by invoking
+/* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level.
+/*
+/* Arguments:
+/* .IP path
+/* The database pathname, not including the ".db" suffix.
+/* .IP open_flags
+/* Flags passed to dbopen().
+/* .IP dict_flags
+/* Flags used by the dictionary interface.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, write error, out of memory.
+/* 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
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_DB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <limits.h>
+#ifdef PATH_DB_H
+#include PATH_DB_H
+#else
+#include <db.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK)
+#error "Error: this system must not use the db 1.85 compatibility interface"
+#endif
+
+#ifndef DB_VERSION_MAJOR
+#define DB_VERSION_MAJOR 1
+#define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag)
+#define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag)
+#define DICT_DB_DEL(db, key, flag) db->del(db, key, flag)
+#define DICT_DB_SYNC(db, flag) db->sync(db, flag)
+#define DICT_DB_CLOSE(db) db->close(db)
+#define DONT_CLOBBER R_NOOVERWRITE
+#endif
+
+#if DB_VERSION_MAJOR > 1
+#define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag))
+#define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag))
+#define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag))
+#define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0)
+#define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0)
+#define DONT_CLOBBER DB_NOOVERWRITE
+#endif
+
+#if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6)
+#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs))
+#else
+#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0)
+#endif
+
+#ifndef DB_FCNTL_LOCKING
+#define DB_FCNTL_LOCKING 0
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "myflock.h"
+#include "dict.h"
+#include "dict_db.h"
+#include "warn_stat.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ DB *db; /* open db file */
+#if DB_VERSION_MAJOR > 2
+ DB_ENV *dbenv;
+#endif
+#if DB_VERSION_MAJOR > 1
+ DBC *cursor; /* dict_db_sequence() */
+#endif
+ VSTRING *key_buf; /* key result */
+ VSTRING *val_buf; /* value result */
+} DICT_DB;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+#define DICT_DB_NELM 4096
+
+#if DB_VERSION_MAJOR > 1
+
+/* sanitize - sanitize db_get/put/del result */
+
+static int sanitize(int status)
+{
+
+ /*
+ * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize
+ * results into non-fatal errors (i.e., errors that we can deal with),
+ * success, or fatal error (i.e., all other errors).
+ */
+ switch (status) {
+
+ case DB_NOTFOUND: /* get, del */
+ case DB_KEYEXIST: /* put */
+ return (1); /* non-fatal */
+
+ case 0:
+ return (0); /* success */
+
+ case DB_KEYEMPTY: /* get, others? */
+ status = EINVAL;
+ /* FALLTHROUGH */
+ default:
+ errno = status;
+ return (-1); /* fatal */
+ }
+}
+
+#endif
+
+/* dict_db_lookup - find database entry */
+
+static const char *dict_db_lookup(DICT *dict, const char *name)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * See if this DB file was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name) + 1;
+ if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
+ msg_fatal("error reading %s: %m", dict_db->dict.name);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ }
+
+ /*
+ * See if this DB file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name);
+ if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
+ msg_fatal("error reading %s: %m", dict_db->dict.name);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return (result);
+}
+
+/* dict_db_update - add or update database entry */
+
+static int dict_db_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+ db_key.data = (void *) name;
+ db_value.data = (void *) value;
+ db_key.size = strlen(name);
+ db_value.size = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DB_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.size++;
+ db_value.size++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = DICT_DB_PUT(db, &db_key, &db_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0)
+ msg_fatal("error writing %s: %m", dict_db->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
+ }
+ if (dict->flags & DICT_FLAG_SYNC_UPDATE)
+ if (DICT_DB_SYNC(db, 0) < 0)
+ msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return (status);
+}
+
+/* delete one entry from the dictionary */
+
+static int dict_db_delete(DICT *dict, const char *name)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ int status = 1;
+ int flags = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ memset(&db_key, 0, sizeof(db_key));
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * See if this DB file was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name) + 1;
+ if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
+ msg_fatal("error deleting from %s: %m", dict_db->dict.name);
+ if (status == 0)
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ }
+
+ /*
+ * See if this DB file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name);
+ if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
+ msg_fatal("error deleting from %s: %m", dict_db->dict.name);
+ if (status == 0)
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ }
+ if (dict->flags & DICT_FLAG_SYNC_UPDATE)
+ if (DICT_DB_SYNC(db, 0) < 0)
+ msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return status;
+}
+
+/* dict_db_sequence - traverse the dictionary */
+
+static int dict_db_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_db_sequence";
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status = 0;
+ int db_function;
+
+ dict->error = 0;
+
+#if DB_VERSION_MAJOR > 1
+
+ /*
+ * Initialize.
+ */
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+
+ /*
+ * Determine the function.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ if (dict_db->cursor == 0)
+ DICT_DB_CURSOR(db, &(dict_db->cursor));
+ db_function = DB_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ if (dict_db->cursor == 0)
+ msg_panic("%s: no cursor", myname);
+ db_function = DB_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Database lookup.
+ */
+ status =
+ dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function);
+ if (status != 0 && status != DB_NOTFOUND)
+ msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name);
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ if (status == 0) {
+
+ /*
+ * Copy the result so it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
+ *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ return (status);
+#else
+
+ /*
+ * determine the function
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ db_function = R_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ db_function = R_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0)
+ msg_fatal("error seeking %s: %m", dict_db->dict.name);
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ if (status == 0) {
+
+ /*
+ * Copy the result so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
+ *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ return status;
+#endif
+}
+
+/* dict_db_close - close data base */
+
+static void dict_db_close(DICT *dict)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+
+#if DB_VERSION_MAJOR > 1
+ if (dict_db->cursor)
+ dict_db->cursor->c_close(dict_db->cursor);
+#endif
+ if (DICT_DB_SYNC(dict_db->db, 0) < 0)
+ msg_fatal("flush database %s: %m", dict_db->dict.name);
+
+ /*
+ * With some Berkeley DB implementations, close fails with a bogus ENOENT
+ * error, while it reports no errors with put+sync, no errors with
+ * del+sync, and no errors with the sync operation just before this
+ * comment. This happens in programs that never fork and that never share
+ * the database with other processes. The bogus close error has been
+ * reported for programs that use the first/next iterator. Instead of
+ * making Postfix look bad because it reports errors that other programs
+ * ignore, I'm going to report the bogus error as a non-error.
+ */
+ if (DICT_DB_CLOSE(dict_db->db) < 0)
+ msg_info("close database %s: %m (possible Berkeley DB bug)",
+ dict_db->dict.name);
+#if DB_VERSION_MAJOR > 2
+ dict_db->dbenv->close(dict_db->dbenv, 0);
+#endif
+ if (dict_db->key_buf)
+ vstring_free(dict_db->key_buf);
+ if (dict_db->val_buf)
+ vstring_free(dict_db->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+#if DB_VERSION_MAJOR > 2
+
+/* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */
+
+static DB_ENV *dict_db_new_env(const char *db_path)
+{
+ VSTRING *db_home_buf;
+ DB_ENV *dbenv;
+ u_int32_t cache_size_gbytes;
+ u_int32_t cache_size_bytes;
+ int ncache;
+
+ if ((errno = db_env_create(&dbenv, 0)) != 0)
+ msg_fatal("create DB environment: %m");
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7)
+ if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes,
+ &cache_size_bytes, &ncache)) != 0)
+ msg_fatal("get DB cache size: %m");
+ if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) {
+ if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes,
+ dict_db_cache_size)) != 0)
+ msg_fatal("set DB max cache size %d: %m", dict_db_cache_size);
+ if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes,
+ dict_db_cache_size, ncache)) != 0)
+ msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
+ }
+#endif
+ /* XXX db_home is also the default directory for the .db file. */
+ db_home_buf = vstring_alloc(100);
+ if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path),
+ DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0)
+ msg_fatal("open DB environment: %m");
+ vstring_free(db_home_buf);
+ return (dbenv);
+}
+
+#endif
+
+/* dict_db_open - open data base */
+
+static DICT *dict_db_open(const char *class, const char *path, int open_flags,
+ int type, void *tweak, int dict_flags)
+{
+ DICT_DB *dict_db;
+ struct stat st;
+ DB *db = 0;
+ char *db_path = 0;
+ VSTRING *db_base_buf = 0;
+ int lock_fd = -1;
+ int dbfd;
+
+#if DB_VERSION_MAJOR > 1
+ int db_flags;
+
+#endif
+#if DB_VERSION_MAJOR > 2
+ DB_ENV *dbenv = 0;
+
+#endif
+
+ /*
+ * Mismatches between #include file and library are a common cause for
+ * trouble.
+ */
+#if DB_VERSION_MAJOR > 1
+ int major_version;
+ int minor_version;
+ int patch_version;
+
+ (void) db_version(&major_version, &minor_version, &patch_version);
+ if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
+ return (dict_surrogate(class, path, open_flags, dict_flags,
+ "incorrect version of Berkeley DB: "
+ "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
+ major_version, minor_version, patch_version));
+ if (msg_verbose) {
+ msg_info("Compiled against Berkeley DB: %d.%d.%d\n",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH);
+ msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n",
+ major_version, minor_version, patch_version);
+ }
+#else
+ if (msg_verbose)
+ msg_info("Compiled against Berkeley DB version 1");
+#endif
+
+ db_path = concatenate(path, ".db", (char *) 0);
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ *
+ * XXX DB version 4.1 will not open a zero-length file. This means we must
+ * open an existing file without O_CREAT|O_TRUNC, and that we must let
+ * db_open() create a non-existent file for us.
+ */
+#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
+#if DB_VERSION_MAJOR <= 2
+#define FREE_RETURN(e) do { \
+ DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
+ if (lock_fd >= 0) (void) close(lock_fd); \
+ if (db_base_buf) vstring_free(db_base_buf); \
+ if (db_path) myfree(db_path); return (_dict); \
+ } while (0)
+#else
+#define FREE_RETURN(e) do { \
+ DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
+ if (dbenv) dbenv->close(dbenv, 0); \
+ if (lock_fd >= 0) (void) close(lock_fd); \
+ if (db_base_buf) vstring_free(db_base_buf); \
+ if (db_path) myfree(db_path); \
+ return (_dict); \
+ } while (0)
+#endif
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
+ if (errno != ENOENT)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ } else {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", db_path);
+ }
+ }
+
+ /*
+ * Use the DB 1.x programming interface. This is the default interface
+ * with 4.4BSD systems. It is also available via the db_185 compatibility
+ * interface, but that interface does not have the undocumented feature
+ * that we need to make file locking safe with POSIX fcntl() locking.
+ */
+#if DB_VERSION_MAJOR < 2
+ if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ dbfd = db->fd(db);
+#endif
+
+ /*
+ * Use the DB 2.x programming interface. Jump a couple extra hoops.
+ */
+#if DB_VERSION_MAJOR == 2
+ db_flags = DB_FCNTL_LOCKING;
+ if (open_flags == O_RDONLY)
+ db_flags |= DB_RDONLY;
+ if (open_flags & O_CREAT)
+ db_flags |= DB_CREATE;
+ if (open_flags & O_TRUNC)
+ db_flags |= DB_TRUNCATE;
+ if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ if (db == 0)
+ msg_panic("db_open null result");
+ if ((errno = db->fd(db, &dbfd)) != 0)
+ msg_fatal("get database file descriptor: %m");
+#endif
+
+ /*
+ * Use the DB 3.x programming interface. Jump even more hoops.
+ */
+#if DB_VERSION_MAJOR > 2
+ db_flags = DB_FCNTL_LOCKING;
+ if (open_flags == O_RDONLY)
+ db_flags |= DB_RDONLY;
+ if (open_flags & O_CREAT)
+ db_flags |= DB_CREATE;
+ if (open_flags & O_TRUNC)
+ db_flags |= DB_TRUNCATE;
+ if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0)
+ msg_fatal("create DB database: %m");
+ if (db == 0)
+ msg_panic("db_create null result");
+ if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
+ msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
+ db_base_buf = vstring_alloc(100);
+#if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \
+ (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
+ if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path),
+ 0, type, db_flags, 0644)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
+ if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0,
+ type, db_flags, 0644)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+#else
+#error "Unsupported Berkeley DB version"
+#endif
+ vstring_free(db_base_buf);
+ if ((errno = db->fd(db, &dbfd)) != 0)
+ msg_fatal("get database file descriptor: %m");
+#endif
+ if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", db_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", db_path);
+ lock_fd = -1;
+ }
+ dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db));
+ dict_db->dict.lookup = dict_db_lookup;
+ dict_db->dict.update = dict_db_update;
+ dict_db->dict.delete = dict_db_delete;
+ dict_db->dict.sequence = dict_db_sequence;
+ dict_db->dict.close = dict_db_close;
+ dict_db->dict.lock_fd = dbfd;
+ dict_db->dict.stat_fd = dbfd;
+ if (fstat(dict_db->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_db_open: fstat: %m");
+ dict_db->dict.mtime = st.st_mtime;
+ dict_db->dict.owner.uid = st.st_uid;
+ dict_db->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_db->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", db_path, path);
+
+ close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC);
+ close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC);
+ dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_db->dict.fold_buf = vstring_alloc(10);
+ dict_db->db = db;
+#if DB_VERSION_MAJOR > 2
+ dict_db->dbenv = dbenv;
+#endif
+#if DB_VERSION_MAJOR > 1
+ dict_db->cursor = 0;
+#endif
+ dict_db->key_buf = 0;
+ dict_db->val_buf = 0;
+
+ myfree(db_path);
+ return (DICT_DEBUG (&dict_db->dict));
+}
+
+/* dict_hash_open - create association with data base */
+
+DICT *dict_hash_open(const char *path, int open_flags, int dict_flags)
+{
+#if DB_VERSION_MAJOR < 2
+ HASHINFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.nelem = DICT_DB_NELM;
+ tweak.cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR == 2
+ DB_INFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.h_nelem = DICT_DB_NELM;
+ tweak.db_cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR > 2
+ void *tweak;
+
+ tweak = 0;
+#endif
+ return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH,
+ (void *) &tweak, dict_flags));
+}
+
+/* dict_btree_open - create association with data base */
+
+DICT *dict_btree_open(const char *path, int open_flags, int dict_flags)
+{
+#if DB_VERSION_MAJOR < 2
+ BTREEINFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR == 2
+ DB_INFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.db_cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR > 2
+ void *tweak;
+
+ tweak = 0;
+#endif
+
+ return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE,
+ (void *) &tweak, dict_flags));
+}
+
+#endif
diff --git a/src/util/dict_db.h b/src/util/dict_db.h
new file mode 100644
index 0000000..cc14dc8
--- /dev/null
+++ b/src/util/dict_db.h
@@ -0,0 +1,55 @@
+#ifndef _DICT_DB_H_INCLUDED_
+#define _DICT_DB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_db 3h
+/* SUMMARY
+/* dictionary manager interface to DB files
+/* SYNOPSIS
+/* #include <dict_db.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_HASH "hash"
+#define DICT_TYPE_BTREE "btree"
+
+extern DICT *dict_hash_open(const char *, int, int);
+extern DICT *dict_btree_open(const char *, int, int);
+
+ /*
+ * XXX Should be part of the DICT interface.
+ *
+ * You can override the default dict_db_cache_size setting before calling
+ * dict_hash_open() or dict_btree_open(). This is done in mkmap_db_open() to
+ * set a larger memory pool for database (re)builds.
+ */
+extern int dict_db_cache_size;
+
+#define DEFINE_DICT_DB_CACHE_SIZE int dict_db_cache_size = (128 * 1024)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_dbm.c b/src/util/dict_dbm.c
new file mode 100644
index 0000000..e47b7ee
--- /dev/null
+++ b/src/util/dict_dbm.c
@@ -0,0 +1,503 @@
+/*++
+/* NAME
+/* dict_dbm 3
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/*
+/* DICT *dict_dbm_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_dbm_open() opens the named DBM database and makes it available
+/* via the generic interface described in dict_open(3).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* ndbm(3) data base subroutines
+/* 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
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_DBM
+
+/* System library. */
+
+#include <sys/stat.h>
+#ifdef PATH_NDBM_H
+#include PATH_NDBM_H
+#else
+#include <ndbm.h>
+#endif
+#ifdef R_FIRST
+#error "Error: you are including the Berkeley DB version of ndbm.h"
+#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file"
+#endif
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "htable.h"
+#include "iostuff.h"
+#include "vstring.h"
+#include "myflock.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_dbm.h"
+#include "warn_stat.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ DBM *dbm; /* open database */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* result buffer */
+} DICT_DBM;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+/* dict_dbm_lookup - find database entry */
+
+static const char *dict_dbm_lookup(DICT *dict, const char *name)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (result);
+}
+
+/* dict_dbm_update - add or update database entry */
+
+static int dict_dbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ dbm_key.dptr = (void *) name;
+ dbm_value.dptr = (void *) value;
+ dbm_key.dsize = strlen(name);
+ dbm_value.dsize = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DBM_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dsize++;
+ dbm_value.dsize++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
+ msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name);
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* dict_dbm_delete - delete one entry from the dictionary */
+
+static int dict_dbm_delete(DICT *dict, const char *name)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ int status = 1;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_clearerr(dict_dbm->dbm);
+ if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
+ if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_clearerr(dict_dbm->dbm);
+ if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
+ if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_dbm_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_dbm_sequence";
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * Determine and execute the seek function. It returns the key.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ dbm_key = dbm_firstkey(dict_dbm->dbm);
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ dbm_key = dbm_nextkey(dict_dbm->dbm);
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
+
+ /*
+ * Copy the key so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize);
+
+ /*
+ * Fetch the corresponding value.
+ */
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+
+ if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
+
+ /*
+ * Copy the value so that it is guaranteed null terminated.
+ */
+ *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ status = 0;
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error
+ * condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
+ status = 1; /* no error: eof/not found
+ * (should not happen!) */
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
+ status = 1; /* no error: eof/not found */
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* dict_dbm_close - disassociate from data base */
+
+static void dict_dbm_close(DICT *dict)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+
+ dbm_close(dict_dbm->dbm);
+ if (dict_dbm->key_buf)
+ vstring_free(dict_dbm->key_buf);
+ if (dict_dbm->val_buf)
+ vstring_free(dict_dbm->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_dbm_open - open DBM data base */
+
+DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_DBM *dict_dbm;
+ struct stat st;
+ DBM *dbm;
+ char *dbm_path = 0;
+ int lock_fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_DBM_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (dbm_path != 0) \
+ myfree(dbm_path); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ */
+ if (dict_flags & DICT_FLAG_LOCK) {
+ dbm_path = concatenate(path, ".dir", (char *) 0);
+ if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
+ DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path,
+ open_flags, dict_flags,
+ "open database %s: %m",
+ dbm_path));
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", dbm_path);
+ }
+
+ /*
+ * XXX SunOS 5.x has no const in dbm_open() prototype.
+ */
+ if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0)
+ DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path,
+ open_flags, dict_flags,
+ "open database %s.{dir,pag}: %m",
+ path));
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", dbm_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", dbm_path);
+ }
+ dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm));
+ dict_dbm->dict.lookup = dict_dbm_lookup;
+ dict_dbm->dict.update = dict_dbm_update;
+ dict_dbm->dict.delete = dict_dbm_delete;
+ dict_dbm->dict.sequence = dict_dbm_sequence;
+ dict_dbm->dict.close = dict_dbm_close;
+ dict_dbm->dict.lock_fd = dbm_dirfno(dbm);
+ dict_dbm->dict.stat_fd = dbm_pagfno(dbm);
+ if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd)
+ msg_fatal("open database %s: cannot support GDBM", path);
+ if (fstat(dict_dbm->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_dbm_open: fstat: %m");
+ dict_dbm->dict.mtime = st.st_mtime;
+ dict_dbm->dict.owner.uid = st.st_uid;
+ dict_dbm->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_dbm->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", dbm_path, path);
+
+ close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC);
+ close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC);
+ dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_dbm->dict.fold_buf = vstring_alloc(10);
+ dict_dbm->dbm = dbm;
+ dict_dbm->key_buf = 0;
+ dict_dbm->val_buf = 0;
+
+ DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict));
+}
+
+#endif
diff --git a/src/util/dict_dbm.h b/src/util/dict_dbm.h
new file mode 100644
index 0000000..ecaab62
--- /dev/null
+++ b/src/util/dict_dbm.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_DBM_H_INCLUDED_
+#define _DICT_DBM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_dbm 3h
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_DBM "dbm"
+
+extern DICT *dict_dbm_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_debug.c b/src/util/dict_debug.c
new file mode 100644
index 0000000..46634d4
--- /dev/null
+++ b/src/util/dict_debug.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dict_debug 3
+/* SUMMARY
+/* dictionary manager, logging proxy
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_debug(dict_handle)
+/* DICT *dict_handle;
+/*
+/* DICT *DICT_DEBUG(dict_handle)
+/* DICT *dict_handle;
+/* DESCRIPTION
+/* dict_debug() encapsulates the given dictionary object and returns
+/* a proxy object that logs all access to the encapsulated object.
+/* This is more convenient than having to add logging capability
+/* to each individual dictionary access method.
+/*
+/* DICT_DEBUG() is an unsafe macro that returns the original object if
+/* the object's debugging flag is not set, and that otherwise encapsulates
+/* the object with dict_debug(). This macro simplifies usage by avoiding
+/* clumsy expressions. The macro evaluates its argument multiple times.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* the proxy service */
+ DICT *real_dict; /* encapsulated object */
+} DICT_DEBUG;
+
+/* dict_debug_lookup - log lookup operation */
+
+static const char *dict_debug_lookup(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ const char *result;
+
+ real_dict->flags = dict->flags;
+ result = dict_get(real_dict, key);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key,
+ result ? result : real_dict->error ? "error" : "not_found");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_update - log update operation */
+
+static int dict_debug_update(DICT *dict, const char *key, const char *value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_put(real_dict, key, value);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name,
+ key, value, result == 0 ? "success" : real_dict->error ?
+ "error" : "failed");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_delete - log delete operation */
+
+static int dict_debug_delete(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_del(real_dict, key);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key,
+ result == 0 ? "success" : real_dict->error ?
+ "error" : "failed");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_sequence - log sequence operation */
+
+static int dict_debug_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_seq(real_dict, function, key, value);
+ dict->flags = real_dict->flags;
+ if (result == 0)
+ msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name,
+ *key, *value);
+ else
+ msg_info("%s:%s sequence: found EOF", dict->type, dict->name);
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_close - log operation */
+
+static void dict_debug_close(DICT *dict)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+
+ dict_close(dict_debug->real_dict);
+ dict_free(dict);
+}
+
+/* dict_debug - encapsulate dictionary object and install proxies */
+
+DICT *dict_debug(DICT *real_dict)
+{
+ DICT_DEBUG *dict_debug;
+
+ dict_debug = (DICT_DEBUG *) dict_alloc(real_dict->type,
+ real_dict->name, sizeof(*dict_debug));
+ dict_debug->dict.flags = real_dict->flags; /* XXX not synchronized */
+ dict_debug->dict.lookup = dict_debug_lookup;
+ dict_debug->dict.update = dict_debug_update;
+ dict_debug->dict.delete = dict_debug_delete;
+ dict_debug->dict.sequence = dict_debug_sequence;
+ dict_debug->dict.close = dict_debug_close;
+ dict_debug->real_dict = real_dict;
+ return (&dict_debug->dict);
+}
diff --git a/src/util/dict_env.c b/src/util/dict_env.c
new file mode 100644
index 0000000..1635b8d
--- /dev/null
+++ b/src/util/dict_env.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* dict_env 3
+/* SUMMARY
+/* dictionary manager interface to environment variables
+/* SYNOPSIS
+/* #include <dict_env.h>
+/*
+/* DICT *dict_env_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_env_open() opens the environment variable array and
+/* makes it accessible via the generic operations documented
+/* in dict_open(3). The \fIname\fR and \fIdummy\fR arguments
+/* are ignored.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* safe_getenv(3) safe getenv() interface
+/* 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 <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_env.h"
+
+/* dict_env_update - update environment array */
+
+static int dict_env_update(DICT *dict, const char *name, const char *value)
+{
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if (setenv(name, value, 1))
+ msg_fatal("setenv: %m");
+
+ return (DICT_STAT_SUCCESS);
+}
+
+/* dict_env_lookup - access environment array */
+
+static const char *dict_env_lookup(DICT *dict, const char *name)
+{
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ return (safe_getenv(name));
+}
+
+/* dict_env_close - close environment dictionary */
+
+static void dict_env_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_env_open - make association with environment array */
+
+DICT *dict_env_open(const char *name, int unused_flags, int dict_flags)
+{
+ DICT *dict;
+
+ dict = dict_alloc(DICT_TYPE_ENVIRON, name, sizeof(*dict));
+ dict->lookup = dict_env_lookup;
+ dict->update = dict_env_update;
+ dict->close = dict_env_close;
+ dict->flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict->fold_buf = vstring_alloc(10);
+ dict->owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_env.h b/src/util/dict_env.h
new file mode 100644
index 0000000..9abfa4f
--- /dev/null
+++ b/src/util/dict_env.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_ENV_H_INCLUDED_
+#define _DICT_ENV_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_env 3h
+/* SUMMARY
+/* dictionary manager interface to environment variables
+/* SYNOPSIS
+/* #include <dict_env.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_ENVIRON "environ"
+
+extern DICT *dict_env_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_fail.c b/src/util/dict_fail.c
new file mode 100644
index 0000000..c8d9a19
--- /dev/null
+++ b/src/util/dict_fail.c
@@ -0,0 +1,115 @@
+/*++
+/* NAME
+/* dict_fail 3
+/* SUMMARY
+/* dictionary manager interface to 'always fail' table
+/* SYNOPSIS
+/* #include <dict_fail.h>
+/*
+/* DICT *dict_fail_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_fail_open() implements a dummy dictionary that fails
+/* all operations. The name can be used for logging.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+#include <dict_fail.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ int dict_errno; /* fixed error result */
+} DICT_FAIL;
+
+/* dict_fail_sequence - fail lookup */
+
+static int dict_fail_sequence(DICT *dict, int unused_func,
+ const char **key, const char **value)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_update - fail lookup */
+
+static int dict_fail_update(DICT *dict, const char *unused_name,
+ const char *unused_value)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_lookup - fail lookup */
+
+static const char *dict_fail_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, (char *) 0);
+}
+
+/* dict_fail_delete - fail delete */
+
+static int dict_fail_delete(DICT *dict, const char *unused_name)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_close - close fail dictionary */
+
+static void dict_fail_close(DICT *dict)
+{
+ dict_free(dict);
+}
+
+/* dict_fail_open - make association with fail variable */
+
+DICT *dict_fail_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_FAIL *dp;
+
+ dp = (DICT_FAIL *) dict_alloc(DICT_TYPE_FAIL, name, sizeof(*dp));
+ dp->dict.lookup = dict_fail_lookup;
+ if (open_flags & O_RDWR) {
+ dp->dict.update = dict_fail_update;
+ dp->dict.delete = dict_fail_delete;
+ }
+ dp->dict.sequence = dict_fail_sequence;
+ dp->dict.close = dict_fail_close;
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dp->dict_errno = DICT_ERR_RETRY;
+ dp->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_fail.h b/src/util/dict_fail.h
new file mode 100644
index 0000000..b42a31b
--- /dev/null
+++ b/src/util/dict_fail.h
@@ -0,0 +1,35 @@
+#ifndef _DICT_FAIL_H_INCLUDED_
+#define _DICT_FAIL_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_fail 3h
+/* SUMMARY
+/* dictionary manager interface to 'always fail' table
+/* SYNOPSIS
+/* #include <dict_fail.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_FAIL "fail"
+
+extern DICT *dict_fail_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* jeffm
+/* ghostgun.com
+/*--*/
+
+#endif
diff --git a/src/util/dict_file.c b/src/util/dict_file.c
new file mode 100644
index 0000000..c6ea74c
--- /dev/null
+++ b/src/util/dict_file.c
@@ -0,0 +1,231 @@
+/*++
+/* NAME
+/* dict_file_to_buf 3
+/* SUMMARY
+/* include content from file as blob
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* VSTRING *dict_file_to_buf(
+/* DICT *dict,
+/* const char *pathnames)
+/*
+/* VSTRING *dict_file_to_b64(
+/* DICT *dict,
+/* const char *pathnames)
+/*
+/* VSTRING *dict_file_from_b64(
+/* DICT *dict,
+/* const char *value)
+/*
+/* char *dict_file_get_error(
+/* DICT *dict)
+/*
+/* void dict_file_purge_buffers(
+/* DICT *dict)
+/*
+/* const char *dict_file_lookup(
+/* DICT *dict)
+/* DESCRIPTION
+/* dict_file_to_buf() reads the content of the specified files,
+/* with names separated by CHARS_COMMA_SP, while inserting a
+/* gratuitous newline character between files. It returns a
+/* pointer to a buffer which is owned by the DICT, or a null
+/* pointer in case of error.
+/*
+/* dict_file_to_b64() invokes dict_file_to_buf() and converts
+/* the result to base64. It returns a pointer to a buffer which
+/* is owned by the DICT, or a null pointer in case of error.
+/*
+/* dict_file_from_b64() converts a value from base64. It returns
+/* a pointer to a buffer which is owned by the DICT, or a null
+/* pointer in case of error.
+/*
+/* dict_file_purge_buffers() disposes of dict_file-related
+/* memory that are associated with this DICT.
+/*
+/* dict_file_get_error() should be called only after error;
+/* it returns a description of the problem. Storage is owned
+/* by the caller.
+/*
+/* dict_file_lookup() wraps the dictionary lookup method and
+/* decodes the base64 lookup result. The dictionary must be
+/* opened with DICT_FLAG_SRC_RHS_IS_FILE. Sets dict->error to
+/* DICT_ERR_CONFIG if the content is invalid. Decoding is not
+/* built into the dict->lookup() method, because that would
+/* complicate the implementation of map nesting (inline, thash),
+/* map composition (pipemap, unionmap), and map proxying.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* In case of error the VSTRING result value is a null pointer, and
+/* an error description can be retrieved with dict_file_get_error().
+/* The storage is owned by the caller.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <base64_code.h>
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_file_to_buf - read files into a buffer */
+
+VSTRING *dict_file_to_buf(DICT *dict, const char *pathnames)
+{
+ struct stat st;
+ VSTREAM *fp = 0;
+ ARGV *argv;
+ char **cpp;
+
+ /* dict_file_to_buf() postcondition: dict->file_buf exists. */
+ if (dict->file_buf == 0)
+ dict->file_buf = vstring_alloc(100);
+
+#define DICT_FILE_RETURN(retval) do { \
+ argv_free(argv); \
+ if (fp) vstream_fclose(fp); \
+ return (retval); \
+ } while (0);
+
+ argv = argv_split(pathnames, CHARS_COMMA_SP);
+ if (argv->argc == 0) {
+ vstring_sprintf(dict->file_buf, "empty pathname list: >>%s<<'",
+ pathnames);
+ DICT_FILE_RETURN(0);
+ }
+ VSTRING_RESET(dict->file_buf);
+ for (cpp = argv->argv; *cpp; cpp++) {
+ if ((fp = vstream_fopen(*cpp, O_RDONLY, 0)) == 0
+ || fstat(vstream_fileno(fp), &st) < 0) {
+ vstring_sprintf(dict->file_buf, "open %s: %m", *cpp);
+ DICT_FILE_RETURN(0);
+ }
+ if (st.st_size > SSIZE_T_MAX - LEN(dict->file_buf)) {
+ vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
+ DICT_FILE_RETURN(0);
+ }
+ if (vstream_fread_app(fp, dict->file_buf, st.st_size) != st.st_size) {
+ vstring_sprintf(dict->file_buf, "read %s: %m", *cpp);
+ DICT_FILE_RETURN(0);
+ }
+ (void) vstream_fclose(fp);
+ fp = 0;
+ if (cpp[1] != 0)
+ VSTRING_ADDCH(dict->file_buf, '\n');
+ }
+ VSTRING_TERMINATE(dict->file_buf);
+ DICT_FILE_RETURN(dict->file_buf);
+}
+
+/* dict_file_to_b64 - read files into a base64-encoded buffer */
+
+VSTRING *dict_file_to_b64(DICT *dict, const char *pathnames)
+{
+ ssize_t helper;
+
+ if (dict_file_to_buf(dict, pathnames) == 0)
+ return (0);
+ if (dict->file_b64 == 0)
+ dict->file_b64 = vstring_alloc(100);
+ helper = (LEN(dict->file_buf) + 2) / 3;
+ if (helper > SSIZE_T_MAX / 4) {
+ vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
+ return (0);
+ }
+ VSTRING_RESET(dict->file_b64);
+ VSTRING_SPACE(dict->file_b64, helper * 4);
+ return (base64_encode(dict->file_b64, STR(dict->file_buf),
+ LEN(dict->file_buf)));
+}
+
+/* dict_file_from_b64 - convert value from base64 */
+
+VSTRING *dict_file_from_b64(DICT *dict, const char *value)
+{
+ ssize_t helper;
+ VSTRING *result;
+
+ if (dict->file_buf == 0)
+ dict->file_buf = vstring_alloc(100);
+ helper = strlen(value) / 4;
+ VSTRING_RESET(dict->file_buf);
+ VSTRING_SPACE(dict->file_buf, helper * 3);
+ result = base64_decode(dict->file_buf, value, strlen(value));
+ if (result == 0)
+ vstring_sprintf(dict->file_buf, "malformed BASE64 value: %.30s", value);
+ return (result);
+}
+
+/* dict_file_get_error - return error text */
+
+char *dict_file_get_error(DICT *dict)
+{
+ if (dict->file_buf == 0)
+ msg_panic("dict_file_get_error: no buffer");
+ return (mystrdup(STR(dict->file_buf)));
+}
+
+/* dict_file_purge_buffers - purge file buffers */
+
+void dict_file_purge_buffers(DICT *dict)
+{
+ if (dict->file_buf) {
+ vstring_free(dict->file_buf);
+ dict->file_buf = 0;
+ }
+ if (dict->file_b64) {
+ vstring_free(dict->file_b64);
+ dict->file_b64 = 0;
+ }
+}
+
+/* dict_file_lookup - look up and decode dictionary entry */
+
+const char *dict_file_lookup(DICT *dict, const char *key)
+{
+ const char myname[] = "dict_file_lookup";
+ const char *res;
+ VSTRING *unb64;
+ char *err;
+
+ if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
+ msg_panic("%s: dictionary opened without DICT_FLAG_SRC_RHS_IS_FILE",
+ myname);
+ if ((res = dict->lookup(dict, key)) == 0)
+ return (0);
+ if ((unb64 = dict_file_from_b64(dict, res)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("table %s:%s: key %s: %s", dict->type, dict->name, key, err);
+ myfree(err);
+ dict->error = DICT_ERR_CONFIG;
+ return (0);
+ }
+ return STR(unb64);
+}
diff --git a/src/util/dict_ht.c b/src/util/dict_ht.c
new file mode 100644
index 0000000..74020d5
--- /dev/null
+++ b/src/util/dict_ht.c
@@ -0,0 +1,171 @@
+/*++
+/* NAME
+/* dict_ht 3
+/* SUMMARY
+/* dictionary manager interface to hash tables
+/* SYNOPSIS
+/* #include <dict_ht.h>
+/*
+/* DICT *dict_ht_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_ht_open() creates a memory-resident hash table and
+/* makes it accessible via the generic dictionary operations
+/* documented in dict_open(3). The open_flags argument is
+/* ignored.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 "mymalloc.h"
+#include "htable.h"
+#include "dict.h"
+#include "dict_ht.h"
+#include "stringops.h"
+#include "vstring.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ HTABLE *table; /* hash table */
+} DICT_HT;
+
+/* dict_ht_delete - delete hash-table entry */
+
+static int dict_ht_delete(DICT *dict, const char *name)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if (htable_locate(dict_ht->table, name) == 0) {
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ } else {
+ htable_delete(dict_ht->table, name, myfree);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ }
+}
+
+/* dict_ht_lookup - find hash-table entry */
+
+static const char *dict_ht_lookup(DICT *dict, const char *name)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, htable_find(dict_ht->table, name));
+}
+
+/* dict_ht_update - add or update hash-table entry */
+
+static int dict_ht_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+ HTABLE_INFO *ht;
+ char *saved_value = mystrdup(value);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((ht = htable_locate(dict_ht->table, name)) != 0) {
+ myfree(ht->value);
+ } else {
+ ht = htable_enter(dict_ht->table, name, (void *) 0);
+ }
+ ht->value = saved_value;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+}
+
+/* dict_ht_sequence - first/next iterator */
+
+static int dict_ht_sequence(DICT *dict, int how, const char **name,
+ const char **value)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+ HTABLE_INFO *ht;
+
+ ht = htable_sequence(dict_ht->table,
+ how == DICT_SEQ_FUN_FIRST ? HTABLE_SEQ_FIRST :
+ how == DICT_SEQ_FUN_NEXT ? HTABLE_SEQ_NEXT :
+ HTABLE_SEQ_STOP);
+ if (ht != 0) {
+ *name = ht->key;
+ *value = ht->value;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ } else {
+ *name = 0;
+ *value = 0;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ }
+}
+
+/* dict_ht_close - disassociate from hash table */
+
+static void dict_ht_close(DICT *dict)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ htable_free(dict_ht->table, myfree);
+ if (dict_ht->dict.fold_buf)
+ vstring_free(dict_ht->dict.fold_buf);
+ dict_free(dict);
+}
+
+/* dict_ht_open - create association with hash table */
+
+DICT *dict_ht_open(const char *name, int unused_open_flags, int dict_flags)
+{
+ DICT_HT *dict_ht;
+
+ dict_ht = (DICT_HT *) dict_alloc(DICT_TYPE_HT, name, sizeof(*dict_ht));
+ dict_ht->dict.lookup = dict_ht_lookup;
+ dict_ht->dict.update = dict_ht_update;
+ dict_ht->dict.delete = dict_ht_delete;
+ dict_ht->dict.sequence = dict_ht_sequence;
+ dict_ht->dict.close = dict_ht_close;
+ dict_ht->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_ht->dict.fold_buf = vstring_alloc(10);
+ dict_ht->table = htable_create(0);
+ dict_ht->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (&dict_ht->dict);
+}
diff --git a/src/util/dict_ht.h b/src/util/dict_ht.h
new file mode 100644
index 0000000..7cb31aa
--- /dev/null
+++ b/src/util/dict_ht.h
@@ -0,0 +1,38 @@
+#ifndef _DICT_HT_H_INCLUDED_
+#define _DICT_HT_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_ht 3h
+/* SUMMARY
+/* dictionary manager interface to hash tables
+/* SYNOPSIS
+/* #include <dict_ht.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_HT "internal"
+
+extern DICT *dict_ht_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_inline.c b/src/util/dict_inline.c
new file mode 100644
index 0000000..72339b2
--- /dev/null
+++ b/src/util/dict_inline.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dict_inline 3
+/* SUMMARY
+/* dictionary manager interface for inline table
+/* SYNOPSIS
+/* #include <dict_inline.h>
+/*
+/* DICT *dict_inline_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_inline_open() opens a read-only, in-memory table.
+/* Example: "\fBinline:{\fIkey_1=value_1, ..., key_n=value_n\fR}".
+/* The longer form with { key = value } allows values that
+/* contain whitespace or comma.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_ht.h>
+#include <dict_inline.h>
+
+/* Application-specific. */
+
+/* dict_inline_open - open inline table */
+
+DICT *dict_inline_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT *dict;
+ char *cp, *saved_name = 0;
+ size_t len;
+ char *nameval, *vname, *value;
+ const char *err = 0;
+ char *free_me = 0;
+ int count = 0;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_INLINE_RETURN(x) do { \
+ DICT *__d = (x); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (free_me != 0) \
+ myfree(free_me); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_INLINE, name));
+
+ /*
+ * UTF-8 syntax check.
+ */
+ if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)
+ && allascii(name) == 0
+ && valid_utf8_string(name, strlen(name)) == 0)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "bad UTF-8 syntax: \"%s:%s\"; "
+ "need \"%s:{name=value...}\"",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE));
+
+ /*
+ * Parse the table into its constituent name=value pairs.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(cp = saved_name = mystrndup(name + 1, len - 2)) == 0)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{name=value...}\"",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE));
+
+ /*
+ * Reuse the "internal" dictionary type.
+ */
+ dict = dict_open3(DICT_TYPE_HT, name, open_flags, dict_flags);
+ dict_type_override(dict, DICT_TYPE_INLINE);
+ while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (nameval[0] == CHARS_BRACE[0])
+ err = free_me = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_STRIP);
+ if (err != 0 || (err = split_qnameval(nameval, &vname, &value)) != 0)
+ break;
+
+ if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) != 0) {
+ VSTRING *base64_buf;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = free_me = dict_file_get_error(dict);
+ break;
+ }
+ value = vstring_str(base64_buf);
+ }
+ /* No duplicate checks. See comments in dict_thash.c. */
+ dict->update(dict, vname, value);
+ count += 1;
+ }
+ if (err != 0 || count == 0) {
+ dict->close(dict);
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "%s: \"%s:%s\"; "
+ "need \"%s:{name=%s...}\"",
+ err != 0 ? err : "empty table",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE,
+ (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ?
+ "filename" : "value"));
+ }
+ dict->owner.status = DICT_OWNER_TRUSTED;
+
+ dict_file_purge_buffers(dict);
+ DICT_INLINE_RETURN(DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_inline.h b/src/util/dict_inline.h
new file mode 100644
index 0000000..9879acf
--- /dev/null
+++ b/src/util/dict_inline.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_INLINE_H_INCLUDED_
+#define _DICT_INLINE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_inline 3h
+/* SUMMARY
+/* dictionary manager interface for inline tables
+/* SYNOPSIS
+/* #include <dict_inline.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_INLINE "inline"
+
+extern DICT *dict_inline_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_inline.ref b/src/util/dict_inline.ref
new file mode 100644
index 0000000..e64e6d0
--- /dev/null
+++ b/src/util/dict_inline.ref
@@ -0,0 +1,24 @@
+./dict_open: error: empty table: "inline:{ }"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: missing '=' after attribute name: "inline:{ foo = xx }"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: bad syntax: "inline:{ foo=xx }x"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: bad syntax: "inline:{ foo=xx x"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: syntax error after '}' in "{x=y}x": "inline:{ foo=xx {x=y}x}"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+owner=trusted (uid=2147483647)
+> get foo
+foo=XX
+> get bar
+bar=lotsa stuff
+> get baz
+baz: not found
+owner=trusted (uid=2147483647)
+> get foo
+foo=XX
+> get bar
+bar=lotsa stuff
+> get baz
+baz: not found
diff --git a/src/util/dict_inline_cidr.ref b/src/util/dict_inline_cidr.ref
new file mode 100644
index 0000000..56c00c5
--- /dev/null
+++ b/src/util/dict_inline_cidr.ref
@@ -0,0 +1,4 @@
+> get 1.2.3.4
+1.2.3.4=got 1.2.3.4
+> get 4.3.2.1
+4.3.2.1: not found
diff --git a/src/util/dict_inline_file.ref b/src/util/dict_inline_file.ref
new file mode 100644
index 0000000..9d49e95
--- /dev/null
+++ b/src/util/dict_inline_file.ref
@@ -0,0 +1,12 @@
+./dict_open: error: open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: inline:{ foo=xx, bar=yy } is unavailable. open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}"
+foo: error
+owner=trusted (uid=2147483647)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
diff --git a/src/util/dict_inline_pcre.ref b/src/util/dict_inline_pcre.ref
new file mode 100644
index 0000000..0c381f1
--- /dev/null
+++ b/src/util/dict_inline_pcre.ref
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
diff --git a/src/util/dict_inline_regexp.ref b/src/util/dict_inline_regexp.ref
new file mode 100644
index 0000000..0c381f1
--- /dev/null
+++ b/src/util/dict_inline_regexp.ref
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
diff --git a/src/util/dict_lmdb.c b/src/util/dict_lmdb.c
new file mode 100644
index 0000000..bed20e0
--- /dev/null
+++ b/src/util/dict_lmdb.c
@@ -0,0 +1,706 @@
+/*++
+/* NAME
+/* dict_lmdb 3
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP LMDB files
+/* SYNOPSIS
+/* #include <dict_lmdb.h>
+/*
+/* extern size_t dict_lmdb_map_size;
+/*
+/* DEFINE_DICT_LMDB_MAP_SIZE;
+/*
+/* DICT *dict_lmdb_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_lmdb_open() opens the named LMDB database and makes
+/* it available via the generic interface described in
+/* dict_open(3).
+/*
+/* The dict_lmdb_map_size variable specifies the initial
+/* database memory map size. When a map becomes full its size
+/* is doubled, and other programs pick up the size change.
+/*
+/* This variable cannot be exported via the dict(3) API and
+/* must therefore be defined in the calling program by invoking
+/* the DEFINE_DICT_LMDB_MAP_SIZE macro at the global level.
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of
+/* memory.
+/*
+/* If a jump buffer is specified with dict_setjmp(), then the LMDB
+/* client will call dict_longjmp() to return to that execution
+/* context after a recoverable error.
+/* BUGS
+/* The on-the-fly map resize operations require no concurrent
+/* activity in the same database by other threads in the same
+/* memory address space.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*
+/* 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
+/*--*/
+
+#include <sys_defs.h>
+
+#ifdef HAS_LMDB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <myflock.h>
+#include <stringops.h>
+#include <slmdb.h>
+#include <dict.h>
+#include <dict_lmdb.h>
+#include <warn_stat.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ SLMDB slmdb; /* sane LMDB API */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* value buffer */
+} DICT_LMDB;
+
+ /*
+ * The LMDB database filename suffix happens to equal our DICT_TYPE_LMDB
+ * prefix, but that doesn't mean it is kosher to use DICT_TYPE_LMDB where a
+ * suffix is needed, so we define an explicit suffix here.
+ */
+#define DICT_LMDB_SUFFIX "lmdb"
+
+ /*
+ * Make a safe string copy that is guaranteed to be null-terminated.
+ */
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+ /*
+ * Postfix writers recover from a "map full" error by increasing the memory
+ * map size with a factor DICT_LMDB_SIZE_INCR (up to some limit) and
+ * retrying the transaction.
+ *
+ * Each dict(3) API call is retried no more than a few times. For bulk-mode
+ * transactions the number of retries is proportional to the size of the
+ * address space.
+ *
+ * We do not expose these details to the Postfix user interface. The purpose of
+ * Postfix is to solve problems, not punt them to the user.
+ */
+#define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */
+#define DICT_LMDB_SIZE_MAX SSIZE_T_MAX
+
+#define DICT_LMDB_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */
+#define DICT_LMDB_BULK_RETRY_LIMIT \
+ ((int) (2 * sizeof(size_t) * CHAR_BIT)) /* Retries per bulk-mode
+ * transaction */
+
+/* #define msg_verbose 1 */
+
+/* dict_lmdb_lookup - find database entry */
+
+static const char *dict_lmdb_lookup(DICT *dict, const char *name)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ const char *result = 0;
+ int status;
+ ssize_t klen;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * See if this LMDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ } else if (status != MDB_NOTFOUND) {
+ msg_fatal("error reading %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * See if this LMDB file was written with no null byte appended to key
+ * and value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ } else if (status != MDB_NOTFOUND) {
+ msg_fatal("error reading %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (result);
+}
+
+/* dict_lmdb_update - add or update database entry */
+
+static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ mdb_key.mv_data = (void *) name;
+ mdb_value.mv_data = (void *) value;
+ mdb_key.mv_size = strlen(name);
+ mdb_value.mv_size = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef LMDB_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_size++;
+ mdb_value.mv_size++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * Do the update.
+ */
+ status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE);
+ if (status != 0) {
+ if (status == MDB_KEYEXIST) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s:%s: duplicate entry: \"%s\"",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, name);
+ else
+ msg_fatal("%s:%s: duplicate entry: \"%s\"",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, name);
+ } else {
+ msg_fatal("error updating %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_delete - delete one entry from the dictionary */
+
+static int dict_lmdb_delete(DICT *dict, const char *name)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ int status = 1;
+ ssize_t klen;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * See if this LMDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
+ if (status != 0) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this LMDB file was written with no null byte appended to key
+ * and value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
+ if (status != 0) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_sequence - traverse the dictionary */
+
+static int dict_lmdb_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_lmdb_sequence";
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ MDB_cursor_op op;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Determine the seek function.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ op = MDB_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ op = MDB_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * Database lookup.
+ */
+ status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op);
+
+ switch (status) {
+
+ /*
+ * Copy the key and value so they are guaranteed null terminated.
+ */
+ case 0:
+ *key = SCOPY(dict_lmdb->key_buf, mdb_key.mv_data, mdb_key.mv_size);
+ if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0)
+ *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ else
+ *value = ""; /* XXX */
+ break;
+
+ /*
+ * End-of-database.
+ */
+ case MDB_NOTFOUND:
+ status = 1;
+ /* Not: mdb_cursor_close(). Wrong abstraction level. */
+ break;
+
+ /*
+ * Bust.
+ */
+ default:
+ msg_fatal("error seeking %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_close - disassociate from data base */
+
+static void dict_lmdb_close(DICT *dict)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+
+ slmdb_close(&dict_lmdb->slmdb);
+ if (dict_lmdb->key_buf)
+ vstring_free(dict_lmdb->key_buf);
+ if (dict_lmdb->val_buf)
+ vstring_free(dict_lmdb->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_lmdb_longjmp - repeat bulk transaction */
+
+static void dict_lmdb_longjmp(void *context, int val)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+
+ dict_longjmp(&dict_lmdb->dict, val);
+}
+
+/* dict_lmdb_notify - debug logging */
+
+static void dict_lmdb_notify(void *context, int error_code,...)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+ va_list ap;
+
+ va_start(ap, error_code);
+ switch (error_code) {
+ case MDB_SUCCESS:
+ msg_info("database %s:%s: using size limit %lu during open",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_MAP_FULL:
+ msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_MAP_RESIZED:
+ msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_READERS_FULL:
+ msg_info("database %s:%s: pausing after MDB_READERS_FULL",
+ dict_lmdb->dict.type, dict_lmdb->dict.name);
+ break;
+ default:
+ msg_warn("unknown MDB error code: %d", error_code);
+ break;
+ }
+ va_end(ap);
+}
+
+/* dict_lmdb_assert - report LMDB internal assertion failure */
+
+static void dict_lmdb_assert(void *context, const char *text)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+
+ msg_fatal("%s:%s: internal error: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, text);
+}
+
+/* dict_lmdb_open - open LMDB data base */
+
+DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_LMDB *dict_lmdb;
+ DICT *dict;
+ struct stat st;
+ SLMDB slmdb;
+ char *mdb_path;
+ int mdb_flags, slmdb_flags, status;
+ int db_fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_LMDB_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ myfree(mdb_path); \
+ return (__d); \
+ } while (0)
+
+ mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0);
+
+ /*
+ * Impedance adapters.
+ */
+ mdb_flags = MDB_NOSUBDIR | MDB_NOLOCK;
+ if (open_flags == O_RDONLY)
+ mdb_flags |= MDB_RDONLY;
+
+ slmdb_flags = 0;
+ if (dict_flags & DICT_FLAG_BULK_UPDATE)
+ slmdb_flags |= SLMDB_FLAG_BULK;
+
+ /*
+ * Security violation.
+ *
+ * By default, LMDB 0.9.9 writes uninitialized heap memory to a
+ * world-readable database file, as chunks of up to 4096 bytes. This is a
+ * huge memory disclosure vulnerability: memory content that a program
+ * does not intend to share ends up in a world-readable file. The content
+ * of uninitialized heap memory depends on program execution history.
+ * That history includes code execution in other libraries that are
+ * linked into the program.
+ *
+ * This is a problem whenever the user who writes the database file differs
+ * from the user who reads the database file. For example, a privileged
+ * writer and an unprivileged reader. In the case of Postfix, the
+ * postmap(1) and postalias(1) commands would leak uninitialized heap
+ * memory, as chunks of up to 4096 bytes, from a root-privileged process
+ * that writes to a database file, to unprivileged processes that read
+ * from that database file.
+ *
+ * As a workaround the postmap(1) and postalias(1) commands turn on
+ * MDB_WRITEMAP which disables the use of malloc() in LMDB. However, that
+ * does not address several disclosures of stack memory. We don't enable
+ * this workaround for Postfix databases are maintained by Postfix daemon
+ * processes, because those are accessible only by the postfix user.
+ *
+ * LMDB 0.9.10 by default does not write uninitialized heap memory to file
+ * (specify MDB_NOMEMINIT to revert that change). We use the MDB_WRITEMAP
+ * workaround for older LMDB versions.
+ */
+#ifndef MDB_NOMEMINIT
+ if (dict_flags & DICT_FLAG_BULK_UPDATE) /* XXX Good enough */
+ mdb_flags |= MDB_WRITEMAP;
+#endif
+
+ /*
+ * Gracefully handle most database open errors.
+ */
+ if ((status = slmdb_init(&slmdb, dict_lmdb_map_size, DICT_LMDB_SIZE_INCR,
+ DICT_LMDB_SIZE_MAX)) != 0
+ || (status = slmdb_open(&slmdb, mdb_path, open_flags, mdb_flags,
+ slmdb_flags)) != 0) {
+ /* This leaks a little memory that would have been used otherwise. */
+ dict = dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags,
+ "open database %s: %s", mdb_path, mdb_strerror(status));
+ DICT_LMDB_OPEN_RETURN(dict);
+ }
+
+ /*
+ * XXX Persistent locking belongs in mkmap_lmdb.
+ *
+ * We just need to acquire exclusive access momentarily. This establishes
+ * that no readers are accessing old (obsoleted by copy-on-write) txn
+ * snapshots, so we are free to reuse all eligible old pages. Downgrade
+ * the lock right after acquiring it. This is sufficient to keep out
+ * other writers until we are done.
+ */
+ db_fd = slmdb_fd(&slmdb);
+ if (dict_flags & DICT_FLAG_BULK_UPDATE) {
+ if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", mdb_path);
+ if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: unlock dictionary: %m", mdb_path);
+ }
+
+ /*
+ * Bundle up. From here on no more assignments to slmdb.
+ */
+ dict_lmdb = (DICT_LMDB *) dict_alloc(DICT_TYPE_LMDB, path, sizeof(*dict_lmdb));
+ dict_lmdb->slmdb = slmdb;
+ dict_lmdb->dict.lookup = dict_lmdb_lookup;
+ dict_lmdb->dict.update = dict_lmdb_update;
+ dict_lmdb->dict.delete = dict_lmdb_delete;
+ dict_lmdb->dict.sequence = dict_lmdb_sequence;
+ dict_lmdb->dict.close = dict_lmdb_close;
+
+ if (fstat(db_fd, &st) < 0)
+ msg_fatal("dict_lmdb_open: fstat: %m");
+ dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd;
+ dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL;
+ dict_lmdb->dict.mtime = st.st_mtime;
+ dict_lmdb->dict.owner.uid = st.st_uid;
+ dict_lmdb->dict.owner.status = (st.st_uid != 0);
+
+ dict_lmdb->key_buf = 0;
+ dict_lmdb->val_buf = 0;
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_lmdb->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", mdb_path, path);
+
+#define DICT_LMDB_IMPL_FLAGS (DICT_FLAG_FIXED | DICT_FLAG_MULTI_WRITER)
+
+ dict_lmdb->dict.flags = dict_flags | DICT_LMDB_IMPL_FLAGS;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_lmdb->dict.fold_buf = vstring_alloc(10);
+
+ if (dict_flags & DICT_FLAG_BULK_UPDATE)
+ dict_jmp_alloc(&dict_lmdb->dict);
+
+ /*
+ * The following requests return an error result only if we have serious
+ * memory corruption problem.
+ */
+ if (slmdb_control(&dict_lmdb->slmdb,
+ CA_SLMDB_CTL_API_RETRY_LIMIT(DICT_LMDB_API_RETRY_LIMIT),
+ CA_SLMDB_CTL_BULK_RETRY_LIMIT(DICT_LMDB_BULK_RETRY_LIMIT),
+ CA_SLMDB_CTL_LONGJMP_FN(dict_lmdb_longjmp),
+ CA_SLMDB_CTL_NOTIFY_FN(msg_verbose ?
+ dict_lmdb_notify : (SLMDB_NOTIFY_FN) 0),
+ CA_SLMDB_CTL_ASSERT_FN(dict_lmdb_assert),
+ CA_SLMDB_CTL_CB_CONTEXT((void *) dict_lmdb),
+ CA_SLMDB_CTL_END) != 0)
+ msg_panic("dict_lmdb_open: slmdb_control: %m");
+
+ if (msg_verbose)
+ dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS,
+ slmdb_curr_limit(&dict_lmdb->slmdb));
+
+ DICT_LMDB_OPEN_RETURN(DICT_DEBUG (&dict_lmdb->dict));
+}
+
+#endif
diff --git a/src/util/dict_lmdb.h b/src/util/dict_lmdb.h
new file mode 100644
index 0000000..ccc165a
--- /dev/null
+++ b/src/util/dict_lmdb.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_LMDB_H_INCLUDED_
+#define _DICT_LMDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_lmdb 3h
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP LMDB files
+/* SYNOPSIS
+/* #include <dict_lmdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_LMDB "lmdb"
+
+extern DICT *dict_lmdb_open(const char *, int, int);
+
+ /*
+ * XXX Should be part of the DICT interface.
+ */
+extern size_t dict_lmdb_map_size;
+
+ /* Minimum size without SIGSEGV. */
+#define DEFINE_DICT_LMDB_MAP_SIZE size_t dict_lmdb_map_size = 8192
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+#endif
diff --git a/src/util/dict_ni.c b/src/util/dict_ni.c
new file mode 100644
index 0000000..3f62559
--- /dev/null
+++ b/src/util/dict_ni.c
@@ -0,0 +1,194 @@
+/*++
+/* NAME
+/* dict_ni 3
+/* SUMMARY
+/* dictionary manager interface to NetInfo
+/* SYNOPSIS
+/* #include <dict_ni.h>
+/*
+/* DICT *dict_ni_open(path, dummy, dict_flags)
+/* char *path;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_ni_open() `opens' the named NetInfo database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/* DIAGNOSTICS
+/* dict_ni_register() returns 0 in case of success, -1 in case
+/* of problems.
+/* Fatal errors: NetInfo errors, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* netinfo(3N) data base subroutines
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_NETINFO
+
+/* System library. */
+
+#include <stdio.h>
+#include <netinfo/ni.h>
+
+/* Utility library. */
+
+#include "dict.h"
+#include "dict_ni.h"
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+
+typedef struct {
+ DICT dict; /* my super */
+ char *path; /* directory path */
+} DICT_NI;
+
+ /*
+ * We'd like other possibilities, but that is not possible in the current
+ * dictionary setup... An example of a different setup: use `members' for
+ * multi-valued lookups (to be compatible with /aliases), and `value' for
+ * single-valued tables.
+ */
+#define NETINFO_PROP_KEY "name"
+#define NETINFO_PROP_VALUE "members"
+#define NETINFO_VALUE_SEP ","
+
+#define NETINFO_MAX_DOMAIN_DEPTH 100
+
+/* Hard worker doing lookups. Returned value is statically allocated and
+ reused each call. */
+static const char *dict_ni_do_lookup(char *path, char *key_prop,
+ const char *key_value, char *val_prop)
+{
+ unsigned int result_cap = 0;
+ static char *result = 0;
+
+ char *return_val = 0;
+ ni_namelist values;
+ int depth = 0;
+ void *domain;
+ void *next_domain;
+ char *query;
+ ni_status r;
+ ni_id dir;
+
+ if (msg_verbose)
+ msg_info("ni_lookup %s %s=%s", path, key_prop, key_value);
+
+ r = ni_open(NULL, ".", &domain);
+ if (r != NI_OK) {
+ msg_warn("ni_open `.': %d", r);
+ return NULL;
+ }
+ query = mymalloc(strlen(path) + strlen(key_prop) + 3 + strlen(key_value));
+ sprintf(query, "%s/%s=%s", path, key_prop, key_value);
+
+ for (;;) {
+
+ /*
+ * What does it _mean_ if we find the directory but not the value?
+ */
+ if (ni_pathsearch(domain, &dir, query) == NI_OK
+ && ni_lookupprop(domain, &dir, val_prop, &values) == NI_OK)
+ if (values.ni_namelist_len <= 0)
+ ni_namelist_free(&values);
+ else {
+ unsigned int i, l, n;
+
+ for (i = l = 0; i < values.ni_namelist_len; i++)
+ l += 1 + strlen(values.ni_namelist_val[i]);
+ if (result_cap < l) {
+ if (result)
+ myfree(result);
+ result_cap = l + 100;
+ result = mymalloc(result_cap);
+ }
+ for (i = l = 0; i < values.ni_namelist_len; i++) {
+ n = strlen(values.ni_namelist_val[i]);
+ memcpy(result + l, values.ni_namelist_val[i], n);
+ l += n;
+ if (i < values.ni_namelist_len - 1)
+ result[l++] = ',';
+ }
+ result[l] = '\0';
+ return_val = result;
+ break;
+ }
+
+ if (++depth >= NETINFO_MAX_DOMAIN_DEPTH) {
+ msg_warn("ni_open: domain depth limit");
+ break;
+ }
+ r = ni_open(domain, "..", &next_domain);
+ if (r != NI_OK) {
+ if (r != NI_FAILED)
+ msg_warn("ni_open `..': %d", r);
+ break;
+ }
+ ni_free(domain);
+ domain = next_domain;
+ }
+
+ ni_free(domain);
+ myfree(query);
+
+ return return_val;
+}
+
+/* dict_ni_lookup - find table entry */
+
+static const char *dict_ni_lookup(DICT *dict, const char *key)
+{
+ DICT_NI *d = (DICT_NI *) dict;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ return dict_ni_do_lookup(d->dict.name, NETINFO_PROP_KEY,
+ key, NETINFO_PROP_VALUE);
+}
+
+/* dict_ni_close - disassociate from NetInfo map */
+
+static void dict_ni_close(DICT *dict)
+{
+ DICT_NI *d = (DICT_NI *) dict;
+
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_ni_open - create association with NetInfo map */
+
+DICT *dict_ni_open(const char *path, int unused_flags, int dict_flags)
+{
+ DICT_NI *d = (void *) dict_alloc(DICT_TYPE_NETINFO, path, sizeof(*d));
+
+ d->dict.lookup = dict_ni_lookup;
+ d->dict.close = dict_ni_close;
+ d->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ d->dict.fold_buf = vstring_alloc(10);
+ d->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ return (DICT_DEBUG (&d->dict));
+}
+
+#endif
diff --git a/src/util/dict_ni.h b/src/util/dict_ni.h
new file mode 100644
index 0000000..652b422
--- /dev/null
+++ b/src/util/dict_ni.h
@@ -0,0 +1,34 @@
+#ifndef _DICT_NI_H_INCLUDED_
+#define _DICT_NI_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_ni 3h
+/* SUMMARY
+/* dictionary manager interface to NetInfo maps
+/* SYNOPSIS
+/* #include <dict_ni.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NETINFO "netinfo"
+
+extern DICT *dict_ni_open(const char *, int, int);
+
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#endif
diff --git a/src/util/dict_nis.c b/src/util/dict_nis.c
new file mode 100644
index 0000000..357011f
--- /dev/null
+++ b/src/util/dict_nis.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* dict_nis 3
+/* SUMMARY
+/* dictionary manager interface to NIS maps
+/* SYNOPSIS
+/* #include <dict_nis.h>
+/*
+/* DICT *dict_nis_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_nis_open() makes the specified NIS map accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, attempt to update NIS map.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef HAS_NIS
+
+#include <rpcsvc/ypclnt.h>
+#ifndef YPERR_BUSY
+#define YPERR_BUSY 16
+#endif
+#ifndef YPERR_ACCESS
+#define YPERR_ACCESS 15
+#endif
+
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_nis.h"
+
+#ifdef HAS_NIS
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+} DICT_NIS;
+
+ /*
+ * Class variables, so that multiple maps can share this info.
+ */
+static char dict_nis_disabled[1];
+static char *dict_nis_domain;
+
+/* dict_nis_init - NIS binding */
+
+static void dict_nis_init(void)
+{
+ const char *myname = "dict_nis_init";
+
+ if (yp_get_default_domain(&dict_nis_domain) != 0
+ || dict_nis_domain == 0 || *dict_nis_domain == 0
+ || strcasecmp(dict_nis_domain, "(none)") == 0) {
+ dict_nis_domain = dict_nis_disabled;
+ msg_warn("%s: NIS domain name not set - NIS lookups disabled", myname);
+ }
+ if (msg_verbose)
+ msg_info("%s: NIS domain %s", myname, dict_nis_domain);
+}
+
+/* dict_nis_strerror - map error number to string */
+
+static char *dict_nis_strerror(int err)
+{
+
+ /*
+ * Grr. There should be a standard function for this.
+ */
+ switch (err) {
+ case YPERR_BADARGS:
+ return ("args to function are bad");
+ case YPERR_RPC:
+ return ("RPC failure - domain has been unbound");
+ case YPERR_DOMAIN:
+ return ("can't bind to server on this domain");
+ case YPERR_MAP:
+ return ("no such map in server's domain");
+ case YPERR_KEY:
+ return ("no such key in map");
+ case YPERR_YPERR:
+ return ("internal yp server or client error");
+ case YPERR_RESRC:
+ return ("resource allocation failure");
+ case YPERR_NOMORE:
+ return ("no more records in map database");
+ case YPERR_PMAP:
+ return ("can't communicate with portmapper");
+ case YPERR_YPBIND:
+ return ("can't communicate with ypbind");
+ case YPERR_YPSERV:
+ return ("can't communicate with ypserv");
+ case YPERR_NODOM:
+ return ("local domain name not set");
+ case YPERR_BADDB:
+ return ("yp database is bad");
+ case YPERR_VERS:
+ return ("yp version mismatch");
+ case YPERR_ACCESS:
+ return ("access violation");
+ case YPERR_BUSY:
+ return ("database busy");
+ default:
+ return ("unknown NIS lookup error");
+ }
+}
+
+/* dict_nis_lookup - find table entry */
+
+static const char *dict_nis_lookup(DICT *dict, const char *key)
+{
+ DICT_NIS *dict_nis = (DICT_NIS *) dict;
+ static char *result;
+ int result_len;
+ int err;
+ static VSTRING *buf;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ if (dict_nis_domain == dict_nis_disabled)
+ return (0);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * See if this NIS map was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ err = yp_match(dict_nis_domain, dict_nis->dict.name,
+ (void *) key, strlen(key) + 1,
+ &result, &result_len);
+ if (err == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ return (result);
+ }
+ }
+
+ /*
+ * See if this NIS map was written with no null byte appended to key and
+ * value. This should never be the case, but better play safe.
+ */
+ if (dict->flags & DICT_FLAG_TRY0NULL) {
+ err = yp_match(dict_nis_domain, dict_nis->dict.name,
+ (void *) key, strlen(key),
+ &result, &result_len);
+ if (err == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_strncpy(buf, result, result_len);
+ return (vstring_str(buf));
+ }
+ }
+
+ /*
+ * When the NIS lookup fails for reasons other than "key not found", keep
+ * logging warnings, and hope that someone will eventually notice the
+ * problem and fix it.
+ */
+ if (err != YPERR_KEY) {
+ msg_warn("lookup %s, NIS domain %s, map %s: %s",
+ key, dict_nis_domain, dict_nis->dict.name,
+ dict_nis_strerror(err));
+ dict->error = DICT_ERR_RETRY;
+ }
+ return (0);
+}
+
+/* dict_nis_close - close NIS map */
+
+static void dict_nis_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_nis_open - open NIS map */
+
+DICT *dict_nis_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_NIS *dict_nis;
+
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_NIS, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_NIS, map));
+
+ dict_nis = (DICT_NIS *) dict_alloc(DICT_TYPE_NIS, map, sizeof(*dict_nis));
+ dict_nis->dict.lookup = dict_nis_lookup;
+ dict_nis->dict.close = dict_nis_close;
+ dict_nis->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_nis->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_nis->dict.fold_buf = vstring_alloc(10);
+ if (dict_nis_domain == 0)
+ dict_nis_init();
+ dict_nis->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (&dict_nis->dict));
+}
+
+#endif
diff --git a/src/util/dict_nis.h b/src/util/dict_nis.h
new file mode 100644
index 0000000..97aa7cd
--- /dev/null
+++ b/src/util/dict_nis.h
@@ -0,0 +1,37 @@
+#ifndef _DIST_NIS_H_INCLUDED_
+#define _DIST_NIS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_nis 3h
+/* SUMMARY
+/* dictionary manager interface to NIS maps
+/* SYNOPSIS
+/* #include <dict_nis.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NIS "nis"
+
+extern DICT *dict_nis_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_nisplus.c b/src/util/dict_nisplus.c
new file mode 100644
index 0000000..1f5e126
--- /dev/null
+++ b/src/util/dict_nisplus.c
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/* dict_nisplus 3
+/* SUMMARY
+/* dictionary manager interface to NIS+ maps
+/* SYNOPSIS
+/* #include <dict_nisplus.h>
+/*
+/* DICT *dict_nisplus_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_nisplus_open() makes the specified NIS+ map accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors:
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Geoff Gibbs
+/* UK-HGMP-RC
+/* Hinxton
+/* Cambridge
+/* CB10 1SB, UK
+/*
+/* based on the code for dict_nis.c et al by :-
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#ifdef HAS_NISPLUS
+#include <rpcsvc/nis.h> /* for nis_list */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_nisplus.h>
+
+#ifdef HAS_NISPLUS
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *template; /* parsed query template */
+ int column; /* NIS+ field number (start at 1) */
+} DICT_NISPLUS;
+
+ /*
+ * Begin quote from nis+(1):
+ *
+ * The following text represents a context-free grammar that defines the
+ * set of legal NIS+ names. The terminals in this grammar are the
+ * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,'
+ * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'),
+ * which delineate non- terminals, are not part of the grammar. The
+ * character `|' (vertical bar) is used to separate alternate productions
+ * and should be read as ``this production OR this production''.
+ *
+ * name ::= . | <simple name> | <indexed name>
+ *
+ * simple name ::= <string>. | <string>.<simple name>
+ *
+ * indexed name ::= <search criterion>,<simple name>
+ *
+ * search criterion ::= [ <attribute list> ]
+ *
+ * attribute list ::= <attribute> | <attribute>,<attribute list>
+ *
+ * attribute ::= <string> = <string>
+ *
+ * string ::= ISO Latin 1 character set except the character
+ * '/' (slash). The initial character may not be a terminal character or
+ * the characters '@' (at), '+' (plus), or (`-') hyphen.
+ *
+ * Terminals that appear in strings must be quoted with `"' (double quote).
+ * The `"' character may be quoted by quoting it with itself `""'.
+ *
+ * End quote fron nis+(1).
+ *
+ * This NIS client always quotes the entire query string (the value part of
+ * [attribute=value],file.domain.) so the issue with initial characters
+ * should not be applicable. One wonders what restrictions are applicable
+ * when a string is quoted, but the manual doesn't specify what can appear
+ * between quotes, and we don't want to get burned.
+ */
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* dict_nisplus_lookup - find table entry */
+
+static const char *dict_nisplus_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_nisplus_lookup";
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+ static VSTRING *quoted_key;
+ static VSTRING *query;
+ static VSTRING *retval;
+ nis_result *reply;
+ int count;
+ const char *cp;
+ int last_col;
+ int ch;
+
+ dict->error = 0;
+
+ /*
+ * Initialize.
+ */
+ if (quoted_key == 0) {
+ query = vstring_alloc(100);
+ retval = vstring_alloc(100);
+ quoted_key = vstring_alloc(100);
+ }
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Check that the lookup key does not contain characters disallowed by
+ * nis+(1).
+ *
+ * XXX Many client implementations don't seem to care about disallowed
+ * characters.
+ */
+ VSTRING_RESET(quoted_key);
+ VSTRING_ADDCH(quoted_key, '"');
+ for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) {
+ msg_warn("map %s:%s: lookup key with non-printing character 0x%x:"
+ " ignoring this request",
+ dict->type, dict->name, ch);
+ return (0);
+ } else if (ch == '"') {
+ VSTRING_ADDCH(quoted_key, '"');
+ }
+ VSTRING_ADDCH(quoted_key, ch);
+ }
+ VSTRING_ADDCH(quoted_key, '"');
+ VSTRING_TERMINATE(quoted_key);
+
+ /*
+ * Plug the key into the query template, which typically looks something
+ * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain.
+ *
+ * XXX The nis+ documentation defines a length limit for simple names like
+ * a.b.c., but defines no length limit for (the components of) indexed
+ * names such as [x=y],a.b.c. Our query length is limited because Postfix
+ * addresses (in envelopes or in headers) have a finite length.
+ */
+ vstring_sprintf(query, dict_nisplus->template, STR(quoted_key));
+ reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+
+ /*
+ * When lookup succeeds, the result may be ambiguous, or the requested
+ * column may not exist.
+ */
+ if (reply->status == NIS_SUCCESS) {
+ if ((count = NIS_RES_NUMOBJ(reply)) != 1) {
+ msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:"
+ " ignoring this request",
+ count, key, dict_nisplus->dict.name);
+ nis_freeresult(reply);
+ return (0);
+ } else {
+ last_col = NIS_RES_OBJECT(reply)->zo_data
+ .objdata_u.en_data.en_cols.en_cols_len - 1;
+ if (dict_nisplus->column > last_col)
+ msg_fatal("requested column %d > max column %d in table %s",
+ dict_nisplus->column, last_col,
+ dict_nisplus->dict.name);
+ vstring_strcpy(retval,
+ NIS_RES_OBJECT(reply)->zo_data.objdata_u
+ .en_data.en_cols.en_cols_val[dict_nisplus->column]
+ .ec_value.ec_value_val);
+ if (msg_verbose)
+ msg_info("%s: %s, column %d -> %s", myname, STR(query),
+ dict_nisplus->column, STR(retval));
+ nis_freeresult(reply);
+ return (STR(retval));
+ }
+ }
+
+ /*
+ * When the NIS+ lookup fails for reasons other than "key not found",
+ * keep logging warnings, and hope that someone will eventually notice
+ * the problem and fix it.
+ */
+ else {
+ if (reply->status != NIS_NOTFOUND
+ && reply->status != NIS_PARTIAL) {
+ msg_warn("lookup %s, NIS+ map %s: %s",
+ key, dict_nisplus->dict.name,
+ nis_sperrno(reply->status));
+ dict->error = DICT_ERR_RETRY;
+ } else {
+ if (msg_verbose)
+ msg_info("%s: not found: query %s", myname, STR(query));
+ }
+ nis_freeresult(reply);
+ return (0);
+ }
+}
+
+/* dict_nisplus_close - close NISPLUS map */
+
+static void dict_nisplus_close(DICT *dict)
+{
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+
+ myfree(dict_nisplus->template);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_nisplus_open - open NISPLUS map */
+
+DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags)
+{
+ const char *myname = "dict_nisplus_open";
+ DICT_NISPLUS *dict_nisplus;
+ char *col_field;
+
+ /*
+ * Sanity check.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_NISPLUS, map));
+
+ /*
+ * Initialize. This is a read-only map with fixed strings, not with
+ * regular expressions.
+ */
+ dict_nisplus = (DICT_NISPLUS *)
+ dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus));
+ dict_nisplus->dict.lookup = dict_nisplus_lookup;
+ dict_nisplus->dict.close = dict_nisplus_close;
+ dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_nisplus->dict.fold_buf = vstring_alloc(10);
+ dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ /*
+ * Convert the query template into an indexed name and column number. The
+ * query template looks like:
+ *
+ * [attribute=%s;attribute=value...];simple.name.:column
+ *
+ * One instance of %s gets to be replaced by a version of the lookup key;
+ * other attributes must specify fixed values. The reason for using ';'
+ * is that the comma character is special in main.cf. When no column
+ * number is given at the end of the map name, we use a default column.
+ */
+ dict_nisplus->template = mystrdup(map);
+ translit(dict_nisplus->template, ";", ",");
+ if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) {
+ col_field[1] = 0;
+ col_field += 2;
+ if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1)
+ msg_fatal("bad column field in NIS+ map name: %s", map);
+ } else {
+ dict_nisplus->column = 1;
+ }
+ if (msg_verbose)
+ msg_info("%s: opened NIS+ table %s for column %d",
+ myname, dict_nisplus->template, dict_nisplus->column);
+ return (DICT_DEBUG (&dict_nisplus->dict));
+}
+
+#endif
diff --git a/src/util/dict_nisplus.h b/src/util/dict_nisplus.h
new file mode 100644
index 0000000..1fd31c9
--- /dev/null
+++ b/src/util/dict_nisplus.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_NISPLUS_H_INCLUDED_
+#define _DICT_NISPLUS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_nisplus 3h
+/* SUMMARY
+/* dictionary manager interface to NIS+ maps
+/* SYNOPSIS
+/* #include <dict_nisplus.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NISPLUS "nisplus"
+
+extern DICT *dict_nisplus_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_open.c b/src/util/dict_open.c
new file mode 100644
index 0000000..afea391
--- /dev/null
+++ b/src/util/dict_open.c
@@ -0,0 +1,600 @@
+/*++
+/* NAME
+/* dict_open 3
+/* SUMMARY
+/* low-level dictionary interface
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_open(dict_spec, open_flags, dict_flags)
+/* const char *dict_spec;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DICT *dict_open3(dict_type, dict_name, open_flags, dict_flags)
+/* const char *dict_type;
+/* const char *dict_name;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* int dict_put(dict, key, value)
+/* DICT *dict;
+/* const char *key;
+/* const char *value;
+/*
+/* const char *dict_get(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* int dict_del(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* int dict_seq(dict, func, key, value)
+/* DICT *dict;
+/* int func;
+/* const char **key;
+/* const char **value;
+/*
+/* void dict_close(dict)
+/* DICT *dict;
+/*
+/* typedef DICT *(*DICT_OPEN_FN) (const char *, int, int);
+/*
+/* dict_open_register(type, open)
+/* const char *type;
+/* DICT_OPEN_FN open;
+/*
+/* typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN)(const char *type);
+/*
+/* DICT_OPEN_EXTEND_FN dict_open_extend(call_back)
+/* DICT_OPEN_EXTEND_FN call_back;
+/*
+/* ARGV *dict_mapnames()
+/*
+/* typedef ARGV *(*DICT_MAPNAMES_EXTEND_FN)(ARGV *names);
+/*
+/* DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(call_back)
+/* DICT_MAPNAMES_EXTEND_FN call_back;
+/*
+/* int dict_isjmp(dict)
+/* DICT *dict;
+/*
+/* int dict_setjmp(dict)
+/* DICT *dict;
+/*
+/* int dict_longjmp(dict, val)
+/* DICT *dict;
+/* int val;
+/*
+/* void dict_type_override(dict, type)
+/* DICT *dict;
+/* const char *type;
+/* DESCRIPTION
+/* This module implements a low-level interface to multiple
+/* physical dictionary types.
+/*
+/* dict_open() takes a type:name pair that specifies a dictionary type
+/* and dictionary name, opens the dictionary, and returns a dictionary
+/* handle. The \fIopen_flags\fR arguments are as in open(2). The
+/* \fIdict_flags\fR are the bit-wise OR of zero or more of the following:
+/* .IP DICT_FLAG_DUP_WARN
+/* Warn about duplicate keys, if the underlying database does not
+/* support duplicate keys. The default is to terminate with a fatal
+/* error.
+/* .IP DICT_FLAG_DUP_IGNORE
+/* Ignore duplicate keys if the underlying database does not
+/* support duplicate keys. The default is to terminate with a fatal
+/* error.
+/* .IP DICT_FLAG_DUP_REPLACE
+/* Replace duplicate keys if the underlying database supports such
+/* an operation. The default is to terminate with a fatal error.
+/* .IP DICT_FLAG_TRY0NULL
+/* With maps where this is appropriate, append no null byte to
+/* keys and values.
+/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are
+/* specified, the software guesses what format to use for reading;
+/* and in the absence of definite information, a system-dependent
+/* default is chosen for writing.
+/* .IP DICT_FLAG_TRY1NULL
+/* With maps where this is appropriate, append one null byte to
+/* keys and values.
+/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are
+/* specified, the software guesses what format to use for reading;
+/* and in the absence of definite information, a system-dependent
+/* default is chosen for writing.
+/* .IP DICT_FLAG_LOCK
+/* With maps where this is appropriate, acquire an exclusive lock
+/* before writing, and acquire a shared lock before reading.
+/* Release the lock when the operation completes.
+/* .IP DICT_FLAG_OPEN_LOCK
+/* The behavior of this flag depends on whether a database
+/* sets the DICT_FLAG_MULTI_WRITER flag to indicate that it
+/* is multi-writer safe.
+/*
+/* With databases that are not multi-writer safe, dict_open()
+/* acquires a persistent exclusive lock, or it terminates with
+/* a fatal run-time error.
+/*
+/* With databases that are multi-writer safe, dict_open()
+/* downgrades the DICT_FLAG_OPEN_LOCK flag (persistent lock)
+/* to DICT_FLAG_LOCK (temporary lock).
+/* .IP DICT_FLAG_FOLD_FIX
+/* With databases whose lookup fields are fixed-case strings,
+/* fold the search string to lower case before accessing the
+/* database. This includes hash:, cdb:, dbm:. nis:, ldap:,
+/* *sql. WARNING: case folding is supported only for ASCII or
+/* valid UTF-8.
+/* .IP DICT_FLAG_FOLD_MUL
+/* With databases where one lookup field can match both upper
+/* and lower case, fold the search key to lower case before
+/* accessing the database. This includes regexp: and pcre:.
+/* WARNING: case folding is supported only for ASCII or valid
+/* UTF-8.
+/* .IP DICT_FLAG_FOLD_ANY
+/* Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL).
+/* .IP DICT_FLAG_SYNC_UPDATE
+/* With file-based maps, flush I/O buffers to file after each update.
+/* Thus feature is not supported with some file-based dictionaries.
+/* .IP DICT_FLAG_NO_REGSUB
+/* Disallow regular expression substitution from the lookup string
+/* into the lookup result, to block data injection attacks.
+/* .IP DICT_FLAG_NO_PROXY
+/* Disallow access through the unprivileged \fBproxymap\fR
+/* service, to block privilege escalation attacks.
+/* .IP DICT_FLAG_NO_UNAUTH
+/* Disallow lookup mechanisms that lack any form of authentication,
+/* to block privilege escalation attacks (example: tcp_table;
+/* even NIS can be secured to some extent by requiring that
+/* the server binds to a privileged port).
+/* .IP DICT_FLAG_PARANOID
+/* A combination of all the paranoia flags: DICT_FLAG_NO_REGSUB,
+/* DICT_FLAG_NO_PROXY and DICT_FLAG_NO_UNAUTH.
+/* .IP DICT_FLAG_BULK_UPDATE
+/* Enable preliminary code for bulk-mode database updates.
+/* The caller must create an exception handler with dict_jmp_alloc()
+/* and must trap exceptions from the database client with dict_setjmp().
+/* .IP DICT_FLAG_DEBUG
+/* Enable additional logging.
+/* .IP DICT_FLAG_UTF8_REQUEST
+/* With util_utf8_enable != 0, require that lookup/update/delete
+/* keys and values are valid UTF-8. Skip a lookup/update/delete
+/* request with a non-UTF-8 key, skip an update request with
+/* a non-UTF-8 value, and fail a lookup request with a non-UTF-8
+/* value.
+/* .IP DICT_FLAG_SRC_RHS_IS_FILE
+/* With dictionaries that are created from source text, each
+/* value in the source of a dictionary specifies a list of
+/* file names separated by comma and/or whitespace. The file
+/* contents are concatenated with a newline inserted between
+/* files, and the base64-encoded result is stored under the
+/* key.
+/* .sp
+/* NOTE 1: it is up to the application to decode lookup results
+/* with dict_file_lookup() or equivalent (this requires that
+/* the dictionary is opened with DICT_FLAG_SRC_RHS_IS_FILE).
+/* Decoding is not built into the normal dictionary lookup
+/* method, because that would complicate dictionary nesting,
+/* pipelining, and proxying.
+/* .sp
+/* NOTE 2: it is up to the application to convert file names
+/* into base64-encoded file content before calling the dictionary
+/* update method (see dict_file(3) for support). Automatic
+/* file content encoding is available only when a dictionary
+/* is created from source text.
+/* .PP
+/* Specify DICT_FLAG_NONE for no special processing.
+/*
+/* The dictionary types are as follows:
+/* .IP environ
+/* The process environment array. The \fIdict_name\fR argument is ignored.
+/* .IP dbm
+/* DBM file.
+/* .IP hash
+/* Berkeley DB file in hash format.
+/* .IP btree
+/* Berkeley DB file in btree format.
+/* .IP nis
+/* NIS map. Only read access is supported.
+/* .IP nisplus
+/* NIS+ map. Only read access is supported.
+/* .IP netinfo
+/* NetInfo table. Only read access is supported.
+/* .IP ldap
+/* LDAP ("light-weight" directory access protocol) database access.
+/* .IP pcre
+/* PERL-compatible regular expressions.
+/* .IP regexp
+/* POSIX-compatible regular expressions.
+/* .IP texthash
+/* Flat text in postmap(1) input format.
+/* .PP
+/* dict_open3() takes separate arguments for dictionary type and
+/* name, but otherwise performs the same functions as dict_open().
+/*
+/* The dict_get(), dict_put(), dict_del(), and dict_seq()
+/* macros evaluate their first argument multiple times.
+/* These names should have been in uppercase.
+/*
+/* dict_get() retrieves the value stored in the named dictionary
+/* under the given key. A null pointer means the value was not found.
+/* As with dict_lookup(), the result is owned by the lookup table
+/* implementation. Make a copy if the result is to be modified,
+/* or if the result is to survive multiple table lookups.
+/*
+/* dict_put() stores the specified key and value into the named
+/* dictionary. A zero (DICT_STAT_SUCCESS) result means the
+/* update was made.
+/*
+/* dict_del() removes a dictionary entry, and returns
+/* DICT_STAT_SUCCESS in case of success.
+/*
+/* dict_seq() iterates over all members in the named dictionary.
+/* func is define DICT_SEQ_FUN_FIRST (select first member) or
+/* DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS)
+/* result means that an entry was found.
+/*
+/* dict_close() closes the specified dictionary and cleans up the
+/* associated data structures.
+/*
+/* dict_open_register() adds support for a new dictionary type.
+/*
+/* dict_open_extend() registers a call-back function that looks
+/* up the dictionary open() function for a type that is not
+/* registered, or null in case of error. The result value is
+/* the last previously-registered call-back or null.
+/*
+/* dict_mapnames() returns a sorted list with the names of all available
+/* dictionary types.
+/*
+/* dict_mapnames_extend() registers a call-back function that
+/* enumerates additional dictionary type names. The result
+/* will be sorted by dict_mapnames(). The result value
+/* is the last previously-registered call-back or null.
+/*
+/* dict_setjmp() saves processing context and makes that context
+/* available for use with dict_longjmp(). Normally, dict_setjmp()
+/* returns zero. A non-zero result means that dict_setjmp()
+/* returned through a dict_longjmp() call; the result is the
+/* \fIval\fR argument given to dict_longjmp(). dict_isjmp()
+/* returns non-zero when dict_setjmp() and dict_longjmp()
+/* are enabled for a given dictionary.
+/*
+/* NB: non-local jumps such as dict_longjmp() are not safe for
+/* jumping out of any routine that manipulates DICT data.
+/* longjmp() like calls are best avoided in signal handlers.
+/*
+/* dict_type_override() changes the symbolic dictionary type.
+/* This is used by dictionaries whose internals are based on
+/* some other dictionary type.
+/* DIAGNOSTICS
+/* Fatal error: open error, unsupported dictionary type, attempt to
+/* update non-writable dictionary.
+/*
+/* The lookup routine returns non-null when the request is
+/* satisfied. The update, delete and sequence routines return
+/* zero (DICT_STAT_SUCCESS) when the request is satisfied.
+/* The dict->errno value is non-zero only when the last operation
+/* was not satisfied due to a dictionary access error. This
+/* can have the following values:
+/* .IP DICT_ERR_NONE(zero)
+/* There was no dictionary access error. For example, the
+/* request was satisfied, the requested information did not
+/* exist in the dictionary, or the information already existed
+/* when it should not exist (collision).
+/* .IP DICT_ERR_RETRY(<0)
+/* The dictionary was temporarily unavailable. This can happen
+/* with network-based services.
+/* .IP DICT_ERR_CONFIG(<0)
+/* The dictionary was unavailable due to a configuration error.
+/* .PP
+/* Generally, a program is expected to test the function result
+/* value for "success" first. If the operation was not successful,
+/* a program is expected to test for a non-zero dict->error
+/* status to distinguish between a data notfound/collision
+/* condition or a dictionary access error.
+/* 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 <argv.h>
+#include <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+#include <dict_cdb.h>
+#include <dict_env.h>
+#include <dict_unix.h>
+#include <dict_tcp.h>
+#include <dict_sdbm.h>
+#include <dict_dbm.h>
+#include <dict_db.h>
+#include <dict_lmdb.h>
+#include <dict_nis.h>
+#include <dict_nisplus.h>
+#include <dict_ni.h>
+#include <dict_pcre.h>
+#include <dict_regexp.h>
+#include <dict_static.h>
+#include <dict_cidr.h>
+#include <dict_ht.h>
+#include <dict_thash.h>
+#include <dict_sockmap.h>
+#include <dict_fail.h>
+#include <dict_pipe.h>
+#include <dict_random.h>
+#include <dict_union.h>
+#include <dict_inline.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <htable.h>
+#include <myflock.h>
+
+ /*
+ * lookup table for available map types.
+ */
+typedef struct {
+ char *type;
+ DICT_OPEN_FN open;
+} DICT_OPEN_INFO;
+
+static const DICT_OPEN_INFO dict_open_info[] = {
+ DICT_TYPE_ENVIRON, dict_env_open,
+ DICT_TYPE_HT, dict_ht_open,
+ DICT_TYPE_UNIX, dict_unix_open,
+ DICT_TYPE_TCP, dict_tcp_open,
+#ifdef HAS_DBM
+ DICT_TYPE_DBM, dict_dbm_open,
+#endif
+#ifdef HAS_DB
+ DICT_TYPE_HASH, dict_hash_open,
+ DICT_TYPE_BTREE, dict_btree_open,
+#endif
+#ifdef HAS_NIS
+ DICT_TYPE_NIS, dict_nis_open,
+#endif
+#ifdef HAS_NISPLUS
+ DICT_TYPE_NISPLUS, dict_nisplus_open,
+#endif
+#ifdef HAS_NETINFO
+ DICT_TYPE_NETINFO, dict_ni_open,
+#endif
+#ifdef HAS_POSIX_REGEXP
+ DICT_TYPE_REGEXP, dict_regexp_open,
+#endif
+ DICT_TYPE_STATIC, dict_static_open,
+ DICT_TYPE_CIDR, dict_cidr_open,
+ DICT_TYPE_THASH, dict_thash_open,
+ DICT_TYPE_SOCKMAP, dict_sockmap_open,
+ DICT_TYPE_FAIL, dict_fail_open,
+ DICT_TYPE_PIPE, dict_pipe_open,
+ DICT_TYPE_RANDOM, dict_random_open,
+ DICT_TYPE_UNION, dict_union_open,
+ DICT_TYPE_INLINE, dict_inline_open,
+#ifndef USE_DYNAMIC_MAPS
+#ifdef HAS_PCRE
+ DICT_TYPE_PCRE, dict_pcre_open,
+#endif
+#ifdef HAS_CDB
+ DICT_TYPE_CDB, dict_cdb_open,
+#endif
+#ifdef HAS_SDBM
+ DICT_TYPE_SDBM, dict_sdbm_open,
+#endif
+#ifdef HAS_LMDB
+ DICT_TYPE_LMDB, dict_lmdb_open,
+#endif
+#endif /* !USE_DYNAMIC_MAPS */
+ 0,
+};
+
+static HTABLE *dict_open_hash;
+
+ /*
+ * Extension hooks.
+ */
+static DICT_OPEN_EXTEND_FN dict_open_extend_hook;
+static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook;
+
+ /*
+ * Workaround.
+ */
+DEFINE_DICT_LMDB_MAP_SIZE;
+DEFINE_DICT_DB_CACHE_SIZE;
+
+/* dict_open_init - one-off initialization */
+
+static void dict_open_init(void)
+{
+ const char *myname = "dict_open_init";
+ const DICT_OPEN_INFO *dp;
+
+ if (dict_open_hash != 0)
+ msg_panic("%s: multiple initialization", myname);
+ dict_open_hash = htable_create(10);
+
+ for (dp = dict_open_info; dp->type; dp++)
+ htable_enter(dict_open_hash, dp->type, (void *) dp);
+}
+
+/* dict_open - open dictionary */
+
+DICT *dict_open(const char *dict_spec, int open_flags, int dict_flags)
+{
+ char *saved_dict_spec = mystrdup(dict_spec);
+ char *dict_name;
+ DICT *dict;
+
+ if ((dict_name = split_at(saved_dict_spec, ':')) == 0)
+ msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"",
+ dict_spec);
+
+ dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags);
+ myfree(saved_dict_spec);
+ return (dict);
+}
+
+
+/* dict_open3 - open dictionary */
+
+DICT *dict_open3(const char *dict_type, const char *dict_name,
+ int open_flags, int dict_flags)
+{
+ const char *myname = "dict_open";
+ DICT_OPEN_INFO *dp;
+ DICT_OPEN_FN open_fn;
+ DICT *dict;
+
+ if (*dict_type == 0 || *dict_name == 0)
+ msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"",
+ dict_type, dict_name);
+ if (dict_open_hash == 0)
+ dict_open_init();
+ if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0) {
+ if (dict_open_extend_hook != 0
+ && (open_fn = dict_open_extend_hook(dict_type)) != 0) {
+ dict_open_register(dict_type, open_fn);
+ dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type);
+ }
+ if (dp == 0)
+ return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
+ "unsupported dictionary type: %s", dict_type));
+ }
+ if ((dict = dp->open(dict_name, open_flags, dict_flags)) == 0)
+ return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
+ "cannot open %s:%s: %m", dict_type, dict_name));
+ if (msg_verbose)
+ msg_info("%s: %s:%s", myname, dict_type, dict_name);
+ /* XXX The choice between wait-for-lock or no-wait is hard-coded. */
+ if (dict->flags & DICT_FLAG_OPEN_LOCK) {
+ if (dict->flags & DICT_FLAG_LOCK)
+ msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock",
+ myname, dict_type, dict_name);
+ /* Multi-writer safe map: downgrade persistent lock to temporary. */
+ if (dict->flags & DICT_FLAG_MULTI_WRITER) {
+ dict->flags &= ~DICT_FLAG_OPEN_LOCK;
+ dict->flags |= DICT_FLAG_LOCK;
+ }
+ /* Multi-writer unsafe map: acquire exclusive lock or bust. */
+ else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0)
+ msg_fatal("%s:%s: unable to get exclusive lock: %m",
+ dict_type, dict_name);
+ }
+ /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags))
+ dict = dict_utf8_activate(dict);
+ return (dict);
+}
+
+/* dict_open_register - register dictionary type */
+
+void dict_open_register(const char *type, DICT_OPEN_FN open)
+{
+ const char *myname = "dict_open_register";
+ DICT_OPEN_INFO *dp;
+ HTABLE_INFO *ht;
+
+ if (dict_open_hash == 0)
+ dict_open_init();
+ if (htable_find(dict_open_hash, type))
+ msg_panic("%s: dictionary type exists: %s", myname, type);
+ dp = (DICT_OPEN_INFO *) mymalloc(sizeof(*dp));
+ dp->open = open;
+ ht = htable_enter(dict_open_hash, type, (void *) dp);
+ dp->type = ht->key;
+}
+
+/* dict_open_extend - register alternate dictionary search routine */
+
+DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb)
+{
+ DICT_OPEN_EXTEND_FN old_cb;
+
+ old_cb = dict_open_extend_hook;
+ dict_open_extend_hook = new_cb;
+ return (old_cb);
+}
+
+/* dict_sort_alpha_cpp - qsort() callback */
+
+static int dict_sort_alpha_cpp(const void *a, const void *b)
+{
+ return (strcmp(((char **) a)[0], ((char **) b)[0]));
+}
+
+/* dict_mapnames - return an ARGV of available map_names */
+
+ARGV *dict_mapnames()
+{
+ HTABLE_INFO **ht_info;
+ HTABLE_INFO **ht;
+ DICT_OPEN_INFO *dp;
+ ARGV *mapnames;
+
+ if (dict_open_hash == 0)
+ dict_open_init();
+ mapnames = argv_alloc(dict_open_hash->used + 1);
+ for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) {
+ dp = (DICT_OPEN_INFO *) ht[0]->value;
+ argv_add(mapnames, dp->type, ARGV_END);
+ }
+ if (dict_mapnames_extend_hook != 0)
+ (void) dict_mapnames_extend_hook(mapnames);
+ qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]),
+ dict_sort_alpha_cpp);
+ myfree((void *) ht_info);
+ argv_terminate(mapnames);
+ return mapnames;
+}
+
+/* dict_mapnames_extend - register alternate dictionary type list routine */
+
+DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb)
+{
+ DICT_MAPNAMES_EXTEND_FN old_cb;
+
+ old_cb = dict_mapnames_extend_hook;
+ dict_mapnames_extend_hook = new_cb;
+ return (old_cb);
+}
+
+/* dict_type_override - disguise a dictionary type */
+
+void dict_type_override(DICT *dict, const char *type)
+{
+ myfree(dict->type);
+ dict->type = mystrdup(type);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program.
+ */
+int main(int argc, char **argv)
+{
+ dict_test(argc, argv);
+ return (0);
+}
+
+#endif
diff --git a/src/util/dict_pcre.c b/src/util/dict_pcre.c
new file mode 100644
index 0000000..3cceda2
--- /dev/null
+++ b/src/util/dict_pcre.c
@@ -0,0 +1,1120 @@
+/*++
+/* NAME
+/* dict_pcre 3
+/* SUMMARY
+/* dictionary manager interface to PCRE regular expression library
+/* SYNOPSIS
+/* #include <dict_pcre.h>
+/*
+/* DICT *dict_pcre_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_pcre_open() opens the named file and compiles the contained
+/* regular expressions. The result object can be used to match strings
+/* against the table.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* 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
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_PCRE
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#if HAS_PCRE == 1
+#include <pcre.h>
+#elif HAS_PCRE == 2
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+#else
+#error "define HAS_PCRE=2 or HAS_PCRE=1"
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "readlline.h"
+#include "dict.h"
+#include "dict_pcre.h"
+#include "mac_parse.h"
+#include "warn_stat.h"
+#include "mvect.h"
+
+ /*
+ * Backwards compatibility.
+ */
+#if HAS_PCRE == 1
+ /* PCRE Legacy JIT supprt. */
+#ifdef PCRE_STUDY_JIT_COMPILE
+#define DICT_PCRE_FREE_STUDY(x) pcre_free_study(x)
+#else
+#define DICT_PCRE_FREE_STUDY(x) pcre_free((char *) (x))
+#endif
+
+ /* PCRE Compiled pattern. */
+#define DICT_PCRE_CODE pcre
+#define DICT_PCRE_CODE_FREE(x) myfree((void *) (x))
+
+ /* Old-style hints versus new-style match_data. */
+#define DICT_PCRE_MATCH_HINT_TYPE pcre_extra *
+#define DICT_PCRE_MATCH_HINT_NAME hints
+#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME)
+#define DICT_PCRE_MATCH_HINT_FREE(x) do { \
+ if (DICT_PCRE_MATCH_HINT(x)) \
+ DICT_PCRE_FREE_STUDY(DICT_PCRE_MATCH_HINT(x)); \
+ } while (0)
+
+ /* PCRE Pattern options. */
+#define DICT_PCRE_CASELESS PCRE_CASELESS
+#define DICT_PCRE_MULTILINE PCRE_MULTILINE
+#define DICT_PCRE_DOTALL PCRE_DOTALL
+#define DICT_PCRE_EXTENDED PCRE_EXTENDED
+#define DICT_PCRE_ANCHORED PCRE_ANCHORED
+#define DICT_PCRE_DOLLAR_ENDONLY PCRE_DOLLAR_ENDONLY
+#define DICT_PCRE_UNGREEDY PCRE_UNGREEDY
+#define DICT_PCRE_EXTRA PCRE_EXTRA
+
+ /* PCRE Number of captures in pattern. */
+#ifdef PCRE_INFO_CAPTURECOUNT
+#define DICT_PCRE_CAPTURECOUNT_T int
+#endif
+
+#else /* HAS_PCRE */
+
+ /* PCRE2 Compiled pattern. */
+#define DICT_PCRE_CODE pcre2_code
+#define DICT_PCRE_CODE_FREE(x) pcre2_code_free(x)
+
+ /* PCRE2 Old-style hints versus new-style match_data. */
+#define DICT_PCRE_MATCH_HINT_TYPE pcre2_match_data *
+#define DICT_PCRE_MATCH_HINT_NAME match_data
+#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME)
+#define DICT_PCRE_MATCH_HINT_FREE(x) \
+ pcre2_match_data_free(DICT_PCRE_MATCH_HINT(x))
+
+ /* PCRE2 Pattern options. */
+#define DICT_PCRE_CASELESS PCRE2_CASELESS
+#define DICT_PCRE_MULTILINE PCRE2_MULTILINE
+#define DICT_PCRE_DOTALL PCRE2_DOTALL
+#define DICT_PCRE_EXTENDED PCRE2_EXTENDED
+#define DICT_PCRE_ANCHORED PCRE2_ANCHORED
+#define DICT_PCRE_DOLLAR_ENDONLY PCRE2_DOLLAR_ENDONLY
+#define DICT_PCRE_UNGREEDY PCRE2_UNGREEDY
+#define DICT_PCRE_EXTRA 0
+
+ /* PCRE2 Number of captures in pattern. */
+#define DICT_PCRE_CAPTURECOUNT_T uint32_t
+
+#endif /* HAS_PCRE */
+
+ /*
+ * Support for IF/ENDIF based on an idea by Bert Driehuis.
+ */
+#define DICT_PCRE_OP_MATCH 1 /* Match this regexp */
+#define DICT_PCRE_OP_IF 2 /* Increase if/endif nesting on match */
+#define DICT_PCRE_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+ /*
+ * Max strings captured by regexp - essentially the max number of (..)
+ */
+#if HAS_PCRE == 1
+#define PCRE_MAX_CAPTURE 99
+#endif
+
+ /*
+ * Regular expression before and after compilation.
+ */
+typedef struct {
+ char *regexp; /* regular expression */
+ int options; /* options */
+ int match; /* positive or negative match */
+} DICT_PCRE_REGEXP;
+
+typedef struct {
+ DICT_PCRE_CODE *pattern; /* the compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+} DICT_PCRE_ENGINE;
+
+ /*
+ * Compiled generic rule, and subclasses that derive from it.
+ */
+typedef struct DICT_PCRE_RULE {
+ int op; /* DICT_PCRE_OP_MATCH/IF/ENDIF */
+ int lineno; /* source file line number */
+ struct DICT_PCRE_RULE *next; /* next rule in dict */
+} DICT_PCRE_RULE;
+
+typedef struct {
+ DICT_PCRE_RULE rule; /* generic part */
+ DICT_PCRE_CODE *pattern; /* compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+ char *replacement; /* replacement string */
+ int match; /* positive or negative match */
+ size_t max_sub; /* largest $number in replacement */
+} DICT_PCRE_MATCH_RULE;
+
+typedef struct {
+ DICT_PCRE_RULE rule; /* generic members */
+ DICT_PCRE_CODE *pattern; /* compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+ int match; /* positive or negative match */
+ struct DICT_PCRE_RULE *endif_rule; /* matching endif rule */
+} DICT_PCRE_IF_RULE;
+
+ /*
+ * PCRE map.
+ */
+typedef struct {
+ DICT dict; /* generic members */
+ DICT_PCRE_RULE *head;
+ VSTRING *expansion_buf; /* lookup result */
+} DICT_PCRE;
+
+#if HAS_PCRE == 1
+static int dict_pcre_init = 0; /* flag need to init pcre library */
+
+#endif
+
+/*
+ * Context for $number expansion callback.
+ */
+typedef struct {
+ DICT_PCRE *dict_pcre; /* the dictionary handle */
+#if HAS_PCRE == 1
+ DICT_PCRE_MATCH_RULE *match_rule; /* the rule we matched */
+#endif
+ const char *lookup_string; /* string against which we match */
+#if HAS_PCRE == 1
+ int offsets[PCRE_MAX_CAPTURE * 3]; /* Cut substrings */
+#else /* HAS_PCRE */
+ PCRE2_SIZE *ovector; /* matched string offsets */
+#endif /* HAS_PCRE */
+ int matches; /* Count of cuts */
+} DICT_PCRE_EXPAND_CONTEXT;
+
+ /*
+ * Context for $number pre-scan callback.
+ */
+typedef struct {
+ const char *mapname; /* name of regexp map */
+ int lineno; /* where in file */
+ size_t max_sub; /* Largest $n seen */
+ char *literal; /* constant result, $$ -> $ */
+} DICT_PCRE_PRESCAN_CONTEXT;
+
+ /*
+ * Compatibility.
+ */
+#ifndef MAC_PARSE_OK
+#define MAC_PARSE_OK 0
+#endif
+
+ /*
+ * Macros to make dense code more accessible.
+ */
+#define NULL_STARTOFFSET (0)
+#define NULL_EXEC_OPTIONS (0)
+
+/* dict_pcre_expand - replace $number with matched text */
+
+static int dict_pcre_expand(int type, VSTRING *buf, void *ptr)
+{
+ DICT_PCRE_EXPAND_CONTEXT *ctxt = (DICT_PCRE_EXPAND_CONTEXT *) ptr;
+ DICT_PCRE *dict_pcre = ctxt->dict_pcre;
+ int n;
+
+#if HAS_PCRE == 1
+ DICT_PCRE_MATCH_RULE *match_rule = ctxt->match_rule;
+ const char *pp;
+ int ret;
+
+#else
+ PCRE2_SPTR start;
+ PCRE2_SIZE length;
+
+#endif
+
+ /*
+ * Replace $0-${99} with strings cut from matched text.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ n = atoi(vstring_str(buf));
+#if HAS_PCRE == 1
+ ret = pcre_get_substring(ctxt->lookup_string, ctxt->offsets,
+ ctxt->matches, n, &pp);
+ if (ret < 0) {
+ if (ret == PCRE_ERROR_NOSUBSTRING)
+ return (MAC_PARSE_UNDEF);
+ else
+ msg_fatal("pcre map %s, line %d: pcre_get_substring error: %d",
+ dict_pcre->dict.name, match_rule->rule.lineno, ret);
+ }
+ if (*pp == 0) {
+ myfree((void *) pp);
+ return (MAC_PARSE_UNDEF);
+ }
+ vstring_strcat(dict_pcre->expansion_buf, pp);
+ myfree((void *) pp);
+ return (MAC_PARSE_OK);
+#else
+ start = (unsigned char *) ctxt->lookup_string + ctxt->ovector[2 * n];
+ length = ctxt->ovector[2 * n + 1] - ctxt->ovector[2 * n];
+ if (length == 0)
+ return (MAC_PARSE_UNDEF);
+ vstring_strncat(dict_pcre->expansion_buf, (char *) start, length);
+ return (MAC_PARSE_OK);
+#endif
+ }
+
+ /*
+ * Straight text - duplicate with no substitution.
+ */
+ else {
+ vstring_strcat(dict_pcre->expansion_buf, vstring_str(buf));
+ return (MAC_PARSE_OK);
+ }
+}
+
+#if HAS_PCRE == 2
+
+#define DICT_PCRE_GET_ERROR_BUF_LEN 256
+
+/* dict_pcre_get_error - convert PCRE2 error number or text */
+
+static char *dict_pcre_get_error(VSTRING *buf, int errval)
+{
+ ssize_t len;
+
+ VSTRING_SPACE(buf, DICT_PCRE_GET_ERROR_BUF_LEN);
+ if ((len = pcre2_get_error_message(errval,
+ (unsigned char *) vstring_str(buf),
+ DICT_PCRE_GET_ERROR_BUF_LEN)) > 0) {
+ vstring_set_payload_size(buf, len);
+ } else
+ vstring_sprintf(buf, "unexpected pcre2 error code %d", errval);
+ return (vstring_str(buf));
+}
+
+#endif /* HAS_PCRE == 2 */
+
+/* dict_pcre_exec_error - report matching error */
+
+static void dict_pcre_exec_error(const char *mapname, int lineno, int errval)
+{
+#if HAS_PCRE == 1
+ switch (errval) {
+ case 0:
+ msg_warn("pcre map %s, line %d: too many (...)",
+ mapname, lineno);
+ return;
+ case PCRE_ERROR_NULL:
+ case PCRE_ERROR_BADOPTION:
+ msg_warn("pcre map %s, line %d: bad args to re_exec",
+ mapname, lineno);
+ return;
+ case PCRE_ERROR_BADMAGIC:
+ case PCRE_ERROR_UNKNOWN_NODE:
+ msg_warn("pcre map %s, line %d: corrupt compiled regexp",
+ mapname, lineno);
+ return;
+#ifdef PCRE_ERROR_NOMEMORY
+ case PCRE_ERROR_NOMEMORY:
+ msg_warn("pcre map %s, line %d: out of memory",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_MATCHLIMIT
+ case PCRE_ERROR_MATCHLIMIT:
+ msg_warn("pcre map %s, line %d: backtracking limit exceeded",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_BADUTF8
+ case PCRE_ERROR_BADUTF8:
+ msg_warn("pcre map %s, line %d: bad UTF-8 sequence in search string",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_BADUTF8_OFFSET
+ case PCRE_ERROR_BADUTF8_OFFSET:
+ msg_warn("pcre map %s, line %d: bad UTF-8 start offset in search string",
+ mapname, lineno);
+ return;
+#endif
+ default:
+ msg_warn("pcre map %s, line %d: unknown pcre_exec error: %d",
+ mapname, lineno, errval);
+ return;
+ }
+#else /* HAS_PCRE */
+ VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN);
+
+ msg_warn("pcre map %s, line %d: %s", mapname, lineno,
+ dict_pcre_get_error(buf, errval));
+ vstring_free(buf);
+#endif /* HAS_PCRE */
+}
+
+ /*
+ * Inlined to reduce function call overhead in the time-critical loop.
+ */
+#if HAS_PCRE == 1
+#define DICT_PCRE_EXEC(ctxt, map, line, pattern, hints, match, str, len) \
+ ((ctxt).matches = pcre_exec((pattern), (hints), (str), (len), \
+ NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \
+ (ctxt).offsets, PCRE_MAX_CAPTURE * 3), \
+ (ctxt).matches > 0 ? (match) : \
+ (ctxt).matches == PCRE_ERROR_NOMATCH ? !(match) : \
+ (dict_pcre_exec_error((map), (line), (ctxt).matches), 0))
+#else
+#define DICT_PCRE_EXEC(ctxt, map, line, pattern, match_data, match, str, len) \
+ ((ctxt).matches = pcre2_match((pattern), (unsigned char *) (str), (len), \
+ NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \
+ (match_data), (pcre2_match_context *) 0), \
+ (ctxt).matches > 0 ? (match) : \
+ (ctxt).matches == PCRE2_ERROR_NOMATCH ? !(match) : \
+ (dict_pcre_exec_error((map), (line), (ctxt).matches), 0))
+#endif
+
+/* dict_pcre_lookup - match string and perform optional substitution */
+
+static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string)
+{
+ DICT_PCRE *dict_pcre = (DICT_PCRE *) dict;
+ DICT_PCRE_RULE *rule;
+ DICT_PCRE_IF_RULE *if_rule;
+ DICT_PCRE_MATCH_RULE *match_rule;
+ int lookup_len = strlen(lookup_string);
+ DICT_PCRE_EXPAND_CONTEXT ctxt;
+
+ dict->error = 0;
+
+ if (msg_verbose)
+ msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, lookup_string);
+ lookup_string = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (rule = dict_pcre->head; rule; rule = rule->next) {
+
+ switch (rule->op) {
+
+ /*
+ * Search for a matching expression.
+ */
+ case DICT_PCRE_OP_MATCH:
+ match_rule = (DICT_PCRE_MATCH_RULE *) rule;
+ if (!DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno,
+ match_rule->pattern,
+ DICT_PCRE_MATCH_HINT(match_rule),
+ match_rule->match, lookup_string, lookup_len))
+ continue;
+
+ /*
+ * Skip $number substitutions when the replacement text contains
+ * no $number strings, as learned during the compile time
+ * pre-scan. The pre-scan already replaced $$ by $.
+ */
+ if (match_rule->max_sub == 0)
+ return match_rule->replacement;
+
+ /*
+ * We've got a match. Perform substitution on replacement string.
+ */
+ if (dict_pcre->expansion_buf == 0)
+ dict_pcre->expansion_buf = vstring_alloc(10);
+ VSTRING_RESET(dict_pcre->expansion_buf);
+ ctxt.dict_pcre = dict_pcre;
+#if HAS_PCRE == 1
+ ctxt.match_rule = match_rule;
+#else
+ ctxt.ovector = pcre2_get_ovector_pointer(match_rule->match_data);
+#endif
+ ctxt.lookup_string = lookup_string;
+
+ if (mac_parse(match_rule->replacement, dict_pcre_expand,
+ (void *) &ctxt) & MAC_PARSE_ERROR)
+ msg_fatal("pcre map %s, line %d: bad replacement syntax",
+ dict->name, rule->lineno);
+
+ VSTRING_TERMINATE(dict_pcre->expansion_buf);
+ return (vstring_str(dict_pcre->expansion_buf));
+
+ /*
+ * Conditional. XXX We provide space for matched substring info
+ * because PCRE uses part of it as workspace for backtracking.
+ * PCRE will allocate memory if it runs out of backtracking
+ * storage.
+ */
+ case DICT_PCRE_OP_IF:
+ if_rule = (DICT_PCRE_IF_RULE *) rule;
+ if (DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno,
+ if_rule->pattern,
+ DICT_PCRE_MATCH_HINT(if_rule),
+ if_rule->match, lookup_string, lookup_len))
+ continue;
+ /* An IF without matching ENDIF has no "endif" rule. */
+ if ((rule = if_rule->endif_rule) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ /*
+ * ENDIF after IF.
+ */
+ case DICT_PCRE_OP_ENDIF:
+ continue;
+
+ default:
+ msg_panic("dict_pcre_lookup: impossible operation %d", rule->op);
+ }
+ }
+ return (0);
+}
+
+/* dict_pcre_close - close pcre dictionary */
+
+static void dict_pcre_close(DICT *dict)
+{
+ DICT_PCRE *dict_pcre = (DICT_PCRE *) dict;
+ DICT_PCRE_RULE *rule;
+ DICT_PCRE_RULE *next;
+ DICT_PCRE_MATCH_RULE *match_rule;
+ DICT_PCRE_IF_RULE *if_rule;
+
+ for (rule = dict_pcre->head; rule; rule = next) {
+ next = rule->next;
+ switch (rule->op) {
+ case DICT_PCRE_OP_MATCH:
+ match_rule = (DICT_PCRE_MATCH_RULE *) rule;
+ if (match_rule->pattern)
+ DICT_PCRE_CODE_FREE(match_rule->pattern);
+ DICT_PCRE_MATCH_HINT_FREE(match_rule);
+ if (match_rule->replacement)
+ myfree((void *) match_rule->replacement);
+ break;
+ case DICT_PCRE_OP_IF:
+ if_rule = (DICT_PCRE_IF_RULE *) rule;
+ if (if_rule->pattern)
+ DICT_PCRE_CODE_FREE(if_rule->pattern);
+ DICT_PCRE_MATCH_HINT_FREE(if_rule);
+ break;
+ case DICT_PCRE_OP_ENDIF:
+ break;
+ default:
+ msg_panic("dict_pcre_close: unknown operation %d", rule->op);
+ }
+ myfree((void *) rule);
+ }
+ if (dict_pcre->expansion_buf)
+ vstring_free(dict_pcre->expansion_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_pcre_get_pattern - extract pattern from rule */
+
+static int dict_pcre_get_pattern(const char *mapname, int lineno, char **bufp,
+ DICT_PCRE_REGEXP *pattern)
+{
+ char *p = *bufp;
+ char re_delimiter;
+
+ /*
+ * Process negation operators.
+ */
+ pattern->match = 1;
+ for (;;) {
+ if (*p == '!')
+ pattern->match = !pattern->match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ msg_warn("pcre map %s, line %d: no regexp: skipping this rule",
+ mapname, lineno);
+ return (0);
+ }
+ re_delimiter = *p++;
+ pattern->regexp = p;
+
+ /*
+ * Search for second delimiter, handling backslash escape.
+ */
+ while (*p) {
+ if (*p == '\\') {
+ ++p;
+ if (*p == 0)
+ break;
+ } else if (*p == re_delimiter)
+ break;
+ ++p;
+ }
+
+ if (!*p) {
+ msg_warn("pcre map %s, line %d: no closing regexp delimiter \"%c\": "
+ "ignoring this rule", mapname, lineno, re_delimiter);
+ return (0);
+ }
+ *p++ = 0; /* Null term the regexp */
+
+ /*
+ * Parse any regexp options.
+ */
+ pattern->options = DICT_PCRE_CASELESS | DICT_PCRE_DOTALL;
+ while (*p && !ISSPACE(*p)) {
+ switch (*p) {
+ case 'i':
+ pattern->options ^= DICT_PCRE_CASELESS;
+ break;
+ case 'm':
+ pattern->options ^= DICT_PCRE_MULTILINE;
+ break;
+ case 's':
+ pattern->options ^= DICT_PCRE_DOTALL;
+ break;
+ case 'x':
+ pattern->options ^= DICT_PCRE_EXTENDED;
+ break;
+ case 'A':
+ pattern->options ^= DICT_PCRE_ANCHORED;
+ break;
+ case 'E':
+ pattern->options ^= DICT_PCRE_DOLLAR_ENDONLY;
+ break;
+ case 'U':
+ pattern->options ^= DICT_PCRE_UNGREEDY;
+ break;
+ case 'X':
+#if DICT_PCRE_EXTRA != 0
+ pattern->options ^= DICT_PCRE_EXTRA;
+#else
+ msg_warn("pcre map %s, line %d: ignoring obsolete regexp "
+ "option \"%c\"", mapname, lineno, *p);
+#endif
+ break;
+ default:
+ msg_warn("pcre map %s, line %d: unknown regexp option \"%c\": "
+ "skipping this rule", mapname, lineno, *p);
+ return (0);
+ }
+ ++p;
+ }
+ *bufp = p;
+ return (1);
+}
+
+/* dict_pcre_prescan - sanity check $number instances in replacement text */
+
+static int dict_pcre_prescan(int type, VSTRING *buf, void *context)
+{
+ DICT_PCRE_PRESCAN_CONTEXT *ctxt = (DICT_PCRE_PRESCAN_CONTEXT *) context;
+ size_t n;
+
+ /*
+ * Keep a copy of literal text (with $$ already replaced by $) if and
+ * only if the replacement text contains no $number expression. This way
+ * we can avoid having to scan the replacement text at lookup time.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ if (ctxt->literal) {
+ myfree(ctxt->literal);
+ ctxt->literal = 0;
+ }
+ if (!alldig(vstring_str(buf))) {
+ msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ n = atoi(vstring_str(buf));
+ if (n < 1) {
+ msg_warn("pcre map %s, line %d: out of range replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ if (n > ctxt->max_sub)
+ ctxt->max_sub = n;
+ } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) {
+ if (ctxt->literal)
+ msg_panic("pcre map %s, line %d: multiple literals but no $number",
+ ctxt->mapname, ctxt->lineno);
+ ctxt->literal = mystrdup(vstring_str(buf));
+ }
+ return (MAC_PARSE_OK);
+}
+
+/* dict_pcre_compile - compile pattern */
+
+static int dict_pcre_compile(const char *mapname, int lineno,
+ DICT_PCRE_REGEXP *pattern,
+ DICT_PCRE_ENGINE *engine)
+{
+#if HAS_PCRE == 1
+ const char *error;
+ int errptr;
+
+ engine->pattern = pcre_compile(pattern->regexp, pattern->options,
+ &error, &errptr, NULL);
+ if (engine->pattern == 0) {
+ msg_warn("pcre map %s, line %d: error in regex at offset %d: %s",
+ mapname, lineno, errptr, error);
+ return (0);
+ }
+ engine->hints = pcre_study(engine->pattern, 0, &error);
+ if (error != 0) {
+ msg_warn("pcre map %s, line %d: error while studying regex: %s",
+ mapname, lineno, error);
+ DICT_PCRE_CODE_FREE(engine->pattern);
+ return (0);
+ }
+#else
+ int error;
+ size_t errptr;
+
+ engine->pattern = pcre2_compile((unsigned char *) pattern->regexp,
+ PCRE2_ZERO_TERMINATED,
+ pattern->options, &error, &errptr, NULL);
+ if (engine->pattern == 0) {
+ VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN);
+
+ msg_warn("pcre map %s, line %d: error in regex at offset %lu: %s",
+ mapname, lineno, (unsigned long) errptr,
+ dict_pcre_get_error(buf, error));
+ vstring_free(buf);
+ return (0);
+ }
+ engine->match_data = pcre2_match_data_create_from_pattern(
+ engine->pattern, (void *) 0);
+#endif
+ return (1);
+}
+
+/* dict_pcre_rule_alloc - fill in a generic rule structure */
+
+static DICT_PCRE_RULE *dict_pcre_rule_alloc(int op, int lineno, size_t size)
+{
+ DICT_PCRE_RULE *rule;
+
+ rule = (DICT_PCRE_RULE *) mymalloc(size);
+ rule->op = op;
+ rule->lineno = lineno;
+ rule->next = 0;
+
+ return (rule);
+}
+
+/* dict_pcre_parse_rule - parse and compile one rule */
+
+static DICT_PCRE_RULE *dict_pcre_parse_rule(DICT *dict, const char *mapname,
+ int lineno, char *line,
+ int nesting)
+{
+ char *p;
+
+#ifdef DICT_PCRE_CAPTURECOUNT_T
+ DICT_PCRE_CAPTURECOUNT_T actual_sub;
+
+#endif
+#if 0
+ uint32_t namecount;
+
+#endif
+
+ p = line;
+
+ /*
+ * An ordinary match rule takes one pattern and replacement text.
+ */
+ if (!ISALNUM(*p)) {
+ DICT_PCRE_REGEXP regexp;
+ DICT_PCRE_ENGINE engine;
+ DICT_PCRE_PRESCAN_CONTEXT prescan_context;
+ DICT_PCRE_MATCH_RULE *match_rule;
+
+ /*
+ * Get the pattern string and options.
+ */
+ if (dict_pcre_get_pattern(mapname, lineno, &p, &regexp) == 0)
+ return (0);
+
+ /*
+ * Get the replacement text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (!*p)
+ msg_warn("pcre map %s, line %d: no replacement text: "
+ "using empty string", mapname, lineno);
+
+ /*
+ * Sanity check the $number instances in the replacement text.
+ */
+ prescan_context.mapname = mapname;
+ prescan_context.lineno = lineno;
+ prescan_context.max_sub = 0;
+ prescan_context.literal = 0;
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \
+ if (prescan_context.literal) \
+ myfree(prescan_context.literal); \
+ return (rval); \
+ } while (0)
+
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, p)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("pcre map %s, line %d: %s: skipping this rule",
+ mapname, lineno, err);
+ myfree(err);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ p = vstring_str(base64_buf);
+ }
+ if (mac_parse(p, dict_pcre_prescan, (void *) &prescan_context)
+ & MAC_PARSE_ERROR) {
+ msg_warn("pcre map %s, line %d: bad replacement syntax: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Substring replacement not possible with negative regexps.
+ */
+ if (prescan_context.max_sub > 0 && regexp.match == 0) {
+ msg_warn("pcre map %s, line %d: $number found in negative match "
+ "replacement text: skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) {
+ msg_warn("pcre map %s, line %d: "
+ "regular expression substitution is not allowed: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Compile the pattern.
+ */
+ if (dict_pcre_compile(mapname, lineno, &regexp, &engine) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+#ifdef DICT_PCRE_CAPTURECOUNT_T
+#if HAS_PCRE == 1
+ if (pcre_fullinfo(engine.pattern, engine.hints,
+ PCRE_INFO_CAPTURECOUNT,
+ (void *) &actual_sub) != 0)
+ msg_panic("pcre map %s, line %d: pcre_fullinfo failed",
+ mapname, lineno);
+#else /* HAS_PCRE */
+#if 0
+ if (pcre2_pattern_info(
+ engine.pattern, PCRE2_INFO_NAMECOUNT, &namecount) != 0)
+ msg_panic("pcre map %s, line %d: pcre2_pattern_info failed",
+ mapname, lineno);
+ if (namecount > 0) {
+ msg_warn("pcre map %s, line %d: named substrings are not supported",
+ mapname, lineno);
+ if (engine.pattern)
+ DICT_PCRE_CODE_FREE(engine.pattern);
+ DICT_PCRE_MATCH_HINT_FREE(&engine);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+#endif
+ if (pcre2_pattern_info(engine.pattern, PCRE2_INFO_CAPTURECOUNT,
+ (void *) &actual_sub) != 0)
+ msg_panic("pcre map %s, line %d: pcre2_pattern_info failed",
+ mapname, lineno);
+#endif /* HAS_PCRE */
+ if (prescan_context.max_sub > actual_sub) {
+ msg_warn("pcre map %s, line %d: out of range replacement index \"%d\": "
+ "skipping this rule", mapname, lineno,
+ (int) prescan_context.max_sub);
+ if (engine.pattern)
+ DICT_PCRE_CODE_FREE(engine.pattern);
+ DICT_PCRE_MATCH_HINT_FREE(&engine);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+#endif /* DICT_PCRE_CAPTURECOUNT_T */
+
+ /*
+ * Save the result.
+ */
+ match_rule = (DICT_PCRE_MATCH_RULE *)
+ dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH, lineno,
+ sizeof(DICT_PCRE_MATCH_RULE));
+ match_rule->match = regexp.match;
+ match_rule->max_sub = prescan_context.max_sub;
+ if (prescan_context.literal)
+ match_rule->replacement = prescan_context.literal;
+ else
+ match_rule->replacement = mystrdup(p);
+ match_rule->pattern = engine.pattern;
+ DICT_PCRE_MATCH_HINT(match_rule) = DICT_PCRE_MATCH_HINT(&engine);
+ return ((DICT_PCRE_RULE *) match_rule);
+ }
+
+ /*
+ * The IF operator takes one pattern but no replacement text.
+ */
+ else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ DICT_PCRE_REGEXP regexp;
+ DICT_PCRE_ENGINE engine;
+ DICT_PCRE_IF_RULE *if_rule;
+
+ p += 2;
+
+ /*
+ * Get the pattern.
+ */
+ while (*p && ISSPACE(*p))
+ p++;
+ if (!dict_pcre_get_pattern(mapname, lineno, &p, &regexp))
+ return (0);
+
+ /*
+ * Warn about out-of-place text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p) {
+ msg_warn("pcre map %s, line %d: ignoring extra text after "
+ "IF statement: \"%s\"", mapname, lineno, p);
+ msg_warn("pcre map %s, line %d: do not prepend whitespace"
+ " to statements between IF and ENDIF", mapname, lineno);
+ }
+
+ /*
+ * Compile the pattern.
+ */
+ if (dict_pcre_compile(mapname, lineno, &regexp, &engine) == 0)
+ return (0);
+
+ /*
+ * Save the result.
+ */
+ if_rule = (DICT_PCRE_IF_RULE *)
+ dict_pcre_rule_alloc(DICT_PCRE_OP_IF, lineno,
+ sizeof(DICT_PCRE_IF_RULE));
+ if_rule->match = regexp.match;
+ if_rule->pattern = engine.pattern;
+ DICT_PCRE_MATCH_HINT(if_rule) = DICT_PCRE_MATCH_HINT(&engine);
+ if_rule->endif_rule = 0;
+ return ((DICT_PCRE_RULE *) if_rule);
+ }
+
+ /*
+ * The ENDIF operator takes no patterns and no replacement text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ DICT_PCRE_RULE *rule;
+
+ p += 5;
+
+ /*
+ * Warn about out-of-place ENDIFs.
+ */
+ if (nesting == 0) {
+ msg_warn("pcre map %s, line %d: ignoring ENDIF without matching IF",
+ mapname, lineno);
+ return (0);
+ }
+
+ /*
+ * Warn about out-of-place text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p)
+ msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF",
+ mapname, lineno);
+
+ /*
+ * Save the result.
+ */
+ rule = dict_pcre_rule_alloc(DICT_PCRE_OP_ENDIF, lineno,
+ sizeof(DICT_PCRE_RULE));
+ return (rule);
+ }
+
+ /*
+ * Unrecognized input.
+ */
+ else {
+ msg_warn("pcre map %s, line %d: ignoring unrecognized request",
+ mapname, lineno);
+ return (0);
+ }
+}
+
+/* dict_pcre_open - load and compile a file containing regular expressions */
+
+DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_pcre_open";
+ DICT_PCRE *dict_pcre;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *why = 0;
+ VSTRING *line_buffer = 0;
+ DICT_PCRE_RULE *last_rule = 0;
+ DICT_PCRE_RULE *rule;
+ int last_line = 0;
+ int lineno;
+ int nesting = 0;
+ char *p;
+ DICT_PCRE_RULE **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_PCRE_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (map_fp != 0) \
+ vstream_fclose(map_fp); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_PCRE, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_PCRE, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+
+ dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname,
+ sizeof(*dict_pcre));
+ dict_pcre->dict.lookup = dict_pcre_lookup;
+ dict_pcre->dict.close = dict_pcre_close;
+ dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_pcre->dict.fold_buf = vstring_alloc(10);
+ dict_pcre->head = 0;
+ dict_pcre->expansion_buf = 0;
+
+#if HAS_PCRE == 1
+ if (dict_pcre_init == 0) {
+ pcre_malloc = (void *(*) (size_t)) mymalloc;
+ pcre_free = (void (*) (void *)) myfree;
+ dict_pcre_init = 1;
+ }
+#endif
+ dict_pcre->dict.owner.uid = st.st_uid;
+ dict_pcre->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Parse the pcre table.
+ */
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ p = vstring_str(line_buffer);
+ trimblanks(p, 0)[0] = 0; /* Trim space at end */
+ if (*p == 0)
+ continue;
+ rule = dict_pcre_parse_rule(&dict_pcre->dict, mapname, lineno,
+ p, nesting);
+ if (rule == 0)
+ continue;
+ if (rule->op == DICT_PCRE_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_PCRE_RULE **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_PCRE_RULE **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->op == DICT_PCRE_OP_ENDIF) {
+ DICT_PCRE_IF_RULE *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_pcre_parse_rule(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if (rule_stack[nesting]->op != DICT_PCRE_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, rule_stack[nesting]->op);
+ if_rule = (DICT_PCRE_IF_RULE *) rule_stack[nesting];
+ if_rule->endif_rule = rule;
+ }
+ if (last_rule == 0)
+ dict_pcre->head = rule;
+ else
+ last_rule->next = rule;
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("pcre map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ dict_file_purge_buffers(&dict_pcre->dict);
+ DICT_PCRE_OPEN_RETURN(DICT_DEBUG (&dict_pcre->dict));
+}
+
+#endif /* HAS_PCRE */
diff --git a/src/util/dict_pcre.h b/src/util/dict_pcre.h
new file mode 100644
index 0000000..3aa4955
--- /dev/null
+++ b/src/util/dict_pcre.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_PCRE_H_INCLUDED_
+#define _DICT_PCRE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_pcre 3h
+/* SUMMARY
+/* dictionary manager interface to PCRE regular expression library
+/* SYNOPSIS
+/* #include <dict_pcre.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PCRE "pcre"
+
+extern DICT *dict_pcre_open(const char *, int, int);
+
+/* 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
+/*
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*--*/
+
+#endif
diff --git a/src/util/dict_pcre.in b/src/util/dict_pcre.in
new file mode 100644
index 0000000..b0644bd
--- /dev/null
+++ b/src/util/dict_pcre.in
@@ -0,0 +1,15 @@
+get true
+get true1
+get true2
+get truefalse2
+get 3
+get true3
+get c
+get d
+get 1235
+get 1234
+get 123
+get bar/find
+get bar/whynot
+get bar/elbereth
+get say/elbereth
diff --git a/src/util/dict_pcre.map b/src/util/dict_pcre.map
new file mode 100644
index 0000000..23c976a
--- /dev/null
+++ b/src/util/dict_pcre.map
@@ -0,0 +1,28 @@
+if /true/ fodder
+/1/ 1
+if /false/
+/2/ 2
+endif fodder
+/3/ 3
+endif
+/a/!/b/ a!b
+/c/
+/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5)
+/(1)(2)(3)(4)/ ($1)($2)($3)($4)
+/(1)(2)(3)/ ($1)($2)($3)
+# trailing whitespace below
+if /bar/
+if !/xyzzy/
+/(elbereth)/ ($1)
+!/(bogus)/ ($1)
+!/find/ Don't have a liquor license
+endif
+endif
+# trailing whitespace above
+!
+# dangling endif and if
+endif
+endif
+if /./
+if /./
+/(/ unused
diff --git a/src/util/dict_pcre.ref b/src/util/dict_pcre.ref
new file mode 100644
index 0000000..9b14678
--- /dev/null
+++ b/src/util/dict_pcre.ref
@@ -0,0 +1,44 @@
+./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF statement: "fodder"
+./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 9: no replacement text: using empty string
+./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 24: ignoring ENDIF without matching IF
+./dict_open: warning: pcre map dict_pcre.map, line 25: ignoring ENDIF without matching IF
+./dict_open: warning: pcre map dict_pcre.map, line 28: error in regex at offset 1: missing closing parenthesis
+./dict_open: warning: pcre map dict_pcre.map, line 27: IF has no matching ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 26: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get true
+true: not found
+> get true1
+true1=1
+> get true2
+true2: not found
+> get truefalse2
+truefalse2=2
+> get 3
+3: not found
+> get true3
+true3=3
+> get c
+c=
+> get d
+d: not found
+> get 1235
+1235=(1)(2)(3)
+> get 1234
+1234=(1)(2)(3)(4)
+> get 123
+123=(1)(2)(3)
+> get bar/find
+bar/find: not found
+> get bar/whynot
+bar/whynot=Don't have a liquor license
+> get bar/elbereth
+bar/elbereth=(elbereth)
+> get say/elbereth
+say/elbereth: not found
diff --git a/src/util/dict_pcre_file.in b/src/util/dict_pcre_file.in
new file mode 100644
index 0000000..28c0bd5
--- /dev/null
+++ b/src/util/dict_pcre_file.in
@@ -0,0 +1,4 @@
+get file1
+get file2
+get file3
+get files12
diff --git a/src/util/dict_pcre_file.map b/src/util/dict_pcre_file.map
new file mode 100644
index 0000000..4fd12e6
--- /dev/null
+++ b/src/util/dict_pcre_file.map
@@ -0,0 +1,6 @@
+/file1/ dict_pcre_file1
+/file2/ dict_pcre_file2
+/file3/ dict_pcre_file3
+/files12/ dict_pcre_file1, dict_pcre_file2
+/files13/ dict_pcre_file1, dict_pcre_file3
+/file-comma/ ,
diff --git a/src/util/dict_pcre_file.ref b/src/util/dict_pcre_file.ref
new file mode 100644
index 0000000..727306d
--- /dev/null
+++ b/src/util/dict_pcre_file.ref
@@ -0,0 +1,12 @@
+./dict_open: warning: pcre map dict_pcre_file.map, line 3: open dict_pcre_file3: No such file or directory: skipping this rule
+./dict_open: warning: pcre map dict_pcre_file.map, line 5: open dict_pcre_file3: No such file or directory: skipping this rule
+./dict_open: warning: pcre map dict_pcre_file.map, line 6: empty pathname list: >>,<<': skipping this rule
+owner=untrusted (uid=USER)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
+> get files12
+files12=dGhpcy1pcy1maWxlMQoKdGhpcy1pcy1maWxlMgo=
diff --git a/src/util/dict_pipe.c b/src/util/dict_pipe.c
new file mode 100644
index 0000000..8ce0faa
--- /dev/null
+++ b/src/util/dict_pipe.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* dict_pipe 3
+/* SUMMARY
+/* dictionary manager interface for pipelined tables
+/* SYNOPSIS
+/* #include <dict_pipe.h>
+/*
+/* DICT *dict_pipe_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_pipe_open() opens a pipeline of one or more tables.
+/* Example: "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fR}".
+/*
+/* Each "pipemap:" query is given to the first table. Each
+/* lookup result becomes the query for the next table in the
+/* pipeline, and the last table produces the final result.
+/* When any table lookup produces no result, the pipeline
+/* produces no result.
+/*
+/* The first and last characters of the "pipemap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/*
+/* The open_flags and dict_flags arguments are passed on to
+/* the underlying dictionaries.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 "mymalloc.h"
+#include "htable.h"
+#include "dict.h"
+#include "dict_pipe.h"
+#include "stringops.h"
+#include "vstring.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *map_pipe; /* pipelined tables */
+ VSTRING *qr_buf; /* query/reply buffer */
+} DICT_PIPE;
+
+#define STR(x) vstring_str(x)
+
+/* dict_pipe_lookup - search pipelined tables */
+
+static const char *dict_pipe_lookup(DICT *dict, const char *query)
+{
+ static const char myname[] = "dict_pipe_lookup";
+ DICT_PIPE *dict_pipe = (DICT_PIPE *) dict;
+ DICT *map;
+ char **cpp;
+ char *dict_type_name;
+ const char *result = 0;
+
+ vstring_strcpy(dict_pipe->qr_buf, query);
+ for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if ((map = dict_handle(dict_type_name)) == 0)
+ msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
+ if ((result = dict_get(map, STR(dict_pipe->qr_buf))) == 0)
+ DICT_ERR_VAL_RETURN(dict, map->error, result);
+ vstring_strcpy(dict_pipe->qr_buf, result);
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_pipe->qr_buf));
+}
+
+/* dict_pipe_close - disassociate from pipelined tables */
+
+static void dict_pipe_close(DICT *dict)
+{
+ DICT_PIPE *dict_pipe = (DICT_PIPE *) dict;
+ char **cpp;
+ char *dict_type_name;
+
+ for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++)
+ dict_unregister(dict_type_name);
+ argv_free(dict_pipe->map_pipe);
+ vstring_free(dict_pipe->qr_buf);
+ dict_free(dict);
+}
+
+/* dict_pipe_open - open pipelined tables */
+
+DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
+{
+ static const char myname[] = "dict_pipe_open";
+ DICT_PIPE *dict_pipe;
+ char *saved_name = 0;
+ char *dict_type_name;
+ ARGV *argv = 0;
+ char **cpp;
+ DICT *dict;
+ int match_flags = 0;
+ struct DICT_OWNER aggr_owner;
+ size_t len;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_PIPE_RETURN(x) do { \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (argv != 0) \
+ argv_free(argv); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_PIPE, name));
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
+ (argv->argc == 0)))
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_PIPE, name,
+ DICT_TYPE_PIPE));
+
+ /*
+ * The least-trusted table in the pipeline determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ if (strchr(dict_type_name, ':') == 0)
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_PIPE, name,
+ DICT_TYPE_PIPE));
+ if ((dict = dict_handle(dict_type_name)) == 0)
+ dict = dict_open(dict_type_name, open_flags, dict_flags);
+ dict_register(dict_type_name, dict);
+ DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
+ if (cpp == argv->argv)
+ match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ dict_pipe =
+ (DICT_PIPE *) dict_alloc(DICT_TYPE_PIPE, name, sizeof(*dict_pipe));
+ dict_pipe->dict.lookup = dict_pipe_lookup;
+ dict_pipe->dict.close = dict_pipe_close;
+ dict_pipe->dict.flags = dict_flags | match_flags;
+ dict_pipe->dict.owner = aggr_owner;
+ dict_pipe->qr_buf = vstring_alloc(100);
+ dict_pipe->map_pipe = argv;
+ argv = 0;
+ DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict));
+}
diff --git a/src/util/dict_pipe.h b/src/util/dict_pipe.h
new file mode 100644
index 0000000..8d03009
--- /dev/null
+++ b/src/util/dict_pipe.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_PIPE_H_INCLUDED_
+#define _DICT_PIPE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_pipe 3h
+/* SUMMARY
+/* dictionary manager interface for pipelined tables
+/* SYNOPSIS
+/* #include <dict_pipe.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PIPE "pipemap"
+
+extern DICT *dict_pipe_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_pipe_test.in b/src/util/dict_pipe_test.in
new file mode 100644
index 0000000..9626dcd
--- /dev/null
+++ b/src/util/dict_pipe_test.in
@@ -0,0 +1,9 @@
+${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read <<EOF
+get k0
+get k1
+get k2
+EOF
+${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1},fail:fail}' read <<EOF
+get k0
+get k1
+EOF
diff --git a/src/util/dict_pipe_test.ref b/src/util/dict_pipe_test.ref
new file mode 100644
index 0000000..ecb865a
--- /dev/null
+++ b/src/util/dict_pipe_test.ref
@@ -0,0 +1,14 @@
++ ./dict_open pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}} read
+owner=trusted (uid=2147483647)
+> get k0
+k0: not found
+> get k1
+k1: not found
+> get k2
+k2=v3
++ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read
+owner=trusted (uid=2147483647)
+> get k0
+k0: not found
+> get k1
+k1: error
diff --git a/src/util/dict_random.c b/src/util/dict_random.c
new file mode 100644
index 0000000..36f79b3
--- /dev/null
+++ b/src/util/dict_random.c
@@ -0,0 +1,179 @@
+/*++
+/* NAME
+/* dict_random 3
+/* SUMMARY
+/* dictionary manager interface for randomized tables
+/* SYNOPSIS
+/* #include <dict_random.h>
+/*
+/* DICT *dict_random_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_random_open() opens an in-memory, read-only, table.
+/* Example: "\fBrandmap:{\fIresult_1, ... ,result_n}\fR".
+/*
+/* Each table query returns a random choice from the specified
+/* results. Other table access methods are not supported.
+/*
+/* The first and last characters of the "randmap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 <mymalloc.h>
+#include <myrand.h>
+#include <stringops.h>
+#include <dict_random.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *replies; /* reply values */
+} DICT_RANDOM;
+
+#define STR(x) vstring_str(x)
+
+/* dict_random_lookup - find randomized-table entry */
+
+static const char *dict_random_lookup(DICT *dict, const char *unused_query)
+{
+ DICT_RANDOM *dict_random = (DICT_RANDOM *) dict;
+
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
+ dict_random->replies->argv[myrand() % dict_random->replies->argc]);
+}
+
+/* dict_random_close - disassociate from randomized table */
+
+static void dict_random_close(DICT *dict)
+{
+ DICT_RANDOM *dict_random = (DICT_RANDOM *) dict;
+
+ if (dict_random->replies)
+ argv_free(dict_random->replies);
+ dict_free(dict);
+}
+
+static char *dict_random_parse_name(DICT *dict, ARGV **argv,
+ const char *string,
+ const char *delim,
+ const char *parens)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+ VSTRING *b64 = 0;
+ char *err = 0;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0) {
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ if ((b64 = dict_file_to_b64(dict, arg)) != 0) {
+ argv_add(argvp, vstring_str(b64), (char *) 0);
+ } else {
+ err = dict_file_get_error(dict);
+ break;
+ }
+ } else {
+ argv_add(argvp, arg, (char *) 0);
+ }
+ }
+ argv_terminate(argvp);
+ myfree(saved_string);
+ *argv = argvp;
+ return (err);
+}
+
+/* dict_random_open - open a randomized table */
+
+DICT *dict_random_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_RANDOM *dict_random;
+ char *saved_name = 0;
+ size_t len;
+ char *err = 0;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_RANDOM_RETURN(x) do { \
+ DICT *__d = (x); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (err != 0) \
+ myfree(err); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_RANDOM_RETURN(dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_RANDOM, name));
+
+ /*
+ * Bundle up preliminary results.
+ */
+ dict_random =
+ (DICT_RANDOM *) dict_alloc(DICT_TYPE_RANDOM, name, sizeof(*dict_random));
+ dict_random->dict.lookup = dict_random_lookup;
+ dict_random->dict.close = dict_random_close;
+ dict_random->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dict_random->replies = 0;
+ dict_random->dict.owner.status = DICT_OWNER_TRUSTED;
+ dict_random->dict.owner.uid = 0;
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || (err = dict_random_parse_name(&dict_random->dict,
+ &dict_random->replies, saved_name,
+ CHARS_COMMA_SP, CHARS_BRACE)) != 0
+ || dict_random->replies->argc == 0) {
+ dict_random_close(&dict_random->dict);
+ DICT_RANDOM_RETURN(err == 0 ?
+ dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{value...}\"",
+ DICT_TYPE_RANDOM, name,
+ DICT_TYPE_RANDOM) :
+ dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "%s", err));
+ }
+ dict_file_purge_buffers(&dict_random->dict);
+ DICT_RANDOM_RETURN(DICT_DEBUG (&dict_random->dict));
+}
diff --git a/src/util/dict_random.h b/src/util/dict_random.h
new file mode 100644
index 0000000..b143d11
--- /dev/null
+++ b/src/util/dict_random.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_RANDOM_H_INCLUDED_
+#define _DICT_RANDOM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_random 3h
+/* SUMMARY
+/* dictionary manager interface for randomized tables
+/* SYNOPSIS
+/* #include <dict_random.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_RANDOM "randmap"
+
+extern DICT *dict_random_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_random.ref b/src/util/dict_random.ref
new file mode 100644
index 0000000..0993409
--- /dev/null
+++ b/src/util/dict_random.ref
@@ -0,0 +1,20 @@
+owner=trusted (uid=0)
+> get foo
+foo=123
+> get bar
+bar=123
+./dict_open: error: bad syntax: "randmap:123"; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:123 is unavailable. bad syntax: "randmap:123"; need "randmap:{value...}"
+foo: error
+./dict_open: error: bad syntax: "randmap:{ 123 "; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{ 123 is unavailable. bad syntax: "randmap:{ 123 "; need "randmap:{value...}"
+foo: error
+./dict_open: error: bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{ 123 }x is unavailable. bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}"
+foo: error
diff --git a/src/util/dict_random_file.ref b/src/util/dict_random_file.ref
new file mode 100644
index 0000000..cb01873
--- /dev/null
+++ b/src/util/dict_random_file.ref
@@ -0,0 +1,10 @@
+./dict_open: error: open fooxx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{fooxx} is unavailable. open fooxx: No such file or directory
+foo: error
+owner=trusted (uid=0)
+> get foo
+foo=dGhpcy1pcy1maWxlMQo=
+> get bar
+bar=dGhpcy1pcy1maWxlMQo=
diff --git a/src/util/dict_regexp.c b/src/util/dict_regexp.c
new file mode 100644
index 0000000..e5e95cf
--- /dev/null
+++ b/src/util/dict_regexp.c
@@ -0,0 +1,874 @@
+/*++
+/* NAME
+/* dict_regexp 3
+/* SUMMARY
+/* dictionary manager interface to REGEXP regular expression library
+/* SYNOPSIS
+/* #include <dict_regexp.h>
+/*
+/* DICT *dict_regexp_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_regexp_open() opens the named file and compiles the contained
+/* regular expressions. The result object can be used to match strings
+/* against the table.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* regexp_table(5) format of Postfix regular expression tables
+/* AUTHOR(S)
+/* LaMont Jones
+/* lamont@hp.com
+/*
+/* Based on PCRE dictionary contributed by Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* Heavily rewritten by 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"
+
+#ifdef HAS_POSIX_REGEXP
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <regex.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "readlline.h"
+#include "dict.h"
+#include "dict_regexp.h"
+#include "mac_parse.h"
+#include "warn_stat.h"
+#include "mvect.h"
+
+ /*
+ * Support for IF/ENDIF based on an idea by Bert Driehuis.
+ */
+#define DICT_REGEXP_OP_MATCH 1 /* Match this regexp */
+#define DICT_REGEXP_OP_IF 2 /* Increase if/endif nesting on match */
+#define DICT_REGEXP_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+ /*
+ * Regular expression before compiling.
+ */
+typedef struct {
+ char *regexp; /* regular expression */
+ int options; /* regcomp() options */
+ int match; /* positive or negative match */
+} DICT_REGEXP_PATTERN;
+
+ /*
+ * Compiled generic rule, and subclasses that derive from it.
+ */
+typedef struct DICT_REGEXP_RULE {
+ int op; /* DICT_REGEXP_OP_MATCH/IF/ENDIF */
+ int lineno; /* source file line number */
+ struct DICT_REGEXP_RULE *next; /* next rule in dict */
+} DICT_REGEXP_RULE;
+
+typedef struct {
+ DICT_REGEXP_RULE rule; /* generic part */
+ regex_t *first_exp; /* compiled primary pattern */
+ int first_match; /* positive or negative match */
+ regex_t *second_exp; /* compiled secondary pattern */
+ int second_match; /* positive or negative match */
+ char *replacement; /* replacement text */
+ size_t max_sub; /* largest $number in replacement */
+} DICT_REGEXP_MATCH_RULE;
+
+typedef struct {
+ DICT_REGEXP_RULE rule; /* generic members */
+ regex_t *expr; /* the condition */
+ int match; /* positive or negative match */
+ struct DICT_REGEXP_RULE *endif_rule;/* matching endif rule */
+} DICT_REGEXP_IF_RULE;
+
+ /*
+ * Regexp map.
+ */
+typedef struct {
+ DICT dict; /* generic members */
+ regmatch_t *pmatch; /* matched substring info */
+ DICT_REGEXP_RULE *head; /* first rule */
+ VSTRING *expansion_buf; /* lookup result */
+} DICT_REGEXP;
+
+ /*
+ * Macros to make dense code more readable.
+ */
+#define NULL_SUBSTITUTIONS (0)
+#define NULL_MATCH_RESULT ((regmatch_t *) 0)
+
+ /*
+ * Context for $number expansion callback.
+ */
+typedef struct {
+ DICT_REGEXP *dict_regexp; /* the dictionary handle */
+ DICT_REGEXP_MATCH_RULE *match_rule; /* the rule we matched */
+ const char *lookup_string; /* matched text */
+} DICT_REGEXP_EXPAND_CONTEXT;
+
+ /*
+ * Context for $number pre-scan callback.
+ */
+typedef struct {
+ const char *mapname; /* name of regexp map */
+ int lineno; /* where in file */
+ size_t max_sub; /* largest $number seen */
+ char *literal; /* constant result, $$ -> $ */
+} DICT_REGEXP_PRESCAN_CONTEXT;
+
+ /*
+ * Compatibility.
+ */
+#ifndef MAC_PARSE_OK
+#define MAC_PARSE_OK 0
+#endif
+
+/* dict_regexp_expand - replace $number with substring from matched text */
+
+static int dict_regexp_expand(int type, VSTRING *buf, void *ptr)
+{
+ DICT_REGEXP_EXPAND_CONTEXT *ctxt = (DICT_REGEXP_EXPAND_CONTEXT *) ptr;
+ DICT_REGEXP_MATCH_RULE *match_rule = ctxt->match_rule;
+ DICT_REGEXP *dict_regexp = ctxt->dict_regexp;
+ regmatch_t *pmatch;
+ size_t n;
+
+ /*
+ * Replace $number by the corresponding substring from the matched text.
+ * We pre-scanned the replacement text at compile time, so any out of
+ * range $number means that something impossible has happened.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ n = atoi(vstring_str(buf));
+ if (n < 1 || n > match_rule->max_sub)
+ msg_panic("regexp map %s, line %d: out of range replacement index \"%s\"",
+ dict_regexp->dict.name, match_rule->rule.lineno,
+ vstring_str(buf));
+ pmatch = dict_regexp->pmatch + n;
+ if (pmatch->rm_so < 0 || pmatch->rm_so == pmatch->rm_eo)
+ return (MAC_PARSE_UNDEF); /* empty or not matched */
+ vstring_strncat(dict_regexp->expansion_buf,
+ ctxt->lookup_string + pmatch->rm_so,
+ pmatch->rm_eo - pmatch->rm_so);
+ return (MAC_PARSE_OK);
+ }
+
+ /*
+ * Straight text - duplicate with no substitution.
+ */
+ else {
+ vstring_strcat(dict_regexp->expansion_buf, vstring_str(buf));
+ return (MAC_PARSE_OK);
+ }
+}
+
+/* dict_regexp_regerror - report regexp compile/execute error */
+
+static void dict_regexp_regerror(const char *mapname, int lineno, int error,
+ const regex_t *expr)
+{
+ char errbuf[256];
+
+ (void) regerror(error, expr, errbuf, sizeof(errbuf));
+ msg_warn("regexp map %s, line %d: %s", mapname, lineno, errbuf);
+}
+
+ /*
+ * Inlined to reduce function call overhead in the time-critical loop.
+ */
+#define DICT_REGEXP_REGEXEC(err, map, line, expr, match, str, nsub, pmatch) \
+ ((err) = regexec((expr), (str), (nsub), (pmatch), 0), \
+ ((err) == REG_NOMATCH ? !(match) : \
+ (err) == 0 ? (match) : \
+ (dict_regexp_regerror((map), (line), (err), (expr)), 0)))
+
+/* dict_regexp_lookup - match string and perform optional substitution */
+
+static const char *dict_regexp_lookup(DICT *dict, const char *lookup_string)
+{
+ DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_IF_RULE *if_rule;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+ DICT_REGEXP_EXPAND_CONTEXT expand_context;
+ int error;
+
+ dict->error = 0;
+
+ if (msg_verbose)
+ msg_info("dict_regexp_lookup: %s: %s", dict->name, lookup_string);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, lookup_string);
+ lookup_string = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (rule = dict_regexp->head; rule; rule = rule->next) {
+
+ switch (rule->op) {
+
+ /*
+ * Search for the first matching primary expression. Limit the
+ * overhead for substring substitution to the bare minimum.
+ */
+ case DICT_REGEXP_OP_MATCH:
+ match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
+ if (!DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ match_rule->first_exp,
+ match_rule->first_match,
+ lookup_string,
+ match_rule->max_sub > 0 ?
+ match_rule->max_sub + 1 : 0,
+ dict_regexp->pmatch))
+ continue;
+ if (match_rule->second_exp
+ && !DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ match_rule->second_exp,
+ match_rule->second_match,
+ lookup_string,
+ NULL_SUBSTITUTIONS,
+ NULL_MATCH_RESULT))
+ continue;
+
+ /*
+ * Skip $number substitutions when the replacement text contains
+ * no $number strings, as learned during the compile time
+ * pre-scan. The pre-scan already replaced $$ by $.
+ */
+ if (match_rule->max_sub == 0)
+ return (match_rule->replacement);
+
+ /*
+ * Perform $number substitutions on the replacement text. We
+ * pre-scanned the replacement text at compile time. Any macro
+ * expansion errors at this point mean something impossible has
+ * happened.
+ */
+ if (!dict_regexp->expansion_buf)
+ dict_regexp->expansion_buf = vstring_alloc(10);
+ VSTRING_RESET(dict_regexp->expansion_buf);
+ expand_context.lookup_string = lookup_string;
+ expand_context.match_rule = match_rule;
+ expand_context.dict_regexp = dict_regexp;
+
+ if (mac_parse(match_rule->replacement, dict_regexp_expand,
+ (void *) &expand_context) & MAC_PARSE_ERROR)
+ msg_panic("regexp map %s, line %d: bad replacement syntax",
+ dict->name, rule->lineno);
+ VSTRING_TERMINATE(dict_regexp->expansion_buf);
+ return (vstring_str(dict_regexp->expansion_buf));
+
+ /*
+ * Conditional.
+ */
+ case DICT_REGEXP_OP_IF:
+ if_rule = (DICT_REGEXP_IF_RULE *) rule;
+ if (DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ if_rule->expr, if_rule->match, lookup_string,
+ NULL_SUBSTITUTIONS, NULL_MATCH_RESULT))
+ continue;
+ /* An IF without matching ENDIF has no "endif" rule. */
+ if ((rule = if_rule->endif_rule) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ /*
+ * ENDIF after IF.
+ */
+ case DICT_REGEXP_OP_ENDIF:
+ continue;
+
+ default:
+ msg_panic("dict_regexp_lookup: impossible operation %d", rule->op);
+ }
+ }
+ return (0);
+}
+
+/* dict_regexp_close - close regexp dictionary */
+
+static void dict_regexp_close(DICT *dict)
+{
+ DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_RULE *next;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ for (rule = dict_regexp->head; rule; rule = next) {
+ next = rule->next;
+ switch (rule->op) {
+ case DICT_REGEXP_OP_MATCH:
+ match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
+ if (match_rule->first_exp) {
+ regfree(match_rule->first_exp);
+ myfree((void *) match_rule->first_exp);
+ }
+ if (match_rule->second_exp) {
+ regfree(match_rule->second_exp);
+ myfree((void *) match_rule->second_exp);
+ }
+ if (match_rule->replacement)
+ myfree((void *) match_rule->replacement);
+ break;
+ case DICT_REGEXP_OP_IF:
+ if_rule = (DICT_REGEXP_IF_RULE *) rule;
+ if (if_rule->expr) {
+ regfree(if_rule->expr);
+ myfree((void *) if_rule->expr);
+ }
+ break;
+ case DICT_REGEXP_OP_ENDIF:
+ break;
+ default:
+ msg_panic("dict_regexp_close: unknown operation %d", rule->op);
+ }
+ myfree((void *) rule);
+ }
+ if (dict_regexp->pmatch)
+ myfree((void *) dict_regexp->pmatch);
+ if (dict_regexp->expansion_buf)
+ vstring_free(dict_regexp->expansion_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_regexp_get_pat - extract one pattern with options from rule */
+
+static int dict_regexp_get_pat(const char *mapname, int lineno, char **bufp,
+ DICT_REGEXP_PATTERN *pat)
+{
+ char *p = *bufp;
+ char re_delim;
+
+ /*
+ * Process negation operators.
+ */
+ pat->match = 1;
+ for (;;) {
+ if (*p == '!')
+ pat->match = !pat->match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ msg_warn("regexp map %s, line %d: no regexp: skipping this rule",
+ mapname, lineno);
+ return (0);
+ }
+
+ /*
+ * Search for the closing delimiter, handling backslash escape.
+ */
+ re_delim = *p++;
+ pat->regexp = p;
+ while (*p) {
+ if (*p == '\\') {
+ if (p[1])
+ p++;
+ else
+ break;
+ } else if (*p == re_delim) {
+ break;
+ }
+ ++p;
+ }
+ if (!*p) {
+ msg_warn("regexp map %s, line %d: no closing regexp delimiter \"%c\": "
+ "skipping this rule", mapname, lineno, re_delim);
+ return (0);
+ }
+ *p++ = 0; /* null terminate */
+
+ /*
+ * Search for options.
+ */
+ pat->options = REG_EXTENDED | REG_ICASE;
+ while (*p && !ISSPACE(*p) && *p != '!') {
+ switch (*p) {
+ case 'i':
+ pat->options ^= REG_ICASE;
+ break;
+ case 'm':
+ pat->options ^= REG_NEWLINE;
+ break;
+ case 'x':
+ pat->options ^= REG_EXTENDED;
+ break;
+ default:
+ msg_warn("regexp map %s, line %d: unknown regexp option \"%c\": "
+ "skipping this rule", mapname, lineno, *p);
+ return (0);
+ }
+ ++p;
+ }
+ *bufp = p;
+ return (1);
+}
+
+/* dict_regexp_get_pats - get the primary and second patterns and flags */
+
+static int dict_regexp_get_pats(const char *mapname, int lineno, char **p,
+ DICT_REGEXP_PATTERN *first_pat,
+ DICT_REGEXP_PATTERN *second_pat)
+{
+
+ /*
+ * Get the primary and optional secondary patterns and their flags.
+ */
+ if (dict_regexp_get_pat(mapname, lineno, p, first_pat) == 0)
+ return (0);
+ if (**p == '!') {
+#if 0
+ static int bitrot_warned = 0;
+
+ if (bitrot_warned == 0) {
+ msg_warn("regexp file %s, line %d: /pattern1/!/pattern2/ goes away,"
+ " use \"if !/pattern2/ ... /pattern1/ ... endif\" instead",
+ mapname, lineno);
+ bitrot_warned = 1;
+ }
+#endif
+ if (dict_regexp_get_pat(mapname, lineno, p, second_pat) == 0)
+ return (0);
+ } else {
+ second_pat->regexp = 0;
+ }
+ return (1);
+}
+
+/* dict_regexp_prescan - find largest $number in replacement text */
+
+static int dict_regexp_prescan(int type, VSTRING *buf, void *context)
+{
+ DICT_REGEXP_PRESCAN_CONTEXT *ctxt = (DICT_REGEXP_PRESCAN_CONTEXT *) context;
+ size_t n;
+
+ /*
+ * Keep a copy of literal text (with $$ already replaced by $) if and
+ * only if the replacement text contains no $number expression. This way
+ * we can avoid having to scan the replacement text at lookup time.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ if (ctxt->literal) {
+ myfree(ctxt->literal);
+ ctxt->literal = 0;
+ }
+ if (!alldig(vstring_str(buf))) {
+ msg_warn("regexp map %s, line %d: non-numeric replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ n = atoi(vstring_str(buf));
+ if (n < 1) {
+ msg_warn("regexp map %s, line %d: out-of-range replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ if (n > ctxt->max_sub)
+ ctxt->max_sub = n;
+ } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) {
+ if (ctxt->literal)
+ msg_panic("regexp map %s, line %d: multiple literals but no $number",
+ ctxt->mapname, ctxt->lineno);
+ ctxt->literal = mystrdup(vstring_str(buf));
+ }
+ return (MAC_PARSE_OK);
+}
+
+/* dict_regexp_compile_pat - compile one pattern */
+
+static regex_t *dict_regexp_compile_pat(const char *mapname, int lineno,
+ DICT_REGEXP_PATTERN *pat)
+{
+ int error;
+ regex_t *expr;
+
+ expr = (regex_t *) mymalloc(sizeof(*expr));
+ error = regcomp(expr, pat->regexp, pat->options);
+ if (error != 0) {
+ dict_regexp_regerror(mapname, lineno, error, expr);
+ myfree((void *) expr);
+ return (0);
+ }
+ return (expr);
+}
+
+/* dict_regexp_rule_alloc - fill in a generic rule structure */
+
+static DICT_REGEXP_RULE *dict_regexp_rule_alloc(int op, int lineno, size_t size)
+{
+ DICT_REGEXP_RULE *rule;
+
+ rule = (DICT_REGEXP_RULE *) mymalloc(size);
+ rule->op = op;
+ rule->lineno = lineno;
+ rule->next = 0;
+
+ return (rule);
+}
+
+/* dict_regexp_parseline - parse one rule */
+
+static DICT_REGEXP_RULE *dict_regexp_parseline(DICT *dict, const char *mapname,
+ int lineno, char *line,
+ int nesting)
+{
+ char *p;
+
+ p = line;
+
+ /*
+ * An ordinary rule takes one or two patterns and replacement text.
+ */
+ if (!ISALNUM(*p)) {
+ DICT_REGEXP_PATTERN first_pat;
+ DICT_REGEXP_PATTERN second_pat;
+ DICT_REGEXP_PRESCAN_CONTEXT prescan_context;
+ regex_t *first_exp = 0;
+ regex_t *second_exp;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+
+ /*
+ * Get the primary and the optional secondary patterns.
+ */
+ if (!dict_regexp_get_pats(mapname, lineno, &p, &first_pat, &second_pat))
+ return (0);
+
+ /*
+ * Get the replacement text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (!*p) {
+ msg_warn("regexp map %s, line %d: no replacement text: "
+ "using empty string", mapname, lineno);
+ }
+
+ /*
+ * Find the highest-numbered $number in the replacement text. We can
+ * speed up pattern matching 1) by passing hints to the regexp
+ * compiler, setting the REG_NOSUB flag when the replacement text
+ * contains no $number string; 2) by passing hints to the regexp
+ * execution code, limiting the amount of text that is made available
+ * for substitution.
+ */
+ prescan_context.mapname = mapname;
+ prescan_context.lineno = lineno;
+ prescan_context.max_sub = 0;
+ prescan_context.literal = 0;
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \
+ if (first_exp) { \
+ regfree(first_exp); \
+ myfree((void *) first_exp); \
+ } \
+ if (prescan_context.literal) \
+ myfree(prescan_context.literal); \
+ return (rval); \
+ } while (0)
+
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, p)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("regexp map %s, line %d: %s: skipping this rule",
+ mapname, lineno, err);
+ myfree(err);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ p = vstring_str(base64_buf);
+ }
+ if (mac_parse(p, dict_regexp_prescan, (void *) &prescan_context)
+ & MAC_PARSE_ERROR) {
+ msg_warn("regexp map %s, line %d: bad replacement syntax: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Compile the primary and the optional secondary pattern. Speed up
+ * execution when no matched text needs to be substituted into the
+ * result string, or when the highest numbered substring is less than
+ * the total number of () subpatterns.
+ */
+ if (prescan_context.max_sub == 0)
+ first_pat.options |= REG_NOSUB;
+ if (prescan_context.max_sub > 0 && first_pat.match == 0) {
+ msg_warn("regexp map %s, line %d: $number found in negative match "
+ "replacement text: skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) {
+ msg_warn("regexp map %s, line %d: "
+ "regular expression substitution is not allowed: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if ((first_exp = dict_regexp_compile_pat(mapname, lineno,
+ &first_pat)) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ if (prescan_context.max_sub > first_exp->re_nsub) {
+ msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": "
+ "skipping this rule", mapname, lineno,
+ (int) prescan_context.max_sub);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (second_pat.regexp != 0) {
+ second_pat.options |= REG_NOSUB;
+ if ((second_exp = dict_regexp_compile_pat(mapname, lineno,
+ &second_pat)) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ } else {
+ second_exp = 0;
+ }
+ match_rule = (DICT_REGEXP_MATCH_RULE *)
+ dict_regexp_rule_alloc(DICT_REGEXP_OP_MATCH, lineno,
+ sizeof(DICT_REGEXP_MATCH_RULE));
+ match_rule->first_exp = first_exp;
+ match_rule->first_match = first_pat.match;
+ match_rule->max_sub = prescan_context.max_sub;
+ match_rule->second_exp = second_exp;
+ match_rule->second_match = second_pat.match;
+ if (prescan_context.literal)
+ match_rule->replacement = prescan_context.literal;
+ else
+ match_rule->replacement = mystrdup(p);
+ return ((DICT_REGEXP_RULE *) match_rule);
+ }
+
+ /*
+ * The IF operator takes one pattern but no replacement text.
+ */
+ else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ DICT_REGEXP_PATTERN pattern;
+ regex_t *expr;
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ p += 2;
+ while (*p && ISSPACE(*p))
+ p++;
+ if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern))
+ return (0);
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p) {
+ msg_warn("regexp map %s, line %d: ignoring extra text after"
+ " IF statement: \"%s\"", mapname, lineno, p);
+ msg_warn("regexp map %s, line %d: do not prepend whitespace"
+ " to statements between IF and ENDIF", mapname, lineno);
+ }
+ if ((expr = dict_regexp_compile_pat(mapname, lineno, &pattern)) == 0)
+ return (0);
+ if_rule = (DICT_REGEXP_IF_RULE *)
+ dict_regexp_rule_alloc(DICT_REGEXP_OP_IF, lineno,
+ sizeof(DICT_REGEXP_IF_RULE));
+ if_rule->expr = expr;
+ if_rule->match = pattern.match;
+ if_rule->endif_rule = 0;
+ return ((DICT_REGEXP_RULE *) if_rule);
+ }
+
+ /*
+ * The ENDIF operator takes no patterns and no replacement text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ DICT_REGEXP_RULE *rule;
+
+ p += 5;
+ if (nesting == 0) {
+ msg_warn("regexp map %s, line %d: ignoring ENDIF without matching IF",
+ mapname, lineno);
+ return (0);
+ }
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p)
+ msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF",
+ mapname, lineno);
+ rule = dict_regexp_rule_alloc(DICT_REGEXP_OP_ENDIF, lineno,
+ sizeof(DICT_REGEXP_RULE));
+ return (rule);
+ }
+
+ /*
+ * Unrecognized input.
+ */
+ else {
+ msg_warn("regexp map %s, line %d: ignoring unrecognized request",
+ mapname, lineno);
+ return (0);
+ }
+}
+
+/* dict_regexp_open - load and compile a file containing regular expressions */
+
+DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_regexp_open";
+ DICT_REGEXP *dict_regexp;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *why = 0;
+ VSTRING *line_buffer = 0;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_RULE *last_rule = 0;
+ int lineno;
+ int last_line = 0;
+ size_t max_sub = 0;
+ int nesting = 0;
+ char *p;
+ DICT_REGEXP_RULE **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_REGEXP_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (map_fp != 0) \
+ vstream_fclose(map_fp); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP,
+ mapname, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_REGEXP, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_REGEXP, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+
+ dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname,
+ sizeof(*dict_regexp));
+ dict_regexp->dict.lookup = dict_regexp_lookup;
+ dict_regexp->dict.close = dict_regexp_close;
+ dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_regexp->dict.fold_buf = vstring_alloc(10);
+ dict_regexp->head = 0;
+ dict_regexp->pmatch = 0;
+ dict_regexp->expansion_buf = 0;
+ dict_regexp->dict.owner.uid = st.st_uid;
+ dict_regexp->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Parse the regexp table.
+ */
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ p = vstring_str(line_buffer);
+ trimblanks(p, 0)[0] = 0;
+ if (*p == 0)
+ continue;
+ rule = dict_regexp_parseline(&dict_regexp->dict, mapname, lineno,
+ p, nesting);
+ if (rule == 0)
+ continue;
+ if (rule->op == DICT_REGEXP_OP_MATCH) {
+ if (((DICT_REGEXP_MATCH_RULE *) rule)->max_sub > max_sub)
+ max_sub = ((DICT_REGEXP_MATCH_RULE *) rule)->max_sub;
+ } else if (rule->op == DICT_REGEXP_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_REGEXP_RULE **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_REGEXP_RULE **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->op == DICT_REGEXP_OP_ENDIF) {
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_regexp_parseline(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if (rule_stack[nesting]->op != DICT_REGEXP_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, rule_stack[nesting]->op);
+ if_rule = (DICT_REGEXP_IF_RULE *) rule_stack[nesting];
+ if_rule->endif_rule = rule;
+ }
+ if (last_rule == 0)
+ dict_regexp->head = rule;
+ else
+ last_rule->next = rule;
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("regexp map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ /*
+ * Allocate space for only as many matched substrings as used in the
+ * replacement text.
+ */
+ if (max_sub > 0)
+ dict_regexp->pmatch =
+ (regmatch_t *) mymalloc(sizeof(regmatch_t) * (max_sub + 1));
+
+ dict_file_purge_buffers(&dict_regexp->dict);
+ DICT_REGEXP_OPEN_RETURN(DICT_DEBUG (&dict_regexp->dict));
+}
+
+#endif
diff --git a/src/util/dict_regexp.h b/src/util/dict_regexp.h
new file mode 100644
index 0000000..2874b27
--- /dev/null
+++ b/src/util/dict_regexp.h
@@ -0,0 +1,42 @@
+#ifndef _DICT_REGEXP_H_INCLUDED_
+#define _DICT_REGEXP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_regexp 3h
+/* SUMMARY
+/* dictionary manager interface to REGEXP regular expression library
+/* SYNOPSIS
+/* #include <dict_regexp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_REGEXP "regexp"
+
+extern DICT *dict_regexp_open(const char *, int, int);
+
+/* AUTHOR(S)
+/* LaMont Jones
+/* lamont@hp.com
+/*
+/* Based on PCRE dictionary contributed by Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_regexp.in b/src/util/dict_regexp.in
new file mode 100644
index 0000000..d5f3b2e
--- /dev/null
+++ b/src/util/dict_regexp.in
@@ -0,0 +1,17 @@
+get true
+get true1
+get true2
+get truefalse2
+get 3
+get true3
+get c
+get d
+get ab
+get aa
+get 1235
+get 1234
+get 123
+get bar/find
+get bar/whynot
+get bar/elbereth
+get say/elbereth
diff --git a/src/util/dict_regexp.map b/src/util/dict_regexp.map
new file mode 100644
index 0000000..7a6cefb
--- /dev/null
+++ b/src/util/dict_regexp.map
@@ -0,0 +1,27 @@
+if /true/ fodder
+/1/ 1
+if /false/
+/2/ 2
+endif fodder
+/3/ 3
+endif
+/a/!/b/ a!b
+/c/
+/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5)
+/(1)(2)(3)(4)/ ($1)($2)($3)($4)
+/(1)(2)(3)/ ($1)($2)($3)
+# trailing whitespace below
+if /bar/
+if !/xyzzy/
+/(elbereth)/ ($1)
+!/(bogus)/ ($1)
+!/find/ Don't have a liquor license
+endif
+endif
+# trailing whitespace above
+!
+# dangling endif and if
+endif
+endif
+if /./
+if /./
diff --git a/src/util/dict_regexp.ref b/src/util/dict_regexp.ref
new file mode 100644
index 0000000..2a08a3b
--- /dev/null
+++ b/src/util/dict_regexp.ref
@@ -0,0 +1,46 @@
+./dict_open: warning: regexp map dict_regexp.map, line 1: ignoring extra text after IF statement: "fodder"
+./dict_open: warning: regexp map dict_regexp.map, line 1: do not prepend whitespace to statements between IF and ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 9: no replacement text: using empty string
+./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 22: no regexp: skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 24: ignoring ENDIF without matching IF
+./dict_open: warning: regexp map dict_regexp.map, line 25: ignoring ENDIF without matching IF
+./dict_open: warning: regexp map dict_regexp.map, line 27: IF has no matching ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 26: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get true
+true: not found
+> get true1
+true1=1
+> get true2
+true2: not found
+> get truefalse2
+truefalse2=2
+> get 3
+3: not found
+> get true3
+true3=3
+> get c
+c=
+> get d
+d: not found
+> get ab
+ab: not found
+> get aa
+aa=a!b
+> get 1235
+1235=(1)(2)(3)
+> get 1234
+1234=(1)(2)(3)(4)
+> get 123
+123=(1)(2)(3)
+> get bar/find
+bar/find: not found
+> get bar/whynot
+bar/whynot=Don't have a liquor license
+> get bar/elbereth
+bar/elbereth=(elbereth)
+> get say/elbereth
+say/elbereth: not found
diff --git a/src/util/dict_regexp_file.in b/src/util/dict_regexp_file.in
new file mode 100644
index 0000000..fef4146
--- /dev/null
+++ b/src/util/dict_regexp_file.in
@@ -0,0 +1,3 @@
+get file1
+get file2
+get file3
diff --git a/src/util/dict_regexp_file.map b/src/util/dict_regexp_file.map
new file mode 100644
index 0000000..41c6b6a
--- /dev/null
+++ b/src/util/dict_regexp_file.map
@@ -0,0 +1,3 @@
+/file1/ dict_regexp_file1
+/file2/ dict_regexp_file2
+/file3/ dict_regexp_file3
diff --git a/src/util/dict_regexp_file.ref b/src/util/dict_regexp_file.ref
new file mode 100644
index 0000000..2218143
--- /dev/null
+++ b/src/util/dict_regexp_file.ref
@@ -0,0 +1,8 @@
+./dict_open: warning: regexp map dict_regexp_file.map, line 3: open dict_regexp_file3: No such file or directory: skipping this rule
+owner=untrusted (uid=USER)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
diff --git a/src/util/dict_sdbm.c b/src/util/dict_sdbm.c
new file mode 100644
index 0000000..23371dc
--- /dev/null
+++ b/src/util/dict_sdbm.c
@@ -0,0 +1,483 @@
+/*++
+/* NAME
+/* dict_sdbm 3
+/* SUMMARY
+/* dictionary manager interface to SDBM files
+/* SYNOPSIS
+/* #include <dict_sdbm.h>
+/*
+/* DICT *dict_sdbm_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sdbm_open() opens the named SDBM database and makes it available
+/* via the generic interface described in dict_open(3).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* sdbm(3) data base subroutines
+/* 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
+/*--*/
+
+#include "sys_defs.h"
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAS_SDBM
+#include <sdbm.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <myflock.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_sdbm.h>
+#include <warn_stat.h>
+
+#ifdef HAS_SDBM
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ SDBM *dbm; /* open database */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* result buffer */
+} DICT_SDBM;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+/* dict_sdbm_lookup - find database entry */
+
+static const char *dict_sdbm_lookup(DICT *dict, const char *name)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (result);
+}
+
+/* dict_sdbm_update - add or update database entry */
+
+static int dict_sdbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ dbm_key.dptr = (void *) name;
+ dbm_value.dptr = (void *) value;
+ dbm_key.dsize = strlen(name);
+ dbm_value.dsize = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DBM_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dsize++;
+ dbm_value.dsize++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
+ msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* dict_sdbm_delete - delete one entry from the dictionary */
+
+static int dict_sdbm_delete(DICT *dict, const char *name)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ int status = 1;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ sdbm_clearerr(dict_sdbm->dbm);
+ if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
+ if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ sdbm_clearerr(dict_sdbm->dbm);
+ if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
+ if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_sdbm_sequence(DICT *dict, const int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_sdbm_sequence";
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * Determine and execute the seek function. It returns the key.
+ */
+ sdbm_clearerr(dict_sdbm->dbm);
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ dbm_key = sdbm_firstkey(dict_sdbm->dbm);
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ dbm_key = sdbm_nextkey(dict_sdbm->dbm);
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
+
+ /*
+ * Copy the key so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize);
+
+ /*
+ * Fetch the corresponding value.
+ */
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+
+ if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
+
+ /*
+ * Copy the value so that it is guaranteed null terminated.
+ */
+ *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ status = 0;
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error
+ * condition.
+ */
+ if (sdbm_error(dict_sdbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
+ status = 1; /* no error: eof/not found
+ * (should not happen!) */
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error condition.
+ */
+ if (sdbm_error(dict_sdbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
+ status = 1; /* no error: eof/not found */
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* dict_sdbm_close - disassociate from data base */
+
+static void dict_sdbm_close(DICT *dict)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+
+ sdbm_close(dict_sdbm->dbm);
+ if (dict_sdbm->key_buf)
+ vstring_free(dict_sdbm->key_buf);
+ if (dict_sdbm->val_buf)
+ vstring_free(dict_sdbm->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sdbm_open - open SDBM data base */
+
+DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_SDBM *dict_sdbm;
+ struct stat st;
+ SDBM *dbm;
+ char *dbm_path;
+ int lock_fd;
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ */
+ if (dict_flags & DICT_FLAG_LOCK) {
+ dbm_path = concatenate(path, ".dir", (char *) 0);
+ if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
+ msg_fatal("open database %s: %m", dbm_path);
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", dbm_path);
+ }
+
+ /*
+ * XXX sdbm_open() has no const in prototype.
+ */
+ if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0)
+ msg_fatal("open database %s.{dir,pag}: %m", path);
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", dbm_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", dbm_path);
+ }
+ dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm));
+ dict_sdbm->dict.lookup = dict_sdbm_lookup;
+ dict_sdbm->dict.update = dict_sdbm_update;
+ dict_sdbm->dict.delete = dict_sdbm_delete;
+ dict_sdbm->dict.sequence = dict_sdbm_sequence;
+ dict_sdbm->dict.close = dict_sdbm_close;
+ dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm);
+ dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
+ if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_sdbm_open: fstat: %m");
+ dict_sdbm->dict.mtime = st.st_mtime;
+ dict_sdbm->dict.owner.uid = st.st_uid;
+ dict_sdbm->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_sdbm->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", dbm_path, path);
+
+ close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
+ close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
+ dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_sdbm->dict.fold_buf = vstring_alloc(10);
+ dict_sdbm->dbm = dbm;
+ dict_sdbm->key_buf = 0;
+ dict_sdbm->val_buf = 0;
+
+ if ((dict_flags & DICT_FLAG_LOCK))
+ myfree(dbm_path);
+
+ return (DICT_DEBUG (&dict_sdbm->dict));
+}
+
+#endif
diff --git a/src/util/dict_sdbm.h b/src/util/dict_sdbm.h
new file mode 100644
index 0000000..6a1b281
--- /dev/null
+++ b/src/util/dict_sdbm.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_SDBM_H_INCLUDED_
+#define _DICT_SDBM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_dbm 3h
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_SDBM "sdbm"
+
+extern DICT *dict_sdbm_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_seq.in b/src/util/dict_seq.in
new file mode 100644
index 0000000..998e7c4
--- /dev/null
+++ b/src/util/dict_seq.in
@@ -0,0 +1,11 @@
+put 5 5
+put 3 3
+put 4 4
+put 1 1
+put 2 2
+first
+next
+next
+next
+next
+next
diff --git a/src/util/dict_seq.ref b/src/util/dict_seq.ref
new file mode 100644
index 0000000..ce7d744
--- /dev/null
+++ b/src/util/dict_seq.ref
@@ -0,0 +1,22 @@
+> put 5 5
+5=5
+> put 3 3
+3=3
+> put 4 4
+4=4
+> put 1 1
+1=1
+> put 2 2
+2=2
+> first
+4=4
+> next
+2=2
+> next
+5=5
+> next
+3=3
+> next
+1=1
+> next
+not found
diff --git a/src/util/dict_sockmap.c b/src/util/dict_sockmap.c
new file mode 100644
index 0000000..becad1a
--- /dev/null
+++ b/src/util/dict_sockmap.c
@@ -0,0 +1,384 @@
+/*++
+/* NAME
+/* dict_sockmap 3
+/* SUMMARY
+/* dictionary manager interface to Sendmail-style socketmap server
+/* SYNOPSIS
+/* #include <dict_sockmap.h>
+/*
+/* DICT *dict_sockmap_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sockmap_open() makes a Sendmail-style socketmap server
+/* accessible via the generic dictionary operations described
+/* in dict_open(3). The only implemented operation is dictionary
+/* lookup. This map type can be useful for simulating a dynamic
+/* lookup table.
+/*
+/* Postfix socketmap names have the form inet:host:port:socketmap-name
+/* or unix:pathname:socketmap-name, where socketmap-name
+/* specifies the socketmap name that the socketmap server uses.
+/*
+/* To test this module, build the netstring and dict_open test
+/* programs. Run "./netstring nc -l portnumber" as the server,
+/* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname"
+/* as the client.
+/* PROTOCOL
+/* .ad
+/* .fi
+/* The socketmap class implements a simple protocol: the client
+/* sends one request, and the server sends one reply.
+/* ENCODING
+/* .ad
+/* .fi
+/* Each request and reply are sent as one netstring object.
+/* REQUEST FORMAT
+/* .ad
+/* .fi
+/* .IP "<mapname> <space> <key>"
+/* Search the specified socketmap under the specified key.
+/* REPLY FORMAT
+/* .ad
+/* .fi
+/* Replies must be no longer than 100000 characters (not including
+/* the netstring encapsulation), and must have the following
+/* form:
+/* .IP "OK <space> <data>"
+/* The requested data was found.
+/* .IP "NOTFOUND <space>"
+/* The requested data was not found.
+/* .IP "TEMP <space> <reason>"
+/* .IP "TIMEOUT <space> <reason>"
+/* .IP "PERM <space> <reason>"
+/* The request failed. The reason, if non-empty, is descriptive
+/* text.
+/* SECURITY
+/* This map cannot be used for security-sensitive information,
+/* because neither the connection nor the server are authenticated.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* netstring(3) netstring stream I/O support
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update or iterate over map.
+/* BUGS
+/* The protocol limits are not yet configurable.
+/* 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 <string.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <auto_clnt.h>
+#include <netstring.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <htable.h>
+#include <dict_sockmap.h>
+
+ /*
+ * Socket map data structure.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ char *sockmap_name; /* on-the-wire socketmap name */
+ VSTRING *rdwr_buf; /* read/write buffer */
+ HTABLE_INFO *client_info; /* shared endpoint name and handle */
+} DICT_SOCKMAP;
+
+ /*
+ * Default limits.
+ */
+#define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */
+#define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */
+#define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */
+#define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */
+
+ /*
+ * Class variables.
+ */
+static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT;
+static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY;
+static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE;
+static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL;
+
+ /*
+ * The client handle is shared between socketmap instances that have the
+ * same inet:host:port or unix:pathame information. This could be factored
+ * out as a general module for reference-counted handles of any kind.
+ */
+static HTABLE *dict_sockmap_handles; /* shared handles */
+
+typedef struct {
+ AUTO_CLNT *client_handle; /* the client handle */
+ int refcount; /* the reference count */
+} DICT_SOCKMAP_REFC_HANDLE;
+
+#define DICT_SOCKMAP_RH_NAME(ht) (ht)->key
+#define DICT_SOCKMAP_RH_HANDLE(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle
+#define DICT_SOCKMAP_RH_REFCOUNT(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount
+
+ /*
+ * Socketmap protocol elements.
+ */
+#define DICT_SOCKMAP_PROT_OK "OK"
+#define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND"
+#define DICT_SOCKMAP_PROT_TEMP "TEMP"
+#define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT"
+#define DICT_SOCKMAP_PROT_PERM "PERM"
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_sockmap_lookup - socket map lookup */
+
+static const char *dict_sockmap_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_sockmap_lookup";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+ AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info);
+ VSTREAM *fp;
+ int netstring_err;
+ char *reply_payload;
+ int except_count;
+ const char *error_class;
+
+ if (msg_verbose)
+ msg_info("%s: key %s", myname, key);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(100);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(STR(dict->fold_buf));
+ }
+
+ /*
+ * We retry connection-level errors once, to make server restarts
+ * transparent.
+ */
+ for (except_count = 0; /* see below */ ; except_count++) {
+
+ /*
+ * Look up the stream.
+ */
+ if ((fp = auto_clnt_access(sockmap_clnt)) == 0) {
+ msg_warn("table %s:%s lookup error: %m", dict->type, dict->name);
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ /*
+ * Set up an exception handler.
+ */
+ netstring_setup(fp, dict_sockmap_timeout);
+ if ((netstring_err = vstream_setjmp(fp)) == 0) {
+
+ /*
+ * Send the query. This may raise an exception.
+ */
+ vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key);
+ NETSTRING_PUT_BUF(fp, dp->rdwr_buf);
+
+ /*
+ * Receive the response. This may raise an exception.
+ */
+ netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply);
+
+ /*
+ * If we got here, then no exception was raised.
+ */
+ break;
+ }
+
+ /*
+ * Handle exceptions.
+ */
+ else {
+
+ /*
+ * We retry a broken connection only once.
+ */
+ if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF
+ && errno != ETIMEDOUT) {
+ auto_clnt_recover(sockmap_clnt);
+ continue;
+ }
+
+ /*
+ * We do not retry other errors.
+ */
+ else {
+ msg_warn("table %s:%s lookup error: %s",
+ dict->type, dict->name,
+ netstring_strerror(netstring_err));
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+ }
+ }
+
+ /*
+ * Parse the reply.
+ */
+ VSTRING_TERMINATE(dp->rdwr_buf);
+ reply_payload = split_at(STR(dp->rdwr_buf), ' ');
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
+ dict->error = 0;
+ return (reply_payload);
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
+ dict->error = 0;
+ return (0);
+ }
+ /* We got no definitive reply. */
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) {
+ error_class = "temporary";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) {
+ error_class = "timeout";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) {
+ error_class = "permanent";
+ dict->error = DICT_ERR_CONFIG;
+ } else {
+ error_class = "unknown";
+ dict->error = DICT_ERR_RETRY;
+ }
+ while (reply_payload && ISSPACE(*reply_payload))
+ reply_payload++;
+ msg_warn("%s:%s socketmap server %s error%s%.200s",
+ dict->type, dict->name, error_class,
+ reply_payload && *reply_payload ? ": " : "",
+ reply_payload && *reply_payload ?
+ printable(reply_payload, '?') : "");
+ return (0);
+}
+
+/* dict_sockmap_close - close socket map */
+
+static void dict_sockmap_close(DICT *dict)
+{
+ const char *myname = "dict_sockmap_close";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+
+ if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0)
+ msg_panic("%s: attempt to close a non-existent map", myname);
+ vstring_free(dp->rdwr_buf);
+ myfree(dp->sockmap_name);
+ if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) {
+ auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info));
+ htable_delete(dict_sockmap_handles,
+ DICT_SOCKMAP_RH_NAME(dp->client_info), myfree);
+ }
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sockmap_open - open socket map */
+
+DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags)
+{
+ DICT_SOCKMAP *dp;
+ char *saved_name = 0;
+ char *sockmap;
+ DICT_SOCKMAP_REFC_HANDLE *ref_handle;
+ HTABLE_INFO *client_info;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_SOCKMAP_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_SOCKMAP, mapname));
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map is not allowed for security-sensitive data",
+ DICT_TYPE_SOCKMAP, mapname));
+
+ /*
+ * Separate the socketmap name from the socketmap server name.
+ */
+ saved_name = mystrdup(mapname);
+ if ((sockmap = split_at_right(saved_name, ':')) == 0)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s requires server:socketmap argument",
+ DICT_TYPE_SOCKMAP));
+
+ /*
+ * Use one reference-counted client handle for all socketmaps with the
+ * same inet:host:port or unix:pathname information.
+ *
+ * XXX Todo: graceful degradation after endpoint syntax error.
+ */
+ if (dict_sockmap_handles == 0)
+ dict_sockmap_handles = htable_create(1);
+ if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) {
+ ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle));
+ client_info = htable_enter(dict_sockmap_handles,
+ saved_name, (void *) ref_handle);
+ /* XXX Late initialization, so we can reuse macros for consistency. */
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1;
+ DICT_SOCKMAP_RH_HANDLE(client_info) =
+ auto_clnt_create(saved_name, dict_sockmap_timeout,
+ dict_sockmap_max_idle, dict_sockmap_max_ttl);
+ } else
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1;
+
+ /*
+ * Instantiate a socket map handle.
+ */
+ dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp));
+ dp->rdwr_buf = vstring_alloc(100);
+ dp->sockmap_name = mystrdup(sockmap);
+ dp->client_info = client_info;
+ dp->dict.lookup = dict_sockmap_lookup;
+ dp->dict.close = dict_sockmap_close;
+ /* Don't look up parent domains or network superblocks. */
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+
+ DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_sockmap.h b/src/util/dict_sockmap.h
new file mode 100644
index 0000000..b81212a
--- /dev/null
+++ b/src/util/dict_sockmap.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_SOCKMAP_H_INCLUDED_
+#define _DICT_SOCKMAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_sockmap 3h
+/* SUMMARY
+/* dictionary manager interface to Sendmail-stye socketmap.
+/* SYNOPSIS
+/* #include <dict_sockmap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_SOCKMAP "socketmap"
+
+extern DICT *dict_sockmap_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_static.c b/src/util/dict_static.c
new file mode 100644
index 0000000..448dde0
--- /dev/null
+++ b/src/util/dict_static.c
@@ -0,0 +1,151 @@
+/*++
+/* NAME
+/* dict_static 3
+/* SUMMARY
+/* dictionary manager interface to static variables
+/* SYNOPSIS
+/* #include <dict_static.h>
+/*
+/* DICT *dict_static_open(name, name, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_static_open() implements a dummy dictionary that returns
+/* as lookup result the dictionary name, regardless of the lookup
+/* key value.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* jeffm
+/* ghostgun.com
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_static.h"
+
+ /*
+ * Subclass of DICT.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ char *value;
+} DICT_STATIC;
+
+#define STR(x) vstring_str(x)
+
+/* dict_static_lookup - access static value*/
+
+static const char *dict_static_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_STATIC *dict_static = (DICT_STATIC *) dict;
+
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict_static->value);
+}
+
+/* dict_static_close - close static dictionary */
+
+static void dict_static_close(DICT *dict)
+{
+ DICT_STATIC *dict_static = (DICT_STATIC *) dict;
+
+ if (dict_static->value)
+ myfree(dict_static->value);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_static_open - make association with static variable */
+
+DICT *dict_static_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_STATIC *dict_static;
+ char *err = 0;
+ char *cp, *saved_name = 0;
+ const char *value;
+ VSTRING *base64_buf;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_STATIC_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (err != 0) \
+ myfree(err); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Optionally strip surrounding braces and whitespace.
+ */
+ if (name[0] == CHARS_BRACE[0]) {
+ saved_name = cp = mystrdup(name);
+ if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0)
+ DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name,
+ open_flags, dict_flags,
+ "bad %s:name syntax: %s",
+ DICT_TYPE_STATIC,
+ err));
+ value = cp;
+ } else {
+ value = name;
+ }
+
+ /*
+ * Bundle up the preliminary result.
+ */
+ dict_static = (DICT_STATIC *) dict_alloc(DICT_TYPE_STATIC, name,
+ sizeof(*dict_static));
+ dict_static->dict.lookup = dict_static_lookup;
+ dict_static->dict.close = dict_static_close;
+ dict_static->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ dict_static->dict.owner.status = DICT_OWNER_TRUSTED;
+ dict_static->value = 0;
+
+ /*
+ * Optionally replace the value with file contents.
+ */
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ if ((base64_buf = dict_file_to_b64(&dict_static->dict, value)) == 0) {
+ err = dict_file_get_error(&dict_static->dict);
+ dict_close(&dict_static->dict);
+ DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name,
+ open_flags, dict_flags,
+ "%s", err));
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Finalize the result.
+ */
+ dict_static->value = mystrdup(value);
+ dict_file_purge_buffers(&dict_static->dict);
+
+ DICT_STATIC_OPEN_RETURN(DICT_DEBUG (&(dict_static->dict)));
+}
diff --git a/src/util/dict_static.h b/src/util/dict_static.h
new file mode 100644
index 0000000..d4ad1cc
--- /dev/null
+++ b/src/util/dict_static.h
@@ -0,0 +1,35 @@
+#ifndef _DICT_STATIC_H_INCLUDED_
+#define _DICT_STATIC_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_static 3h
+/* SUMMARY
+/* dictionary manager interface to static settings
+/* SYNOPSIS
+/* #include <dict_static.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_STATIC "static"
+
+extern DICT *dict_static_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* jeffm
+/* ghostgun.com
+/*--*/
+
+#endif
diff --git a/src/util/dict_static.ref b/src/util/dict_static.ref
new file mode 100644
index 0000000..550264a
--- /dev/null
+++ b/src/util/dict_static.ref
@@ -0,0 +1,14 @@
+owner=trusted (uid=2147483647)
+> get foo
+foo=fooxx
+> get bar
+bar=fooxx
+./dict_open: error: bad static:name syntax: missing '}' in "{ foo xx "
+owner=trusted (uid=2147483647)
+./dict_open: error: bad static:name syntax: syntax error after '}' in "{ foo xx }x"
+owner=trusted (uid=2147483647)
+owner=trusted (uid=2147483647)
+> get foo
+foo=foo xx
+> get bar
+bar=foo xx
diff --git a/src/util/dict_static_file.ref b/src/util/dict_static_file.ref
new file mode 100644
index 0000000..259f9fb
--- /dev/null
+++ b/src/util/dict_static_file.ref
@@ -0,0 +1,10 @@
+./dict_open: error: open fooxx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: static:fooxx is unavailable. open fooxx: No such file or directory
+foo: error
+owner=trusted (uid=2147483647)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMQo=
diff --git a/src/util/dict_stream.c b/src/util/dict_stream.c
new file mode 100644
index 0000000..e28ad71
--- /dev/null
+++ b/src/util/dict_stream.c
@@ -0,0 +1,274 @@
+/*++
+/* NAME
+/* dict_stream 3
+/* SUMMARY
+/*
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* VSTREAM *dict_stream_open(
+/* const char *dict_type,
+/* const char *mapname,
+/* int open_flags,
+/* int dict_flags,
+/* struct stat * st,
+/* VSTRING **why)
+/* DESCRIPTION
+/* dict_stream_open() opens a dictionary, which can be specified
+/* as a file name, or as inline text enclosed with {}. If successful,
+/* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise,
+/* it returns an error text through the why argument, allocating
+/* storage for the error text if the why argument points to a
+/* null pointer.
+/*
+/* When the dictionary file is specified inline, dict_stream_open()
+/* removes the outer {} from the mapname value, and removes leading
+/* or trailing comma or whitespace from the result. It then expects
+/* to find zero or more rules enclosed in {}, separated by comma
+/* and/or whitespace. dict_stream() writes each rule as one text
+/* line to an in-memory stream, without its enclosing {} and without
+/* leading or trailing whitespace. The result value is a VSTREAM
+/* pointer for the in-memory stream that can be read as a regular
+/* file.
+/* .sp
+/* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}"
+/* .sp
+/* rule-spec = "{" 0*wsp rule-text 0*wsp "}"
+/* .sp
+/* rule-text = any text containing zero or more balanced {}
+/* .sp
+/* wsp-comma = wsp | ","
+/* .sp
+/* wsp = whitespace
+/*
+/* Arguments:
+/* .IP dict_type
+/* .IP open_flags
+/* .IP dict_flags
+/* The same as with dict_open(3).
+/* .IP mapname
+/* Pathname of a file with dictionary content, or inline dictionary
+/* content as specified above.
+/* .IP st
+/* File metadata with the file owner, or fake metadata with the
+/* real UID and GID of the dict_stream_open() caller. This is
+/* used for "taint" tracking (zero=trusted, non-zero=untrusted).
+/* IP why
+/* Pointer to pointer to error message storage. dict_stream_open()
+/* updates this storage when reporting an error, and allocates
+/* memory if why points to a null pointer.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_inline_to_multiline - convert inline map spec to multiline text */
+
+static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname)
+{
+ char *saved_name = mystrdup(mapname);
+ char *bp = saved_name;
+ char *cp;
+ char *err = 0;
+
+ VSTRING_RESET(vp);
+ /* Strip the {} from the map "name". */
+ err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE);
+ /* Extract zero or more rules inside {}. */
+ while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0)
+ if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0)
+ /* Write rule to in-memory file. */
+ vstring_sprintf_append(vp, "%s\n", cp);
+ VSTRING_TERMINATE(vp);
+ myfree(saved_name);
+ return (err);
+}
+
+/* dict_stream_open - open inline configuration or configuration file */
+
+VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+ int open_flags, int dict_flags,
+ struct stat * st, VSTRING **why)
+{
+ VSTRING *inline_buf = 0;
+ VSTREAM *map_fp;
+ char *err = 0;
+
+#define RETURN_0_WITH_REASON(...) do { \
+ if (*why == 0) \
+ *why = vstring_alloc(100); \
+ vstring_sprintf(*why, __VA_ARGS__); \
+ if (inline_buf != 0) \
+ vstring_free(inline_buf); \
+ if (err != 0) \
+ myfree(err); \
+ return (0); \
+ } while (0)
+
+ if (mapname[0] == CHARS_BRACE[0]) {
+ inline_buf = vstring_alloc(100);
+ if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0)
+ RETURN_0_WITH_REASON("%s map: %s", dict_type, err);
+ map_fp = vstream_memopen(inline_buf, O_RDONLY);
+ vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END);
+ st->st_uid = getuid(); /* geteuid()? */
+ st->st_gid = getgid(); /* getegid()? */
+ return (map_fp);
+ } else {
+ if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0)
+ RETURN_0_WITH_REASON("open %s: %m", mapname);
+ if (fstat(vstream_fileno(map_fp), st) < 0)
+ msg_fatal("fstat %s: %m", mapname);
+ return (map_fp);
+ }
+}
+
+#ifdef TEST
+
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ struct testcase {
+ const char *title;
+ const char *mapname; /* starts with brace */
+ const char *expect_err; /* null or message */
+ const char *expect_cont; /* null or content */
+ };
+
+#define EXP_NOERR 0
+#define EXP_NOCONT 0
+
+#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null))
+#define DICT_TYPE_TEST "test"
+
+ const char rule_spec_error[] = DICT_TYPE_TEST " map: "
+ "syntax error after '}' in \"{blah blah}x\"";
+ const char inline_config_error[] = DICT_TYPE_TEST " map: "
+ "syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
+ struct testcase testcases[] = {
+ {"normal",
+ "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"trims leading/trailing wsp around rule-text",
+ "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"trims leading/trailing comma-wsp around rule-spec",
+ "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"empty inline-file",
+ "{, }", EXP_NOERR, ""
+ },
+ {"propagates extpar error for inline-file",
+ "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT
+ },
+ {"propagates extpar error for rule-spec",
+ "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT
+ },
+ 0,
+ };
+ struct testcase *tp;
+ VSTRING *act_err = 0;
+ VSTRING *act_cont = vstring_alloc(100);
+ VSTREAM *fp;
+ struct stat st;
+ ssize_t exp_len;
+ ssize_t act_len;
+ int pass;
+ int fail;
+
+ for (pass = fail = 0, tp = testcases; tp->title; tp++) {
+ int test_passed = 0;
+
+ msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
+
+#if 0
+ msg_info("title=%s", tp->title);
+ msg_info("mapname=%s", tp->mapname);
+ msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err));
+ msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont));
+#endif
+
+ if (act_err)
+ VSTRING_RESET(act_err);
+ fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
+ 0, &st, &act_err);
+ if (fp) {
+ if (tp->expect_err) {
+ msg_warn("test case %s: got stream, expected error", tp->title);
+ } else if (!tp->expect_err && act_err && LEN(act_err) > 0) {
+ msg_warn("test case %s: got error '%s', expected noerror",
+ tp->title, STR(act_err));
+ } else if (!tp->expect_cont) {
+ msg_warn("test case %s: got stream, expected nostream",
+ tp->title);
+ } else {
+ exp_len = strlen(tp->expect_cont);
+ if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) {
+ msg_warn("test case %s: content read error", tp->title);
+ } else {
+ VSTRING_TERMINATE(act_cont);
+ if (strcmp(tp->expect_cont, STR(act_cont)) != 0) {
+ msg_warn("test case %s: got content '%s', expected '%s'",
+ tp->title, STR(act_cont), tp->expect_cont);
+ } else {
+ test_passed = 1;
+ }
+ }
+ }
+ } else {
+ if (!tp->expect_err) {
+ msg_warn("test case %s: got nostream, expected noerror",
+ tp->title);
+ } else if (tp->expect_cont) {
+ msg_warn("test case %s: got nostream, expected stream",
+ tp->title);
+ } else if (strcmp(STR(act_err), tp->expect_err) != 0) {
+ msg_warn("test case %s: got error '%s', expected '%s'",
+ tp->title, STR(act_err), tp->expect_err);
+ } else {
+ test_passed = 1;
+ }
+
+ }
+ if (test_passed) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ } else {
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ }
+ if (fp)
+ vstream_fclose(fp);
+ }
+ if (act_err)
+ vstring_free(act_err);
+ vstring_free(act_cont);
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
+}
+
+#endif /* TEST */
diff --git a/src/util/dict_stream.ref b/src/util/dict_stream.ref
new file mode 100644
index 0000000..87c30e5
--- /dev/null
+++ b/src/util/dict_stream.ref
@@ -0,0 +1,13 @@
+unknown: RUN test case 0 normal
+unknown: PASS test 0
+unknown: RUN test case 1 trims leading/trailing wsp around rule-text
+unknown: PASS test 1
+unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec
+unknown: PASS test 2
+unknown: RUN test case 3 empty inline-file
+unknown: PASS test 3
+unknown: RUN test case 4 propagates extpar error for inline-file
+unknown: PASS test 4
+unknown: RUN test case 5 propagates extpar error for rule-spec
+unknown: PASS test 5
+unknown: PASS=6 FAIL=0
diff --git a/src/util/dict_surrogate.c b/src/util/dict_surrogate.c
new file mode 100644
index 0000000..a23cba3
--- /dev/null
+++ b/src/util/dict_surrogate.c
@@ -0,0 +1,180 @@
+/*++
+/* NAME
+/* dict_surrogate 3
+/* SUMMARY
+/* surrogate table for graceful "open" failure
+/* SYNOPSIS
+/* #include <dict_surrogate.h>
+/*
+/* DICT *dict_surrogate(dict_type, dict_name,
+/* open_flags, dict_flags,
+/* format, ...)
+/* const char *dict_type;
+/* const char *dict_name;
+/* int open_flags;
+/* int dict_flags;
+/* const char *format;
+/*
+/* int dict_allow_surrogate;
+/* DESCRIPTION
+/* dict_surrogate() either terminates the program with a fatal
+/* error, or provides a dummy dictionary that fails all
+/* operations with an error message, allowing the program to
+/* continue with reduced functionality.
+/*
+/* The global dict_allow_surrogate variable controls the choice
+/* between fatal error or reduced functionality. The default
+/* value is zero (fatal error). This is appropriate for user
+/* commands; the non-default is more appropriate for daemons.
+/*
+/* Arguments:
+/* .IP dict_type
+/* .IP dict_name
+/* .IP open_flags
+/* .IP dict_flags
+/* The parameters to the failed dictionary open() request.
+/* .IP format, ...
+/* The reason why the table could not be opened. This text is
+/* logged immediately as an "error" class message, and is logged
+/* as a "warning" class message upon every attempt to access the
+/* surrogate dictionary, before returning a "failed" completion
+/* status.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 <mymalloc.h>
+#include <msg.h>
+#include <compat_va_copy.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *reason; /* open failure reason */
+} DICT_SURROGATE;
+
+/* dict_surrogate_sequence - fail lookup */
+
+static int dict_surrogate_sequence(DICT *dict, int unused_func,
+ const char **key, const char **value)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_update - fail lookup */
+
+static int dict_surrogate_update(DICT *dict, const char *unused_name,
+ const char *unused_value)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_lookup - fail lookup */
+
+static const char *dict_surrogate_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0);
+}
+
+/* dict_surrogate_delete - fail delete */
+
+static int dict_surrogate_delete(DICT *dict, const char *unused_name)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_close - close fail dictionary */
+
+static void dict_surrogate_close(DICT *dict)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ myfree((void *) dp->reason);
+ dict_free(dict);
+}
+
+int dict_allow_surrogate = 0;
+
+/* dict_surrogate - terminate or provide surrogate dictionary */
+
+DICT *dict_surrogate(const char *dict_type, const char *dict_name,
+ int open_flags, int dict_flags,
+ const char *fmt,...)
+{
+ va_list ap;
+ va_list ap2;
+ DICT_SURROGATE *dp;
+ VSTRING *buf;
+ void (*log_fn) (const char *, va_list);
+ int saved_errno = errno;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, fmt);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Log the problem immediately when it is detected. The table may not be
+ * accessed in every program execution (that is the whole point of
+ * continuing with reduced functionality) but we don't want the problem
+ * to remain unnoticed until long after a configuration mistake is made.
+ */
+ log_fn = dict_allow_surrogate ? vmsg_error : vmsg_fatal;
+ log_fn(fmt, ap);
+ va_end(ap);
+
+ /*
+ * Log the problem upon each access.
+ */
+ dp = (DICT_SURROGATE *) dict_alloc(dict_type, dict_name, sizeof(*dp));
+ dp->dict.lookup = dict_surrogate_lookup;
+ if (open_flags & O_RDWR) {
+ dp->dict.update = dict_surrogate_update;
+ dp->dict.delete = dict_surrogate_delete;
+ }
+ dp->dict.sequence = dict_surrogate_sequence;
+ dp->dict.close = dict_surrogate_close;
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dp->dict.owner.status = DICT_OWNER_TRUSTED;
+ buf = vstring_alloc(10);
+ errno = saved_errno;
+ vstring_vsprintf(buf, fmt, ap2);
+ va_end(ap2);
+ dp->reason = vstring_export(buf);
+ return (DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_tcp.c b/src/util/dict_tcp.c
new file mode 100644
index 0000000..922f449
--- /dev/null
+++ b/src/util/dict_tcp.c
@@ -0,0 +1,315 @@
+/*++
+/* NAME
+/* dict_tcp 3
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/*
+/* DICT *dict_tcp_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_tcp_open() makes a TCP server accessible via the generic
+/* dictionary operations described in dict_open(3).
+/* The only implemented operation is dictionary lookup. This map
+/* type can be useful for simulating a dynamic lookup table.
+/*
+/* Map names have the form host:port.
+/*
+/* The TCP map class implements a very simple protocol: the client
+/* sends a request, and the server sends one reply. Requests and
+/* replies are sent as one line of ASCII text, terminated by the
+/* ASCII newline character. Request and reply parameters (see below)
+/* are separated by whitespace.
+/* ENCODING
+/* .ad
+/* .fi
+/* In request and reply parameters, the character % and any non-printing
+/* and whitespace characters must be replaced by %XX, XX being the
+/* corresponding ASCII hexadecimal character value. The hexadecimal codes
+/* can be specified in any case (upper, lower, mixed).
+/* REQUEST FORMAT
+/* .ad
+/* .fi
+/* Requests are strings that serve as lookup key in the simulated
+/* table.
+/* .IP "get SPACE key NEWLINE"
+/* Look up data under the specified key.
+/* .IP "put SPACE key SPACE value NEWLINE"
+/* This request is currently not implemented.
+/* REPLY FORMAT
+/* .ad
+/* .fi
+/* Replies must be no longer than 4096 characters including the
+/* newline terminator, and must have the following form:
+/* .IP "500 SPACE text NEWLINE"
+/* In case of a lookup request, the requested data does not exist.
+/* In case of an update request, the request was rejected.
+/* The text gives the nature of the problem.
+/* .IP "400 SPACE text NEWLINE"
+/* This indicates an error condition. The text gives the nature of
+/* the problem. The client should retry the request later.
+/* .IP "200 SPACE text NEWLINE"
+/* The request was successful. In the case of a lookup request,
+/* the text contains an encoded version of the requested data.
+/* SECURITY
+/* This map must not be used for security sensitive information,
+/* because neither the connection nor the server are authenticated.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* hex_quote(3) http-style quoting
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update or iterate over map.
+/* BUGS
+/* Only the lookup method is currently implemented.
+/* 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 <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <connect.h>
+#include <hex_quote.h>
+#include <dict.h>
+#include <stringops.h>
+#include <dict_tcp.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ VSTRING *raw_buf; /* raw I/O buffer */
+ VSTRING *hex_buf; /* quoted I/O buffer */
+ VSTREAM *fp; /* I/O stream */
+} DICT_TCP;
+
+#define DICT_TCP_MAXTRY 10 /* attempts before giving up */
+#define DICT_TCP_TMOUT 100 /* connect/read/write timeout */
+#define DICT_TCP_MAXLEN 4096 /* server reply size limit */
+
+#define STR(x) vstring_str(x)
+
+/* dict_tcp_connect - connect to TCP server */
+
+static int dict_tcp_connect(DICT_TCP *dict_tcp)
+{
+ int fd;
+
+ /*
+ * Connect to the server. Enforce a time limit on all operations so that
+ * we do not get stuck.
+ */
+ if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) {
+ msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name);
+ return (-1);
+ }
+ dict_tcp->fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(dict_tcp->fp,
+ CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT),
+ CA_VSTREAM_CTL_END);
+
+ /*
+ * Allocate per-map I/O buffers on the fly.
+ */
+ if (dict_tcp->raw_buf == 0) {
+ dict_tcp->raw_buf = vstring_alloc(10);
+ dict_tcp->hex_buf = vstring_alloc(10);
+ }
+ return (0);
+}
+
+/* dict_tcp_disconnect - disconnect from TCP server */
+
+static void dict_tcp_disconnect(DICT_TCP *dict_tcp)
+{
+ (void) vstream_fclose(dict_tcp->fp);
+ dict_tcp->fp = 0;
+}
+
+/* dict_tcp_lookup - request TCP server */
+
+static const char *dict_tcp_lookup(DICT *dict, const char *key)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+ const char *myname = "dict_tcp_lookup";
+ int tries;
+ char *start;
+ int last_ch;
+
+#define RETURN(errval, result) { dict->error = errval; return (result); }
+
+ if (msg_verbose)
+ msg_info("%s: key %s", myname, key);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (tries = 0; /* see below */ ; /* see below */ ) {
+
+ /*
+ * Connect to the server, or use an existing connection.
+ */
+ if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) {
+
+ /*
+ * Send request and receive response. Both are %XX quoted and
+ * both are terminated by newline. This encoding is convenient
+ * for data that is mostly text.
+ */
+ hex_quote(dict_tcp->hex_buf, key);
+ vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf));
+ if (msg_verbose)
+ msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf));
+ last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp,
+ DICT_TCP_MAXLEN);
+ if (last_ch == '\n')
+ break;
+
+ /*
+ * Disconnect from the server if it can't talk to us.
+ */
+ if (last_ch < 0)
+ msg_warn("read TCP map reply from %s: unexpected EOF (%m)",
+ dict_tcp->dict.name);
+ else
+ msg_warn("read TCP map reply from %s: text longer than %d",
+ dict_tcp->dict.name, DICT_TCP_MAXLEN);
+ dict_tcp_disconnect(dict_tcp);
+ }
+
+ /*
+ * Try to connect a limited number of times before giving up.
+ */
+ if (++tries >= DICT_TCP_MAXTRY)
+ RETURN(DICT_ERR_RETRY, 0);
+
+ /*
+ * Sleep between attempts, instead of hammering the server.
+ */
+ sleep(1);
+ }
+ if (msg_verbose)
+ msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf));
+
+ /*
+ * Check the general reply syntax. If the reply is malformed, disconnect
+ * and try again later.
+ */
+ if (start = STR(dict_tcp->hex_buf),
+ !ISDIGIT(start[0]) || !ISDIGIT(start[1])
+ || !ISDIGIT(start[2]) || !ISSPACE(start[3])
+ || !hex_unquote(dict_tcp->raw_buf, start + 4)) {
+ msg_warn("read TCP map reply from %s: malformed reply: %.100s",
+ dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ }
+
+ /*
+ * Examine the reply status code. If the reply is malformed, disconnect
+ * and try again later.
+ */
+ switch (start[0]) {
+ default:
+ msg_warn("read TCP map reply from %s: bad status code: %.100s",
+ dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ case '4':
+ if (msg_verbose)
+ msg_info("%s: soft error: %s",
+ myname, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ case '5':
+ if (msg_verbose)
+ msg_info("%s: not found: %s",
+ myname, printable(STR(dict_tcp->hex_buf), '_'));
+ RETURN(DICT_ERR_NONE, 0);
+ case '2':
+ if (msg_verbose)
+ msg_info("%s: found: %s",
+ myname, printable(STR(dict_tcp->raw_buf), '_'));
+ RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf));
+ }
+}
+
+/* dict_tcp_close - close TCP map */
+
+static void dict_tcp_close(DICT *dict)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+
+ if (dict_tcp->fp)
+ (void) vstream_fclose(dict_tcp->fp);
+ if (dict_tcp->raw_buf)
+ vstring_free(dict_tcp->raw_buf);
+ if (dict_tcp->hex_buf)
+ vstring_free(dict_tcp->hex_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_tcp_open - open TCP map */
+
+DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_TCP *dict_tcp;
+
+ /*
+ * Sanity checks.
+ */
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
+ "%s:%s map is not allowed for security sensitive data",
+ DICT_TYPE_TCP, map));
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_TCP, map));
+
+ /*
+ * Create the dictionary handle. Do not open the connection until the
+ * first request is made.
+ */
+ dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp));
+ dict_tcp->fp = 0;
+ dict_tcp->raw_buf = dict_tcp->hex_buf = 0;
+ dict_tcp->dict.lookup = dict_tcp_lookup;
+ dict_tcp->dict.close = dict_tcp_close;
+ dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_tcp->dict.fold_buf = vstring_alloc(10);
+
+ return (DICT_DEBUG (&dict_tcp->dict));
+}
diff --git a/src/util/dict_tcp.h b/src/util/dict_tcp.h
new file mode 100644
index 0000000..9814c61
--- /dev/null
+++ b/src/util/dict_tcp.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_TCP_H_INCLUDED_
+#define _DICT_TCP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_tcp 3h
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_TCP "tcp"
+
+extern DICT *dict_tcp_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_test.c b/src/util/dict_test.c
new file mode 100644
index 0000000..ead61b2
--- /dev/null
+++ b/src/util/dict_test.c
@@ -0,0 +1,166 @@
+ /*
+ * Proof-of-concept test program. Create, update or read a database. Type
+ * '?' for a list of commands.
+ */
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <dict.h>
+#include <dict_lmdb.h>
+#include <dict_db.h>
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s type:file read|write|create [flags...]", myname);
+}
+
+void dict_test(int argc, char **argv)
+{
+ VSTRING *keybuf = vstring_alloc(1);
+ VSTRING *inbuf = vstring_alloc(1);
+ DICT *dict;
+ char *dict_name;
+ int open_flags;
+ char *bufp;
+ char *cmd;
+ const char *key;
+ const char *value;
+ int ch;
+ int dict_flags = 0;
+ int n;
+ int rc;
+
+#define USAGE "verbose|del key|get key|put key=value|first|next|masks|flags"
+
+ signal(SIGPIPE, SIG_IGN);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ optind = OPTIND;
+ if (argc - optind < 2)
+ usage(argv[0]);
+ if (strcasecmp(argv[optind + 1], "create") == 0)
+ open_flags = O_CREAT | O_RDWR | O_TRUNC;
+ else if (strcasecmp(argv[optind + 1], "write") == 0)
+ open_flags = O_RDWR;
+ else if (strcasecmp(argv[optind + 1], "read") == 0)
+ open_flags = O_RDONLY;
+ else
+ msg_fatal("unknown access mode: %s", argv[2]);
+ for (n = 2; argv[optind + n]; n++)
+ dict_flags |= dict_flags_mask(argv[optind + 2]);
+ if ((dict_flags & DICT_FLAG_OPEN_LOCK) == 0)
+ dict_flags |= DICT_FLAG_LOCK;
+ if ((dict_flags & (DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE)) == 0)
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ dict_flags |= DICT_FLAG_UTF8_REQUEST;
+ vstream_fflush(VSTREAM_OUT);
+ dict_name = argv[optind];
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ dict = dict_open(dict_name, open_flags, dict_flags);
+ dict_register(dict_name, dict);
+ vstream_printf("owner=%s (uid=%ld)\n",
+ dict->owner.status == DICT_OWNER_TRUSTED ? "trusted" :
+ dict->owner.status == DICT_OWNER_UNTRUSTED ? "untrusted" :
+ dict->owner.status == DICT_OWNER_UNKNOWN ? "unspecified" :
+ "error", (long) dict->owner.uid);
+ vstream_fflush(VSTREAM_OUT);
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ bufp = vstring_str(inbuf);
+ if (!isatty(0)) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((cmd = mystrtok(&bufp, " ")) == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ if (dict_changed_name())
+ msg_warn("dictionary has changed");
+ key = *bufp ? vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))) : 0;
+ value = mystrtok(&bufp, " =");
+ if (strcmp(cmd, "verbose") == 0 && !key) {
+ msg_verbose++;
+ } else if (strcmp(cmd, "del") == 0 && key && !value) {
+ if ((rc = dict_del(dict, key)) > 0)
+ vstream_printf("%s: not found\n", key);
+ else if (rc < 0)
+ vstream_printf("%s: error\n", key);
+ else
+ vstream_printf("%s: deleted\n", key);
+ } else if (strcmp(cmd, "get") == 0 && key && !value) {
+ if ((value = dict_get(dict, key)) == 0) {
+ vstream_printf("%s: %s\n", key, dict->error ?
+ "error" : "not found");
+ } else {
+ vstream_printf("%s=%s\n", key, value);
+ }
+ } else if (strcmp(cmd, "put") == 0 && key && value) {
+ if (dict_put(dict, key, value) != 0)
+ vstream_printf("%s: %s\n", key, dict->error ?
+ "error" : "not updated");
+ } else if (strcmp(cmd, "first") == 0 && !key && !value) {
+ if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0)
+ vstream_printf("%s=%s\n", key, value);
+ else
+ vstream_printf("%s\n", dict->error ?
+ "error" : "not found");
+ } else if (strcmp(cmd, "next") == 0 && !key && !value) {
+ if (dict_seq(dict, DICT_SEQ_FUN_NEXT, &key, &value) == 0)
+ vstream_printf("%s=%s\n", key, value);
+ else
+ vstream_printf("%s\n", dict->error ?
+ "error" : "not found");
+ } else if (strcmp(cmd, "flags") == 0 && !key && !value) {
+ vstream_printf("dict flags %s\n",
+ dict_flags_str(dict->flags));
+ } else if (strcmp(cmd, "masks") == 0 && !key && !value) {
+ vstream_printf("DICT_FLAG_IMPL_MASK %s\n",
+ dict_flags_str(DICT_FLAG_IMPL_MASK));
+ vstream_printf("DICT_FLAG_PARANOID %s\n",
+ dict_flags_str(DICT_FLAG_PARANOID));
+ vstream_printf("DICT_FLAG_RQST_MASK %s\n",
+ dict_flags_str(DICT_FLAG_RQST_MASK));
+ vstream_printf("DICT_FLAG_INST_MASK %s\n",
+ dict_flags_str(DICT_FLAG_INST_MASK));
+ } else {
+ vstream_printf("usage: %s\n", USAGE);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(keybuf);
+ vstring_free(inbuf);
+ dict_close(dict);
+}
diff --git a/src/util/dict_test.in b/src/util/dict_test.in
new file mode 100644
index 0000000..ef838a1
--- /dev/null
+++ b/src/util/dict_test.in
@@ -0,0 +1,10 @@
+del bar
+get bar
+get nonexist
+del nonexist
+get foo
+del foo
+put baz bazval
+get baz
+del baz
+get baz
diff --git a/src/util/dict_test.ref b/src/util/dict_test.ref
new file mode 100644
index 0000000..54e91f8
--- /dev/null
+++ b/src/util/dict_test.ref
@@ -0,0 +1,20 @@
+owner=untrusted (uid=USER)
+> del bar
+bar: deleted
+> get bar
+bar: not found
+> get nonexist
+nonexist: not found
+> del nonexist
+nonexist: not found
+> get foo
+foo=fooval
+> del foo
+foo: deleted
+> put baz bazval
+> get baz
+baz=bazval
+> del baz
+baz: deleted
+> get baz
+baz: not found
diff --git a/src/util/dict_thash.c b/src/util/dict_thash.c
new file mode 100644
index 0000000..69eb17b
--- /dev/null
+++ b/src/util/dict_thash.c
@@ -0,0 +1,255 @@
+/*++
+/* NAME
+/* dict_thash 3
+/* SUMMARY
+/* dictionary manager interface to hashed flat text files
+/* SYNOPSIS
+/* #include <dict_thash.h>
+/*
+/* DICT *dict_thash_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_thash_open() opens the named flat text file, creates
+/* an in-memory hash table, and makes it available via the
+/* generic interface described in dict_open(3). The input
+/* format is as with postmap(1).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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/stat.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <dict.h>
+#include <dict_ht.h>
+#include <dict_thash.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* dict_thash_open - open flat text data base */
+
+DICT *dict_thash_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT *dict;
+ VSTREAM *fp = 0; /* DICT_THASH_OPEN_RETURN() */
+ struct stat st;
+ time_t before;
+ time_t after;
+ VSTRING *line_buffer = 0; /* DICT_THASH_OPEN_RETURN() */
+ int lineno;
+ int last_line;
+ char *key;
+ char *value;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_THASH_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (fp != 0) \
+ vstream_fclose(fp); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_THASH, path));
+
+ /*
+ * Read the flat text file into in-memory hash. Read the file again if it
+ * may have changed while we were reading.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) {
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "open database %s: %m", path));
+ }
+
+ /*
+ * Reuse the "internal" dictionary type.
+ */
+ dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags);
+ dict_type_override(dict, DICT_TYPE_THASH);
+
+ /*
+ * XXX This duplicates the parser in postmap.c.
+ */
+ if (line_buffer == 0)
+ line_buffer = vstring_alloc(100);
+ last_line = 0;
+ while (readllines(line_buffer, fp, &last_line, &lineno)) {
+ int in_quotes = 0;
+
+ /*
+ * First some UTF-8 checks sans casefolding.
+ */
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE)
+ && allascii(STR(line_buffer)) == 0
+ && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) {
+ msg_warn("%s, line %d: non-UTF-8 input \"%s\""
+ " -- ignoring this line",
+ VSTREAM_PATH(fp), lineno, STR(line_buffer));
+ continue;
+ }
+
+ /*
+ * Split on the first whitespace character, then trim leading and
+ * trailing whitespace from key and value.
+ */
+ for (value = STR(line_buffer); *value; value++) {
+ if (*value == '\\') {
+ if (*++value == 0)
+ break;
+ } else if (ISSPACE(*value)) {
+ if (!in_quotes)
+ break;
+ } else if (*value == '"') {
+ in_quotes = !in_quotes;
+ }
+ }
+ if (in_quotes) {
+ msg_warn("%s, line %d: unbalanced '\"' in '%s'"
+ " -- ignoring this line",
+ VSTREAM_PATH(fp), lineno, STR(line_buffer));
+ continue;
+ }
+ if (*value)
+ *value++ = 0;
+ while (ISSPACE(*value))
+ value++;
+ trimblanks(value, 0)[0] = 0;
+
+ /*
+ * Leave the key in quoted form, for consistency with postmap.c
+ * and dict_inline.c.
+ */
+ key = STR(line_buffer);
+
+ /*
+ * Enforce the "key whitespace value" format. Disallow missing
+ * keys or missing values.
+ */
+ if (*key == 0 || *value == 0) {
+ msg_warn("%s, line %d: expected format: key whitespace value"
+ " -- ignoring this line", path, lineno);
+ continue;
+ }
+ if (key[strlen(key) - 1] == ':')
+ msg_warn("%s, line %d: record is in \"key: value\" format;"
+ " is this an alias file?", path, lineno);
+
+ /*
+ * Optionally treat the value as a filename, and replace the value
+ * with the BASE64-encoded content of the named file.
+ */
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("%s, line %d: %s: skipping this entry",
+ VSTREAM_PATH(fp), lineno, err);
+ myfree(err);
+ continue;
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Store the value under the key. Handle duplicates
+ * appropriately. XXX Move this into dict_ht, but 1) that map
+ * ignores duplicates by default and we would have to check that
+ * we won't break existing code that depends on such behavior; 2)
+ * by inlining the checks here we can degrade gracefully instead
+ * of terminating with a fatal error. See comment in
+ * dict_inline.c.
+ */
+ if (dict->lookup(dict, key) != 0) {
+ if (dict_flags & DICT_FLAG_DUP_IGNORE) {
+ /* void */ ;
+ } else if (dict_flags & DICT_FLAG_DUP_REPLACE) {
+ dict->update(dict, key, value);
+ } else if (dict_flags & DICT_FLAG_DUP_WARN) {
+ msg_warn("%s, line %d: duplicate entry: \"%s\"",
+ path, lineno, key);
+ } else {
+ dict->close(dict);
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "%s, line %d: duplicate entry: \"%s\"",
+ path, lineno, key));
+ }
+ } else {
+ dict->update(dict, key, value);
+ }
+ }
+
+ /*
+ * See if the source file is hot.
+ */
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ fp = 0; /* DICT_THASH_OPEN_RETURN() */
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+
+ /*
+ * Yes, it is hot. Discard the result and read the file again.
+ */
+ dict->close(dict);
+ if (msg_verbose > 1)
+ msg_info("pausing to let file %s cool down", path);
+ doze(300000);
+ }
+
+ dict->owner.uid = st.st_uid;
+ dict->owner.status = (st.st_uid != 0);
+
+ DICT_THASH_OPEN_RETURN(DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_thash.h b/src/util/dict_thash.h
new file mode 100644
index 0000000..878366a
--- /dev/null
+++ b/src/util/dict_thash.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_THASH_H_INCLUDED_
+#define _DICT_THASH_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_thash 3h
+/* SUMMARY
+/* dictionary manager interface to flat text files
+/* SYNOPSIS
+/* #include <dict_thash.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_THASH "texthash"
+
+extern DICT *dict_thash_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_thash.in b/src/util/dict_thash.in
new file mode 100644
index 0000000..89938f6
--- /dev/null
+++ b/src/util/dict_thash.in
@@ -0,0 +1,5 @@
+"the answer is 42
+xxx: yyy
+xxx
+aaa bbb
+aaa bbb
diff --git a/src/util/dict_thash.map b/src/util/dict_thash.map
new file mode 100644
index 0000000..cce1144
--- /dev/null
+++ b/src/util/dict_thash.map
@@ -0,0 +1,17 @@
+"the answer is" 42
+ABCDEF 012345
+allascii.c 915
+alldig.c 928
+allprint.c 943
+allspace.c 931
+argv.c 5271
+argv_split.c 2780
+attr_clnt.c 5813
+attr_print0.c 7243
+attr_print64.c 8104
+attr_print_plain.c 7086
+attr_scan0.c 15454
+attr_scan64.c 17256
+attr_scan_plain.c 16924
+auto_clnt.c 9819
+the answer is 42
diff --git a/src/util/dict_thash.ref b/src/util/dict_thash.ref
new file mode 100644
index 0000000..efdc207
--- /dev/null
+++ b/src/util/dict_thash.ref
@@ -0,0 +1,6 @@
+postmap: warning: dict_thash.in, line 1: unbalanced '"' in '"the answer is 42' -- ignoring this line
+postmap: warning: dict_thash.in, line 2: record is in "key: value" format; is this an alias file?
+postmap: warning: dict_thash.in, line 3: expected format: key whitespace value -- ignoring this line
+postmap: warning: dict_thash.in, line 5: duplicate entry: "aaa"
+aaa bbb
+xxx: yyy
diff --git a/src/util/dict_union.c b/src/util/dict_union.c
new file mode 100644
index 0000000..80df03b
--- /dev/null
+++ b/src/util/dict_union.c
@@ -0,0 +1,202 @@
+/*++
+/* NAME
+/* dict_union 3
+/* SUMMARY
+/* dictionary manager interface for union of tables
+/* SYNOPSIS
+/* #include <dict_union.h>
+/*
+/* DICT *dict_union_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_union_open() opens a sequence of one or more tables.
+/* Example: "\fBunionmap:{\fItype_1:name_1, ..., type_n:name_n\fR}".
+/*
+/* Each "unionmap:" query is given to each table in the specified
+/* order. All found results are concatenated, separated by
+/* comma. The unionmap table produces no result when all
+/* lookup tables return no result.
+/*
+/* The first and last characters of a "unionmap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/*
+/* The open_flags and dict_flags arguments are passed on to
+/* the underlying dictionaries.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* 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 <mymalloc.h>
+#include <htable.h>
+#include <dict.h>
+#include <dict_union.h>
+#include <stringops.h>
+#include <vstring.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *map_union; /* pipelined tables */
+ VSTRING *re_buf; /* reply buffer */
+} DICT_UNION;
+
+#define STR(x) vstring_str(x)
+
+/* dict_union_lookup - search a bunch of tables and combine the results */
+
+static const char *dict_union_lookup(DICT *dict, const char *query)
+{
+ static const char myname[] = "dict_union_lookup";
+ DICT_UNION *dict_union = (DICT_UNION *) dict;
+ DICT *map;
+ char **cpp;
+ char *dict_type_name;
+ const char *result = 0;
+
+ /*
+ * After Roel van Meer, postfix-users mailing list, Sept 2014.
+ */
+ VSTRING_RESET(dict_union->re_buf);
+ for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if ((map = dict_handle(dict_type_name)) == 0)
+ msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
+ if ((result = dict_get(map, query)) != 0) {
+ if (VSTRING_LEN(dict_union->re_buf) > 0)
+ VSTRING_ADDCH(dict_union->re_buf, ',');
+ vstring_strcat(dict_union->re_buf, result);
+ } else if (map->error != 0) {
+ DICT_ERR_VAL_RETURN(dict, map->error, 0);
+ }
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
+ VSTRING_LEN(dict_union->re_buf) > 0 ?
+ STR(dict_union->re_buf) : 0);
+}
+
+/* dict_union_close - disassociate from a bunch of tables */
+
+static void dict_union_close(DICT *dict)
+{
+ DICT_UNION *dict_union = (DICT_UNION *) dict;
+ char **cpp;
+ char *dict_type_name;
+
+ for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++)
+ dict_unregister(dict_type_name);
+ argv_free(dict_union->map_union);
+ vstring_free(dict_union->re_buf);
+ dict_free(dict);
+}
+
+/* dict_union_open - open a bunch of tables */
+
+DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
+{
+ static const char myname[] = "dict_union_open";
+ DICT_UNION *dict_union;
+ char *saved_name = 0;
+ char *dict_type_name;
+ ARGV *argv = 0;
+ char **cpp;
+ DICT *dict;
+ int match_flags = 0;
+ struct DICT_OWNER aggr_owner;
+ size_t len;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_UNION_RETURN(x) do { \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (argv != 0) \
+ argv_free(argv); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_UNION, name));
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
+ (argv->argc == 0)))
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_UNION, name,
+ DICT_TYPE_UNION));
+
+ /*
+ * The least-trusted table in the set determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ if (strchr(dict_type_name, ':') == 0)
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_UNION, name,
+ DICT_TYPE_UNION));
+ if ((dict = dict_handle(dict_type_name)) == 0)
+ dict = dict_open(dict_type_name, open_flags, dict_flags);
+ dict_register(dict_type_name, dict);
+ DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
+ if (cpp == argv->argv)
+ match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ dict_union =
+ (DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union));
+ dict_union->dict.lookup = dict_union_lookup;
+ dict_union->dict.close = dict_union_close;
+ dict_union->dict.flags = dict_flags | match_flags;
+ dict_union->dict.owner = aggr_owner;
+ dict_union->re_buf = vstring_alloc(100);
+ dict_union->map_union = argv;
+ argv = 0;
+ DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict));
+}
diff --git a/src/util/dict_union.h b/src/util/dict_union.h
new file mode 100644
index 0000000..9554f84
--- /dev/null
+++ b/src/util/dict_union.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_UNION_H_INCLUDED_
+#define _DICT_UNION_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_union 3h
+/* SUMMARY
+/* dictionary manager interface for union of tables
+/* SYNOPSIS
+/* #include <dict_union.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_UNION "unionmap"
+
+extern DICT *dict_union_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_union_test.in b/src/util/dict_union_test.in
new file mode 100644
index 0000000..9d111d4
--- /dev/null
+++ b/src/util/dict_union_test.in
@@ -0,0 +1,7 @@
+${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read <<EOF
+get foo
+get bar
+EOF
+${VALGRIND} ./dict_open 'unionmap:{static:one,fail:fail}' read <<EOF
+get foo
+EOF
diff --git a/src/util/dict_union_test.ref b/src/util/dict_union_test.ref
new file mode 100644
index 0000000..b609410
--- /dev/null
+++ b/src/util/dict_union_test.ref
@@ -0,0 +1,10 @@
++ ./dict_open unionmap:{static:one,static:two,inline:{foo=three}} read
+owner=trusted (uid=2147483647)
+> get foo
+foo=one,two,three
+> get bar
+bar=one,two
++ ./dict_open unionmap:{static:one,fail:fail} read
+owner=trusted (uid=2147483647)
+> get foo
+foo: error
diff --git a/src/util/dict_unix.c b/src/util/dict_unix.c
new file mode 100644
index 0000000..4635344
--- /dev/null
+++ b/src/util/dict_unix.c
@@ -0,0 +1,204 @@
+/*++
+/* NAME
+/* dict_unix 3
+/* SUMMARY
+/* dictionary manager interface to UNIX tables
+/* SYNOPSIS
+/* #include <dict_unix.h>
+/*
+/* DICT *dict_unix_open(map, dummy, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_unix_open() makes the specified UNIX table accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used.
+/*
+/* Known map names:
+/* .IP passwd.byname
+/* The table is the UNIX password database. The key is a login name.
+/* The result is a password file entry in passwd(5) format.
+/* .IP group.byname
+/* The table is the UNIX group database. The key is a group name.
+/* The result is a group file entry in group(5) format.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown map name, attempt to update map.
+/* 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 <pwd.h>
+#include <grp.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_unix.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+} DICT_UNIX;
+
+/* dict_unix_getpwnam - find password table entry */
+
+static const char *dict_unix_getpwnam(DICT *dict, const char *key)
+{
+ struct passwd *pwd;
+ static VSTRING *buf;
+ static int sanity_checked;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((pwd = getpwnam(key)) == 0) {
+ if (sanity_checked == 0) {
+ sanity_checked = 1;
+ errno = 0;
+ if (getpwuid(0) == 0) {
+ msg_warn("cannot access UNIX password database: %m");
+ dict->error = DICT_ERR_RETRY;
+ }
+ }
+ return (0);
+ } else {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ sanity_checked = 1;
+ vstring_sprintf(buf, "%s:%s:%ld:%ld:%s:%s:%s",
+ pwd->pw_name, pwd->pw_passwd, (long) pwd->pw_uid,
+ (long) pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir,
+ pwd->pw_shell);
+ return (vstring_str(buf));
+ }
+}
+
+/* dict_unix_getgrnam - find group table entry */
+
+static const char *dict_unix_getgrnam(DICT *dict, const char *key)
+{
+ struct group *grp;
+ static VSTRING *buf;
+ char **cpp;
+ static int sanity_checked;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((grp = getgrnam(key)) == 0) {
+ if (sanity_checked == 0) {
+ sanity_checked = 1;
+ errno = 0;
+ if (getgrgid(0) == 0) {
+ msg_warn("cannot access UNIX group database: %m");
+ dict->error = DICT_ERR_RETRY;
+ }
+ }
+ return (0);
+ } else {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ sanity_checked = 1;
+ vstring_sprintf(buf, "%s:%s:%ld:",
+ grp->gr_name, grp->gr_passwd, (long) grp->gr_gid);
+ for (cpp = grp->gr_mem; *cpp; cpp++) {
+ vstring_strcat(buf, *cpp);
+ if (cpp[1])
+ VSTRING_ADDCH(buf, ',');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+ }
+}
+
+/* dict_unix_close - close UNIX map */
+
+static void dict_unix_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_unix_open - open UNIX map */
+
+DICT *dict_unix_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_UNIX *dict_unix;
+ struct dict_unix_lookup {
+ char *name;
+ const char *(*lookup) (DICT *, const char *);
+ };
+ static struct dict_unix_lookup dict_unix_lookup[] = {
+ "passwd.byname", dict_unix_getpwnam,
+ "group.byname", dict_unix_getgrnam,
+ 0,
+ };
+ struct dict_unix_lookup *lp;
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_UNIX, map));
+
+ /*
+ * "Open" the database.
+ */
+ for (lp = dict_unix_lookup; /* void */ ; lp++) {
+ if (lp->name == 0)
+ return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
+ "unknown table: %s:%s", DICT_TYPE_UNIX, map));
+ if (strcmp(map, lp->name) == 0)
+ break;
+ }
+ dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map,
+ sizeof(*dict_unix));
+ dict_unix->dict.lookup = lp->lookup;
+ dict_unix->dict.close = dict_unix_close;
+ dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_unix->dict.fold_buf = vstring_alloc(10);
+ dict_unix->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ return (DICT_DEBUG (&dict_unix->dict));
+}
diff --git a/src/util/dict_unix.h b/src/util/dict_unix.h
new file mode 100644
index 0000000..b5674b2
--- /dev/null
+++ b/src/util/dict_unix.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_UNIX_H_INCLUDED_
+#define _DICT_UNIX_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_unix 3h
+/* SUMMARY
+/* dictionary manager interface to UNIX maps
+/* SYNOPSIS
+/* #include <dict_unix.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_UNIX "unix"
+
+extern DICT *dict_unix_open(const char *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_utf8.c b/src/util/dict_utf8.c
new file mode 100644
index 0000000..f1fc65a
--- /dev/null
+++ b/src/util/dict_utf8.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* dict_utf8 3
+/* SUMMARY
+/* dictionary UTF-8 helpers
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_utf8_activate(
+/* DICT *dict)
+/* DESCRIPTION
+/* dict_utf8_activate() wraps a dictionary's lookup/update/delete
+/* methods with code that enforces UTF-8 checks on keys and
+/* values, and that logs a warning when incorrect UTF-8 is
+/* encountered. The original dictionary handle becomes invalid.
+/*
+/* The wrapper code enforces a policy that maximizes application
+/* robustness (it avoids the need for new error-handling code
+/* paths in application code). Attempts to store non-UTF-8
+/* keys or values are skipped while reporting a non-error
+/* status, attempts to look up or delete non-UTF-8 keys are
+/* skipped while reporting a non-error status, and lookup
+/* results that contain a non-UTF-8 value are blocked while
+/* reporting a configuration error.
+/* BUGS
+/* dict_utf8_activate() does not nest.
+/* 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 <stringops.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <msg.h>
+
+ /*
+ * The goal is to maximize robustness: bad UTF-8 should not appear in keys,
+ * because those are derived from controlled inputs, and values should be
+ * printable before they are stored. But if we failed to check something
+ * then it should not result in fatal errors and thus open up the system for
+ * a denial-of-service attack.
+ *
+ * Proposed over-all policy: skip attempts to store invalid UTF-8 lookup keys
+ * or values. Rationale: some storage may not permit malformed UTF-8. This
+ * maximizes program robustness. If we get an invalid lookup result, report
+ * a configuration error.
+ *
+ * LOOKUP
+ *
+ * If the key is invalid, log a warning and skip the request. Rationale: the
+ * item cannot exist.
+ *
+ * If the lookup result is invalid, log a warning and return a configuration
+ * error.
+ *
+ * UPDATE
+ *
+ * If the key is invalid, then log a warning and skip the request. Rationale:
+ * the item cannot exist.
+ *
+ * If the value is invalid, log a warning and skip the request. Rationale:
+ * storage may not permit malformed UTF-8. This maximizes program
+ * robustness.
+ *
+ * DELETE
+ *
+ * If the key is invalid, then skip the request. Rationale: the item cannot
+ * exist.
+ */
+
+/* dict_utf8_check_fold - casefold or validate string */
+
+static char *dict_utf8_check_fold(DICT *dict, const char *string,
+ CONST_CHAR_STAR *err)
+{
+ int fold_flag = (dict->flags & DICT_FLAG_FOLD_ANY);
+
+ /*
+ * Validate UTF-8 without casefolding.
+ */
+ if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+
+ /*
+ * Casefold UTF-8.
+ */
+ if (fold_flag != 0
+ && (fold_flag & ((dict->flags & DICT_FLAG_FIXED) ?
+ DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL))) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ return (casefold(dict->fold_buf, string));
+ }
+ return ((char *) string);
+}
+
+/* dict_utf8_check validate UTF-8 string */
+
+static int dict_utf8_check(const char *string, CONST_CHAR_STAR *err)
+{
+ if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+ return (1);
+}
+
+/* dict_utf8_lookup - UTF-8 lookup method wrapper */
+
+static const char *dict_utf8_lookup(DICT *dict, const char *key)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ const char *value;
+ int saved_flags;
+
+ /*
+ * Validate and optionally fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (0);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ value = backup->lookup(dict, fold_res);
+ dict->flags |= saved_flags;
+
+ /*
+ * Validate the result, and if invalid fail the request.
+ */
+ if (value != 0 && dict_utf8_check(value, &utf8_err) == 0) {
+ msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s",
+ dict->type, dict->name, key, value, utf8_err);
+ dict->error = DICT_ERR_CONFIG;
+ return (0);
+ } else {
+ return (value);
+ }
+}
+
+/* dict_utf8_update - UTF-8 update method wrapper */
+
+static int dict_utf8_update(DICT *dict, const char *key, const char *value)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ int saved_flags;
+ int status;
+
+ /*
+ * Validate or fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Validate the value, and if invalid skip the request.
+ */
+ else if (dict_utf8_check(value, &utf8_err) == 0) {
+ msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s",
+ dict->type, dict->name, key, value, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ else {
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ status = backup->update(dict, fold_res, value);
+ dict->flags |= saved_flags;
+ return (status);
+ }
+}
+
+/* dict_utf8_delete - UTF-8 delete method wrapper */
+
+static int dict_utf8_delete(DICT *dict, const char *key)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ int saved_flags;
+ int status;
+
+ /*
+ * Validate and optionally fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ else {
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ status = backup->delete(dict, fold_res);
+ dict->flags |= saved_flags;
+ return (status);
+ }
+}
+
+/* dict_utf8_activate - wrap a legacy dict object for UTF-8 processing */
+
+DICT *dict_utf8_activate(DICT *dict)
+{
+ const char myname[] = "dict_utf8_activate";
+ DICT_UTF8_BACKUP *backup;
+
+ /*
+ * Sanity check.
+ */
+ if (util_utf8_enable == 0)
+ msg_panic("%s: Unicode support is not available", myname);
+ if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0)
+ msg_panic("%s: %s:%s does not request Unicode support",
+ myname, dict->type, dict->name);
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) || dict->utf8_backup != 0)
+ msg_panic("%s: %s:%s Unicode support is already activated",
+ myname, dict->type, dict->name);
+
+ /*
+ * Unlike dict_debug(3) we do not put a proxy dict object in front of the
+ * encapsulated object, because then we would have to bidirectionally
+ * propagate changes in the data members (errors, flags, jbuf, and so on)
+ * between proxy object and encapsulated object.
+ *
+ * Instead we attach ourselves behind the encapsulated dict object, and
+ * redirect some function pointers to ourselves.
+ */
+ backup = dict->utf8_backup = (DICT_UTF8_BACKUP *) mymalloc(sizeof(*backup));
+
+ /*
+ * Interpose on the lookup/update/delete methods. It is a conscious
+ * decision not to tinker with the iterator or destructor.
+ */
+ backup->lookup = dict->lookup;
+ backup->update = dict->update;
+ backup->delete = dict->delete;
+
+ dict->lookup = dict_utf8_lookup;
+ dict->update = dict_utf8_update;
+ dict->delete = dict_utf8_delete;
+
+ /*
+ * Leave our mark. See sanity check above.
+ */
+ dict->flags |= DICT_FLAG_UTF8_ACTIVE;
+
+ return (dict);
+}
diff --git a/src/util/dict_utf8_test.in b/src/util/dict_utf8_test.in
new file mode 100644
index 0000000..f8d4536
--- /dev/null
+++ b/src/util/dict_utf8_test.in
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+LC_ALL=C awk 'BEGIN {
+ print "flags"
+ print "verbose"
+ printf "get foo\n"
+ printf "put Δημοσθένους.example.com aaa\n"
+ printf "get Δημοσθένους.example.com\n"
+ printf "put %c%c%c xxx\n", 128, 128, 128
+ printf "get %c%c%c\n", 128, 128, 128
+ printf "put xxx %c%c%c\n", 128, 128, 128
+ printf "get xxx\n"
+ exit
+}' | ${VALGRIND} ./dict_open internal:whatever write utf8_request
diff --git a/src/util/dict_utf8_test.ref b/src/util/dict_utf8_test.ref
new file mode 100644
index 0000000..7b825f9
--- /dev/null
+++ b/src/util/dict_utf8_test.ref
@@ -0,0 +1,18 @@
+owner=trusted (uid=2147483647)
+> flags
+dict flags fixed|lock|replace|utf8_request|utf8_active
+> verbose
+> get foo
+foo: not found
+> put Δημοσθένους.example.com aaa
+> get Δημοσθένους.example.com
+Δημοσθένους.example.com=aaa
+> put €€€ xxx
+./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint
+> get €€€
+./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint
+€€€: not found
+> put xxx €€€
+./dict_open: warning: internal:whatever: key "xxx": non-UTF-8 value "???": malformed UTF-8 or invalid codepoint
+> get xxx
+xxx: not found
diff --git a/src/util/dir_forest.c b/src/util/dir_forest.c
new file mode 100644
index 0000000..0070177
--- /dev/null
+++ b/src/util/dir_forest.c
@@ -0,0 +1,110 @@
+/*++
+/* NAME
+/* dir_forest 3
+/* SUMMARY
+/* file name to directory forest
+/* SYNOPSIS
+/* #include <dir_forest.h>
+/*
+/* char *dir_forest(buf, path, depth)
+/* VSTRING *buf;
+/* const char *path;
+/* int depth;
+/* DESCRIPTION
+/* This module implements support for directory forests: a file
+/* organization that introduces one or more levels of intermediate
+/* subdirectories in order to reduce the number of files per directory.
+/*
+/* dir_forest() maps a file basename to a directory forest and
+/* returns the resulting string: file name "abcd" becomes "a/b/"
+/* and so on. The number of subdirectory levels is adjustable.
+/*
+/* Arguments:
+/* .IP buf
+/* A buffer that is overwritten with the result. The result
+/* ends in "/" and is null terminated. If a null pointer is
+/* specified, the result is written to a private buffer that
+/* is overwritten upon each call.
+/* .IP path
+/* A null-terminated string of printable characters. Characters
+/* special to the file system are not permitted.
+/* The first subdirectory is named after the first character
+/* in \fIpath\fR, and so on. When the path is shorter than the
+/* desired number of subdirectory levels, directory names
+/* of '_' (underscore) are used as replacement.
+/* .IP depth
+/* The desired number of subdirectory levels.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal error: out of memory.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "dir_forest.h"
+
+/* dir_forest - translate base name to directory forest */
+
+char *dir_forest(VSTRING *buf, const char *path, int depth)
+{
+ const char *myname = "dir_forest";
+ static VSTRING *private_buf = 0;
+ int n;
+ const char *cp;
+ int ch;
+
+ /*
+ * Sanity checks.
+ */
+ if (*path == 0)
+ msg_panic("%s: empty path", myname);
+ if (depth < 1)
+ msg_panic("%s: depth %d", myname, depth);
+
+ /*
+ * Your buffer or mine?
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(1);
+ buf = private_buf;
+ }
+
+ /*
+ * Generate one or more subdirectory levels, depending on the pathname
+ * contents. When the pathname is short, use underscores instead.
+ * Disallow non-printable characters or characters that are special to
+ * the file system.
+ */
+ VSTRING_RESET(buf);
+ for (cp = path, n = 0; n < depth; n++) {
+ if ((ch = *cp) == 0) {
+ ch = '_';
+ } else {
+ if (!ISPRINT(ch) || ch == '.' || ch == '/')
+ msg_panic("%s: invalid pathname: %s", myname, path);
+ cp++;
+ }
+ VSTRING_ADDCH(buf, ch);
+ VSTRING_ADDCH(buf, '/');
+ }
+ VSTRING_TERMINATE(buf);
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s -> %s", myname, path, vstring_str(buf));
+ return (vstring_str(buf));
+}
diff --git a/src/util/dir_forest.h b/src/util/dir_forest.h
new file mode 100644
index 0000000..2c4c363
--- /dev/null
+++ b/src/util/dir_forest.h
@@ -0,0 +1,35 @@
+#ifndef _DIR_FOREST_H_INCLUDED_
+#define _DIR_FOREST_H_INCLUDED_
+
+/*++
+/* NAME
+/* dir_forest 3h
+/* SUMMARY
+/* file name to directory forest
+/* SYNOPSIS
+/* #include <dir_forest.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *dir_forest(VSTRING *, const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/doze.c b/src/util/doze.c
new file mode 100644
index 0000000..28d5669
--- /dev/null
+++ b/src/util/doze.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* doze 3
+/* SUMMARY
+/* take a nap
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void doze(microseconds)
+/* unsigned microseconds;
+/* DESCRIPTION
+/* doze() sleeps for the specified number of microseconds. It is
+/* a simple alternative for systems that lack usleep().
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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/time.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* doze - sleep a while */
+
+void doze(unsigned delay)
+{
+ struct timeval tv;
+
+#define MILLION 1000000
+
+ tv.tv_sec = delay / MILLION;
+ tv.tv_usec = delay % MILLION;
+ while (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &tv) < 0)
+ if (errno != EINTR)
+ msg_fatal("doze: select: %m");
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ unsigned delay;
+
+ if (argc != 2 || (delay = atol(argv[1])) == 0)
+ msg_fatal("usage: %s microseconds", argv[0]);
+ doze(delay);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/dummy_read.c b/src/util/dummy_read.c
new file mode 100644
index 0000000..639004f
--- /dev/null
+++ b/src/util/dummy_read.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* dummy_read 3
+/* SUMMARY
+/* dummy read operation
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t dummy_read(fd, buf, buf_len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* dummy_read() reports an EOF condition without side effects.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor whose value is logged when verbose logging
+/* is turned on.
+/* .IP buf
+/* Read buffer pointer. Not used.
+/* .IP buf_len
+/* Read buffer size. Its value is logged when verbose logging is
+/* turned on.
+/* .IP timeout
+/* The deadline in seconds. Not used.
+/* .IP context
+/* Application context. Not used.
+/* DIAGNOSTICS
+/* None.
+/* 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 <iostuff.h>
+
+/* dummy_read - dummy read operation */
+
+ssize_t dummy_read(int fd, void *unused_buf, size_t len,
+ int unused_timeout, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("dummy_read: fd %d, len %lu", fd, (unsigned long) len);
+ return (0);
+}
diff --git a/src/util/dummy_write.c b/src/util/dummy_write.c
new file mode 100644
index 0000000..943e2ad
--- /dev/null
+++ b/src/util/dummy_write.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* dummy_write 3
+/* SUMMARY
+/* dummy write operation
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t dummy_write(fd, buf, buf_len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* dummy_write() implements a data sink without side effects.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor whose value is logged when verbose logging
+/* is turned on.
+/* .IP buf
+/* Write buffer pointer. Not used.
+/* .IP buf_len
+/* Write buffer size. Its value is logged when verbose logging is
+/* turned on.
+/* .IP timeout
+/* The deadline in seconds. Not used.
+/* .IP context
+/* Application context. Not used.
+/* DIAGNOSTICS
+/* None.
+/* 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 <iostuff.h>
+
+/* dummy_write - dummy write operation */
+
+ssize_t dummy_write(int fd, void *unused_buf, size_t len,
+ int unused_timeout, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("dummy_write: fd %d, len %lu", fd, (unsigned long) len);
+ return (len);
+}
diff --git a/src/util/dup2_pass_on_exec.c b/src/util/dup2_pass_on_exec.c
new file mode 100644
index 0000000..5286e5b
--- /dev/null
+++ b/src/util/dup2_pass_on_exec.c
@@ -0,0 +1,64 @@
+/*++
+/* NAME
+/* dup2_pass_on_exec 1
+/* SUMMARY
+/* dup2 close-on-exec behavior test program
+/* SYNOPSIS
+/* dup2_pass_on_exec
+/* DESCRIPTION
+/* dup2_pass_on_exec sets the close-on-exec flag on its
+/* standard input and then dup2() to duplicate it.
+/* Posix-1003.1 specifies in section 6.2.1.2 that dup2(o,n) should behave
+/* as: close(n); n = fcntl(o, F_DUPFD, n); as long as o is a valid
+/* file-descriptor, n!=o, and 0<=n<=[OPEN_MAX].
+/* Section 6.5.2.2 states that the close-on-exec flag of the result of a
+/* successful fcntl(o, F_DUPFD, n) is cleared.
+/*
+/* At least Ultrix4.3a does not clear the close-on-exec flag of n on
+/* dup2(o, n).
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Christian von Roques <roques@pond.sub.org>
+/* Forststrasse 71
+/* 76131 Karlsruhe, GERMANY
+/*--*/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define DO(s) if (s < 0) { perror(#s); exit(1); }
+
+int main(int unused_argc, char **unused_argv)
+{
+ int res;
+
+ printf("Setting the close-on-exec flag of file-descriptor 0.\n");
+ DO(fcntl(0, F_SETFD, 1));
+
+ printf("Duplicating file-descriptor 0 to 3.\n");
+ DO(dup2(0, 3));
+
+ printf("Testing if the close-on-exec flag of file-descriptor 3 is set.\n");
+ DO((res = fcntl(3, F_GETFD, 0)));
+ if (res & 1)
+ printf(
+"Yes, a newly dup2()ed file-descriptor has the close-on-exec \
+flag cloned.\n\
+THIS VIOLATES Posix1003.1 section 6.2.1.2 or 6.5.2.2!\n\
+You should #define DUP2_DUPS_CLOSE_ON_EXEC in sys_defs.h \
+for your OS.\n");
+ else
+ printf(
+"No, a newly dup2()ed file-descriptor has the close-on-exec \
+flag cleared.\n\
+This complies with Posix1003.1 section 6.2.1.2 and 6.5.2.2!\n");
+
+ return 0;
+}
diff --git a/src/util/duplex_pipe.c b/src/util/duplex_pipe.c
new file mode 100644
index 0000000..04f23f6
--- /dev/null
+++ b/src/util/duplex_pipe.c
@@ -0,0 +1,49 @@
+/*++
+/* NAME
+/* duplex_pipe 3
+/* SUMMARY
+/* local IPD
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int duplex_pipe(fds)
+/* int *fds;
+/* DESCRIPTION
+/* duplex_pipe() uses whatever local primitive it takes
+/* to get a two-way I/O channel.
+/* DIAGNOSTICS
+/* A null result means success. In case of error, the result
+/* is -1 and errno is set to the appropriate number.
+/* 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 libraries */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "iostuff.h"
+#include "sane_socketpair.h"
+
+/* duplex_pipe - give me a duplex pipe or bust */
+
+int duplex_pipe(int *fds)
+{
+#ifdef HAS_DUPLEX_PIPE
+ return (pipe(fds));
+#else
+ return (sane_socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
+#endif
+}
+
diff --git a/src/util/edit_file.c b/src/util/edit_file.c
new file mode 100644
index 0000000..9d76b93
--- /dev/null
+++ b/src/util/edit_file.c
@@ -0,0 +1,353 @@
+/*++
+/* NAME
+/* edit_file 3
+/* SUMMARY
+/* simple cooperative file updating protocol
+/* SYNOPSIS
+/* #include <edit_file.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *tmp_path; /* temp. pathname */
+/* VSTREAM *tmp_fp; /* temp. stream */
+/* /* private members... */
+/* .in -4
+/* } EDIT_FILE;
+/*
+/* EDIT_FILE *edit_file_open(original_path, output_flags, output_mode)
+/* const char *original_path;
+/* int output_flags;
+/* mode_t output_mode;
+/*
+/* int edit_file_close(edit_file)
+/* EDIT_FILE *edit_file;
+/*
+/* void edit_file_cleanup(edit_file)
+/* EDIT_FILE *edit_file;
+/* DESCRIPTION
+/* This module implements a simple protocol for cooperative
+/* processes to update one file. The idea is to 1) create a
+/* new file under a deterministic temporary pathname, 2)
+/* populate the new file with updated information, and 3)
+/* rename the new file into the place of the original file.
+/* This module provides 1) and 3), and leaves 2) to the
+/* application. The temporary pathname is deterministic to
+/* avoid accumulation of thrash after program crashes.
+/*
+/* edit_file_open() implements the first phase of the protocol.
+/* It creates or opens an output file with a deterministic
+/* temporary pathname, obtained by appending the suffix defined
+/* with EDIT_FILE_SUFFIX to the specified original file pathname.
+/* The original file itself is not opened. edit_file_open()
+/* then locks the output file for exclusive access, and verifies
+/* that the file still exists under the temporary pathname.
+/* At this point in the protocol, the current process controls
+/* both the output file content and its temporary pathname.
+/*
+/* In the second phase, the application opens the original
+/* file if needed, and updates the output file via the
+/* \fBtmp_fp\fR member of the EDIT_FILE data structure. This
+/* phase is not implemented by the edit_file() module.
+/*
+/* edit_file_close() implements the third and final phase of
+/* the protocol. It flushes the output file to persistent
+/* storage, and renames the output file from its temporary
+/* pathname into the place of the original file. When any of
+/* these operations fails, edit_file_close() behaves as if
+/* edit_file_cleanup() was called. Regardless of whether these
+/* operations succeed, edit_file_close() releases the exclusive
+/* lock, closes the output file, and frees up memory that was
+/* allocated by edit_file_open().
+/*
+/* edit_file_cleanup() aborts the protocol. It discards the
+/* output file, releases the exclusive lock, closes the output
+/* file, and frees up memory that was allocated by edit_file_open().
+/*
+/* Arguments:
+/* .IP original_path
+/* The pathname of the original file that will be replaced by
+/* the output file. The temporary pathname for the output file
+/* is obtained by appending the suffix defined with EDIT_FILE_SUFFIX
+/* to a copy of the specified original file pathname, and is
+/* made available via the \fBtmp_path\fR member of the EDIT_FILE
+/* data structure.
+/* .IP output_flags
+/* Flags for opening the output file. These are as with open(2),
+/* except that the O_TRUNC flag is ignored. edit_file_open()
+/* always truncates the output file after it has obtained
+/* exclusive control over the output file content and temporary
+/* pathname.
+/* .IP output_mode
+/* Permissions for the output file. These are as with open(2),
+/* except that the output file is initially created with no
+/* group or other access permissions. The specified output
+/* file permissions are applied by edit_file_close().
+/* .IP edit_file
+/* Pointer to data structure that is returned upon successful
+/* completion by edit_file_open(), and that must be passed to
+/* edit_file_close() or edit_file_cleanup().
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure, fstat() failure,
+/* unlink() failure, lock failure, ftruncate() failure.
+/*
+/* edit_file_open() immediately returns a null pointer when
+/* it cannot open the output file.
+/*
+/* edit_file_close() returns zero on success, VSTREAM_EOF on
+/* failure.
+/*
+/* With both functions, the global errno variable indicates
+/* the nature of the problem. All errors are relative to the
+/* temporary output's pathname. With both functions, this
+/* pathname is not available via the EDIT_FILE data structure,
+/* because that structure was already destroyed, or not created.
+/* BUGS
+/* In the non-error case, edit_file_open() will not return
+/* until it obtains exclusive control over the output file
+/* content and temporary pathname. Applications that are
+/* concerned about deadlock should protect the edit_file_open()
+/* call with a watchdog timer.
+/*
+/* When interrupted, edit_file_close() may leave behind a
+/* world-readable output file under the temporary pathname.
+/* On some systems this can be used to inflict a shared-lock
+/* DOS on the protocol. Applications that are concerned about
+/* maximal safety should protect the edit_file_close() call
+/* with sigdelay() and sigresume() calls, but this introduces
+/* the risk that the program will get stuck forever.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Based on code originally by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Packaged into one module with minor improvements by:
+/* 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>
+#include <stdio.h> /* rename(2) */
+#include <errno.h>
+
+ /*
+ * This mask selects all permission bits in the st_mode stat data. There is
+ * no portable definition (unlike S_IFMT, which is defined for the file type
+ * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB.
+ */
+#define FILE_PERM_MASK \
+ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Utility Library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myflock.h>
+#include <edit_file.h>
+#include <warn_stat.h>
+
+ /*
+ * Do we reuse and truncate an output file that persists after a crash, or
+ * do we unlink it and create a new file?
+ */
+#define EDIT_FILE_REUSE_AFTER_CRASH
+
+ /*
+ * Protocol internals: the temporary file permissions.
+ */
+#define EDIT_FILE_MODE (S_IRUSR | S_IWUSR) /* temp file mode */
+
+ /*
+ * Make complex operations more readable. We could use functions, instead.
+ * The main thing is that we keep the _alloc and _free code together.
+ */
+#define EDIT_FILE_ALLOC(ep, path, mode) do { \
+ (ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \
+ (ep)->final_path = mystrdup(path); \
+ (ep)->final_mode = (mode); \
+ (ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \
+ (ep)->tmp_fp = 0; \
+ } while (0)
+
+#define EDIT_FILE_FREE(ep) do { \
+ myfree((ep)->final_path); \
+ myfree((ep)->tmp_path); \
+ myfree((void *) (ep)); \
+ } while (0)
+
+/* edit_file_open - open and lock file with deterministic temporary pathname */
+
+EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode)
+{
+ struct stat before_lock;
+ struct stat after_lock;
+ int saved_errno;
+ EDIT_FILE *ep;
+
+ /*
+ * Initialize. Do not bother to optimize for the error case.
+ */
+ EDIT_FILE_ALLOC(ep, path, mode);
+
+ /*
+ * As long as the output file can be opened under the temporary pathname,
+ * this code can loop or block forever.
+ *
+ * Applications that are concerned about deadlock should protect the
+ * edit_file_open() call with a watchdog timer.
+ */
+ for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) {
+
+ /*
+ * Try to open the output file under the temporary pathname. This
+ * succeeds or fails immediately. To avoid creating a shared-lock DOS
+ * opportunity after we crash, we create the output file with no
+ * group or other permissions, and set the final permissions at the
+ * end (this is one reason why we try to get exclusive control over
+ * the output file instead of the original file). We postpone file
+ * truncation until we have obtained exclusive control over the file
+ * content and temporary pathname. If the open operation fails, we
+ * give up immediately. The caller can retry the call if desirable.
+ *
+ * XXX If we replace the vstream_fopen() call by safe_open(), then we
+ * should replace the stat() call below by lstat().
+ */
+ if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC),
+ EDIT_FILE_MODE)) == 0) {
+ saved_errno = errno;
+ EDIT_FILE_FREE(ep);
+ errno = saved_errno;
+ return (0);
+ }
+
+ /*
+ * At this point we may have opened an existing output file that was
+ * already locked. Try to lock the open file exclusively. This may
+ * take some time.
+ */
+ if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", ep->tmp_path);
+
+ /*
+ * At this point we have an exclusive lock, but some other process
+ * may have renamed or removed the output file while we were waiting
+ * for the lock. If that is the case, back out and try again.
+ */
+ if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0)
+ msg_fatal("open %s: %m", ep->tmp_path);
+ if (stat(ep->tmp_path, &after_lock) < 0
+ || before_lock.st_dev != after_lock.st_dev
+ || before_lock.st_ino != after_lock.st_ino
+#ifdef HAS_ST_GEN
+ || before_lock.st_gen != after_lock.st_gen
+#endif
+ /* No need to compare st_rdev or st_nlink here. */
+ ) {
+ continue;
+ }
+
+ /*
+ * At this point we have exclusive control over the output file
+ * content and its temporary pathname (within the rules of the
+ * cooperative protocol). But wait, there is more.
+ *
+ * There are many opportunities for trouble when opening a pre-existing
+ * output file. Here are just a few.
+ *
+ * - Victor observes that a system crash in the middle of the
+ * final-phase rename() operation may result in the output file
+ * having both the temporary pathname and the final pathname. In that
+ * case we must not write to the output file.
+ *
+ * - Wietse observes that crashes may also leave the output file in
+ * other inconsistent states. To avoid permission-related trouble, we
+ * simply refuse to work with an output file that has the wrong
+ * temporary permissions. This won't stop the shared-lock DOS if we
+ * crash after changing the file permissions, though.
+ *
+ * To work around these crash-related problems, remove the temporary
+ * pathname, back out, and try again.
+ */
+ if (!S_ISREG(after_lock.st_mode)
+#ifndef EDIT_FILE_REUSE_AFTER_CRASH
+ || after_lock.st_size > 0
+#endif
+ || after_lock.st_nlink > 1
+ || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) {
+ if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
+ msg_fatal("unlink %s: %m", ep->tmp_path);
+ continue;
+ }
+
+ /*
+ * Settle the final details.
+ */
+#ifdef EDIT_FILE_REUSE_AFTER_CRASH
+ if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0)
+ msg_fatal("truncate %s: %m", ep->tmp_path);
+#endif
+ return (ep);
+ }
+}
+
+/* edit_file_cleanup - clean up without completing the protocol */
+
+void edit_file_cleanup(EDIT_FILE *ep)
+{
+
+ /*
+ * Don't touch the file after we lose the exclusive lock!
+ */
+ if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
+ msg_fatal("unlink %s: %m", ep->tmp_path);
+ (void) vstream_fclose(ep->tmp_fp);
+ EDIT_FILE_FREE(ep);
+}
+
+/* edit_file_close - rename the file into place and close the file */
+
+int edit_file_close(EDIT_FILE *ep)
+{
+ VSTREAM *fp = ep->tmp_fp;
+ int fd = vstream_fileno(fp);
+ int saved_errno;
+
+ /*
+ * The rename/unlock portion of the protocol is relatively simple. The
+ * only things that really matter here are that we change permissions as
+ * late as possible, and that we rename the file to its final pathname
+ * before we lose the exclusive lock.
+ *
+ * Applications that are concerned about maximal safety should protect the
+ * edit_file_close() call with sigdelay() and sigresume() calls. It is
+ * not safe for us to call these functions directly, because the calls do
+ * not nest. It is also not nice to force every caller to run with
+ * interrupts turned off.
+ */
+ if (vstream_fflush(fp) < 0
+ || fchmod(fd, ep->final_mode) < 0
+#ifdef HAS_FSYNC
+ || fsync(fd) < 0
+#endif
+ || rename(ep->tmp_path, ep->final_path) < 0) {
+ saved_errno = errno;
+ edit_file_cleanup(ep);
+ errno = saved_errno;
+ return (VSTREAM_EOF);
+ } else {
+ (void) vstream_fclose(ep->tmp_fp);
+ EDIT_FILE_FREE(ep);
+ return (0);
+ }
+}
diff --git a/src/util/edit_file.h b/src/util/edit_file.h
new file mode 100644
index 0000000..bd13a29
--- /dev/null
+++ b/src/util/edit_file.h
@@ -0,0 +1,53 @@
+#ifndef _EDIT_FILE_H_INCLUDED_
+#define _EDIT_FILE_H_INCLUDED_
+
+/*++
+/* NAME
+/* edit_file 3h
+/* SUMMARY
+/* simple cooperative file updating protocol
+/* SYNOPSIS
+/* #include <edit_file.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ /* Private. */
+ char *final_path;
+ mode_t final_mode;
+ /* Public. */
+ char *tmp_path;
+ VSTREAM *tmp_fp;
+} EDIT_FILE;
+
+#define EDIT_FILE_SUFFIX ".tmp"
+
+extern EDIT_FILE *edit_file_open(const char *, int, mode_t);
+extern int WARN_UNUSED_RESULT edit_file_close(EDIT_FILE *);
+extern void edit_file_cleanup(EDIT_FILE *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/environ.c b/src/util/environ.c
new file mode 100644
index 0000000..4b6c59e
--- /dev/null
+++ b/src/util/environ.c
@@ -0,0 +1,156 @@
+ /*
+ * From: TCP Wrapper.
+ *
+ * Many systems have putenv() but no setenv(). Other systems have setenv() but
+ * no putenv() (MIPS). Still other systems have neither (NeXT). This is a
+ * re-implementation that hopefully ends all problems.
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+#include "sys_defs.h"
+
+#ifdef MISSING_SETENV_PUTENV
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern char **environ;
+
+static int addenv(char *); /* append entry to environment */
+static int allocated = 0; /* environ is, or is not, allocated */
+
+#define DO_CLOBBER 1
+
+/* namelength - determine length of name in "name=whatever" */
+
+static ssize_t namelength(const char *name)
+{
+ char *equal;
+
+ equal = strchr(name, '=');
+ return ((equal == 0) ? strlen(name) : (equal - name));
+}
+
+/* findenv - given name, locate name=value */
+
+static char **findenv(const char *name, ssize_t len)
+{
+ char **envp;
+
+ for (envp = environ; envp && *envp; envp++)
+ if (strncmp(name, *envp, len) == 0 && (*envp)[len] == '=')
+ return (envp);
+ return (0);
+}
+
+#if 0
+
+/* getenv - given name, locate value */
+
+char *getenv(const char *name)
+{
+ ssize_t len = namelength(name);
+ char **envp = findenv(name, len);
+
+ return (envp ? *envp + len + 1 : 0);
+}
+
+/* putenv - update or append environment (name,value) pair */
+
+int putenv(const char *nameval)
+{
+ char *equal = strchr(nameval, '=');
+ char *value = (equal ? equal : "");
+
+ return (setenv(nameval, value, DO_CLOBBER));
+}
+
+/* unsetenv - remove variable from environment */
+
+void unsetenv(const char *name)
+{
+ char **envp;
+
+ while ((envp = findenv(name, namelength(name))) != 0)
+ while (envp[0] = envp[1])
+ envp++;
+}
+
+#endif
+
+/* setenv - update or append environment (name,value) pair */
+
+int setenv(const char *name, const char *value, int clobber)
+{
+ char *destination;
+ char **envp;
+ ssize_t l_name; /* length of name part */
+ unsigned int l_nameval; /* length of name=value */
+
+ /* Permit name= and =value. */
+
+ l_name = namelength(name);
+ envp = findenv(name, l_name);
+ if (envp != 0 && clobber == 0)
+ return (0);
+ if (*value == '=')
+ value++;
+ l_nameval = l_name + strlen(value) + 1;
+
+ /*
+ * Use available memory if the old value is long enough. Never free an
+ * old name=value entry because it may not be allocated.
+ */
+
+ destination = (envp != 0 && strlen(*envp) >= l_nameval) ?
+ *envp : malloc(l_nameval + 1);
+ if (destination == 0)
+ return (-1);
+ strncpy(destination, name, l_name);
+ destination[l_name] = '=';
+ strcpy(destination + l_name + 1, value);
+ return ((envp == 0) ? addenv(destination) : (*envp = destination, 0));
+}
+
+/* cmalloc - malloc and copy block of memory */
+
+static char *cmalloc(ssize_t new_len, char *old, ssize_t old_len)
+{
+ char *new = malloc(new_len);
+
+ if (new != 0)
+ memcpy(new, old, old_len);
+ return (new);
+}
+
+/* addenv - append environment entry */
+
+static int addenv(char *nameval)
+{
+ char **envp;
+ ssize_t n_used; /* number of environment entries */
+ ssize_t l_used; /* bytes used excl. terminator */
+ ssize_t l_need; /* bytes needed incl. terminator */
+
+ for (envp = environ; envp && *envp; envp++)
+ /* void */ ;
+ n_used = envp - environ;
+ l_used = n_used * sizeof(*envp);
+ l_need = l_used + 2 * sizeof(*envp);
+
+ envp = allocated ?
+ (char **) realloc((char *) environ, l_need) :
+ (char **) cmalloc(l_need, (char *) environ, l_used);
+ if (envp == 0) {
+ return (-1);
+ } else {
+ allocated = 1;
+ environ = envp;
+ environ[n_used++] = nameval; /* add new entry */
+ environ[n_used] = 0; /* terminate list */
+ return (0);
+ }
+}
+
+#endif
diff --git a/src/util/events.c b/src/util/events.c
new file mode 100644
index 0000000..c2157bb
--- /dev/null
+++ b/src/util/events.c
@@ -0,0 +1,1261 @@
+/*++
+/* NAME
+/* events 3
+/* SUMMARY
+/* event manager
+/* SYNOPSIS
+/* #include <events.h>
+/*
+/* time_t event_time()
+/*
+/* void event_loop(delay)
+/* int delay;
+/*
+/* time_t event_request_timer(callback, context, delay)
+/* void (*callback)(int event, void *context);
+/* void *context;
+/* int delay;
+/*
+/* int event_cancel_timer(callback, context)
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_enable_read(fd, callback, context)
+/* int fd;
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_enable_write(fd, callback, context)
+/* int fd;
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_disable_readwrite(fd)
+/* int fd;
+/*
+/* void event_drain(time_limit)
+/* int time_limit;
+/*
+/* void event_fork(void)
+/* DESCRIPTION
+/* This module delivers I/O and timer events.
+/* Multiple I/O streams and timers can be monitored simultaneously.
+/* Events are delivered via callback routines provided by the
+/* application. When requesting an event, the application can provide
+/* private context that is passed back when the callback routine is
+/* executed.
+/*
+/* event_time() returns a cached value of the current time.
+/*
+/* event_loop() monitors all I/O channels for which the application has
+/* expressed interest, and monitors the timer request queue.
+/* It notifies the application whenever events of interest happen.
+/* A negative delay value causes the function to pause until something
+/* happens; a positive delay value causes event_loop() to return when
+/* the next event happens or when the delay time in seconds is over,
+/* whatever happens first. A zero delay effectuates a poll.
+/*
+/* Note: in order to avoid race conditions, event_loop() cannot
+/* not be called recursively.
+/*
+/* event_request_timer() causes the specified callback function to
+/* be called with the specified context argument after \fIdelay\fR
+/* seconds, or as soon as possible thereafter. The delay should
+/* not be negative (the manifest EVENT_NULL_DELAY provides for
+/* convenient zero-delay notification).
+/* The event argument is equal to EVENT_TIME.
+/* Only one timer request can be active per (callback, context) pair.
+/* Calling event_request_timer() with an existing (callback, context)
+/* pair does not schedule a new event, but updates the time of event
+/* delivery. The result is the absolute time at which the timer is
+/* scheduled to go off.
+/*
+/* event_cancel_timer() cancels the specified (callback, context) request.
+/* The application is allowed to cancel non-existing requests. The result
+/* value is the amount of time left before the timer would have gone off,
+/* or -1 in case of no pending timer.
+/*
+/* event_enable_read() (event_enable_write()) enables read (write) events
+/* on the named I/O channel. It is up to the application to assemble
+/* partial reads or writes.
+/* An I/O channel cannot handle more than one request at the
+/* same time. The application is allowed to enable an event that
+/* is already enabled (same channel, same read or write operation,
+/* but perhaps a different callback or context). On systems with
+/* kernel-based event filters this is preferred usage, because
+/* each disable and enable request would cost a system call.
+/*
+/* The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE
+/* provide convenient null values.
+/*
+/* The callback routine has the following arguments:
+/* .IP fd
+/* The stream on which the event happened.
+/* .IP event
+/* An indication of the event type:
+/* .RS
+/* .IP EVENT_READ
+/* read event,
+/* .IP EVENT_WRITE
+/* write event,
+/* .IP EVENT_XCPT
+/* exception (actually, any event other than read or write).
+/* .RE
+/* .IP context
+/* Application context given to event_enable_read() (event_enable_write()).
+/* .PP
+/* event_disable_readwrite() disables further I/O events on the specified
+/* I/O channel. The application is allowed to cancel non-existing
+/* I/O event requests.
+/*
+/* event_drain() repeatedly calls event_loop() until no more timer
+/* events or I/O events are pending or until the time limit is reached.
+/* This routine must not be called from an event_whatever() callback
+/* routine. Note: this function assumes that no new I/O events
+/* will be registered.
+/*
+/* event_fork() must be called by a child process after it is
+/* created with fork(), to re-initialize event processing.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory,
+/* system call failure. Warnings: the number of available
+/* file descriptors is much less than FD_SETSIZE.
+/* BUGS
+/* This module is based on event selection. It assumes that the
+/* event_loop() routine is called frequently. This approach is
+/* not suitable for applications with compute-bound loops that
+/* take a significant amount of time.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+#include <sys/time.h> /* XXX: 44BSD uses bzero() */
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stddef.h> /* offsetof() */
+#include <string.h> /* bzero() prototype for 44BSD */
+#include <limits.h> /* INT_MAX */
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "iostuff.h"
+#include "ring.h"
+#include "events.h"
+
+#if !defined(EVENTS_STYLE)
+#error "must define EVENTS_STYLE"
+#endif
+
+ /*
+ * Traditional BSD-style select(2). Works everywhere, but has a built-in
+ * upper bound on the number of file descriptors, and that limit is hard to
+ * change on Linux. Is sometimes emulated with SYSV-style poll(2) which
+ * doesn't have the file descriptor limit, but unfortunately does not help
+ * to improve the performance of servers with lots of connections.
+ */
+#define EVENT_ALLOC_INCR 10
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+typedef fd_set EVENT_MASK;
+
+#define EVENT_MASK_BYTE_COUNT(mask) sizeof(*(mask))
+#define EVENT_MASK_ZERO(mask) FD_ZERO(mask)
+#define EVENT_MASK_SET(fd, mask) FD_SET((fd), (mask))
+#define EVENT_MASK_ISSET(fd, mask) FD_ISSET((fd), (mask))
+#define EVENT_MASK_CLR(fd, mask) FD_CLR((fd), (mask))
+#define EVENT_MASK_CMP(m1, m2) memcmp((m1), (m2), EVENT_MASK_BYTE_COUNT(m1))
+#else
+
+ /*
+ * Kernel-based event filters (kqueue, /dev/poll, epoll). We use the
+ * following file descriptor mask structure which is expanded on the fly.
+ */
+typedef struct {
+ char *data; /* bit mask */
+ size_t data_len; /* data byte count */
+} EVENT_MASK;
+
+ /* Bits per byte, byte in vector, bit offset in byte, bytes per set. */
+#define EVENT_MASK_NBBY (8)
+#define EVENT_MASK_FD_BYTE(fd, mask) \
+ (((unsigned char *) (mask)->data)[(fd) / EVENT_MASK_NBBY])
+#define EVENT_MASK_FD_BIT(fd) (1 << ((fd) % EVENT_MASK_NBBY))
+#define EVENT_MASK_BYTES_NEEDED(len) \
+ (((len) + (EVENT_MASK_NBBY -1)) / EVENT_MASK_NBBY)
+#define EVENT_MASK_BYTE_COUNT(mask) ((mask)->data_len)
+
+ /* Memory management. */
+#define EVENT_MASK_ALLOC(mask, bit_len) do { \
+ size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+ (mask)->data = mymalloc(_byte_len); \
+ memset((mask)->data, 0, _byte_len); \
+ (mask)->data_len = _byte_len; \
+ } while (0)
+#define EVENT_MASK_REALLOC(mask, bit_len) do { \
+ size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+ size_t _old_len = (mask)->data_len; \
+ (mask)->data = myrealloc((mask)->data, _byte_len); \
+ if (_byte_len > _old_len) \
+ memset((mask)->data + _old_len, 0, _byte_len - _old_len); \
+ (mask)->data_len = _byte_len; \
+ } while (0)
+#define EVENT_MASK_FREE(mask) myfree((mask)->data)
+
+ /* Set operations, modeled after FD_ZERO/SET/ISSET/CLR. */
+#define EVENT_MASK_ZERO(mask) \
+ memset((mask)->data, 0, (mask)->data_len)
+#define EVENT_MASK_SET(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) |= EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_ISSET(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CLR(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CMP(m1, m2) \
+ memcmp((m1)->data, (m2)->data, EVENT_MASK_BYTE_COUNT(m1))
+#endif
+
+ /*
+ * I/O events.
+ */
+typedef struct EVENT_FDTABLE EVENT_FDTABLE;
+
+struct EVENT_FDTABLE {
+ EVENT_NOTIFY_RDWR_FN callback;
+ char *context;
+};
+static EVENT_MASK event_rmask; /* enabled read events */
+static EVENT_MASK event_wmask; /* enabled write events */
+static EVENT_MASK event_xmask; /* for bad news mostly */
+static int event_fdlimit; /* per-process open file limit */
+static EVENT_FDTABLE *event_fdtable; /* one slot per file descriptor */
+static int event_fdslots; /* number of file descriptor slots */
+static int event_max_fd = -1; /* highest fd number seen */
+
+ /*
+ * FreeBSD kqueue supports no system call to find out what descriptors are
+ * registered in the kernel-based filter. To implement our own sanity checks
+ * we maintain our own descriptor bitmask.
+ *
+ * FreeBSD kqueue does support application context pointers. Unfortunately,
+ * changing that information would cost a system call, and some of the
+ * competitors don't support application context. To keep the implementation
+ * simple we maintain our own table with call-back information.
+ *
+ * FreeBSD kqueue silently unregisters a descriptor from its filter when the
+ * descriptor is closed, so our information could get out of sync with the
+ * kernel. But that will never happen, because we have to meticulously
+ * unregister a file descriptor before it is closed, to avoid errors on
+ * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_KQUEUE)
+#include <sys/event.h>
+
+ /*
+ * Some early FreeBSD implementations don't have the EV_SET macro.
+ */
+#ifndef EV_SET
+#define EV_SET(kp, id, fi, fl, ffl, da, ud) do { \
+ (kp)->ident = (id); \
+ (kp)->filter = (fi); \
+ (kp)->flags = (fl); \
+ (kp)->fflags = (ffl); \
+ (kp)->data = (da); \
+ (kp)->udata = (ud); \
+ } while(0)
+#endif
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_kq; /* handle to event filter */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_kq = kqueue(); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "kqueue"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_kq); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+ struct kevent dummy; \
+ EV_SET(&dummy, (fh), (ev), (op), 0, 0, 0); \
+ (er) = kevent(event_kq, &dummy, 1, 0, 0, 0); \
+ } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_ADD)
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_ADD_TEXT "kevent EV_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_DELETE)
+#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_DEL_TEXT "kevent EV_DELETE"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct kevent EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ struct timespec ts; \
+ struct timespec *tsp; \
+ if ((delay) < 0) { \
+ tsp = 0; \
+ } else { \
+ tsp = &ts; \
+ ts.tv_nsec = 0; \
+ ts.tv_sec = (delay); \
+ } \
+ (event_count) = kevent(event_kq, (struct kevent *) 0, 0, (event_buf), \
+ (buflen), (tsp)); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "kevent"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->ident)
+#define EVENT_GET_TYPE(bp) ((bp)->filter)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) == EVFILT_READ)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) == EVFILT_WRITE)
+
+#endif
+
+ /*
+ * Solaris /dev/poll does not support application context, so we have to
+ * maintain our own. This has the benefit of avoiding an expensive system
+ * call just to change a call-back function or argument.
+ *
+ * Solaris /dev/poll does have a way to query if a specific descriptor is
+ * registered. However, we maintain a descriptor mask anyway because a) it
+ * avoids having to make an expensive system call to find out if something
+ * is registered, b) some EVENTS_STYLE_MUMBLE implementations need a
+ * descriptor bitmask anyway and c) we use the bitmask already to implement
+ * sanity checks.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_DEVPOLL)
+#include <sys/devpoll.h>
+#include <fcntl.h>
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_pollfd; /* handle to file descriptor set */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_pollfd = open("/dev/poll", O_RDWR); \
+ if (event_pollfd >= 0) close_on_exec(event_pollfd, CLOSE_ON_EXEC); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "open /dev/poll"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_pollfd); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev) do { \
+ struct pollfd dummy; \
+ dummy.fd = (fh); \
+ dummy.events = (ev); \
+ (er) = write(event_pollfd, (void *) &dummy, \
+ sizeof(dummy)) != sizeof(dummy) ? -1 : 0; \
+ } while (0)
+
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_FD_OP((e), (f), POLLIN)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_FD_OP((e), (f), POLLOUT)
+#define EVENT_REG_ADD_TEXT "write /dev/poll"
+
+#define EVENT_REG_DEL_BOTH(e, f) EVENT_REG_FD_OP((e), (f), POLLREMOVE)
+#define EVENT_REG_DEL_TEXT "write /dev/poll"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct pollfd EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ struct dvpoll dvpoll; \
+ dvpoll.dp_fds = (event_buf); \
+ dvpoll.dp_nfds = (buflen); \
+ dvpoll.dp_timeout = (delay) < 0 ? -1 : (delay) * 1000; \
+ (event_count) = ioctl(event_pollfd, DP_POLL, &dvpoll); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "ioctl DP_POLL"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->fd)
+#define EVENT_GET_TYPE(bp) ((bp)->revents)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & POLLIN)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & POLLOUT)
+
+#endif
+
+ /*
+ * Linux epoll supports no system call to find out what descriptors are
+ * registered in the kernel-based filter. To implement our own sanity checks
+ * we maintain our own descriptor bitmask.
+ *
+ * Linux epoll does support application context pointers. Unfortunately,
+ * changing that information would cost a system call, and some of the
+ * competitors don't support application context. To keep the implementation
+ * simple we maintain our own table with call-back information.
+ *
+ * Linux epoll silently unregisters a descriptor from its filter when the
+ * descriptor is closed, so our information could get out of sync with the
+ * kernel. But that will never happen, because we have to meticulously
+ * unregister a file descriptor before it is closed, to avoid errors on
+ * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_EPOLL)
+#include <sys/epoll.h>
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_epollfd; /* epoll handle */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_epollfd = epoll_create(n); \
+ if (event_epollfd >= 0) close_on_exec(event_epollfd, CLOSE_ON_EXEC); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "epoll_create"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_epollfd); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+ struct epoll_event dummy; \
+ dummy.events = (ev); \
+ dummy.data.fd = (fh); \
+ (er) = epoll_ctl(event_epollfd, (op), (fh), &dummy); \
+ } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_ADD)
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLIN)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_ADD_TEXT "epoll_ctl EPOLL_CTL_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_DEL)
+#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLIN)
+#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_DEL_TEXT "epoll_ctl EPOLL_CTL_DEL"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct epoll_event EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ (event_count) = epoll_wait(event_epollfd, (event_buf), (buflen), \
+ (delay) < 0 ? -1 : (delay) * 1000); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "epoll_wait"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->data.fd)
+#define EVENT_GET_TYPE(bp) ((bp)->events)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & EPOLLIN)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & EPOLLOUT)
+
+#endif
+
+ /*
+ * Timer events. Timer requests are kept sorted, in a circular list. We use
+ * the RING abstraction, so we get to use a couple ugly macros.
+ *
+ * When a call-back function adds a timer request, we label the request with
+ * the event_loop() call instance that invoked the call-back. We use this to
+ * prevent zero-delay timer requests from running in a tight loop and
+ * starving I/O events.
+ */
+typedef struct EVENT_TIMER EVENT_TIMER;
+
+struct EVENT_TIMER {
+ time_t when; /* when event is wanted */
+ EVENT_NOTIFY_TIME_FN callback; /* callback function */
+ char *context; /* callback context */
+ long loop_instance; /* event_loop() call instance */
+ RING ring; /* linkage */
+};
+
+static RING event_timer_head; /* timer queue head */
+static long event_loop_instance; /* event_loop() call instance */
+
+#define RING_TO_TIMER(r) \
+ ((EVENT_TIMER *) ((void *) (r) - offsetof(EVENT_TIMER, ring)))
+
+#define FOREACH_QUEUE_ENTRY(entry, head) \
+ for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry))
+
+#define FIRST_TIMER(head) \
+ (ring_succ(head) != (head) ? RING_TO_TIMER(ring_succ(head)) : 0)
+
+ /*
+ * Other private data structures.
+ */
+static time_t event_present; /* cached time of day */
+
+#define EVENT_INIT_NEEDED() (event_present == 0)
+
+/* event_init - set up tables and such */
+
+static void event_init(void)
+{
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (!EVENT_INIT_NEEDED())
+ msg_panic("event_init: repeated call");
+
+ /*
+ * Initialize the file descriptor masks and the call-back table. Where
+ * possible we extend these data structures on the fly. With select(2)
+ * based implementations we can only handle FD_SETSIZE open files.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if ((event_fdlimit = open_limit(FD_SETSIZE)) < 0)
+ msg_fatal("unable to determine open file limit");
+#else
+ if ((event_fdlimit = open_limit(INT_MAX)) < 0)
+ msg_fatal("unable to determine open file limit");
+#endif
+ if (event_fdlimit < FD_SETSIZE / 2 && event_fdlimit < 256)
+ msg_warn("could allocate space for only %d open files", event_fdlimit);
+ event_fdslots = EVENT_ALLOC_INCR;
+ event_fdtable = (EVENT_FDTABLE *)
+ mymalloc(sizeof(EVENT_FDTABLE) * event_fdslots);
+ for (fdp = event_fdtable; fdp < event_fdtable + event_fdslots; fdp++) {
+ fdp->callback = 0;
+ fdp->context = 0;
+ }
+
+ /*
+ * Initialize the I/O event request masks.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ EVENT_MASK_ZERO(&event_rmask);
+ EVENT_MASK_ZERO(&event_wmask);
+ EVENT_MASK_ZERO(&event_xmask);
+#else
+ EVENT_MASK_ALLOC(&event_rmask, event_fdslots);
+ EVENT_MASK_ALLOC(&event_wmask, event_fdslots);
+ EVENT_MASK_ALLOC(&event_xmask, event_fdslots);
+
+ /*
+ * Initialize the kernel-based filter.
+ */
+ EVENT_REG_INIT_HANDLE(err, event_fdslots);
+ if (err < 0)
+ msg_fatal("%s: %m", EVENT_REG_INIT_TEXT);
+#endif
+
+ /*
+ * Initialize timer stuff.
+ */
+ ring_init(&event_timer_head);
+ (void) time(&event_present);
+
+ /*
+ * Avoid an infinite initialization loop.
+ */
+ if (EVENT_INIT_NEEDED())
+ msg_panic("event_init: unable to initialize");
+}
+
+/* event_extend - make room for more descriptor slots */
+
+static void event_extend(int fd)
+{
+ const char *myname = "event_extend";
+ int old_slots = event_fdslots;
+ int new_slots = (event_fdslots > fd / 2 ?
+ 2 * old_slots : fd + EVENT_ALLOC_INCR);
+ EVENT_FDTABLE *fdp;
+
+#ifdef EVENT_REG_UPD_HANDLE
+ int err;
+
+#endif
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+ event_fdtable = (EVENT_FDTABLE *)
+ myrealloc((void *) event_fdtable, sizeof(EVENT_FDTABLE) * new_slots);
+ event_fdslots = new_slots;
+ for (fdp = event_fdtable + old_slots;
+ fdp < event_fdtable + new_slots; fdp++) {
+ fdp->callback = 0;
+ fdp->context = 0;
+ }
+
+ /*
+ * Initialize the I/O event request masks.
+ */
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_MASK_REALLOC(&event_rmask, new_slots);
+ EVENT_MASK_REALLOC(&event_wmask, new_slots);
+ EVENT_MASK_REALLOC(&event_xmask, new_slots);
+#endif
+#ifdef EVENT_REG_UPD_HANDLE
+ EVENT_REG_UPD_HANDLE(err, new_slots);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_UPD_TEXT);
+#endif
+}
+
+/* event_time - look up cached time of day */
+
+time_t event_time(void)
+{
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ return (event_present);
+}
+
+/* event_drain - loop until all pending events are done */
+
+void event_drain(int time_limit)
+{
+ EVENT_MASK zero_mask;
+ time_t max_time;
+
+ if (EVENT_INIT_NEEDED())
+ return;
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ EVENT_MASK_ZERO(&zero_mask);
+#else
+ EVENT_MASK_ALLOC(&zero_mask, event_fdslots);
+#endif
+ (void) time(&event_present);
+ max_time = event_present + time_limit;
+ while (event_present < max_time
+ && (event_timer_head.pred != &event_timer_head
+ || EVENT_MASK_CMP(&zero_mask, &event_xmask) != 0)) {
+ event_loop(1);
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ if (EVENT_MASK_BYTE_COUNT(&zero_mask)
+ != EVENT_MASK_BYTES_NEEDED(event_fdslots))
+ EVENT_MASK_REALLOC(&zero_mask, event_fdslots);
+#endif
+ }
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_MASK_FREE(&zero_mask);
+#endif
+}
+
+/* event_fork - resume event processing after fork() */
+
+void event_fork(void)
+{
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_FDTABLE *fdp;
+ int err;
+ int fd;
+
+ /*
+ * No event was ever registered, so there's nothing to be done.
+ */
+ if (EVENT_INIT_NEEDED())
+ return;
+
+ /*
+ * Close the existing filter handle and open a new kernel-based filter.
+ */
+ EVENT_REG_FORK_HANDLE(err, event_fdslots);
+ if (err < 0)
+ msg_fatal("%s: %m", EVENT_REG_INIT_TEXT);
+
+ /*
+ * Populate the new kernel-based filter with events that were registered
+ * in the parent process.
+ */
+ for (fd = 0; fd <= event_max_fd; fd++) {
+ if (EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_MASK_CLR(fd, &event_wmask);
+ fdp = event_fdtable + fd;
+ event_enable_write(fd, fdp->callback, fdp->context);
+ } else if (EVENT_MASK_ISSET(fd, &event_rmask)) {
+ EVENT_MASK_CLR(fd, &event_rmask);
+ fdp = event_fdtable + fd;
+ event_enable_read(fd, fdp->callback, fdp->context);
+ }
+ }
+#endif
+}
+
+/* event_enable_read - enable read events */
+
+void event_enable_read(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context)
+{
+ const char *myname = "event_enable_read";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ if (fd >= event_fdslots)
+ event_extend(fd);
+
+ /*
+ * Disallow mixed (i.e. read and write) requests on the same descriptor.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_wmask))
+ msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+ /*
+ * Postfix 2.4 allows multiple event_enable_read() calls on the same
+ * descriptor without requiring event_disable_readwrite() calls between
+ * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+ * wasteful to make system calls when we change only application
+ * call-back information. It has a noticeable effect on smtp-source
+ * performance.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_rmask) == 0) {
+ EVENT_MASK_SET(fd, &event_xmask);
+ EVENT_MASK_SET(fd, &event_rmask);
+ if (event_max_fd < fd)
+ event_max_fd = fd;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_REG_ADD_READ(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+ }
+ fdp = event_fdtable + fd;
+ if (fdp->callback != callback || fdp->context != context) {
+ fdp->callback = callback;
+ fdp->context = context;
+ }
+}
+
+/* event_enable_write - enable write events */
+
+void event_enable_write(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context)
+{
+ const char *myname = "event_enable_write";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ if (fd >= event_fdslots)
+ event_extend(fd);
+
+ /*
+ * Disallow mixed (i.e. read and write) requests on the same descriptor.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_rmask))
+ msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+ /*
+ * Postfix 2.4 allows multiple event_enable_write() calls on the same
+ * descriptor without requiring event_disable_readwrite() calls between
+ * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+ * incredibly wasteful to make unregister and register system calls when
+ * we change only application call-back information. It has a noticeable
+ * effect on smtp-source performance.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_wmask) == 0) {
+ EVENT_MASK_SET(fd, &event_xmask);
+ EVENT_MASK_SET(fd, &event_wmask);
+ if (event_max_fd < fd)
+ event_max_fd = fd;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_REG_ADD_WRITE(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+ }
+ fdp = event_fdtable + fd;
+ if (fdp->callback != callback || fdp->context != context) {
+ fdp->callback = callback;
+ fdp->context = context;
+ }
+}
+
+/* event_disable_readwrite - disable request for read or write events */
+
+void event_disable_readwrite(int fd)
+{
+ const char *myname = "event_disable_readwrite";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ /*
+ * Don't complain when there is nothing to cancel. The request may have
+ * been canceled from another thread.
+ */
+ if (fd >= event_fdslots)
+ return;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifdef EVENT_REG_DEL_BOTH
+ /* XXX Can't seem to disable READ and WRITE events selectively. */
+ if (EVENT_MASK_ISSET(fd, &event_rmask)
+ || EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_REG_DEL_BOTH(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ }
+#else
+ if (EVENT_MASK_ISSET(fd, &event_rmask)) {
+ EVENT_REG_DEL_READ(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ } else if (EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_REG_DEL_WRITE(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ }
+#endif /* EVENT_REG_DEL_BOTH */
+#endif /* != EVENTS_STYLE_SELECT */
+ EVENT_MASK_CLR(fd, &event_xmask);
+ EVENT_MASK_CLR(fd, &event_rmask);
+ EVENT_MASK_CLR(fd, &event_wmask);
+ fdp = event_fdtable + fd;
+ fdp->callback = 0;
+ fdp->context = 0;
+}
+
+/* event_request_timer - (re)set timer */
+
+time_t event_request_timer(EVENT_NOTIFY_TIME_FN callback, void *context, int delay)
+{
+ const char *myname = "event_request_timer";
+ RING *ring;
+ EVENT_TIMER *timer;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (delay < 0)
+ msg_panic("%s: invalid delay: %d", myname, delay);
+
+ /*
+ * Make sure we schedule this event at the right time.
+ */
+ time(&event_present);
+
+ /*
+ * See if they are resetting an existing timer request. If so, take the
+ * request away from the timer queue so that it can be inserted at the
+ * right place.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ if (timer->callback == callback && timer->context == context) {
+ timer->when = event_present + delay;
+ timer->loop_instance = event_loop_instance;
+ ring_detach(ring);
+ if (msg_verbose > 2)
+ msg_info("%s: reset 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, delay);
+ break;
+ }
+ }
+
+ /*
+ * If not found, schedule a new timer request.
+ */
+ if (ring == &event_timer_head) {
+ timer = (EVENT_TIMER *) mymalloc(sizeof(EVENT_TIMER));
+ timer->when = event_present + delay;
+ timer->callback = callback;
+ timer->context = context;
+ timer->loop_instance = event_loop_instance;
+ if (msg_verbose > 2)
+ msg_info("%s: set 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, delay);
+ }
+
+ /*
+ * Timer requests are kept sorted to reduce lookup overhead in the event
+ * loop.
+ *
+ * XXX Append the new request after existing requests for the same time
+ * slot. The event_loop() routine depends on this to avoid starving I/O
+ * events when a call-back function schedules a zero-delay timer request.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ if (timer->when < RING_TO_TIMER(ring)->when)
+ break;
+ }
+ ring_prepend(ring, &timer->ring);
+
+ return (timer->when);
+}
+
+/* event_cancel_timer - cancel timer */
+
+int event_cancel_timer(EVENT_NOTIFY_TIME_FN callback, void *context)
+{
+ const char *myname = "event_cancel_timer";
+ RING *ring;
+ EVENT_TIMER *timer;
+ int time_left = -1;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * See if they are canceling an existing timer request. Do not complain
+ * when the request is not found. It might have been canceled from some
+ * other thread.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ if (timer->callback == callback && timer->context == context) {
+ if ((time_left = timer->when - event_present) < 0)
+ time_left = 0;
+ ring_detach(ring);
+ myfree((void *) timer);
+ break;
+ }
+ }
+ if (msg_verbose > 2)
+ msg_info("%s: 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, time_left);
+ return (time_left);
+}
+
+/* event_loop - wait for the next event */
+
+void event_loop(int delay)
+{
+ const char *myname = "event_loop";
+ static int nested;
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ fd_set rmask;
+ fd_set wmask;
+ fd_set xmask;
+ struct timeval tv;
+ struct timeval *tvp;
+ int new_max_fd;
+
+#else
+ EVENT_BUFFER event_buf[100];
+ EVENT_BUFFER *bp;
+
+#endif
+ int event_count;
+ EVENT_TIMER *timer;
+ int fd;
+ EVENT_FDTABLE *fdp;
+ int select_delay;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * XXX Also print the select() masks?
+ */
+ if (msg_verbose > 2) {
+ RING *ring;
+
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ msg_info("%s: time left %3d for 0x%lx 0x%lx", myname,
+ (int) (timer->when - event_present),
+ (long) timer->callback, (long) timer->context);
+ }
+ }
+
+ /*
+ * Find out when the next timer would go off. Timer requests are sorted.
+ * If any timer is scheduled, adjust the delay appropriately.
+ */
+ if ((timer = FIRST_TIMER(&event_timer_head)) != 0) {
+ event_present = time((time_t *) 0);
+ if ((select_delay = timer->when - event_present) < 0) {
+ select_delay = 0;
+ } else if (delay >= 0 && select_delay > delay) {
+ select_delay = delay;
+ }
+ } else {
+ select_delay = delay;
+ }
+ if (msg_verbose > 2)
+ msg_info("event_loop: select_delay %d", select_delay);
+
+ /*
+ * Negative delay means: wait until something happens. Zero delay means:
+ * poll. Positive delay means: wait at most this long.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if (select_delay < 0) {
+ tvp = 0;
+ } else {
+ tvp = &tv;
+ tv.tv_usec = 0;
+ tv.tv_sec = select_delay;
+ }
+
+ /*
+ * Pause until the next event happens. When select() has a problem, don't
+ * go into a tight loop. Allow select() to be interrupted due to the
+ * arrival of a signal.
+ */
+ rmask = event_rmask;
+ wmask = event_wmask;
+ xmask = event_xmask;
+
+ event_count = select(event_max_fd + 1, &rmask, &wmask, &xmask, tvp);
+ if (event_count < 0) {
+ if (errno != EINTR)
+ msg_fatal("event_loop: select: %m");
+ return;
+ }
+#else
+ EVENT_BUFFER_READ(event_count, event_buf,
+ sizeof(event_buf) / sizeof(event_buf[0]),
+ select_delay);
+ if (event_count < 0) {
+ if (errno != EINTR)
+ msg_fatal("event_loop: " EVENT_BUFFER_READ_TEXT ": %m");
+ return;
+ }
+#endif
+
+ /*
+ * Before entering the application call-back routines, make sure we
+ * aren't being called from a call-back routine. Doing so would make us
+ * vulnerable to all kinds of race conditions.
+ */
+ if (nested++ > 0)
+ msg_panic("event_loop: recursive call");
+
+ /*
+ * Deliver timer events. Allow the application to add/delete timer queue
+ * requests while it is being called back. Requests are sorted: we keep
+ * running over the timer request queue from the start, and stop when we
+ * reach the future or the list end. We also stop when we reach a timer
+ * request that was added by a call-back that was invoked from this
+ * event_loop() call instance, for reasons that are explained below.
+ *
+ * To avoid dangling pointer problems 1) we must remove a request from the
+ * timer queue before delivering its event to the application and 2) we
+ * must look up the next timer request *after* calling the application.
+ * The latter complicates the handling of zero-delay timer requests that
+ * are added by event_loop() call-back functions.
+ *
+ * XXX When a timer event call-back function adds a new timer request,
+ * event_request_timer() labels the request with the event_loop() call
+ * instance that invoked the timer event call-back. We use this instance
+ * label here to prevent zero-delay timer requests from running in a
+ * tight loop and starving I/O events. To make this solution work,
+ * event_request_timer() appends a new request after existing requests
+ * for the same time slot.
+ */
+ event_present = time((time_t *) 0);
+ event_loop_instance += 1;
+
+ while ((timer = FIRST_TIMER(&event_timer_head)) != 0) {
+ if (timer->when > event_present)
+ break;
+ if (timer->loop_instance == event_loop_instance)
+ break;
+ ring_detach(&timer->ring); /* first this */
+ if (msg_verbose > 2)
+ msg_info("%s: timer 0x%lx 0x%lx", myname,
+ (long) timer->callback, (long) timer->context);
+ timer->callback(EVENT_TIME, timer->context); /* then this */
+ myfree((void *) timer);
+ }
+
+ /*
+ * Deliver I/O events. Allow the application to cancel event requests
+ * while it is being called back. To this end, we keep an eye on the
+ * contents of event_xmask, so that we deliver only events that are still
+ * wanted. We do not change the event request masks. It is up to the
+ * application to determine when a read or write is complete.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if (event_count > 0) {
+ for (new_max_fd = 0, fd = 0; fd <= event_max_fd; fd++) {
+ if (FD_ISSET(fd, &event_xmask)) {
+ new_max_fd = fd;
+ /* In case event_fdtable is updated. */
+ fdp = event_fdtable + fd;
+ if (FD_ISSET(fd, &xmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: exception fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_XCPT, fdp->context);
+ } else if (FD_ISSET(fd, &wmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_WRITE, fdp->context);
+ } else if (FD_ISSET(fd, &rmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_READ, fdp->context);
+ }
+ }
+ }
+ event_max_fd = new_max_fd;
+ }
+#else
+ for (bp = event_buf; bp < event_buf + event_count; bp++) {
+ fd = EVENT_GET_FD(bp);
+ if (fd < 0 || fd > event_max_fd)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+ if (EVENT_MASK_ISSET(fd, &event_xmask)) {
+ fdp = event_fdtable + fd;
+ if (EVENT_TEST_READ(bp)) {
+ if (msg_verbose > 2)
+ msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_READ, fdp->context);
+ } else if (EVENT_TEST_WRITE(bp)) {
+ if (msg_verbose > 2)
+ msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback,
+ (long) fdp->context);
+ fdp->callback(EVENT_WRITE, fdp->context);
+ } else {
+ if (msg_verbose > 2)
+ msg_info("%s: other fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_XCPT, fdp->context);
+ }
+ }
+ }
+#endif
+ nested--;
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program for the event manager. Schedule a series of
+ * events at one-second intervals and let them happen, while echoing any
+ * lines read from stdin.
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+/* timer_event - display event */
+
+static void timer_event(int unused_event, void *context)
+{
+ printf("%ld: %s\n", (long) event_present, context);
+ fflush(stdout);
+}
+
+/* echo - echo text received on stdin */
+
+static void echo(int unused_event, void *unused_context)
+{
+ char buf[BUFSIZ];
+
+ if (fgets(buf, sizeof(buf), stdin) == 0)
+ exit(0);
+ printf("Result: %s", buf);
+}
+
+/* request - request a bunch of timer events */
+
+static void request(int unused_event, void *unused_context)
+{
+ event_request_timer(timer_event, "3 first", 3);
+ event_request_timer(timer_event, "3 second", 3);
+ event_request_timer(timer_event, "4 first", 4);
+ event_request_timer(timer_event, "4 second", 4);
+ event_request_timer(timer_event, "2 first", 2);
+ event_request_timer(timer_event, "2 second", 2);
+ event_request_timer(timer_event, "1 first", 1);
+ event_request_timer(timer_event, "1 second", 1);
+ event_request_timer(timer_event, "0 first", 0);
+ event_request_timer(timer_event, "0 second", 0);
+}
+
+int main(int argc, void **argv)
+{
+ if (argv[1])
+ msg_verbose = atoi(argv[1]);
+ event_request_timer(request, (void *) 0, 0);
+ event_enable_read(fileno(stdin), echo, (void *) 0);
+ event_drain(10);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/events.h b/src/util/events.h
new file mode 100644
index 0000000..b7f2047
--- /dev/null
+++ b/src/util/events.h
@@ -0,0 +1,67 @@
+#ifndef _EVENTS_H_INCLUDED_
+#define _EVENTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* events 3h
+/* SUMMARY
+/* event manager
+/* SYNOPSIS
+/* #include <events.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * External interface.
+ */
+typedef void (*EVENT_NOTIFY_FN) (int, void *);
+
+#define EVENT_NOTIFY_TIME_FN EVENT_NOTIFY_FN /* legacy */
+#define EVENT_NOTIFY_RDWR_FN EVENT_NOTIFY_FN /* legacy */
+
+extern time_t event_time(void);
+extern void event_enable_read(int, EVENT_NOTIFY_RDWR_FN, void *);
+extern void event_enable_write(int, EVENT_NOTIFY_RDWR_FN, void *);
+extern void event_disable_readwrite(int);
+extern time_t event_request_timer(EVENT_NOTIFY_TIME_FN, void *, int);
+extern int event_cancel_timer(EVENT_NOTIFY_TIME_FN, void *);
+extern void event_loop(int);
+extern void event_drain(int);
+extern void event_fork(void);
+
+ /*
+ * Event codes.
+ */
+#define EVENT_READ (1<<0) /* read event */
+#define EVENT_WRITE (1<<1) /* write event */
+#define EVENT_XCPT (1<<2) /* exception */
+#define EVENT_TIME (1<<3) /* timer event */
+
+#define EVENT_ERROR EVENT_XCPT
+
+ /*
+ * Dummies.
+ */
+#define EVENT_NULL_TYPE (0)
+#define EVENT_NULL_CONTEXT ((void *) 0)
+#define EVENT_NULL_DELAY (0)
+
+/* 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
+/* CREATION DATE
+/* Wed Jan 29 17:00:03 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/exec_command.c b/src/util/exec_command.c
new file mode 100644
index 0000000..8629b0c
--- /dev/null
+++ b/src/util/exec_command.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* exec_command 3
+/* SUMMARY
+/* execute command
+/* SYNOPSIS
+/* #include <exec_command.h>
+/*
+/* NORETURN exec_command(command)
+/* const char *command;
+/* DESCRIPTION
+/* \fIexec_command\fR() replaces the current process by an instance
+/* of \fIcommand\fR. This routine uses a simple heuristic to avoid
+/* the overhead of running a command shell interpreter.
+/* DIAGNOSTICS
+/* This routine never returns. All errors are fatal.
+/* 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 <string.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <exec_command.h>
+
+/* Application-specific. */
+
+#define SPACE_TAB " \t"
+
+/* exec_command - exec command */
+
+NORETURN exec_command(const char *command)
+{
+ ARGV *argv;
+
+ /*
+ * Character filter. In this particular case, we allow space and tab in
+ * addition to the regular character set.
+ */
+ static char ok_chars[] = "1234567890!@%-_=+:,./\
+abcdefghijklmnopqrstuvwxyz\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ" SPACE_TAB;
+
+ /*
+ * See if this command contains any shell magic characters.
+ */
+ if (command[strspn(command, ok_chars)] == 0
+ && command[strspn(command, SPACE_TAB)] != 0) {
+
+ /*
+ * No shell meta characters found, so we can try to avoid the overhead
+ * of running a shell. Just split the command on whitespace and exec
+ * the result directly.
+ */
+ argv = argv_split(command, SPACE_TAB);
+ (void) execvp(argv->argv[0], argv->argv);
+
+ /*
+ * Auch. Perhaps they're using some shell built-in command.
+ */
+ if (errno != ENOENT || strchr(argv->argv[0], '/') != 0)
+ msg_fatal("execvp %s: %m", argv->argv[0]);
+
+ /*
+ * Not really necessary, but...
+ */
+ argv_free(argv);
+ }
+
+ /*
+ * Pass the command to a shell.
+ */
+ (void) execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0);
+ msg_fatal("execl %s: %m", _PATH_BSHELL);
+}
+
+#ifdef TEST
+
+ /*
+ * Yet another proof-of-concept test program.
+ */
+#include <vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 2)
+ msg_fatal("usage: %s 'command'", argv[0]);
+ exec_command(argv[1]);
+}
+
+#endif
diff --git a/src/util/exec_command.h b/src/util/exec_command.h
new file mode 100644
index 0000000..4e77211
--- /dev/null
+++ b/src/util/exec_command.h
@@ -0,0 +1,30 @@
+#ifndef _EXEC_COMMAND_H_INCLUDED_
+#define _EXEC_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* exec_command 3h
+/* SUMMARY
+/* execute command
+/* SYNOPSIS
+/* #include <exec_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern NORETURN exec_command(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/extpar.c b/src/util/extpar.c
new file mode 100644
index 0000000..0b106ba
--- /dev/null
+++ b/src/util/extpar.c
@@ -0,0 +1,109 @@
+/*++
+/* NAME
+/* extpar 3
+/* SUMMARY
+/* extract text from parentheses
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *extpar(bp, parens, flags)
+/* char **bp;
+/* const char *parens;
+/* int flags;
+/* DESCRIPTION
+/* extpar() extracts text from an input string that is enclosed
+/* in the specified parentheses, and updates the buffer pointer
+/* to point to that text.
+/*
+/* Arguments:
+/* .IP bp
+/* Pointer to buffer pointer. Both the buffer and the buffer
+/* pointer are modified.
+/* .IP parens
+/* One matching pair of parentheses, opening parenthesis first.
+/* .IP flags
+/* EXTPAR_FLAG_NONE, or the bitwise OR of one or more flags:
+/* .RS
+/* .IP EXTPAR_FLAG_EXTRACT
+/* This flag is intended to instruct extpar() callers that
+/* extpar() should be invoked. It has no effect on expar()
+/* itself.
+/* .IP EXTPAR_FLAG_STRIP
+/* Skip whitespace after the opening parenthesis, and trim
+/* whitespace before the closing parenthesis.
+/* .RE
+/* DIAGNOSTICS
+/* In case of error the result value is a dynamically-allocated
+/* string with a description of the problem that includes a
+/* copy of the offending input. A non-null result value should
+/* be destroyed with myfree(). The following describes the errors
+/* and the state of the buffer and buffer pointer.
+/* .IP "no opening parenthesis at start of text"
+/* The buffer pointer points to the input text.
+/* .IP "missing closing parenthesis"
+/* The buffer pointer points to text as if a closing parenthesis
+/* were present at the end of the input.
+/* .IP "text after closing parenthesis"
+/* The buffer pointer points to text as if the offending text
+/* were not present.
+/* SEE ALSO
+/* balpar(3) determine length of string in parentheses
+/* 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>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <stringops.h>
+
+/* extpar - extract text from parentheses */
+
+char *extpar(char **bp, const char *parens, int flags)
+{
+ char *cp = *bp;
+ char *err = 0;
+ size_t len;
+
+ if (cp[0] != parens[0]) {
+ err = vstring_export(vstring_sprintf(vstring_alloc(100),
+ "no '%c' at start of text in \"%s\"", parens[0], cp));
+ len = 0;
+ } else if ((len = balpar(cp, parens)) == 0) {
+ err = concatenate("missing '", parens + 1, "' in \"",
+ cp, "\"", (char *) 0);
+ cp += 1;
+ } else {
+ if (cp[len] != 0)
+ err = concatenate("syntax error after '", parens + 1, "' in \"",
+ cp, "\"", (char *) 0);
+ cp += 1;
+ cp[len -= 2] = 0;
+ }
+ if (flags & EXTPAR_FLAG_STRIP) {
+ trimblanks(cp, len)[0] = 0;
+ while (ISSPACE(*cp))
+ cp++;
+ }
+ *bp = cp;
+ return (err);
+}
diff --git a/src/util/fifo_listen.c b/src/util/fifo_listen.c
new file mode 100644
index 0000000..9ad3440
--- /dev/null
+++ b/src/util/fifo_listen.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* fifo_listen 3
+/* SUMMARY
+/* start fifo listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int fifo_listen(path, permissions, block_mode)
+/* const char *path;
+/* int permissions;
+/* int block_mode;
+/* DESCRIPTION
+/* The \fBfifo_listen\fR routine creates the specified named pipe with
+/* the specified permissions, opens the FIFO read-write or read-only,
+/* depending on the host operating system, and returns the resulting
+/* file descriptor.
+/* The \fIblock_mode\fR argument is either NON_BLOCKING for
+/* a non-blocking socket, or BLOCKING for blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: all system call failures.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "warn_stat.h"
+
+#define BUF_LEN 100
+
+/* fifo_listen - create fifo listener */
+
+int fifo_listen(const char *path, int permissions, int block_mode)
+{
+ char buf[BUF_LEN];
+ static int open_mode = 0;
+ const char *myname = "fifo_listen";
+ struct stat st;
+ int fd;
+ int count;
+
+ /*
+ * Create a named pipe (fifo). Do whatever we can so we don't run into
+ * trouble when this process is restarted after crash. Make sure that we
+ * open a fifo and not something else, then change permissions to what we
+ * wanted them to be, because mkfifo() is subject to umask settings.
+ * Instead we could zero the umask temporarily before creating the FIFO,
+ * but that would cost even more system calls. Figure out if the fifo
+ * needs to be opened O_RDWR or O_RDONLY. Some systems need one, some
+ * need the other. If we choose the wrong mode, the fifo will stay
+ * readable, causing the program to go into a loop.
+ */
+ if (unlink(path) && errno != ENOENT)
+ msg_fatal("%s: remove %s: %m", myname, path);
+ if (mkfifo(path, permissions) < 0)
+ msg_fatal("%s: create fifo %s: %m", myname, path);
+ switch (open_mode) {
+ case 0:
+ if ((fd = open(path, O_RDWR | O_NONBLOCK, 0)) < 0)
+ msg_fatal("%s: open %s: %m", myname, path);
+ if (readable(fd) == 0) {
+ open_mode = O_RDWR | O_NONBLOCK;
+ break;
+ } else {
+ open_mode = O_RDONLY | O_NONBLOCK;
+ if (msg_verbose)
+ msg_info("open O_RDWR makes fifo readable - trying O_RDONLY");
+ (void) close(fd);
+ /* FALLTRHOUGH */
+ }
+ default:
+ if ((fd = open(path, open_mode, 0)) < 0)
+ msg_fatal("%s: open %s: %m", myname, path);
+ break;
+ }
+
+ /*
+ * Make sure we opened a FIFO and skip any cruft that might have
+ * accumulated before we opened it.
+ */
+ if (fstat(fd, &st) < 0)
+ msg_fatal("%s: fstat %s: %m", myname, path);
+ if (S_ISFIFO(st.st_mode) == 0)
+ msg_fatal("%s: not a fifo: %s", myname, path);
+ if (fchmod(fd, permissions) < 0)
+ msg_fatal("%s: fchmod %s: %m", myname, path);
+ non_blocking(fd, block_mode);
+ while ((count = peekfd(fd)) > 0
+ && read(fd, buf, BUF_LEN < count ? BUF_LEN : count) > 0)
+ /* void */ ;
+ return (fd);
+}
diff --git a/src/util/fifo_open.c b/src/util/fifo_open.c
new file mode 100644
index 0000000..1587779
--- /dev/null
+++ b/src/util/fifo_open.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* fifo_open 1
+/* SUMMARY
+/* fifo client test program
+/* SYNOPSIS
+/* fifo_open
+/* DESCRIPTION
+/* fifo_open creates a FIFO, then attempts to open it for writing
+/* with non-blocking mode enabled. According to the POSIX standard
+/* the open should succeed.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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
+/*--*/
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define FIFO_PATH "test-fifo"
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+static void stuck(int unused_sig)
+{
+ printf("Non-blocking, write-only open of FIFO blocked\n");
+ cleanup();
+ exit(1);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ (void) unlink(FIFO_PATH);
+ printf("Creating fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+ signal(SIGALRM, stuck);
+ alarm(5);
+ printf("Opening fifo %s, non-blocking, write-only mode...\n", FIFO_PATH);
+ if (open(FIFO_PATH, O_WRONLY | O_NONBLOCK, 0) < 0) {
+ perror("open");
+ cleanup();
+ exit(1);
+ }
+ printf("Non-blocking, write-only open of FIFO succeeded\n");
+ cleanup();
+ exit(0);
+}
diff --git a/src/util/fifo_rdonly_bug.c b/src/util/fifo_rdonly_bug.c
new file mode 100644
index 0000000..bf93467
--- /dev/null
+++ b/src/util/fifo_rdonly_bug.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* fifo_rdonly_bug 1
+/* SUMMARY
+/* fifo server test program
+/* SYNOPSIS
+/* fifo_rdonly_bug
+/* DESCRIPTION
+/* fifo_rdonly_bug creates a FIFO and opens it read only. It
+/* then opens the FIFO for writing, writes one byte, and closes
+/* the writing end. On Linux Redhat 4.2 and 5.0, and HP-UX 9.05
+/* and 10.20, select() will report that the FIFO remains readable
+/* even after multiple read operations.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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
+/*--*/
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+
+#define FIFO_PATH "test-fifo"
+#define TRIGGER_DELAY 5
+
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+static void perrorcleanup(char *str)
+{
+ perror(str);
+ cleanup();
+ exit(0);
+}
+
+static void readable_event(int fd)
+{
+ char ch;
+ static int count = 0;
+
+ if (read(fd, &ch, 1) < 0) {
+ perror("read");
+ sleep(1);
+ }
+ if (count++ > 5) {
+ printf("FIFO remains readable after multiple reads.\n");
+ cleanup();
+ exit(1);
+ }
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ struct timeval tv;
+ fd_set read_fds;
+ fd_set except_fds;
+ int fd;
+ int fd2;
+
+ (void) unlink(FIFO_PATH);
+
+ printf("Create fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+
+ printf("Open fifo %s, read-only mode...\n", FIFO_PATH);
+ if ((fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK, 0)) < 0)
+ perrorcleanup("open");
+
+ printf("Write one byte to the fifo, then close it...\n");
+ if ((fd2 = open(FIFO_PATH, O_WRONLY, 0)) < 0)
+ perrorcleanup("open fifo O_WRONLY");
+ if (write(fd2, "", 1) < 1)
+ perrorcleanup("write one byte to fifo");
+ if (close(fd2) < 0)
+ perrorcleanup("close fifo");
+
+ printf("Selecting the fifo for readability...\n");
+
+ for (;;) {
+ FD_ZERO(&read_fds);
+ FD_SET(fd, &read_fds);
+ FD_ZERO(&except_fds);
+ FD_SET(fd, &except_fds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) {
+ case -1:
+ perrorexit("select");
+ default:
+ if (FD_ISSET(fd, &except_fds)) {
+ printf("Exceptional fifo condition! You are not normal!\n");
+ readable_event(fd);
+ } else if (FD_ISSET(fd, &read_fds)) {
+ printf("Readable fifo condition\n");
+ readable_event(fd);
+ }
+ break;
+ case 0:
+ printf("The fifo is not readable. You're normal.\n");
+ cleanup();
+ exit(0);
+ break;
+ }
+ }
+}
diff --git a/src/util/fifo_rdwr_bug.c b/src/util/fifo_rdwr_bug.c
new file mode 100644
index 0000000..2486876
--- /dev/null
+++ b/src/util/fifo_rdwr_bug.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* fifo_rdwr_bug 1
+/* SUMMARY
+/* fifo server test program
+/* SYNOPSIS
+/* fifo_rdwr_bug
+/* DESCRIPTION
+/* fifo_rdwr_bug creates a FIFO and opens it read-write mode.
+/* On BSD/OS 3.1 select() will report that the FIFO is readable
+/* even before any data is written to it. Doing an actual read
+/* causes the read to block; a non-blocking read fails.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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
+/*--*/
+
+#include <sys_defs.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define FIFO_PATH "test-fifo"
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ struct timeval tv;
+ fd_set read_fds;
+ fd_set except_fds;
+ int fd;
+
+ (void) unlink(FIFO_PATH);
+
+ printf("Creating fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+
+ printf("Opening fifo %s, read-write mode...\n", FIFO_PATH);
+ if ((fd = open(FIFO_PATH, O_RDWR, 0)) < 0) {
+ perror("open");
+ cleanup();
+ exit(1);
+ }
+ printf("Selecting the fifo for readability...\n");
+ FD_ZERO(&read_fds);
+ FD_SET(fd, &read_fds);
+ FD_ZERO(&except_fds);
+ FD_SET(fd, &except_fds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) {
+ case -1:
+ perrorexit("select");
+ default:
+ if (FD_ISSET(fd, &read_fds)) {
+ printf("Opening a fifo read-write makes it readable!!\n");
+ break;
+ }
+ case 0:
+ printf("The fifo is not readable, as it should be.\n");
+ break;
+ }
+ cleanup();
+ exit(0);
+}
diff --git a/src/util/fifo_trigger.c b/src/util/fifo_trigger.c
new file mode 100644
index 0000000..e5c5604
--- /dev/null
+++ b/src/util/fifo_trigger.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* fifo_trigger 3
+/* SUMMARY
+/* wakeup fifo server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int fifo_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* fifo_trigger() wakes up the named fifo server by writing
+/* the contents of the specified buffer to the fifo. There is
+/* no guarantee that the written data will actually be received.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero when the fifo could be opened, -1 otherwise.
+/* BUGS
+/* 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 <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <safe_open.h>
+#include <trigger.h>
+
+/* fifo_trigger - wakeup fifo server */
+
+int fifo_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ static VSTRING *why;
+ const char *myname = "fifo_trigger";
+ VSTREAM *fp;
+ int fd;
+
+ if (why == 0)
+ why = vstring_alloc(1);
+
+ /*
+ * Write the request to the service fifo. According to POSIX, the open
+ * shall always return immediately, and shall return an error when no
+ * process is reading from the FIFO.
+ *
+ * Use safe_open() so that we don't follow symlinks, and so that we don't
+ * open files with multiple hard links. We're not (yet) going to bother
+ * the caller with safe_open() specific quirks such as the why argument.
+ */
+ if ((fp = safe_open(service, O_WRONLY | O_NONBLOCK, 0,
+ (struct stat *) 0, -1, -1, why)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: open %s: %s", myname, service, vstring_str(why));
+ return (-1);
+ }
+ fd = vstream_fileno(fp);
+
+ /*
+ * Write the request...
+ */
+ non_blocking(fd, timeout > 0 ? NON_BLOCKING : BLOCKING);
+ if (write_buf(fd, buf, len, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write %s: %m", myname, service);
+
+ /*
+ * Disconnect.
+ */
+ if (vstream_fclose(fp))
+ if (msg_verbose)
+ msg_warn("%s: close %s: %m", myname, service);
+ return (0);
+}
+
+#ifdef TEST
+
+ /*
+ * Set up a FIFO listener, and keep triggering until the listener becomes
+ * idle, which should never happen.
+ */
+#include <signal.h>
+#include <stdlib.h>
+
+#include "events.h"
+#include "listen.h"
+
+#define TEST_FIFO "test-fifo"
+
+int trig_count;
+int wakeup_count;
+
+static void cleanup(void)
+{
+ unlink(TEST_FIFO);
+ exit(1);
+}
+
+static void handler(int sig)
+{
+ msg_fatal("got signal %d after %d triggers %d wakeups",
+ sig, trig_count, wakeup_count);
+}
+
+static void read_event(int unused_event, char *context)
+{
+ int fd = CAST_ANY_PTR_TO_INT(context);
+ char ch;
+
+ wakeup_count++;
+
+ if (read(fd, &ch, 1) != 1)
+ msg_fatal("read %s: %m", TEST_FIFO);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ int listen_fd;
+
+ listen_fd = fifo_listen(TEST_FIFO, 0600, NON_BLOCKING);
+ msg_cleanup(cleanup);
+ event_enable_read(listen_fd, read_event, CAST_INT_TO_VOID_PTR(listen_fd));
+ signal(SIGINT, handler);
+ signal(SIGALRM, handler);
+ for (;;) {
+ alarm(10);
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ event_loop(-1);
+ event_loop(-1);
+ event_loop(-1);
+ }
+}
+
+#endif
diff --git a/src/util/file_limit.c b/src/util/file_limit.c
new file mode 100644
index 0000000..5cfae4e
--- /dev/null
+++ b/src/util/file_limit.c
@@ -0,0 +1,96 @@
+/*++
+/* NAME
+/* file_limit 3
+/* SUMMARY
+/* limit the file size
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* off_t get_file_limit()
+/*
+/* void set_file_limit(limit)
+/* off_t limit;
+/* DESCRIPTION
+/* This module manipulates the process-wide file size limit.
+/* The limit is specified in bytes.
+/*
+/* get_file_limit() looks up the process-wide file size limit.
+/*
+/* set_file_limit() sets the process-wide file size limit to
+/* \fIlimit\fR.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* SEE ALSO
+/* setrlimit(2)
+/* ulimit(2)
+/* 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>
+#ifdef USE_ULIMIT
+#include <ulimit.h>
+#else
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <signal.h>
+#endif
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+#define ULIMIT_BLOCK_SIZE 512
+
+/* get_file_limit - get process-wide file size limit */
+
+off_t get_file_limit(void)
+{
+ off_t limit;
+
+#ifdef USE_ULIMIT
+ if ((limit = ulimit(UL_GETFSIZE, 0)) < 0)
+ msg_fatal("ulimit: %m");
+ if (limit > OFF_T_MAX / ULIMIT_BLOCK_SIZE)
+ limit = OFF_T_MAX / ULIMIT_BLOCK_SIZE;
+ return (limit * ULIMIT_BLOCK_SIZE);
+#else
+ struct rlimit rlim;
+
+ if (getrlimit(RLIMIT_FSIZE, &rlim) < 0)
+ msg_fatal("getrlimit: %m");
+ limit = rlim.rlim_cur;
+ return (limit < 0 ? OFF_T_MAX : rlim.rlim_cur);
+#endif /* USE_ULIMIT */
+}
+
+/* set_file_limit - process-wide file size limit */
+
+void set_file_limit(off_t limit)
+{
+#ifdef USE_ULIMIT
+ if (ulimit(UL_SETFSIZE, limit / ULIMIT_BLOCK_SIZE) < 0)
+ msg_fatal("ulimit: %m");
+#else
+ struct rlimit rlim;
+
+ rlim.rlim_cur = rlim.rlim_max = limit;
+ if (setrlimit(RLIMIT_FSIZE, &rlim) < 0)
+ msg_fatal("setrlimit: %m");
+#ifdef SIGXFSZ
+ if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR)
+ msg_fatal("signal(SIGXFSZ,SIG_IGN): %m");
+#endif
+#endif /* USE_ULIMIT */
+}
diff --git a/src/util/find_inet.c b/src/util/find_inet.c
new file mode 100644
index 0000000..294634c
--- /dev/null
+++ b/src/util/find_inet.c
@@ -0,0 +1,253 @@
+/*++
+/* NAME
+/* find_inet 3
+/* SUMMARY
+/* inet-domain name services
+/* SYNOPSIS
+/* #include <find_inet.h>
+/*
+/* unsigned find_inet_addr(host)
+/* const char *host;
+/*
+/* int find_inet_port(port, proto)
+/* const char *port;
+/* const char *proto;
+/* DESCRIPTION
+/* These functions translate network address information from
+/* between printable form to the internal the form used by the
+/* BSD TCP/IP network software.
+/*
+/* find_inet_addr() translates a symbolic or numerical hostname.
+/* This function is deprecated. Use hostname_to_hostaddr() instead.
+/*
+/* find_inet_port() translates a symbolic or numerical port name.
+/* BUGS
+/* find_inet_addr() ignores all but the first address listed for
+/* a symbolic hostname.
+/* DIAGNOSTICS
+/* Lookup and conversion errors are fatal.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "stringops.h"
+#include "find_inet.h"
+#include "known_tcp_ports.h"
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+#ifdef TEST
+extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...);
+
+#define msg_fatal test_msg_fatal
+#endif
+
+/* find_inet_addr - translate numerical or symbolic host name */
+
+unsigned find_inet_addr(const char *host)
+{
+ struct in_addr addr;
+ struct hostent *hp;
+
+ addr.s_addr = inet_addr(host);
+ if ((addr.s_addr == INADDR_NONE) || (addr.s_addr == 0)) {
+ if ((hp = gethostbyname(host)) == 0)
+ msg_fatal("host not found: %s", host);
+ if (hp->h_addrtype != AF_INET)
+ msg_fatal("unexpected address family: %d", hp->h_addrtype);
+ if (hp->h_length != sizeof(addr))
+ msg_fatal("unexpected address length %d", hp->h_length);
+ memcpy((void *) &addr, hp->h_addr, hp->h_length);
+ }
+ return (addr.s_addr);
+}
+
+/* find_inet_port - translate numerical or symbolic service name */
+
+int find_inet_port(const char *service, const char *protocol)
+{
+ struct servent *sp;
+ int port;
+
+ service = filter_known_tcp_port(service);
+ if (alldig(service) && (port = atoi(service)) != 0) {
+ if (port < 0 || port > 65535)
+ msg_fatal("bad port number: %s", service);
+ return (htons(port));
+ } else {
+ if ((sp = getservbyname(service, protocol)) == 0)
+ msg_fatal("unknown service: %s/%s", service, protocol);
+ return (sp->s_port);
+ }
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+ /* TODO(wietse) make this a proper VSTREAM interface */
+
+/* vstream_swap - kludge to capture output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+jmp_buf test_fatal_jbuf;
+
+#undef msg_fatal
+
+/* test_msg_fatal - does not return, and does not terminate */
+
+void test_msg_fatal(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ longjmp(test_fatal_jbuf, 1);
+}
+
+struct association {
+ const char *lhs; /* service name */
+ const char *rhs; /* service port */
+};
+
+struct test_case {
+ const char *label; /* identifies test case */
+ struct association associations[10];
+ const char *service;
+ const char *proto;
+ const char *exp_warning; /* expected error */
+ int exp_hport; /* expected port, host byte order */
+};
+
+struct test_case test_cases[] = {
+ {"good-symbolic",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "foobar",
+ /* proto */ "tcp",
+ /* exp_warning */ "",
+ /* exp_hport */ 25252,
+ },
+ {"good-numeric",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "25252",
+ /* proto */ "tcp",
+ /* exp_warning */ "",
+ /* exp_hport */ 25252,
+ },
+ {"bad-symbolic",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "an-impossible-name",
+ /* proto */ "tcp",
+ /* exp_warning */ "find_inet: warning: unknown service: an-impossible-name/tcp\n",
+ },
+ {"bad-numeric",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "123456",
+ /* proto */ "tcp",
+ /* exp_warning */ "find_inet: warning: bad port number: 123456\n",
+ },
+};
+
+int main(int argc, char **argv) {
+ struct test_case *tp;
+ struct association *ap;
+ int pass = 0;
+ int fail = 0;
+ const char *err;
+ int test_failed;
+ int nport;
+ VSTRING *msg_buf;
+ VSTREAM *memory_stream;
+
+ msg_vstream_init("find_inet", VSTREAM_ERR);
+ msg_buf = vstring_alloc(100);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_failed = 0;
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ clear_known_tcp_ports();
+ for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
+ err = add_known_tcp_port(ap->lhs, ap->rhs);
+ if (err != 0) {
+ msg_warn("test case %s: got err: \"%s\"", tp->label, err);
+ test_failed = 1;
+ } else {
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ if (setjmp(test_fatal_jbuf) == 0)
+ nport = find_inet_port(tp->service, tp->proto);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ if (vstream_fclose(memory_stream))
+ msg_fatal("close memory stream: %m");
+ if (strcmp(STR(msg_buf), tp->exp_warning) != 0) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ tp->label, STR(msg_buf), tp->exp_warning);
+ test_failed = 1;
+ } else if (tp->exp_warning[0] == 0) {
+ if (ntohs(nport) != tp->exp_hport) {
+ msg_warn("test case %s: got port \"%d\", want: \"%d\"",
+ tp->label, ntohs(nport), tp->exp_hport);
+ test_failed = 1;
+ }
+ }
+ }
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(msg_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/find_inet.h b/src/util/find_inet.h
new file mode 100644
index 0000000..0e5ce72
--- /dev/null
+++ b/src/util/find_inet.h
@@ -0,0 +1,33 @@
+#ifndef _FIND_INET_H_INCLUDED_
+#define _FIND_INET_H_INCLUDED_
+
+/*++
+/* NAME
+/* find_inet 3h
+/* SUMMARY
+/* inet-domain name services
+/* SYNOPSIS
+/* #include <find_inet.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern unsigned find_inet_addr(const char *);
+extern int find_inet_port(const char *, const char *);
+
+/* 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
+/* LAST MODIFICATION
+/* Thu Feb 6 12:46:36 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/find_inet.ref b/src/util/find_inet.ref
new file mode 100644
index 0000000..a6f9cac
--- /dev/null
+++ b/src/util/find_inet.ref
@@ -0,0 +1,5 @@
+find_inet: good-symbolic: PASS
+find_inet: good-numeric: PASS
+find_inet: bad-symbolic: PASS
+find_inet: bad-numeric: PASS
+find_inet: PASS=4 FAIL=0
diff --git a/src/util/format_tv.c b/src/util/format_tv.c
new file mode 100644
index 0000000..a932bdb
--- /dev/null
+++ b/src/util/format_tv.c
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/* format_tv 3
+/* SUMMARY
+/* format time value with sane precision
+/* SYNOPSIS
+/* #include <format_tv.h>
+/*
+/* VSTRING *format_tv(buffer, sec, usec, sig_dig, max_dig)
+/* VSTRING *buffer;
+/* long sec;
+/* long usec;
+/* int sig_dig;
+/* int max_dig;
+/* DESCRIPTION
+/* format_tv() formats the specified time as a floating-point
+/* number while suppressing irrelevant digits in the output.
+/* Large numbers are always rounded up to an integral number
+/* of seconds. Small numbers are produced with a limited number
+/* of significant digits, provided that the result does not
+/* exceed the limit on the total number of digits after the
+/* decimal point. Trailing zeros are always omitted from the
+/* output.
+/*
+/* Arguments:
+/* .IP buffer
+/* The buffer to which the result is appended.
+/* .IP sec
+/* The seconds portion of the time value.
+/* .IP usec
+/* The microseconds portion of the time value.
+/* .IP sig_dig
+/* The maximal number of significant digits when formatting
+/* small numbers. Leading nulls don't count as significant,
+/* and trailing nulls are not included in the output. Specify
+/* a number in the range 1..6.
+/* .IP max_dig
+/* The maximal number of all digits after the decimal point.
+/* Specify a number in the range 0..6.
+/* 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
+/*--*/
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <format_tv.h>
+
+/* Application-specific. */
+
+#define MILLION 1000000
+
+/* format_tv - print time with limited precision */
+
+VSTRING *format_tv(VSTRING *buf, long sec, long usec,
+ int sig_dig, int max_dig)
+{
+ static int pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
+ int n;
+ int rem;
+ int wid;
+ int ures;
+
+ /*
+ * Sanity check.
+ */
+ if (max_dig < 0 || max_dig > 6)
+ msg_panic("format_tv: bad maximum decimal count %d", max_dig);
+ if (sec < 0 || usec < 0 || usec > MILLION)
+ msg_panic("format_tv: bad time %lds %ldus", sec, usec);
+ if (sig_dig < 1 || sig_dig > 6)
+ msg_panic("format_tv: bad significant decimal count %d", sig_dig);
+ ures = MILLION / pow10[max_dig];
+ wid = pow10[sig_dig];
+
+ /*
+ * Adjust the resolution to suppress irrelevant digits.
+ */
+ if (ures < MILLION) {
+ if (sec > 0) {
+ for (n = 1; sec >= n && n <= wid / 10; n *= 10)
+ /* void */ ;
+ ures = (MILLION / wid) * n;
+ } else {
+ while (usec >= wid * ures)
+ ures *= 10;
+ }
+ }
+
+ /*
+ * Round up the number if necessary. Leave thrash below the resolution.
+ */
+ if (ures > 1) {
+ usec += ures / 2;
+ if (usec >= MILLION) {
+ sec += 1;
+ usec -= MILLION;
+ }
+ }
+
+ /*
+ * Format the number. Truncate trailing null and thrash below resolution.
+ */
+ vstring_sprintf_append(buf, "%ld", sec);
+ if (usec >= ures) {
+ VSTRING_ADDCH(buf, '.');
+ for (rem = usec, n = MILLION / 10; rem >= ures && n > 0; n /= 10) {
+ VSTRING_ADDCH(buf, "0123456789"[rem / n]);
+ rem %= n;
+ }
+ }
+ VSTRING_TERMINATE(buf);
+ return (buf);
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(10);
+ VSTRING *out = vstring_alloc(10);
+ double tval;
+ int sec;
+ int usec;
+ int sig_dig;
+ int max_dig;
+
+ while (vstring_get_nonl(in, VSTREAM_IN) > 0) {
+ vstream_printf(">> %s\n", vstring_str(in));
+ if (vstring_str(in)[0] == 0 || vstring_str(in)[0] == '#')
+ continue;
+ if (sscanf(vstring_str(in), "%lf %d %d", &tval, &sig_dig, &max_dig) != 3)
+ msg_fatal("bad input: %s", vstring_str(in));
+ sec = (int) tval; /* raw seconds */
+ usec = (tval - sec) * MILLION; /* raw microseconds */
+ VSTRING_RESET(out);
+ format_tv(out, sec, usec, sig_dig, max_dig);
+ vstream_printf("%s\n", vstring_str(out));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in);
+ vstring_free(out);
+ return (0);
+}
+
+#endif
diff --git a/src/util/format_tv.h b/src/util/format_tv.h
new file mode 100644
index 0000000..bef787a
--- /dev/null
+++ b/src/util/format_tv.h
@@ -0,0 +1,35 @@
+#ifndef _FORMAT_TV_H_INCLUDED_
+#define _FORMAT_TV_H_INCLUDED_
+
+/*++
+/* NAME
+/* format_tv 3h
+/* SUMMARY
+/* format time with limited precision
+/* SYNOPSIS
+/* #include <format_tv.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *format_tv(VSTRING *, long, long, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/format_tv.in b/src/util/format_tv.in
new file mode 100644
index 0000000..a6b5848
--- /dev/null
+++ b/src/util/format_tv.in
@@ -0,0 +1,68 @@
+# Three digits in, 2/6 digits out, rounding down.
+1110 2 6
+111 2 6
+11.1 2 6
+1.11 2 6
+0.111 2 6
+0.0111 2 6
+0.00111 2 6
+0.000111 2 6
+0.0000111 2 6
+
+# One digit in. Must not produce spurious digits or trailing nulls.
+
+1000 2 6
+100 2 6
+10 2 6
+1 2 6
+0.1 2 6
+0.01 2 6
+0.001 2 6
+0.0001 2 6
+0.00001 2 6
+0.0000011 2 6
+
+# Three digits in, 2/6 digits out, rounding up.
+
+996 2 6
+99.6 2 6
+9.96 2 6
+.996 2 6
+.0996 2 6
+.00996 2 6
+.000996 2 6
+
+# Three digits in, 1/6 digits out, rounding down.
+
+1110 1 6
+111 1 6
+11.1 1 6
+1.11 1 6
+0.111 1 6
+0.0111 1 6
+0.00111 1 6
+0.000111 1 6
+0.000011 1 6
+
+# One digit in. Must not produce trailing nulls.
+
+1000 1 6
+100 1 6
+10 1 6
+1 1 6
+0.1 1 6
+0.01 1 6
+0.001 1 6
+0.0001 1 6
+0.00001 1 6
+0.0000011 1 6
+
+# Three digits in, 1/6 digits out, rounding up.
+
+996 1 6
+99.6 1 6
+9.96 1 6
+.996 1 6
+.0996 1 6
+.00996 1 6
+.000996 1 6
diff --git a/src/util/format_tv.ref b/src/util/format_tv.ref
new file mode 100644
index 0000000..718c3c7
--- /dev/null
+++ b/src/util/format_tv.ref
@@ -0,0 +1,120 @@
+>> # Three digits in, 2/6 digits out, rounding down.
+>> 1110 2 6
+1110
+>> 111 2 6
+111
+>> 11.1 2 6
+11
+>> 1.11 2 6
+1.1
+>> 0.111 2 6
+0.11
+>> 0.0111 2 6
+0.011
+>> 0.00111 2 6
+0.0011
+>> 0.000111 2 6
+0.00011
+>> 0.0000111 2 6
+0.000011
+>>
+>> # One digit in. Must not produce spurious digits or trailing nulls.
+>>
+>> 1000 2 6
+1000
+>> 100 2 6
+100
+>> 10 2 6
+10
+>> 1 2 6
+1
+>> 0.1 2 6
+0.1
+>> 0.01 2 6
+0.01
+>> 0.001 2 6
+0.001
+>> 0.0001 2 6
+0.0001
+>> 0.00001 2 6
+0.00001
+>> 0.0000011 2 6
+0.000001
+>>
+>> # Three digits in, 2/6 digits out, rounding up.
+>>
+>> 996 2 6
+996
+>> 99.6 2 6
+100
+>> 9.96 2 6
+10
+>> .996 2 6
+1
+>> .0996 2 6
+0.1
+>> .00996 2 6
+0.01
+>> .000996 2 6
+0.001
+>>
+>> # Three digits in, 1/6 digits out, rounding down.
+>>
+>> 1110 1 6
+1110
+>> 111 1 6
+111
+>> 11.1 1 6
+11
+>> 1.11 1 6
+1
+>> 0.111 1 6
+0.1
+>> 0.0111 1 6
+0.01
+>> 0.00111 1 6
+0.001
+>> 0.000111 1 6
+0.0001
+>> 0.000011 1 6
+0.00001
+>>
+>> # One digit in. Must not produce trailing nulls.
+>>
+>> 1000 1 6
+1000
+>> 100 1 6
+100
+>> 10 1 6
+10
+>> 1 1 6
+1
+>> 0.1 1 6
+0.1
+>> 0.01 1 6
+0.01
+>> 0.001 1 6
+0.001
+>> 0.0001 1 6
+0.0001
+>> 0.00001 1 6
+0.00001
+>> 0.0000011 1 6
+0.000001
+>>
+>> # Three digits in, 1/6 digits out, rounding up.
+>>
+>> 996 1 6
+996
+>> 99.6 1 6
+100
+>> 9.96 1 6
+10
+>> .996 1 6
+1
+>> .0996 1 6
+0.1
+>> .00996 1 6
+0.01
+>> .000996 1 6
+0.001
diff --git a/src/util/fsspace.c b/src/util/fsspace.c
new file mode 100644
index 0000000..50a4aa7
--- /dev/null
+++ b/src/util/fsspace.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* fsspace 3
+/* SUMMARY
+/* determine available file system space
+/* SYNOPSIS
+/* #include <fsspace.h>
+/*
+/* struct fsspace {
+/* .in +4
+/* unsigned long block_size;
+/* unsigned long block_free;
+/* .in -4
+/* };
+/*
+/* void fsspace(path, sp)
+/* const char *path;
+/* struct fsspace *sp;
+/* DESCRIPTION
+/* fsspace() returns the amount of available space in the file
+/* system specified in \fIpath\fR, in terms of the block size and
+/* of the number of available blocks.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* Use caution when doing computations with the result from fsspace().
+/* It is easy to cause overflow (by multiplying large numbers) or to
+/* cause underflow (by subtracting unsigned numbers).
+/* 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>
+
+#if defined(STATFS_IN_SYS_MOUNT_H)
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined(STATFS_IN_SYS_VFS_H)
+#include <sys/vfs.h>
+#elif defined(STATVFS_IN_SYS_STATVFS_H)
+#include <sys/statvfs.h>
+#elif defined(STATFS_IN_SYS_STATFS_H)
+#include <sys/statfs.h>
+#else
+#ifdef USE_STATFS
+#error "please specify the include file with `struct statfs'"
+#else
+#error "please specify the include file with `struct statvfs'"
+#endif
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <fsspace.h>
+
+/* fsspace - find amount of available file system space */
+
+void fsspace(const char *path, struct fsspace * sp)
+{
+ const char *myname = "fsspace";
+
+#ifdef USE_STATFS
+#ifdef USE_STRUCT_FS_DATA /* Ultrix */
+ struct fs_data fsbuf;
+
+ if (statfs(path, &fsbuf) < 0)
+ msg_fatal("statfs %s: %m", path);
+ sp->block_size = 1024;
+ sp->block_free = fsbuf.fd_bfreen;
+#else
+ struct statfs fsbuf;
+
+ if (statfs(path, &fsbuf) < 0)
+ msg_fatal("statfs %s: %m", path);
+ sp->block_size = fsbuf.f_bsize;
+ sp->block_free = fsbuf.f_bavail;
+#endif
+#endif
+#ifdef USE_STATVFS
+ struct statvfs fsbuf;
+
+ if (statvfs(path, &fsbuf) < 0)
+ msg_fatal("statvfs %s: %m", path);
+ sp->block_size = fsbuf.f_frsize;
+ sp->block_free = fsbuf.f_bavail;
+#endif
+ if (msg_verbose)
+ msg_info("%s: %s: block size %lu, blocks free %lu",
+ myname, path, sp->block_size, sp->block_free);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: print free space unit and count for all
+ * listed file systems.
+ */
+
+#include <vstream.h>
+
+int main(int argc, char **argv)
+{
+ struct fsspace sp;
+
+ if (argc == 1)
+ msg_fatal("usage: %s filesystem...", argv[0]);
+
+ while (--argc && *++argv) {
+ fsspace(*argv, &sp);
+ vstream_printf("%10s: block size %lu, blocks free %lu\n",
+ *argv, sp.block_size, sp.block_free);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/fsspace.h b/src/util/fsspace.h
new file mode 100644
index 0000000..c118e50
--- /dev/null
+++ b/src/util/fsspace.h
@@ -0,0 +1,33 @@
+#ifndef _FSSPACE_H_INCLUDED_
+#define _FSSPACE_H_INCLUDED_
+
+/*++
+/* NAME
+/* fsspace 3h
+/* SUMMARY
+/* determine available file system space
+/* SYNOPSIS
+/* #include <fsspace.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+struct fsspace {
+ unsigned long block_size; /* block size */
+ unsigned long block_free; /* free space */
+};
+
+extern void fsspace(const char *, struct fsspace *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/fullname.c b/src/util/fullname.c
new file mode 100644
index 0000000..a9d4b32
--- /dev/null
+++ b/src/util/fullname.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* fullname 3
+/* SUMMARY
+/* lookup personal name of invoking user
+/* SYNOPSIS
+/* #include <fullname.h>
+/*
+/* const char *fullname()
+/* DESCRIPTION
+/* fullname() looks up the personal name of the invoking user.
+/* The result is volatile. Make a copy if it is to be used for
+/* an appreciable amount of time.
+/*
+/* On UNIX systems, fullname() first tries to use the NAME environment
+/* variable, provided that the environment can be trusted.
+/* If that fails, fullname() extracts the username from the GECOS
+/* field of the user's password-file entry, replacing any occurrence
+/* of "&" by the login name, first letter capitalized.
+/*
+/* A null result means that no full name information was found.
+/* SEE ALSO
+/* safe_getenv(3) safe getenv() interface
+/* 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 <pwd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "vstring.h"
+#include "safe.h"
+#include "fullname.h"
+
+/* fullname - get name of user */
+
+const char *fullname(void)
+{
+ static VSTRING *result;
+ char *cp;
+ int ch;
+ uid_t uid;
+ struct passwd *pwd;
+
+ if (result == 0)
+ result = vstring_alloc(10);
+
+ /*
+ * Try the environment.
+ */
+ if ((cp = safe_getenv("NAME")) != 0)
+ return (vstring_str(vstring_strcpy(result, cp)));
+
+ /*
+ * Try the password file database.
+ */
+ uid = getuid();
+ if ((pwd = getpwuid(uid)) == 0)
+ return (0);
+
+ /*
+ * Replace all `&' characters by the login name of this user, first
+ * letter capitalized. Although the full name comes from the protected
+ * password file, the actual data is specified by the user so we should
+ * not trust its sanity.
+ */
+ VSTRING_RESET(result);
+ for (cp = pwd->pw_gecos; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == ',' || ch == ';' || ch == '%')
+ break;
+ if (ch == '&') {
+ if (pwd->pw_name[0]) {
+ VSTRING_ADDCH(result, TOUPPER(pwd->pw_name[0]));
+ vstring_strcat(result, pwd->pw_name + 1);
+ }
+ } else {
+ VSTRING_ADDCH(result, ch);
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (vstring_str(result));
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ const char *cp = fullname();
+
+ printf("%s\n", cp ? cp : "null!");
+ return (0);
+}
+
+#endif
diff --git a/src/util/fullname.h b/src/util/fullname.h
new file mode 100644
index 0000000..f24492a
--- /dev/null
+++ b/src/util/fullname.h
@@ -0,0 +1,29 @@
+#ifndef _FULLNAME_H_INCLUDED_
+#define _FULLNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* fullname 3h
+/* SUMMARY
+/* lookup personal name of invoking user
+/* SYNOPSIS
+/* #include <fullname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *fullname(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/gccw.c b/src/util/gccw.c
new file mode 100644
index 0000000..d3fd985
--- /dev/null
+++ b/src/util/gccw.c
@@ -0,0 +1,58 @@
+ /*
+ * This is a regression test for all the things that gcc is meant to warn
+ * about.
+ *
+ * gcc version 3 breaks several tests:
+ *
+ * -W does not report missing return value
+ *
+ * -Wunused does not report unused parameter
+ */
+
+#include <stdio.h>
+#include <setjmp.h>
+
+jmp_buf jbuf;
+
+ /* -Wmissing-prototypes: no previous prototype for 'test1' */
+ /* -Wimplicit: return type defaults to `int' */
+test1(void)
+{
+ /* -Wunused: unused variable `foo' */
+ int foo;
+
+ /* -Wparentheses: suggest parentheses around && within || */
+ printf("%d\n", 1 && 2 || 3 && 4);
+ /* -W: statement with no effect */
+ 0;
+ /* BROKEN in gcc 3 */
+ /* -W: control reaches end of non-void function */
+}
+
+
+ /* -W??????: unused parameter `foo' */
+void test2(int foo)
+{
+ enum {
+ a = 10, b = 15} moe;
+ int bar;
+
+ /* -Wuninitialized: 'bar' might be used uninitialized in this function */
+ /* -Wformat: format argument is not a pointer (arg 2) */
+ printf("%s\n", bar);
+ /* -Wformat: too few arguments for format */
+ printf("%s%s\n", "bar");
+ /* -Wformat: too many arguments for format */
+ printf("%s\n", "bar", "bar");
+
+ /* -Wswitch: enumeration value `b' not handled in switch */
+ switch (moe) {
+ case a:
+ return;
+ }
+}
+
+ /* -Wstrict-prototypes: function declaration isn't a prototype */
+void test3()
+{
+}
diff --git a/src/util/gccw.ref b/src/util/gccw.ref
new file mode 100644
index 0000000..7616c29
--- /dev/null
+++ b/src/util/gccw.ref
@@ -0,0 +1,18 @@
+gcc -Wall -Wno-comment -Wformat -Wimplicit -Wmissing-prototypes -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized -Wunused -DUSE_TLS -DHAS_PCRE -I/usr/local/include -DSNAPSHOT -g -O -I. -DFREEBSD5 -c gccw.c
+gccw.c: At top level:
+gccw.c: At top level:
+gccw.c: In function 'test1':
+gccw.c: In function 'test2':
+gccw.c:20: warning: no previous prototype for 'test1'
+gccw.c:20: warning: return type defaults to 'int'
+gccw.c:22: warning: unused variable 'foo'
+gccw.c:25: warning: suggest parentheses around && within ||
+gccw.c:27: warning: statement with no effect
+gccw.c:30: warning: control reaches end of non-void function
+gccw.c:35: warning: no previous prototype for 'test2'
+gccw.c:38: warning: 'bar' might be used uninitialized in this function
+gccw.c:42: warning: format argument is not a pointer (arg 2)
+gccw.c:44: warning: too few arguments for format
+gccw.c:46: warning: too many arguments for format
+gccw.c:52: warning: enumeration value 'b' not handled in switch
+gccw.c:57: warning: function declaration isn't a prototype
diff --git a/src/util/get_domainname.c b/src/util/get_domainname.c
new file mode 100644
index 0000000..0897f52
--- /dev/null
+++ b/src/util/get_domainname.c
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* get_domainname 3
+/* SUMMARY
+/* network domain name lookup
+/* SYNOPSIS
+/* #include <get_domainname.h>
+/*
+/* const char *get_domainname()
+/* DESCRIPTION
+/* get_domainname() returns the local domain name as obtained
+/* by stripping the hostname component from the result from
+/* get_hostname(). The result is the hostname when get_hostname()
+/* does not return a FQDN form ("foo"), or its result has only two
+/* components ("foo.com").
+/* DIAGNOSTICS
+/* Fatal errors: no hostname, invalid hostname.
+/* SEE ALSO
+/* get_hostname(3)
+/* 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 "mymalloc.h"
+#include "get_hostname.h"
+#include "get_domainname.h"
+
+/* Local stuff. */
+
+static char *my_domain_name;
+
+/* get_domainname - look up my domain name */
+
+const char *get_domainname(void)
+{
+ const char *host;
+ const char *dot;
+
+ /*
+ * Use the hostname when it is not a FQDN ("foo"), or when the hostname
+ * actually is a domain name ("foo.com").
+ */
+ if (my_domain_name == 0) {
+ host = get_hostname();
+ if ((dot = strchr(host, '.')) == 0 || strchr(dot + 1, '.') == 0) {
+ my_domain_name = mystrdup(host);
+ } else {
+ my_domain_name = mystrdup(dot + 1);
+ }
+ }
+ return (my_domain_name);
+}
diff --git a/src/util/get_domainname.h b/src/util/get_domainname.h
new file mode 100644
index 0000000..177e3fd
--- /dev/null
+++ b/src/util/get_domainname.h
@@ -0,0 +1,29 @@
+#ifndef _GET_DOMAINNAME_H_INCLUDED_
+#define _GET_DOMAINNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* get_domainname 3h
+/* SUMMARY
+/* network domain name lookup
+/* SYNOPSIS
+/* #include <get_domainname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+extern const char *get_domainname(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/get_hostname.c b/src/util/get_hostname.c
new file mode 100644
index 0000000..eb2cfea
--- /dev/null
+++ b/src/util/get_hostname.c
@@ -0,0 +1,84 @@
+/*++
+/* NAME
+/* get_hostname 3
+/* SUMMARY
+/* network name lookup
+/* SYNOPSIS
+/* #include <get_hostname.h>
+/*
+/* const char *get_hostname()
+/* DESCRIPTION
+/* get_hostname() returns the local hostname as obtained
+/* via gethostname() or its moral equivalent. This routine
+/* goes to great length to avoid dependencies on any network
+/* services.
+/* DIAGNOSTICS
+/* Fatal errors: no hostname, invalid hostname.
+/* SEE ALSO
+/* valid_hostname(3)
+/* 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/param.h>
+#include <string.h>
+#include <unistd.h>
+
+#if (MAXHOSTNAMELEN < 256)
+#undef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "valid_hostname.h"
+#include "get_hostname.h"
+
+/* Local stuff. */
+
+static char *my_host_name;
+
+/* get_hostname - look up my host name */
+
+const char *get_hostname(void)
+{
+ char namebuf[MAXHOSTNAMELEN + 1];
+
+ /*
+ * The gethostname() call is not (or not yet) in ANSI or POSIX, but it is
+ * part of the socket interface library. We avoid the more politically-
+ * correct uname() routine because that has no portable way of dealing
+ * with long (FQDN) hostnames.
+ *
+ * DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION. IT BREAKS MAILDIR DELIVERY
+ * AND OTHER THINGS WHEN THE MACHINE NAME IS NOT FOUND IN /ETC/HOSTS OR
+ * CAUSES PROCESSES TO HANG WHEN THE NETWORK IS DISCONNECTED.
+ *
+ * POSTFIX NO LONGER NEEDS A FULLY QUALIFIED HOSTNAME. INSTEAD POSTFIX WILL
+ * USE A DEFAULT DOMAIN NAME "LOCALDOMAIN".
+ */
+ if (my_host_name == 0) {
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ if (gethostname(namebuf, sizeof(namebuf)) < 0)
+ msg_fatal("gethostname: %m");
+ namebuf[MAXHOSTNAMELEN] = 0;
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ if (valid_hostname(namebuf, DO_GRIPE) == 0)
+ msg_fatal("unable to use my own hostname");
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ my_host_name = mystrdup(namebuf);
+ }
+ return (my_host_name);
+}
diff --git a/src/util/get_hostname.h b/src/util/get_hostname.h
new file mode 100644
index 0000000..32d2ab2
--- /dev/null
+++ b/src/util/get_hostname.h
@@ -0,0 +1,29 @@
+#ifndef _GET_HOSTNAME_H_INCLUDED_
+#define _GET_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* get_hostname 3h
+/* SUMMARY
+/* network name lookup
+/* SYNOPSIS
+/* #include <get_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+extern const char *get_hostname(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/hash_fnv.c b/src/util/hash_fnv.c
new file mode 100644
index 0000000..10e97f0
--- /dev/null
+++ b/src/util/hash_fnv.c
@@ -0,0 +1,107 @@
+/*++
+/* NAME
+/* hash_fnv 3
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/*
+/* HASH_FNV_T hash_fnv(
+/* const void *src,
+/* size_t len)
+/* DESCRIPTION
+/* hash_fnv() implements a modified FNV type 1a hash function.
+/*
+/* To thwart collision attacks, the hash function is seeded
+/* once from /dev/urandom, and if that is unavailable, from
+/* wallclock time, monotonic system clocks, and the process
+/* ID. To disable seeding (typically, for regression tests),
+/* specify the NORANDOMIZE environment variable; the value
+/* does not matter.
+/*
+/* This function implements a workaround for a "sticky state"
+/* problem with FNV hash functions: when an input produces a
+/* zero intermediate hash state, and the next input byte is
+/* zero, then the operations "hash ^= 0" and "hash *= FNV_prime"
+/* would not change the hash value. To avoid this, hash_fnv()
+/* adds 1 to each input byte. Compile with -DSTRICT_FNV1A to
+/* get the standard behavior.
+/*
+/* The default HASH_FNV_T result type is uint64_t. When compiled
+/* with -DUSE_FNV_32BIT, the result type is uint32_t. On ancient
+/* systems without <stdint.h>, define HASH_FNV_T on the compiler
+/* command line as an unsigned 32-bit or 64-bit integer type,
+/* and specify -DUSE_FNV_32BIT when HASH_FNV_T is a 32-bit type.
+/* SEE ALSO
+/* http://www.isthe.com/chongo/tech/comp/fnv/index.html
+/* https://softwareengineering.stackexchange.com/questions/49550/
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <ldseed.h>
+#include <hash_fnv.h>
+
+ /*
+ * Application-specific.
+ */
+#ifdef USE_FNV_32BIT
+#define FNV_prime 0x01000193UL
+#define FNV_offset_basis 0x811c9dc5UL
+#else
+#define FNV_prime 0x00000100000001B3ULL
+#define FNV_offset_basis 0xcbf29ce484222325ULL
+#endif
+
+/* hash_fnv - modified FNV 1a hash */
+
+HASH_FNV_T hash_fnv(const void *src, size_t len)
+{
+ static HASH_FNV_T basis = FNV_offset_basis;
+ static int randomize = 1;
+ HASH_FNV_T hash;
+
+ /*
+ * Initialize.
+ */
+ if (randomize) {
+ if (!getenv("NORANDOMIZE")) {
+ HASH_FNV_T seed;
+
+ ldseed(&seed, sizeof(seed));
+ basis ^= seed;
+ }
+ randomize = 0;
+ }
+
+#ifdef STRICT_FNV1A
+#define FNV_NEXT_BYTE(s) ((HASH_FNV_T) * (const unsigned char *) s++)
+#else
+#define FNV_NEXT_BYTE(s) (1 + (HASH_FNV_T) * (const unsigned char *) s++)
+#endif
+
+ hash = basis;
+ while (len-- > 0) {
+ hash ^= FNV_NEXT_BYTE(src);
+ hash *= FNV_prime;
+ }
+ return (hash);
+}
diff --git a/src/util/hash_fnv.h b/src/util/hash_fnv.h
new file mode 100644
index 0000000..dbbb383
--- /dev/null
+++ b/src/util/hash_fnv.h
@@ -0,0 +1,39 @@
+#ifndef _HASH_FNV_H_INCLUDED_
+#define _HASH_FNV_H_INCLUDED_
+
+/*++
+/* NAME
+/* hash_fnv 3h
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef HASH_FNV_T
+#include <stdint.h>
+#ifdef USE_FNV_32BIT
+#define HASH_FNV_T uint32_t
+#else /* USE_FNV_32BIT */
+#define HASH_FNV_T uint64_t
+#endif /* USE_FNV_32BIT */
+#endif /* HASH_FNV_T */
+
+extern HASH_FNV_T hash_fnv(const void *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/hex_code.c b/src/util/hex_code.c
new file mode 100644
index 0000000..3dfcb98
--- /dev/null
+++ b/src/util/hex_code.c
@@ -0,0 +1,243 @@
+/*++
+/* NAME
+/* hex_code 3
+/* SUMMARY
+/* encode/decode data, hexadecimal style
+/* SYNOPSIS
+/* #include <hex_code.h>
+/*
+/* VSTRING *hex_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *hex_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *hex_encode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/*
+/* VSTRING *hex_decode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/* DESCRIPTION
+/* hex_encode() takes a block of len bytes and encodes it as one
+/* upper-case null-terminated string. The result value is
+/* the result argument.
+/*
+/* hex_decode() performs the opposite transformation on
+/* lower-case, upper-case or mixed-case input. The result
+/* value is the result argument. The result is null terminated,
+/* whether or not that makes sense.
+/*
+/* hex_encode_opt() enables extended functionality as controlled
+/* with \fIflags\fR.
+/* .IP HEX_ENCODE_FLAG_NONE
+/* The default: a self-documenting flag that enables no
+/* functionality.
+/* .IP HEX_ENCODE_FLAG_USE_COLON
+/* Inserts one ":" between bytes.
+/* .PP
+/* hex_decode_opt() enables extended functionality as controlled
+/* with \fIflags\fR.
+/* .IP HEX_DECODE_FLAG_NONE
+/* The default: a self-documenting flag that enables no
+/* functionality.
+/* .IP HEX_DECODE_FLAG_ALLOW_COLON
+/* Allows, but does not require, one ":" between bytes.
+/* DIAGNOSTICS
+/* hex_decode() returns a null pointer when the input contains
+/* characters not in the hexadecimal alphabet.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <hex_code.h>
+
+/* Application-specific. */
+
+static const unsigned char hex_chars[] = "0123456789ABCDEF";
+
+#define UCHAR_PTR(x) ((const unsigned char *)(x))
+
+/* hex_encode - ABI compatibility */
+
+#undef hex_encode
+
+VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE));
+}
+
+/* hex_encode_opt - raw data to encoded */
+
+VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
+{
+ const unsigned char *cp;
+ int ch;
+ ssize_t count;
+
+ VSTRING_RESET(result);
+ for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) {
+ ch = *cp;
+ VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]);
+ VSTRING_ADDCH(result, hex_chars[ch & 0xf]);
+ if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1)
+ VSTRING_ADDCH(result, ':');
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* hex_decode - ABI compatibility wrapper */
+
+#undef hex_decode
+
+VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE));
+}
+
+/* hex_decode_opt - encoded data to raw */
+
+VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
+{
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int hex;
+ unsigned int bin;
+
+ VSTRING_RESET(result);
+ for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) {
+ if (count < 2)
+ return (0);
+ hex = cp[0];
+ if (hex >= '0' && hex <= '9')
+ bin = (hex - '0') << 4;
+ else if (hex >= 'A' && hex <= 'F')
+ bin = (hex - 'A' + 10) << 4;
+ else if (hex >= 'a' && hex <= 'f')
+ bin = (hex - 'a' + 10) << 4;
+ else
+ return (0);
+ hex = cp[1];
+ if (hex >= '0' && hex <= '9')
+ bin |= (hex - '0');
+ else if (hex >= 'A' && hex <= 'F')
+ bin |= (hex - 'A' + 10);
+ else if (hex >= 'a' && hex <= 'f')
+ bin |= (hex - 'a' + 10);
+ else
+ return (0);
+ VSTRING_ADDCH(result, bin);
+
+ /*
+ * Support *colon-separated* input (no leading or trailing colons).
+ * After decoding "xx", skip a possible ':' preceding "yy" in
+ * "xx:yy".
+ */
+ if ((flags & HEX_DECODE_FLAG_ALLOW_COLON)
+ && count > 4 && cp[2] == ':') {
+ ++cp;
+ --count;
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+#include <argv.h>
+
+ /*
+ * Proof-of-concept test program: convert to hexadecimal and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ char *test = "this is a test";
+ ARGV *argv;
+
+#define DECODE(b,x,l) { \
+ if (hex_decode((b),(x),(l)) == 0) \
+ msg_panic("bad hex: %s", (x)); \
+ }
+#define VERIFY(b,t) { \
+ if (strcmp((b), (t)) != 0) \
+ msg_panic("bad test: %s", (b)); \
+ }
+
+ hex_encode(b1, test, strlen(test));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode(b1, test, strlen(test));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode(b1, test, strlen(test));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode_opt(b1, test, strlen(test), HEX_ENCODE_FLAG_USE_COLON);
+ argv = argv_split(STR(b1), ":");
+ if (argv->argc != strlen(test))
+ msg_panic("HEX_ENCODE_FLAG_USE_COLON");
+ if (hex_decode_opt(b2, STR(b1), LEN(b1), HEX_DECODE_FLAG_ALLOW_COLON) == 0)
+ msg_panic("HEX_DECODE_FLAG_ALLOW_COLON");
+ VERIFY(STR(b2), test);
+ argv_free(argv);
+
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/hex_code.h b/src/util/hex_code.h
new file mode 100644
index 0000000..720977a
--- /dev/null
+++ b/src/util/hex_code.h
@@ -0,0 +1,54 @@
+#ifndef _HEX_CODE_H_INCLUDED_
+#define _HEX_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* hex_code 3h
+/* SUMMARY
+/* encode/decode data, hexadecimal style
+/* SYNOPSIS
+/* #include <hex_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define HEX_ENCODE_FLAG_NONE (0)
+#define HEX_ENCODE_FLAG_USE_COLON (1<<0)
+
+#define HEX_DECODE_FLAG_NONE (0)
+#define HEX_DECODE_FLAG_ALLOW_COLON (1<<0)
+
+extern VSTRING *hex_encode(VSTRING *, const char *, ssize_t);
+extern VSTRING *WARN_UNUSED_RESULT hex_decode(VSTRING *, const char *, ssize_t);
+extern VSTRING *hex_encode_opt(VSTRING *, const char *, ssize_t, int);
+extern VSTRING *WARN_UNUSED_RESULT hex_decode_opt(VSTRING *, const char *, ssize_t, int);
+
+#define hex_encode(res, in, len) \
+ hex_encode_opt((res), (in), (len), HEX_ENCODE_FLAG_NONE)
+#define hex_decode(res, in, len) \
+ hex_decode_opt((res), (in), (len), HEX_DECODE_FLAG_NONE)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/hex_quote.c b/src/util/hex_quote.c
new file mode 100644
index 0000000..7089385
--- /dev/null
+++ b/src/util/hex_quote.c
@@ -0,0 +1,153 @@
+/*++
+/* NAME
+/* hex_quote 3
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/*
+/* VSTRING *hex_quote(hex, raw)
+/* VSTRING *hex;
+/* const char *raw;
+/*
+/* VSTRING *hex_unquote(raw, hex)
+/* VSTRING *raw;
+/* const char *hex;
+/* DESCRIPTION
+/* hex_quote() takes a null-terminated string and replaces non-printable
+/* and whitespace characters and the % by %XX, XX being the two-digit
+/* hexadecimal equivalent.
+/* The hexadecimal codes are produced as upper-case characters. The result
+/* value is the hex argument.
+/*
+/* hex_unquote() performs the opposite transformation. This function
+/* understands lowercase, uppercase, and mixed case %XX sequences. The
+/* result value is the raw argument in case of success, a null pointer
+/* otherwise.
+/* BUGS
+/* hex_quote() cannot process null characters in data.
+/* 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>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "hex_quote.h"
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* hex_quote - raw data to quoted */
+
+VSTRING *hex_quote(VSTRING *hex, const char *raw)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(hex);
+ for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ch != '%' && !ISSPACE(ch) && ISPRINT(ch)) {
+ VSTRING_ADDCH(hex, ch);
+ } else {
+ vstring_sprintf_append(hex, "%%%02X", ch);
+ }
+ }
+ VSTRING_TERMINATE(hex);
+ return (hex);
+}
+
+/* hex_unquote - quoted data to raw */
+
+VSTRING *hex_unquote(VSTRING *raw, const char *hex)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(raw);
+ for (cp = hex; (ch = *cp) != 0; cp++) {
+ if (ch == '%') {
+ if (ISDIGIT(cp[1]))
+ ch = (cp[1] - '0') << 4;
+ else if (cp[1] >= 'a' && cp[1] <= 'f')
+ ch = (cp[1] - 'a' + 10) << 4;
+ else if (cp[1] >= 'A' && cp[1] <= 'F')
+ ch = (cp[1] - 'A' + 10) << 4;
+ else
+ return (0);
+ if (ISDIGIT(cp[2]))
+ ch |= (cp[2] - '0');
+ else if (cp[2] >= 'a' && cp[2] <= 'f')
+ ch |= (cp[2] - 'a' + 10);
+ else if (cp[2] >= 'A' && cp[2] <= 'F')
+ ch |= (cp[2] - 'A' + 10);
+ else
+ return (0);
+ cp += 2;
+ }
+ VSTRING_ADDCH(raw, ch);
+ }
+ VSTRING_TERMINATE(raw);
+ return (raw);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to hex and back.
+ */
+#include <vstream.h>
+
+#define BUFLEN 1024
+
+static ssize_t read_buf(VSTREAM *fp, VSTRING *buf)
+{
+ ssize_t len;
+
+ len = vstream_fread_buf(fp, buf, BUFLEN);
+ VSTRING_TERMINATE(buf);
+ return (len);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *raw = vstring_alloc(BUFLEN);
+ VSTRING *hex = vstring_alloc(100);
+ ssize_t len;
+
+ while ((len = read_buf(VSTREAM_IN, raw)) > 0) {
+ hex_quote(hex, STR(raw));
+ if (hex_unquote(raw, STR(hex)) == 0)
+ msg_fatal("bad input: %.100s", STR(hex));
+ if (LEN(raw) != len)
+ msg_fatal("len %ld != raw len %ld", (long) len, (long) LEN(raw));
+ if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw))
+ msg_fatal("write error: %m");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(raw);
+ vstring_free(hex);
+ return (0);
+}
+
+#endif
diff --git a/src/util/hex_quote.h b/src/util/hex_quote.h
new file mode 100644
index 0000000..d72ec57
--- /dev/null
+++ b/src/util/hex_quote.h
@@ -0,0 +1,36 @@
+#ifndef _HEX_QUOTE_H_INCLUDED_
+#define _HEX_QUOTE_H_INCLUDED_
+
+/*++
+/* NAME
+/* hex_quote 3h
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *hex_quote(VSTRING *, const char *);
+extern VSTRING *hex_unquote(VSTRING *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/host_port.c b/src/util/host_port.c
new file mode 100644
index 0000000..c4e8616
--- /dev/null
+++ b/src/util/host_port.c
@@ -0,0 +1,203 @@
+/*++
+/* NAME
+/* host_port 3
+/* SUMMARY
+/* split string into host and port, destroy string
+/* SYNOPSIS
+/* #include <host_port.h>
+/*
+/* const char *host_port(string, host, def_host, port, def_service)
+/* char *string;
+/* char **host;
+/* char *def_host;
+/* char **port;
+/* char *def_service;
+/* DESCRIPTION
+/* host_port() splits a string into substrings with the host
+/* name or address, and the service name or port number.
+/* The input string is modified.
+/*
+/* Host/domain names are validated with valid_utf8_hostname(),
+/* and host addresses are validated with valid_hostaddr().
+/*
+/* The following input formats are understood (null means
+/* a null pointer argument):
+/*
+/* When def_service is not null, and def_host is null:
+/*
+/* [host]:port, [host]:, [host]
+/*
+/* host:port, host:, host
+/*
+/* When def_host is not null, and def_service is null:
+/*
+/* :port, port
+/*
+/* Other combinations of def_service and def_host are
+/* not supported and produce undefined results.
+/* DIAGNOSTICS
+/* The result is a null pointer in case of success.
+/* In case of problems the result is a string pointer with
+/* the problem type.
+/* CLIENT EXAMPLE
+/* .ad
+/* .fi
+/* Typical client usage allows the user to omit the service port,
+/* in which case the client connects to a pre-determined default
+/* port:
+/* .nf
+/* .na
+/*
+/* buf = mystrdup(endpoint);
+/* if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0)
+/* msg_fatal("%s in \"%s\"", parse_error, endpoint);
+/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
+/* myfree(buf);
+/* SERVER EXAMPLE
+/* .ad
+/* .fi
+/* Typical server usage allows the user to omit the host, meaning
+/* listen on all available network addresses:
+/* .nf
+/* .na
+/*
+/* buf = mystrdup(endpoint);
+/* if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0)
+/* msg_fatal("%s in \"%s\"", parse_error, endpoint);
+/* if (*host == 0)
+/* host = 0;
+/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
+/* myfree(buf);
+/* 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>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <split_at.h>
+#include <stringops.h> /* XXX util_utf8_enable */
+#include <valid_utf8_hostname.h>
+
+/* Global library. */
+
+#include <host_port.h>
+
+ /*
+ * Point-fix workaround. The libutil library should be email agnostic, but
+ * we can't rip up the library APIs in the stable releases.
+ */
+#include <string.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#define IPV6_COL "IPv6:" /* RFC 2821 */
+#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1)
+#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0)
+
+/* host_port - parse string into host and port, destroy string */
+
+const char *host_port(char *buf, char **host, char *def_host,
+ char **port, char *def_service)
+{
+ char *cp = buf;
+ int ipv6 = 0;
+
+ /*-
+ * [host]:port, [host]:, [host].
+ * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr].
+ */
+ if (*cp == '[') {
+ ++cp;
+ if ((ipv6 = HAS_IPV6_COL(cp)) != 0)
+ cp += IPV6_COL_LEN;
+ *host = cp;
+ if ((cp = split_at(cp, ']')) == 0)
+ return ("missing \"]\"");
+ if (*cp && *cp++ != ':')
+ return ("garbage after \"]\"");
+ if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE))
+ return ("malformed IPv6 address");
+ *port = *cp ? cp : def_service;
+ }
+
+ /*
+ * host:port, host:, host, :port, port.
+ */
+ else {
+ if ((cp = split_at_right(buf, ':')) != 0) {
+ *host = *buf ? buf : def_host;
+ *port = *cp ? cp : def_service;
+ } else {
+ *host = def_host ? def_host : (*buf ? buf : 0);
+ *port = def_service ? def_service : (*buf ? buf : 0);
+ }
+ }
+ if (*host == 0)
+ return ("missing host information");
+ if (*port == 0)
+ return ("missing service information");
+
+ /*
+ * Final sanity checks. We're still sloppy, allowing bare numerical
+ * network addresses instead of requiring proper [ipaddress] forms.
+ */
+ if (*host != def_host
+ && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE)
+ && !valid_hostaddr(*host, DONT_GRIPE))
+ return ("valid hostname or network address required");
+ if (*port != def_service && ISDIGIT(**port) && !alldig(*port))
+ return ("garbage after numerical service");
+ return (0);
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *in_buf = vstring_alloc(10);
+ VSTRING *parse_buf = vstring_alloc(10);
+ char *host;
+ char *port;
+ const char *err;
+
+ while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) {
+ vstream_printf(">> %s\n", STR(in_buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (*STR(in_buf) == '#')
+ continue;
+ vstring_strcpy(parse_buf, STR(in_buf));
+ if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) {
+ msg_warn("%s in %s", err, STR(in_buf));
+ } else {
+ vstream_printf("host %s port %s\n", host, port);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(in_buf);
+ vstring_free(parse_buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/host_port.h b/src/util/host_port.h
new file mode 100644
index 0000000..f2fecbb
--- /dev/null
+++ b/src/util/host_port.h
@@ -0,0 +1,35 @@
+#ifndef _HOST_PORT_H_INCLUDED_
+#define _HOST_PORT_H_INCLUDED_
+
+/*++
+/* NAME
+/* host_port 3h
+/* SUMMARY
+/* split string into host and port, destroy string
+/* SYNOPSIS
+/* #include <host_port.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *WARN_UNUSED_RESULT host_port(char *, char **, char *,
+ char **, char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/host_port.in b/src/util/host_port.in
new file mode 100644
index 0000000..1608222
--- /dev/null
+++ b/src/util/host_port.in
@@ -0,0 +1,16 @@
+hhh:ppp
+hhh:
+hhh
+[hhh]:ppp
+[hhh]:
+[hhh]
+#[hhh:ppp]
+#[hhh:]
+hhh:1pp
+[hh.]
+hh.
+999
+[::1]
+[ipv6:::1]
+[ipv6:127.0.0.1]
+[ipv6:example.com]
diff --git a/src/util/host_port.ref b/src/util/host_port.ref
new file mode 100644
index 0000000..1d79745
--- /dev/null
+++ b/src/util/host_port.ref
@@ -0,0 +1,30 @@
+>> hhh:ppp
+host hhh port ppp
+>> hhh:
+host hhh port default-service
+>> hhh
+host hhh port default-service
+>> [hhh]:ppp
+host hhh port ppp
+>> [hhh]:
+host hhh port default-service
+>> [hhh]
+host hhh port default-service
+>> #[hhh:ppp]
+>> #[hhh:]
+>> hhh:1pp
+unknown: warning: garbage after numerical service in hhh:1pp
+>> [hh.]
+unknown: warning: valid hostname or network address required in [hh.]
+>> hh.
+unknown: warning: valid hostname or network address required in hh.
+>> 999
+unknown: warning: valid hostname or network address required in 999
+>> [::1]
+host ::1 port default-service
+>> [ipv6:::1]
+host ::1 port default-service
+>> [ipv6:127.0.0.1]
+unknown: warning: malformed IPv6 address in [ipv6:127.0.0.1]
+>> [ipv6:example.com]
+unknown: warning: malformed IPv6 address in [ipv6:example.com]
diff --git a/src/util/htable.c b/src/util/htable.c
new file mode 100644
index 0000000..1c08e97
--- /dev/null
+++ b/src/util/htable.c
@@ -0,0 +1,439 @@
+/*++
+/* NAME
+/* htable 3
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <htable.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *key;
+/* void *value;
+/* /* private fields... */
+/* .in -4
+/* } HTABLE_INFO;
+/*
+/* HTABLE *htable_create(size)
+/* int size;
+/*
+/* HTABLE_INFO *htable_enter(table, key, value)
+/* HTABLE *table;
+/* const char *key;
+/* void *value;
+/*
+/* char *htable_find(table, key)
+/* HTABLE *table;
+/* const char *key;
+/*
+/* HTABLE_INFO *htable_locate(table, key)
+/* HTABLE *table;
+/* const char *key;
+/*
+/* void htable_delete(table, key, free_fn)
+/* HTABLE *table;
+/* const char *key;
+/* void (*free_fn)(void *);
+/*
+/* void htable_free(table, free_fn)
+/* HTABLE *table;
+/* void (*free_fn)(void *);
+/*
+/* void htable_walk(table, action, ptr)
+/* HTABLE *table;
+/* void (*action)(HTABLE_INFO *, void *ptr);
+/* void *ptr;
+/*
+/* HTABLE_INFO **htable_list(table)
+/* HTABLE *table;
+/*
+/* HTABLE_INFO *htable_sequence(table, how)
+/* HTABLE *table;
+/* int how;
+/* DESCRIPTION
+/* This module maintains one or more hash tables. Each table entry
+/* consists of a unique string-valued lookup key and a generic
+/* character-pointer value.
+/* The tables are automatically resized when they fill up. When the
+/* values to be remembered are not character pointers, proper casts
+/* should be used or the code will not be portable.
+/*
+/* htable_create() creates a table of the specified size and returns a
+/* pointer to the result. The lookup keys are saved with mystrdup().
+/* htable_enter() stores a (key, value) pair into the specified table
+/* and returns a pointer to the resulting entry. The code does not
+/* check if an entry with that key already exists: use htable_locate()
+/* for updating an existing entry.
+/*
+/* htable_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use htable_locate().
+/*
+/* htable_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* htable_delete() removes one entry that was stored under the given key.
+/* If the free_fn argument is not a null pointer, the corresponding
+/* function is called with as argument the non-zero value stored under
+/* the key.
+/*
+/* htable_free() destroys a hash table, including contents. If the free_fn
+/* argument is not a null pointer, the corresponding function is called
+/* for each table entry, with as argument the non-zero value stored
+/* with the entry.
+/*
+/* htable_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* htable_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/*
+/* htable_sequence() returns the first or next element depending
+/* on the value of the "how" argument. Specify HTABLE_SEQ_FIRST
+/* to start a new sequence, HTABLE_SEQ_NEXT to continue, and
+/* HTABLE_SEQ_STOP to terminate a sequence early. The caller
+/* must not delete an element before it is visited.
+/* RESTRICTIONS
+/* A callback function should not modify the hash table that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* hash_fnv(3) Fowler/Noll/Vo hash function
+/* 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
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Local stuff */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "htable.h"
+
+/* htable_hash - hash a string */
+
+#ifndef NO_HASH_FNV
+#include "hash_fnv.h"
+
+#define htable_hash(s, size) (hash_fnv((s), strlen(s)) % (size))
+
+#else
+
+static size_t htable_hash(const char *s, size_t size)
+{
+ size_t h = 0;
+ size_t g;
+
+ /*
+ * From the "Dragon" book by Aho, Sethi and Ullman.
+ */
+
+ while (*s) {
+ h = (h << 4U) + *(unsigned const char *) s++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= (g >> 24U);
+ h ^= g;
+ }
+ }
+ return (h % size);
+}
+
+#endif
+
+/* htable_link - insert element into table */
+
+#define htable_link(table, element) { \
+ HTABLE_INFO **_h = table->data + htable_hash(element->key, table->size);\
+ element->prev = 0; \
+ if ((element->next = *_h) != 0) \
+ (*_h)->prev = element; \
+ *_h = element; \
+ table->used++; \
+}
+
+/* htable_size - allocate and initialize hash table */
+
+static void htable_size(HTABLE *table, size_t size)
+{
+ HTABLE_INFO **h;
+
+ size |= 1;
+
+ table->data = h = (HTABLE_INFO **) mymalloc(size * sizeof(HTABLE_INFO *));
+ table->size = size;
+ table->used = 0;
+
+ while (size-- > 0)
+ *h++ = 0;
+}
+
+/* htable_create - create initial hash table */
+
+HTABLE *htable_create(ssize_t size)
+{
+ HTABLE *table;
+
+ table = (HTABLE *) mymalloc(sizeof(HTABLE));
+ htable_size(table, size < 13 ? 13 : size);
+ table->seq_bucket = table->seq_element = 0;
+ return (table);
+}
+
+/* htable_grow - extend existing table */
+
+static void htable_grow(HTABLE *table)
+{
+ HTABLE_INFO *ht;
+ HTABLE_INFO *next;
+ size_t old_size = table->size;
+ HTABLE_INFO **h = table->data;
+ HTABLE_INFO **old_entries = h;
+
+ htable_size(table, 2 * old_size);
+
+ while (old_size-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ htable_link(table, ht);
+ }
+ }
+ myfree((void *) old_entries);
+}
+
+/* htable_enter - enter (key, value) pair */
+
+HTABLE_INFO *htable_enter(HTABLE *table, const char *key, void *value)
+{
+ HTABLE_INFO *ht;
+
+ if (table->used >= table->size)
+ htable_grow(table);
+ ht = (HTABLE_INFO *) mymalloc(sizeof(HTABLE_INFO));
+ ht->key = mystrdup(key);
+ ht->value = value;
+ htable_link(table, ht);
+ return (ht);
+}
+
+/* htable_find - lookup value */
+
+void *htable_find(HTABLE *table, const char *key)
+{
+ HTABLE_INFO *ht;
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ if (table)
+ for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
+ if (STREQ(key, ht->key))
+ return (ht->value);
+ return (0);
+}
+
+/* htable_locate - lookup entry */
+
+HTABLE_INFO *htable_locate(HTABLE *table, const char *key)
+{
+ HTABLE_INFO *ht;
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ if (table)
+ for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
+ if (STREQ(key, ht->key))
+ return (ht);
+ return (0);
+}
+
+/* htable_delete - delete one entry */
+
+void htable_delete(HTABLE *table, const char *key, void (*free_fn) (void *))
+{
+ if (table) {
+ HTABLE_INFO *ht;
+ HTABLE_INFO **h = table->data + htable_hash(key, table->size);
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ for (ht = *h; ht; ht = ht->next) {
+ if (STREQ(key, ht->key)) {
+ if (ht->next)
+ ht->next->prev = ht->prev;
+ if (ht->prev)
+ ht->prev->next = ht->next;
+ else
+ *h = ht->next;
+ table->used--;
+ myfree(ht->key);
+ if (free_fn && ht->value)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ return;
+ }
+ }
+ msg_panic("htable_delete: unknown_key: \"%s\"", key);
+ }
+}
+
+/* htable_free - destroy hash table */
+
+void htable_free(HTABLE *table, void (*free_fn) (void *))
+{
+ if (table) {
+ ssize_t i = table->size;
+ HTABLE_INFO *ht;
+ HTABLE_INFO *next;
+ HTABLE_INFO **h = table->data;
+
+ while (i-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ myfree(ht->key);
+ if (free_fn && ht->value)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ }
+ }
+ myfree((void *) table->data);
+ table->data = 0;
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = 0;
+ myfree((void *) table);
+ }
+}
+
+/* htable_walk - iterate over hash table */
+
+void htable_walk(HTABLE *table, void (*action) (HTABLE_INFO *, void *),
+ void *ptr) {
+ if (table) {
+ ssize_t i = table->size;
+ HTABLE_INFO **h = table->data;
+ HTABLE_INFO *ht;
+
+ while (i-- > 0)
+ for (ht = *h++; ht; ht = ht->next)
+ (*action) (ht, ptr);
+ }
+}
+
+/* htable_list - list all table members */
+
+HTABLE_INFO **htable_list(HTABLE *table)
+{
+ HTABLE_INFO **list;
+ HTABLE_INFO *member;
+ ssize_t count = 0;
+ ssize_t i;
+
+ if (table != 0) {
+ list = (HTABLE_INFO **) mymalloc(sizeof(*list) * (table->used + 1));
+ for (i = 0; i < table->size; i++)
+ for (member = table->data[i]; member != 0; member = member->next)
+ list[count++] = member;
+ } else {
+ list = (HTABLE_INFO **) mymalloc(sizeof(*list));
+ }
+ list[count] = 0;
+ return (list);
+}
+
+/* htable_sequence - dict(3) compatibility iterator */
+
+HTABLE_INFO *htable_sequence(HTABLE *table, int how)
+{
+ if (table == 0)
+ return (0);
+
+ switch (how) {
+ case HTABLE_SEQ_FIRST: /* start new sequence */
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = htable_list(table);
+ table->seq_element = table->seq_bucket;
+ return (*(table->seq_element)++);
+ case HTABLE_SEQ_NEXT: /* next element */
+ if (table->seq_element && *table->seq_element)
+ return (*(table->seq_element)++);
+ /* FALLTHROUGH */
+ default: /* terminate sequence */
+ if (table->seq_bucket) {
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = table->seq_element = 0;
+ }
+ return (0);
+ }
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+#include <myrand.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ ssize_t count = 0;
+ HTABLE *hash;
+ HTABLE_INFO **ht_info;
+ HTABLE_INFO **ht;
+ HTABLE_INFO *info;
+ ssize_t i;
+ ssize_t r;
+ int op;
+
+ /*
+ * Load a large number of strings and delete them in a random order.
+ */
+ hash = htable_create(10);
+ while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
+ htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++));
+ if (count != hash->used)
+ msg_panic("%ld entries stored, but %lu entries exist",
+ (long) count, (unsigned long) hash->used);
+ for (i = 0, op = HTABLE_SEQ_FIRST; htable_sequence(hash, op) != 0;
+ i++, op = HTABLE_SEQ_NEXT)
+ /* void */ ;
+ if (i != hash->used)
+ msg_panic("%ld entries found, but %lu entries exist",
+ (long) i, (unsigned long) hash->used);
+ ht_info = htable_list(hash);
+ for (i = 0; i < hash->used; i++) {
+ r = myrand() % hash->used;
+ info = ht_info[i];
+ ht_info[i] = ht_info[r];
+ ht_info[r] = info;
+ }
+ for (ht = ht_info; *ht; ht++)
+ htable_delete(hash, ht[0]->key, (void (*) (void *)) 0);
+ if (hash->used > 0)
+ msg_panic("%ld entries not deleted", (long) hash->used);
+ myfree((void *) ht_info);
+ htable_free(hash, (void (*) (void *)) 0);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/htable.h b/src/util/htable.h
new file mode 100644
index 0000000..bba43e8
--- /dev/null
+++ b/src/util/htable.h
@@ -0,0 +1,70 @@
+#ifndef _HTABLE_H_INCLUDED_
+#define _HTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* htable 3h
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <htable.h>
+/* DESCRIPTION
+/* .nf
+
+ /* Structure of one hash table entry. */
+
+typedef struct HTABLE_INFO {
+ char *key; /* lookup key */
+ void *value; /* associated value */
+ struct HTABLE_INFO *next; /* colliding entry */
+ struct HTABLE_INFO *prev; /* colliding entry */
+} HTABLE_INFO;
+
+ /* Structure of one hash table. */
+
+typedef struct HTABLE {
+ ssize_t size; /* length of entries array */
+ ssize_t used; /* number of entries in table */
+ HTABLE_INFO **data; /* entries array, auto-resized */
+ HTABLE_INFO **seq_bucket; /* current sequence hash bucket */
+ HTABLE_INFO **seq_element; /* current sequence element */
+} HTABLE;
+
+extern HTABLE *htable_create(ssize_t);
+extern HTABLE_INFO *htable_enter(HTABLE *, const char *, void *);
+extern HTABLE_INFO *htable_locate(HTABLE *, const char *);
+extern void *htable_find(HTABLE *, const char *);
+extern void htable_delete(HTABLE *, const char *, void (*) (void *));
+extern void htable_free(HTABLE *, void (*) (void *));
+extern void htable_walk(HTABLE *, void (*) (HTABLE_INFO *, void *), void *);
+extern HTABLE_INFO **htable_list(HTABLE *);
+extern HTABLE_INFO *htable_sequence(HTABLE *, int);
+
+#define HTABLE_SEQ_FIRST 0
+#define HTABLE_SEQ_NEXT 1
+#define HTABLE_SEQ_STOP (-1)
+
+ /*
+ * Correct only when casting (char *) to (void *).
+ */
+#define HTABLE_ACTION_FN_CAST(f) ((void *)(HTABLE_INFO *, void *)) (f)
+#define HTABLE_FREE_FN_CAST(f) ((void *)(void *)) (f)
+
+/* 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
+/* CREATION DATE
+/* Fri Feb 14 13:43:19 EST 1997
+/* LAST MODIFICATION
+/* %E% %U%
+/* VERSION/RELEASE
+/* %I%
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_host.c b/src/util/inet_addr_host.c
new file mode 100644
index 0000000..d2c9d84
--- /dev/null
+++ b/src/util/inet_addr_host.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* inet_addr_host 3
+/* SUMMARY
+/* determine all host internet interface addresses
+/* SYNOPSIS
+/* #include <inet_addr_host.h>
+/*
+/* int inet_addr_host(addr_list, hostname)
+/* INET_ADDR_LIST *addr_list;
+/* const char *hostname;
+/* DESCRIPTION
+/* inet_addr_host() determines all interface addresses of the
+/* named host. The host may be specified as a symbolic name,
+/* or as a numerical address. An empty host expands as the
+/* wild-card address. Address results are appended to
+/* the specified address list. The result value is the number
+/* of addresses appended to the list.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* BUGS
+/* This code uses the name service, so it talks to the network,
+/* and that may not be desirable.
+/* SEE ALSO
+/* inet_addr_list(3) address list management
+/* 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 <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <inet_addr_host.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <msg.h>
+
+/* inet_addr_host - look up address list for host */
+
+int inet_addr_host(INET_ADDR_LIST *addr_list, const char *hostname)
+{
+ const char *myname = "inet_addr_host";
+ int sock;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ int aierr;
+ ssize_t hostnamelen;
+ const char *hname;
+ const char *serv;
+ int initial_count = addr_list->used;
+ const INET_PROTO_INFO *proto_info;
+
+ /*
+ * The use of square brackets around an IPv6 addresses is required, even
+ * though we don't enforce it as it'd make the code unnecessarily
+ * complicated.
+ *
+ * XXX AIX 5.1 getaddrinfo() does not allow "0" as service, regardless of
+ * whether or not a host is specified.
+ */
+ if (*hostname == 0) {
+ hname = 0;
+ serv = "1";
+ } else if (*hostname == '['
+ && hostname[(hostnamelen = strlen(hostname)) - 1] == ']') {
+ hname = mystrndup(hostname + 1, hostnamelen - 2);
+ serv = 0;
+ } else {
+ hname = hostname;
+ serv = 0;
+ }
+
+ proto_info = inet_proto_info();
+ if ((aierr = hostname_to_sockaddr(hname, serv, SOCK_STREAM, &res0)) == 0) {
+ for (res = res0; res; res = res->ai_next) {
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("%s: skipping address family %d for host \"%s\"",
+ myname, res->ai_family, hostname);
+ continue;
+ }
+
+ /*
+ * On Linux systems it is not unusual for user-land to be out of
+ * sync with kernel-land. When this is the case we try to be
+ * helpful and filter out address families that the library
+ * claims to understand but that are not supported by the kernel.
+ */
+ if ((sock = socket(res->ai_family, SOCK_STREAM, 0)) < 0) {
+ msg_warn("%s: skipping address family %d: %m",
+ myname, res->ai_family);
+ continue;
+ }
+ if (close(sock))
+ msg_warn("%s: close socket: %m", myname);
+
+ inet_addr_list_append(addr_list, res->ai_addr);
+ }
+ freeaddrinfo(res0);
+ }
+ if (hname && hname != hostname)
+ myfree((void *) hname);
+
+ return (addr_list->used - initial_count);
+}
+
+#ifdef TEST
+
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <sock_addr.h>
+
+int main(int argc, char **argv)
+{
+ INET_ADDR_LIST list;
+ struct sockaddr_storage *sa;
+ MAI_HOSTADDR_STR hostaddr;
+ INET_PROTO_INFO *proto_info;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc < 3)
+ msg_fatal("usage: %s protocols hostname...", argv[0]);
+
+ proto_info = inet_proto_init(argv[0], argv[1]);
+ argv += 1;
+
+ while (--argc && *++argv) {
+ inet_addr_list_init(&list);
+ if (inet_addr_host(&list, *argv) == 0)
+ msg_fatal("not found: %s", *argv);
+
+ for (sa = list.addrs; sa < list.addrs + list.used; sa++) {
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s\t%s\n", *argv, hostaddr.buf);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ inet_addr_list_free(&list);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_host.h b/src/util/inet_addr_host.h
new file mode 100644
index 0000000..39d150a
--- /dev/null
+++ b/src/util/inet_addr_host.h
@@ -0,0 +1,35 @@
+#ifndef INET_ADDR_HOST_H_INCLUDED_
+#define INET_ADDR_HOST_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_host 3h
+/* SUMMARY
+/* determine all host internet interface addresses
+/* SYNOPSIS
+/* #include <inet_addr_host.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern int inet_addr_host(INET_ADDR_LIST *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_list.c b/src/util/inet_addr_list.c
new file mode 100644
index 0000000..e579b17
--- /dev/null
+++ b/src/util/inet_addr_list.c
@@ -0,0 +1,186 @@
+/*++
+/* NAME
+/* inet_addr_list 3
+/* SUMMARY
+/* internet address list manager
+/* SYNOPSIS
+/* #include <inet_addr_list.h>
+/*
+/* void inet_addr_list_init(list)
+/* INET_ADDR_LIST *list;
+/*
+/* void inet_addr_list_append(list,addr)
+/* INET_ADDR_LIST *list;
+/* struct sockaddr *addr;
+/*
+/* void inet_addr_list_uniq(list)
+/* INET_ADDR_LIST *list;
+/*
+/* void inet_addr_list_free(list)
+/* INET_ADDR_LIST *list;
+/* DESCRIPTION
+/* This module maintains simple lists of internet addresses.
+/*
+/* inet_addr_list_init() initializes a user-provided structure
+/* so that it can be used by inet_addr_list_append() and by
+/* inet_addr_list_free().
+/*
+/* inet_addr_list_append() appends the specified address to
+/* the specified list, extending the list on the fly.
+/*
+/* inet_addr_list_uniq() sorts the specified address list and
+/* eliminates duplicates.
+/*
+/* inet_addr_list_free() reclaims memory used for the
+/* specified address list.
+/* 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/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_addr_list.h>
+
+/* inet_addr_list_init - initialize internet address list */
+
+void inet_addr_list_init(INET_ADDR_LIST *list)
+{
+ int init_size;
+
+ list->used = 0;
+ list->size = 0;
+ init_size = 2;
+ list->addrs = (struct sockaddr_storage *)
+ mymalloc(sizeof(*list->addrs) * init_size);
+ list->size = init_size;
+}
+
+/* inet_addr_list_append - append address to internet address list */
+
+void inet_addr_list_append(INET_ADDR_LIST *list,
+ struct sockaddr *addr)
+{
+ const char *myname = "inet_addr_list_append";
+ MAI_HOSTADDR_STR hostaddr;
+ int new_size;
+
+ if (msg_verbose > 1) {
+ SOCKADDR_TO_HOSTADDR(addr, SOCK_ADDR_LEN(addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: %s", myname, hostaddr.buf);
+ }
+ if (list->used >= list->size) {
+ new_size = list->size * 2;
+ list->addrs = (struct sockaddr_storage *)
+ myrealloc((void *) list->addrs, sizeof(*list->addrs) * new_size);
+ list->size = new_size;
+ }
+ memcpy(list->addrs + list->used++, addr, SOCK_ADDR_LEN(addr));
+}
+
+/* inet_addr_list_comp - compare addresses */
+
+static int inet_addr_list_comp(const void *a, const void *b)
+{
+
+ /*
+ * In case (struct *) != (void *).
+ */
+ return (sock_addr_cmp_addr(SOCK_ADDR_PTR(a), SOCK_ADDR_PTR(b)));
+}
+
+/* inet_addr_list_uniq - weed out duplicates */
+
+void inet_addr_list_uniq(INET_ADDR_LIST *list)
+{
+ int n;
+ int m;
+
+ /*
+ * Put the identical members right next to each other.
+ */
+ qsort((void *) list->addrs, list->used,
+ sizeof(list->addrs[0]), inet_addr_list_comp);
+
+ /*
+ * Nuke the duplicates. Postcondition after while loop: m is the largest
+ * index for which list->addrs[n] == list->addrs[m].
+ */
+ for (m = n = 0; m < list->used; m++, n++) {
+ if (m != n)
+ list->addrs[n] = list->addrs[m];
+ while (m + 1 < list->used
+ && inet_addr_list_comp((void *) &(list->addrs[n]),
+ (void *) &(list->addrs[m + 1])) == 0)
+ m += 1;
+ }
+ list->used = n;
+}
+
+/* inet_addr_list_free - destroy internet address list */
+
+void inet_addr_list_free(INET_ADDR_LIST *list)
+{
+ myfree((void *) list->addrs);
+}
+
+#ifdef TEST
+#include <inet_proto.h>
+
+ /*
+ * Duplicate elimination needs to be tested.
+ */
+#include <inet_addr_host.h>
+
+static void inet_addr_list_print(INET_ADDR_LIST *list)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ struct sockaddr_storage *sa;
+
+ for (sa = list->addrs; sa < list->addrs + list->used; sa++) {
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s", hostaddr.buf);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ INET_ADDR_LIST list;
+ INET_PROTO_INFO *proto_info;
+
+ proto_info = inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+ inet_addr_list_init(&list);
+ while (--argc && *++argv)
+ if (inet_addr_host(&list, *argv) == 0)
+ msg_fatal("host not found: %s", *argv);
+ msg_info("list before sort/uniq");
+ inet_addr_list_print(&list);
+ inet_addr_list_uniq(&list);
+ msg_info("list after sort/uniq");
+ inet_addr_list_print(&list);
+ inet_addr_list_free(&list);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_list.h b/src/util/inet_addr_list.h
new file mode 100644
index 0000000..8a109c1
--- /dev/null
+++ b/src/util/inet_addr_list.h
@@ -0,0 +1,44 @@
+#ifndef _INET_ADDR_LIST_H_INCLUDED_
+#define _INET_ADDR_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_list 3h
+/* SUMMARY
+/* internet address list manager
+/* SYNOPSIS
+/* #include <inet_addr_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <myaddrinfo.h> /* generic name/addr API */
+
+ /*
+ * External interface.
+ */
+typedef struct INET_ADDR_LIST {
+ int used; /* nr of elements in use */
+ int size; /* actual list size */
+ struct sockaddr_storage *addrs; /* payload */
+} INET_ADDR_LIST;
+
+extern void inet_addr_list_init(INET_ADDR_LIST *);
+extern void inet_addr_list_free(INET_ADDR_LIST *);
+extern void inet_addr_list_uniq(INET_ADDR_LIST *);
+extern void inet_addr_list_append(INET_ADDR_LIST *, struct sockaddr *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_list.in b/src/util/inet_addr_list.in
new file mode 100644
index 0000000..5c72a1b
--- /dev/null
+++ b/src/util/inet_addr_list.in
@@ -0,0 +1,9 @@
+168.100.3.2
+168.100.3.2
+168.100.3.1
+168.100.3.3
+168.100.3.3
+168.100.3.3
+168.100.3.4
+168.100.3.1
+168.100.3.4
diff --git a/src/util/inet_addr_list.ref b/src/util/inet_addr_list.ref
new file mode 100644
index 0000000..9fbf48e
--- /dev/null
+++ b/src/util/inet_addr_list.ref
@@ -0,0 +1,15 @@
+unknown: list before sort/uniq
+unknown: 168.100.3.2
+unknown: 168.100.3.2
+unknown: 168.100.3.1
+unknown: 168.100.3.3
+unknown: 168.100.3.3
+unknown: 168.100.3.3
+unknown: 168.100.3.4
+unknown: 168.100.3.1
+unknown: 168.100.3.4
+unknown: list after sort/uniq
+unknown: 168.100.3.1
+unknown: 168.100.3.2
+unknown: 168.100.3.3
+unknown: 168.100.3.4
diff --git a/src/util/inet_addr_local.c b/src/util/inet_addr_local.c
new file mode 100644
index 0000000..e48803a
--- /dev/null
+++ b/src/util/inet_addr_local.c
@@ -0,0 +1,621 @@
+/*++
+/* NAME
+/* inet_addr_local 3
+/* SUMMARY
+/* determine if IP address is local
+/* SYNOPSIS
+/* #include <inet_addr_local.h>
+/*
+/* int inet_addr_local(addr_list, mask_list, addr_family_list)
+/* INET_ADDR_LIST *addr_list;
+/* INET_ADDR_LIST *mask_list;
+/* unsigned *addr_family;
+/* DESCRIPTION
+/* inet_addr_local() determines all active IP interface addresses
+/* of the local system. Any address found is appended to the
+/* specified address list. The result value is the number of
+/* active interfaces found.
+/*
+/* The mask_list is either a null pointer, or it is a list that
+/* receives the netmasks of the interface addresses that were found.
+/*
+/* The addr_family_list specifies one or more of AF_INET or AF_INET6.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* inet_addr_list(3) address list management
+/* 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
+/*
+/* Dean C. Strik
+/* Department ICT
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven, Netherlands
+/* E-mail: <dean@ipnet6.org>
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#ifdef USE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#ifdef HAS_IPV6 /* Linux only? */
+#include <netdb.h>
+#include <stdio.h>
+#endif
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <inet_addr_list.h>
+#include <inet_addr_local.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <mask_addr.h>
+#include <hex_code.h>
+
+ /*
+ * Postfix needs its own interface address information to determine whether
+ * or not it is an MX host for some destination; without this information,
+ * mail would loop between MX hosts. Postfix also needs its interface
+ * addresses to figure out whether or not it is final destination for
+ * addresses of the form username@[ipaddress].
+ *
+ * Postfix needs its own interface netmask information when no explicit
+ * mynetworks setting is given in main.cf, and "mynetworks_style = subnet".
+ * The mynetworks parameter controls, among others, what mail clients are
+ * allowed to relay mail through Postfix.
+ *
+ * Different systems have different ways to find out this information. We will
+ * therefore use OS dependent methods. An overview:
+ *
+ * - Use getifaddrs() when available. This supports both IPv4/IPv6 addresses.
+ * The implementation however is not present in all major operating systems.
+ *
+ * - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses.
+ * With SIOCGLIFNETMASK we can obtain the netmask for either address family.
+ * Again, this is not present in all major operating systems.
+ *
+ * - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some
+ * time, but IPv6 information was not returned until 2.3.3. With older Linux
+ * versions we get IPv4 interface information with SIOCGIFCONF, and read
+ * IPv6 address/prefix information from a file in the /proc filesystem.
+ *
+ * - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since
+ * SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set
+ * the prefix length to /128 (host), and expect the user to configure a more
+ * appropriate mynetworks setting if needed.
+ *
+ * XXX: Each lookup method is implemented by its own function, so we duplicate
+ * some code. In this case, I think this is better than really drowning in
+ * the #ifdefs...
+ *
+ * -- Dean Strik (dcs)
+ */
+
+#ifndef HAVE_GETIFADDRS
+
+/* ial_socket - make socket for ioctl() operations */
+
+static int ial_socket(int af)
+{
+ const char *myname = "inet_addr_local[socket]";
+ int sock;
+
+ /*
+ * The host may not be actually configured with IPv6. When IPv6 support
+ * is not actually in the kernel, don't consider failure to create an
+ * IPv6 socket as fatal. This could be tuned better though. For other
+ * families, the error is fatal.
+ *
+ * XXX Now that Postfix controls protocol support centrally with the
+ * inet_proto(3) module, this workaround should no longer be needed.
+ */
+ if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) {
+#ifdef HAS_IPV6
+ if (af == AF_INET6) {
+ if (msg_verbose)
+ msg_warn("%s: socket: %m", myname);
+ return (-1);
+ }
+#endif
+ msg_fatal("%s: socket: %m", myname);
+ }
+ return (sock);
+}
+
+#endif
+
+#ifdef HAVE_GETIFADDRS
+
+/*
+ * The getifaddrs(3) function, introduced by BSD/OS, provides a
+ * platform-independent way of requesting interface addresses,
+ * including IPv6 addresses. The implementation however is not
+ * present in all major operating systems.
+ */
+
+/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */
+
+static int ial_getifaddrs(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[getifaddrs]";
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr *sa, *sam;
+
+ if (getifaddrs(&ifap) < 0)
+ msg_fatal("%s: getifaddrs: %m", myname);
+
+ /*
+ * Get the address of each IP network interface. According to BIND we
+ * must include interfaces that are down because the machine may still
+ * receive packets for that address (yes, via some other interface).
+ * Having no way to verify this claim on every machine, I will give them
+ * the benefit of the doubt.
+ *
+ * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces;
+ * fixed by replacing IFF_RUNNING by IFF_UP.
+ *
+ * FIX 200501: The IPV6 patch did not skip wild-card interface addresses
+ * (tested on FreeBSD).
+ */
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0)
+ continue;
+ sa = ifa->ifa_addr;
+ if (af != AF_UNSPEC && sa->sa_family != af)
+ continue;
+ sam = ifa->ifa_netmask;
+ if (sam == 0) {
+ /* XXX In mynetworks, a null netmask would match everyone. */
+ msg_warn("ignoring interface with null netmask, address family %d",
+ sa->sa_family);
+ continue;
+ }
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY)
+ continue;
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))
+ continue;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list != 0) {
+
+ /*
+ * Unfortunately, sa_len/sa_family may be broken in the netmask
+ * sockaddr structure. We must fix this manually to have correct
+ * addresses. --dcs
+ */
+#ifdef HAS_SA_LEN
+ sam->sa_len = sa->sa_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) :
+ sizeof(struct sockaddr_in);
+#endif
+ sam->sa_family = sa->sa_family;
+ inet_addr_list_append(mask_list, sam);
+ }
+ }
+ freeifaddrs(ifap);
+ return (0);
+}
+
+#elif defined(HAS_SIOCGLIF) /* HAVE_GETIFADDRS */
+
+/*
+ * The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris
+ * and HP/UX operating systems. The data is stored in sockaddr_storage
+ * structure. Both IPv4 and IPv6 addresses are returned though these
+ * calls.
+ */
+#define NEXT_INTERFACE(lifr) (lifr + 1)
+#define LIFREQ_SIZE(lifr) sizeof(lifr[0])
+
+/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */
+
+static int ial_siocglif(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[siocglif]";
+ struct lifconf lifc;
+ struct lifreq *lifr;
+ struct lifreq *lifr_mask;
+ struct lifreq *the_end;
+ struct sockaddr *sa;
+ int sock;
+ VSTRING *buf;
+
+ /*
+ * See also comments in ial_siocgif()
+ */
+ if (af != AF_INET && af != AF_INET6)
+ msg_fatal("%s: address family was %d, must be AF_INET (%d) or "
+ "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6);
+ sock = ial_socket(af);
+ if (sock < 0)
+ return (0);
+ buf = vstring_alloc(1024);
+ for (;;) {
+ memset(&lifc, 0, sizeof(lifc));
+ lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */
+ lifc.lifc_len = vstring_avail(buf);
+ lifc.lifc_buf = vstring_str(buf);
+ if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) {
+ if (errno != EINVAL)
+ msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname);
+ } else if (lifc.lifc_len < vstring_avail(buf) / 2)
+ break;
+ VSTRING_SPACE(buf, vstring_avail(buf) * 2);
+ }
+
+ the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len);
+ for (lifr = lifc.lifc_req; lifr < the_end;) {
+ sa = (struct sockaddr *) &lifr->lifr_addr;
+ if (sa->sa_family != af) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+ if (af == AF_INET) {
+ if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+#ifdef HAS_IPV6
+ } else if (af == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+ }
+#endif
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list) {
+ lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq));
+ memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq));
+ if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0)
+ msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname);
+ /* XXX: Check whether sa_len/family are honoured --dcs */
+ inet_addr_list_append(mask_list,
+ (struct sockaddr *) &lifr_mask->lifr_addr);
+ myfree((void *) lifr_mask);
+ }
+ lifr = NEXT_INTERFACE(lifr);
+ }
+ vstring_free(buf);
+ (void) close(sock);
+ return (0);
+}
+
+#else /* HAVE_SIOCGLIF */
+
+/*
+ * The classic SIOCGIF* ioctls. Modern BSD operating systems will
+ * also return IPv6 addresses through these structure. Note however
+ * that recent versions of these operating systems have getifaddrs.
+ */
+#if defined(_SIZEOF_ADDR_IFREQ)
+#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
+ ((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr)))
+#define IFREQ_SIZE(ifr) _SIZEOF_ADDR_IFREQ(*ifr)
+#elif defined(HAS_SA_LEN)
+#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
+ ((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len))
+#define IFREQ_SIZE(ifr) (sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)
+#else
+#define NEXT_INTERFACE(ifr) (ifr + 1)
+#define IFREQ_SIZE(ifr) sizeof(ifr[0])
+#endif
+
+/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */
+
+static int ial_siocgif(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[siocgif]";
+ struct in_addr addr;
+ struct ifconf ifc;
+ struct ifreq *ifr;
+ struct ifreq *ifr_mask;
+ struct ifreq *the_end;
+ int sock;
+ VSTRING *buf;
+
+ /*
+ * Get the network interface list. XXX The socket API appears to have no
+ * function that returns the number of network interfaces, so we have to
+ * guess how much space is needed to store the result.
+ *
+ * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
+ * possible, leaving it up to the application to repeat the request with
+ * a larger buffer if the result caused a tight fit.
+ *
+ * Other systems, such as Solaris 2.5, generate an EINVAL error when the
+ * buffer is too small for the entire result. Workaround: ignore EINVAL
+ * errors and repeat the request with a larger buffer. The downside is
+ * that the program can run out of memory due to a non-memory problem,
+ * making it more difficult than necessary to diagnose the real problem.
+ */
+ sock = ial_socket(af);
+ if (sock < 0)
+ return (0);
+ buf = vstring_alloc(1024);
+ for (;;) {
+ ifc.ifc_len = vstring_avail(buf);
+ ifc.ifc_buf = vstring_str(buf);
+ if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
+ if (errno != EINVAL)
+ msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
+ } else if (ifc.ifc_len < vstring_avail(buf) / 2)
+ break;
+ VSTRING_SPACE(buf, vstring_avail(buf) * 2);
+ }
+
+ the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
+ for (ifr = ifc.ifc_req; ifr < the_end;) {
+ if (ifr->ifr_addr.sa_family != af) {
+ ifr = NEXT_INTERFACE(ifr);
+ continue;
+ }
+ if (af == AF_INET) {
+ addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
+ if (addr.s_addr != INADDR_ANY) {
+ inet_addr_list_append(addr_list, &ifr->ifr_addr);
+ if (mask_list) {
+ ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
+ memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr));
+ if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
+ msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);
+
+ /*
+ * Note that this SIOCGIFNETMASK has truly screwed up the
+ * contents of sa_len/sa_family. We must fix this
+ * manually to have correct addresses. --dcs
+ */
+ ifr_mask->ifr_addr.sa_family = af;
+#ifdef HAS_SA_LEN
+ ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in);
+#endif
+ inet_addr_list_append(mask_list, &ifr_mask->ifr_addr);
+ myfree((void *) ifr_mask);
+ }
+ }
+ }
+#ifdef HAS_IPV6
+ else if (af == AF_INET6) {
+ struct sockaddr *sa;
+
+ sa = SOCK_ADDR_PTR(&ifr->ifr_addr);
+ if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) {
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list) {
+ /* XXX Assume /128 for everything */
+ struct sockaddr_in6 mask6;
+
+ mask6 = *SOCK_ADDR_IN6_PTR(sa);
+ memset((void *) &mask6.sin6_addr, ~0,
+ sizeof(mask6.sin6_addr));
+ inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6));
+ }
+ }
+ }
+#endif
+ ifr = NEXT_INTERFACE(ifr);
+ }
+ vstring_free(buf);
+ (void) close(sock);
+ return (0);
+}
+
+#endif /* HAVE_SIOCGLIF */
+
+#ifdef HAS_PROCNET_IFINET6
+
+/*
+ * Older Linux versions lack proper calls to retrieve IPv6 interface
+ * addresses. Instead, the addresses can be read from a file in the
+ * /proc tree. The most important issue with this approach however
+ * is that the /proc tree may not always be available, for example
+ * in a chrooted environment or in "hardened" (sic) installations.
+ */
+
+/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */
+
+static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list)
+{
+ const char *myname = "inet_addr_local[procnet_ifinet6]";
+ FILE *fp;
+ char buf[BUFSIZ];
+ unsigned plen;
+ VSTRING *addrbuf;
+ struct sockaddr_in6 addr;
+ struct sockaddr_in6 mask;
+
+ /*
+ * Example: 00000000000000000000000000000001 01 80 10 80 lo
+ *
+ * Fields: address, interface index, prefix length, scope value
+ * (net/ipv6.h), interface flags (linux/rtnetlink.h), device name.
+ *
+ * FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected
+ * input. Use fgets() + sscanf() instead.
+ */
+ if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) {
+ addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1);
+ memset((void *) &addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+#ifdef HAS_SA_LEN
+ addr.sin6_len = sizeof(addr);
+#endif
+ mask = addr;
+ while (fgets(buf, sizeof(buf), fp) != 0) {
+ /* 200501 hex_decode() is light-weight compared to getaddrinfo(). */
+ if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0
+ || sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1
+ || plen > MAI_V6ADDR_BITS) {
+ msg_warn("unexpected data in %s - skipping IPv6 configuration",
+ _PATH_PROCNET_IFINET6);
+ break;
+ }
+ /* vstring_str(addrbuf) has worst-case alignment. */
+ addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf);
+ inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr));
+
+ memset((void *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr));
+ mask_addr((unsigned char *) &mask.sin6_addr,
+ sizeof(mask.sin6_addr), plen);
+ inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask));
+ }
+ vstring_free(addrbuf);
+ fclose(fp); /* FIX 200501 */
+ } else {
+ msg_warn("can't open %s (%m) - skipping IPv6 configuration",
+ _PATH_PROCNET_IFINET6);
+ }
+ return (0);
+}
+
+#endif /* HAS_PROCNET_IFINET6 */
+
+/* inet_addr_local - find all IP addresses for this host */
+
+int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list,
+ unsigned *addr_family_list)
+{
+ const char *myname = "inet_addr_local";
+ int initial_count = addr_list->used;
+ unsigned family;
+ int count;
+
+ while ((family = *addr_family_list++) != 0) {
+
+ /*
+ * IP Version 4
+ */
+ if (family == AF_INET) {
+ count = addr_list->used;
+#if defined(HAVE_GETIFADDRS)
+ ial_getifaddrs(addr_list, mask_list, AF_INET);
+#elif defined (HAS_SIOCGLIF)
+ ial_siocglif(addr_list, mask_list, AF_INET);
+#else
+ ial_siocgif(addr_list, mask_list, AF_INET);
+#endif
+ if (msg_verbose)
+ msg_info("%s: configured %d IPv4 addresses",
+ myname, addr_list->used - count);
+ }
+
+ /*
+ * IP Version 6
+ */
+#ifdef HAS_IPV6
+ else if (family == AF_INET6) {
+ count = addr_list->used;
+#if defined(HAVE_GETIFADDRS)
+ ial_getifaddrs(addr_list, mask_list, AF_INET6);
+#elif defined(HAS_PROCNET_IFINET6)
+ ial_procnet_ifinet6(addr_list, mask_list);
+#elif defined(HAS_SIOCGLIF)
+ ial_siocglif(addr_list, mask_list, AF_INET6);
+#else
+ ial_siocgif(addr_list, mask_list, AF_INET6);
+#endif
+ if (msg_verbose)
+ msg_info("%s: configured %d IPv6 addresses", myname,
+ addr_list->used - count);
+ }
+#endif
+
+ /*
+ * Something's not right.
+ */
+ else
+ msg_panic("%s: unknown address family %d", myname, family);
+ }
+ return (addr_list->used - initial_count);
+}
+
+#ifdef TEST
+
+#include <string.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <inet_proto.h>
+
+int main(int unused_argc, char **argv)
+{
+ INET_ADDR_LIST addr_list;
+ INET_ADDR_LIST mask_list;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_HOSTADDR_STR hostmask;
+ struct sockaddr *sa;
+ int i;
+ INET_PROTO_INFO *proto_info;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+
+ proto_info = inet_proto_init(argv[0],
+ argv[1] ? argv[1] : INET_PROTO_NAME_ALL);
+ inet_addr_list_init(&addr_list);
+ inet_addr_list_init(&mask_list);
+ inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list);
+
+ if (addr_list.used == 0)
+ msg_fatal("cannot find any active network interfaces");
+
+ if (addr_list.used == 1)
+ msg_warn("found only one active network interface");
+
+ for (i = 0; i < addr_list.used; i++) {
+ sa = SOCK_ADDR_PTR(addr_list.addrs + i);
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ sa = SOCK_ADDR_PTR(mask_list.addrs + i);
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostmask, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ inet_addr_list_free(&addr_list);
+ inet_addr_list_free(&mask_list);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_local.h b/src/util/inet_addr_local.h
new file mode 100644
index 0000000..cb6b3f2
--- /dev/null
+++ b/src/util/inet_addr_local.h
@@ -0,0 +1,35 @@
+#ifndef _INET_ADDR_LOCAL_H_INCLUDED_
+#define _INET_ADDR_LOCAL_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_local 3h
+/* SUMMARY
+/* determine if IP address is local
+/* SYNOPSIS
+/* #include <inet_addr_local.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern int inet_addr_local(INET_ADDR_LIST *, INET_ADDR_LIST *, unsigned *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/inet_connect.c b/src/util/inet_connect.c
new file mode 100644
index 0000000..0f5542e
--- /dev/null
+++ b/src/util/inet_connect.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* inet_connect 3
+/* SUMMARY
+/* connect to TCP listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int inet_windowsize;
+/*
+/* int inet_connect(addr, block_mode, timeout)
+/* const char *addr;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* inet_connect connects to a TCP listener at
+/* the specified address, and returns the resulting file descriptor.
+/*
+/* Specify an inet_windowsize value > 0 to override the TCP
+/* window size that the client advertises to the server.
+/*
+/* Arguments:
+/* .IP addr
+/* The destination to connect to. The format is host:port. If no
+/* host is specified, a port on the local host is assumed.
+/* Host and port information may be given in numerical form
+/* or as symbolical names.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP timeout
+/* Bounds the number of seconds that the operation may take. Specify
+/* a value <= 0 to disable the time limit.
+/* DIAGNOSTICS
+/* The result is -1 when the connection could not be made.
+/* The nature of the error is available via the global \fIerrno\fR
+/* variable.
+/* Fatal errors: other system call failures.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "iostuff.h"
+#include "host_port.h"
+#include "sane_connect.h"
+#include "connect.h"
+#include "timed_connect.h"
+#include "myaddrinfo.h"
+#include "sock_addr.h"
+#include "inet_proto.h"
+
+static int inet_connect_one(struct addrinfo *, int, int);
+
+/* inet_connect - connect to TCP listener */
+
+int inet_connect(const char *addr, int block_mode, int timeout)
+{
+ char *buf;
+ char *host;
+ char *port;
+ const char *parse_err;
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ int sock;
+ MAI_HOSTADDR_STR hostaddr;
+ const INET_PROTO_INFO *proto_info;
+ int found;
+
+ /*
+ * Translate address information to internal form. No host defaults to
+ * the local host.
+ */
+ buf = mystrdup(addr);
+ if ((parse_err = host_port(buf, &host, "localhost", &port, (char *) 0)) != 0)
+ msg_fatal("%s: %s", addr, parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0)
+ msg_warn("host or service %s not found: %s",
+ addr, MAI_STRERROR(aierr));
+ myfree(buf);
+ if (aierr) {
+ errno = EADDRNOTAVAIL; /* for up-stream "%m" */
+ return (-1);
+ }
+ proto_info = inet_proto_info();
+ for (sock = -1, found = 0, res = res0; res != 0; res = res->ai_next) {
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ found++;
+
+ /*
+ * In case of multiple addresses, show what address we're trying now.
+ */
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("trying... [%s]", hostaddr.buf);
+ }
+ if ((sock = inet_connect_one(res, block_mode, timeout)) < 0) {
+ if (msg_verbose)
+ msg_info("%m");
+ } else
+ break;
+ }
+ if (found == 0)
+ msg_fatal("host not found: %s", addr);
+ freeaddrinfo(res0);
+ return (sock);
+}
+
+/* inet_connect_one - try to connect to one address */
+
+static int inet_connect_one(struct addrinfo * res, int block_mode, int timeout)
+{
+ int sock;
+
+ /*
+ * Create a client socket.
+ */
+ sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sock < 0)
+ return (-1);
+
+ /*
+ * Window scaling workaround.
+ */
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+
+ /*
+ * Timed connect.
+ */
+ if (timeout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ if (timed_connect(sock, res->ai_addr, res->ai_addrlen, timeout) < 0) {
+ close(sock);
+ return (-1);
+ }
+ if (block_mode != NON_BLOCKING)
+ non_blocking(sock, block_mode);
+ return (sock);
+ }
+
+ /*
+ * Maybe block until connected.
+ */
+ else {
+ non_blocking(sock, block_mode);
+ if (sane_connect(sock, res->ai_addr, res->ai_addrlen) < 0
+ && errno != EINPROGRESS) {
+ close(sock);
+ return (-1);
+ }
+ return (sock);
+ }
+}
diff --git a/src/util/inet_listen.c b/src/util/inet_listen.c
new file mode 100644
index 0000000..31800cd
--- /dev/null
+++ b/src/util/inet_listen.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* inet_listen 3
+/* SUMMARY
+/* start TCP listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int inet_windowsize;
+/*
+/* int inet_listen(addr, backlog, block_mode)
+/* const char *addr;
+/* int backlog;
+/* int block_mode;
+/*
+/* int inet_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* The \fBinet_listen\fR routine starts a TCP listener
+/* on the specified address, with the specified backlog, and returns
+/* the resulting file descriptor.
+/*
+/* inet_accept() accepts a connection and sanitizes error results.
+/*
+/* Specify an inet_windowsize value > 0 to override the TCP
+/* window size that the server advertises to the client.
+/*
+/* Arguments:
+/* .IP addr
+/* The communication endpoint to listen on. The syntax is "host:port".
+/* Host and port may be specified in symbolic form or numerically.
+/* A null host field means listen on all network interfaces.
+/* .IP backlog
+/* This argument is passed on to the \fIlisten(2)\fR routine.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP fd
+/* File descriptor returned by inet_listen().
+/* DIAGNOSTICS
+/* Fatal errors: inet_listen() aborts upon any system call failure.
+/* inet_accept() leaves all error handling up to the caller.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#ifndef MAXHOSTNAMELEN
+#include <sys/param.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "host_port.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "sane_accept.h"
+#include "myaddrinfo.h"
+#include "sock_addr.h"
+#include "inet_proto.h"
+
+/* inet_listen - create TCP listener */
+
+int inet_listen(const char *addr, int backlog, int block_mode)
+{
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ int sock;
+ int on = 1;
+ char *buf;
+ char *host;
+ char *port;
+ const char *parse_err;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_SERVPORT_STR portnum;
+ const INET_PROTO_INFO *proto_info;
+
+ /*
+ * Translate address information to internal form.
+ */
+ buf = mystrdup(addr);
+ if ((parse_err = host_port(buf, &host, "", &port, (char *) 0)) != 0)
+ msg_fatal("%s: %s", addr, parse_err);
+ if (*host == 0)
+ host = 0;
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0)
+ msg_fatal("%s: %s", addr, MAI_STRERROR(aierr));
+ myfree(buf);
+ /* No early returns or res0 leaks. */
+
+ proto_info = inet_proto_info();
+ for (res = res0; /* see below */ ; res = res->ai_next) {
+
+ /*
+ * No usable address found.
+ */
+ if (res == 0)
+ msg_fatal("%s: host found but no usable address", addr);
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) != 0)
+ break;
+
+ msg_info("skipping address family %d for %s", res->ai_family, addr);
+ }
+
+ /*
+ * Show what address we're trying.
+ */
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, &portnum, 0);
+ msg_info("trying... [%s]:%s", hostaddr.buf, portnum.buf);
+ }
+
+ /*
+ * Create a listener socket.
+ */
+ if ((sock = socket(res->ai_family, res->ai_socktype, 0)) < 0)
+ msg_fatal("socket: %m");
+#ifdef HAS_IPV6
+#if defined(IPV6_V6ONLY) && !defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ if (res->ai_family == AF_INET6
+ && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(IPV6_V6ONLY): %m");
+#endif
+#endif
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEADDR): %m");
+#if defined(SO_REUSEPORT_LB)
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEPORT_LB): %m");
+#elif defined(SO_REUSEPORT)
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEPORT): %m");
+#endif
+ if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, &portnum, 0);
+ msg_fatal("bind %s port %s: %m", hostaddr.buf, portnum.buf);
+ }
+ freeaddrinfo(res0);
+ non_blocking(sock, block_mode);
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+ if (listen(sock, backlog) < 0)
+ msg_fatal("listen: %m");
+ return (sock);
+}
+
+/* inet_accept - accept connection */
+
+int inet_accept(int fd)
+{
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE ss_len = sizeof(ss);
+
+ return (sane_accept(fd, (struct sockaddr *) &ss, &ss_len));
+}
diff --git a/src/util/inet_proto.c b/src/util/inet_proto.c
new file mode 100644
index 0000000..fedf761
--- /dev/null
+++ b/src/util/inet_proto.c
@@ -0,0 +1,328 @@
+/*++
+/* NAME
+/* inet_proto 3
+/* SUMMARY
+/* convert protocol names to assorted constants
+/* SYNOPSIS
+/* #include <inet_proto.h>
+/*
+/* typedef struct {
+/* .in +4
+/* unsigned ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */
+/* unsigned *ai_family_list; /* PF_INET and/or PF_INET6 */
+/* unsigned *dns_atype_list;/* TAAAA and/or TA */
+/* unsigned char *sa_family_list;/* AF_INET6 and/or AF_INET */
+/* .in -4
+/* } INET_PROTO_INFO;
+/*
+/* const INET_PROTO_INFO *inet_proto_init(context, protocols)
+/*
+/* const INET_PROTO_INFO *inet_proto_info()
+/* DESCRIPTION
+/* inet_proto_init() converts a string with protocol names
+/* into null-terminated lists of appropriate constants used
+/* by Postfix library routines. The idea is that one should
+/* be able to configure an MTA for IPv4 only, without having
+/* to recompile code (what a concept).
+/*
+/* Unfortunately, some compilers won't link initialized data
+/* without a function call into the same source module, so
+/* we invoke inet_proto_info() in order to access the result
+/* from inet_proto_init() from within library routines.
+/* inet_proto_info() also conveniently initializes the data
+/* to built-in defaults.
+/*
+/* Arguments:
+/* .IP context
+/* Typically, a configuration parameter name.
+/* .IP protocols
+/* Null-terminated string with protocol names separated by
+/* whitespace and/or commas:
+/* .RS
+/* .IP INET_PROTO_NAME_ALL
+/* Enable all available IP protocols.
+/* .IP INET_PROTO_NAME_IPV4
+/* Enable IP version 4 support.
+/* .IP INET_PROTO_NAME_IPV6
+/* Enable IP version 6 support.
+/* .RS
+/* .PP
+/* Results:
+/* .IP ai_family
+/* Only one of PF_UNSPEC, PF_INET, or PF_INET6. This can be
+/* used as input for the getaddrinfo() and getnameinfo()
+/* routines.
+/* .IP ai_family_list
+/* One or more of PF_INET or PF_INET6. This can be used as
+/* input for the inet_addr_local() routine.
+/* .IP dns_atype_list
+/* One or more of T_AAAA or T_A. This can be used as input for
+/* the dns_lookup_v() and dns_lookup_l() routines.
+/* .IP sa_family_list
+/* One or more of AF_INET6 or AF_INET. This can be used as an
+/* output filter for the results from the getaddrinfo() and
+/* getnameinfo() routines.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* This module will warn and turn off support for any protocol
+/* that is requested but unavailable.
+/*
+/* Fatal errors: memory allocation problem.
+/* 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 <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef RESOLVE_H_NEEDS_STDIO_H
+#include <stdio.h>
+#endif
+#include <resolv.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <name_mask.h>
+#include <inet_proto.h>
+
+ /*
+ * Application-specific.
+ */
+
+ /*
+ * Run-time initialization, so we can work around LINUX where IPv6 falls
+ * flat on its face because it is not turned on in the kernel.
+ */
+INET_PROTO_INFO *inet_proto_table = 0;
+
+ /*
+ * Infrastructure: lookup table with the protocol names that we support.
+ */
+#define INET_PROTO_MASK_IPV4 (1<<0)
+#define INET_PROTO_MASK_IPV6 (1<<1)
+
+static const NAME_MASK proto_table[] = {
+#ifdef HAS_IPV6
+ INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4 | INET_PROTO_MASK_IPV6,
+ INET_PROTO_NAME_IPV6, INET_PROTO_MASK_IPV6,
+#else
+ INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4,
+#endif
+ INET_PROTO_NAME_IPV4, INET_PROTO_MASK_IPV4,
+ 0,
+};
+
+/* make_uchar_vector - create and initialize uchar vector */
+
+static unsigned char *make_uchar_vector(int len,...)
+{
+ const char *myname = "make_uchar_vector";
+ va_list ap;
+ int count;
+ unsigned char *vp;
+
+ va_start(ap, len);
+ if (len <= 0)
+ msg_panic("%s: bad vector length: %d", myname, len);
+ vp = (unsigned char *) mymalloc(sizeof(*vp) * len);
+ for (count = 0; count < len; count++)
+ vp[count] = va_arg(ap, unsigned);
+ va_end(ap);
+ return (vp);
+}
+
+/* make_unsigned_vector - create and initialize integer vector */
+
+static unsigned *make_unsigned_vector(int len,...)
+{
+ const char *myname = "make_unsigned_vector";
+ va_list ap;
+ int count;
+ unsigned *vp;
+
+ va_start(ap, len);
+ if (len <= 0)
+ msg_panic("%s: bad vector length: %d", myname, len);
+ vp = (unsigned *) mymalloc(sizeof(*vp) * len);
+ for (count = 0; count < len; count++)
+ vp[count] = va_arg(ap, unsigned);
+ va_end(ap);
+ return (vp);
+}
+
+/* inet_proto_free - destroy data */
+
+static void inet_proto_free(INET_PROTO_INFO *pf)
+{
+ myfree((void *) pf->ai_family_list);
+ myfree((void *) pf->dns_atype_list);
+ myfree((void *) pf->sa_family_list);
+ myfree((void *) pf);
+}
+
+/* inet_proto_init - convert protocol names to library inputs */
+
+const INET_PROTO_INFO *inet_proto_init(const char *context, const char *protocols)
+{
+ const char *myname = "inet_proto";
+ INET_PROTO_INFO *pf;
+ int inet_proto_mask;
+ int sock;
+
+ /*
+ * Avoid run-time errors when all network protocols are disabled. We
+ * can't look up interface information, and we can't convert explicit
+ * names or addresses.
+ */
+ inet_proto_mask = name_mask(context, proto_table, protocols);
+#ifdef HAS_IPV6
+ if (inet_proto_mask & INET_PROTO_MASK_IPV6) {
+ if ((sock = socket(PF_INET6, SOCK_STREAM, 0)) >= 0) {
+ close(sock);
+ } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
+ msg_warn("%s: disabling IPv6 name/address support: %m", context);
+ inet_proto_mask &= ~INET_PROTO_MASK_IPV6;
+ } else {
+ msg_fatal("socket: %m");
+ }
+ }
+#endif
+ if (inet_proto_mask & INET_PROTO_MASK_IPV4) {
+ if ((sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0) {
+ close(sock);
+ } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
+ msg_warn("%s: disabling IPv4 name/address support: %m", context);
+ inet_proto_mask &= ~INET_PROTO_MASK_IPV4;
+ } else {
+ msg_fatal("socket: %m");
+ }
+ }
+
+ /*
+ * Store address family etc. info as null-terminated vectors. If that
+ * breaks because we must be able to store nulls, we'll deal with the
+ * additional complexity.
+ *
+ * XXX Use compile-time initialized data templates instead of building the
+ * reply on the fly.
+ */
+ switch (inet_proto_mask) {
+#ifdef HAS_IPV6
+ case INET_PROTO_MASK_IPV6:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_INET6;
+ pf->ai_family_list = make_unsigned_vector(2, PF_INET6, 0);
+ pf->dns_atype_list = make_unsigned_vector(2, T_AAAA, 0);
+ pf->sa_family_list = make_uchar_vector(2, AF_INET6, 0);
+ break;
+ case (INET_PROTO_MASK_IPV6 | INET_PROTO_MASK_IPV4):
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_UNSPEC;
+ pf->ai_family_list = make_unsigned_vector(3, PF_INET, PF_INET6, 0);
+ pf->dns_atype_list = make_unsigned_vector(3, T_A, T_AAAA, 0);
+ pf->sa_family_list = make_uchar_vector(3, AF_INET, AF_INET6, 0);
+ break;
+#endif
+ case INET_PROTO_MASK_IPV4:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_INET;
+ pf->ai_family_list = make_unsigned_vector(2, PF_INET, 0);
+ pf->dns_atype_list = make_unsigned_vector(2, T_A, 0);
+ pf->sa_family_list = make_uchar_vector(2, AF_INET, 0);
+ break;
+ case 0:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_UNSPEC;
+ pf->ai_family_list = make_unsigned_vector(1, 0);
+ pf->dns_atype_list = make_unsigned_vector(1, 0);
+ pf->sa_family_list = make_uchar_vector(1, 0);
+ break;
+ default:
+ msg_panic("%s: bad inet_proto_mask 0x%x", myname, inet_proto_mask);
+ }
+ if (inet_proto_table)
+ inet_proto_free(inet_proto_table);
+ return (inet_proto_table = pf);
+}
+
+#ifdef TEST
+
+ /*
+ * Small driver for unit tests.
+ */
+
+static char *print_unsigned_vector(VSTRING *buf, unsigned *vector)
+{
+ unsigned *p;
+
+ VSTRING_RESET(buf);
+ for (p = vector; *p; p++) {
+ vstring_sprintf_append(buf, "%u", *p);
+ if (p[1])
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+}
+
+static char *print_uchar_vector(VSTRING *buf, unsigned char *vector)
+{
+ unsigned char *p;
+
+ VSTRING_RESET(buf);
+ for (p = vector; *p; p++) {
+ vstring_sprintf_append(buf, "%u", *p);
+ if (p[1])
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+}
+
+int main(int argc, char **argv)
+{
+ const char *myname = argv[0];
+ INET_PROTO_INFO *pf;
+ VSTRING *buf;
+
+ if (argc < 2)
+ msg_fatal("usage: %s protocol(s)...", myname);
+
+ buf = vstring_alloc(10);
+ while (*++argv) {
+ msg_info("=== %s ===", *argv);
+ inet_proto_init(myname, *argv);
+ pf = inet_proto_table;
+ msg_info("ai_family = %u", pf->ai_family);
+ msg_info("ai_family_list = %s",
+ print_unsigned_vector(buf, pf->ai_family_list));
+ msg_info("dns_atype_list = %s",
+ print_unsigned_vector(buf, pf->dns_atype_list));
+ msg_info("sa_family_list = %s",
+ print_uchar_vector(buf, pf->sa_family_list));
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_proto.h b/src/util/inet_proto.h
new file mode 100644
index 0000000..9175eae
--- /dev/null
+++ b/src/util/inet_proto.h
@@ -0,0 +1,56 @@
+#ifndef _INET_PROTO_INFO_H_INCLUDED_
+#define _INET_PROTO_INFO_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_proto_info 3h
+/* SUMMARY
+/* convert protocol names to assorted constants
+/* SYNOPSIS
+/* #include <inet_proto_info.h>
+ DESCRIPTION
+ .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ unsigned int ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */
+ unsigned int *ai_family_list; /* PF_INET and/or PF_INET6 */
+ unsigned int *dns_atype_list; /* TAAAA and/or TA */
+ unsigned char *sa_family_list; /* AF_INET6 and/or AF_INET */
+} INET_PROTO_INFO;
+
+ /*
+ * Some compilers won't link initialized data unless we call a function in
+ * the same source file. Therefore, inet_proto_info() is a function instead
+ * of a global variable.
+ */
+#define inet_proto_info() \
+ (inet_proto_table ? (const INET_PROTO_INFO*) inet_proto_table : \
+ inet_proto_init("default protocol setting", DEF_INET_PROTOCOLS))
+
+extern const INET_PROTO_INFO *inet_proto_init(const char *, const char *);
+extern INET_PROTO_INFO *inet_proto_table;
+
+#define INET_PROTO_NAME_IPV6 "ipv6"
+#define INET_PROTO_NAME_IPV4 "ipv4"
+#define INET_PROTO_NAME_ALL "all"
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/inet_trigger.c b/src/util/inet_trigger.c
new file mode 100644
index 0000000..b1734ee
--- /dev/null
+++ b/src/util/inet_trigger.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* inet_trigger 3
+/* SUMMARY
+/* wakeup INET-domain server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int inet_trigger(service, buf, len, timeout)
+/* char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* inet_trigger() wakes up the named INET-domain server by making
+/* a brief connection to it and by writing the contents of the
+/* named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* BUGS
+/* SEE ALSO
+/* inet_connect(3), INET-domain client
+/* 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/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct inet_trigger {
+ int fd;
+ char *service;
+};
+
+/* inet_trigger_event - disconnect from peer */
+
+static void inet_trigger_event(int event, void *context)
+{
+ struct inet_trigger *ip = (struct inet_trigger *) context;
+ static const char *myname = "inet_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, ip->service);
+ event_disable_readwrite(ip->fd);
+ event_cancel_timer(inet_trigger_event, context);
+ if (close(ip->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, ip->service);
+ myfree(ip->service);
+ myfree((void *) ip);
+}
+
+
+/* inet_trigger - wakeup INET-domain server */
+
+int inet_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "inet_trigger";
+ struct inet_trigger *ip;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ ip = (struct inet_trigger *) mymalloc(sizeof(*ip));
+ ip->fd = fd;
+ ip->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(inet_trigger_event, (void *) ip, timeout + 100);
+ event_enable_read(fd, inet_trigger_event, (void *) ip);
+ return (0);
+}
diff --git a/src/util/inet_windowsize.c b/src/util/inet_windowsize.c
new file mode 100644
index 0000000..e982149
--- /dev/null
+++ b/src/util/inet_windowsize.c
@@ -0,0 +1,82 @@
+/*++
+/* NAME
+/* inet_windowsize 3
+/* SUMMARY
+/* TCP window scaling control
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int inet_windowsize;
+/*
+/* void set_inet_windowsize(sock, windowsize)
+/* int sock;
+/* int windowsize;
+/* DESCRIPTION
+/* set_inet_windowsize() overrides the default TCP window size
+/* with the specified value. When called before listen() or
+/* accept(), this works around broken infrastructure that
+/* mis-handles TCP window scaling options.
+/*
+/* The global inet_windowsize variable is available for other
+/* routines to remember that they wish to override the default
+/* TCP window size. The variable is not accessed by the
+/* set_inet_windowsize() function itself.
+/*
+/* Arguments:
+/* .IP sock
+/* TCP communication endpoint, before the connect(2) or listen(2) call.
+/* .IP windowsize
+/* The preferred TCP window size. This must be > 0.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/* Warnings: some error return from setsockopt().
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application storage. */
+
+ /*
+ * Tunable to work around broken routers.
+ */
+int inet_windowsize = 0;
+
+/* set_inet_windowsize - set TCP send/receive window size */
+
+void set_inet_windowsize(int sock, int windowsize)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (windowsize <= 0)
+ msg_panic("inet_windowsize: bad window size %d", windowsize);
+
+ /*
+ * Generic implementation: set the send and receive buffer size before
+ * listen() or connect().
+ */
+ if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &windowsize,
+ sizeof(windowsize)) < 0)
+ msg_warn("setsockopt SO_SNDBUF %d: %m", windowsize);
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *) &windowsize,
+ sizeof(windowsize)) < 0)
+ msg_warn("setsockopt SO_RCVBUF %d: %m", windowsize);
+}
diff --git a/src/util/iostuff.h b/src/util/iostuff.h
new file mode 100644
index 0000000..0c54d03
--- /dev/null
+++ b/src/util/iostuff.h
@@ -0,0 +1,73 @@
+#ifndef _IOSTUFF_H_INCLUDED_
+#define _IOSTUFF_H_INCLUDED_
+
+/*++
+/* NAME
+/* iostuff 3h
+/* SUMMARY
+/* miscellaneous I/O primitives
+/* SYNOPSIS
+/* #include <iostuff.h>
+/* DESCRIPTION
+
+ /*
+ * External interface.
+ */
+extern int non_blocking(int, int);
+extern int close_on_exec(int, int);
+extern int open_limit(int);
+extern int poll_fd(int, int, int, int, int);
+extern off_t get_file_limit(void);
+extern void set_file_limit(off_t);
+extern ssize_t peekfd(int);
+extern ssize_t write_buf(int, const char *, ssize_t, int);
+extern ssize_t timed_read(int, void *, size_t, int, void *);
+extern ssize_t timed_write(int, const void *, size_t, int, void *);
+extern void doze(unsigned);
+extern void rand_sleep(unsigned, unsigned);
+extern int duplex_pipe(int *);
+extern int stream_recv_fd(int);
+extern int stream_send_fd(int, int);
+extern int unix_recv_fd(int);
+extern int unix_send_fd(int, int);
+extern ssize_t dummy_read(int, void *, size_t, int, void *);
+extern ssize_t dummy_write(int, void *, size_t, int, void *);
+
+#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1, 0)
+#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1, 0)
+
+#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1)
+#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1)
+
+extern int inet_windowsize;
+extern void set_inet_windowsize(int, int);
+
+#define POLL_FD_READ 0
+#define POLL_FD_WRITE 1
+
+#define BLOCKING 0
+#define NON_BLOCKING 1
+
+#define CLOSE_ON_EXEC 1
+#define PASS_ON_EXEC 0
+
+extern int unix_pass_fd_fix;
+extern void set_unix_pass_fd_fix(const char *);
+
+#define UNIX_PASS_FD_FIX_NONE (0)
+#define UNIX_PASS_FD_FIX_CMSG_LEN (1<<0)
+
+/* 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
+/* CREATION DATE
+/* Sat Jan 25 16:54:13 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/ip_match.c b/src/util/ip_match.c
new file mode 100644
index 0000000..aeea799
--- /dev/null
+++ b/src/util/ip_match.c
@@ -0,0 +1,676 @@
+/*++
+/* NAME
+/* ip_match 3
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/*
+/* char *ip_match_parse(byte_codes, pattern)
+/* VSTRING *byte_codes;
+/* char *pattern;
+/*
+/* char *ip_match_save(byte_codes)
+/* const VSTRING *byte_codes;
+/*
+/* int ip_match_execute(byte_codes, addr_bytes)
+/* cost char *byte_codes;
+/* const char *addr_bytes;
+/*
+/* char *ip_match_dump(printable, byte_codes)
+/* VSTRING *printable;
+/* const char *byte_codes;
+/* DESCRIPTION
+/* This module supports IP address pattern matching. See below
+/* for a description of the supported address pattern syntax.
+/*
+/* This implementation aims to minimize the cost of encoding
+/* the pattern in internal form, while still providing good
+/* matching performance in the typical case. The first byte
+/* of an encoded pattern specifies the expected address family
+/* (for example, AF_INET); other details of the encoding are
+/* private and are subject to change.
+/*
+/* ip_match_parse() converts the user-specified pattern to
+/* internal form. The result value is a null pointer in case
+/* of success, or a pointer into the byte_codes buffer with a
+/* detailed problem description.
+/*
+/* ip_match_save() saves the result from ip_match_parse() for
+/* longer-term usage. The result should be passed to myfree().
+/*
+/* ip_match_execute() matches a binary network in addr_bytes
+/* against a byte-code array in byte_codes. It is an error to
+/* use different address families for the byte_codes and addr_bytes
+/* arguments (the first byte-code value contains the expected
+/* address family). The result is non-zero in case of success.
+/*
+/* ip_match_dump() produces an ASCII dump of a byte-code array.
+/* The dump is supposed to be identical to the input pattern
+/* modulo upper/lower case or leading nulls with IPv6). This
+/* function is primarily a debugging aid.
+/*
+/* Arguments
+/* .IP addr_bytes
+/* Binary network address in network-byte order.
+/* .IP byte_codes
+/* Byte-code array produced by ip_match_parse().
+/* .IP pattern
+/* Human-readable address pattern.
+/* .IP printable
+/* storage for ASCII dump of a byte-code array.
+/* IPV4 PATTERN SYNTAX
+/* .ad
+/* .fi
+/* An IPv4 address pattern has four fields separated by ".".
+/* Each field is either a decimal number, or a sequence inside
+/* "[]" that contains one or more ";"-separated decimal
+/* numbers or number..number ranges.
+/*
+/* Examples of patterns are 1.2.3.4 (matches itself, as one
+/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4,
+/* 1.2.3.6, 1.2.3.7, 1.2.3.8).
+/*
+/* Thus, any pattern field can be a sequence inside "[]", but
+/* a "[]" sequence cannot span multiple address fields, and
+/* a pattern field cannot contain both a number and a "[]"
+/* sequence at the same time.
+/*
+/* This means that the pattern 1.2.[3.4] is not valid (the
+/* sequence [3.4] cannot span two address fields) and the
+/* pattern 1.2.3.3[6..9] is also not valid (the last field
+/* cannot be both number 3 and sequence [6..9] at the same
+/* time).
+/*
+/* The syntax for IPv4 patterns is as follows:
+/*
+/* .in +5
+/* v4pattern = v4field "." v4field "." v4field "." v4field
+/* .br
+/* v4field = v4octet | "[" v4sequence "]"
+/* .br
+/* v4octet = any decimal number in the range 0 through 255
+/* .br
+/* v4sequence = v4seq_member | v4sequence ";" v4seq_member
+/* .br
+/* v4seq_member = v4octet | v4octet ".." v4octet
+/* .in
+/* 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/socket.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <ip_match.h>
+
+ /*
+ * Token values. The in-band values are also used as byte-code values.
+ */
+#define IP_MATCH_CODE_OPEN '[' /* in-band */
+#define IP_MATCH_CODE_CLOSE ']' /* in-band */
+#define IP_MATCH_CODE_OVAL 'N' /* in-band */
+#define IP_MATCH_CODE_RANGE 'R' /* in-band */
+#define IP_MATCH_CODE_EOF '\0' /* in-band */
+#define IP_MATCH_CODE_ERR 256 /* out-of-band */
+
+ /*
+ * SLMs.
+ */
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* ip_match_save - make longer-term copy of byte code */
+
+char *ip_match_save(const VSTRING *byte_codes)
+{
+ char *dst;
+
+ dst = mymalloc(LEN(byte_codes));
+ return (memcpy(dst, STR(byte_codes), LEN(byte_codes)));
+}
+
+/* ip_match_dump - byte-code pretty printer */
+
+char *ip_match_dump(VSTRING *printable, const char *byte_codes)
+{
+ const char *myname = "ip_match_dump";
+ const unsigned char *bp;
+ int octet_count = 0;
+ int ch;
+
+ /*
+ * Sanity check. Use different dumping loops for AF_INET and AF_INET6.
+ */
+ if (*byte_codes != AF_INET)
+ msg_panic("%s: malformed byte-code header", myname);
+
+ /*
+ * Pretty-print and sanity-check the byte codes. Note: the loops in this
+ * code have no auto-increment at the end of the iteration. Instead, each
+ * byte-code handler bumps the byte-code pointer appropriately.
+ */
+ VSTRING_RESET(printable);
+ bp = (const unsigned char *) byte_codes + 1;
+ for (;;) {
+
+ /*
+ * Simple numeric field.
+ */
+ if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
+ vstring_sprintf_append(printable, "%d", *bp);
+ bp += 1;
+ }
+
+ /*
+ * Wild-card numeric field.
+ */
+ else if (ch == IP_MATCH_CODE_OPEN) {
+ vstring_sprintf_append(printable, "[");
+ for (;;) {
+ /* Numeric range. */
+ if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
+ vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]);
+ bp += 2;
+ }
+ /* Number. */
+ else if (ch == IP_MATCH_CODE_OVAL) {
+ vstring_sprintf_append(printable, "%d", *bp);
+ bp += 1;
+ }
+ /* End-of-wildcard. */
+ else if (ch == IP_MATCH_CODE_CLOSE) {
+ break;
+ }
+ /* Corruption. */
+ else {
+ msg_panic("%s: unexpected byte code (decimal %d) "
+ "after \"%s\"", myname, ch, STR(printable));
+ }
+ /* Output the wild-card field separator and repeat the loop. */
+ if (*bp != IP_MATCH_CODE_CLOSE)
+ vstring_sprintf_append(printable, ";");
+ }
+ vstring_sprintf_append(printable, "]");
+ }
+
+ /*
+ * Corruption.
+ */
+ else {
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, STR(printable));
+ }
+
+ /*
+ * Require four octets, not one more, not one less.
+ */
+ if (++octet_count == 4) {
+ if (*bp != 0)
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, STR(printable));
+ return (STR(printable));
+ }
+ if (*bp == 0)
+ msg_panic("%s: truncated byte code after \"%s\"",
+ myname, STR(printable));
+
+ /*
+ * Output the address field separator and repeat the loop.
+ */
+ vstring_sprintf_append(printable, ".");
+ }
+}
+
+/* ip_match_print_code_prefix - printable byte-code prefix */
+
+static char *ip_match_print_code_prefix(const char *byte_codes, size_t len)
+{
+ static VSTRING *printable = 0;
+ const char *fmt;
+ const char *bp;
+
+ /*
+ * This is primarily for emergency debugging so we don't care about
+ * non-reentrancy.
+ */
+ if (printable == 0)
+ printable = vstring_alloc(100);
+ else
+ VSTRING_RESET(printable);
+
+ /*
+ * Use decimal for IPv4 and hexadecimal otherwise, so that address octet
+ * values are easy to recognize.
+ */
+ fmt = (*byte_codes == AF_INET ? "%d " : "%02x ");
+ for (bp = byte_codes; bp < byte_codes + len; bp++)
+ vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp);
+
+ return (STR(printable));
+}
+
+/* ip_match_execute - byte-code matching engine */
+
+int ip_match_execute(const char *byte_codes, const char *addr_bytes)
+{
+ const char *myname = "ip_match_execute";
+ const unsigned char *bp;
+ const unsigned char *ap;
+ int octet_count = 0;
+ int ch;
+ int matched;
+
+ /*
+ * Sanity check. Use different execute loops for AF_INET and AF_INET6.
+ */
+ if (*byte_codes != AF_INET)
+ msg_panic("%s: malformed byte-code header (decimal %d)",
+ myname, *(const unsigned char *) byte_codes);
+
+ /*
+ * Match the address bytes against the byte codes. Avoid problems with
+ * (char -> int) sign extension on architectures with signed characters.
+ */
+ bp = (const unsigned char *) byte_codes + 1;
+ ap = (const unsigned char *) addr_bytes;
+
+ for (octet_count = 0; octet_count < 4; octet_count++, ap++) {
+
+ /*
+ * Simple numeric field.
+ */
+ if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
+ if (*ap == *bp)
+ bp += 1;
+ else
+ return (0);
+ }
+
+ /*
+ * Wild-card numeric field.
+ */
+ else if (ch == IP_MATCH_CODE_OPEN) {
+ matched = 0;
+ for (;;) {
+ /* Numeric range. */
+ if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
+ if (!matched)
+ matched = (*ap >= bp[0] && *ap <= bp[1]);
+ bp += 2;
+ }
+ /* Number. */
+ else if (ch == IP_MATCH_CODE_OVAL) {
+ if (!matched)
+ matched = (*ap == *bp);
+ bp += 1;
+ }
+ /* End-of-wildcard. */
+ else if (ch == IP_MATCH_CODE_CLOSE) {
+ break;
+ }
+ /* Corruption. */
+ else {
+ size_t len = (const char *) bp - byte_codes - 1;
+
+ msg_panic("%s: unexpected byte code (decimal %d) "
+ "after \"%s\"", myname, ch,
+ ip_match_print_code_prefix(byte_codes, len));
+ }
+ }
+ if (matched == 0)
+ return (0);
+ }
+
+ /*
+ * Corruption.
+ */
+ else {
+ size_t len = (const char *) bp - byte_codes - 1;
+
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, ip_match_print_code_prefix(byte_codes, len));
+ }
+ }
+ return (1);
+}
+
+/* ip_match_next_token - carve out the next token from input pattern */
+
+static int ip_match_next_token(char **pstart, char **psaved_start, int *poval)
+{
+ unsigned char *cp;
+ int oval; /* octet value */
+ int type; /* token value */
+
+ /*
+ * Return a literal, error, or EOF token. Update the read pointer to the
+ * start of the next token or leave it at the string terminator.
+ */
+#define IP_MATCH_RETURN_TOK(next, type) \
+ do { *pstart = (char *) (next); return (type); } while (0)
+
+ /*
+ * Return a token that contains an IPv4 address octet value.
+ */
+#define IP_MATCH_RETURN_TOK_VAL(next, type, oval) do { \
+ *poval = (oval); IP_MATCH_RETURN_TOK((next), type); \
+ } while (0)
+
+ /*
+ * Light-weight tokenizer. Each result is an IPv4 address octet value, a
+ * literal character value, error, or EOF.
+ */
+ *psaved_start = *pstart;
+ cp = (unsigned char *) *pstart;
+ if (ISDIGIT(*cp)) {
+ oval = *cp - '0';
+ type = IP_MATCH_CODE_OVAL;
+ for (cp += 1; ISDIGIT(*cp); cp++) {
+ oval *= 10;
+ oval += *cp - '0';
+ if (oval > 255)
+ type = IP_MATCH_CODE_ERR;
+ }
+ IP_MATCH_RETURN_TOK_VAL(cp, type, oval);
+ } else {
+ IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp);
+ }
+}
+
+/* ipmatch_print_parse_error - formatted parsing error, with context */
+
+static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply,
+ char *start,
+ char *here,
+ char *next,
+ const char *fmt,...)
+{
+ va_list ap;
+ int start_width;
+ int here_width;
+
+ /*
+ * Format the error type.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(reply, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Format the error context. The syntax is complex enough that it is
+ * worth the effort to precisely indicate what input is in error.
+ *
+ * XXX Workaround for %.*s to avoid output when a zero width is specified.
+ */
+ if (start != 0) {
+ start_width = here - start;
+ here_width = next - here;
+ vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"",
+ start_width, start_width == 0 ? "" : start,
+ here_width, here_width == 0 ? "" : here, next);
+ }
+}
+
+/* ip_match_parse - parse an entire wild-card address pattern */
+
+char *ip_match_parse(VSTRING *byte_codes, char *pattern)
+{
+ int octet_count;
+ char *saved_cp;
+ char *cp;
+ int token_type;
+ int look_ahead;
+ int oval;
+ int saved_oval;
+
+ /*
+ * Simplify this if we change to {} for wildcard notation.
+ */
+#define FIND_TERMINATOR(start, cp) do { \
+ int _level = 0; \
+ for (cp = (start) ; *cp; cp++) { \
+ if (*cp == '[') _level++; \
+ if (*cp != ']') continue; \
+ if (--_level == 0) break; \
+ } \
+ } while (0)
+
+ /*
+ * Strip [] around the entire pattern.
+ */
+ if (*pattern == '[') {
+ FIND_TERMINATOR(pattern, cp);
+ if (cp[0] == 0) {
+ vstring_sprintf(byte_codes, "missing \"]\" character");
+ return (STR(byte_codes));
+ }
+ if (cp[1] == 0) {
+ *cp = 0;
+ pattern += 1;
+ }
+ }
+
+ /*
+ * Sanity check. In this case we can't show any error context.
+ */
+ if (*pattern == 0) {
+ vstring_sprintf(byte_codes, "empty address pattern");
+ return (STR(byte_codes));
+ }
+
+ /*
+ * Simple parser with on-the-fly encoding. For now, IPv4 support only.
+ * Use different parser loops for IPv4 and IPv6.
+ */
+ VSTRING_RESET(byte_codes);
+ VSTRING_ADDCH(byte_codes, AF_INET);
+ octet_count = 0;
+ cp = pattern;
+
+ /*
+ * Require four address fields separated by ".", each field containing a
+ * numeric octet value or a sequence inside []. The loop head has no test
+ * and does not step the loop variable. The tokenizer advances the loop
+ * variable, and the loop termination logic is inside the loop.
+ */
+ for (;;) {
+ switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) {
+
+ /*
+ * Numeric address field.
+ */
+ case IP_MATCH_CODE_OVAL:
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
+ VSTRING_ADDCH(byte_codes, oval);
+ break;
+
+ /*
+ * Wild-card address field.
+ */
+ case IP_MATCH_CODE_OPEN:
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN);
+ /* Require ";"-separated numbers or numeric ranges. */
+ for (;;) {
+ token_type = ip_match_next_token(&cp, &saved_cp, &oval);
+ if (token_type == IP_MATCH_CODE_OVAL) {
+ saved_oval = oval;
+ look_ahead = ip_match_next_token(&cp, &saved_cp, &oval);
+ /* Numeric range. */
+ if (look_ahead == '.') {
+ /* Brute-force parsing. */
+ if (ip_match_next_token(&cp, &saved_cp, &oval) == '.'
+ && ip_match_next_token(&cp, &saved_cp, &oval)
+ == IP_MATCH_CODE_OVAL
+ && saved_oval <= oval) {
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE);
+ VSTRING_ADDCH(byte_codes, saved_oval);
+ VSTRING_ADDCH(byte_codes, oval);
+ look_ahead =
+ ip_match_next_token(&cp, &saved_cp, &oval);
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern,
+ saved_cp, cp,
+ "numeric range error");
+ return (STR(byte_codes));
+ }
+ }
+ /* Single number. */
+ else {
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
+ VSTRING_ADDCH(byte_codes, saved_oval);
+ }
+ /* Require ";" or end-of-wildcard. */
+ token_type = look_ahead;
+ if (token_type == ';') {
+ continue;
+ } else if (token_type == IP_MATCH_CODE_CLOSE) {
+ break;
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern,
+ saved_cp, cp,
+ "need \";\" or \"%c\"",
+ IP_MATCH_CODE_CLOSE);
+ return (STR(byte_codes));
+ }
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need decimal number 0..255");
+ return (STR(byte_codes));
+ }
+ }
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE);
+ break;
+
+ /*
+ * Invalid field.
+ */
+ default:
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need decimal number 0..255 or \"%c\"",
+ IP_MATCH_CODE_OPEN);
+ return (STR(byte_codes));
+ }
+ octet_count += 1;
+
+ /*
+ * Require four address fields. Not one more, not one less.
+ */
+ if (octet_count == 4) {
+ if (*cp != 0) {
+ (void) ip_match_next_token(&cp, &saved_cp, &oval);
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "garbage after pattern");
+ return (STR(byte_codes));
+ }
+ VSTRING_ADDCH(byte_codes, 0);
+ return (0);
+ }
+
+ /*
+ * Require "." before the next address field.
+ */
+ if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') {
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need \".\"");
+ return (STR(byte_codes));
+ }
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Dummy main program for regression tests.
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *byte_codes = vstring_alloc(100);
+ VSTRING *line_buf = vstring_alloc(100);
+ char *bufp;
+ char *err;
+ char *user_pattern;
+ char *user_address;
+ int echo_input = !isatty(0);
+
+ /*
+ * Iterate over the input stream. The input format is a pattern, followed
+ * by optional addresses to match against.
+ */
+ while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) {
+ bufp = STR(line_buf);
+ if (echo_input) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((user_pattern = mystrtok(&bufp, " \t")) == 0)
+ continue;
+
+ /*
+ * Parse and dump the pattern.
+ */
+ if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) {
+ vstream_printf("Error: %s\n", err);
+ } else {
+ vstream_printf("Code: %s\n",
+ ip_match_dump(line_buf, STR(byte_codes)));
+ }
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Match the optional patterns.
+ */
+ while ((user_address = mystrtok(&bufp, " \t")) != 0) {
+ struct in_addr netw_addr;
+
+ switch (inet_pton(AF_INET, user_address, &netw_addr)) {
+ case 1:
+ vstream_printf("Match %s: %s\n", user_address,
+ ip_match_execute(STR(byte_codes),
+ (char *) &netw_addr.s_addr) ?
+ "yes" : "no");
+ break;
+ case 0:
+ vstream_printf("bad address syntax: %s\n", user_address);
+ break;
+ case -1:
+ vstream_printf("%s: %m\n", user_address);
+ break;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(line_buf);
+ vstring_free(byte_codes);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/ip_match.h b/src/util/ip_match.h
new file mode 100644
index 0000000..781a04a
--- /dev/null
+++ b/src/util/ip_match.h
@@ -0,0 +1,38 @@
+#ifndef _IP_MATCH_H_INCLUDED_
+#define _IP_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* ip_match 3h
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *ip_match_parse(VSTRING *, char *);
+extern char *ip_match_save(const VSTRING *);
+extern char *ip_match_dump(VSTRING *, const char *);
+extern int ip_match_execute(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/ip_match.in b/src/util/ip_match.in
new file mode 100644
index 0000000..eec13e3
--- /dev/null
+++ b/src/util/ip_match.in
@@ -0,0 +1,26 @@
+1.2.3.4
+1.2.300.4
+1.2.3000.4
+1.2.3.
+1.2.3
+a
+1.2.3;4
+1.2.[3].4
+1.2.[].4
+1.2.[.4
+1.2.].4
+1.2.[1..127;128..255].5
+1.2.[1-255].5
+1.2.[1..127.128..255].5
+1.2.3.[4]
+1.2.3.[4..1]
+1.2.3.[4.1]
+1.2.3.[4.x]
+1.2.3.[x]
+1.2.3.4x
+1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6
+1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6
+[1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5
+[[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5
+[[1;2].3.4.5
+1;2].3.4.5
diff --git a/src/util/ip_match.ref b/src/util/ip_match.ref
new file mode 100644
index 0000000..04b291f
--- /dev/null
+++ b/src/util/ip_match.ref
@@ -0,0 +1,69 @@
+> 1.2.3.4
+Code: 1.2.3.4
+> 1.2.300.4
+Error: need decimal number 0..255 or "[" at "1.2.>300<.4"
+> 1.2.3000.4
+Error: need decimal number 0..255 or "[" at "1.2.>3000<.4"
+> 1.2.3.
+Error: need decimal number 0..255 or "[" at "1.2.3.><"
+> 1.2.3
+Error: need "." at "1.2.3><"
+> a
+Error: need decimal number 0..255 or "[" at ">a<"
+> 1.2.3;4
+Error: need "." at "1.2.3>;<4"
+> 1.2.[3].4
+Code: 1.2.[3].4
+> 1.2.[].4
+Error: need decimal number 0..255 at "1.2.[>]<.4"
+> 1.2.[.4
+Error: need decimal number 0..255 at "1.2.[>.<4"
+> 1.2.].4
+Error: need decimal number 0..255 or "[" at "1.2.>]<.4"
+> 1.2.[1..127;128..255].5
+Code: 1.2.[1..127;128..255].5
+> 1.2.[1-255].5
+Error: need ";" or "]" at "1.2.[1>-<255].5"
+> 1.2.[1..127.128..255].5
+Error: need ";" or "]" at "1.2.[1..127>.<128..255].5"
+> 1.2.3.[4]
+Code: 1.2.3.[4]
+> 1.2.3.[4..1]
+Error: numeric range error at "1.2.3.[4..>1<]"
+> 1.2.3.[4.1]
+Error: numeric range error at "1.2.3.[4.>1<]"
+> 1.2.3.[4.x]
+Error: numeric range error at "1.2.3.[4.>x<]"
+> 1.2.3.[x]
+Error: need decimal number 0..255 at "1.2.3.[>x<]"
+> 1.2.3.4x
+Error: garbage after pattern at "1.2.3.4>x<"
+> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6
+Code: 1.2.[3..11].5
+Match 1.2.3.5: yes
+Match 1.2.2.5: no
+Match 1.2.11.5: yes
+Match 1.2.12.5: no
+Match 1.2.11.6: no
+> 1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6
+Code: 1.2.[3;5;7;9;11].5
+Match 1.2.3.5: yes
+Match 1.2.2.5: no
+Match 1.2.4.5: no
+Match 1.2.11.5: yes
+Match 1.2.12.5: no
+Match 1.2.11.6: no
+> [1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5
+Code: [1;2].3.4.5
+Match 1.3.4.5: yes
+Match 2.3.4.5: yes
+Match 3.3.4.5: no
+> [[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5
+Code: [1;2].3.4.5
+Match 1.3.4.5: yes
+Match 2.3.4.5: yes
+Match 3.3.4.5: no
+> [[1;2].3.4.5
+Error: missing "]" character
+> 1;2].3.4.5
+Error: need "." at "1>;<2].3.4.5"
diff --git a/src/util/killme_after.c b/src/util/killme_after.c
new file mode 100644
index 0000000..34e0434
--- /dev/null
+++ b/src/util/killme_after.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* killme_after 3
+/* SUMMARY
+/* programmed death
+/* SYNOPSIS
+/* #include <killme_after.h>
+/*
+/* void killme_after(seconds)
+/* unsigned int seconds;
+/* DESCRIPTION
+/* The killme_after() function does a best effort to terminate
+/* the process after the specified time, should it still exist.
+/* It is meant to be used in a signal handler, as an insurance
+/* against getting stuck somewhere while preparing for exit.
+/* DIAGNOSTICS
+/* None. This routine does a best effort, damn the torpedoes.
+/* 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>
+
+/* Utility library. */
+
+#include <killme_after.h>
+
+/* killme_after - self-assured death */
+
+void killme_after(unsigned int seconds)
+{
+ struct sigaction sig_action;
+
+ /*
+ * Schedule an ALARM signal, and make sure the signal will be delivered
+ * even if we are being called from a signal handler and SIGALRM delivery
+ * is blocked.
+ *
+ * Undocumented: when a process runs with PID 1, Linux won't deliver a
+ * signal unless the process specifies a handler (i.e. SIG_DFL is treated
+ * as SIG_IGN). Conveniently, _exit() can be used directly as a signal
+ * handler. This changes the wait status that a parent would see, but in
+ * the case of "init" mode on Linux, no-one would care.
+ */
+ alarm(0);
+ sigemptyset(&sig_action.sa_mask);
+ sig_action.sa_flags = 0;
+ sig_action.sa_handler = (getpid() == 1 ? _exit : SIG_DFL);
+ sigaction(SIGALRM, &sig_action, (struct sigaction *) 0);
+ alarm(seconds);
+ sigaddset(&sig_action.sa_mask, SIGALRM);
+ sigprocmask(SIG_UNBLOCK, &sig_action.sa_mask, (sigset_t *) 0);
+}
diff --git a/src/util/killme_after.h b/src/util/killme_after.h
new file mode 100644
index 0000000..9505b2d
--- /dev/null
+++ b/src/util/killme_after.h
@@ -0,0 +1,30 @@
+#ifndef _KILLME_AFTER_H_INCLUDED_
+#define _KILLME_AFTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* killme_after 3h
+/* SUMMARY
+/* programmed death
+/* SYNOPSIS
+/* #include "killme_after.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void killme_after(unsigned int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/known_tcp_ports.c b/src/util/known_tcp_ports.c
new file mode 100644
index 0000000..1d524d4
--- /dev/null
+++ b/src/util/known_tcp_ports.c
@@ -0,0 +1,253 @@
+/*++
+/* NAME
+/* known_tcp_ports 3
+/* SUMMARY
+/* reduce dependency on the services(5) database
+/* SYNOPSIS
+/* #include <known_tcp_ports.h>
+/*
+/* const char *add_known_tcp_port(
+/* const char *name)
+/* const char *port)
+/*
+/* const char *filter_known_tcp_port(
+/* const char *name_or_port)
+/*
+/* void clear_known_tcp_ports(void)
+/* AUXILIARY FUNCTIONS
+/* char *export_known_tcp_ports(
+/* VSTRING *result)
+/* DESCRIPTION
+/* This module reduces dependency on the services(5) database.
+/*
+/* add_known_tcp_port() associates a symbolic name with a numerical
+/* port. The function returns a pointer to error text if the
+/* arguments are malformed or if the symbolic name already has
+/* an association.
+/*
+/* filter_known_tcp_port() returns the argument if it does not
+/* specify a symbolic name, or if the argument specifies a symbolic
+/* name that is not associated with a numerical port. Otherwise,
+/* it returns the associated numerical port.
+/*
+/* clear_known_tcp_ports() destroys all name-number associations.
+/* string.
+/*
+/* export_known_tcp_ports() overwrites a VSTRING with all known
+/* name=port associations, sorted by service name, and separated
+/* by whitespace. The result is pointer to the VSTRING payload.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Application-specific.
+ */
+#include <known_tcp_ports.h>
+
+#define STR(x) vstring_str(x)
+
+static HTABLE *known_tcp_ports;
+
+/* add_known_tcp_port - associate symbolic name with numerical port */
+
+const char *add_known_tcp_port(const char *name, const char *port)
+{
+ if (alldig(name))
+ return ("numerical service name");
+ if (!alldig(port))
+ return ("non-numerical service port");
+ if (known_tcp_ports == 0)
+ known_tcp_ports = htable_create(10);
+ if (htable_locate(known_tcp_ports, name) != 0)
+ return ("duplicate service name");
+ (void) htable_enter(known_tcp_ports, name, mystrdup(port));
+ return (0);
+}
+
+/* filter_known_tcp_port - replace argument if associated with known port */
+
+const char *filter_known_tcp_port(const char *name_or_port)
+{
+ HTABLE_INFO *ht;
+
+ if (name_or_port == 0 || known_tcp_ports == 0 || alldig(name_or_port)) {
+ return (name_or_port);
+ } else if ((ht = htable_locate(known_tcp_ports, name_or_port)) != 0) {
+ return (ht->value);
+ } else {
+ return (name_or_port);
+ }
+}
+
+/* clear_known_tcp_ports - destroy all name-port associations */
+
+void clear_known_tcp_ports(void)
+{
+ htable_free(known_tcp_ports, myfree);
+ known_tcp_ports = 0;
+}
+
+/* compare_ht_keys - compare table keys */
+
+static int compare_ht_keys(const void *a, const void *b)
+{
+ HTABLE_INFO **ap = (HTABLE_INFO **) a;
+ HTABLE_INFO **bp = (HTABLE_INFO **) b;
+
+ return (strcmp((const char *) ap[0]->key, (const char *) bp[0]->key));
+}
+
+/* export_known_tcp_ports - sorted dump */
+
+char *export_known_tcp_ports(VSTRING *out)
+{
+ HTABLE_INFO **list;
+ HTABLE_INFO **ht;
+
+ VSTRING_RESET(out);
+ if (known_tcp_ports) {
+ list = htable_list(known_tcp_ports);
+ qsort((void *) list, known_tcp_ports->used, sizeof(*list),
+ compare_ht_keys);
+ for (ht = list; *ht; ht++)
+ vstring_sprintf_append(out, "%s%s=%s", ht > list ? " " : "",
+ ht[0]->key, (const char *) ht[0]->value);
+ myfree((void *) list);
+ }
+ VSTRING_TERMINATE(out);
+ return (STR(out));
+}
+
+#ifdef TEST
+
+#include <msg.h>
+
+struct association {
+ const char *lhs; /* service name */
+ const char *rhs; /* service port */
+};
+
+struct probe {
+ const char *query; /* query */
+ const char *exp_reply; /* expected reply */
+};
+
+struct test_case {
+ const char *label; /* identifies test case */
+ struct association associations[10];
+ const char *exp_err; /* expected error */
+ const char *exp_export; /* expected export output */
+ struct probe probes[10];
+};
+
+struct test_case test_cases[] = {
+ {"good",
+ /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0},
+ /* error */ 0,
+ /* export */ "lmtp=24 smtp=25",
+ /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0}
+ },
+ {"duplicate lhs",
+ /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0},
+ /* error */ "duplicate service name"
+ },
+ {"numerical lhs",
+ /* association */ {{"100", "100"}, 0},
+ /* error */ "numerical service name"
+ },
+ {"symbolic rhs",
+ /* association */ {{"smtp", "lmtp"}, 0},
+ /* error */ "non-numerical service port"
+ },
+ {"uninitialized",
+ /* association */ {0},
+ /* error */ 0,
+ /* export */ "",
+ /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0}
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ VSTRING *export_buf;
+ struct test_case *tp;
+ struct association *ap;
+ struct probe *pp;
+ int pass = 0;
+ int fail = 0;
+ const char *err;
+ int test_failed;
+ const char *reply;
+ const char *export;
+
+#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
+
+ export_buf = vstring_alloc(100);
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_failed = 0;
+ for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
+ err = add_known_tcp_port(ap->lhs, ap->rhs);
+ if (!err != !tp->exp_err) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err));
+ test_failed = 1;
+ } else if (err != 0) {
+ if (strcmp(err, tp->exp_err) != 0) {
+ msg_warn("test case %s: got err: \"%s\", want: \"%s\"",
+ tp->label, err, tp->exp_err);
+ test_failed = 1;
+ }
+ } else {
+ export = export_known_tcp_ports(export_buf);
+ if (strcmp(export, tp->exp_export) != 0) {
+ msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
+ tp->label, export, tp->exp_export);
+ test_failed = 1;
+ }
+ for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) {
+ reply = filter_known_tcp_port(pp->query);
+ if (strcmp(reply, pp->exp_reply) != 0) {
+ msg_warn("test case %s: got reply: \"%s\", want: \"%s\"",
+ tp->label, reply, pp->exp_reply);
+ test_failed = 1;
+ }
+ }
+ }
+ clear_known_tcp_ports();
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(export_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/known_tcp_ports.h b/src/util/known_tcp_ports.h
new file mode 100644
index 0000000..bc254f2
--- /dev/null
+++ b/src/util/known_tcp_ports.h
@@ -0,0 +1,38 @@
+#ifndef _KNOWN_TCP_PORTS_H_INCLUDED_
+#define _KNOWN_TCP_PORTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* known_tcp_port 3h
+/* SUMMARY
+/* reduce dependency on the services(5) database
+/* SYNOPSIS
+/* #include <known_tcp_ports.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern const char *add_known_tcp_port(const char *name, const char *port);
+extern const char *filter_known_tcp_port(const char *name_or_port);
+extern void clear_known_tcp_ports(void);
+extern char *export_known_tcp_ports(VSTRING *out);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/known_tcp_ports.ref b/src/util/known_tcp_ports.ref
new file mode 100644
index 0000000..adcf182
--- /dev/null
+++ b/src/util/known_tcp_ports.ref
@@ -0,0 +1,6 @@
+unknown: good: PASS
+unknown: duplicate lhs: PASS
+unknown: numerical lhs: PASS
+unknown: symbolic rhs: PASS
+unknown: uninitialized: PASS
+unknown: PASS=5 FAIL=0
diff --git a/src/util/ldseed.c b/src/util/ldseed.c
new file mode 100644
index 0000000..c231152
--- /dev/null
+++ b/src/util/ldseed.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* ldseed 3
+/* SUMMARY
+/* seed for non-cryptographic applications
+/* SYNOPSIS
+/* #include <ldseed.h>
+/*
+/* void ldseed(
+/* void *dst,
+/* size_t len)
+/* DESCRIPTION
+/* ldseed() preferably extracts pseudo-random bits from
+/* /dev/urandom, a non-blocking device that is available on
+/* modern systems.
+/*
+/* On systems where /dev/urandom is unavailable or does not
+/* immediately return the requested amount of randomness,
+/* ldseed() falls back to a combination of wallclock time,
+/* the time since boot, and the process ID.
+/* BUGS
+/* With Linux "the O_NONBLOCK flag has no effect when opening
+/* /dev/urandom", but reads "can incur an appreciable delay
+/* when requesting large amounts of data". Apparently, "large"
+/* means more than 256 bytes.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <msg.h>
+#include <ldseed.h>
+
+ /*
+ * Different systems have different names for non-wallclock time.
+ */
+#ifdef CLOCK_UPTIME
+#define NON_WALLTIME_CLOCK CLOCK_UPTIME
+#elif defined(CLOCK_BOOTTIME)
+#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME
+#elif defined(CLOCK_MONOTONIC)
+#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC
+#elif defined(CLOCK_HIGHRES)
+#define NON_WALLTIME_CLOCK CLOCK_HIGHRES
+#endif
+
+/* ldseed - best-effort, low-dependency seed */
+
+void ldseed(void *dst, size_t len)
+{
+ int count;
+ int fd;
+ int n;
+ time_t fallback = 0;
+
+ /*
+ * Medium-quality seed.
+ */
+ if ((fd = open("/dev/urandom", O_RDONLY)) > 0) {
+ non_blocking(fd, NON_BLOCKING);
+ count = read(fd, dst, len);
+ (void) close(fd);
+ if (count == len)
+ return;
+ }
+
+ /*
+ * Low-quality seed. Based on 1) the time since boot (good when an
+ * attacker knows the program start time but not the system boot time),
+ * and 2) absolute time (good when an attacker does not know the program
+ * start time). Assumes a system with better than microsecond resolution,
+ * and a network stack that does not leak the time since boot, for
+ * example, through TCP or ICMP timestamps. With those caveats, this seed
+ * is good for 20-30 bits of randomness.
+ */
+#ifdef NON_WALLTIME_CLOCK
+ {
+ struct timespec ts;
+
+ if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ fallback += ts.tv_sec ^ ts.tv_nsec;
+ }
+#elif defined(USE_GETHRTIME)
+ fallback += gethrtime();
+#endif
+
+#ifdef CLOCK_REALTIME
+ {
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ fallback += ts.tv_sec ^ ts.tv_nsec;
+ }
+#else
+ {
+ struct timeval tv;
+
+ if (GETTIMEOFDAY(&tv) != 0)
+ msg_fatal("gettimeofday() failed: %m");
+ fallback += tv.tv_sec + tv.tv_usec;
+ }
+#endif
+ fallback += getpid();
+
+ /*
+ * Copy the least significant bytes first, because those are the most
+ * volatile.
+ */
+ for (n = 0; n < sizeof(fallback) && n < len; n++) {
+ *(char *) dst++ ^= (fallback & 0xff);
+ fallback >>= CHAR_BIT;
+ }
+ return;
+}
diff --git a/src/util/ldseed.h b/src/util/ldseed.h
new file mode 100644
index 0000000..891986e
--- /dev/null
+++ b/src/util/ldseed.h
@@ -0,0 +1,30 @@
+#ifndef _LDSEED_H_INCLUDED_
+#define _LDSEED_H_INCLUDED_
+
+/*++
+/* NAME
+/* ldseed 3h
+/* SUMMARY
+/* seed for non-cryptographic applications
+/* SYNOPSIS
+/* #include <ldseed.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void ldseed(void *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/line_number.c b/src/util/line_number.c
new file mode 100644
index 0000000..557e053
--- /dev/null
+++ b/src/util/line_number.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* line_number 3
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/*
+/* char *format_line_number(result, first, last)
+/* VSTRING *buffer;
+/* ssize_t first;
+/* ssize_t lastl
+/* DESCRIPTION
+/* format_line_number() formats a line number or number range.
+/* The output is <first-number>-<last-number> when the numbers
+/* differ, <first-number> when the numbers are identical.
+/* .IP result
+/* Result buffer, or null-pointer. In the latter case the
+/* result is stored in a static buffer that is overwritten
+/* with subsequent calls. The function result value is a
+/* pointer into the result buffer.
+/* .IP first
+/* First line number.
+/* .IP last
+/* Last line number.
+/* 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 <vstring.h>
+#include <line_number.h>
+
+/* format_line_number - pretty-print line number or number range */
+
+char *format_line_number(VSTRING *result, ssize_t first, ssize_t last)
+{
+ static VSTRING *buf;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (result == 0) {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ result = buf;
+ }
+
+ /*
+ * Print a range only when the numbers differ.
+ */
+ vstring_sprintf(result, "%ld", (long) first);
+ if (first != last)
+ vstring_sprintf_append(result, "-%ld", (long) last);
+
+ return (vstring_str(result));
+}
diff --git a/src/util/line_number.h b/src/util/line_number.h
new file mode 100644
index 0000000..0a53d15
--- /dev/null
+++ b/src/util/line_number.h
@@ -0,0 +1,35 @@
+#ifndef _LINE_NUMBER_H_INCLUDED_
+#define _LINE_NUMBER_H_INCLUDED_
+
+/*++
+/* NAME
+/* line_number 3h
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *format_line_number(VSTRING *, ssize_t, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/line_wrap.c b/src/util/line_wrap.c
new file mode 100644
index 0000000..0f399e8
--- /dev/null
+++ b/src/util/line_wrap.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* line_wrap 3
+/* SUMMARY
+/* wrap long lines upon output
+/* SYNOPSIS
+/* #include <line_wrap.h>
+/*
+/* void line_wrap(string, len, indent, output_fn, context)
+/* const char *buf;
+/* int len;
+/* int indent;
+/* void (*output_fn)(const char *str, int len, int indent, void *context);
+/* void *context;
+/* DESCRIPTION
+/* The \fBline_wrap\fR routine outputs the specified string via
+/* the specified output function, and attempts to keep output lines
+/* shorter than the specified length. The routine does not attempt to
+/* break long words that do not fit on a single line. Upon output,
+/* trailing whitespace is stripped.
+/*
+/* Arguments
+/* .IP string
+/* The input, which cannot contain any newline characters.
+/* .IP len
+/* The desired maximal output line length.
+/* .IP indent
+/* The desired amount of indentation of the second etc. output lines
+/* with respect to the first output line. A negative indent causes
+/* only the first line to be indented; a positive indent causes all
+/* but the first line to be indented. A zero count causes no indentation.
+/* .IP output_fn
+/* The output function that is called with as arguments a string
+/* pointer, a string length, a non-negative indentation count, and
+/* application context. A typical implementation looks like this:
+/* .sp
+/* .nf
+/* .na
+void print(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *fp = (VSTREAM *) context;
+
+ vstream_fprintf(fp, "%*s%.*s", indent, "", len, str);
+}
+/* .fi
+/* .ad
+/* .IP context
+/* Application context that is passed on to the output function.
+/* For example, a VSTREAM pointer, or a structure that contains
+/* a VSTREAM pointer.
+/* BUGS
+/* No tab expansion and no backspace 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <line_wrap.h>
+
+/* line_wrap - wrap long lines upon output */
+
+void line_wrap(const char *str, int len, int indent, LINE_WRAP_FN output_fn,
+ void *context)
+{
+ const char *start_line;
+ const char *word;
+ const char *next_word;
+ const char *next_space;
+ int line_len;
+ int curr_len;
+ int curr_indent;
+
+ if (indent < 0) {
+ curr_indent = -indent;
+ curr_len = len + indent;
+ } else {
+ curr_indent = 0;
+ curr_len = len;
+ }
+
+ /*
+ * At strategic positions, output what we have seen, after stripping off
+ * trailing blanks.
+ */
+ for (start_line = word = str; word != 0; word = next_word) {
+ next_space = word + strcspn(word, " \t");
+ if (word > start_line) {
+ if (next_space - start_line > curr_len) {
+ line_len = word - start_line;
+ while (line_len > 0 && ISSPACE(start_line[line_len - 1]))
+ line_len--;
+ output_fn(start_line, line_len, curr_indent, context);
+ while (*word && ISSPACE(*word))
+ word++;
+ if (start_line == str) {
+ curr_indent += indent;
+ curr_len -= indent;
+ }
+ start_line = word;
+ }
+ }
+ next_word = *next_space ? next_space + 1 : 0;
+ }
+ line_len = strlen(start_line);
+ while (line_len > 0 && ISSPACE(start_line[line_len - 1]))
+ line_len--;
+ output_fn(start_line, line_len, curr_indent, context);
+}
diff --git a/src/util/line_wrap.h b/src/util/line_wrap.h
new file mode 100644
index 0000000..32c3548
--- /dev/null
+++ b/src/util/line_wrap.h
@@ -0,0 +1,31 @@
+#ifndef _LINE_WRAP_H_INCLUDED_
+#define _LINE_WRAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* line_wrap 3h
+/* SUMMARY
+/* wrap long lines upon output
+/* SYNOPSIS
+/* #include <line_wrap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef void (*LINE_WRAP_FN) (const char *, int, int, void *);
+extern void line_wrap(const char *, int, int, LINE_WRAP_FN, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/listen.h b/src/util/listen.h
new file mode 100644
index 0000000..131755c
--- /dev/null
+++ b/src/util/listen.h
@@ -0,0 +1,53 @@
+#ifndef _LISTEN_H_INCLUDED_
+#define _LISTEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* listen 3h
+/* SUMMARY
+/* listener interface file
+/* SYNOPSIS
+/* #include <listen.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <htable.h>
+
+ /*
+ * Listener external interface.
+ */
+extern int unix_listen(const char *, int, int);
+extern int inet_listen(const char *, int, int);
+extern int fifo_listen(const char *, int, int);
+extern int stream_listen(const char *, int, int);
+extern int unix_dgram_listen(const char *, int);
+
+extern int inet_accept(int);
+extern int unix_accept(int);
+extern int stream_accept(int);
+
+extern int WARN_UNUSED_RESULT recv_pass_attr(int, HTABLE **, int, ssize_t);
+extern int pass_accept(int);
+extern int pass_accept_attr(int, HTABLE **);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/lmdb_cache_test_1.sh b/src/util/lmdb_cache_test_1.sh
new file mode 100644
index 0000000..f5d4767
--- /dev/null
+++ b/src/util/lmdb_cache_test_1.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Torture test: run update and purge operations in parallel processes.
+# This will result in some purge operations not finding all entries,
+# but the final sequential purge should eliminate all.
+
+set -e
+
+rm -f foo.lmdb
+./dict_cache <<EOF
+lmdb_map_size 20000
+cache lmdb:foo
+EOF
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update x ${1-10000}
+run
+update y ${1-10000}
+purge x
+run
+purge y
+run
+EOF
+) &
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update a ${1-10000}
+run
+update b ${1-10000}
+purge a
+run
+purge b
+run
+EOF
+) &
+
+wait
+
+./dict_cache <<EOF
+cache lmdb:foo
+purge a
+run
+purge b
+run
+purge x
+run
+purge y
+run
+EOF
+
+../../bin/postmap -s lmdb:foo | diff /dev/null -
+
+rm -f foo.lmdb
diff --git a/src/util/lmdb_cache_test_2.sh b/src/util/lmdb_cache_test_2.sh
new file mode 100644
index 0000000..a54e7bd
--- /dev/null
+++ b/src/util/lmdb_cache_test_2.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# Torture test: run update and delete ${1-10000}perations in
+# parallel processes. None should remain.
+
+set -e
+
+rm -f foo.lmdb
+./dict_cache <<EOF
+lmdb_map_size 20000
+cache lmdb:foo
+EOF
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update x ${1-10000}
+run
+update y ${1-10000}
+delete x ${1-10000}
+run
+delete y ${1-10000}
+run
+EOF
+) &
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update a ${1-10000}
+run
+update b ${1-10000}
+delete a ${1-10000}
+run
+delete b ${1-10000}
+run
+EOF
+) &
+
+wait
+
+../../bin/postmap -s lmdb:foo | diff /dev/null -
+
+rm -f foo.lmdb
diff --git a/src/util/load_file.c b/src/util/load_file.c
new file mode 100644
index 0000000..4e575d1
--- /dev/null
+++ b/src/util/load_file.c
@@ -0,0 +1,80 @@
+/*++
+/* NAME
+/* load_file 3
+/* SUMMARY
+/* load file with some prejudice
+/* SYNOPSIS
+/* #include <load_file.h>
+/*
+/* void load_file(path, action, context)
+/* const char *path;
+/* void (*action)(VSTREAM, void *);
+/* void *context;
+/* DESCRIPTION
+/* This routine reads a file and reads it again when the
+/* file changed recently.
+/*
+/* Arguments:
+/* .IP path
+/* The file to be opened, read-only.
+/* .IP action
+/* The function that presumably reads the file.
+/* .IP context
+/* Application-specific context for the action routine.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, cannot open file.
+/* 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>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <load_file.h>
+#include <warn_stat.h>
+
+/* load_file - load file with some prejudice */
+
+void load_file(const char *path, LOAD_FILE_FN action, void *context)
+{
+ VSTREAM *fp;
+ struct stat st;
+ time_t before;
+ time_t after;
+
+ /*
+ * Read the file again if it is hot. This may result in reading a partial
+ * parameter name or missing end marker when a file changes in the middle
+ * of a read.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", path);
+ action(fp, context);
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_ferror(fp) || vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+ if (msg_verbose)
+ msg_info("pausing to let %s cool down", path);
+ doze(300000);
+ }
+}
diff --git a/src/util/load_file.h b/src/util/load_file.h
new file mode 100644
index 0000000..3e635f3
--- /dev/null
+++ b/src/util/load_file.h
@@ -0,0 +1,32 @@
+#ifndef LOAD_FILE_H_INCLUDED_
+#define LOAD_FILE_H_INCLUDED_
+
+/*++
+/* NAME
+/* load_file 3h
+/* SUMMARY
+/* load file with some prejudice
+/* SYNOPSIS
+/* #include <load_file.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef void (*LOAD_FILE_FN)(VSTREAM *, void *);
+
+extern void load_file(const char *, LOAD_FILE_FN, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/load_lib.c b/src/util/load_lib.c
new file mode 100644
index 0000000..44e540d
--- /dev/null
+++ b/src/util/load_lib.c
@@ -0,0 +1,146 @@
+/*++
+/* NAME
+/* load_lib 3
+/* SUMMARY
+/* library loading wrappers
+/* SYNOPSIS
+/* #include <load_lib.h>
+/*
+/* void load_library_symbols(const char *, LIB_FN *, LIB_DP *);
+/* const char *libname;
+/* LIB_FN *libfuncs;
+/* LIB_DP *libdata;
+/* DESCRIPTION
+/* load_library_symbols() loads the specified shared object
+/* and looks up the function or data pointers for the specified
+/* symbols. All errors are fatal.
+/*
+/* Arguments:
+/* .IP libname
+/* shared-library pathname.
+/* .IP libfuncs
+/* Array of LIB_FN structures. The last name member must be null.
+/* .IP libdata
+/* Array of LIB_DP structures. The last name member must be null.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* library not found, symbols not found, other fatal errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System libraries.
+ */
+#include "sys_defs.h"
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef USE_DYNAMIC_MAPS
+#if defined(HAS_DLOPEN)
+#include <dlfcn.h>
+#elif defined(HAS_SHL_LOAD)
+#include <dl.h>
+#else
+#error "USE_DYNAMIC_LIBS requires HAS_DLOPEN or HAS_SHL_LOAD"
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <load_lib.h>
+
+/* load_library_symbols - load shared library and look up symbols */
+
+void load_library_symbols(const char *libname, LIB_FN *libfuncs,
+ LIB_DP *libdata)
+{
+ static const char myname[] = "load_library_symbols";
+ LIB_FN *fn;
+ LIB_DP *dp;
+
+#if defined(HAS_DLOPEN)
+ void *handle;
+ char *emsg;
+
+ /*
+ * XXX This is basically how FreeBSD dlfunc() silences a compiler warning
+ * about a data/function pointer conversion. The solution below is non-
+ * portable: it assumes that both data and function pointers are the same
+ * in size, and that both have the same representation.
+ */
+ union {
+ void *dptr; /* data pointer */
+ void (*fptr) (void); /* function pointer */
+ } non_portable_union;
+
+ if ((handle = dlopen(libname, RTLD_NOW)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlopen failure loading %s: %s", myname, libname,
+ emsg ? emsg : "don't know why");
+ }
+ if (libfuncs) {
+ for (fn = libfuncs; fn->name; fn++) {
+ if ((non_portable_union.dptr = dlsym(handle, fn->name)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname,
+ fn->name, libname, emsg ? emsg : "don't know why");
+ }
+ fn->fptr = non_portable_union.fptr;
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", fn->name, non_portable_union.dptr);
+ }
+ }
+ if (libdata) {
+ for (dp = libdata; dp->name; dp++) {
+ if ((dp->dptr = dlsym(handle, dp->name)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname,
+ dp->name, libname, emsg ? emsg : "don't know why");
+ }
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", dp->name, dp->dptr);
+ }
+ }
+#elif defined(HAS_SHL_LOAD)
+ shl_t handle;
+
+ handle = shl_load(libname, BIND_IMMEDIATE, 0);
+
+ if (libfuncs) {
+ for (fn = libfuncs; fn->name; fn++) {
+ if (shl_findsym(&handle, fn->name, TYPE_PROCEDURE, &fn->fptr) != 0)
+ msg_fatal("%s: shl_findsym failure looking up %s in %s: %m",
+ myname, fn->name, libname);
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", fn->name, (void *) fn->fptr);
+ }
+ }
+ if (libdata) {
+ for (dp = libdata; dp->name; dp++) {
+ if (shl_findsym(&handle, dp->name, TYPE_DATA, &dp->dptr) != 0)
+ msg_fatal("%s: shl_findsym failure looking up %s in %s: %m",
+ myname, dp->name, libname);
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", dp->name, dp->dptr);
+ }
+ }
+#endif
+}
+
+#endif
diff --git a/src/util/load_lib.h b/src/util/load_lib.h
new file mode 100644
index 0000000..1c999c5
--- /dev/null
+++ b/src/util/load_lib.h
@@ -0,0 +1,46 @@
+#ifndef _LOAD_LIB_H_INCLUDED_
+#define _LOAD_LIB_H_INCLUDED_
+
+/*++
+/* NAME
+/* load_lib 3h
+/* SUMMARY
+/* library loading wrappers
+/* SYNOPSIS
+/* #include "load_lib.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+/* NULL name terminates list */
+typedef struct LIB_FN {
+ const char *name;
+ void (*fptr)(void);
+} LIB_FN;
+
+typedef struct LIB_DP {
+ const char *name;
+ void *dptr;
+} LIB_DP;
+
+extern void load_library_symbols(const char *, LIB_FN *, LIB_DP *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/logwriter.c b/src/util/logwriter.c
new file mode 100644
index 0000000..aea2767
--- /dev/null
+++ b/src/util/logwriter.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* logwriter 3
+/* SUMMARY
+/* logfile writer
+/* SYNOPSIS
+/* #include <logwriter.h>
+/*
+/* VSTREAM *logwriter_open_or_die(
+/* const char *path)
+/*
+/* int logwriter_write(
+/* VSTREAM *file,
+/* const char *buffer.
+/* ssize_t buflen)
+/*
+/* int logwriter_close(
+/* VSTREAM *file)
+/*
+/* int logwriter_one_shot(
+/* const char *path,
+/* const char *buffer,
+/* ssize_t buflen)
+/* DESCRIPTION
+/* This module manages a logfile writer.
+/*
+/* logwriter_open_or_die() safely opens the specified file in
+/* write+append mode. File open/create errors are fatal.
+/*
+/* logwriter_write() writes the buffer plus newline to the
+/* open logfile. The result is zero if successful, VSTREAM_EOF
+/* if the operation failed.
+/*
+/* logwriter_close() closes the logfile and destroys the VSTREAM
+/* instance. The result is zero if there were no errors writing
+/* the file, VSTREAM_EOF otherwise.
+/*
+/* logwriter_one_shot() combines all the above operations. The
+/* result is zero if successful, VSTREAM_EOF if any operation
+/* failed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <logwriter.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <safe_open.h>
+#include <vstream.h>
+
+ /*
+ * Application-specific.
+ */
+
+/* logwriter_open_or_die - open logfile */
+
+VSTREAM *logwriter_open_or_die(const char *path)
+{
+ VSTREAM *fp;
+ VSTRING *why = vstring_alloc(100);
+
+#define NO_STATP ((struct stat *) 0)
+#define NO_CHOWN (-1)
+#define NO_CHGRP (-1)
+
+ fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, 0644,
+ NO_STATP, NO_CHOWN, NO_CHGRP, why);
+ if (fp == 0)
+ msg_fatal("open logfile '%s': %s", path, vstring_str(why));
+ close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
+ vstring_free(why);
+ return (fp);
+}
+
+/* logwriter_write - append to logfile */
+
+int logwriter_write(VSTREAM *fp, const char *buf, ssize_t len)
+{
+ if (len < 0)
+ msg_panic("logwriter_write: negative length %ld", (long) len);
+ if (vstream_fwrite(fp, buf, len) != len)
+ return (VSTREAM_EOF);
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_fflush(fp));
+}
+
+/* logwriter_close - close logfile */
+
+int logwriter_close(VSTREAM *fp)
+{
+ return (vstream_fclose(fp));
+}
+
+/* logwriter_one_shot - one-shot logwriter */
+
+int logwriter_one_shot(const char *path, const char *buf, ssize_t len)
+{
+ VSTREAM *fp;
+ int err;
+
+ fp = logwriter_open_or_die(path);
+ err = logwriter_write(fp, buf, len);
+ err |= logwriter_close(fp);
+ return (err ? VSTREAM_EOF : 0);
+}
diff --git a/src/util/logwriter.h b/src/util/logwriter.h
new file mode 100644
index 0000000..f5266e4
--- /dev/null
+++ b/src/util/logwriter.h
@@ -0,0 +1,38 @@
+#ifndef _LOGWRITER_H_INCLUDED_
+#define _LOGWRITER_H_INCLUDED_
+
+/*++
+/* NAME
+/* logwriter 3h
+/* SUMMARY
+/* logfile writer
+/* SYNOPSIS
+/* #include <logwriter.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *logwriter_open_or_die(const char *);
+extern int logwriter_write(VSTREAM *, const char *, ssize_t);
+extern int logwriter_close(VSTREAM *);
+extern int logwriter_one_shot(const char *, const char *, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/lowercase.c b/src/util/lowercase.c
new file mode 100644
index 0000000..f7388b4
--- /dev/null
+++ b/src/util/lowercase.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* lowercase 3
+/* SUMMARY
+/* map uppercase characters to lowercase
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *lowercase(buf)
+/* char *buf;
+/* DESCRIPTION
+/* lowercase() replaces uppercase characters in its null-terminated
+/* input by their lowercase equivalent.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *lowercase(char *string)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *cp) != 0; cp++)
+ if (ISUPPER(ch))
+ *cp = TOLOWER(ch);
+ return (string);
+}
diff --git a/src/util/lstat_as.c b/src/util/lstat_as.c
new file mode 100644
index 0000000..18e0f9f
--- /dev/null
+++ b/src/util/lstat_as.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* lstat_as 3
+/* SUMMARY
+/* lstat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <lstat_as.h>
+/*
+/* int lstat_as(path, st, euid, egid)
+/* const char *path;
+/* struct stat *st;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* lstat_as() looks up the file status of the named \fIpath\fR,
+/* using the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR, and stores the result into the structure pointed
+/* to by \fIst\fR. A -1 result means the lookup failed.
+/* This call does not follow symbolic links.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* 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>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "lstat_as.h"
+#include "warn_stat.h"
+
+/* lstat_as - lstat file as user */
+
+int lstat_as(const char *path, struct stat * st, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int status;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Lstat that file.
+ */
+ status = lstat(path, st);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (status);
+}
diff --git a/src/util/lstat_as.h b/src/util/lstat_as.h
new file mode 100644
index 0000000..2195a1e
--- /dev/null
+++ b/src/util/lstat_as.h
@@ -0,0 +1,35 @@
+#ifndef _LSTAT_AS_H_INCLUDED_
+#define _LSTAT_AS_H_INCLUDED_
+
+/*++
+/* NAME
+/* lstat_as 3h
+/* SUMMARY
+/* lstat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <lstat_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT lstat_as(const char *, struct stat *, uid_t, gid_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mac_expand.c b/src/util/mac_expand.c
new file mode 100644
index 0000000..f40819e
--- /dev/null
+++ b/src/util/mac_expand.c
@@ -0,0 +1,848 @@
+/*++
+/* NAME
+/* mac_expand 3
+/* SUMMARY
+/* attribute expansion
+/* SYNOPSIS
+/* #include <mac_expand.h>
+/*
+/* int mac_expand(result, pattern, flags, filter, lookup, context)
+/* VSTRING *result;
+/* const char *pattern;
+/* int flags;
+/* const char *filter;
+/* const char *lookup(const char *key, int mode, void *context)
+/* void *context;
+/* AUXILIARY FUNCTIONS
+/* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (
+/* const char *left,
+/* int tok_val,
+/* const char *rite)
+/*
+/* void mac_expand_add_relop(
+/* int *tok_list,
+/* const char *suffix,
+/* MAC_EXPAND_RELOP_FN relop_eval)
+/*
+/* MAC_EXP_OP_RES mac_exp_op_res_bool[2];
+/* DESCRIPTION
+/* This module implements parameter-less named attribute
+/* expansions, both conditional and unconditional. As of Postfix
+/* 3.0 this code supports relational expression evaluation.
+/*
+/* In this text, an attribute is considered "undefined" when its value
+/* is a null pointer. Otherwise, the attribute is considered "defined"
+/* and is expected to have as value a null-terminated string.
+/*
+/* In the text below, the legacy form $(...) is equivalent to
+/* ${...}. The legacy form $(...) may eventually disappear
+/* from documentation. In the text below, the name in $name
+/* and ${name...} must contain only characters from the set
+/* [a-zA-Z0-9_].
+/*
+/* The following substitutions are supported:
+/* .IP "$name, ${name}"
+/* Unconditional attribute-based substitution. The result is the
+/* named attribute value (empty if the attribute is not defined)
+/* after optional further named attribute substitution.
+/* .IP "${name?text}, ${name?{text}}"
+/* Conditional attribute-based substitution. If the named attribute
+/* value is non-empty, the result is the given text, after
+/* named attribute expansion and relational expression evaluation.
+/* Otherwise, the result is empty. Whitespace before or after
+/* {text} is ignored.
+/* .IP "${name:text}, ${name:{text}}"
+/* Conditional attribute-based substitution. If the attribute
+/* value is empty or undefined, the expansion is the given
+/* text, after named attribute expansion and relational expression
+/* evaluation. Otherwise, the result is empty. Whitespace
+/* before or after {text} is ignored.
+/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}"
+/* Conditional attribute-based substitution. If the named attribute
+/* value is non-empty, the result is text1. Otherwise, the
+/* result is text2. In both cases the result is subject to
+/* named attribute expansion and relational expression evaluation.
+/* Whitespace before or after {text1} or {text2} is ignored.
+/* .IP "${{text1} == ${text2} ? {text3} : {text4}}"
+/* Relational expression-based substitution. First, the content
+/* of {text1} and ${text2} is subjected to named attribute and
+/* relational expression-based substitution. Next, the relational
+/* expression is evaluated. If it evaluates to "true", the
+/* result is the content of {text3}, otherwise it is the content
+/* of {text4}, after named attribute and relational expression-based
+/* substitution. In addition to ==, this supports !=, <, <=,
+/* >=, and >. Comparisons are numerical when both operands are
+/* all digits, otherwise the comparisons are lexicographical.
+/*
+/* Arguments:
+/* .IP result
+/* Storage for the result of expansion. By default, the result
+/* is truncated upon entry.
+/* .IP pattern
+/* The string to be expanded.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP MAC_EXP_FLAG_RECURSE
+/* Expand attributes in lookup results. This should never be
+/* done with data whose origin is untrusted.
+/* .IP MAC_EXP_FLAG_APPEND
+/* Append text to the result buffer without truncating it.
+/* .IP MAC_EXP_FLAG_SCAN
+/* Scan the input for named attributes, including named
+/* attributes in all conditional result values. Do not expand
+/* named attributes, and do not truncate or write to the result
+/* argument.
+/* .IP MAC_EXP_FLAG_PRINTABLE
+/* Use the printable() function instead of \fIfilter\fR.
+/* .PP
+/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
+/* .RE
+/* .IP filter
+/* A null pointer, or a null-terminated array of characters that
+/* are allowed to appear in an expansion. Illegal characters are
+/* replaced by underscores.
+/* .IP lookup
+/* The attribute lookup routine. Arguments are: the attribute name,
+/* MAC_EXP_MODE_TEST to test the existence of the named attribute
+/* or MAC_EXP_MODE_USE to use the value of the named attribute,
+/* and the caller context that was given to mac_expand(). A null
+/* result value means that the requested attribute was not defined.
+/* .IP context
+/* Caller context that is passed on to the attribute lookup routine.
+/* .PP
+/* mac_expand_add_relop() registers a function that implements
+/* support for custom relational operators. Custom operator names
+/* such as "==xxx" have two parts: a prefix that is identical to
+/* a built-in operator such as "==", and an application-specified
+/* suffix such as "xxx".
+/*
+/* Arguments:
+/* .IP tok_list
+/* A null-terminated list of MAC_EXP_OP_TOK_* values that support
+/* the custom operator suffix.
+/* .IP suffix
+/* A null-terminated alphanumeric string that specifies the custom
+/* operator suffix.
+/* .IP relop_eval
+/* A function that compares two strings according to the
+/* MAC_EXP_OP_TOK_* value specified with the tok_val argument,
+/* and that returns non-zero if the custom operator evaluates to
+/* true, zero otherwise.
+/*
+/* mac_exp_op_res_bool provides an array that converts a boolean
+/* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or
+/* MAX_EXP_OP_RES_FALSE value.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
+/* recursion depth.
+/*
+/* The result value is the binary OR of zero or more of the following:
+/* .IP MAC_PARSE_ERROR
+/* A syntax error was found in \fBpattern\fR, or some attribute had
+/* an unreasonable nesting depth.
+/* .IP MAC_PARSE_UNDEF
+/* An attribute was expanded but its value was not defined.
+/* SEE ALSO
+/* mac_parse(3) locate macro references in string.
+/* 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <name_code.h>
+#include <sane_strtol.h>
+#include <mac_parse.h>
+#include <mac_expand.h>
+
+ /*
+ * Simplifies the return of common relational operator results.
+ */
+MAC_EXP_OP_RES mac_exp_op_res_bool[2] = {
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_TRUE
+};
+
+ /*
+ * Little helper structure.
+ */
+typedef struct {
+ VSTRING *result; /* result buffer */
+ int flags; /* features */
+ const char *filter; /* character filter */
+ MAC_EXP_LOOKUP_FN lookup; /* lookup routine */
+ void *context; /* caller context */
+ int status; /* findings */
+ int level; /* nesting level */
+} MAC_EXP_CONTEXT;
+
+ /*
+ * Support for relational expressions.
+ *
+ * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the
+ * result respectively when the parameter value is non-empty, or when the
+ * parameter value is undefined or empty; support for the ternary ?:
+ * operator was anticipated, but not implemented for 10 years.
+ *
+ * To make ${relational-expr?result} and ${relational-expr:result} work as
+ * expected without breaking the way that ? and : work, relational
+ * expressions evaluate to a non-empty or empty value. It does not matter
+ * what non-empty value we use for TRUE. However we must not use the
+ * undefined (null pointer) value for FALSE - that would raise the
+ * MAC_PARSE_UNDEF flag.
+ *
+ * The value of a relational expression can be exposed with ${relational-expr},
+ * i.e. a relational expression that is not followed by ? or : conditional
+ * expansion.
+ */
+#define MAC_EXP_BVAL_TRUE "true"
+#define MAC_EXP_BVAL_FALSE ""
+
+ /*
+ * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header
+ * file.
+ */
+#define MAC_EXP_OP_STR_EQ "=="
+#define MAC_EXP_OP_STR_NE "!="
+#define MAC_EXP_OP_STR_LT "<"
+#define MAC_EXP_OP_STR_LE "<="
+#define MAC_EXP_OP_STR_GE ">="
+#define MAC_EXP_OP_STR_GT ">"
+#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \
+ "\" or \"" MAC_EXP_OP_STR_NE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LT "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GT "\""
+
+static const NAME_CODE mac_exp_op_table[] =
+{
+ MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
+ MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT,
+ MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE,
+ MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT,
+ 0, MAC_EXP_OP_TOK_NONE,
+};
+
+ /*
+ * The whitespace separator set.
+ */
+#define MAC_EXP_WHITESPACE CHARS_SPACE
+
+ /*
+ * Support for operator extensions.
+ */
+static HTABLE *mac_exp_ext_table;
+static VSTRING *mac_exp_ext_key;
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* atol_or_die - convert or die */
+
+static long atol_or_die(const char *strval)
+{
+ long result;
+ char *remainder;
+
+ result = sane_strtol(strval, &remainder, 10);
+ if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE)
+ msg_fatal("mac_exp_eval: bad conversion: %s", strval);
+ return (result);
+}
+
+/* mac_exp_eval - evaluate binary expression */
+
+static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val,
+ const char *rite)
+{
+ static const char myname[] = "mac_exp_eval";
+ long delta;
+
+ /*
+ * Numerical or string comparison.
+ */
+ if (alldig(left) && alldig(rite)) {
+ delta = atol_or_die(left) - atol_or_die(rite);
+ } else {
+ delta = strcmp(left, rite);
+ }
+ switch (tok_val) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, tok_val);
+ }
+}
+
+/* mac_exp_parse_error - report parse error, set error flag, return status */
+
+static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ return (mc->status |= MAC_PARSE_ERROR);
+};
+
+/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */
+
+#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \
+ return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \
+ } while (0)
+
+ /*
+ * Postfix 3.0 introduces support for {text} operands. Only with these do we
+ * support the ternary ?: operator and relational operators.
+ *
+ * We cannot support operators in random text, because that would break Postfix
+ * 2.11 compatibility. For example, with the expression "${name?value}", the
+ * value is random text that may contain ':', '?', '{' and '}' characters.
+ * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates
+ * to "?foo:{b}ar" or empty. There are explicit tests in this directory and
+ * the postconf directory to ensure that Postfix 2.11 compatibility is
+ * maintained.
+ *
+ * Ideally, future Postfix configurations enclose random text operands inside
+ * {} braces. These allow whitespace around operands, which improves
+ * readability.
+ */
+
+/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */
+
+#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \
+ ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \
+ (cp += len) : 0)
+
+/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */
+
+static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp)
+{
+ char *payload;
+ char *cp;
+ int level;
+ int ch;
+
+ /*
+ * Extract the payload and balance the {}. The caller is expected to skip
+ * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) {
+ if ((ch = *cp) == 0) {
+ mac_exp_parse_error(mc, "unbalanced {} in attribute expression: "
+ "\"%s\"",
+ *bp);
+ return (0);
+ } else if (ch == '{') {
+ level++;
+ } else if (ch == '}') {
+ if (--level <= 0)
+ break;
+ }
+ }
+ *cp++ = 0;
+
+ /*
+ * Skip trailing whitespace after }.
+ */
+ *bp = cp + strspn(cp, MAC_EXP_WHITESPACE);
+ return (payload);
+}
+
+/* mac_exp_parse_relational - parse relational expression, advance read ptr */
+
+static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup,
+ char **bp)
+{
+ char *cp = *bp;
+ VSTRING *left_op_buf;
+ VSTRING *rite_op_buf;
+ const char *left_op_strval;
+ const char *rite_op_strval;
+ char *op_pos;
+ char *op_strval;
+ size_t op_len;
+ int op_tokval;
+ int op_result;
+ size_t tmp_len;
+ char *type_pos;
+ size_t type_len;
+ MAC_EXPAND_RELOP_FN relop_eval;
+
+ /*
+ * Left operand. The caller is expected to skip leading whitespace before
+ * the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Operator. Todo: regexp operator.
+ */
+ op_pos = cp;
+ op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */
+ op_strval = mystrndup(cp, op_len);
+ op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval);
+ myfree(op_strval);
+ if (op_tokval == MAC_EXP_OP_TOK_NONE)
+ MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"",
+ MAC_EXP_OP_STR_ANY, left_op_strval, cp);
+ cp += op_len;
+
+ /*
+ * Custom operator suffix.
+ */
+ if (mac_exp_ext_table && ISALNUM(*cp)) {
+ type_pos = cp;
+ for (type_len = 1; ISALNUM(cp[type_len]); type_len++)
+ /* void */ ;
+ cp += type_len;
+ vstring_sprintf(mac_exp_ext_key, "%.*s",
+ (int) (op_len + type_len), op_pos);
+ if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table,
+ STR(mac_exp_ext_key))) == 0)
+ MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"",
+ (int) op_len, op_pos, (int) type_len, type_pos);
+ } else {
+ relop_eval = mac_exp_eval;
+ }
+
+ /*
+ * Right operand. Todo: syntax may depend on operator.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0)
+ MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: "
+ "\"...{%s} %.*s>>>%.20s\"",
+ left_op_strval, (int) op_len, op_pos, cp);
+ if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Evaluate the relational expression. Todo: regexp support.
+ */
+ mc->status |=
+ mac_expand(left_op_buf = vstring_alloc(100), left_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ mc->status |=
+ mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0
+ && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval,
+ vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR)
+ mc->status |= MAC_PARSE_ERROR;
+ vstring_free(left_op_buf);
+ vstring_free(rite_op_buf);
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Here, we fake up a non-empty or empty parameter value lookup result,
+ * for compatibility with the historical code that looks named parameter
+ * values.
+ */
+ if (mc->flags & MAC_EXP_FLAG_SCAN) {
+ *lookup = 0;
+ } else {
+ switch (op_result) {
+ case MAC_EXP_OP_RES_TRUE:
+ *lookup = MAC_EXP_BVAL_TRUE;
+ break;
+ case MAC_EXP_OP_RES_FALSE:
+ *lookup = MAC_EXP_BVAL_FALSE;
+ break;
+ default:
+ msg_panic("mac_expand: unexpected operator result: %d", op_result);
+ }
+ }
+ *bp = cp;
+ return (0);
+}
+
+/* mac_expand_add_relop - register operator extensions */
+
+void mac_expand_add_relop(int *tok_list, const char *suffix,
+ MAC_EXPAND_RELOP_FN relop_eval)
+{
+ const char myname[] = "mac_expand_add_relop";
+ const char *tok_name;
+ int *tp;
+
+ /*
+ * Sanity checks.
+ */
+ if (!allalnum(suffix))
+ msg_panic("%s: bad operator suffix: %s", myname, suffix);
+
+ /*
+ * One-time initialization.
+ */
+ if (mac_exp_ext_table == 0) {
+ mac_exp_ext_table = htable_create(10);
+ mac_exp_ext_key = vstring_alloc(10);
+ }
+ for (tp = tok_list; *tp; tp++) {
+ if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0)
+ msg_panic("%s: unknown token code: %d", myname, *tp);
+ vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix);
+ if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0)
+ msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key));
+ (void) htable_enter(mac_exp_ext_table,
+ STR(mac_exp_ext_key), (void *) relop_eval);
+ }
+}
+
+/* mac_expand_callback - callback for mac_parse */
+
+static int mac_expand_callback(int type, VSTRING *buf, void *ptr)
+{
+ static const char myname[] = "mac_expand_callback";
+ MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr;
+ int lookup_mode;
+ const char *lookup;
+ char *cp;
+ int ch;
+ ssize_t res_len;
+ ssize_t tmp_len;
+ const char *res_iftrue;
+ const char *res_iffalse;
+
+ /*
+ * Sanity check.
+ */
+ if (mc->level++ > 100)
+ mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"",
+ vstring_str(buf));
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Named parameter or relational expression. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ if (type == MAC_PARSE_EXPR) {
+
+ cp = vstring_str(buf);
+
+ /*
+ * Relational expression. If recursion is disabled, perform only one
+ * level of $name expansion.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if (mac_exp_parse_relational(mc, &lookup, &cp) != 0)
+ return (mc->status);
+
+ /*
+ * Look for the ? or : operator.
+ */
+ if ((ch = *cp) != 0) {
+ if (ch != '?' && ch != ':')
+ MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: "
+ "\"...}>>>%.20s\"", cp);
+ cp++;
+ }
+ }
+
+ /*
+ * Named parameter.
+ */
+ else {
+ char *start;
+
+ /*
+ * Look for the ? or : operator. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ start = (cp += strspn(cp, MAC_EXP_WHITESPACE));
+ for ( /* void */ ; /* void */ ; cp++) {
+ if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) {
+ *cp = 0;
+ lookup_mode = MAC_EXP_MODE_USE;
+ break;
+ }
+ if (ch == '?' || ch == ':') {
+ *cp++ = 0;
+ cp += tmp_len;
+ lookup_mode = MAC_EXP_MODE_TEST;
+ break;
+ }
+ ch = *cp;
+ if (!ISALNUM(ch) && ch != '_') {
+ MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: "
+ "\"...%.*s>>>%.20s\"",
+ (int) (cp - vstring_str(buf)),
+ vstring_str(buf), cp);
+ }
+ }
+
+ /*
+ * Look up the named parameter. Todo: allow the lookup function
+ * to specify if the result is safe for $name expansion.
+ */
+ lookup = mc->lookup(start, lookup_mode, mc->context);
+ }
+
+ /*
+ * Return the requested result. After parsing the result operand
+ * following ?, we fall through to parse the result operand following
+ * :. This is necessary with the ternary ?: operator: first, with
+ * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(),
+ * and second, to find garbage after any result operand. Without
+ * MAC_EXP_FLAG_SCAN the content of only one of the ?: result
+ * operands will be parsed with mac_parse(); syntax errors in the
+ * other operand will be missed.
+ */
+ switch (ch) {
+ case '?':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iftrue = cp;
+ cp = ""; /* no left-over text */
+ }
+ if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iftrue, mac_expand_callback,
+ (void *) mc);
+ if (*cp == 0) /* end of input, OK */
+ break;
+ if (*cp != ':') /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "\":\" expected at: "
+ "\"...%s}>>>%.20s\"", res_iftrue, cp);
+ cp += 1;
+ /* FALLTHROUGH: do not remove, see comment above. */
+ case ':':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iffalse = cp;
+ cp = ""; /* no left-over text */
+ }
+ if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iffalse, mac_expand_callback,
+ (void *) mc);
+ if (*cp != 0) /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "unexpected input at: "
+ "\"...%s}>>>%.20s\"", res_iffalse, cp);
+ break;
+ case 0:
+ if (lookup == 0) {
+ mc->status |= MAC_PARSE_UNDEF;
+ } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
+ /* void */ ;
+ } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
+ vstring_strcpy(buf, lookup);
+ mc->status |= mac_parse(vstring_str(buf), mac_expand_callback,
+ (void *) mc);
+ } else {
+ res_len = VSTRING_LEN(mc->result);
+ vstring_strcat(mc->result, lookup);
+ if (mc->flags & MAC_EXP_FLAG_PRINTABLE) {
+ printable(vstring_str(mc->result) + res_len, '_');
+ } else if (mc->filter) {
+ cp = vstring_str(mc->result) + res_len;
+ while (*(cp += strspn(cp, mc->filter)))
+ *cp++ = '_';
+ }
+ }
+ break;
+ default:
+ msg_panic("%s: unknown operator code %d", myname, ch);
+ }
+ }
+
+ /*
+ * Literal text.
+ */
+ else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) {
+ vstring_strcat(mc->result, vstring_str(buf));
+ }
+ mc->level--;
+
+ return (mc->status);
+}
+
+/* mac_expand - expand $name instances */
+
+int mac_expand(VSTRING *result, const char *pattern, int flags,
+ const char *filter,
+ MAC_EXP_LOOKUP_FN lookup, void *context)
+{
+ MAC_EXP_CONTEXT mc;
+ int status;
+
+ /*
+ * Bundle up the request and do the substitutions.
+ */
+ mc.result = result;
+ mc.flags = flags;
+ mc.filter = filter;
+ mc.lookup = lookup;
+ mc.context = context;
+ mc.status = 0;
+ mc.level = 0;
+ if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0)
+ VSTRING_RESET(result);
+ status = mac_parse(pattern, mac_expand_callback, (void *) &mc);
+ if ((flags & MAC_EXP_FLAG_SCAN) == 0)
+ VSTRING_TERMINATE(result);
+
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * This code certainly deserves a stand-alone test program.
+ */
+#include <stringops.h>
+#include <htable.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop,
+ const char *rite)
+{
+ const char myname[] = "length_relop_eval";
+ ssize_t delta = strlen(left) - strlen(rite);
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+ int length_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ mac_expand_add_relop(length_relops, "length", length_relop_eval);
+
+ /*
+ * Loop over the inputs.
+ */
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/mac_expand.h b/src/util/mac_expand.h
new file mode 100644
index 0000000..fbe6347
--- /dev/null
+++ b/src/util/mac_expand.h
@@ -0,0 +1,81 @@
+#ifndef _MAC_EXPAND_H_INCLUDED_
+#define _MAC_EXPAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* mac_expand 3h
+/* SUMMARY
+/* expand macro references in string
+/* SYNOPSIS
+/* #include <mac_expand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <mac_parse.h>
+
+ /*
+ * Features.
+ */
+#define MAC_EXP_FLAG_NONE (0)
+#define MAC_EXP_FLAG_RECURSE (1<<0)
+#define MAC_EXP_FLAG_APPEND (1<<1)
+#define MAC_EXP_FLAG_SCAN (1<<2)
+#define MAC_EXP_FLAG_PRINTABLE (1<<3)
+
+ /*
+ * Token codes, public so tha they are available to mac_expand_add_relop()
+ */
+#define MAC_EXP_OP_TOK_NONE 0 /* Sentinel */
+#define MAC_EXP_OP_TOK_EQ 1 /* == */
+#define MAC_EXP_OP_TOK_NE 2 /* != */
+#define MAC_EXP_OP_TOK_LT 3 /* < */
+#define MAC_EXP_OP_TOK_LE 4 /* <= */
+#define MAC_EXP_OP_TOK_GE 5 /* >= */
+#define MAC_EXP_OP_TOK_GT 6 /* > */
+
+ /*
+ * Relational operator results. An enum to discourage assuming that 0 is
+ * false, !0 is true.
+ */
+typedef enum MAC_EXP_OP_RES {
+ MAC_EXP_OP_RES_TRUE,
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_ERROR,
+} MAC_EXP_OP_RES;
+
+
+extern MAC_EXP_OP_RES mac_exp_op_res_bool[2];
+
+ /*
+ * Real lookup or just a test?
+ */
+#define MAC_EXP_MODE_TEST (0)
+#define MAC_EXP_MODE_USE (1)
+
+typedef const char *(*MAC_EXP_LOOKUP_FN) (const char *, int, void *);
+typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (const char *, int, const char *);
+
+extern int mac_expand(VSTRING *, const char *, int, const char *, MAC_EXP_LOOKUP_FN, void *);
+void mac_expand_add_relop(int *, const char *, MAC_EXPAND_RELOP_FN);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mac_expand.in b/src/util/mac_expand.in
new file mode 100644
index 0000000..1d61906
--- /dev/null
+++ b/src/util/mac_expand.in
@@ -0,0 +1,105 @@
+name1 = name1-value
+
+$name1
+$(name1
+$(name1)
+$( name1)
+$(name1 )
+$(na me1)
+${na me1}
+${${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} ?name 1 defined, |$name1|$name2|}
+${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1}x?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+${{text}}
+${{text}?{non-empty}:{empty}}
+${{text} = {}}
+${{${ name1}} == {}}
+${name1?{${ name1}}:{${name2}}}
+${name2?{${ name1}}:{${name2}}}
+${name2?{${name1}}:{${ name2}}}
+${name2:{${name1}}:{${name2}}}
+${name2?{${name1}}?{${name2}}}
+${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{xx}==(yy)?{oops}:{phew}}
+
+name1 = name1-value
+
+${name1?name 1 defined, |$name1|$name2|}
+${name1:name 1 undefined, |$name1|$name2|}
+${name2?name 2 defined, |$name1|$name2|}
+${name2:name 2 undefined, |$name1|$name2|}
+|$name1|$name2|
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+${{$name1} != {}}
+${{$name1} == {}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+${{$name2} != {}}
+${{$name2} == {}}
+
+
+${{1} == {1}}
+${{1} < {1}}
+${{1} <= {1}}
+${{1} >= {1}}
+${{1} > {1}}
+${{1} == {2}}
+${{1} < {2}}
+${{1} <= {2}}
+${{1} >= {2}}
+${{1} > {2}}
+${{a} == {a}}
+${{a} < {a}}
+${{a} <= {a}}
+${{a} >= {a}}
+${{a} > {a}}
+${{a} == {b}}
+${{a} < {b}}
+${{a} <= {b}}
+${{a} >= {b}}
+${{a} > {b}}
+
+name1 = foo
+
+${{$name1} >=blah {bar}}
+${{aaa} == {bbb}}
+${{aaa} ==length {bbb}}
+${{aaa} <=length {bbb}}
+${{aaa} >=length {bbb}}
+${{aaa} != {bbb}}
+${{aaa} !=length {bbb}}
+${{aaa} > {bb}}
+${{aaa} >length {bb}}
+${{aaa} >= {bb}}
+${{aaa} >=length {bb}}
+${{aaa} < {bb}}
+${{aaa} <length {bb}}
+${{aaa} <= {bb}}
+${{aaa} <=length {bb}}
diff --git a/src/util/mac_expand.ref b/src/util/mac_expand.ref
new file mode 100644
index 0000000..854c4f9
--- /dev/null
+++ b/src/util/mac_expand.ref
@@ -0,0 +1,222 @@
+<< name1 = name1-value
+<<
+<< $name1
+stat=0 result=name1-value
+<< $(name1
+unknown: warning: truncated macro reference: "$(name1"
+stat=1 result=
+<< $(name1)
+stat=0 result=name1-value
+<< $( name1)
+stat=0 result=name1-value
+<< $(name1 )
+stat=0 result=name1-value
+<< $(na me1)
+unknown: warning: attribute name syntax error at: "...na>>> me1"
+stat=1 result=
+<< ${na me1}
+unknown: warning: attribute name syntax error at: "...na>>> me1"
+stat=1 result=
+<< ${${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "...>>>${name1} != {}?name "
+stat=1 result=
+<< ${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "... >>>${name1} != {}?name "
+stat=1 result=
+<< ${ ${name1} ?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "... >>>${name1} ?name 1 def"
+stat=1 result=
+<< ${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>? {name 1 defined, |"
+stat=1 result=
+<< ${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+unknown: warning: attribute name syntax error at: "...x>>>{$name1} != {}?{name"
+stat=1 result=
+<< ${{$name1}x?{name 1 defined, |$name1|$name2|}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>x?{name 1 defined, |"
+stat=1 result=
+<< ${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+unknown: warning: "?" or ":" expected at: "...}>>>x{name 1 defined, |$"
+stat=1 result=
+<< ${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+stat=2 result=x{name 1 defined, |name1-value||}
+<< ${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x:{name 1 undefined,"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+stat=2 result=x{name 2 undefined, |name1-value||}
+<< ${{text}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>"
+stat=1 result=
+<< ${{text}?{non-empty}:{empty}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>?{non-empty}:{empty}"
+stat=1 result=
+<< ${{text} = {}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>= {}"
+stat=1 result=
+<< ${{${ name1}} == {}}
+stat=0 result=
+<< ${name1?{${ name1}}:{${name2}}}
+stat=0 result=name1-value
+<< ${name2?{${ name1}}:{${name2}}}
+stat=2 result=
+<< ${name2?{${name1}}:{${ name2}}}
+stat=2 result=
+<< ${name2:{${name1}}:{${name2}}}
+unknown: warning: unexpected input at: "...${name1}}>>>:{${name2}}"
+stat=1 result=name1-value
+<< ${name2?{${name1}}?{${name2}}}
+unknown: warning: ":" expected at: "...${name1}}>>>?{${name2}}"
+stat=1 result=
+<< ${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{xx}==(yy)?{oops}:{phew}}
+unknown: warning: "{expression}" expected at: "...{xx} ==>>>(yy)?{oops}:{phew}"
+stat=1 result=
+<<
+
+<< name1 = name1-value
+<<
+<< ${name1?name 1 defined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${name1:name 1 undefined, |$name1|$name2|}
+stat=0 result=
+<< ${name2?name 2 defined, |$name1|$name2|}
+stat=0 result=
+<< ${name2:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
+<< |$name1|$name2|
+stat=2 result=|name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}}
+stat=0 result=true
+<< ${{$name1} == {}}
+stat=0 result=
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+stat=2 result= name 2 undefined, |name1-value||
+<< ${{$name2} != {}}
+stat=2 result=
+<< ${{$name2} == {}}
+stat=2 result=true
+<<
+
+<<
+<< ${{1} == {1}}
+stat=0 result=true
+<< ${{1} < {1}}
+stat=0 result=
+<< ${{1} <= {1}}
+stat=0 result=true
+<< ${{1} >= {1}}
+stat=0 result=true
+<< ${{1} > {1}}
+stat=0 result=
+<< ${{1} == {2}}
+stat=0 result=
+<< ${{1} < {2}}
+stat=0 result=true
+<< ${{1} <= {2}}
+stat=0 result=true
+<< ${{1} >= {2}}
+stat=0 result=
+<< ${{1} > {2}}
+stat=0 result=
+<< ${{a} == {a}}
+stat=0 result=true
+<< ${{a} < {a}}
+stat=0 result=
+<< ${{a} <= {a}}
+stat=0 result=true
+<< ${{a} >= {a}}
+stat=0 result=true
+<< ${{a} > {a}}
+stat=0 result=
+<< ${{a} == {b}}
+stat=0 result=
+<< ${{a} < {b}}
+stat=0 result=true
+<< ${{a} <= {b}}
+stat=0 result=true
+<< ${{a} >= {b}}
+stat=0 result=
+<< ${{a} > {b}}
+stat=0 result=
+<<
+
+<< name1 = foo
+<<
+<< ${{$name1} >=blah {bar}}
+unknown: warning: bad operator suffix at: "...>=>>>blah"
+stat=1 result=
+<< ${{aaa} == {bbb}}
+stat=0 result=
+<< ${{aaa} ==length {bbb}}
+stat=0 result=true
+<< ${{aaa} <=length {bbb}}
+stat=0 result=true
+<< ${{aaa} >=length {bbb}}
+stat=0 result=true
+<< ${{aaa} != {bbb}}
+stat=0 result=true
+<< ${{aaa} !=length {bbb}}
+stat=0 result=
+<< ${{aaa} > {bb}}
+stat=0 result=
+<< ${{aaa} >length {bb}}
+stat=0 result=true
+<< ${{aaa} >= {bb}}
+stat=0 result=
+<< ${{aaa} >=length {bb}}
+stat=0 result=true
+<< ${{aaa} < {bb}}
+stat=0 result=true
+<< ${{aaa} <length {bb}}
+stat=0 result=
+<< ${{aaa} <= {bb}}
+stat=0 result=true
+<< ${{aaa} <=length {bb}}
+stat=0 result=
diff --git a/src/util/mac_parse.c b/src/util/mac_parse.c
new file mode 100644
index 0000000..45b717a
--- /dev/null
+++ b/src/util/mac_parse.c
@@ -0,0 +1,199 @@
+/*++
+/* NAME
+/* mac_parse 3
+/* SUMMARY
+/* locate macro references in string
+/* SYNOPSIS
+/* #include <mac_parse.h>
+/*
+/* int mac_parse(string, action, context)
+/* const char *string;
+/* int (*action)(int type, VSTRING *buf, void *context);
+/* DESCRIPTION
+/* This module recognizes macro expressions in null-terminated
+/* strings. Macro expressions have the form $name, $(text) or
+/* ${text}. A macro name consists of alphanumerics and/or
+/* underscore. Text other than macro expressions is treated
+/* as literal text.
+/*
+/* mac_parse() breaks up its string argument into macro references
+/* and other text, and invokes the \fIaction\fR routine for each item
+/* found. With each action routine call, the \fItype\fR argument
+/* indicates what was found, \fIbuf\fR contains a copy of the text
+/* found, and \fIcontext\fR is passed on unmodified from the caller.
+/* The application is at liberty to clobber \fIbuf\fR.
+/* .IP MAC_PARSE_LITERAL
+/* The content of \fIbuf\fR is literal text.
+/* .IP MAC_PARSE_EXPR
+/* The content of \fIbuf\fR is a macro expression: either a
+/* bare macro name without the preceding "$", or all the text
+/* inside $() or ${}.
+/* .PP
+/* The action routine result value is the bit-wise OR of zero or more
+/* of the following:
+/* .IP MAC_PARSE_ERROR
+/* A parsing error was detected.
+/* .IP MAC_PARSE_UNDEF
+/* A macro was expanded but not defined.
+/* .PP
+/* Use the constant MAC_PARSE_OK when no error was detected.
+/* SEE ALSO
+/* dict(3) dictionary interface.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. malformed macro name.
+/*
+/* The result value is the bit-wise OR of zero or more of the
+/* following:
+/* .IP MAC_PARSE_ERROR
+/* A parsing error was detected.
+/* .IP MAC_PARSE_UNDEF
+/* A macro was expanded but not defined.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mac_parse.h>
+
+ /*
+ * Helper macro for consistency. Null-terminate the temporary buffer,
+ * execute the action, and reset the temporary buffer for re-use.
+ */
+#define MAC_PARSE_ACTION(status, type, buf, context) \
+ do { \
+ VSTRING_TERMINATE(buf); \
+ status |= action((type), (buf), (context)); \
+ VSTRING_RESET(buf); \
+ } while(0)
+
+/* mac_parse - split string into literal text and macro references */
+
+int mac_parse(const char *value, MAC_PARSE_FN action, void *context)
+{
+ const char *myname = "mac_parse";
+ VSTRING *buf = vstring_alloc(1); /* result buffer */
+ const char *vp; /* value pointer */
+ const char *pp; /* open_paren pointer */
+ const char *ep; /* string end pointer */
+ static char open_paren[] = "({";
+ static char close_paren[] = ")}";
+ int level;
+ int status = 0;
+
+#define SKIP(start, var, cond) do { \
+ for (var = start; *var && (cond); var++) \
+ /* void */; \
+ } while (0)
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s", myname, value);
+
+ for (vp = value; *vp;) {
+ if (*vp != '$') { /* ordinary character */
+ VSTRING_ADDCH(buf, *vp);
+ vp += 1;
+ } else if (vp[1] == '$') { /* $$ becomes $ */
+ VSTRING_ADDCH(buf, *vp);
+ vp += 2;
+ } else { /* found bare $ */
+ if (VSTRING_LEN(buf) > 0)
+ MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
+ vp += 1;
+ pp = open_paren;
+ if (*vp == *pp || *vp == *++pp) { /* ${x} or $(x) */
+ level = 1;
+ vp += 1;
+ for (ep = vp; level > 0; ep++) {
+ if (*ep == 0) {
+ msg_warn("truncated macro reference: \"%s\"", value);
+ status |= MAC_PARSE_ERROR;
+ break;
+ }
+ if (*ep == *pp)
+ level++;
+ if (*ep == close_paren[pp - open_paren])
+ level--;
+ }
+ if (status & MAC_PARSE_ERROR)
+ break;
+ vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1);
+ vp = ep;
+ } else { /* plain $x */
+ SKIP(vp, ep, ISALNUM(*ep) || *ep == '_');
+ vstring_strncat(buf, vp, ep - vp);
+ vp = ep;
+ }
+ if (VSTRING_LEN(buf) == 0) {
+ status |= MAC_PARSE_ERROR;
+ msg_warn("empty macro name: \"%s\"", value);
+ break;
+ }
+ MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context);
+ }
+ }
+ if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0)
+ MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read strings from stdin, print parsed
+ * result to stdout.
+ */
+#include <vstring_vstream.h>
+
+/* mac_parse_print - print parse tree */
+
+static int mac_parse_print(int type, VSTRING *buf, void *unused_context)
+{
+ char *type_name;
+
+ switch (type) {
+ case MAC_PARSE_EXPR:
+ type_name = "MAC_PARSE_EXPR";
+ break;
+ case MAC_PARSE_LITERAL:
+ type_name = "MAC_PARSE_LITERAL";
+ break;
+ default:
+ msg_panic("unknown token type %d", type);
+ }
+ vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf));
+ return (0);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(1);
+
+ while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
+ mac_parse(vstring_str(buf), mac_parse_print, (void *) 0);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/mac_parse.h b/src/util/mac_parse.h
new file mode 100644
index 0000000..5ed0dc1
--- /dev/null
+++ b/src/util/mac_parse.h
@@ -0,0 +1,51 @@
+#ifndef _MAC_PARSE_H_INCLUDED_
+#define _MAC_PARSE_H_INCLUDED_
+
+/*++
+/* NAME
+/* mac_parse 3h
+/* SUMMARY
+/* locate macro references in string
+/* SYNOPSIS
+/* #include <mac_parse.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define MAC_PARSE_LITERAL 1
+#define MAC_PARSE_EXPR 2
+#define MAC_PARSE_VARNAME MAC_PARSE_EXPR /* 2.1 compatibility */
+
+#define MAC_PARSE_OK 0
+#define MAC_PARSE_ERROR (1<<0)
+#define MAC_PARSE_UNDEF (1<<1)
+#define MAC_PARSE_USER 2 /* start user definitions */
+
+typedef int (*MAC_PARSE_FN) (int, VSTRING *, void *);
+
+extern int WARN_UNUSED_RESULT mac_parse(const char *, MAC_PARSE_FN, void *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/make_dirs.c b/src/util/make_dirs.c
new file mode 100644
index 0000000..2e37f8f
--- /dev/null
+++ b/src/util/make_dirs.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* make_dirs 3
+/* SUMMARY
+/* create directory hierarchy
+/* SYNOPSIS
+/* #include <make_dirs.h>
+/*
+/* int make_dirs(path, perms)
+/* const char *path;
+/* int perms;
+/* DESCRIPTION
+/* make_dirs() creates the directory specified in \fIpath\fR, and
+/* creates any missing intermediate directories as well. Directories
+/* are created with the permissions specified in \fIperms\fR, as
+/* modified by the process umask.
+/* DIAGNOSTICS:
+/* Fatal: out of memory. make_dirs() returns 0 in case of success.
+/* In case of problems. make_dirs() returns -1 and \fIerrno\fR
+/* reflects the nature of the problem.
+/* SEE ALSO
+/* mkdir(2)
+/* 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>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "make_dirs.h"
+#include "warn_stat.h"
+
+/* make_dirs - create directory hierarchy */
+
+int make_dirs(const char *path, int perms)
+{
+ const char *myname = "make_dirs";
+ char *saved_path;
+ unsigned char *cp;
+ int saved_ch;
+ struct stat st;
+ int ret;
+ mode_t saved_mode = 0;
+ gid_t egid = -1;
+
+ /*
+ * Initialize. Make a copy of the path that we can safely clobber.
+ */
+ cp = (unsigned char *) (saved_path = mystrdup(path));
+
+ /*
+ * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with
+ * my own took a day, spread out over several days.
+ */
+#define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; }
+
+ SKIP_WHILE(*cp == '/', cp);
+
+ for (;;) {
+ SKIP_WHILE(*cp != '/', cp);
+ if ((saved_ch = *cp) != 0)
+ *cp = 0;
+ if ((ret = stat(saved_path, &st)) >= 0) {
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ ret = -1;
+ break;
+ }
+ saved_mode = st.st_mode;
+ } else {
+ if (errno != ENOENT)
+ break;
+
+ /*
+ * mkdir(foo) fails with EEXIST if foo is a symlink.
+ */
+#if 0
+
+ /*
+ * Create a new directory. Unfortunately, mkdir(2) has no
+ * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must
+ * require that the parent directory is not world writable.
+ * Detecting a lost race condition after the fact is not
+ * sufficient, as an attacker could repeat the attack and add one
+ * directory level at a time.
+ */
+ if (saved_mode & S_IWOTH) {
+ msg_warn("refusing to mkdir %s: parent directory is writable by everyone",
+ saved_path);
+ errno = EPERM;
+ ret = -1;
+ break;
+ }
+#endif
+ if ((ret = mkdir(saved_path, perms)) < 0) {
+ if (errno != EEXIST)
+ break;
+ /* Race condition? */
+ if ((ret = stat(saved_path, &st)) < 0)
+ break;
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ ret = -1;
+ break;
+ }
+ }
+
+ /*
+ * Fix directory ownership when mkdir() ignores the effective
+ * GID. Don't change the effective UID for doing this.
+ */
+ if ((ret = stat(saved_path, &st)) < 0) {
+ msg_warn("%s: stat %s: %m", myname, saved_path);
+ break;
+ }
+ if (egid == -1)
+ egid = getegid();
+ if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) {
+ msg_warn("%s: chgrp %s: %m", myname, saved_path);
+ break;
+ }
+ }
+ if (saved_ch != 0)
+ *cp = saved_ch;
+ SKIP_WHILE(*cp == '/', cp);
+ if (*cp == 0)
+ break;
+ }
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_path);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program. Usage: make_dirs path...
+ */
+#include <stdlib.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc < 2)
+ msg_fatal("usage: %s path...", argv[0]);
+ while (--argc > 0 && *++argv != 0)
+ if (make_dirs(*argv, 0755))
+ msg_fatal("%s: %m", *argv);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/make_dirs.h b/src/util/make_dirs.h
new file mode 100644
index 0000000..0df6117
--- /dev/null
+++ b/src/util/make_dirs.h
@@ -0,0 +1,30 @@
+#ifndef MAKE_DIRS_H_INCLUDED_
+#define MAKE_DIRS_H_INCLUDED_
+
+/*++
+/* NAME
+/* make_dirs 3h
+/* SUMMARY
+/* create directory hierarchy
+/* SYNOPSIS
+/* #include <make_dirs.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int make_dirs(const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mask_addr.c b/src/util/mask_addr.c
new file mode 100644
index 0000000..5ddd0ca
--- /dev/null
+++ b/src/util/mask_addr.c
@@ -0,0 +1,68 @@
+/*++
+/* NAME
+/* mask_addr 3
+/* SUMMARY
+/* address bit banging
+/* SYNOPSIS
+/* #include <mask_addr.h>
+/*
+/* void mask_addr(addr_bytes, addr_byte_count, network_bits)
+/* unsigned char *addr_bytes;
+/* unsigned addr_byte_count;
+/* unsigned network_bits;
+/* DESCRIPTION
+/* mask_addr() clears all the host bits in the specified
+/* address. The code can handle addresses of any length,
+/* and bytes of any width.
+/*
+/* Arguments:
+/* .IP addr_bytes
+/* The network address in network byte order.
+/* .IP addr_byte_count
+/* The network address length in bytes.
+/* .IP network_bits
+/* The number of initial bits that will not be cleared.
+/* DIAGNOSTICS
+/* Fatal errors: the number of network bits exceeds the address size.
+/* 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 <limits.h> /* CHAR_BIT */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mask_addr.h>
+
+/* mask_addr - mask off a variable-length address */
+
+void mask_addr(unsigned char *addr_bytes,
+ unsigned addr_byte_count,
+ unsigned network_bits)
+{
+ unsigned char *p;
+
+ if (network_bits > addr_byte_count * CHAR_BIT)
+ msg_panic("mask_addr: address byte count %d too small for bit count %d",
+ addr_byte_count, network_bits);
+
+ p = addr_bytes + network_bits / CHAR_BIT;
+ network_bits %= CHAR_BIT;
+
+ if (network_bits != 0)
+ *p++ &= ~0U << (CHAR_BIT - network_bits);
+
+ while (p < addr_bytes + addr_byte_count)
+ *p++ = 0;
+}
diff --git a/src/util/mask_addr.h b/src/util/mask_addr.h
new file mode 100644
index 0000000..0e70a41
--- /dev/null
+++ b/src/util/mask_addr.h
@@ -0,0 +1,30 @@
+#ifndef _MASK_ADDR_H_INCLUDED_
+#define _MASK_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mask_addr 3h
+/* SUMMARY
+/* address bit banging
+/* SYNOPSIS
+/* #include <mask_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void mask_addr(unsigned char *, unsigned, unsigned);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/match_list.c b/src/util/match_list.c
new file mode 100644
index 0000000..520485d
--- /dev/null
+++ b/src/util/match_list.c
@@ -0,0 +1,281 @@
+/*++
+/* NAME
+/* match_list 3
+/* SUMMARY
+/* generic list-based pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/*
+/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/* int count;
+/* int (*func)(int flags, const char *string, const char *pattern);
+/*
+/* int match_list_match(list, string,...)
+/* MATCH_LIST *list;
+/* const char *string;
+/*
+/* void match_list_free(list)
+/* MATCH_LIST *list;
+/* DESCRIPTION
+/* This module implements a framework for tests for list
+/* membership. The actual tests are done by user-supplied
+/* functions.
+/*
+/* Patterns are separated by whitespace and/or commas. A pattern
+/* is either a string, a file name (in which case the contents
+/* of the file are substituted for the file name) or a type:name
+/* lookup table specification. In order to reverse the result
+/* of a pattern match, precede a pattern with an exclamation
+/* point (!).
+/*
+/* match_list_init() performs initializations. When the global
+/* util_utf8_enable variable is non-zero, and when the code
+/* is compiled with EAI support, string comparison will use
+/* caseless UTF-8 mode. Otherwise, only ASCII characters will
+/* be casefolded.
+/*
+/* match_list_match() matches strings against the specified
+/* pattern list, passing the first string to the first function
+/* given to match_list_init(), the second string to the second
+/* function, and so on.
+/*
+/* match_list_free() releases storage allocated by match_list_init().
+/*
+/* Arguments:
+/* .IP pname
+/* Parameter name or other identifying information that is
+/* prepended to error messages.
+/* .IP flags
+/* Specifies the bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches any name within the
+/* domain foo.com. If this flag is cleared, foo.com matches
+/* itself only, and .foo.com matches any name below the domain
+/* foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Request that match_list_match() logs a warning and returns
+/* zero (with list->error set to a non-zero dictionary error
+/* code) instead of raising a fatal run-time error.
+/* .RE
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* .IP pattern_list
+/* A list of patterns.
+/* .IP count
+/* Specifies how many match functions follow.
+/* .IP list
+/* Pattern list produced by match_list_init().
+/* .IP string
+/* Search string.
+/* DIAGNOSTICS
+/* Fatal error: unable to open or read a match_list file; invalid
+/* match_list pattern; casefold error (UTF-8 mode only).
+/* SEE ALSO
+/* host_match(3) match hosts by name or by address
+/* 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 <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <argv.h>
+#include <dict.h>
+#include <match_list.h>
+
+/* Application-specific */
+
+#define MATCH_DICTIONARY(pattern) \
+ ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
+
+/* match_list_parse - parse buffer, destroy buffer */
+
+static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list,
+ char *string, int init_match)
+{
+ const char *myname = "match_list_parse";
+ VSTRING *buf = vstring_alloc(10);
+ VSTREAM *fp;
+ const char *delim = CHARS_COMMA_SP;
+ char *bp = string;
+ char *start;
+ char *item;
+ char *map_type_name_flags;
+ int match;
+
+ /*
+ * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search
+ * string at the beginning of a search, and we use strcmp() for string
+ * comparison. This works because string patterns are casefolded during
+ * match_list initialization, and databases are supposed to fold case
+ * upon creation.
+ */
+#define OPEN_FLAGS O_RDONLY
+#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST)
+#define STR(x) vstring_str(x)
+
+ /*
+ * /filename contents are expanded in-line. To support !/filename we
+ * prepend the negation operator to each item from the file.
+ *
+ * If there is an error, implement graceful degradation by inserting a
+ * pseudo table whose lookups fail with a warning message.
+ */
+ while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) {
+ if (*start == '#') {
+ msg_warn("%s: comment at end of line is not supported: %s %s",
+ match_list->pname, start, bp);
+ break;
+ }
+ for (match = init_match, item = start; *item == '!'; item++)
+ match = !match;
+ if (*item == 0)
+ /* No graceful degradation for this... */
+ msg_fatal("%s: no pattern after '!'", match_list->pname);
+ if (*item == '/') { /* /file/name */
+ if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) {
+ /* Replace unusable pattern with pseudo table. */
+ vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item);
+ if (dict_handle(STR(buf)) == 0)
+ dict_register(STR(buf),
+ dict_surrogate(DICT_TYPE_NOFILE, item,
+ OPEN_FLAGS, DICT_FLAGS,
+ "open file %s: %m", item));
+ argv_add(pat_list, STR(buf), (char *) 0);
+ } else {
+ while (vstring_fgets(buf, fp))
+ if (vstring_str(buf)[0] != '#')
+ pat_list = match_list_parse(match_list, pat_list,
+ vstring_str(buf), match);
+ if (vstream_fclose(fp))
+ msg_fatal("%s: read file %s: %m", myname, item);
+ }
+ } else if (MATCH_DICTIONARY(item)) { /* type:table */
+ vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!",
+ item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS));
+ map_type_name_flags = STR(buf) + (match == 0);
+ if (dict_handle(map_type_name_flags) == 0)
+ dict_register(map_type_name_flags,
+ dict_open(item, OPEN_FLAGS, DICT_FLAGS));
+ argv_add(pat_list, STR(buf), (char *) 0);
+ } else { /* other pattern */
+ casefold(match_list->fold_buf, match ?
+ item : STR(vstring_sprintf(buf, "!%s", item)));
+ argv_add(pat_list, STR(match_list->fold_buf), (char *) 0);
+ }
+ }
+ vstring_free(buf);
+ return (pat_list);
+}
+
+/* match_list_init - initialize pattern list */
+
+MATCH_LIST *match_list_init(const char *pname, int flags,
+ const char *patterns, int match_count,...)
+{
+ MATCH_LIST *list;
+ char *saved_patterns;
+ va_list ap;
+ int i;
+
+ if (flags & ~MATCH_FLAG_ALL)
+ msg_panic("match_list_init: bad flags 0x%x", flags);
+
+ list = (MATCH_LIST *) mymalloc(sizeof(*list));
+ list->pname = mystrdup(pname);
+ list->flags = flags;
+ list->match_count = match_count;
+ list->match_func =
+ (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN));
+ list->match_args =
+ (const char **) mymalloc(match_count * sizeof(const char *));
+ va_start(ap, match_count);
+ for (i = 0; i < match_count; i++)
+ list->match_func[i] = va_arg(ap, MATCH_LIST_FN);
+ va_end(ap);
+ list->error = 0;
+ list->fold_buf = vstring_alloc(20);
+
+#define DO_MATCH 1
+
+ saved_patterns = mystrdup(patterns);
+ list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns,
+ DO_MATCH);
+ argv_terminate(list->patterns);
+ myfree(saved_patterns);
+ return (list);
+}
+
+/* match_list_match - match strings against pattern list */
+
+int match_list_match(MATCH_LIST *list,...)
+{
+ const char *myname = "match_list_match";
+ char **cpp;
+ char *pat;
+ int match;
+ int i;
+ va_list ap;
+
+ /*
+ * Iterate over all patterns in the list, stop at the first match.
+ */
+ va_start(ap, list);
+ for (i = 0; i < list->match_count; i++)
+ list->match_args[i] = va_arg(ap, const char *);
+ va_end(ap);
+
+ list->error = 0;
+ for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) {
+ for (match = 1; *pat == '!'; pat++)
+ match = !match;
+ for (i = 0; i < list->match_count; i++) {
+ casefold(list->fold_buf, list->match_args[i]);
+ if (list->match_func[i] (list, STR(list->fold_buf), pat))
+ return (match);
+ else if (list->error != 0)
+ return (0);
+ }
+ }
+ if (msg_verbose)
+ for (i = 0; i < list->match_count; i++)
+ msg_info("%s: %s: no match", myname, list->match_args[i]);
+ return (0);
+}
+
+/* match_list_free - release storage */
+
+void match_list_free(MATCH_LIST *list)
+{
+ /* XXX Should decrement map refcounts. */
+ myfree(list->pname);
+ argv_free(list->patterns);
+ myfree((void *) list->match_func);
+ myfree((void *) list->match_args);
+ vstring_free(list->fold_buf);
+ myfree((void *) list);
+}
diff --git a/src/util/match_list.h b/src/util/match_list.h
new file mode 100644
index 0000000..d8b7794
--- /dev/null
+++ b/src/util/match_list.h
@@ -0,0 +1,66 @@
+#ifndef _MATCH_LIST_H_INCLUDED_
+#define _MATCH_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* match_list 3h
+/* SUMMARY
+/* generic list-based pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct MATCH_LIST MATCH_LIST;
+
+typedef int (*MATCH_LIST_FN) (MATCH_LIST *, const char *, const char *);
+
+struct MATCH_LIST {
+ char *pname; /* used in error messages */
+ int flags; /* processing options */
+ ARGV *patterns; /* one pattern each */
+ int match_count; /* match function/argument count */
+ MATCH_LIST_FN *match_func; /* match functions */
+ const char **match_args; /* match arguments */
+ VSTRING *fold_buf; /* case-folded pattern string */
+ int error; /* last operation */
+};
+
+#define MATCH_FLAG_NONE 0
+#define MATCH_FLAG_PARENT (1<<0)
+#define MATCH_FLAG_RETURN (1<<1)
+#define MATCH_FLAG_ALL (MATCH_FLAG_PARENT | MATCH_FLAG_RETURN)
+
+extern MATCH_LIST *match_list_init(const char *, int, const char *, int,...);
+extern int match_list_match(MATCH_LIST *,...);
+extern void match_list_free(MATCH_LIST *);
+
+ /*
+ * The following functions are not part of the public interface. These
+ * functions may be called only through match_list_match().
+ */
+extern int match_string(MATCH_LIST *, const char *, const char *);
+extern int match_hostname(MATCH_LIST *, const char *, const char *);
+extern int match_hostaddr(MATCH_LIST *, const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/match_ops.c b/src/util/match_ops.c
new file mode 100644
index 0000000..e0b7779
--- /dev/null
+++ b/src/util/match_ops.c
@@ -0,0 +1,312 @@
+/*++
+/* NAME
+/* match_ops 3
+/* SUMMARY
+/* simple string or host pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/*
+/* int match_string(list, string, pattern)
+/* MATCH_LIST *list;
+/* const char *string;
+/* const char *pattern;
+/*
+/* int match_hostname(list, name, pattern)
+/* MATCH_LIST *list;
+/* const char *name;
+/* const char *pattern;
+/*
+/* int match_hostaddr(list, addr, pattern)
+/* MATCH_LIST *list;
+/* const char *addr;
+/* const char *pattern;
+/* DESCRIPTION
+/* This module implements simple string and host name or address
+/* matching. The matching process is case insensitive. If a pattern
+/* has the form type:name, table lookup is used instead of string
+/* or address comparison.
+/*
+/* match_string() matches the string against the pattern, requiring
+/* an exact (case-insensitive) match. The flags argument is not used.
+/*
+/* match_hostname() matches the host name when the hostname matches
+/* the pattern exactly, or when the pattern matches a parent domain
+/* of the named host. The flags argument specifies the bit-wise OR
+/* of zero or more of the following:
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches itself and any name below
+/* the domain foo.com. If this flag is cleared, foo.com matches itself
+/* only, and .foo.com matches any name below the domain foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Log a warning, return "not found", and set list->error to
+/* a non-zero dictionary error code, instead of raising a fatal
+/* run-time error.
+/* .RE
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/*
+/* match_hostaddr() matches a host address when the pattern is
+/* identical to the host address, or when the pattern is a net/mask
+/* that contains the address. The mask specifies the number of
+/* bits in the network part of the pattern. The flags argument is
+/* not used.
+/* 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 <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <dict.h>
+#include <match_list.h>
+#include <stringops.h>
+#include <cidr_match.h>
+
+#define MATCH_DICTIONARY(pattern) \
+ ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
+
+/* match_error - return or raise fatal error */
+
+static int match_error(MATCH_LIST *list, const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ va_list ap;
+
+ /*
+ * Report, and maybe return.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ if (list->flags & MATCH_FLAG_RETURN) {
+ msg_warn("%s: %s", list->pname, vstring_str(buf));
+ } else {
+ msg_fatal("%s: %s", list->pname, vstring_str(buf));
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+/* match_string - match a string literal */
+
+int match_string(MATCH_LIST *list, const char *string, const char *pattern)
+{
+ const char *myname = "match_string";
+ DICT *dict;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern);
+
+ /*
+ * Try dictionary lookup: exact match.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ if (dict_get(dict, string) != 0)
+ return (1);
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ return (0);
+ }
+
+ /*
+ * Try an exact string match. Note that the string and pattern are
+ * already casefolded.
+ */
+ if (strcmp(string, pattern) == 0) {
+ return (1);
+ }
+
+ /*
+ * No match found.
+ */
+ return (0);
+}
+
+/* match_hostname - match a host by name */
+
+int match_hostname(MATCH_LIST *list, const char *name, const char *pattern)
+{
+ const char *myname = "match_hostname";
+ const char *pd;
+ const char *entry;
+ const char *next;
+ int match;
+ DICT *dict;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern);
+
+ /*
+ * Try dictionary lookup: exact match and parent domains.
+ *
+ * Don't look up parent domain substrings with regexp maps etc.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ match = 0;
+ for (entry = name; *entry != 0; entry = next) {
+ if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
+ match = (dict_get(dict, entry) != 0);
+ if (msg_verbose > 1)
+ msg_info("%s: %s: lookup %s:%s %s: %s",
+ myname, list->pname, dict->type, dict->name,
+ entry, match ? "found" : "notfound");
+ if (match != 0)
+ break;
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ }
+ if ((next = strchr(entry + 1, '.')) == 0)
+ break;
+ if (list->flags & MATCH_FLAG_PARENT)
+ next += 1;
+ }
+ return (match);
+ }
+
+ /*
+ * Try an exact match with the host name. Note that the name and the
+ * pattern are already casefolded.
+ */
+ if (strcmp(name, pattern) == 0) {
+ return (1);
+ }
+
+ /*
+ * See if the pattern is a parent domain of the hostname. Note that the
+ * name and the pattern are already casefolded.
+ */
+ else {
+ if (list->flags & MATCH_FLAG_PARENT) {
+ pd = name + strlen(name) - strlen(pattern);
+ if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0)
+ return (1);
+ } else if (pattern[0] == '.') {
+ pd = name + strlen(name) - strlen(pattern);
+ if (pd > name && strcmp(pd, pattern) == 0)
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* match_hostaddr - match host by address */
+
+int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern)
+{
+ const char *myname = "match_hostaddr";
+ char *saved_patt;
+ CIDR_MATCH match_info;
+ DICT *dict;
+ VSTRING *err;
+ int rc;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern);
+
+#define V4_ADDR_STRING_CHARS "01234567890."
+#define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:"
+
+ if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
+ return (0);
+
+ /*
+ * Try dictionary lookup. This can be case insensitive.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ if (dict_get(dict, addr) != 0)
+ return (1);
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ return (0);
+ }
+
+ /*
+ * Try an exact match with the host address. Note that the address and
+ * pattern are already casefolded.
+ */
+ if (pattern[0] != '[') {
+ if (strcmp(addr, pattern) == 0)
+ return (1);
+ } else {
+ size_t addr_len = strlen(addr);
+
+ if (strncmp(addr, pattern + 1, addr_len) == 0
+ && strcmp(pattern + 1 + addr_len, "]") == 0)
+ return (1);
+ }
+
+ /*
+ * Light-weight tests before we get into expensive operations.
+ *
+ * - Don't bother matching IPv4 against IPv6. Postfix transforms
+ * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
+ * Postfix; if not, then Postfix has no business dealing with IPv4
+ * addresses anyway.
+ *
+ * - Don't bother unless the pattern is either an IPv6 address or net/mask.
+ *
+ * We can safely skip IPv4 address patterns because their form is
+ * unambiguous and they did not match in the strcmp() calls above.
+ *
+ * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
+ * input, to avoid triggering false cidr_match_parse() errors.
+ *
+ * The last two conditions below are for backwards compatibility with
+ * earlier Postfix versions: don't abort with fatal errors on junk that
+ * was silently ignored (principle of least astonishment).
+ */
+ if (!strchr(addr, ':') != !strchr(pattern, ':')
+ || pattern[strcspn(pattern, ":/")] == 0
+ || pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
+ || pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
+ return (0);
+
+ /*
+ * No escape from expensive operations: either we have a net/mask
+ * pattern, or we have an address that can have multiple valid
+ * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
+ * to find out if the address matches the pattern is to transform
+ * everything into to binary form, and to do the comparison there.
+ */
+ saved_patt = mystrdup(pattern);
+ err = cidr_match_parse(&match_info, saved_patt, CIDR_MATCH_TRUE,
+ (VSTRING *) 0);
+ myfree(saved_patt);
+ if (err != 0) {
+ list->error = DICT_ERR_RETRY;
+ rc = match_error(list, "%s", vstring_str(err));
+ vstring_free(err);
+ return (rc);
+ }
+ return (cidr_match_execute(&match_info, addr) != 0);
+}
diff --git a/src/util/midna_domain.c b/src/util/midna_domain.c
new file mode 100644
index 0000000..333a5c9
--- /dev/null
+++ b/src/util/midna_domain.c
@@ -0,0 +1,419 @@
+/*++
+/* NAME
+/* midna_domain 3
+/* SUMMARY
+/* ASCII/UTF-8 domain name conversion
+/* SYNOPSIS
+/* #include <midna_domain.h>
+/*
+/* int midna_domain_cache_size;
+/* int midna_domain_transitional;
+/*
+/* const char *midna_domain_to_ascii(
+/* const char *name)
+/*
+/* const char *midna_domain_to_utf8(
+/* const char *name)
+/*
+/* const char *midna_domain_suffix_to_ascii(
+/* const char *name)
+/*
+/* const char *midna_domain_suffix_to_utf8(
+/* const char *name)
+/* AUXILIARY FUNCTIONS
+/* void midna_domain_pre_chroot(void)
+/* DESCRIPTION
+/* The functions in this module transform domain names from/to
+/* ASCII and UTF-8 form. The result is cached to avoid repeated
+/* conversion.
+/*
+/* This module builds on the ICU library implementation of the
+/* UTS #46 specification, using default ICU library options
+/* because those are likely best tested: with transitional
+/* processing, with case mapping, with normalization, with
+/* limited IDNA2003 compatibility, without STD3 ASCII rules.
+/*
+/* midna_domain_to_ascii() converts an UTF-8 or ASCII domain
+/* name to ASCII. The result is a null pointer in case of
+/* error. This function verifies that the result passes
+/* valid_hostname().
+/*
+/* midna_domain_to_utf8() converts an UTF-8 or ASCII domain
+/* name to UTF-8. The result is a null pointer in case of
+/* error. This function verifies that the result, after
+/* conversion to ASCII, passes valid_hostname().
+/*
+/* midna_domain_suffix_to_ascii() and midna_domain_suffix_to_utf8()
+/* take a name that starts with '.' and otherwise perform the
+/* same operations as midna_domain_to_ascii() and
+/* midna_domain_to_utf8().
+/*
+/* midna_domain_cache_size specifies the size of the conversion
+/* result cache. This value is used only once, upon the first
+/* lookup request.
+/*
+/* midna_domain_transitional enables transitional conversion
+/* between UTF8 and ASCII labels.
+/*
+/* midna_domain_pre_chroot() does some pre-chroot initialization.
+/* SEE ALSO
+/* http://unicode.org/reports/tr46/ Unicode IDNA Compatibility processing
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Warnings: conversion error or result validation error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* 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 <ctype.h>
+
+#ifndef NO_EAI
+#include <unicode/uidna.h>
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <ctable.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <name_mask.h>
+#include <midna_domain.h>
+
+ /*
+ * Application-specific.
+ */
+#define DEF_MIDNA_CACHE_SIZE 256
+
+int midna_domain_cache_size = DEF_MIDNA_CACHE_SIZE;
+int midna_domain_transitional = 0;
+static VSTRING *midna_domain_buf; /* x.suffix */
+
+#define STR(x) vstring_str(x)
+
+/* midna_domain_strerror - pick one for error reporting */
+
+static const char *midna_domain_strerror(UErrorCode error, int info_errors)
+{
+
+ /*
+ * XXX The UIDNA_ERROR_EMPTY_LABEL etc. names are defined in an ENUM, so
+ * we can't use #ifdef to dynamically determine which names exist.
+ */
+ static LONG_NAME_MASK uidna_errors[] = {
+ "UIDNA_ERROR_EMPTY_LABEL", UIDNA_ERROR_EMPTY_LABEL,
+ "UIDNA_ERROR_LABEL_TOO_LONG", UIDNA_ERROR_LABEL_TOO_LONG,
+ "UIDNA_ERROR_DOMAIN_NAME_TOO_LONG", UIDNA_ERROR_DOMAIN_NAME_TOO_LONG,
+ "UIDNA_ERROR_LEADING_HYPHEN", UIDNA_ERROR_LEADING_HYPHEN,
+ "UIDNA_ERROR_TRAILING_HYPHEN", UIDNA_ERROR_TRAILING_HYPHEN,
+ "UIDNA_ERROR_HYPHEN_3_4", UIDNA_ERROR_HYPHEN_3_4,
+ "UIDNA_ERROR_LEADING_COMBINING_MARK", UIDNA_ERROR_LEADING_COMBINING_MARK,
+ "UIDNA_ERROR_DISALLOWED", UIDNA_ERROR_DISALLOWED,
+ "UIDNA_ERROR_PUNYCODE", UIDNA_ERROR_PUNYCODE,
+ "UIDNA_ERROR_LABEL_HAS_DOT", UIDNA_ERROR_LABEL_HAS_DOT,
+ "UIDNA_ERROR_INVALID_ACE_LABEL", UIDNA_ERROR_INVALID_ACE_LABEL,
+ "UIDNA_ERROR_BIDI", UIDNA_ERROR_BIDI,
+ "UIDNA_ERROR_CONTEXTJ", UIDNA_ERROR_CONTEXTJ,
+ /* The above errors are defined with ICU 46 and later. */
+ 0,
+ };
+
+ if (info_errors) {
+ return (str_long_name_mask_opt((VSTRING *) 0, "idna error",
+ uidna_errors, info_errors,
+ NAME_MASK_NUMBER | NAME_MASK_COMMA));
+ } else {
+ return u_errorName(error);
+ }
+}
+
+/* midna_domain_pre_chroot - pre-chroot initialization */
+
+void midna_domain_pre_chroot(void)
+{
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
+ if (U_FAILURE(error))
+ msg_warn("ICU library initialization failed: %s",
+ midna_domain_strerror(error, info.errors));
+ uidna_close(idna);
+}
+
+/* midna_domain_to_ascii_create - convert domain to ASCII */
+
+static void *midna_domain_to_ascii_create(const char *name, void *unused_context)
+{
+ static const char myname[] = "midna_domain_to_ascii_create";
+ char buf[1024]; /* XXX */
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+ int anl;
+
+ /*
+ * Paranoia: do not expose uidna_*() to unfiltered network data.
+ */
+ if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, "malformed UTF-8");
+ return (0);
+ }
+
+ /*
+ * Perform the requested conversion.
+ */
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
+ anl = uidna_nameToASCII_UTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
+ uidna_close(idna);
+
+ /*
+ * Paranoia: verify that the result passes valid_hostname(). A quick
+ * check shows that UTS46 ToASCII by default rejects inputs with labels
+ * that start or end in '-', with names or labels that are over-long, or
+ * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on
+ * valid_hostname() on the output side just to be sure.
+ */
+ if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ buf[anl] = 0; /* XXX */
+ if (!valid_hostname(buf, DONT_GRIPE)) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, "malformed ASCII label(s)");
+ return (0);
+ }
+ return (mystrndup(buf, anl));
+ } else {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, midna_domain_strerror(error, info.errors));
+ return (0);
+ }
+}
+
+/* midna_domain_to_utf8_create - convert domain to UTF8 */
+
+static void *midna_domain_to_utf8_create(const char *name, void *unused_context)
+{
+ static const char myname[] = "midna_domain_to_utf8_create";
+ char buf[1024]; /* XXX */
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+ int anl;
+
+ /*
+ * Paranoia: do not expose uidna_*() to unfiltered network data.
+ */
+ if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s",
+ myname, name, "malformed UTF-8");
+ return (0);
+ }
+
+ /*
+ * Perform the requested conversion.
+ */
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_UNICODE, &error);
+ anl = uidna_nameToUnicodeUTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
+ uidna_close(idna);
+
+ /*
+ * Paranoia: UTS46 toUTF8 by default accepts and produces an over-long
+ * name or a name that contains an over-long NR-LDH label (and perhaps
+ * other invalid forms that are not covered in UTS 46, section 4.1). We
+ * rely on midna_domain_to_ascii() to validate the output.
+ */
+ if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ buf[anl] = 0; /* XXX */
+ if (midna_domain_to_ascii(buf) == 0)
+ return (0);
+ return (mystrndup(buf, anl));
+ } else {
+ msg_warn("%s: Problem translating domain \"%.100s\" to UTF8 form: %s",
+ myname, name, midna_domain_strerror(error, info.errors));
+ return (0);
+ }
+}
+
+/* midna_domain_cache_free - cache element destructor */
+
+static void midna_domain_cache_free(void *value, void *unused_context)
+{
+ if (value)
+ myfree(value);
+}
+
+/* midna_domain_to_ascii - convert name to ASCII */
+
+const char *midna_domain_to_ascii(const char *name)
+{
+ static CTABLE *midna_domain_to_ascii_cache = 0;
+
+ if (midna_domain_to_ascii_cache == 0)
+ midna_domain_to_ascii_cache = ctable_create(midna_domain_cache_size,
+ midna_domain_to_ascii_create,
+ midna_domain_cache_free,
+ (void *) 0);
+ return (ctable_locate(midna_domain_to_ascii_cache, name));
+}
+
+/* midna_domain_to_utf8 - convert name to UTF8 */
+
+const char *midna_domain_to_utf8(const char *name)
+{
+ static CTABLE *midna_domain_to_utf8_cache = 0;
+
+ if (midna_domain_to_utf8_cache == 0)
+ midna_domain_to_utf8_cache = ctable_create(midna_domain_cache_size,
+ midna_domain_to_utf8_create,
+ midna_domain_cache_free,
+ (void *) 0);
+ return (ctable_locate(midna_domain_to_utf8_cache, name));
+}
+
+/* midna_domain_suffix_to_ascii - convert .name to ASCII */
+
+const char *midna_domain_suffix_to_ascii(const char *suffix)
+{
+ const char *cache_res;
+
+ /*
+ * If prepending x to .name causes the result to become too long, then
+ * the suffix is bad.
+ */
+ if (midna_domain_buf == 0)
+ midna_domain_buf = vstring_alloc(100);
+ vstring_sprintf(midna_domain_buf, "x%s", suffix);
+ if ((cache_res = midna_domain_to_ascii(STR(midna_domain_buf))) == 0)
+ return (0);
+ else
+ return (cache_res + 1);
+}
+
+/* midna_domain_suffix_to_utf8 - convert .name to UTF8 */
+
+const char *midna_domain_suffix_to_utf8(const char *name)
+{
+ const char *cache_res;
+
+ /*
+ * If prepending x to .name causes the result to become too long, then
+ * the suffix is bad.
+ */
+ if (midna_domain_buf == 0)
+ midna_domain_buf = vstring_alloc(100);
+ vstring_sprintf(midna_domain_buf, "x%s", name);
+ if ((cache_res = midna_domain_to_utf8(STR(midna_domain_buf))) == 0)
+ return (0);
+ else
+ return (cache_res + 1);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - reads names from stdin, reports invalid names to stderr.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <locale.h>
+
+#include <stringops.h> /* XXX util_utf8_enable */
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ const char *bp;
+ const char *ascii;
+ const char *utf8;
+
+ if (setlocale(LC_ALL, "C") == 0)
+ msg_fatal("setlocale(LC_ALL, C) failed: %m");
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ /* msg_verbose = 1; */
+ util_utf8_enable = 1;
+
+ if (geteuid() == 0) {
+ midna_domain_pre_chroot();
+ if (chroot(".") != 0)
+ msg_fatal("chroot(\".\"): %m");
+ }
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+ msg_info("> %s", bp);
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == '#' || *bp == 0)
+ continue;
+ msg_info("unconditional conversions:");
+ utf8 = midna_domain_to_utf8(bp);
+ msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8 ? utf8 : "(error)");
+ ascii = midna_domain_to_ascii(bp);
+ msg_info("\"%s\" ->ascii \"%s\"", bp, ascii ? ascii : "(error)");
+ msg_info("conditional conversions:");
+ if (!allascii(bp)) {
+ if (ascii != 0) {
+ utf8 = midna_domain_to_utf8(ascii);
+ msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"",
+ bp, ascii, utf8 ? utf8 : "(error)");
+ if (utf8 != 0) {
+ if (strcmp(utf8, bp) != 0)
+ msg_warn("\"%s\" != \"%s\"", bp, utf8);
+ }
+ }
+ } else {
+ if (utf8 != 0) {
+ ascii = midna_domain_to_ascii(utf8);
+ msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"",
+ bp, utf8, ascii ? ascii : "(error)");
+ if (ascii != 0) {
+ if (strcmp(ascii, bp) != 0)
+ msg_warn("\"%s\" != \"%s\"", bp, ascii);
+ }
+ }
+ }
+ }
+ exit(0);
+}
+
+#endif /* TEST */
+
+#endif /* NO_EAI */
diff --git a/src/util/midna_domain.h b/src/util/midna_domain.h
new file mode 100644
index 0000000..1abe2a1
--- /dev/null
+++ b/src/util/midna_domain.h
@@ -0,0 +1,43 @@
+#ifndef _MIDNA_H_INCLUDED_
+#define _MIDNA_H_INCLUDED_
+
+/*++
+/* NAME
+/* midna_domain 3h
+/* SUMMARY
+/* ASCII/UTF-8 domain name conversion
+/* SYNOPSIS
+/* #include <midna_domain.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *midna_domain_to_ascii(const char *);
+extern const char *midna_domain_to_utf8(const char *);
+extern const char *midna_domain_suffix_to_ascii(const char *);
+extern const char *midna_domain_suffix_to_utf8(const char *);
+extern void midna_domain_pre_chroot(void);
+
+extern int midna_domain_cache_size;
+extern int midna_domain_transitional;
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/midna_domain_test.in b/src/util/midna_domain_test.in
new file mode 100644
index 0000000..55491e4
--- /dev/null
+++ b/src/util/midna_domain_test.in
@@ -0,0 +1,21 @@
+# Upper-case greek -> lower-case greek.
+Δημοσθένους.example.com
+# Upper-case ASCII -> lower-case ASCII.
+Hello.example.com
+# Invalid LDH label('-' at begin or end).
+bad-.example.com
+-bad.example.com
+# Invalid LDH (label > 63 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com
+# Valid LDH label (label <= 63 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com
+# Invalid name (length > 255 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com
+# Aliases for '.' -> '.'.
+x。example.com
+x.example.com
+x。example.com
+# Good a-label.
+xn--mumble.example.com
+# Bad a-label.
+xn--123456.example.com
diff --git a/src/util/midna_domain_test.ref b/src/util/midna_domain_test.ref
new file mode 100644
index 0000000..c1db0bd
--- /dev/null
+++ b/src/util/midna_domain_test.ref
@@ -0,0 +1,89 @@
+./midna_domain: > # Upper-case greek -> lower-case greek.
+./midna_domain: > Δημοσθένους.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "Δημοσθένους.example.com" ->utf8 "δημοσθένους.example.com"
+./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" ->utf8 "δημοσθένους.example.com"
+./midna_domain: warning: "Δημοσθένους.example.com" != "δημοσθένους.example.com"
+./midna_domain: > # Upper-case ASCII -> lower-case ASCII.
+./midna_domain: > Hello.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "Hello.example.com" ->utf8 "hello.example.com"
+./midna_domain: "Hello.example.com" ->ascii "hello.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com"
+./midna_domain: warning: "Hello.example.com" != "hello.example.com"
+./midna_domain: > # Invalid LDH label('-' at begin or end).
+./midna_domain: > bad-.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "bad-.example.com" to UTF8 form: UIDNA_ERROR_TRAILING_HYPHEN
+./midna_domain: "bad-.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "bad-.example.com" to ASCII form: UIDNA_ERROR_TRAILING_HYPHEN
+./midna_domain: "bad-.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > -bad.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "-bad.example.com" to UTF8 form: UIDNA_ERROR_LEADING_HYPHEN
+./midna_domain: "-bad.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "-bad.example.com" to ASCII form: UIDNA_ERROR_LEADING_HYPHEN
+./midna_domain: "-bad.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Invalid LDH (label > 63 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" to ASCII form: UIDNA_ERROR_LABEL_TOO_LONG
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->utf8 "(error)"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Valid LDH label (label <= 63 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: > # Invalid name (length > 255 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcde" to ASCII form: UIDNA_ERROR_DOMAIN_NAME_TOO_LONG
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->utf8 "(error)"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Aliases for '.' -> '.'.
+./midna_domain: > x。example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x。example.com" ->utf8 "x.example.com"
+./midna_domain: "x。example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x。example.com" != "x.example.com"
+./midna_domain: > x.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x.example.com" ->utf8 "x.example.com"
+./midna_domain: "x.example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x.example.com" != "x.example.com"
+./midna_domain: > x。example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x。example.com" ->utf8 "x.example.com"
+./midna_domain: "x。example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x。example.com" != "x.example.com"
+./midna_domain: > # Good a-label.
+./midna_domain: > xn--mumble.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com"
+./midna_domain: "xn--mumble.example.com" ->ascii "xn--mumble.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" ->ascii "xn--mumble.example.com"
+./midna_domain: > # Bad a-label.
+./midna_domain: > xn--123456.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "xn--123456.example.com" to UTF8 form: UIDNA_ERROR_PUNYCODE
+./midna_domain: "xn--123456.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "xn--123456.example.com" to ASCII form: UIDNA_ERROR_PUNYCODE
+./midna_domain: "xn--123456.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
diff --git a/src/util/miss_endif_cidr.map b/src/util/miss_endif_cidr.map
new file mode 100644
index 0000000..7c88208
--- /dev/null
+++ b/src/util/miss_endif_cidr.map
@@ -0,0 +1 @@
+if 1.2.3.4
diff --git a/src/util/miss_endif_cidr.ref b/src/util/miss_endif_cidr.ref
new file mode 100644
index 0000000..df5dc6c
--- /dev/null
+++ b/src/util/miss_endif_cidr.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: cidr map miss_endif_cidr.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/miss_endif_pcre.ref b/src/util/miss_endif_pcre.ref
new file mode 100644
index 0000000..21d7402
--- /dev/null
+++ b/src/util/miss_endif_pcre.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: pcre map miss_endif_re.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/miss_endif_re.map b/src/util/miss_endif_re.map
new file mode 100644
index 0000000..b085ecb
--- /dev/null
+++ b/src/util/miss_endif_re.map
@@ -0,0 +1 @@
+if /foo/
diff --git a/src/util/miss_endif_regexp.ref b/src/util/miss_endif_regexp.ref
new file mode 100644
index 0000000..f77f95a
--- /dev/null
+++ b/src/util/miss_endif_regexp.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: regexp map miss_endif_re.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/msg.c b/src/util/msg.c
new file mode 100644
index 0000000..70c6eab
--- /dev/null
+++ b/src/util/msg.c
@@ -0,0 +1,340 @@
+/*++
+/* NAME
+/* msg 3
+/* SUMMARY
+/* diagnostic interface
+/* SYNOPSIS
+/* #include <msg.h>
+/*
+/* int msg_verbose;
+/*
+/* void msg_info(format, ...)
+/* const char *format;
+/*
+/* void vmsg_info(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* void msg_warn(format, ...)
+/* const char *format;
+/*
+/* void vmsg_warn(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* void msg_error(format, ...)
+/* const char *format;
+/*
+/* void vmsg_error(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_fatal(format, ...)
+/* const char *format;
+/*
+/* NORETURN vmsg_fatal(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_fatal_status(status, format, ...)
+/* int status;
+/* const char *format;
+/*
+/* NORETURN vmsg_fatal_status(status, format, ap)
+/* int status;
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_panic(format, ...)
+/* const char *format;
+/*
+/* NORETURN vmsg_panic(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* MSG_CLEANUP_FN msg_cleanup(cleanup)
+/* void (*cleanup)(void);
+/* AUXILIARY FUNCTIONS
+/* int msg_error_limit(count)
+/* int count;
+/*
+/* void msg_error_clear()
+/* DESCRIPTION
+/* This module reports diagnostics. By default, diagnostics are sent
+/* to the standard error stream, but the disposition can be changed
+/* by the user. See the hints below in the SEE ALSO section.
+/*
+/* msg_info(), msg_warn(), msg_error(), msg_fatal*() and msg_panic()
+/* produce a one-line record with the program name, a severity code
+/* (except for msg_info()), and an informative message. The program
+/* name must have been set by calling one of the msg_XXX_init()
+/* functions (see the SEE ALSO section).
+/*
+/* msg_error() reports a recoverable error and increments the error
+/* counter. When the error count exceeds a pre-set limit (default: 13)
+/* the program terminates by calling msg_fatal().
+/*
+/* msg_fatal() reports an unrecoverable error and terminates the program
+/* with a non-zero exit status.
+/*
+/* msg_fatal_status() reports an unrecoverable error and terminates the
+/* program with the specified exit status.
+/*
+/* msg_panic() reports an internal inconsistency, terminates the
+/* program immediately (i.e. without calling the optional user-specified
+/* cleanup routine), and forces a core dump when possible.
+/*
+/* msg_cleanup() specifies a function that msg_fatal[_status]() should
+/* invoke before terminating the program, and returns the
+/* current function pointer. Specify a null argument to disable
+/* this feature.
+/*
+/* msg_error_limit() sets the error message count limit, and returns.
+/* the old limit.
+/*
+/* msg_error_clear() sets the error message count to zero.
+/*
+/* msg_verbose is a global flag that can be set to make software
+/* more verbose about what it is doing. By default the flag is zero.
+/* By convention, a larger value means more noise.
+/* REENTRANCY
+/* .ad
+/* .fi
+/* The msg_info() etc. output routines are protected against
+/* ordinary recursive calls and against re-entry by signal
+/* handlers.
+/*
+/* Protection against re-entry by signal handlers is subject
+/* to the following limitations:
+/* .IP \(bu
+/* The signal handlers must never return. In other words, the
+/* signal handlers must do one or more of the following: call
+/* _exit(), kill the process with a signal, and permanently block
+/* the process.
+/* .IP \(bu
+/* The signal handlers must invoke msg_info() etc. not until
+/* after the msg_XXX_init() functions complete initialization,
+/* and not until after the first formatted output to a VSTRING
+/* or VSTREAM.
+/* .IP \(bu
+/* Each msg_cleanup() call-back function, and each Postfix or
+/* system function invoked by that call-back function, either
+/* protects itself against recursive calls and re-entry by a
+/* terminating signal handler, or is called exclusively by the
+/* msg(3) module.
+/* .PP
+/* When re-entrancy is detected, the requested output and
+/* optional cleanup operations are skipped. Skipping the output
+/* operations prevents memory corruption of VSTREAM_ERR data
+/* structures, and prevents deadlock on Linux releases that
+/* use mutexes within system library routines such as syslog().
+/* This protection exists under the condition that these
+/* specific resources are accessed exclusively via the msg_info()
+/* etc. functions.
+/* SEE ALSO
+/* msg_output(3) specify diagnostics disposition
+/* msg_stdio(3) direct diagnostics to standard I/O stream
+/* msg_vstream(3) direct diagnostics to VSTREAM.
+/* msg_syslog(3) direct diagnostics to syslog daemon
+/* BUGS
+/* Some output functions may suffer from intentional or accidental
+/* record length restrictions that are imposed by library routines
+/* and/or by the runtime environment.
+/*
+/* Code that spawns a child process should almost always reset
+/* the cleanup handler. The exception is when the parent exits
+/* immediately and the child continues.
+/*
+/* msg_cleanup() may be unsafe in code that changes process
+/* privileges, because the call-back routine may run with the
+/* wrong privileges.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "msg_output.h"
+
+ /*
+ * Default is verbose logging off.
+ */
+int msg_verbose = 0;
+
+ /*
+ * Private state.
+ */
+static MSG_CLEANUP_FN msg_cleanup_fn = 0;
+static int msg_error_count = 0;
+static int msg_error_bound = 13;
+
+ /*
+ * The msg_exiting flag prevents us from recursively reporting an error with
+ * msg_fatal*() or msg_panic(), and provides a first-level safety net for
+ * optional cleanup actions against signal handler re-entry problems. Note
+ * that msg_vprintf() implements its own guard against re-entry.
+ *
+ * XXX We specify global scope, to discourage the compiler from doing smart
+ * things.
+ */
+volatile int msg_exiting = 0;
+
+/* msg_info - report informative message */
+
+void msg_info(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_info(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_info(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_INFO, fmt, ap);
+}
+
+/* msg_warn - report warning message */
+
+void msg_warn(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_warn(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_WARN, fmt, ap);
+}
+
+/* msg_error - report recoverable error */
+
+void msg_error(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_error(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_error(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_ERROR, fmt, ap);
+ if (++msg_error_count >= msg_error_bound)
+ msg_fatal("too many errors - program terminated");
+}
+
+/* msg_fatal - report error and terminate gracefully */
+
+NORETURN msg_fatal(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_fatal(fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_fatal(const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_FATAL, fmt, ap);
+ if (msg_cleanup_fn)
+ msg_cleanup_fn();
+ }
+ sleep(1);
+ /* In case we're running as a signal handler. */
+ _exit(1);
+}
+
+/* msg_fatal_status - report error and terminate gracefully */
+
+NORETURN msg_fatal_status(int status, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_fatal_status(status, fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_FATAL, fmt, ap);
+ if (msg_cleanup_fn)
+ msg_cleanup_fn();
+ }
+ sleep(1);
+ /* In case we're running as a signal handler. */
+ _exit(status);
+}
+
+/* msg_panic - report error and dump core */
+
+NORETURN msg_panic(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_panic(fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_panic(const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_PANIC, fmt, ap);
+ }
+ sleep(1);
+ abort(); /* Die! */
+ /* In case we're running as a signal handler. */
+ _exit(1); /* DIE!! */
+}
+
+/* msg_cleanup - specify cleanup routine */
+
+MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn)
+{
+ MSG_CLEANUP_FN old_fn = msg_cleanup_fn;
+
+ msg_cleanup_fn = cleanup_fn;
+ return (old_fn);
+}
+
+/* msg_error_limit - set error message counter limit */
+
+int msg_error_limit(int limit)
+{
+ int old = msg_error_bound;
+
+ msg_error_bound = limit;
+ return (old);
+}
+
+/* msg_error_clear - reset error message counter */
+
+void msg_error_clear(void)
+{
+ msg_error_count = 0;
+}
diff --git a/src/util/msg.h b/src/util/msg.h
new file mode 100644
index 0000000..6c75baf
--- /dev/null
+++ b/src/util/msg.h
@@ -0,0 +1,60 @@
+#ifndef _MSG_H_INCLUDED_
+#define _MSG_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg 3h
+/* SUMMARY
+/* diagnostics interface
+/* SYNOPSIS
+/* #include "msg.h"
+/* DESCRIPTION
+/* .nf
+
+/*
+ * System library.
+ */
+#include <stdarg.h>
+#include <time.h>
+
+/*
+ * External interface.
+ */
+typedef void (*MSG_CLEANUP_FN) (void);
+
+extern int msg_verbose;
+
+extern void PRINTFLIKE(1, 2) msg_info(const char *,...);
+extern void PRINTFLIKE(1, 2) msg_warn(const char *,...);
+extern void PRINTFLIKE(1, 2) msg_error(const char *,...);
+extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...);
+extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...);
+extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...);
+
+extern void vmsg_info(const char *, va_list);
+extern void vmsg_warn(const char *, va_list);
+extern void vmsg_error(const char *, va_list);
+extern NORETURN vmsg_fatal(const char *, va_list);
+extern NORETURN vmsg_fatal_status(int, const char *, va_list);
+extern NORETURN vmsg_panic(const char *, va_list);
+
+extern int msg_error_limit(int);
+extern void msg_error_clear(void);
+extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN);
+
+extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int,
+ void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...),
+ const char *,...);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/msg_logger.c b/src/util/msg_logger.c
new file mode 100644
index 0000000..07c9e92
--- /dev/null
+++ b/src/util/msg_logger.c
@@ -0,0 +1,371 @@
+/*++
+/* NAME
+/* msg_logger 3
+/* SUMMARY
+/* direct diagnostics to logger service
+/* SYNOPSIS
+/* #include <msg_logger.h>
+/*
+/* void msg_logger_init(
+/* const char *progname,
+/* const char *hostname,
+/* const char *unix_path,
+/* void (*fallback)(const char *))
+/*
+/* void msg_logger_control(
+/* int key,...)
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* through a logger daemon, with an optional fallback mechanism.
+/* The log record format is like traditional syslog:
+/*
+/* .nf
+/* Mmm dd host progname[pid]: text...
+/* .fi
+/*
+/* msg_logger_init() arranges that subsequent msg(3) calls
+/* will write to an internal logging service. This function
+/* may also be used to update msg_logger settings.
+/*
+/* Arguments:
+/* .IP progname
+/* The program name that is prepended to a log record.
+/* .IP hostname
+/* The host name that is prepended to a log record. Only the
+/* first hostname label will be used.
+/* .IP unix_path
+/* Pathname of a unix-domain datagram service endpoint. A
+/* typical use case is the pathname of the postlog socket.
+/* .IP fallback
+/* Null pointer, or pointer to function that will be called
+/* with a formatted message when the logger service is not
+/* (yet) available. A typical use case is to pass the record
+/* to the logwriter(3) module.
+/* .PP
+/* msg_logger_control() makes adjustments to the msg_logger
+/* client. These adjustments remain in effect until the next
+/* msg_logger_init() or msg_logger_control() call. The arguments
+/* are a list of macros with zero or more arguments, terminated
+/* with CA_MSG_LOGGER_CTL_END which has none. The following
+/* lists the names and the types of the corresponding value
+/* arguments.
+/*
+/* Arguments:
+/* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY
+/* Disable the logging socket, and use the fallback function
+/* only. This remains in effect until the next msg_logger_init()
+/* call.
+/* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *))
+/* Override the fallback setting (see above) with the specified
+/* function pointer. This remains in effect until the next
+/* msg_logger_init() or msg_logger_control() call.
+/* .IP CA_MSG_LOGGER_CTL_DISABLE
+/* Disable the msg_logger. This remains in effect until the
+/* next msg_logger_init() call.
+/* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW
+/* Close the logging socket if it was already open, and open
+/* the logging socket now, if permitted by current settings.
+/* Otherwise, the open is delayed until a logging request.
+/* SEE ALSO
+/* msg(3) diagnostics module
+/* BUGS
+/* Output records are truncated to ~2000 characters, because
+/* unlimited logging is a liability.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System libraries.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+ /*
+ * Application-specific.
+ */
+#include <connect.h>
+#include <logwriter.h>
+#include <msg.h>
+#include <msg_logger.h>
+#include <msg_output.h>
+#include <mymalloc.h>
+#include <safe.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Saved state from msg_logger_init().
+ */
+static char *msg_logger_progname;
+static char *msg_logger_hostname;
+static char *msg_logger_unix_path;
+static void (*msg_logger_fallback_fn) (const char *);
+static int msg_logger_fallback_only_override = 0;
+static int msg_logger_enable = 0;
+
+#define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0)
+
+ /*
+ * Other state.
+ */
+#define MSG_LOGGER_SOCK_NONE (-1)
+
+static VSTRING *msg_logger_buf;
+static int msg_logger_sock = MSG_LOGGER_SOCK_NONE;
+
+ /*
+ * Safety limit.
+ */
+#define MSG_LOGGER_RECLEN 2000
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* msg_logger_connect - connect to logger service */
+
+static void msg_logger_connect(void)
+{
+ if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) {
+ msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING);
+ if (msg_logger_sock >= 0)
+ close_on_exec(msg_logger_sock, CLOSE_ON_EXEC);
+ }
+}
+
+/* msg_logger_disconnect - disconnect from logger service */
+
+static void msg_logger_disconnect(void)
+{
+ if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
+ (void) close(msg_logger_sock);
+ msg_logger_sock = MSG_LOGGER_SOCK_NONE;
+ }
+}
+
+/* msg_logger_print - log info to service or file */
+
+static void msg_logger_print(int level, const char *text)
+{
+ time_t now;
+ struct tm *lt;
+ ssize_t len;
+
+ /*
+ * TODO: this should be a reusable NAME_CODE table plus lookup function.
+ */
+ static int log_level[] = {
+ MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC,
+ };
+ static char *severity_name[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ /*
+ * This test is simple enough that we don't bother with unregistering the
+ * msg_logger_print() function.
+ */
+ if (msg_logger_enable == 0)
+ return;
+
+ /*
+ * Note: there is code in postlogd(8) that attempts to strip off
+ * information that is prepended here. If the formatting below is
+ * changed, then postlogd needs to be updated as well.
+ */
+
+ /*
+ * Format the time stamp.
+ */
+ if (time(&now) < 0)
+ msg_fatal("no time: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(msg_logger_buf);
+ if ((len = strftime(vstring_str(msg_logger_buf),
+ vstring_avail(msg_logger_buf),
+ "%b %d %H:%M:%S ", lt)) == 0)
+ msg_fatal("strftime: %m");
+ vstring_set_payload_size(msg_logger_buf, len);
+
+ /*
+ * Format the host name (first name label only).
+ */
+ vstring_sprintf_append(msg_logger_buf, "%.*s ",
+ (int) strcspn(msg_logger_hostname, "."),
+ msg_logger_hostname);
+
+ /*
+ * Format the message.
+ */
+ if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0])))
+ msg_panic("msg_logger_print: invalid severity level: %d", level);
+
+ if (level == MSG_INFO) {
+ vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s",
+ msg_logger_progname, (long) getpid(),
+ (int) MSG_LOGGER_RECLEN, text);
+ } else {
+ vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s",
+ msg_logger_progname, (long) getpid(),
+ severity_name[level], (int) MSG_LOGGER_RECLEN, text);
+ }
+
+ /*
+ * Connect to logging service, or fall back to direct log. Many systems
+ * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no
+ * server has opened the endpoint.
+ */
+ if (MSG_LOGGER_NEED_SOCKET())
+ msg_logger_connect();
+ if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
+ send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0);
+ } else if (msg_logger_fallback_fn) {
+ msg_logger_fallback_fn(STR(msg_logger_buf));
+ }
+}
+
+/* msg_logger_init - initialize */
+
+void msg_logger_init(const char *progname, const char *hostname,
+ const char *unix_path, void (*fallback) (const char *))
+{
+ static int first_call = 1;
+ extern char **environ;
+
+ /*
+ * XXX If this program is set-gid, then TZ must not be trusted. This
+ * scrubbing code is in the wrong place.
+ */
+ if (first_call) {
+ if (unsafe())
+ while (getenv("TZ")) /* There may be multiple. */
+ if (unsetenv("TZ") < 0) { /* Desperate measures. */
+ environ[0] = 0;
+ msg_fatal("unsetenv: %m");
+ }
+ tzset();
+ }
+
+ /*
+ * Save the request info. Use free-after-update because this data will be
+ * accessed when mystrdup() runs out of memory.
+ */
+#define UPDATE_AND_FREE(dst, src) do { \
+ if ((dst) == 0 || strcmp((dst), (src)) != 0) { \
+ char *_bak = (dst); \
+ (dst) = mystrdup(src); \
+ if ((_bak)) myfree(_bak); \
+ } \
+ } while (0)
+
+ UPDATE_AND_FREE(msg_logger_progname, progname);
+ UPDATE_AND_FREE(msg_logger_hostname, hostname);
+ UPDATE_AND_FREE(msg_logger_unix_path, unix_path);
+ msg_logger_fallback_fn = fallback;
+
+ /*
+ * One-time activity: register the output handler, and allocate a buffer.
+ */
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_logger_print);
+ msg_logger_buf = vstring_alloc(2048);
+ }
+
+ /*
+ * Always.
+ */
+ msg_logger_enable = 1;
+ msg_logger_fallback_only_override = 0;
+}
+
+/* msg_logger_control - tweak the client */
+
+void msg_logger_control(int name,...)
+{
+ const char *myname = "msg_logger_control";
+ va_list ap;
+
+ /*
+ * Overrides remain in effect until the next msg_logger_init() or
+ * msg_logger_control() call,
+ */
+ for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case MSG_LOGGER_CTL_FALLBACK_ONLY:
+ msg_logger_fallback_only_override = 1;
+ msg_logger_disconnect();
+ break;
+ case MSG_LOGGER_CTL_FALLBACK_FN:
+ msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN);
+ break;
+ case MSG_LOGGER_CTL_DISABLE:
+ msg_logger_enable = 0;
+ break;
+ case MSG_LOGGER_CTL_CONNECT_NOW:
+ msg_logger_disconnect();
+ if (MSG_LOGGER_NEED_SOCKET())
+ msg_logger_connect();
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program to test the msg_logger module.
+ *
+ * Usage: msg_logger hostname unix_path fallback_path text...
+ */
+static char *fallback_path;
+
+static void fallback(const char *msg)
+{
+ if (logwriter_one_shot(fallback_path, msg) != 0)
+ msg_fatal("unable to fall back to directly write %s: %m",
+ fallback_path);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(256);
+
+ if (argc < 4)
+ msg_fatal("usage: %s host port path text to log", argv[0]);
+ msg_logger_init(argv[0], argv[1], argv[2], fallback);
+ fallback_path = argv[3];
+ argc -= 3;
+ argv += 3;
+ while (--argc && *++argv) {
+ vstring_strcat(vp, *argv);
+ if (argv[1])
+ vstring_strcat(vp, " ");
+ }
+ msg_warn("static text");
+ msg_warn("dynamic text: >%s<", vstring_str(vp));
+ msg_warn("dynamic numeric: >%d<", 42);
+ msg_warn("error text: >%m<");
+ msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_logger.h b/src/util/msg_logger.h
new file mode 100644
index 0000000..4179f8b
--- /dev/null
+++ b/src/util/msg_logger.h
@@ -0,0 +1,62 @@
+#ifndef _MSG_LOGGER_H_INCLUDED_
+#define _MSG_LOGGER_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_logger 3h
+/* SUMMARY
+/* direct diagnostics to logger service
+/* SYNOPSIS
+/* #include <msg_logger.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+typedef void (*MSG_LOGGER_FALLBACK_FN) (const char *);
+
+extern void msg_logger_init(const char *, const char *, const char *,
+ MSG_LOGGER_FALLBACK_FN);
+extern void msg_logger_control(int,...);
+
+/* Internal-only API: type-unchecked arguments. */
+#define MSG_LOGGER_CTL_END 0
+#define MSG_LOGGER_CTL_FALLBACK_ONLY 1
+#define MSG_LOGGER_CTL_FALLBACK_FN 2
+#define MSG_LOGGER_CTL_DISABLE 3
+#define MSG_LOGGER_CTL_CONNECT_NOW 4
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_MSG_LOGGER_CTL_END MSG_LOGGER_CTL_END
+#define CA_MSG_LOGGER_CTL_FALLBACK_ONLY MSG_LOGGER_CTL_FALLBACK_ONLY
+#define CA_MSG_LOGGER_CTL_FALLBACK_FN(v) \
+ MSG_LOGGER_CTL_FALLBACK_FN, CHECK_VAL(MSG_LOGGER_CTL, \
+ MSG_LOGGER_FALLBACK_FN, (v))
+#define CA_MSG_LOGGER_CTL_DISABLE MSG_LOGGER_CTL_DISABLE
+#define CA_MSG_LOGGER_CTL_CONNECT_NOW MSG_LOGGER_CTL_CONNECT_NOW
+
+CHECK_VAL_HELPER_DCL(MSG_LOGGER_CTL, MSG_LOGGER_FALLBACK_FN);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/msg_output.c b/src/util/msg_output.c
new file mode 100644
index 0000000..6663877
--- /dev/null
+++ b/src/util/msg_output.c
@@ -0,0 +1,174 @@
+/*++
+/* NAME
+/* msg_output 3
+/* SUMMARY
+/* diagnostics output management
+/* SYNOPSIS
+/* #include <msg_output.h>
+/*
+/* typedef void (*MSG_OUTPUT_FN)(int level, char *text)
+/*
+/* void msg_output(output_fn)
+/* MSG_OUTPUT_FN output_fn;
+/*
+/* void msg_printf(level, format, ...)
+/* int level;
+/* const char *format;
+/*
+/* void msg_vprintf(level, format, ap)
+/* int level;
+/* const char *format;
+/* va_list ap;
+/* DESCRIPTION
+/* This module implements low-level output management for the
+/* msg(3) diagnostics interface.
+/*
+/* msg_output() registers an output handler for the diagnostics
+/* interface. An application can register multiple output handlers.
+/* Output handlers are called in the specified order.
+/* An output handler takes as arguments a severity level (MSG_INFO,
+/* MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing
+/* integer values ranging from 0 to MSG_LAST) and pre-formatted,
+/* sanitized, text in the form of a null-terminated string.
+/*
+/* msg_printf() and msg_vprintf() format their arguments, sanitize the
+/* result, and call the output handlers registered with msg_output().
+/*
+/* msg_text() copies a pre-formatted text, sanitizes the result, and
+/* calls the output handlers registered with msg_output().
+/* REENTRANCY
+/* .ad
+/* .fi
+/* The above output routines are protected against ordinary
+/* recursive calls and against re-entry by signal
+/* handlers, with the following limitations:
+/* .IP \(bu
+/* The signal handlers must never return. In other words, the
+/* signal handlers must do one or more of the following: call
+/* _exit(), kill the process with a signal, and permanently
+/* block the process.
+/* .IP \(bu
+/* The signal handlers must call the above output routines not
+/* until after msg_output() completes initialization, and not
+/* until after the first formatted output to a VSTRING or
+/* VSTREAM.
+/* .IP \(bu
+/* Each msg_output() call-back function, and each Postfix or
+/* system function called by that call-back function, either
+/* must protect itself against recursive calls and re-entry
+/* by a terminating signal handler, or it must be called
+/* exclusively by functions in the msg_output(3) module.
+/* .PP
+/* When re-entrancy is detected, the requested output operation
+/* is skipped. This prevents memory corruption of VSTREAM_ERR
+/* data structures, and prevents deadlock on Linux releases
+/* that use mutexes within system library routines such as
+/* syslog(). This protection exists under the condition that
+/* these specific resources are accessed exclusively via
+/* msg_output() call-back functions.
+/* 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 <stdarg.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <msg_output.h>
+
+ /*
+ * Global scope, to discourage the compiler from doing smart things.
+ */
+volatile int msg_vprintf_level;
+
+ /*
+ * Private state. Allow one nested call, so that one logging error can be
+ * reported to stderr before bailing out.
+ */
+#define MSG_OUT_NESTING_LIMIT 2
+static MSG_OUTPUT_FN *msg_output_fn = 0;
+static int msg_output_fn_count = 0;
+static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT];
+
+/* msg_output - specify output handler */
+
+void msg_output(MSG_OUTPUT_FN output_fn)
+{
+ int i;
+
+ /*
+ * Allocate all resources during initialization. This may result in a
+ * recursive call due to memory allocation error.
+ */
+ if (msg_buffers[MSG_OUT_NESTING_LIMIT - 1] == 0) {
+ for (i = 0; i < MSG_OUT_NESTING_LIMIT; i++)
+ msg_buffers[i] = vstring_alloc(100);
+ }
+
+ /*
+ * We're not doing this often, so avoid complexity and allocate memory
+ * for an exact fit.
+ */
+ if (msg_output_fn_count == 0)
+ msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn));
+ else
+ msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn,
+ (msg_output_fn_count + 1) * sizeof(*msg_output_fn));
+ msg_output_fn[msg_output_fn_count++] = output_fn;
+}
+
+/* msg_printf - format text and log it */
+
+void msg_printf(int level, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ msg_vprintf(level, format, ap);
+ va_end(ap);
+}
+
+/* msg_vprintf - format text and log it */
+
+void msg_vprintf(int level, const char *format, va_list ap)
+{
+ int saved_errno = errno;
+ VSTRING *vp;
+ int i;
+
+ if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) {
+ msg_vprintf_level += 1;
+ /* On-the-fly initialization for test programs and startup errors. */
+ if (msg_output_fn_count == 0)
+ msg_vstream_init("unknown", VSTREAM_ERR);
+ vp = msg_buffers[msg_vprintf_level - 1];
+ /* OK if terminating signal handler hijacks control before next stmt. */
+ vstring_vsprintf(vp, format, ap);
+ printable(vstring_str(vp), '?');
+ for (i = 0; i < msg_output_fn_count; i++)
+ msg_output_fn[i] (level, vstring_str(vp));
+ msg_vprintf_level -= 1;
+ }
+ errno = saved_errno;
+}
diff --git a/src/util/msg_output.h b/src/util/msg_output.h
new file mode 100644
index 0000000..bd84276
--- /dev/null
+++ b/src/util/msg_output.h
@@ -0,0 +1,51 @@
+#ifndef _MSG_OUTPUT_FN_
+#define _MSG_OUTPUT_FN_
+
+/*++
+/* NAME
+/* msg_output 3h
+/* SUMMARY
+/* diagnostics output management
+/* SYNOPSIS
+/* #include <msg_output.h>
+/* DESCRIPTION
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * External interface. Severity levels are documented to be monotonically
+ * increasing from 0 up to MSG_LAST.
+ */
+typedef void (*MSG_OUTPUT_FN) (int, const char *);
+extern void msg_output(MSG_OUTPUT_FN);
+extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...);
+extern void msg_vprintf(int, const char *, va_list);
+
+#define MSG_INFO 0 /* informative */
+#define MSG_WARN 1 /* warning (non-fatal) */
+#define MSG_ERROR 2 /* error (fatal) */
+#define MSG_FATAL 3 /* software error (fatal) */
+#define MSG_PANIC 4 /* software error (fatal) */
+
+#define MSG_LAST 4 /* highest-numbered severity level */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/msg_rate_delay.c b/src/util/msg_rate_delay.c
new file mode 100644
index 0000000..e21b021
--- /dev/null
+++ b/src/util/msg_rate_delay.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* msg_rate_delay 3
+/* SUMMARY
+/* diagnostic interface
+/* SYNOPSIS
+/* #include <msg.h>
+/*
+/* void msg_rate_delay(stamp, delay, log_fn, fmt, ...)
+/* time_t *stamp;
+/* int delay;
+/* void (*log_fn)(const char *fmt, ...);
+/* const char *fmt;
+/* DESCRIPTION
+/* msg_rate_delay() produces log output at a reduced rate: no
+/* more than one message per 'delay' seconds. It discards log
+/* output that would violate the output rate policy.
+/*
+/* This is typically used to log errors accessing a cache with
+/* high-frequency access but low-value information, to avoid
+/* spamming the logfile with the same kind of message.
+/*
+/* Arguments:
+/* .IP stamp
+/* Time stamp of last log output; specify a zero time stamp
+/* on the first call. This is an input-output parameter.
+/* This parameter is ignored when verbose logging is enabled
+/* or when the delay value is zero.
+/* .IP delay
+/* The minimum time between log outputs; specify zero to log
+/* all output for debugging purposes. This parameter is ignored
+/* when verbose logging is enabled.
+/* .IP log_fn
+/* The function that produces log output. Typically, this will
+/* be msg_info() or msg_warn().
+/* .IP fmt
+/* Format string as used with msg(3) routines.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <events.h>
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+
+/* msg_rate_delay - rate-limit message logging */
+
+void msg_rate_delay(time_t *stamp, int delay,
+ void (*log_fn) (const char *,...),
+ const char *fmt,...)
+{
+ const char *myname = "msg_rate_delay";
+ static time_t saved_event_time;
+ time_t now;
+ VSTRING *buf;
+ va_list ap;
+
+ /*
+ * Sanity check.
+ */
+ if (delay < 0)
+ msg_panic("%s: bad message rate delay: %d", myname, delay);
+
+ /*
+ * This function may be called frequently. Avoid an unnecessary syscall
+ * if possible. Deal with the possibility that a program does not use the
+ * events(3) engine, so that event_time() always produces the same
+ * result.
+ */
+ if (msg_verbose == 0 && delay > 0) {
+ if (saved_event_time == 0)
+ now = saved_event_time = event_time();
+ else if ((now = event_time()) == saved_event_time)
+ now = time((time_t *) 0);
+
+ /*
+ * Don't log if time is too early.
+ */
+ if (*stamp + delay > now)
+ return;
+ *stamp = now;
+ }
+
+ /*
+ * OK to log. This is a low-rate event, so we can afford some overhead.
+ */
+ buf = vstring_alloc(100);
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ log_fn("%s", STR(buf));
+ vstring_free(buf);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: log messages but skip messages during a
+ * two-second gap.
+ */
+#include <unistd.h>
+
+int main(int argc, char **argv)
+{
+ int n;
+ time_t stamp = 0;
+
+ for (n = 0; n < 6; n++) {
+ msg_rate_delay(&stamp, 2, msg_info, "text here %d", n);
+ sleep(1);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_syslog.c b/src/util/msg_syslog.c
new file mode 100644
index 0000000..7c979c6
--- /dev/null
+++ b/src/util/msg_syslog.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* msg_syslog 3
+/* SUMMARY
+/* direct diagnostics to syslog daemon
+/* SYNOPSIS
+/* #include <msg_syslog.h>
+/*
+/* void msg_syslog_init(progname, log_opt, facility)
+/* const char *progname;
+/* int log_opt;
+/* int facility;
+/*
+/* int msg_syslog_set_facility(facility_name)
+/* const char *facility_name;
+/*
+/* void msg_syslog_disable(void)
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* via the syslog daemon.
+/*
+/* msg_syslog_init() is a wrapper around the openlog(3) routine
+/* that directs subsequent msg(3) output to the syslog daemon.
+/* This function may also be called to update msg_syslog
+/* settings. If the program name appears to contain a process ID
+/* then msg_syslog_init will attempt to suppress its own PID.
+/*
+/* msg_syslog_set_facility() is a helper routine that overrides the
+/* logging facility that is specified with msg_syslog_init().
+/* The result is zero in case of an unknown facility name.
+/*
+/* msg_syslog_disable() turns off the msg_syslog client,
+/* until a subsequent msg_syslog_init() call.
+/* SEE ALSO
+/* syslog(3) syslog library
+/* msg(3) diagnostics module
+/* BUGS
+/* Output records are truncated to 2000 characters. This is done in
+/* order to defend against a buffer overflow problem in some
+/* implementations of the syslog() library routine.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <errno.h>
+#include <syslog.h>
+#include <string.h>
+#include <time.h>
+
+/* Application-specific. */
+
+#include "vstring.h"
+#include "stringops.h"
+#include "msg.h"
+#include "msg_output.h"
+#include "msg_syslog.h"
+#include "safe.h"
+#include <mymalloc.h>
+
+ /*
+ * Stay a little below the 2048-byte limit of older syslog()
+ * implementations.
+ */
+#define MSG_SYSLOG_RECLEN 2000
+
+struct facility_list {
+ const char *name;
+ int facility;
+};
+
+static struct facility_list facility_list[] = {
+#ifdef LOG_AUTH
+ "auth", LOG_AUTH,
+#endif
+#ifdef LOG_AUTHPRIV
+ "authpriv", LOG_AUTHPRIV,
+#endif
+#ifdef LOG_CRON
+ "cron", LOG_CRON,
+#endif
+#ifdef LOG_DAEMON
+ "daemon", LOG_DAEMON,
+#endif
+#ifdef LOG_FTP
+ "ftp", LOG_FTP,
+#endif
+#ifdef LOG_KERN
+ "kern", LOG_KERN,
+#endif
+#ifdef LOG_LPR
+ "lpr", LOG_LPR,
+#endif
+#ifdef LOG_MAIL
+ "mail", LOG_MAIL,
+#endif
+#ifdef LOG_NEWS
+ "news", LOG_NEWS,
+#endif
+#ifdef LOG_SECURITY
+ "security", LOG_SECURITY,
+#endif
+#ifdef LOG_SYSLOG
+ "syslog", LOG_SYSLOG,
+#endif
+#ifdef LOG_USER
+ "user", LOG_USER,
+#endif
+#ifdef LOG_UUCP
+ "uucp", LOG_UUCP,
+#endif
+#ifdef LOG_LOCAL0
+ "local0", LOG_LOCAL0,
+#endif
+#ifdef LOG_LOCAL1
+ "local1", LOG_LOCAL1,
+#endif
+#ifdef LOG_LOCAL2
+ "local2", LOG_LOCAL2,
+#endif
+#ifdef LOG_LOCAL3
+ "local3", LOG_LOCAL3,
+#endif
+#ifdef LOG_LOCAL4
+ "local4", LOG_LOCAL4,
+#endif
+#ifdef LOG_LOCAL5
+ "local5", LOG_LOCAL5,
+#endif
+#ifdef LOG_LOCAL6
+ "local6", LOG_LOCAL6,
+#endif
+#ifdef LOG_LOCAL7
+ "local7", LOG_LOCAL7,
+#endif
+ 0,
+};
+
+static int msg_syslog_facility;
+static int msg_syslog_enable;
+
+/* msg_syslog_print - log info to syslog daemon */
+
+static void msg_syslog_print(int level, const char *text)
+{
+ static int log_level[] = {
+ LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT,
+ };
+ static char *severity_name[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ if (msg_syslog_enable == 0)
+ return;
+
+ if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0])))
+ msg_panic("msg_syslog_print: invalid severity level: %d", level);
+
+ if (level == MSG_INFO) {
+ syslog(msg_syslog_facility | log_level[level], "%.*s",
+ (int) MSG_SYSLOG_RECLEN, text);
+ } else {
+ syslog(msg_syslog_facility | log_level[level], "%s: %.*s",
+ severity_name[level], (int) MSG_SYSLOG_RECLEN, text);
+ }
+}
+
+/* msg_syslog_init - initialize */
+
+void msg_syslog_init(const char *name, int logopt, int facility)
+{
+ static int first_call = 1;
+ extern char **environ;
+
+ /*
+ * XXX If this program is set-gid, then TZ must not be trusted. This
+ * scrubbing code is in the wrong place.
+ */
+ if (first_call) {
+ if (unsafe())
+ while (getenv("TZ")) /* There may be multiple. */
+ if (unsetenv("TZ") < 0) { /* Desperate measures. */
+ environ[0] = 0;
+ msg_fatal("unsetenv: %m");
+ }
+ tzset();
+ }
+ /* Hack for internal logging forwarding after config change. */
+ if (strchr(name, '[') != 0)
+ logopt &= ~LOG_PID;
+ openlog(name, LOG_NDELAY | logopt, facility);
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_syslog_print);
+ }
+ msg_syslog_enable = 1;
+}
+
+/* msg_syslog_set_facility - set logging facility by name */
+
+int msg_syslog_set_facility(const char *facility_name)
+{
+ struct facility_list *fnp;
+
+ for (fnp = facility_list; fnp->name; ++fnp) {
+ if (!strcmp(fnp->name, facility_name)) {
+ msg_syslog_facility = fnp->facility;
+ return (1);
+ }
+ }
+ return 0;
+}
+
+/* msg_syslog_disable - disable the msg_syslog client */
+
+void msg_syslog_disable(void)
+{
+ msg_syslog_enable = 0;
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program to test the syslogging diagnostics interface
+ *
+ * Usage: msg_syslog_test text...
+ */
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(256);
+
+ msg_syslog_init(argv[0], LOG_PID, LOG_MAIL);
+ if (argc < 2)
+ msg_error("usage: %s text to be logged", argv[0]);
+ while (--argc && *++argv) {
+ vstring_strcat(vp, *argv);
+ if (argv[1])
+ vstring_strcat(vp, " ");
+ }
+ msg_warn("static text");
+ msg_warn("dynamic text: >%s<", vstring_str(vp));
+ msg_warn("dynamic numeric: >%d<", 42);
+ msg_warn("error text: >%m<");
+ msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_syslog.h b/src/util/msg_syslog.h
new file mode 100644
index 0000000..d0441bb
--- /dev/null
+++ b/src/util/msg_syslog.h
@@ -0,0 +1,41 @@
+#ifndef _MSG_SYSLOG_H_INCLUDED_
+#define _MSG_SYSLOG_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_syslog 3h
+/* SUMMARY
+/* direct diagnostics to syslog daemon
+/* SYNOPSIS
+/* #include <msg_syslog.h>
+/* DESCRIPTION
+
+ /*
+ * System library.
+ */
+#include <syslog.h>
+
+ /*
+ * External interface.
+ */
+extern void msg_syslog_init(const char *, int, int);
+extern int msg_syslog_set_facility(const char *);
+extern void msg_syslog_disable(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/msg_vstream.c b/src/util/msg_vstream.c
new file mode 100644
index 0000000..b6e24e6
--- /dev/null
+++ b/src/util/msg_vstream.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* msg_vstream 3
+/* SUMMARY
+/* report diagnostics to VSTREAM
+/* SYNOPSIS
+/* #include <msg_vstream.h>
+/*
+/* void msg_vstream_init(progname, stream)
+/* const char *progname;
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* to a VSTREAM.
+/*
+/* msg_vstream_init() sets the program name that appears in each output
+/* record, and directs diagnostics (see msg(3)) to the specified
+/* VSTREAM. The \fIprogname\fR argument is not copied.
+/* SEE ALSO
+/* msg(3)
+/* BUGS
+/* No guarantee that long records are written atomically.
+/* Only the last msg_vstream_init() call takes effect.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include "vstream.h"
+#include "msg.h"
+#include "msg_output.h"
+#include "msg_vstream.h"
+
+ /*
+ * Private state.
+ */
+static const char *msg_tag;
+static VSTREAM *msg_stream;
+
+/* msg_vstream_print - log diagnostic to VSTREAM */
+
+static void msg_vstream_print(int level, const char *text)
+{
+ static const char *level_text[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0])))
+ msg_panic("invalid severity level: %d", level);
+ if (level == MSG_INFO) {
+ vstream_fprintf(msg_stream, "%s: %s\n",
+ msg_tag, text);
+ } else {
+ vstream_fprintf(msg_stream, "%s: %s: %s\n",
+ msg_tag, level_text[level], text);
+ }
+ vstream_fflush(msg_stream);
+}
+
+/* msg_vstream_init - initialize */
+
+void msg_vstream_init(const char *name, VSTREAM *vp)
+{
+ static int first_call = 1;
+
+ msg_tag = name;
+ msg_stream = vp;
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_vstream_print);
+ }
+}
diff --git a/src/util/msg_vstream.h b/src/util/msg_vstream.h
new file mode 100644
index 0000000..d0679a0
--- /dev/null
+++ b/src/util/msg_vstream.h
@@ -0,0 +1,34 @@
+#ifndef _MSG_VSTREAM_H_INCLUDED_
+#define _MSG_VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_vstream 3h
+/* SUMMARY
+/* direct diagnostics to VSTREAM
+/* SYNOPSIS
+/* #include <msg_vstream.h>
+/* DESCRIPTION
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern void msg_vstream_init(const char *, VSTREAM *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mvect.c b/src/util/mvect.c
new file mode 100644
index 0000000..cf4b0d5
--- /dev/null
+++ b/src/util/mvect.c
@@ -0,0 +1,117 @@
+/*++
+/* NAME
+/* mvect 3
+/* SUMMARY
+/* memory vector management
+/* SYNOPSIS
+/* #include <mvect.h>
+/*
+/* char *mvect_alloc(vector, elsize, nelm, init_fn, wipe_fn)
+/* MVECT *vector;
+/* ssize_t elsize;
+/* ssize_t nelm;
+/* void (*init_fn)(char *ptr, ssize_t count);
+/* void (*wipe_fn)(char *ptr, ssize_t count);
+/*
+/* char *mvect_realloc(vector, nelm)
+/* MVECT *vector;
+/* ssize_t nelm;
+/*
+/* char *mvect_free(vector)
+/* MVECT *vector;
+/* DESCRIPTION
+/* This module supports memory management for arrays of arbitrary
+/* objects. It is up to the application to provide specific code
+/* that initializes and uses object memory.
+/*
+/* mvect_alloc() initializes memory for a vector with elements
+/* of \fIelsize\fR bytes, and with at least \fInelm\fR elements.
+/* \fIinit_fn\fR is a null pointer, or a pointer to a function
+/* that initializes \fIcount\fR vector elements.
+/* \fIwipe_fn\fR is a null pointer, or a pointer to a function
+/* that is complementary to \fIinit_fn\fR. This routine is called
+/* by mvect_free(). The result of mvect_alloc() is a pointer to
+/* the allocated vector.
+/*
+/* mvect_realloc() guarantees that the specified vector has space
+/* for at least \fInelm\fR elements. The result is a pointer to the
+/* allocated vector, which may change across calls.
+/*
+/* mvect_free() releases storage for the named vector. The result
+/* is a convenient null pointer.
+/* SEE ALSO
+/* mymalloc(3) memory management
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* the requested amount of memory is not available; improper use
+/* is detected; other fatal errors.
+/* 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 "mvect.h"
+
+/* mvect_alloc - allocate memory vector */
+
+char *mvect_alloc(MVECT *vect, ssize_t elsize, ssize_t nelm,
+ void (*init_fn) (char *, ssize_t), void (*wipe_fn) (char *, ssize_t))
+{
+ vect->init_fn = init_fn;
+ vect->wipe_fn = wipe_fn;
+ vect->nelm = 0;
+ vect->ptr = mymalloc(elsize * nelm);
+ vect->nelm = nelm;
+ vect->elsize = elsize;
+ if (vect->init_fn)
+ vect->init_fn(vect->ptr, vect->nelm);
+ return (vect->ptr);
+}
+
+/* mvect_realloc - adjust memory vector allocation */
+
+char *mvect_realloc(MVECT *vect, ssize_t nelm)
+{
+ ssize_t old_len = vect->nelm;
+ ssize_t incr = nelm - old_len;
+ ssize_t new_nelm;
+
+ if (incr > 0) {
+ if (incr < old_len)
+ incr = old_len;
+ new_nelm = vect->nelm + incr;
+ vect->ptr = myrealloc(vect->ptr, vect->elsize * new_nelm);
+ vect->nelm = new_nelm;
+ if (vect->init_fn)
+ vect->init_fn(vect->ptr + old_len * vect->elsize, incr);
+ }
+ return (vect->ptr);
+}
+
+/* mvect_free - release memory vector storage */
+
+char *mvect_free(MVECT *vect)
+{
+ if (vect->wipe_fn)
+ vect->wipe_fn(vect->ptr, vect->nelm);
+ myfree(vect->ptr);
+ return (0);
+}
diff --git a/src/util/mvect.h b/src/util/mvect.h
new file mode 100644
index 0000000..bdbb701
--- /dev/null
+++ b/src/util/mvect.h
@@ -0,0 +1,42 @@
+#ifndef _MVECT_H_INCLUDED_
+#define _MVECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* mvect 3h
+/* SUMMARY
+/* memory vector management
+/* SYNOPSIS
+/* #include <mvect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Generic memory vector interface.
+ */
+typedef void (*MVECT_FN) (char *, ssize_t);
+
+typedef struct {
+ char *ptr;
+ ssize_t elsize;
+ ssize_t nelm;
+ MVECT_FN init_fn;
+ MVECT_FN wipe_fn;
+} MVECT;
+
+extern char *mvect_alloc(MVECT *, ssize_t, ssize_t, MVECT_FN, MVECT_FN);
+extern char *mvect_realloc(MVECT *, ssize_t);
+extern char *mvect_free(MVECT *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/myaddrinfo.c b/src/util/myaddrinfo.c
new file mode 100644
index 0000000..5edafde
--- /dev/null
+++ b/src/util/myaddrinfo.c
@@ -0,0 +1,905 @@
+/*++
+/* NAME
+/* myaddrinfo 3
+/* SUMMARY
+/* addrinfo encapsulation and emulation
+/* SYNOPSIS
+/* #include <myaddrinfo.h>
+/*
+/* #define MAI_V4ADDR_BITS ...
+/* #define MAI_V6ADDR_BITS ...
+/* #define MAI_V4ADDR_BYTES ...
+/* #define MAI_V6ADDR_BYTES ...
+/*
+/* typedef struct { char buf[....]; } MAI_HOSTNAME_STR;
+/* typedef struct { char buf[....]; } MAI_HOSTADDR_STR;
+/* typedef struct { char buf[....]; } MAI_SERVNAME_STR;
+/* typedef struct { char buf[....]; } MAI_SERVPORT_STR;
+/*
+/* int hostname_to_sockaddr(hostname, service, socktype, result)
+/* const char *hostname;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int hostname_to_sockaddr_pf(hostname, pf, service, socktype, result)
+/* const char *hostname;
+/* int pf;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int hostaddr_to_sockaddr(hostaddr, service, socktype, result)
+/* const char *hostaddr;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int sockaddr_to_hostaddr(sa, salen, hostaddr, portnum, socktype)
+/* const struct sockaddr *sa;
+/* SOCKADDR_SIZE salen;
+/* MAI_HOSTADDR_STR *hostaddr;
+/* MAI_SERVPORT_STR *portnum;
+/* int socktype;
+/*
+/* int sockaddr_to_hostname(sa, salen, hostname, service, socktype)
+/* const struct sockaddr *sa;
+/* SOCKADDR_SIZE salen;
+/* MAI_HOSTNAME_STR *hostname;
+/* MAI_SERVNAME_STR *service;
+/* int socktype;
+/*
+/* const char *MAI_STRERROR(error)
+/* int error;
+/* DESCRIPTION
+/* This module provides a simplified user interface to the
+/* getaddrinfo(3) and getnameinfo(3) routines (which provide
+/* a unified interface to manipulate IPv4 and IPv6 socket
+/* address structures).
+/*
+/* On systems without getaddrinfo(3) and getnameinfo(3) support,
+/* emulation for IPv4 only can be enabled by defining
+/* EMULATE_IPV4_ADDRINFO.
+/*
+/* hostname_to_sockaddr() looks up the binary addresses for
+/* the specified symbolic hostname or numeric address. The
+/* result should be destroyed with freeaddrinfo(). A null host
+/* pointer converts to the null host address.
+/*
+/* hostname_to_sockaddr_pf() is an extended interface that
+/* provides a protocol family override.
+/*
+/* hostaddr_to_sockaddr() converts a printable network address
+/* into the corresponding binary form. The result should be
+/* destroyed with freeaddrinfo(). A null host pointer converts
+/* to the null host address.
+/*
+/* sockaddr_to_hostaddr() converts a binary network address
+/* into printable form. The result buffers should be large
+/* enough to hold the printable address or port including the
+/* null terminator.
+/* This function strips off the IPv6 datalink suffix.
+/*
+/* sockaddr_to_hostname() converts a binary network address
+/* into a hostname or service. The result buffer should be
+/* large enough to hold the hostname or service including the
+/* null terminator. This routine rejects malformed hostnames
+/* or numeric hostnames and pretends that the lookup failed.
+/*
+/* MAI_STRERROR() is an unsafe macro (it evaluates the argument
+/* multiple times) that invokes strerror() or gai_strerror()
+/* as appropriate.
+/*
+/* This module exports the following constants that should be
+/* user for storage allocation of name or address information:
+/* .IP MAI_V4ADDR_BITS
+/* .IP MAI_V6ADDR_BITS
+/* .IP MAI_V4ADDR_BYTES
+/* .IP MAI_V6ADDR_BYTES
+/* The number of bits or bytes needed to store a binary
+/* IPv4 or IPv6 network address.
+/* .PP
+/* The types MAI_HOST{NAME,ADDR}_STR and MAI_SERV{NAME,PORT}_STR
+/* implement buffers for the storage of the string representations
+/* of symbolic or numerical hosts or services. Do not use
+/* buffer types other than the ones that are expected here,
+/* or things will blow up with buffer overflow problems.
+/*
+/* Arguments:
+/* .IP hostname
+/* On input to hostname_to_sockaddr(), a numeric or symbolic
+/* hostname, or a null pointer (meaning the wild-card listen
+/* address). On output from sockaddr_to_hostname(), storage
+/* for the result hostname, or a null pointer.
+/* .IP pf
+/* Protocol type: PF_UNSPEC (meaning: use any protocol that is
+/* available), PF_INET, or PF_INET6. This argument is ignored
+/* in EMULATE_IPV4_ADDRINFO mode.
+/* .IP hostaddr
+/* On input to hostaddr_to_sockaddr(), a numeric hostname,
+/* or a null pointer (meaning the wild-card listen address).
+/* On output from sockaddr_to_hostaddr(), storage for the
+/* result hostaddress, or a null pointer.
+/* .IP service
+/* On input to hostname/addr_to_sockaddr(), a numeric or
+/* symbolic service name, or a null pointer in which case the
+/* socktype argument is ignored. On output from
+/* sockaddr_to_hostname/addr(), storage for the result service
+/* name, or a null pointer.
+/* .IP portnum
+/* Storage for the result service port number, or a null pointer.
+/* .IP socktype
+/* Socket type: SOCK_STREAM, SOCK_DGRAM, etc. This argument is
+/* ignored when no service or port are specified.
+/* .IP sa
+/* Protocol-independent socket address structure.
+/* .IP salen
+/* Protocol-dependent socket address structure size in bytes.
+/* SEE ALSO
+/* getaddrinfo(3), getnameinfo(3), freeaddrinfo(3), gai_strerror(3)
+/* DIAGNOSTICS
+/* All routines either return 0 upon success, or an error code
+/* that is compatible with gai_strerror().
+/*
+/* On systems where addrinfo support is emulated by Postfix,
+/* some out-of-memory errors are not reported to the caller,
+/* but are handled by mymalloc().
+/* BUGS
+/* The IPv4-only emulation code does not support requests that
+/* specify a service but no socket type. It returns an error
+/* indication, instead of enumerating all the possible answers.
+/*
+/* The hostname/addr_to_sockaddr() routines should accept a
+/* list of address families that the caller is interested in,
+/* and they should return only information of those types.
+/*
+/* Unfortunately, it is not possible to remove unwanted address
+/* family results from hostname_to_sockaddr(), because we
+/* don't know how the system library routine getaddrinfo()
+/* allocates memory. For example, getaddrinfo() could save
+/* space by referencing the same string object from multiple
+/* addrinfo structures; or it could allocate a string object
+/* and the addrinfo structure as one memory block.
+/*
+/* We could get around this by copying getaddrinfo() results
+/* to our own private data structures, but that would only
+/* make an already expensive API even more expensive.
+/*
+/* A better workaround is to return a vector of addrinfo
+/* pointers to the elements that contain only the elements
+/* that the caller is interested in. The pointer to the
+/* original getaddrinfo() result can be hidden at the end
+/* after the null terminator, or before the first element.
+/* 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/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h> /* sprintf() */
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <valid_hostname.h>
+#include <sock_addr.h>
+#include <stringops.h>
+#include <msg.h>
+#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <split_at.h>
+#include <known_tcp_ports.h>
+
+/* Application-specific. */
+
+ /*
+ * Use an old trick to save some space: allocate space for two objects in
+ * one. In Postfix we often use this trick for structures that have an array
+ * of things at the end.
+ */
+struct ipv4addrinfo {
+ struct addrinfo info;
+ struct sockaddr_in sin;
+};
+
+ /*
+ * When we're not interested in service ports, we must pick a socket type
+ * otherwise getaddrinfo() will give us duplicate results: one set for TCP,
+ * and another set for UDP. For consistency, we'll use the same default
+ * socket type for the results from emulation mode.
+ */
+#define MAI_SOCKTYPE SOCK_STREAM /* getaddrinfo() query */
+
+#ifdef EMULATE_IPV4_ADDRINFO
+
+/* clone_ipv4addrinfo - clone ipv4addrinfo structure */
+
+static struct ipv4addrinfo *clone_ipv4addrinfo(struct ipv4addrinfo * tp)
+{
+ struct ipv4addrinfo *ip;
+
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ *ip = *tp;
+ ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
+ return (ip);
+}
+
+/* init_ipv4addrinfo - initialize an ipv4addrinfo structure */
+
+static void init_ipv4addrinfo(struct ipv4addrinfo * ip, int socktype)
+{
+
+ /*
+ * Portability: null pointers aren't necessarily all-zero bits, so we
+ * make explicit assignments to all the pointers that we're aware of.
+ */
+ memset((void *) ip, 0, sizeof(*ip));
+ ip->info.ai_family = PF_INET;
+ ip->info.ai_socktype = socktype;
+ ip->info.ai_protocol = 0; /* XXX */
+ ip->info.ai_addrlen = sizeof(ip->sin);
+ ip->info.ai_canonname = 0;
+ ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
+ ip->info.ai_next = 0;
+ ip->sin.sin_family = AF_INET;
+#ifdef HAS_SA_LEN
+ ip->sin.sin_len = sizeof(ip->sin);
+#endif
+}
+
+/* find_service - translate numeric or symbolic service name */
+
+static int find_service(const char *service, int socktype)
+{
+ struct servent *sp;
+ const char *proto;
+ unsigned port;
+
+ service = filter_known_tcp_port(service);
+ if (alldig(service)) {
+ port = atoi(service);
+ return (port < 65536 ? htons(port) : -1);
+ }
+ if (socktype == SOCK_STREAM) {
+ proto = "tcp";
+ } else if (socktype == SOCK_DGRAM) {
+ proto = "udp";
+ } else {
+ return (-1);
+ }
+ if ((sp = getservbyname(service, proto)) != 0) {
+ return (sp->s_port);
+ } else {
+ return (-1);
+ }
+}
+
+#endif
+
+/* hostname_to_sockaddr_pf - hostname to binary address form */
+
+int hostname_to_sockaddr_pf(const char *hostname, int pf,
+ const char *service, int socktype,
+ struct addrinfo ** res)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getaddrinfo(3) version.
+ */
+ static struct ipv4addrinfo template;
+ struct ipv4addrinfo *ip;
+ struct ipv4addrinfo *prev;
+ struct in_addr addr;
+ struct hostent *hp;
+ char **name_list;
+ int port;
+
+ /*
+ * Validate the service.
+ */
+ if (service) {
+ if ((port = find_service(service, socktype)) < 0)
+ return (EAI_SERVICE);
+ } else {
+ port = 0;
+ socktype = MAI_SOCKTYPE;
+ }
+
+ /*
+ * No host means INADDR_ANY.
+ */
+ if (hostname == 0) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr.s_addr = INADDR_ANY;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Numeric host.
+ */
+ if (inet_pton(AF_INET, hostname, (void *) &addr) == 1) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr = addr;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Look up the IPv4 address list.
+ */
+ if ((hp = gethostbyname(hostname)) == 0)
+ return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA);
+ if (hp->h_addrtype != AF_INET
+ || hp->h_length != sizeof(template.sin.sin_addr))
+ return (EAI_NODATA);
+
+ /*
+ * Initialize the result template.
+ */
+ if (template.info.ai_addrlen == 0)
+ init_ipv4addrinfo(&template, socktype);
+
+ /*
+ * Copy the address information into an addrinfo structure.
+ */
+ prev = &template;
+ for (name_list = hp->h_addr_list; name_list[0]; name_list++) {
+ ip = clone_ipv4addrinfo(prev);
+ ip->sin.sin_addr = IN_ADDR(name_list[0]);
+ ip->sin.sin_port = port;
+ if (prev == &template)
+ *res = &(ip->info);
+ else
+ prev->info.ai_next = &(ip->info);
+ prev = ip;
+ }
+ return (0);
+#else
+
+ /*
+ * Native getaddrinfo(3) version.
+ *
+ * XXX Wild-card listener issues.
+ *
+ * With most IPv4 plus IPv6 systems, an IPv6 wild-card listener also listens
+ * on the IPv4 wild-card address. Connections from IPv4 clients appear as
+ * IPv4-in-IPv6 addresses; when Postfix support for IPv4 is turned on,
+ * Postfix automatically maps these embedded addresses to their original
+ * IPv4 form. So everything seems to be fine.
+ *
+ * However, some applications prefer to use separate listener sockets for
+ * IPv4 and IPv6. The Postfix IPv6 patch provided such an example. And
+ * this is where things become tricky. On many systems the IPv6 and IPv4
+ * wild-card listeners cannot coexist. When one is already active, the
+ * other fails with EADDRINUSE. Solaris 9, however, will automagically
+ * "do the right thing" and allow both listeners to coexist.
+ *
+ * Recent systems have the IPV6_V6ONLY feature (RFC 3493), which tells the
+ * system that we really mean IPv6 when we say IPv6. This allows us to
+ * set up separate wild-card listener sockets for IPv4 and IPv6. So
+ * everything seems to be fine again.
+ *
+ * The following workaround disables the wild-card IPv4 listener when
+ * IPV6_V6ONLY is unavailable. This is necessary for some Linux versions,
+ * but is not needed for Solaris 9 (which allows IPv4 and IPv6 wild-card
+ * listeners to coexist). Solaris 10 beta already has IPV6_V6ONLY.
+ *
+ * XXX This workaround obviously breaks if we want to support protocols in
+ * addition to IPv6 and IPv4, but it is needed only until IPv6
+ * implementations catch up with RFC 3493. A nicer fix is to filter the
+ * getaddrinfo() result, and to return a vector of addrinfo pointers to
+ * only those types of elements that the caller has expressed interested
+ * in.
+ *
+ * XXX Vanilla AIX 5.1 getaddrinfo() does not support a null hostname with
+ * AI_PASSIVE. And since we don't know how getaddrinfo() manages its
+ * memory we can't bypass it for this special case, or freeaddrinfo()
+ * might blow up. Instead we turn off IPV6_V6ONLY in inet_listen(), and
+ * supply a protocol-dependent hard-coded string value to getaddrinfo()
+ * below, so that it will convert into the appropriate wild-card address.
+ *
+ * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
+ * service argument is specified.
+ */
+ struct addrinfo hints;
+ int err;
+
+ memset((void *) &hints, 0, sizeof(hints));
+ hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family;
+ hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
+ if (!hostname) {
+ hints.ai_flags = AI_PASSIVE;
+#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ switch (hints.ai_family) {
+ case PF_UNSPEC:
+ hints.ai_family = PF_INET6;
+#ifdef BROKEN_AI_PASSIVE_NULL_HOST
+ case PF_INET6:
+ hostname = "::";
+ break;
+ case PF_INET:
+ hostname = "0.0.0.0";
+ break;
+#endif
+ }
+#endif
+ }
+ if (service) {
+ service = filter_known_tcp_port(service);
+ if (alldig(service))
+ hints.ai_flags |= AI_NUMERICSERV;
+ }
+ err = getaddrinfo(hostname, service, &hints, res);
+#if defined(BROKEN_AI_NULL_SERVICE)
+ if (service == 0 && err == 0) {
+ struct addrinfo *r;
+ unsigned short *portp;
+
+ for (r = *res; r != 0; r = r->ai_next)
+ if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
+ *portp = 0;
+ }
+#endif
+ return (err);
+#endif
+}
+
+/* hostaddr_to_sockaddr - printable address to binary address form */
+
+int hostaddr_to_sockaddr(const char *hostaddr, const char *service,
+ int socktype, struct addrinfo ** res)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getaddrinfo(3) version.
+ */
+ struct ipv4addrinfo *ip;
+ struct in_addr addr;
+ int port;
+
+ /*
+ * Validate the service.
+ */
+ if (service) {
+ if ((port = find_service(service, socktype)) < 0)
+ return (EAI_SERVICE);
+ } else {
+ port = 0;
+ socktype = MAI_SOCKTYPE;
+ }
+
+ /*
+ * No host means INADDR_ANY.
+ */
+ if (hostaddr == 0) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr.s_addr = INADDR_ANY;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Deal with bad address forms.
+ */
+ switch (inet_pton(AF_INET, hostaddr, (void *) &addr)) {
+ case 1: /* Success */
+ break;
+ default: /* Unparsable */
+ return (EAI_NONAME);
+ case -1: /* See errno */
+ return (EAI_SYSTEM);
+ }
+
+ /*
+ * Initialize the result structure.
+ */
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+
+ /*
+ * And copy the result.
+ */
+ ip->sin.sin_addr = addr;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+
+ return (0);
+#else
+
+ /*
+ * Native getaddrinfo(3) version. See comments in hostname_to_sockaddr().
+ *
+ * XXX Vanilla AIX 5.1 getaddrinfo() returns multiple results when
+ * converting a printable ipv4 or ipv6 address to socket address with
+ * ai_family=PF_UNSPEC, ai_flags=AI_NUMERICHOST, ai_socktype=SOCK_STREAM,
+ * ai_protocol=0 or IPPROTO_TCP, and service=0. The workaround is to
+ * ignore all but the first result.
+ *
+ * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
+ * service argument is specified.
+ */
+ struct addrinfo hints;
+ int err;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = inet_proto_info()->ai_family;
+ hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (!hostaddr) {
+ hints.ai_flags |= AI_PASSIVE;
+#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ switch (hints.ai_family) {
+ case PF_UNSPEC:
+ hints.ai_family = PF_INET6;
+#ifdef BROKEN_AI_PASSIVE_NULL_HOST
+ case PF_INET6:
+ hostaddr = "::";
+ break;
+ case PF_INET:
+ hostaddr = "0.0.0.0";
+ break;
+#endif
+ }
+#endif
+ }
+ if (service) {
+ service = filter_known_tcp_port(service);
+ if (alldig(service))
+ hints.ai_flags |= AI_NUMERICSERV;
+ }
+ err = getaddrinfo(hostaddr, service, &hints, res);
+#if defined(BROKEN_AI_NULL_SERVICE)
+ if (service == 0 && err == 0) {
+ struct addrinfo *r;
+ unsigned short *portp;
+
+ for (r = *res; r != 0; r = r->ai_next)
+ if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
+ *portp = 0;
+ }
+#endif
+ return (err);
+#endif
+}
+
+/* sockaddr_to_hostaddr - binary address to printable address form */
+
+int sockaddr_to_hostaddr(const struct sockaddr *sa, SOCKADDR_SIZE salen,
+ MAI_HOSTADDR_STR *hostaddr,
+ MAI_SERVPORT_STR *portnum,
+ int unused_socktype)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+ char portbuf[sizeof("65535")];
+ ssize_t len;
+
+ /*
+ * Emulated getnameinfo(3) version. The buffer length includes the space
+ * for the null terminator.
+ */
+ if (sa->sa_family != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (EAI_SYSTEM);
+ }
+ if (hostaddr != 0) {
+ if (inet_ntop(AF_INET, (void *) &(SOCK_ADDR_IN_ADDR(sa)),
+ hostaddr->buf, sizeof(hostaddr->buf)) == 0)
+ return (EAI_SYSTEM);
+ }
+ if (portnum != 0) {
+ sprintf(portbuf, "%d", ntohs(SOCK_ADDR_IN_PORT(sa)) & 0xffff);
+ if ((len = strlen(portbuf)) >= sizeof(portnum->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(portnum->buf, portbuf, len + 1);
+ }
+ return (0);
+#else
+ int ret;
+
+ /*
+ * Native getnameinfo(3) version.
+ */
+ ret = getnameinfo(sa, salen,
+ hostaddr ? hostaddr->buf : (char *) 0,
+ hostaddr ? sizeof(hostaddr->buf) : 0,
+ portnum ? portnum->buf : (char *) 0,
+ portnum ? sizeof(portnum->buf) : 0,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (hostaddr != 0 && ret == 0 && sa->sa_family == AF_INET6)
+ (void) split_at(hostaddr->buf, '%');
+ return (ret);
+#endif
+}
+
+/* sockaddr_to_hostname - binary address to printable hostname */
+
+int sockaddr_to_hostname(const struct sockaddr *sa, SOCKADDR_SIZE salen,
+ MAI_HOSTNAME_STR *hostname,
+ MAI_SERVNAME_STR *service,
+ int socktype)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getnameinfo(3) version.
+ */
+ struct hostent *hp;
+ struct servent *sp;
+ size_t len;
+
+ /*
+ * Sanity check.
+ */
+ if (sa->sa_family != AF_INET)
+ return (EAI_NODATA);
+
+ /*
+ * Look up the host name.
+ */
+ if (hostname != 0) {
+ if ((hp = gethostbyaddr((char *) &(SOCK_ADDR_IN_ADDR(sa)),
+ sizeof(SOCK_ADDR_IN_ADDR(sa)),
+ AF_INET)) == 0)
+ return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NONAME);
+
+ /*
+ * Save the result. The buffer length includes the space for the null
+ * terminator. Hostname sanity checks are at the end of this
+ * function.
+ */
+ if ((len = strlen(hp->h_name)) >= sizeof(hostname->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(hostname->buf, hp->h_name, len + 1);
+ }
+
+ /*
+ * Look up the service.
+ */
+ if (service != 0) {
+ if ((sp = getservbyport(ntohs(SOCK_ADDR_IN_PORT(sa)),
+ socktype == SOCK_DGRAM ? "udp" : "tcp")) == 0)
+ return (EAI_NONAME);
+
+ /*
+ * Save the result. The buffer length includes the space for the null
+ * terminator.
+ */
+ if ((len = strlen(sp->s_name)) >= sizeof(service->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(service->buf, sp->s_name, len + 1);
+ }
+#else
+
+ /*
+ * Native getnameinfo(3) version.
+ */
+ int err;
+
+ err = getnameinfo(sa, salen,
+ hostname ? hostname->buf : (char *) 0,
+ hostname ? sizeof(hostname->buf) : 0,
+ service ? service->buf : (char *) 0,
+ service ? sizeof(service->buf) : 0,
+ socktype == SOCK_DGRAM ?
+ NI_NAMEREQD | NI_DGRAM : NI_NAMEREQD);
+ if (err != 0)
+ return (err);
+#endif
+
+ /*
+ * Hostname sanity checks.
+ */
+ if (hostname != 0) {
+ if (valid_hostaddr(hostname->buf, DONT_GRIPE)) {
+ msg_warn("numeric hostname: %s", hostname->buf);
+ return (EAI_NONAME);
+ }
+ if (!valid_hostname(hostname->buf, DO_GRIPE))
+ return (EAI_NONAME);
+ }
+ return (0);
+}
+
+/* myaddrinfo_control - fine control */
+
+void myaddrinfo_control(int name,...)
+{
+ const char *myname = "myaddrinfo_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != 0; name = va_arg(ap, int)) {
+ switch (name) {
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+#ifdef EMULATE_IPV4_ADDRINFO
+
+/* freeaddrinfo - release storage */
+
+void freeaddrinfo(struct addrinfo * ai)
+{
+ struct addrinfo *ap;
+ struct addrinfo *next;
+
+ /*
+ * Artifact of implementation: tolerate a null pointer argument.
+ */
+ for (ap = ai; ap != 0; ap = next) {
+ next = ap->ai_next;
+ if (ap->ai_canonname)
+ myfree(ap->ai_canonname);
+ /* ap->ai_addr is allocated within this memory block */
+ myfree((void *) ap);
+ }
+}
+
+static char *ai_errlist[] = {
+ "Success",
+ "Address family for hostname not supported", /* EAI_ADDRFAMILY */
+ "Temporary failure in name resolution", /* EAI_AGAIN */
+ "Invalid value for ai_flags", /* EAI_BADFLAGS */
+ "Non-recoverable failure in name resolution", /* EAI_FAIL */
+ "ai_family not supported", /* EAI_FAMILY */
+ "Memory allocation failure", /* EAI_MEMORY */
+ "No address associated with hostname", /* EAI_NODATA */
+ "hostname nor servname provided, or not known", /* EAI_NONAME */
+ "service name not supported for ai_socktype", /* EAI_SERVICE */
+ "ai_socktype not supported", /* EAI_SOCKTYPE */
+ "System error returned in errno", /* EAI_SYSTEM */
+ "Invalid value for hints", /* EAI_BADHINTS */
+ "Resolved protocol is unknown", /* EAI_PROTOCOL */
+ "Unknown error", /* EAI_MAX */
+};
+
+/* gai_strerror - error number to string */
+
+char *gai_strerror(int ecode)
+{
+
+ /*
+ * Note: EAI_SYSTEM errors are not automatically handed over to
+ * strerror(). The application decides.
+ */
+ if (ecode < 0 || ecode > EAI_MAX)
+ ecode = EAI_MAX;
+ return (ai_errlist[ecode]);
+}
+
+#endif
+
+#ifdef TEST
+
+ /*
+ * A test program that takes some info from the command line and runs it
+ * forward and backward through the above conversion routines.
+ */
+#include <stdlib.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ struct addrinfo *info;
+ struct addrinfo *ip;
+ struct addrinfo **resv;
+ MAI_HOSTNAME_STR host;
+ MAI_HOSTADDR_STR addr;
+ size_t len, n;
+ int err;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc != 4)
+ msg_fatal("usage: %s protocols hostname hostaddress", argv[0]);
+
+ inet_proto_init(argv[0], argv[1]);
+
+ msg_info("=== hostname %s ===", argv[2]);
+
+ if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) {
+ msg_info("hostname_to_sockaddr(%s): %s",
+ argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
+ resv[len++] = ip;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ ip = resv[n];
+ if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
+ (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostaddr: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ continue;
+ }
+ msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2],
+ ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostname: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ continue;
+ }
+ msg_info("%s -> %s", addr.buf, host.buf);
+ }
+ freeaddrinfo(info);
+ myfree((void *) resv);
+ }
+
+ msg_info("=== host address %s ===", argv[3]);
+
+ if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) {
+ msg_info("hostaddr_to_sockaddr(%s): %s",
+ argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
+ (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostaddr: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3],
+ ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostname: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else
+ msg_info("%s -> %s", addr.buf, host.buf);
+ freeaddrinfo(ip);
+ }
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/myaddrinfo.h b/src/util/myaddrinfo.h
new file mode 100644
index 0000000..94f1e9f
--- /dev/null
+++ b/src/util/myaddrinfo.h
@@ -0,0 +1,229 @@
+#ifndef _MYADDRINFO_H_INCLUDED_
+#define _MYADDRINFO_H_INCLUDED_
+
+/*++
+/* NAME
+/* myaddrinfo 3h
+/* SUMMARY
+/* addrinfo encapsulation and emulation
+/* SYNOPSIS
+/* #include <myaddrinfo.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h> /* MAI_STRERROR() */
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Backwards compatibility support for IPV4 systems without addrinfo API.
+ */
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Avoid clashes with global symbols, just in case some third-party library
+ * provides its own addrinfo() implementation. This also allows us to test
+ * the IPV4 emulation code on an IPV6 enabled system.
+ */
+#undef freeaddrinfo
+#define freeaddrinfo mai_freeaddrinfo
+#undef gai_strerror
+#define gai_strerror mai_strerror
+#undef addrinfo
+#define addrinfo mai_addrinfo
+#undef sockaddr_storage
+#define sockaddr_storage mai_sockaddr_storage
+
+ /*
+ * Modern systems define this in <netdb.h>.
+ */
+struct addrinfo {
+ int ai_flags; /* AI_PASSIVE|CANONNAME|NUMERICHOST */
+ int ai_family; /* PF_xxx */
+ int ai_socktype; /* SOCK_xxx */
+ int ai_protocol; /* 0 or IPPROTO_xxx */
+ size_t ai_addrlen; /* length of ai_addr */
+ char *ai_canonname; /* canonical name for nodename */
+ struct sockaddr *ai_addr; /* binary address */
+ struct addrinfo *ai_next; /* next structure in linked list */
+};
+
+ /*
+ * Modern systems define this in <sys/socket.h>.
+ */
+struct sockaddr_storage {
+ struct sockaddr_in dummy; /* alignment!! */
+};
+
+ /*
+ * Result codes. See gai_strerror() for text. Undefine already imported
+ * definitions so that we can test the IPv4-only emulation on a modern
+ * system without getting a ton of compiler warnings.
+ */
+#undef EAI_ADDRFAMILY
+#define EAI_ADDRFAMILY 1
+#undef EAI_AGAIN
+#define EAI_AGAIN 2
+#undef EAI_BADFLAGS
+#define EAI_BADFLAGS 3
+#undef EAI_FAIL
+#define EAI_FAIL 4
+#undef EAI_FAMILY
+#define EAI_FAMILY 5
+#undef EAI_MEMORY
+#define EAI_MEMORY 6
+#undef EAI_NODATA
+#define EAI_NODATA 7
+#undef EAI_NONAME
+#define EAI_NONAME 8
+#undef EAI_SERVICE
+#define EAI_SERVICE 9
+#undef EAI_SOCKTYPE
+#define EAI_SOCKTYPE 10
+#undef EAI_SYSTEM
+#define EAI_SYSTEM 11
+#undef EAI_BADHINTS
+#define EAI_BADHINTS 12
+#undef EAI_PROTOCOL
+#define EAI_PROTOCOL 13
+#undef EAI_RESNULL
+#define EAI_RESNULL 14
+#undef EAI_MAX
+#define EAI_MAX 15
+
+extern void freeaddrinfo(struct addrinfo *);
+extern char *gai_strerror(int);
+
+#endif
+
+ /*
+ * Bounds grow in leaps. These macros attempt to keep non-library code free
+ * from IPV6 #ifdef pollution. Avoid macro names that end in STRLEN because
+ * they suggest that space for the null terminator is not included.
+ */
+#ifdef HAS_IPV6
+# define MAI_HOSTADDR_STRSIZE INET6_ADDRSTRLEN
+#else
+# ifndef INET_ADDRSTRLEN
+# define INET_ADDRSTRLEN 16
+# endif
+# define MAI_HOSTADDR_STRSIZE INET_ADDRSTRLEN
+#endif
+
+#define MAI_HOSTNAME_STRSIZE 1025
+#define MAI_SERVNAME_STRSIZE 32
+#define MAI_SERVPORT_STRSIZE sizeof("65535")
+
+#define MAI_V4ADDR_BITS 32
+#define MAI_V6ADDR_BITS 128
+#define MAI_V4ADDR_BYTES ((MAI_V4ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT)
+#define MAI_V6ADDR_BYTES ((MAI_V6ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT)
+
+ /*
+ * Routines and data structures to hide some of the complexity of the
+ * addrinfo API. They still don't hide that we may get results for address
+ * families that we aren't interested in.
+ *
+ * Note: the getnameinfo() and inet_ntop() system library functions use unsafe
+ * APIs with separate pointer and length arguments. To avoid buffer overflow
+ * problems with these functions, Postfix uses pointers to structures
+ * internally. This way the compiler can enforce that callers provide
+ * buffers with the appropriate length, instead of having to trust that
+ * callers will never mess up some length calculation.
+ */
+typedef struct {
+ char buf[MAI_HOSTNAME_STRSIZE];
+} MAI_HOSTNAME_STR;
+
+typedef struct {
+ char buf[MAI_HOSTADDR_STRSIZE];
+} MAI_HOSTADDR_STR;
+
+typedef struct {
+ char buf[MAI_SERVNAME_STRSIZE];
+} MAI_SERVNAME_STR;
+
+typedef struct {
+ char buf[MAI_SERVPORT_STRSIZE];
+} MAI_SERVPORT_STR;
+
+extern int WARN_UNUSED_RESULT hostname_to_sockaddr_pf(const char *,
+ int, const char *, int, struct addrinfo **);
+extern int WARN_UNUSED_RESULT hostaddr_to_sockaddr(const char *,
+ const char *, int, struct addrinfo **);
+extern int WARN_UNUSED_RESULT sockaddr_to_hostaddr(const struct sockaddr *,
+ SOCKADDR_SIZE, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int);
+extern int WARN_UNUSED_RESULT sockaddr_to_hostname(const struct sockaddr *,
+ SOCKADDR_SIZE, MAI_HOSTNAME_STR *, MAI_SERVNAME_STR *, int);
+extern void myaddrinfo_control(int,...);
+
+#define MAI_CTL_END 0 /* list terminator */
+
+#define MAI_STRERROR(e) ((e) == EAI_SYSTEM ? strerror(errno) : gai_strerror(e))
+
+#define hostname_to_sockaddr(host, serv, sock, res) \
+ hostname_to_sockaddr_pf((host), PF_UNSPEC, (serv), (sock), (res))
+
+ /*
+ * Macros for the case where we really don't want to be bothered with things
+ * that may fail.
+ */
+#define HOSTNAME_TO_SOCKADDR_PF(host, pf, serv, sock, res) \
+ do { \
+ int _aierr; \
+ _aierr = hostname_to_sockaddr_pf((host), (pf), (serv), (sock), (res)); \
+ if (_aierr) \
+ msg_fatal("hostname_to_sockaddr_pf: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \
+ HOSTNAME_TO_SOCKADDR_PF((host), PF_UNSPEC, (serv), (sock), (res))
+
+#define HOSTADDR_TO_SOCKADDR(host, serv, sock, res) \
+ do { \
+ int _aierr; \
+ _aierr = hostaddr_to_sockaddr((host), (serv), (sock), (res)); \
+ if (_aierr) \
+ msg_fatal("hostaddr_to_sockaddr: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define SOCKADDR_TO_HOSTADDR(sa, salen, host, port, sock) \
+ do { \
+ int _aierr; \
+ _aierr = sockaddr_to_hostaddr((sa), (salen), (host), (port), (sock)); \
+ if (_aierr) \
+ msg_fatal("sockaddr_to_hostaddr: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define SOCKADDR_TO_HOSTNAME(sa, salen, host, service, sock) \
+ do { \
+ int _aierr; \
+ _aierr = sockaddr_to_hostname((sa), (salen), (host), (service), (sock)); \
+ if (_aierr) \
+ msg_fatal("sockaddr_to_hostname: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/myaddrinfo.ref b/src/util/myaddrinfo.ref
new file mode 100644
index 0000000..7dccdb0
--- /dev/null
+++ b/src/util/myaddrinfo.ref
@@ -0,0 +1,8 @@
+./myaddrinfo: === hostname belly.porcupine.org ===
+./myaddrinfo: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
+./myaddrinfo: 168.100.3.6 -> belly.porcupine.org
+./myaddrinfo: belly.porcupine.org -> family=28 sock=1 proto=6 2604:8d00:189::6
+./myaddrinfo: 2604:8d00:189::6 -> belly.porcupine.org
+./myaddrinfo: === host address 168.100.3.2 ===
+./myaddrinfo: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
+./myaddrinfo: 168.100.3.2 -> spike.porcupine.org
diff --git a/src/util/myaddrinfo.ref2 b/src/util/myaddrinfo.ref2
new file mode 100644
index 0000000..f2305dc
--- /dev/null
+++ b/src/util/myaddrinfo.ref2
@@ -0,0 +1,5 @@
+./myaddrinfo: === hostname null.porcupine.org ===
+./myaddrinfo: hostname_to_sockaddr(null.porcupine.org): hostname nor servname provided, or not known
+./myaddrinfo: === host address 10.0.0.0 ===
+./myaddrinfo: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
+./myaddrinfo: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/src/util/myaddrinfo4.ref b/src/util/myaddrinfo4.ref
new file mode 100644
index 0000000..33c1284
--- /dev/null
+++ b/src/util/myaddrinfo4.ref
@@ -0,0 +1,6 @@
+./myaddrinfo4: === hostname belly.porcupine.org ===
+./myaddrinfo4: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
+./myaddrinfo4: 168.100.3.6 -> belly.porcupine.org
+./myaddrinfo4: === host address 168.100.3.2 ===
+./myaddrinfo4: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
+./myaddrinfo4: 168.100.3.2 -> spike.porcupine.org
diff --git a/src/util/myaddrinfo4.ref2 b/src/util/myaddrinfo4.ref2
new file mode 100644
index 0000000..f73560b
--- /dev/null
+++ b/src/util/myaddrinfo4.ref2
@@ -0,0 +1,5 @@
+./myaddrinfo4: === hostname null.porcupine.org ===
+./myaddrinfo4: hostname2sockaddr(null.porcupine.org): No address associated with hostname
+./myaddrinfo4: === host address 10.0.0.0 ===
+./myaddrinfo4: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
+./myaddrinfo4: sockaddr2hostname: hostname nor servname provided, or not known
diff --git a/src/util/myflock.c b/src/util/myflock.c
new file mode 100644
index 0000000..bd903ee
--- /dev/null
+++ b/src/util/myflock.c
@@ -0,0 +1,152 @@
+/*++
+/* NAME
+/* myflock 3
+/* SUMMARY
+/* lock open file
+/* SYNOPSIS
+/* #include <myflock.h>
+/*
+/* int myflock(fd, lock_style, operation)
+/* int fd;
+/* int lock_style;
+/* int operation;
+/* DESCRIPTION
+/* myflock() locks or unlocks an entire open file.
+/*
+/* In the case of a blocking request, a call that fails due to
+/* foreseeable transient problems is retried once per second.
+/*
+/* Arguments:
+/* .IP fd
+/* The open file to be locked/unlocked.
+/* .IP lock_style
+/* One of the following values:
+/* .RS
+/* .IP MYFLOCK_STYLE_FLOCK
+/* Use BSD-style flock() locking.
+/* .IP MYFLOCK_STYLE_FCNTL
+/* Use POSIX-style fcntl() locking.
+/* .RE
+/* .IP operation
+/* One of the following values:
+/* .RS
+/* .IP MYFLOCK_OP_NONE
+/* Release any locks the process has on the specified open file.
+/* .IP MYFLOCK_OP_SHARED
+/* Attempt to acquire a shared lock on the specified open file.
+/* This is appropriate for read-only access.
+/* .IP MYFLOCK_OP_EXCLUSIVE
+/* Attempt to acquire an exclusive lock on the specified open
+/* file. This is appropriate for write access.
+/* .PP
+/* In addition, setting the MYFLOCK_OP_NOWAIT bit causes the
+/* call to return immediately when the requested lock cannot
+/* be acquired.
+/* .RE
+/* DIAGNOSTICS
+/* myflock() returns 0 in case of success, -1 in case of failure.
+/* A problem description is returned via the global \fIerrno\fR
+/* variable. In the case of a non-blocking lock request the value
+/* EAGAIN means that a lock is claimed by someone else.
+/*
+/* Panic: attempts to use an unsupported file locking method or
+/* to implement an unsupported operation.
+/* 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 <unistd.h>
+
+#ifdef HAS_FCNTL_LOCK
+#include <fcntl.h>
+#include <string.h>
+#endif
+
+#ifdef HAS_FLOCK_LOCK
+#include <sys/file.h>
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "myflock.h"
+
+/* myflock - lock/unlock entire open file */
+
+int myflock(int fd, int lock_style, int operation)
+{
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if ((operation & (MYFLOCK_OP_BITS)) != operation)
+ msg_panic("myflock: improper operation type: 0x%x", operation);
+
+ switch (lock_style) {
+
+ /*
+ * flock() does exactly what we need. Too bad it is not standard.
+ */
+#ifdef HAS_FLOCK_LOCK
+ case MYFLOCK_STYLE_FLOCK:
+ {
+ static int lock_ops[] = {
+ LOCK_UN, LOCK_SH, LOCK_EX, -1,
+ -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1
+ };
+
+ while ((status = flock(fd, lock_ops[operation])) < 0
+ && errno == EINTR)
+ sleep(1);
+ break;
+ }
+#endif
+
+ /*
+ * fcntl() is standard and does more than we need, but we can handle
+ * it.
+ */
+#ifdef HAS_FCNTL_LOCK
+ case MYFLOCK_STYLE_FCNTL:
+ {
+ struct flock lock;
+ int request;
+ static int lock_ops[] = {
+ F_UNLCK, F_RDLCK, F_WRLCK
+ };
+
+ memset((void *) &lock, 0, sizeof(lock));
+ lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT];
+ request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW;
+ while ((status = fcntl(fd, request, &lock)) < 0
+ && errno == EINTR)
+ sleep(1);
+ break;
+ }
+#endif
+ default:
+ msg_panic("myflock: unsupported lock style: 0x%x", lock_style);
+ }
+
+ /*
+ * Return a consistent result. Some systems return EACCES when a lock is
+ * taken by someone else, and that would complicate error processing.
+ */
+ if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0)
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES)
+ errno = EAGAIN;
+
+ return (status);
+}
diff --git a/src/util/myflock.h b/src/util/myflock.h
new file mode 100644
index 0000000..ee18bdb
--- /dev/null
+++ b/src/util/myflock.h
@@ -0,0 +1,52 @@
+#ifndef _MYFLOCK_H_INCLUDED_
+#define _MYFLOCK_H_INCLUDED_
+
+/*++
+/* NAME
+/* myflock 3h
+/* SUMMARY
+/* lock open file
+/* SYNOPSIS
+/* #include <myflock.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int WARN_UNUSED_RESULT myflock(int, int, int);
+
+ /*
+ * Lock styles.
+ */
+#define MYFLOCK_STYLE_FLOCK 1
+#define MYFLOCK_STYLE_FCNTL 2
+
+ /*
+ * Lock request types.
+ */
+#define MYFLOCK_OP_NONE 0
+#define MYFLOCK_OP_SHARED 1
+#define MYFLOCK_OP_EXCLUSIVE 2
+#define MYFLOCK_OP_NOWAIT 4
+
+#define MYFLOCK_OP_BITS \
+ (MYFLOCK_OP_SHARED | MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mymalloc.c b/src/util/mymalloc.c
new file mode 100644
index 0000000..94f7bb3
--- /dev/null
+++ b/src/util/mymalloc.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* mymalloc 3
+/* SUMMARY
+/* memory management wrappers
+/* SYNOPSIS
+/* #include <mymalloc.h>
+/*
+/* void *mymalloc(len)
+/* ssize_t len;
+/*
+/* void *myrealloc(ptr, len)
+/* void *ptr;
+/* ssize_t len;
+/*
+/* void myfree(ptr)
+/* void *ptr;
+/*
+/* char *mystrdup(str)
+/* const char *str;
+/*
+/* char *mystrndup(str, len)
+/* const char *str;
+/* ssize_t len;
+/*
+/* void *mymemdup(ptr, len)
+/* const void *ptr;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module performs low-level memory management with error
+/* handling. A call of these functions either succeeds or it does
+/* not return at all.
+/*
+/* To save memory, zero-length strings are shared and read-only.
+/* The caller must not attempt to modify the null terminator.
+/* This code is enabled unless NO_SHARED_EMPTY_STRINGS is
+/* defined at compile time (for example, you have an sscanf()
+/* routine that pushes characters back into its input).
+/*
+/* mymalloc() allocates the requested amount of memory. The memory
+/* is not set to zero.
+/*
+/* myrealloc() resizes memory obtained from mymalloc() or myrealloc()
+/* to the requested size. The result pointer value may differ from
+/* that given via the \fIptr\fR argument.
+/*
+/* myfree() takes memory obtained from mymalloc() or myrealloc()
+/* and makes it available for other use.
+/*
+/* mystrdup() returns a dynamic-memory copy of its null-terminated
+/* argument. This routine uses mymalloc().
+/*
+/* mystrndup() returns a dynamic-memory copy of at most \fIlen\fR
+/* leading characters of its null-terminated
+/* argument. The result is null-terminated. This routine uses mymalloc().
+/*
+/* mymemdup() makes a copy of the memory pointed to by \fIptr\fR
+/* with length \fIlen\fR. The result is NOT null-terminated.
+/* This routine uses mymalloc().
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* the requested amount of memory is not available; improper use
+/* is detected; other fatal errors.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "mymalloc.h"
+
+ /*
+ * Structure of an annotated memory block. In order to detect spurious
+ * free() calls we prepend a signature to memory given to the application.
+ * In order to detect access to free()d blocks, overwrite each block as soon
+ * as it is passed to myfree(). With the code below, the user data has
+ * integer alignment or better.
+ */
+typedef struct MBLOCK {
+ int signature; /* set when block is active */
+ ssize_t length; /* user requested length */
+ union {
+ ALIGN_TYPE align;
+ char payload[1]; /* actually a bunch of bytes */
+ } u;
+} MBLOCK;
+
+#define SIGNATURE 0xdead
+#define FILLER 0xff
+
+#define CHECK_IN_PTR(ptr, real_ptr, len, fname) { \
+ if (ptr == 0) \
+ msg_panic("%s: null pointer input", fname); \
+ real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, u.payload[0])); \
+ if (real_ptr->signature != SIGNATURE) \
+ msg_panic("%s: corrupt or unallocated memory block", fname); \
+ real_ptr->signature = 0; \
+ if ((len = real_ptr->length) < 1) \
+ msg_panic("%s: corrupt memory block length", fname); \
+}
+
+#define CHECK_OUT_PTR(ptr, real_ptr, len) { \
+ real_ptr->signature = SIGNATURE; \
+ real_ptr->length = len; \
+ ptr = real_ptr->u.payload; \
+}
+
+#define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len)
+
+ /*
+ * Optimization for short strings. We share one copy with multiple callers.
+ * This differs from normal heap memory in two ways, because the memory is
+ * shared:
+ *
+ * - It must be read-only to avoid horrible bugs. This is OK because there is
+ * no legitimate reason to modify the null terminator.
+ *
+ * - myfree() cannot overwrite the memory with a filler pattern like it can do
+ * with heap memory. Therefore, some dangling pointer bugs will be masked.
+ */
+#ifndef NO_SHARED_EMPTY_STRINGS
+static const char empty_string[] = "";
+
+#endif
+
+/* mymalloc - allocate memory or bust */
+
+void *mymalloc(ssize_t len)
+{
+ void *ptr;
+ MBLOCK *real_ptr;
+
+ /*
+ * Note: for safety reasons the request length is a signed type. This
+ * allows us to catch integer overflow problems that weren't already
+ * caught up-stream.
+ */
+ if (len < 1)
+ msg_panic("mymalloc: requested length %ld", (long) len);
+#ifdef MYMALLOC_FUZZ
+ len += MYMALLOC_FUZZ;
+#endif
+ if ((real_ptr = (MBLOCK *) malloc(SPACE_FOR(len))) == 0)
+ msg_fatal("mymalloc: insufficient memory for %ld bytes: %m",
+ (long) len);
+ CHECK_OUT_PTR(ptr, real_ptr, len);
+ memset(ptr, FILLER, len);
+ return (ptr);
+}
+
+/* myrealloc - reallocate memory or bust */
+
+void *myrealloc(void *ptr, ssize_t len)
+{
+ MBLOCK *real_ptr;
+ ssize_t old_len;
+
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (ptr == empty_string)
+ return (mymalloc(len));
+#endif
+
+ /*
+ * Note: for safety reasons the request length is a signed type. This
+ * allows us to catch integer overflow problems that weren't already
+ * caught up-stream.
+ */
+ if (len < 1)
+ msg_panic("myrealloc: requested length %ld", (long) len);
+#ifdef MYMALLOC_FUZZ
+ len += MYMALLOC_FUZZ;
+#endif
+ CHECK_IN_PTR(ptr, real_ptr, old_len, "myrealloc");
+ if ((real_ptr = (MBLOCK *) realloc((void *) real_ptr, SPACE_FOR(len))) == 0)
+ msg_fatal("myrealloc: insufficient memory for %ld bytes: %m",
+ (long) len);
+ CHECK_OUT_PTR(ptr, real_ptr, len);
+ if (len > old_len)
+ memset(ptr + old_len, FILLER, len - old_len);
+ return (ptr);
+}
+
+/* myfree - release memory */
+
+void myfree(void *ptr)
+{
+ MBLOCK *real_ptr;
+ ssize_t len;
+
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (ptr != empty_string) {
+#endif
+ CHECK_IN_PTR(ptr, real_ptr, len, "myfree");
+ memset((void *) real_ptr, FILLER, SPACE_FOR(len));
+ free((void *) real_ptr);
+#ifndef NO_SHARED_EMPTY_STRINGS
+ }
+#endif
+}
+
+/* mystrdup - save string to heap */
+
+char *mystrdup(const char *str)
+{
+ size_t len;
+
+ if (str == 0)
+ msg_panic("mystrdup: null pointer argument");
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (*str == 0)
+ return ((char *) empty_string);
+#endif
+ if ((len = strlen(str) + 1) > SSIZE_T_MAX)
+ msg_panic("mystrdup: string length >= SSIZE_T_MAX");
+ return (memcpy(mymalloc(len), str, len));
+}
+
+/* mystrndup - save substring to heap */
+
+char *mystrndup(const char *str, ssize_t len)
+{
+ char *result;
+ char *cp;
+
+ if (str == 0)
+ msg_panic("mystrndup: null pointer argument");
+ if (len < 0)
+ msg_panic("mystrndup: requested length %ld", (long) len);
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (*str == 0)
+ return ((char *) empty_string);
+#endif
+ if ((cp = memchr(str, 0, len)) != 0)
+ len = cp - str;
+ result = memcpy(mymalloc(len + 1), str, len);
+ result[len] = 0;
+ return (result);
+}
+
+/* mymemdup - copy memory */
+
+void *mymemdup(const void *ptr, ssize_t len)
+{
+ if (ptr == 0)
+ msg_panic("mymemdup: null pointer argument");
+ return (memcpy(mymalloc(len), ptr, len));
+}
diff --git a/src/util/mymalloc.h b/src/util/mymalloc.h
new file mode 100644
index 0000000..e2190f7
--- /dev/null
+++ b/src/util/mymalloc.h
@@ -0,0 +1,40 @@
+#ifndef _MALLOC_H_INCLUDED_
+#define _MALLOC_H_INCLUDED_
+
+/*++
+/* NAME
+/* mymalloc 3h
+/* SUMMARY
+/* memory management wrappers
+/* SYNOPSIS
+/* #include "mymalloc.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void *mymalloc(ssize_t);
+extern void *myrealloc(void *, ssize_t);
+extern void myfree(void *);
+extern char *mystrdup(const char *);
+extern char *mystrndup(const char *, ssize_t);
+extern void *mymemdup(const void *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/myrand.c b/src/util/myrand.c
new file mode 100644
index 0000000..39fcbfd
--- /dev/null
+++ b/src/util/myrand.c
@@ -0,0 +1,63 @@
+/*++
+/* NAME
+/* myrand 3
+/* SUMMARY
+/* rand wrapper
+/* SYNOPSIS
+/* #include <myrand.h>
+/*
+/* void mysrand(seed)
+/* int seed;
+/*
+/* int myrand()
+/* DESCRIPTION
+/* This module implements a wrapper for the portable, pseudo-random
+/* number generator. The wrapper adds automatic initialization.
+/*
+/* mysrand() performs initialization. This call may be skipped.
+/*
+/* myrand() returns a pseudo-random number in the range [0, RAND_MAX].
+/* If mysrand() was not called, it is invoked with the process ID
+/* ex-or-ed with the time of day in seconds.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* WARNING
+/* Do not use this code for generating unpredictable numbers.
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <myrand.h>
+
+static int myrand_initdone = 0;
+
+/* mysrand - initialize */
+
+void mysrand(int seed)
+{
+ srand(seed);
+ myrand_initdone = 1;
+}
+
+/* myrand - pseudo-random number */
+
+int myrand(void)
+{
+ if (myrand_initdone == 0)
+ mysrand(getpid() ^ time((time_t *) 0));
+ return (rand());
+}
diff --git a/src/util/myrand.h b/src/util/myrand.h
new file mode 100644
index 0000000..4cc88db
--- /dev/null
+++ b/src/util/myrand.h
@@ -0,0 +1,35 @@
+#ifndef _MYRAND_H_INCLUDED_
+#define _MYRAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* myrand 3h
+/* SUMMARY
+/* rand wrapper
+/* SYNOPSIS
+/* #include <myrand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef RAND_MAX
+#define RAND_MAX 0x7fffffff
+#endif
+
+extern void mysrand(int);
+extern int myrand(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/mystrtok.c b/src/util/mystrtok.c
new file mode 100644
index 0000000..85b15f3
--- /dev/null
+++ b/src/util/mystrtok.c
@@ -0,0 +1,258 @@
+/*++
+/* NAME
+/* mystrtok 3
+/* SUMMARY
+/* safe tokenizer
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *mystrtok(bufp, delimiters)
+/* char **bufp;
+/* const char *delimiters;
+/*
+/* char *mystrtokq(bufp, delimiters, parens)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *parens;
+/*
+/* char *mystrtokdq(bufp, delimiters)
+/* char **bufp;
+/* const char *delimiters;
+/* DESCRIPTION
+/* mystrtok() splits a buffer on the specified \fIdelimiters\fR.
+/* Tokens are delimited by runs of delimiters, so this routine
+/* cannot return zero-length tokens.
+/*
+/* mystrtokq() is like mystrtok() but will not split text
+/* between balanced parentheses. \fIparens\fR specifies the
+/* opening and closing parenthesis (one of each). The set of
+/* \fIparens\fR must be distinct from the set of \fIdelimiters\fR.
+/*
+/* mystrtokdq() is like mystrtok() but will not split text
+/* between double quotes. The backslash character may be used
+/* to escape characters. The double quote and backslash
+/* character must not appear in the set of \fIdelimiters\fR.
+/*
+/* The \fIbufp\fR argument specifies the start of the search; it
+/* is updated with each call. The input is destroyed.
+/*
+/* The result value is the next token, or a null pointer when the
+/* end of the buffer was reached.
+/* 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 "stringops.h"
+
+/* mystrtok - safe tokenizer */
+
+char *mystrtok(char **src, const char *sep)
+{
+ char *start = *src;
+ char *end;
+
+ /*
+ * Skip over leading delimiters.
+ */
+ start += strspn(start, sep);
+ if (*start == 0) {
+ *src = start;
+ return (0);
+ }
+
+ /*
+ * Separate off one token.
+ */
+ end = start + strcspn(start, sep);
+ if (*end != 0)
+ *end++ = 0;
+ *src = end;
+ return (start);
+}
+
+/* mystrtokq - safe tokenizer with quoting support */
+
+char *mystrtokq(char **src, const char *sep, const char *parens)
+{
+ char *start = *src;
+ static char *cp;
+ int ch;
+ int level;
+
+ /*
+ * Skip over leading delimiters.
+ */
+ start += strspn(start, sep);
+ if (*start == 0) {
+ *src = start;
+ return (0);
+ }
+
+ /*
+ * Parse out the next token.
+ */
+ for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == parens[0]) {
+ level++;
+ } else if (level > 0 && ch == parens[1]) {
+ level--;
+ } else if (level == 0 && strchr(sep, ch) != 0) {
+ *cp++ = 0;
+ break;
+ }
+ }
+ *src = cp;
+ return (start);
+}
+
+/* mystrtokdq - safe tokenizer, double quote and backslash support */
+
+char *mystrtokdq(char **src, const char *sep)
+{
+ char *cp = *src;
+ char *start;
+
+ /*
+ * Skip leading delimiters.
+ */
+ cp += strspn(cp, sep);
+
+ /*
+ * Skip to next unquoted space or comma.
+ */
+ if (*cp == 0) {
+ start = 0;
+ } else {
+ int in_quotes;
+
+ for (in_quotes = 0, start = cp; *cp; cp++) {
+ if (*cp == '\\') {
+ if (*++cp == 0)
+ break;
+ } else if (*cp == '"') {
+ in_quotes = !in_quotes;
+ } else if (!in_quotes && strchr(sep, *(unsigned char *) cp) != 0) {
+ *cp++ = 0;
+ break;
+ }
+ }
+ }
+ *src = cp;
+ return (start);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program.
+ */
+#include "msg.h"
+#include "mymalloc.h"
+
+ /*
+ * The following needs to be large enough to include a null terminator in
+ * every testcase.expected field.
+ */
+#define EXPECT_SIZE 5
+
+struct testcase {
+ const char *action;
+ const char *input;
+ const char *expected[EXPECT_SIZE];
+};
+static const struct testcase testcases[] = {
+ {"mystrtok", ""},
+ {"mystrtok", " foo ", {"foo"}},
+ {"mystrtok", " foo bar ", {"foo", "bar"}},
+ {"mystrtokq", ""},
+ {"mystrtokq", "foo bar", {"foo", "bar"}},
+ {"mystrtokq", "{ bar } ", {"{ bar }"}},
+ {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}},
+ {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}},
+ {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}},
+ {"mystrtokdq", ""},
+ {"mystrtokdq", " foo ", {"foo"}},
+ {"mystrtokdq", " foo bar ", {"foo", "bar"}},
+ {"mystrtokdq", " foo\\ bar ", {"foo\\ bar"}},
+ {"mystrtokdq", " foo \\\" bar", {"foo", "\\\"", "bar"}},
+ {"mystrtokdq", " foo \" bar baz\" ", {"foo", "\" bar baz\""}},
+};
+
+int main(void)
+{
+ const struct testcase *tp;
+ char *actual;
+ int pass;
+ int fail;
+ int match;
+ int n;
+
+#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+
+ for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+ char *saved_input = mystrdup(tp->input);
+ char *cp = saved_input;
+
+ msg_info("RUN test case %ld %s >%s<",
+ (long) (tp - testcases), tp->action, tp->input);
+#if 0
+ msg_info("action=%s", tp->action);
+ msg_info("input=%s", tp->input);
+ for (n = 0; tp->expected[n]; tp++)
+ msg_info("expected[%d]=%s", n, tp->expected[n]);
+#endif
+
+ for (n = 0; n < EXPECT_SIZE; n++) {
+ if (strcmp(tp->action, "mystrtok") == 0) {
+ actual = mystrtok(&cp, CHARS_SPACE);
+ } else if (strcmp(tp->action, "mystrtokq") == 0) {
+ actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE);
+ } else if (strcmp(tp->action, "mystrtokdq") == 0) {
+ actual = mystrtokdq(&cp, CHARS_SPACE);
+ } else {
+ msg_panic("invalid command: %s", tp->action);
+ }
+ if ((match = (actual && tp->expected[n]) ?
+ (strcmp(actual, tp->expected[n]) == 0) :
+ (actual == tp->expected[n])) != 0) {
+ if (actual == 0) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ break;
+ }
+ } else {
+ msg_warn("expected: >%s<, got: >%s<",
+ STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual));
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ break;
+ }
+ }
+ if (n >= EXPECT_SIZE)
+ msg_panic("need to increase EXPECT_SIZE");
+ myfree(saved_input);
+ }
+ return (fail > 0);
+}
+
+#endif
diff --git a/src/util/mystrtok.ref b/src/util/mystrtok.ref
new file mode 100644
index 0000000..4f920f9
--- /dev/null
+++ b/src/util/mystrtok.ref
@@ -0,0 +1,30 @@
+unknown: RUN test case 0 mystrtok ><
+unknown: PASS test 0
+unknown: RUN test case 1 mystrtok > foo <
+unknown: PASS test 1
+unknown: RUN test case 2 mystrtok > foo bar <
+unknown: PASS test 2
+unknown: RUN test case 3 mystrtokq ><
+unknown: PASS test 3
+unknown: RUN test case 4 mystrtokq >foo bar<
+unknown: PASS test 4
+unknown: RUN test case 5 mystrtokq >{ bar } <
+unknown: PASS test 5
+unknown: RUN test case 6 mystrtokq >foo { bar } baz<
+unknown: PASS test 6
+unknown: RUN test case 7 mystrtokq >foo{ bar } baz<
+unknown: PASS test 7
+unknown: RUN test case 8 mystrtokq >foo { bar }baz<
+unknown: PASS test 8
+unknown: RUN test case 9 mystrtokdq ><
+unknown: PASS test 9
+unknown: RUN test case 10 mystrtokdq > foo <
+unknown: PASS test 10
+unknown: RUN test case 11 mystrtokdq > foo bar <
+unknown: PASS test 11
+unknown: RUN test case 12 mystrtokdq > foo\ bar <
+unknown: PASS test 12
+unknown: RUN test case 13 mystrtokdq > foo \" bar<
+unknown: PASS test 13
+unknown: RUN test case 14 mystrtokdq > foo " bar baz" <
+unknown: PASS test 14
diff --git a/src/util/name_code.c b/src/util/name_code.c
new file mode 100644
index 0000000..ca6d94a
--- /dev/null
+++ b/src/util/name_code.c
@@ -0,0 +1,91 @@
+/*++
+/* NAME
+/* name_code 3
+/* SUMMARY
+/* name to number table mapping
+/* SYNOPSIS
+/* #include <name_code.h>
+/*
+/* typedef struct {
+/* .in +4
+/* const char *name;
+/* int code;
+/* .in -4
+/* } NAME_CODE;
+/*
+/* int name_code(table, flags, name)
+/* const NAME_CODE *table;
+/* int flags;
+/* const char *name;
+/*
+/* const char *str_name_code(table, code)
+/* const NAME_CODE *table;
+/* int code;
+/* DESCRIPTION
+/* This module does simple name<->number mapping. The process
+/* is controlled by a table of (name, code) values.
+/* The table is terminated with a null pointer and a code that
+/* corresponds to "name not found".
+/*
+/* name_code() looks up the code that corresponds with the name.
+/* The lookup is case insensitive. The flags argument specifies
+/* zero or more of the following:
+/* .IP NAME_CODE_FLAG_STRICT_CASE
+/* String lookups are case sensitive.
+/* .PP
+/* For convenience the constant NAME_CODE_FLAG_NONE requests
+/* no special processing.
+/*
+/* str_name_code() translates a number to its equivalent string.
+/* DIAGNOSTICS
+/* When the search fails, the result is the "name not found" code
+/* or the null pointer, respectively.
+/* 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 <name_code.h>
+
+/* name_code - look up code by name */
+
+int name_code(const NAME_CODE *table, int flags, const char *name)
+{
+ const NAME_CODE *np;
+ int (*lookup) (const char *, const char *);
+
+ if (flags & NAME_CODE_FLAG_STRICT_CASE)
+ lookup = strcmp;
+ else
+ lookup = strcasecmp;
+
+ for (np = table; np->name; np++)
+ if (lookup(name, np->name) == 0)
+ break;
+ return (np->code);
+}
+
+/* str_name_code - look up name by code */
+
+const char *str_name_code(const NAME_CODE *table, int code)
+{
+ const NAME_CODE *np;
+
+ for (np = table; np->name; np++)
+ if (code == np->code)
+ break;
+ return (np->name);
+}
diff --git a/src/util/name_code.h b/src/util/name_code.h
new file mode 100644
index 0000000..e08618d
--- /dev/null
+++ b/src/util/name_code.h
@@ -0,0 +1,39 @@
+#ifndef _NAME_CODE_H_INCLUDED_
+#define _NAME_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* name_mask 3h
+/* SUMMARY
+/* name to number table mapping
+/* SYNOPSIS
+/* #include <name_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *name;
+ int code;
+} NAME_CODE;
+
+#define NAME_CODE_FLAG_NONE 0
+#define NAME_CODE_FLAG_STRICT_CASE (1<<0)
+
+extern int name_code(const NAME_CODE *, int, const char *);
+extern const char *str_name_code(const NAME_CODE *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/name_mask.c b/src/util/name_mask.c
new file mode 100644
index 0000000..284d4fa
--- /dev/null
+++ b/src/util/name_mask.c
@@ -0,0 +1,511 @@
+/*++
+/* NAME
+/* name_mask 3
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <name_mask.h>
+/*
+/* int name_mask(context, table, names)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/*
+/* long long_name_mask(context, table, names)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/*
+/* const char *str_name_mask(context, table, mask)
+/* const char *context;
+/* const NAME_MASK *table;
+/* int mask;
+/*
+/* const char *str_long_name_mask(context, table, mask)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* long mask;
+/*
+/* int name_mask_opt(context, table, names, flags)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/* int flags;
+/*
+/* long long_name_mask_opt(context, table, names, flags)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/* int flags;
+/*
+/* int name_mask_delim_opt(context, table, names, delim, flags)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/* const char *delim;
+/* int flags;
+/*
+/* long long_name_mask_delim_opt(context, table, names, delim, flags)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/* const char *delim;
+/* int flags;
+/*
+/* const char *str_name_mask_opt(buf, context, table, mask, flags)
+/* VSTRING *buf;
+/* const char *context;
+/* const NAME_MASK *table;
+/* int mask;
+/* int flags;
+/*
+/* const char *str_long_name_mask_opt(buf, context, table, mask, flags)
+/* VSTRING *buf;
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* long mask;
+/* int flags;
+/* DESCRIPTION
+/* name_mask() takes a null-terminated \fItable\fR with (name, mask)
+/* values and computes the bit-wise OR of the masks that correspond
+/* to the names listed in the \fInames\fR argument, separated by
+/* comma and/or whitespace characters. The "long_" version returns
+/* a "long int" bitmask, rather than an "int" bitmask.
+/*
+/* str_name_mask() translates a mask into its equivalent names.
+/* The result is written to a static buffer that is overwritten
+/* upon each call. The "long_" version converts a "long int"
+/* bitmask, rather than an "int" bitmask.
+/*
+/* name_mask_opt() and str_name_mask_opt() are extended versions
+/* with additional fine control. name_mask_delim_opt() supports
+/* non-default delimiter characters.
+/*
+/* Arguments:
+/* .IP buf
+/* Null pointer or pointer to buffer storage.
+/* .IP context
+/* What kind of names and
+/* masks are being manipulated, in order to make error messages
+/* more understandable. Typically, this would be the name of a
+/* user-configurable parameter.
+/* .IP table
+/* Table with (name, bit mask) pairs.
+/* .IP names
+/* A list of names that is to be converted into a bit mask.
+/* .IP mask
+/* A bit mask.
+/* .IP delim
+/* Delimiter characters to use instead of whitespace and commas.
+/* .IP flags
+/* Bit-wise OR of one or more of the following. Where features
+/* would have conflicting results (e.g., FATAL versus IGNORE),
+/* the feature that takes precedence is described first.
+/*
+/* When converting from string to mask, at least one of the
+/* following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN,
+/* NAME_MASK_WARN or NAME_MASK_IGNORE.
+/*
+/* When converting from mask to string, at least one of the
+/* following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL,
+/* NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE.
+/* .RS
+/* .IP NAME_MASK_NUMBER
+/* When converting from string to mask, accept hexadecimal
+/* inputs starting with "0x" followed by hexadecimal digits.
+/* Each hexadecimal input may specify multiple bits. This
+/* feature is ignored for hexadecimal inputs that cannot be
+/* converted (malformed, out of range, etc.).
+/*
+/* When converting from mask to string, represent bits not
+/* defined in \fItable\fR as "0x" followed by hexadecimal
+/* digits. This conversion always succeeds.
+/* .IP NAME_MASK_FATAL
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Terminate with a fatal run-time error if this
+/* condition is not met. This feature is enabled by default
+/* when calling name_mask() or str_name_mask().
+/* .IP NAME_MASK_RETURN
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Log a warning, and return 0 (name_mask()) or a
+/* null pointer (str_name_mask()) if this condition is not
+/* met. This feature is not enabled by default when calling
+/* name_mask() or str_name_mask().
+/* .IP NAME_MASK_WARN
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Log a warning if this condition is not met, continue
+/* processing, and return all valid bits or names. This feature
+/* is not enabled by default when calling name_mask() or
+/* str_name_mask().
+/* .IP NAME_MASK_IGNORE
+/* Silently ignore names listed in \fIname\fR that don't exist
+/* in \fItable\fR and that can't be parsed as a hexadecimal
+/* string, and silently ignore bits listed in \fImask\fR that
+/* don't exist in \fItable\fR and that can't be converted to
+/* hexadecimal string.
+/* .IP NAME_MASK_ANY_CASE
+/* Enable case-insensitive matching.
+/* This feature is not enabled by default when calling name_mask();
+/* it has no effect with str_name_mask().
+/* .IP NAME_MASK_COMMA
+/* Use comma instead of space when converting a mask to string.
+/* .IP NAME_MASK_PIPE
+/* Use "|" instead of space when converting a mask to string.
+/* .RE
+/* The value NAME_MASK_NONE explicitly requests no features,
+/* and NAME_MASK_DEFAULT enables the default options.
+/* DIAGNOSTICS
+/* Fatal: the \fInames\fR argument specifies a name not found in
+/* \fItable\fR, or the \fImask\fR specifies a bit not found in
+/* \fItable\fR.
+/* 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>
+#include <errno.h>
+#include <stdlib.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <name_mask.h>
+#include <vstring.h>
+
+static int hex_to_ulong(char *, unsigned long, unsigned long *);
+
+#define STR(x) vstring_str(x)
+
+/* name_mask_delim_opt - compute mask corresponding to list of names */
+
+int name_mask_delim_opt(const char *context, const NAME_MASK *table,
+ const char *names, const char *delim, int flags)
+{
+ const char *myname = "name_mask";
+ char *saved_names = mystrdup(names);
+ char *bp = saved_names;
+ int result = 0;
+ const NAME_MASK *np;
+ char *name;
+ int (*lookup) (const char *, const char *);
+ unsigned long ulval;
+
+ if ((flags & NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (flags & NAME_MASK_ANY_CASE)
+ lookup = strcasecmp;
+ else
+ lookup = strcmp;
+
+ /*
+ * Break up the names string, and look up each component in the table. If
+ * the name is found, merge its mask with the result.
+ */
+ while ((name = mystrtok(&bp, delim)) != 0) {
+ for (np = table; /* void */ ; np++) {
+ if (np->name == 0) {
+ if ((flags & NAME_MASK_NUMBER)
+ && hex_to_ulong(name, ~0U, &ulval)) {
+ result |= (unsigned int) ulval;
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ myfree(saved_names);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ }
+ break;
+ }
+ if (lookup(name, np->name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+ myfree(saved_names);
+ return (result);
+}
+
+/* str_name_mask_opt - mask to string */
+
+const char *str_name_mask_opt(VSTRING *buf, const char *context,
+ const NAME_MASK *table,
+ int mask, int flags)
+{
+ const char *myname = "name_mask";
+ const NAME_MASK *np;
+ ssize_t len;
+ static VSTRING *my_buf = 0;
+ int delim = (flags & NAME_MASK_COMMA ? ',' :
+ (flags & NAME_MASK_PIPE ? '|' : ' '));
+
+ if ((flags & STR_NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->name == 0) {
+ if (flags & NAME_MASK_NUMBER) {
+ vstring_sprintf_append(buf, "0x%x%c", mask, delim);
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%s%c", np->name, delim);
+ }
+ }
+ if ((len = VSTRING_LEN(buf)) > 0)
+ vstring_truncate(buf, len - 1);
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+/* long_name_mask_delim_opt - compute mask corresponding to list of names */
+
+long long_name_mask_delim_opt(const char *context,
+ const LONG_NAME_MASK * table,
+ const char *names, const char *delim,
+ int flags)
+{
+ const char *myname = "name_mask";
+ char *saved_names = mystrdup(names);
+ char *bp = saved_names;
+ long result = 0;
+ const LONG_NAME_MASK *np;
+ char *name;
+ int (*lookup) (const char *, const char *);
+ unsigned long ulval;
+
+ if ((flags & NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (flags & NAME_MASK_ANY_CASE)
+ lookup = strcasecmp;
+ else
+ lookup = strcmp;
+
+ /*
+ * Break up the names string, and look up each component in the table. If
+ * the name is found, merge its mask with the result.
+ */
+ while ((name = mystrtok(&bp, delim)) != 0) {
+ for (np = table; /* void */ ; np++) {
+ if (np->name == 0) {
+ if ((flags & NAME_MASK_NUMBER)
+ && hex_to_ulong(name, ~0UL, &ulval)) {
+ result |= ulval;
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ myfree(saved_names);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ }
+ break;
+ }
+ if (lookup(name, np->name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+
+ myfree(saved_names);
+ return (result);
+}
+
+/* str_long_name_mask_opt - mask to string */
+
+const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
+ const LONG_NAME_MASK * table,
+ long mask, int flags)
+{
+ const char *myname = "name_mask";
+ ssize_t len;
+ static VSTRING *my_buf = 0;
+ int delim = (flags & NAME_MASK_COMMA ? ',' :
+ (flags & NAME_MASK_PIPE ? '|' : ' '));
+ const LONG_NAME_MASK *np;
+
+ if ((flags & STR_NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->name == 0) {
+ if (flags & NAME_MASK_NUMBER) {
+ vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%s%c", np->name, delim);
+ }
+ }
+ if ((len = VSTRING_LEN(buf)) > 0)
+ vstring_truncate(buf, len - 1);
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+/* hex_to_ulong - 0x... to unsigned long or smaller */
+
+static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp)
+{
+ unsigned long result;
+ char *cp;
+
+ if (strncasecmp(value, "0x", 2) != 0)
+ return (0);
+
+ /*
+ * Check for valid hex number. Since the value starts with 0x, strtoul()
+ * will not allow a negative sign before the first nibble. So we don't
+ * need to worry about explicit +/- signs.
+ */
+ errno = 0;
+ result = strtoul(value, &cp, 16);
+ if (*cp != '\0' || errno == ERANGE)
+ return (0);
+
+ *ulp = (result & mask);
+ return (*ulp == result);
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program.
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ static const NAME_MASK demo_table[] = {
+ "zero", 1 << 0,
+ "one", 1 << 1,
+ "two", 1 << 2,
+ "three", 1 << 3,
+ 0, 0,
+ };
+ static const NAME_MASK feature_table[] = {
+ "DEFAULT", NAME_MASK_DEFAULT,
+ "FATAL", NAME_MASK_FATAL,
+ "ANY_CASE", NAME_MASK_ANY_CASE,
+ "RETURN", NAME_MASK_RETURN,
+ "COMMA", NAME_MASK_COMMA,
+ "PIPE", NAME_MASK_PIPE,
+ "NUMBER", NAME_MASK_NUMBER,
+ "WARN", NAME_MASK_WARN,
+ "IGNORE", NAME_MASK_IGNORE,
+ 0,
+ };
+ int in_feature_mask;
+ int out_feature_mask;
+ int demo_mask;
+ const char *demo_str;
+ VSTRING *out_buf = vstring_alloc(1);
+ VSTRING *in_buf = vstring_alloc(1);
+
+ if (argc != 3)
+ msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
+ in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
+ out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
+ while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
+ demo_mask = name_mask_opt("name", demo_table,
+ STR(in_buf), in_feature_mask);
+ demo_str = str_name_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("%s -> 0x%x -> %s\n",
+ STR(in_buf), demo_mask,
+ demo_str ? demo_str : "(null)");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in_buf);
+ vstring_free(out_buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/name_mask.h b/src/util/name_mask.h
new file mode 100644
index 0000000..05e45ec
--- /dev/null
+++ b/src/util/name_mask.h
@@ -0,0 +1,86 @@
+#ifndef _NAME_MASK_H_INCLUDED_
+#define _NAME_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* name_mask 3h
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <name_mask.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *name;
+ int mask;
+} NAME_MASK;
+
+#define NAME_MASK_FATAL (1<<0)
+#define NAME_MASK_ANY_CASE (1<<1)
+#define NAME_MASK_RETURN (1<<2)
+#define NAME_MASK_COMMA (1<<3)
+#define NAME_MASK_PIPE (1<<4)
+#define NAME_MASK_NUMBER (1<<5)
+#define NAME_MASK_WARN (1<<6)
+#define NAME_MASK_IGNORE (1<<7)
+
+#define NAME_MASK_REQUIRED \
+ (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE)
+#define STR_NAME_MASK_REQUIRED (NAME_MASK_REQUIRED | NAME_MASK_NUMBER)
+
+#define NAME_MASK_MATCH_REQ NAME_MASK_FATAL
+
+#define NAME_MASK_NONE 0
+#define NAME_MASK_DEFAULT (NAME_MASK_FATAL)
+#define NAME_MASK_DEFAULT_DELIM ", \t\r\n"
+
+#define name_mask_opt(tag, table, str, flags) \
+ name_mask_delim_opt((tag), (table), (str), \
+ NAME_MASK_DEFAULT_DELIM, (flags))
+#define name_mask(tag, table, str) \
+ name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT)
+#define str_name_mask(tag, table, mask) \
+ str_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT)
+
+extern int name_mask_delim_opt(const char *, const NAME_MASK *, const char *, const char *, int);
+extern const char *str_name_mask_opt(VSTRING *, const char *, const NAME_MASK *, int, int);
+
+ /*
+ * "long" API
+ */
+typedef struct {
+ const char *name;
+ long mask;
+} LONG_NAME_MASK;
+
+#define long_name_mask_opt(tag, table, str, flags) \
+ long_name_mask_delim_opt((tag), (table), (str), NAME_MASK_DEFAULT_DELIM, (flags))
+#define long_name_mask(tag, table, str) \
+ long_name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT)
+#define str_long_name_mask(tag, table, mask) \
+ str_long_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT)
+
+extern long long_name_mask_delim_opt(const char *, const LONG_NAME_MASK *, const char *, const char *, int);
+extern const char *str_long_name_mask_opt(VSTRING *, const char *, const LONG_NAME_MASK *, long, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/name_mask.in b/src/util/name_mask.in
new file mode 100644
index 0000000..d4c9b16
--- /dev/null
+++ b/src/util/name_mask.in
@@ -0,0 +1,9 @@
+zero
+one
+two
+three
+four
+zero one two three four
+0xff
+0xffffffff
+0xffffffffffffffff
diff --git a/src/util/name_mask.ref0 b/src/util/name_mask.ref0
new file mode 100644
index 0000000..603dc26
--- /dev/null
+++ b/src/util/name_mask.ref0
@@ -0,0 +1,9 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+four -> 0x0 ->
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0x0 ->
+0xffffffff -> 0x0 ->
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref1 b/src/util/name_mask.ref1
new file mode 100644
index 0000000..1ae0fca
--- /dev/null
+++ b/src/util/name_mask.ref1
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0xff -> zero one two three 0xf0
+0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref2 b/src/util/name_mask.ref2
new file mode 100644
index 0000000..1ac716a
--- /dev/null
+++ b/src/util/name_mask.ref2
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0x0 ->
+0xff -> 0xff -> zero one two three 0xf0
+0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref3 b/src/util/name_mask.ref3
new file mode 100644
index 0000000..82e8850
--- /dev/null
+++ b/src/util/name_mask.ref3
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: unknown name value "0xff" in "0xff"
+0xff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffff" in "0xffffffff"
+0xffffffff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref4 b/src/util/name_mask.ref4
new file mode 100644
index 0000000..4ec996b
--- /dev/null
+++ b/src/util/name_mask.ref4
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0x0 ->
+unknown: warning: unknown name value "0xff" in "0xff"
+0xff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffff" in "0xffffffff"
+0xffffffff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref5 b/src/util/name_mask.ref5
new file mode 100644
index 0000000..68c1c30
--- /dev/null
+++ b/src/util/name_mask.ref5
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xf0
+0xff -> 0xff -> (null)
+unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0
+0xffffffff -> 0xffffffff -> (null)
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref6 b/src/util/name_mask.ref6
new file mode 100644
index 0000000..c86a532
--- /dev/null
+++ b/src/util/name_mask.ref6
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xf0
+0xff -> 0xff -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0
+0xffffffff -> 0xffffffff -> zero one two three
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref7 b/src/util/name_mask.ref7
new file mode 100644
index 0000000..156d7aa
--- /dev/null
+++ b/src/util/name_mask.ref7
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0xff -> zero one two three
+0xffffffff -> 0xffffffff -> zero one two three
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref8 b/src/util/name_mask.ref8
new file mode 100644
index 0000000..7463fb7
--- /dev/null
+++ b/src/util/name_mask.ref8
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero,one,two,three
+0xff -> 0xff -> zero,one,two,three,0xf0
+0xffffffff -> 0xffffffff -> zero,one,two,three,0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref9 b/src/util/name_mask.ref9
new file mode 100644
index 0000000..e50a73a
--- /dev/null
+++ b/src/util/name_mask.ref9
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero|one|two|three
+0xff -> 0xff -> zero|one|two|three|0xf0
+0xffffffff -> 0xffffffff -> zero|one|two|three|0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/nbbio.c b/src/util/nbbio.c
new file mode 100644
index 0000000..e9ccc38
--- /dev/null
+++ b/src/util/nbbio.c
@@ -0,0 +1,382 @@
+/*++
+/* NAME
+/* nbbio 3
+/* SUMMARY
+/* non-blocking buffered I/O
+/* SYNOPSIS
+/* #include <nbbio.h>
+/*
+/* NBBIO *nbbio_create(fd, bufsize, label, action, context)
+/* int fd;
+/* ssize_t bufsize;
+/* const char *label;
+/* void (*action)(int event, void *context);
+/* char *context;
+/*
+/* void nbbio_free(np)
+/* NBBIO *np;
+/*
+/* void nbbio_enable_read(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* void nbbio_enable_write(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* void nbbio_disable_readwrite(np)
+/* NBBIO *np;
+/*
+/* void nbbio_slumber(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* int NBBIO_ACTIVE_FLAGS(np)
+/* NBBIO *np;
+/*
+/* int NBBIO_ERROR_FLAGS(np)
+/* NBBIO *np;
+/*
+/* const ssize_t NBBIO_BUFSIZE(np)
+/* NBBIO *np;
+/*
+/* ssize_t NBBIO_READ_PEND(np)
+/* NBBIO *np;
+/*
+/* char *NBBIO_READ_BUF(np)
+/* NBBIO *np;
+/*
+/* const ssize_t NBBIO_WRITE_PEND(np)
+/* NBBIO *np;
+/*
+/* char *NBBIO_WRITE_BUF(np)
+/* NBBIO *np;
+/* DESCRIPTION
+/* This module implements low-level support for event-driven
+/* I/O on a full-duplex stream. Read/write events are handled
+/* by pseudothreads that run under control by the events(5)
+/* module. After each I/O operation, the application is
+/* notified via a call-back routine.
+/*
+/* It is up to the call-back routine to turn on/off read/write
+/* events as appropriate. It is an error to leave read events
+/* enabled for a buffer that is full, or to leave write events
+/* enabled for a buffer that is empty.
+/*
+/* nbbio_create() creates a pair of buffers of the named size
+/* for the named stream. The label specifies the purpose of
+/* the stream, and is used for diagnostic messages. The
+/* nbbio(3) event handler invokes the application call-back
+/* routine with the current event type (EVENT_READ etc.) and
+/* with the application-specified context.
+/*
+/* nbbio_free() terminates any pseudothreads associated with
+/* the named buffer pair, closes the stream, and destroys the
+/* buffer pair.
+/*
+/* nbbio_enable_read() enables a read pseudothread (if one
+/* does not already exist) for the named buffer pair, and
+/* (re)starts the buffer pair's timer. It is an error to enable
+/* a read pseudothread while the read buffer is full, or while
+/* a write pseudothread is still enabled.
+/*
+/* nbbio_enable_write() enables a write pseudothread (if one
+/* does not already exist) for the named buffer pair, and
+/* (re)starts the buffer pair's timer. It is an error to enable
+/* a write pseudothread while the write buffer is empty, or
+/* while a read pseudothread is still enabled.
+/*
+/* nbbio_disable_readwrite() disables any read/write pseudothreads
+/* for the named buffer pair, including timeouts. To ensure
+/* buffer liveness, use nbbio_slumber() instead of
+/* nbbio_disable_readwrite(). It is no error to call this
+/* function while no read/write pseudothread is enabled.
+/*
+/* nbbio_slumber() disables any read/write pseudothreads for
+/* the named buffer pair, but keeps the timer active to ensure
+/* buffer liveness. It is no error to call this function while
+/* no read/write pseudothread is enabled.
+/*
+/* NBBIO_ERROR_FLAGS() returns the error flags for the named buffer
+/* pair: zero or more of NBBIO_FLAG_EOF (read EOF), NBBIO_FLAG_ERROR
+/* (read/write error) or NBBIO_FLAG_TIMEOUT (time limit
+/* exceeded).
+/*
+/* NBBIO_ACTIVE_FLAGS() returns the pseudothread flags for the
+/* named buffer pair: NBBIO_FLAG_READ (read pseudothread is
+/* active), NBBIO_FLAG_WRITE (write pseudothread is active),
+/* or zero (no pseudothread is active).
+/*
+/* NBBIO_WRITE_PEND() and NBBIO_WRITE_BUF() evaluate to the
+/* number of to-be-written bytes and the write buffer for the
+/* named buffer pair. NBBIO_WRITE_PEND() must be updated by
+/* the application code that fills the write buffer; no more
+/* than NBBIO_BUFSIZE() bytes may be filled.
+/*
+/* NBBIO_READ_PEND() and NBBIO_READ_BUF() evaluate to the
+/* number of unread bytes and the read buffer for the named
+/* buffer pair. NBBIO_READ_PEND() and NBBIO_READ_BUF() must
+/* be updated by the application code that drains the read
+/* buffer.
+/* SEE ALSO
+/* events(3) event manager
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* Fatal: out of memory.
+/* 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 <unistd.h>
+#include <errno.h>
+#include <string.h> /* memmove() */
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <events.h>
+#include <nbbio.h>
+
+/* nbbio_event - non-blocking event handler */
+
+static void nbbio_event(int event, void *context)
+{
+ const char *myname = "nbbio_event";
+ NBBIO *np = (NBBIO *) context;
+ ssize_t count;
+
+ switch (event) {
+
+ /*
+ * Read data into the read buffer. Leave it up to the application to
+ * drain the buffer until it is empty.
+ */
+ case EVENT_READ:
+ if (np->read_pend == np->bufsize)
+ msg_panic("%s: socket fd=%d: read buffer is full",
+ myname, np->fd);
+ if (np->read_pend < 0 || np->read_pend > np->bufsize)
+ msg_panic("%s: socket fd=%d: bad pending read count %ld",
+ myname, np->fd, (long) np->read_pend);
+ count = read(np->fd, np->read_buf + np->read_pend,
+ np->bufsize - np->read_pend);
+ if (count > 0) {
+ np->read_pend += count;
+ if (msg_verbose)
+ msg_info("%s: read %ld on %s fd=%d",
+ myname, (long) count, np->label, np->fd);
+ } else if (count == 0) {
+ np->flags |= NBBIO_FLAG_EOF;
+ if (msg_verbose)
+ msg_info("%s: read EOF on %s fd=%d",
+ myname, np->label, np->fd);
+ } else {
+ if (errno == EAGAIN)
+ msg_warn("%s: read() returns EAGAIN on readable descriptor",
+ myname);
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: read %s fd=%d: %m", myname, np->label, np->fd);
+ }
+ break;
+
+ /*
+ * Drain data from the output buffer. Notify the application
+ * whenever some bytes are written.
+ *
+ * XXX Enforce a total time limit to ensure liveness when a hostile
+ * receiver sets a very small TCP window size.
+ */
+ case EVENT_WRITE:
+ if (np->write_pend == 0)
+ msg_panic("%s: socket fd=%d: empty write buffer", myname, np->fd);
+ if (np->write_pend < 0 || np->write_pend > np->bufsize)
+ msg_panic("%s: socket fd=%d: bad pending write count %ld",
+ myname, np->fd, (long) np->write_pend);
+ count = write(np->fd, np->write_buf, np->write_pend);
+ if (count > 0) {
+ np->write_pend -= count;
+ if (np->write_pend > 0)
+ memmove(np->write_buf, np->write_buf + count, np->write_pend);
+ } else {
+ if (errno == EAGAIN)
+ msg_warn("%s: write() returns EAGAIN on writable descriptor",
+ myname);
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: write %s fd=%d: %m", myname, np->label, np->fd);
+ }
+ break;
+
+ /*
+ * Something bad happened.
+ */
+ case EVENT_XCPT:
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: error on %s fd=%d: %m", myname, np->label, np->fd);
+ break;
+
+ /*
+ * Something good didn't happen.
+ */
+ case EVENT_TIME:
+ np->flags |= NBBIO_FLAG_TIMEOUT;
+ if (msg_verbose)
+ msg_info("%s: %s timeout on %s fd=%d",
+ myname, NBBIO_OP_NAME(np), np->label, np->fd);
+ break;
+
+ default:
+ msg_panic("%s: unknown event %d", myname, event);
+ }
+
+ /*
+ * Application notification. The application will check for any error
+ * flags, copy application data from or to our buffer pair, and decide
+ * what I/O happens next.
+ */
+ np->action(event, np->context);
+}
+
+/* nbbio_enable_read - enable reading from socket into buffer */
+
+void nbbio_enable_read(NBBIO *np, int timeout)
+{
+ const char *myname = "nbbio_enable_read";
+
+ /*
+ * Sanity checks.
+ */
+ if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_READ))
+ msg_panic("%s: socket fd=%d is enabled for %s",
+ myname, np->fd, NBBIO_OP_NAME(np));
+ if (timeout <= 0)
+ msg_panic("%s: socket fd=%d: bad timeout %d",
+ myname, np->fd, timeout);
+ if (np->read_pend >= np->bufsize)
+ msg_panic("%s: socket fd=%d: read buffer is full",
+ myname, np->fd);
+
+ /*
+ * Enable events.
+ */
+ if ((np->flags & NBBIO_FLAG_READ) == 0) {
+ event_enable_read(np->fd, nbbio_event, (void *) np);
+ np->flags |= NBBIO_FLAG_READ;
+ }
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_enable_write - enable writing from buffer to socket */
+
+void nbbio_enable_write(NBBIO *np, int timeout)
+{
+ const char *myname = "nbbio_enable_write";
+
+ /*
+ * Sanity checks.
+ */
+ if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_WRITE))
+ msg_panic("%s: socket fd=%d is enabled for %s",
+ myname, np->fd, NBBIO_OP_NAME(np));
+ if (timeout <= 0)
+ msg_panic("%s: socket fd=%d: bad timeout %d",
+ myname, np->fd, timeout);
+ if (np->write_pend <= 0)
+ msg_panic("%s: socket fd=%d: empty write buffer",
+ myname, np->fd);
+
+ /*
+ * Enable events.
+ */
+ if ((np->flags & NBBIO_FLAG_WRITE) == 0) {
+ event_enable_write(np->fd, nbbio_event, (void *) np);
+ np->flags |= NBBIO_FLAG_WRITE;
+ }
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_disable_readwrite - disable read/write/timer events */
+
+void nbbio_disable_readwrite(NBBIO *np)
+{
+ np->flags &= ~NBBIO_MASK_ACTIVE;
+ event_disable_readwrite(np->fd);
+ event_cancel_timer(nbbio_event, (void *) np);
+}
+
+/* nbbio_slumber - disable read/write events, keep timer */
+
+void nbbio_slumber(NBBIO *np, int timeout)
+{
+ np->flags &= ~NBBIO_MASK_ACTIVE;
+ event_disable_readwrite(np->fd);
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_create - create socket buffer */
+
+NBBIO *nbbio_create(int fd, ssize_t bufsize, const char *label,
+ NBBIO_ACTION action, void *context)
+{
+ NBBIO *np;
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0)
+ msg_panic("nbbio_create: bad file descriptor: %d", fd);
+ if (bufsize <= 0)
+ msg_panic("nbbio_create: bad buffer size: %ld", (long) bufsize);
+
+ /*
+ * Create a new buffer pair.
+ */
+ np = (NBBIO *) mymalloc(sizeof(*np));
+ np->fd = fd;
+ np->bufsize = bufsize;
+ np->label = mystrdup(label);
+ np->action = action;
+ np->context = context;
+ np->flags = 0;
+
+ np->read_buf = mymalloc(bufsize);
+ np->read_pend = 0;
+
+ np->write_buf = mymalloc(bufsize);
+ np->write_pend = 0;
+
+ return (np);
+}
+
+/* nbbio_free - destroy socket buffer */
+
+void nbbio_free(NBBIO *np)
+{
+ nbbio_disable_readwrite(np);
+ (void) close(np->fd);
+ myfree(np->label);
+ myfree(np->read_buf);
+ myfree(np->write_buf);
+ myfree((void *) np);
+}
diff --git a/src/util/nbbio.h b/src/util/nbbio.h
new file mode 100644
index 0000000..d1d2b0e
--- /dev/null
+++ b/src/util/nbbio.h
@@ -0,0 +1,85 @@
+#ifndef _NBBIO_H_INCLUDED_
+#define _NBBIO_H_INCLUDED_
+
+/*++
+/* NAME
+/* nbbio 3h
+/* SUMMARY
+/* non-blocking buffered I/O
+/* SYNOPSIS
+/* #include "nbbio.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <events.h> /* Needed for EVENT_READ etc. */
+
+ /*
+ * External interface. All structure members are private.
+ */
+typedef void (*NBBIO_ACTION) (int, void *);
+
+typedef struct {
+ int fd; /* socket file descriptor */
+ ssize_t bufsize; /* read/write buffer size */
+ char *label; /* diagnostics */
+ NBBIO_ACTION action; /* call-back routine */
+ void *context; /* call-back context */
+ int flags; /* buffer-pair status */
+
+ char *read_buf; /* start of buffer */
+ ssize_t read_pend; /* nr of unread bytes */
+
+ char *write_buf; /* start of buffer */
+ ssize_t write_pend; /* nr of unwritten bytes */
+} NBBIO;
+
+#define NBBIO_FLAG_READ (1<<0)
+#define NBBIO_FLAG_WRITE (1<<1)
+#define NBBIO_FLAG_EOF (1<<2)
+#define NBBIO_FLAG_ERROR (1<<3)
+#define NBBIO_FLAG_TIMEOUT (1<<4)
+
+#define NBBIO_OP_NAME(np) \
+ (((np)->flags & NBBIO_FLAG_READ) ? "read" : \
+ ((np)->flags & NBBIO_FLAG_WRITE) ? "write" : \
+ "unknown")
+
+#define NBBIO_MASK_ACTIVE \
+ (NBBIO_FLAG_READ | NBBIO_FLAG_WRITE)
+
+#define NBBIO_MASK_ERROR \
+ (NBBIO_FLAG_EOF | NBBIO_FLAG_ERROR | NBBIO_FLAG_TIMEOUT)
+
+#define NBBIO_BUFSIZE(np) (((np)->bufsize) + 0) /* Read-only */
+
+#define NBBIO_READ_PEND(np) ((np)->read_pend)
+#define NBBIO_READ_BUF(np) ((np)->read_buf + 0) /* Read-only */
+
+#define NBBIO_WRITE_PEND(np) ((np)->write_pend)
+#define NBBIO_WRITE_BUF(np) ((np)->write_buf + 0) /* Read-only */
+
+#define NBBIO_ACTIVE_FLAGS(np) ((np)->flags & NBBIO_MASK_ACTIVE)
+#define NBBIO_ERROR_FLAGS(np) ((np)->flags & NBBIO_MASK_ERROR)
+
+extern NBBIO *nbbio_create(int, ssize_t, const char *, NBBIO_ACTION, void *);
+extern void nbbio_free(NBBIO *);
+extern void nbbio_enable_read(NBBIO *, int);
+extern void nbbio_enable_write(NBBIO *, int);
+extern void nbbio_disable_readwrite(NBBIO *);
+extern void nbbio_slumber(NBBIO *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/netstring.c b/src/util/netstring.c
new file mode 100644
index 0000000..0edd80e
--- /dev/null
+++ b/src/util/netstring.c
@@ -0,0 +1,498 @@
+/*++
+/* NAME
+/* netstring 3
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/*
+/* void netstring_setup(stream, timeout)
+/* VSTREAM *stream;
+/* int timeout;
+/*
+/* void netstring_except(stream, exception)
+/* VSTREAM *stream;
+/* int exception;
+/*
+/* const char *netstring_strerror(err)
+/* int err;
+/*
+/* VSTRING *netstring_get(stream, buf, limit)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t limit;
+/*
+/* void netstring_put(stream, data, len)
+/* VSTREAM *stream;
+/* const char *data;
+/* ssize_t len;
+/*
+/* void netstring_put_multi(stream, data, len, data, len, ..., 0)
+/* VSTREAM *stream;
+/* const char *data;
+/* ssize_t len;
+/*
+/* void NETSTRING_PUT_BUF(stream, buf)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/*
+/* void netstring_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_memcpy(buf, data, len)
+/* VSTRING *buf;
+/* const char *data;
+/* ssize_t len;
+/*
+/* VSTRING *netstring_memcat(buf, data, len)
+/* VSTRING *buf;
+/* const char *src;
+/* ssize_t len;
+/* AUXILIARY ROUTINES
+/* ssize_t netstring_get_length(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_get_data(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* void netstring_get_terminator(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module reads and writes netstrings with error detection:
+/* timeouts, unexpected end-of-file, or format errors. Netstring
+/* is a data format designed by Daniel Bernstein.
+/*
+/* netstring_setup() arranges for a time limit on the netstring
+/* read and write operations described below.
+/* This routine alters the behavior of streams as follows:
+/* .IP \(bu
+/* The read/write timeout is set to the specified value.
+/* .IP \(bu
+/* The stream is configured to enable exception handling.
+/* .PP
+/* netstring_except() raises the specified exception on the
+/* named stream. See the DIAGNOSTICS section below.
+/*
+/* netstring_strerror() converts an exception number to string.
+/*
+/* netstring_get() reads a netstring from the specified stream
+/* and extracts its content. The limit specifies a maximal size.
+/* Specify zero to disable the size limit. The result is not null
+/* terminated. The result value is the buf argument.
+/*
+/* netstring_put() encapsulates the specified string as a netstring
+/* and sends the result to the specified stream.
+/* The stream output buffer is not flushed.
+/*
+/* netstring_put_multi() encapsulates the content of multiple strings
+/* as one netstring and sends the result to the specified stream. The
+/* argument list must be terminated with a null data pointer.
+/* The stream output buffer is not flushed.
+/*
+/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based
+/* wrapper for the netstring_put() routine.
+/*
+/* netstring_fflush() flushes the output buffer of the specified
+/* stream and handles any errors.
+/*
+/* netstring_memcpy() encapsulates the specified data as a netstring
+/* and copies the result over the specified buffer. The result
+/* value is the buffer.
+/*
+/* netstring_memcat() encapsulates the specified data as a netstring
+/* and appends the result to the specified buffer. The result
+/* value is the buffer.
+/*
+/* The following routines provide low-level access to a netstring
+/* stream.
+/*
+/* netstring_get_length() reads a length field from the specified
+/* stream, and absorbs the netstring length field terminator.
+/*
+/* netstring_get_data() reads the specified number of bytes from the
+/* specified stream into the specified buffer, and absorbs the
+/* netstring terminator. The result value is the buf argument.
+/*
+/* netstring_get_terminator() reads the netstring terminator from
+/* the specified stream.
+/* DIAGNOSTICS
+/* .fi
+/* .ad
+/* In case of error, a vstream_longjmp() call is performed to the
+/* caller-provided context specified with vstream_setjmp().
+/* Error codes passed along with vstream_longjmp() are:
+/* .IP NETSTRING_ERR_EOF
+/* An I/O error happened, or the peer has disconnected unexpectedly.
+/* .IP NETSTRING_ERR_TIME
+/* The time limit specified to netstring_setup() was exceeded.
+/* .IP NETSTRING_ERR_FORMAT
+/* The input contains an unexpected character value.
+/* .IP NETSTRING_ERR_SIZE
+/* The input is larger than acceptable.
+/* BUGS
+/* The timeout deadline affects all I/O on the named stream, not
+/* just the I/O done on behalf of this module.
+/*
+/* The timeout deadline overwrites any previously set up state on
+/* the named stream.
+/*
+/* netstrings are not null terminated, which makes printing them
+/* a bit awkward.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* http://cr.yp.to/proto/netstrings.txt, netstring definition
+/* 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 <stdarg.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <compat_va_copy.h>
+#include <netstring.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* netstring_setup - initialize netstring stream */
+
+void netstring_setup(VSTREAM *stream, int timeout)
+{
+ vstream_control(stream,
+ CA_VSTREAM_CTL_TIMEOUT(timeout),
+ CA_VSTREAM_CTL_EXCEPT,
+ CA_VSTREAM_CTL_END);
+}
+
+/* netstring_except - process netstring stream exception */
+
+void netstring_except(VSTREAM *stream, int exception)
+{
+ vstream_longjmp(stream, exception);
+}
+
+/* netstring_get_length - read netstring length + terminator */
+
+ssize_t netstring_get_length(VSTREAM *stream)
+{
+ const char *myname = "netstring_get_length";
+ ssize_t len = 0;
+ int ch;
+ int digit;
+
+ for (;;) {
+ switch (ch = VSTREAM_GETC(stream)) {
+ case VSTREAM_EOF:
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ case ':':
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring length %ld", myname, (long) len);
+ return (len);
+ default:
+ if (!ISDIGIT(ch))
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+ digit = ch - '0';
+ if (len > SSIZE_T_MAX / 10
+ || (len *= 10) > SSIZE_T_MAX - digit)
+ netstring_except(stream, NETSTRING_ERR_SIZE);
+ len += digit;
+ break;
+ }
+ }
+}
+
+/* netstring_get_data - read netstring payload + terminator */
+
+VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len)
+{
+ const char *myname = "netstring_get_data";
+
+ /*
+ * Read the payload and absorb the terminator.
+ */
+ if (vstream_fread_buf(stream, buf, len) != len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring data %.*s",
+ myname, (int) (len < 30 ? len : 30), STR(buf));
+ netstring_get_terminator(stream);
+
+ /*
+ * Return the buffer.
+ */
+ return (buf);
+}
+
+/* netstring_get_terminator - absorb netstring terminator */
+
+void netstring_get_terminator(VSTREAM *stream)
+{
+ if (VSTREAM_GETC(stream) != ',')
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+}
+
+/* netstring_get - read string from netstring stream */
+
+VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit)
+{
+ ssize_t len;
+
+ len = netstring_get_length(stream);
+ if (ENFORCING_SIZE_LIMIT(limit) && len > limit)
+ netstring_except(stream, NETSTRING_ERR_SIZE);
+ netstring_get_data(stream, buf, len);
+ return (buf);
+}
+
+/* netstring_put - send string as netstring */
+
+void netstring_put(VSTREAM *stream, const char *data, ssize_t len)
+{
+ const char *myname = "netstring_put";
+
+ if (msg_verbose > 1)
+ msg_info("%s: write netstring len %ld data %.*s",
+ myname, (long) len, (int) (len < 30 ? len : 30), data);
+ vstream_fprintf(stream, "%ld:", (long) len);
+ vstream_fwrite(stream, data, len);
+ VSTREAM_PUTC(',', stream);
+}
+
+/* netstring_put_multi - send multiple strings as one netstring */
+
+void netstring_put_multi(VSTREAM *stream,...)
+{
+ const char *myname = "netstring_put_multi";
+ ssize_t total;
+ char *data;
+ ssize_t data_len;
+ va_list ap;
+ va_list ap2;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, stream);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Figure out the total result size.
+ */
+ for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len)
+ if ((data_len = va_arg(ap, ssize_t)) < 0)
+ msg_panic("%s: bad data length %ld", myname, (long) data_len);
+ va_end(ap);
+ if (total < 0)
+ msg_panic("%s: bad total length %ld", myname, (long) total);
+ if (msg_verbose > 1)
+ msg_info("%s: write total length %ld", myname, (long) total);
+
+ /*
+ * Send the length, content and terminator.
+ */
+ vstream_fprintf(stream, "%ld:", (long) total);
+ while ((data = va_arg(ap2, char *)) != 0) {
+ data_len = va_arg(ap2, ssize_t);
+ if (msg_verbose > 1)
+ msg_info("%s: write netstring len %ld data %.*s",
+ myname, (long) data_len,
+ (int) (data_len < 30 ? data_len : 30), data);
+ if (vstream_fwrite(stream, data, data_len) != data_len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ }
+ va_end(ap2);
+ vstream_fwrite(stream, ",", 1);
+}
+
+/* netstring_fflush - flush netstring stream */
+
+void netstring_fflush(VSTREAM *stream)
+{
+ if (vstream_fflush(stream) == VSTREAM_EOF)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+}
+
+/* netstring_memcpy - copy data as in-memory netstring */
+
+VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len)
+{
+ vstring_sprintf(buf, "%ld:", (long) len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
+
+/* netstring_memcat - append data as in-memory netstring */
+
+VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len)
+{
+ vstring_sprintf_append(buf, "%ld:", (long) len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
+
+/* netstring_strerror - convert error number to string */
+
+const char *netstring_strerror(int err)
+{
+ switch (err) {
+ case NETSTRING_ERR_EOF:
+ return ("unexpected disconnect");
+ case NETSTRING_ERR_TIME:
+ return ("time limit exceeded");
+ case NETSTRING_ERR_FORMAT:
+ return ("input format error");
+ case NETSTRING_ERR_SIZE:
+ return ("input exceeds size limit");
+ default:
+ return ("unknown netstring error");
+ }
+}
+
+ /*
+ * Proof-of-concept netstring encoder/decoder.
+ *
+ * Usage: netstring command...
+ *
+ * Run the command as a child process. Then, convert between plain strings on
+ * our own stdin/stdout, and netstrings on the child program's stdin/stdout.
+ *
+ * Example (socketmap test server): netstring nc -l 9999
+ */
+#ifdef TEST
+#include <unistd.h>
+#include <stdlib.h>
+#include <events.h>
+
+static VSTRING *stdin_read_buf; /* stdin line buffer */
+static VSTRING *child_read_buf; /* child read buffer */
+static VSTREAM *child_stream; /* child stream (full-duplex) */
+
+/* stdin_read_event - line-oriented event handler */
+
+static void stdin_read_event(int event, void *context)
+{
+ int ch;
+
+ /*
+ * Send a netstring to the child when we have accumulated an entire line
+ * of input.
+ *
+ * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM
+ * buffer. We must drain the entire VSTREAM buffer before requesting the
+ * next read(2) event.
+ */
+ do {
+ ch = VSTREAM_GETCHAR();
+ switch (ch) {
+ default:
+ VSTRING_ADDCH(stdin_read_buf, ch);
+ break;
+ case '\n':
+ NETSTRING_PUT_BUF(child_stream, stdin_read_buf);
+ vstream_fflush(child_stream);
+ VSTRING_RESET(stdin_read_buf);
+ break;
+ case VSTREAM_EOF:
+ /* Better: wait for child to terminate. */
+ sleep(1);
+ exit(0);
+ }
+ } while (vstream_peek(VSTREAM_IN) > 0);
+}
+
+/* child_read_event - netstring-oriented event handler */
+
+static void child_read_event(int event, void *context)
+{
+
+ /*
+ * Read an entire netstring from the child and send the result to stdout.
+ *
+ * This is a simplistic implementation that assumes a server will not
+ * trickle its data.
+ *
+ * Note: the first netstring_get() call implicitly fills the VSTREAM buffer.
+ * We must drain the entire VSTREAM buffer before requesting the next
+ * read(2) event.
+ */
+ do {
+ netstring_get(child_stream, child_read_buf, 10000);
+ vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ } while (vstream_peek(child_stream) > 0);
+}
+
+int main(int argc, char **argv)
+{
+ int err;
+
+ /*
+ * Sanity check.
+ */
+ if (argv[1] == 0)
+ msg_fatal("usage: %s command...", argv[0]);
+
+ /*
+ * Run the specified command as a child process with stdin and stdout
+ * connected to us.
+ */
+ child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1),
+ CA_VSTREAM_POPEN_END);
+ vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ netstring_setup(child_stream, 10);
+
+ /*
+ * Buffer plumbing.
+ */
+ stdin_read_buf = vstring_alloc(100);
+ child_read_buf = vstring_alloc(100);
+
+ /*
+ * Monitor both the child's stdout stream and our own stdin stream. If
+ * there is activity on the child stdout stream, read an entire netstring
+ * or EOF. If there is activity on stdin, send a netstring to the child
+ * when we have read an entire line, or terminate in case of EOF.
+ */
+ event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0);
+ event_enable_read(vstream_fileno(child_stream), child_read_event,
+ (void *) 0);
+
+ if ((err = vstream_setjmp(child_stream)) == 0) {
+ for (;;)
+ event_loop(-1);
+ } else {
+ msg_fatal("%s: %s", argv[1], netstring_strerror(err));
+ }
+}
+
+#endif
diff --git a/src/util/netstring.h b/src/util/netstring.h
new file mode 100644
index 0000000..e8f418d
--- /dev/null
+++ b/src/util/netstring.h
@@ -0,0 +1,55 @@
+#ifndef _NETSTRING_H_INCLUDED_
+#define _NETSTRING_H_INCLUDED_
+
+/*++
+/* NAME
+/* netstring 3h
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */
+#define NETSTRING_ERR_TIME 2 /* time out */
+#define NETSTRING_ERR_FORMAT 3 /* format error */
+#define NETSTRING_ERR_SIZE 4 /* netstring too large */
+
+extern void netstring_except(VSTREAM *, int);
+extern void netstring_setup(VSTREAM *, int);
+extern ssize_t netstring_get_length(VSTREAM *);
+extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, ssize_t);
+extern void netstring_get_terminator(VSTREAM *);
+extern VSTRING *netstring_get(VSTREAM *, VSTRING *, ssize_t);
+extern void netstring_put(VSTREAM *, const char *, ssize_t);
+extern void netstring_put_multi(VSTREAM *,...);
+extern void netstring_fflush(VSTREAM *);
+extern VSTRING *netstring_memcpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *netstring_memcat(VSTRING *, const char *, ssize_t);
+extern const char *netstring_strerror(int);
+
+#define NETSTRING_PUT_BUF(str, buf) \
+ netstring_put((str), vstring_str(buf), VSTRING_LEN(buf))
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/neuter.c b/src/util/neuter.c
new file mode 100644
index 0000000..53576fb
--- /dev/null
+++ b/src/util/neuter.c
@@ -0,0 +1,56 @@
+/*++
+/* NAME
+/* neuter 3
+/* SUMMARY
+/* neutralize characters before they can explode
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *neuter(buffer, bad, replacement)
+/* char *buffer;
+/* const char *bad;
+/* int replacement;
+/* DESCRIPTION
+/* neuter() replaces bad characters in its input
+/* by the given replacement.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP bad
+/* The null-terminated bad character string.
+/* .IP replacement
+/* Replacement value for characters in \fIbuffer\fR that do not
+/* pass the bad character test.
+/* 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 <stringops.h>
+
+/* neuter - neutralize bad characters */
+
+char *neuter(char *string, const char *bad, int replacement)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (strchr(bad, ch) != 0)
+ *cp = replacement;
+ return (string);
+}
diff --git a/src/util/non_blocking.c b/src/util/non_blocking.c
new file mode 100644
index 0000000..6427cd8
--- /dev/null
+++ b/src/util/non_blocking.c
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* non_blocking 3
+/* SUMMARY
+/* set/clear non-blocking flag
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int non_blocking(int fd, int on)
+/* DESCRIPTION
+/* the \fInon_blocking\fR() function manipulates the non-blocking
+/* flag for the specified open file, and returns the old setting.
+/*
+/* Arguments:
+/* .IP fd
+/* A file descriptor.
+/* .IP on
+/* For non-blocking I/O, specify a non-zero value (or use the
+/* NON_BLOCKING constant); for blocking I/O, specify zero
+/* (or use the BLOCKING constant).
+/*
+/* The result is non-zero when the non-blocking flag was enabled.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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 interfaces. */
+
+#include "sys_defs.h"
+#include <fcntl.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+
+/* Backwards compatibility */
+#ifndef O_NONBLOCK
+#define PATTERN FNDELAY
+#else
+#define PATTERN O_NONBLOCK
+#endif
+
+/* non_blocking - set/clear non-blocking flag */
+
+int non_blocking(fd, on)
+int fd;
+int on;
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ msg_fatal("fcntl: get flags: %m");
+ if (fcntl(fd, F_SETFL, on ? flags | PATTERN : flags & ~PATTERN) < 0)
+ msg_fatal("fcntl: set non-blocking flag %s: %m", on ? "on" : "off");
+ return ((flags & PATTERN) != 0);
+}
diff --git a/src/util/nvtable.c b/src/util/nvtable.c
new file mode 100644
index 0000000..5238e29
--- /dev/null
+++ b/src/util/nvtable.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* nvtable 3
+/* SUMMARY
+/* attribute list manager
+/* SYNOPSIS
+/* #include <nvtable.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *key;
+/* char *value;
+/* /* private fields... */
+/* .in -4
+/* } NVTABLE_INFO;
+/*
+/* NVTABLE *nvtable_create(size)
+/* int size;
+/*
+/* NVTABLE_INFO *nvtable_update(table, key, value)
+/* NVTABLE *table;
+/* const char *key;
+/* const char *value;
+/*
+/* char *nvtable_find(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* NVTABLE_INFO *nvtable_locate(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* void nvtable_delete(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* void nvtable_free(table)
+/* NVTABLE *table;
+/*
+/* void nvtable_walk(table, action, ptr)
+/* NVTABLE *table;
+/* void (*action)(NVTABLE_INFO *, char *ptr);
+/* char *ptr;
+/*
+/* NVTABLE_INFO **nvtable_list(table)
+/* NVTABLE *table;
+/* DESCRIPTION
+/* This module maintains one or more attribute lists. It provides a
+/* more convenient interface than hash tables, although it uses the
+/* same underlying implementation. Each attribute list entry consists
+/* of a unique string-valued lookup key and a string value.
+/*
+/* nvtable_create() creates a table of the specified size and returns a
+/* pointer to the result.
+/*
+/* nvtable_update() stores or updates a (key, value) pair in the specified
+/* table and returns a pointer to the resulting entry. The key and the
+/* value are copied.
+/*
+/* nvtable_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use nvtable_locate().
+/*
+/* nvtable_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* nvtable_delete() removes one entry that was stored under the given key.
+/*
+/* nvtable_free() destroys a hash table, including contents.
+/*
+/* nvtable_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* nvtable_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/* RESTRICTIONS
+/* A callback function should not modify the attribute list that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* htable(3) hash table manager
+/* 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
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <htable.h>
+#include <nvtable.h>
+
+/* nvtable_update - update or enter (key, value) pair */
+
+NVTABLE_INFO *nvtable_update(NVTABLE * table, const char *key, const char *value)
+{
+ NVTABLE_INFO *ht;
+
+ if ((ht = htable_locate(table, key)) != 0) {
+ myfree(ht->value);
+ } else {
+ ht = htable_enter(table, key, (void *) 0);
+ }
+ ht->value = mystrdup(value);
+ return (ht);
+}
diff --git a/src/util/nvtable.h b/src/util/nvtable.h
new file mode 100644
index 0000000..fed366a
--- /dev/null
+++ b/src/util/nvtable.h
@@ -0,0 +1,44 @@
+#ifndef _NVTABLE_H_INCLUDED_
+#define _NVTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* nvtable 3h
+/* SUMMARY
+/* attribute list manager
+/* SYNOPSIS
+/* #include <nvtable.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <mymalloc.h>
+
+typedef struct HTABLE NVTABLE;
+typedef struct HTABLE_INFO NVTABLE_INFO;
+
+#define nvtable_create(size) htable_create(size)
+#define nvtable_locate(table, key) htable_locate((table), (key))
+#define nvtable_walk(table, action, ptr) htable_walk((table), HTABLE_ACTION_FN_CAST(action), (ptr))
+#define nvtable_list(table) htable_list(table)
+#define nvtable_find(table, key) htable_find((table), (key))
+#define nvtable_delete(table, key) htable_delete((table), (key), myfree)
+#define nvtable_free(table) htable_free((table), myfree)
+
+extern NVTABLE_INFO *nvtable_update(NVTABLE *, const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/open_as.c b/src/util/open_as.c
new file mode 100644
index 0000000..0fa84b7
--- /dev/null
+++ b/src/util/open_as.c
@@ -0,0 +1,70 @@
+/*++
+/* NAME
+/* open_as 3
+/* SUMMARY
+/* open file as user
+/* SYNOPSIS
+/* #include <fcntl.h>
+/* #include <open_as.h>
+/*
+/* int open_as(path, flags, mode, euid, egid)
+/* const char *path;
+/* int mode;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* open_as() opens the named \fIpath\fR with the named \fIflags\fR
+/* and \fImode\fR, and with the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR. A -1 result means the open failed.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* 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 <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "open_as.h"
+
+/* open_as - open file as user */
+
+int open_as(const char *path, int flags, int mode, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int fd;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Open that file.
+ */
+ fd = open(path, flags, mode);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (fd);
+}
diff --git a/src/util/open_as.h b/src/util/open_as.h
new file mode 100644
index 0000000..308e009
--- /dev/null
+++ b/src/util/open_as.h
@@ -0,0 +1,30 @@
+#ifndef _OPEN_H_INCLUDED_
+#define _OPEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* open_as 3h
+/* SUMMARY
+/* open file as user
+/* SYNOPSIS
+/* #include <fcntl.h>
+/* #include <open_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int open_as(const char *, int, int, uid_t, gid_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/open_limit.c b/src/util/open_limit.c
new file mode 100644
index 0000000..8d49433
--- /dev/null
+++ b/src/util/open_limit.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* open_limit 3
+/* SUMMARY
+/* set/get open file limit
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int open_limit(int limit)
+/* DESCRIPTION
+/* The \fIopen_limit\fR() routine attempts to change the maximum
+/* number of open files to the specified limit. Specify a null
+/* argument to effect no change. The result is the actual open file
+/* limit for the current process. The number can be smaller or larger
+/* than the requested limit.
+/* DIAGNOSTICS
+/* open_limit() returns -1 in case of problems. The errno
+/* variable gives hints about the nature of the problem.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <errno.h>
+
+#ifdef USE_MAX_FILES_PER_PROC
+#include <sys/sysctl.h>
+#define MAX_FILES_PER_PROC "kern.maxfilesperproc"
+#endif
+
+/* Application-specific. */
+
+#include "iostuff.h"
+
+ /*
+ * 44BSD compatibility.
+ */
+#ifndef RLIMIT_NOFILE
+#ifdef RLIMIT_OFILE
+#define RLIMIT_NOFILE RLIMIT_OFILE
+#endif
+#endif
+
+/* open_limit - set/query file descriptor limit */
+
+int open_limit(int limit)
+{
+#ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+#endif
+
+ if (limit < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+#ifdef RLIMIT_NOFILE
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return (-1);
+ if (limit > 0) {
+
+ /*
+ * MacOSX incorrectly reports rlim_max as RLIM_INFINITY. The true
+ * hard limit is finite and equals the kern.maxfilesperproc value.
+ */
+#ifdef USE_MAX_FILES_PER_PROC
+ int max_files_per_proc;
+ size_t len = sizeof(max_files_per_proc);
+
+ if (sysctlbyname(MAX_FILES_PER_PROC, &max_files_per_proc, &len,
+ (void *) 0, (size_t) 0) < 0)
+ return (-1);
+ if (limit > max_files_per_proc)
+ limit = max_files_per_proc;
+#endif
+ if (limit > rl.rlim_max)
+ rl.rlim_cur = rl.rlim_max;
+ else
+ rl.rlim_cur = limit;
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return (-1);
+ }
+ return (rl.rlim_cur);
+#endif
+
+#ifndef RLIMIT_NOFILE
+ return (getdtablesize());
+#endif
+}
+
diff --git a/src/util/open_lock.c b/src/util/open_lock.c
new file mode 100644
index 0000000..87e852d
--- /dev/null
+++ b/src/util/open_lock.c
@@ -0,0 +1,76 @@
+/*++
+/* NAME
+/* open_lock 3
+/* SUMMARY
+/* open or create file and lock it for exclusive access
+/* SYNOPSIS
+/* #include <open_lock.h>
+/*
+/* VSTREAM *open_lock(path, flags, mode, why)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/* VSTRING *why;
+/* DESCRIPTION
+/* This module opens or creates the named file and attempts to
+/* acquire an exclusive lock. The lock is lost when the last
+/* process closes the file.
+/*
+/* Arguments:
+/* .IP "path, flags, mode"
+/* These are passed on to safe_open().
+/* .IP why
+/* storage for diagnostics.
+/* SEE ALSO
+/* safe_open(3) carefully open or create file
+/* myflock(3) get exclusive lock on file
+/* DIAGNOSTICS
+/* In case of problems the result is a null pointer and a problem
+/* description is returned via the global \fIerrno\fR variable.
+/* 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 <fcntl.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <safe_open.h>
+#include <myflock.h>
+#include <open_lock.h>
+
+/* open_lock - open file and lock it for exclusive access */
+
+VSTREAM *open_lock(const char *path, int flags, mode_t mode, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ /*
+ * Carefully create or open the file, and lock it down. Some systems
+ * don't have the O_LOCK open() flag, or the flag does not do what we
+ * want, so we roll our own lock.
+ */
+ if ((fp = safe_open(path, flags, mode, (struct stat *) 0, -1, -1, why)) == 0)
+ return (0);
+ if (myflock(vstream_fileno(fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) {
+ vstring_sprintf(why, "unable to set exclusive lock: %m");
+ vstream_fclose(fp);
+ return (0);
+ }
+ return (fp);
+}
diff --git a/src/util/open_lock.h b/src/util/open_lock.h
new file mode 100644
index 0000000..ada9f4d
--- /dev/null
+++ b/src/util/open_lock.h
@@ -0,0 +1,41 @@
+#ifndef _OPEN_LOCK_H_INCLUDED_
+#define _OPEN_LOCK_H_INCLUDED_
+
+/*++
+/* NAME
+/* open_lock 3h
+/* SUMMARY
+/* open or create file and lock it for exclusive access
+/* SYNOPSIS
+/* #include <open_lock.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <fcntl.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *open_lock(const char *, int, mode_t, VSTRING *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/pass_accept.c b/src/util/pass_accept.c
new file mode 100644
index 0000000..d06926f
--- /dev/null
+++ b/src/util/pass_accept.c
@@ -0,0 +1,106 @@
+/*++
+/* NAME
+/* pass_accept 3
+/* SUMMARY
+/* start UNIX-domain file descriptor listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int pass_accept(listen_fd)
+/* int listen_fd;
+/*
+/* int pass_accept_attr(listen_fd, attr)
+/* int listen_fd;
+/* HTABLE **attr;
+/* DESCRIPTION
+/* This module implements a listener that receives one attribute list
+/* and file descriptor over each a local connection that is made to it.
+/*
+/* Arguments:
+/* .IP attr
+/* Pointer to attribute list pointer. In case of error, or
+/* no attributes, the attribute list pointer is set to null.
+/* .IP listen_fd
+/* File descriptor returned by LOCAL_LISTEN().
+/* DIAGNOSTICS
+/* Warnings: I/O errors, timeout.
+/* 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 <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <listen.h>
+#include <attr.h>
+
+#define PASS_ACCEPT_TMOUT 100
+
+/* pass_accept - accept descriptor */
+
+int pass_accept(int listen_fd)
+{
+ const char *myname = "pass_accept";
+ int accept_fd;
+ int recv_fd = -1;
+
+ accept_fd = LOCAL_ACCEPT(listen_fd);
+ if (accept_fd < 0) {
+ if (errno != EAGAIN)
+ msg_warn("%s: cannot accept connection: %m", myname);
+ return (-1);
+ } else {
+ if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0)
+ msg_warn("%s: timeout receiving file descriptor: %m", myname);
+ else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0)
+ msg_warn("%s: cannot receive file descriptor: %m", myname);
+ if (close(accept_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ return (recv_fd);
+ }
+}
+
+/* pass_accept_attr - accept descriptor and attribute list */
+
+int pass_accept_attr(int listen_fd, HTABLE **attr)
+{
+ const char *myname = "pass_accept_attr";
+ int accept_fd;
+ int recv_fd = -1;
+
+ *attr = 0;
+ accept_fd = LOCAL_ACCEPT(listen_fd);
+ if (accept_fd < 0) {
+ if (errno != EAGAIN)
+ msg_warn("%s: cannot accept connection: %m", myname);
+ return (-1);
+ } else {
+ if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0)
+ msg_warn("%s: timeout receiving file descriptor: %m", myname);
+ else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0)
+ msg_warn("%s: cannot receive file descriptor: %m", myname);
+ else if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0
+ || recv_pass_attr(accept_fd, attr, PASS_ACCEPT_TMOUT, 0) < 0) {
+ msg_warn("%s: cannot receive connection attributes: %m", myname);
+ if (close(recv_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ recv_fd = -1;
+ }
+ if (close(accept_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ return (recv_fd);
+ }
+}
diff --git a/src/util/pass_trigger.c b/src/util/pass_trigger.c
new file mode 100644
index 0000000..d7d16f2
--- /dev/null
+++ b/src/util/pass_trigger.c
@@ -0,0 +1,151 @@
+/*++
+/* NAME
+/* pass_trigger 3
+/* SUMMARY
+/* trigger file descriptor listener
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int pass_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* pass_trigger() connects to the named local server by sending
+/* a file descriptor to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* unix_connect(3), local client
+/* stream_connect(3), streams-based client
+/* 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/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct pass_trigger {
+ int connect_fd;
+ char *service;
+ int pass_fd[2];
+};
+
+/* pass_trigger_event - disconnect from peer */
+
+static void pass_trigger_event(int event, void *context)
+{
+ struct pass_trigger *pp = (struct pass_trigger *) context;
+ static const char *myname = "pass_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, pp->service);
+ event_disable_readwrite(pp->connect_fd);
+ event_cancel_timer(pass_trigger_event, context);
+ /* Don't combine multiple close() calls into one boolean expression. */
+ if (close(pp->connect_fd) < 0)
+ msg_warn("%s: close %s: %m", myname, pp->service);
+ if (close(pp->pass_fd[0]) < 0)
+ msg_warn("%s: close pipe: %m", myname);
+ if (close(pp->pass_fd[1]) < 0)
+ msg_warn("%s: close pipe: %m", myname);
+ myfree(pp->service);
+ myfree((void *) pp);
+}
+
+/* pass_trigger - wakeup local server */
+
+int pass_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "pass_trigger";
+ int pass_fd[2];
+ struct pass_trigger *pp;
+ int connect_fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((connect_fd = LOCAL_CONNECT(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(connect_fd, CLOSE_ON_EXEC);
+
+ /*
+ * Create a pipe, and send one pipe end to the server.
+ */
+ if (pipe(pass_fd) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ close_on_exec(pass_fd[0], CLOSE_ON_EXEC);
+ close_on_exec(pass_fd[1], CLOSE_ON_EXEC);
+ if (LOCAL_SEND_FD(connect_fd, pass_fd[0]) < 0)
+ msg_fatal("%s: send file descriptor: %m", myname);
+
+ /*
+ * Stash away context.
+ */
+ pp = (struct pass_trigger *) mymalloc(sizeof(*pp));
+ pp->connect_fd = connect_fd;
+ pp->service = mystrdup(service);
+ pp->pass_fd[0] = pass_fd[0];
+ pp->pass_fd[1] = pass_fd[1];
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(pass_fd[1], buf, len, timeout) < 0
+ || write_buf(pass_fd[1], "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(pass_trigger_event, (void *) pp, timeout + 100);
+ event_enable_read(connect_fd, pass_trigger_event, (void *) pp);
+ return (0);
+}
diff --git a/src/util/peekfd.c b/src/util/peekfd.c
new file mode 100644
index 0000000..e9480a2
--- /dev/null
+++ b/src/util/peekfd.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* peekfd 3
+/* SUMMARY
+/* determine amount of data ready to read
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t peekfd(fd)
+/* int fd;
+/* DESCRIPTION
+/* peekfd() attempts to find out how many bytes are available to
+/* be read from the named file descriptor. The result value is
+/* the number of available bytes.
+/* DIAGNOSTICS
+/* peekfd() returns -1 in case of trouble. The global \fIerrno\fR
+/* variable reflects the nature of the problem.
+/* BUGS
+/* On some systems, non-blocking read() may fail even after a
+/* positive return from peekfd(). The smtp-sink program works
+/* around this by using the readable() function instead.
+/* 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/ioctl.h>
+#ifdef FIONREAD_IN_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+#ifdef FIONREAD_IN_TERMIOS_H
+#include <termios.h>
+#endif
+#include <unistd.h>
+
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+/* Utility library. */
+
+#include "iostuff.h"
+
+/* peekfd - return amount of data ready to read */
+
+ssize_t peekfd(int fd)
+{
+
+ /*
+ * Anticipate a series of system-dependent code fragments.
+ */
+#ifdef FIONREAD
+ int count;
+
+#ifdef SUNOS5
+
+ /*
+ * With Solaris10, write_wait() hangs in poll() until timeout, when
+ * invoked after peekfd() has received an ECONNRESET error indication.
+ * This happens when a client sends QUIT and closes the connection
+ * immediately.
+ */
+ if (ioctl(fd, FIONREAD, (char *) &count) < 0) {
+ (void) shutdown(fd, SHUT_RDWR);
+ return (-1);
+ } else {
+ return (count);
+ }
+#else /* SUNOS5 */
+ return (ioctl(fd, FIONREAD, (char *) &count) < 0 ? -1 : count);
+#endif /* SUNOS5 */
+#else
+#error "don't know how to look ahead"
+#endif
+}
diff --git a/src/util/poll_fd.c b/src/util/poll_fd.c
new file mode 100644
index 0000000..80cd0f6
--- /dev/null
+++ b/src/util/poll_fd.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* poll_fd 3
+/* SUMMARY
+/* wait until file descriptor becomes readable or writable
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int readable(fd)
+/* int fd;
+/*
+/* int writable(fd)
+/* int fd;
+/*
+/* int read_wait(fd, time_limit)
+/* int fd;
+/* int time_limit;
+/*
+/* int write_wait(fd, time_limit)
+/* int fd;
+/* int time_limit;
+/*
+/* int poll_fd(fd, request, time_limit, true_res, false_res)
+/* int fd;
+/* int request;
+/* int time_limit;
+/* int true_res;
+/* int false_res;
+/* DESCRIPTION
+/* The read*() and write*() functions in this module are macros
+/* that provide a convenient interface to poll_fd().
+/*
+/* readable() asks the kernel if the specified file descriptor
+/* is readable, i.e. a read operation would not block.
+/*
+/* writable() asks the kernel if the specified file descriptor
+/* is writable, i.e. a write operation would not block.
+/*
+/* read_wait() waits until the specified file descriptor becomes
+/* readable, or until the time limit is reached.
+/*
+/* write_wait() waits until the specified file descriptor
+/* becomes writable, or until the time limit is reached.
+/*
+/* poll_fd() waits until the specified file descriptor becomes
+/* readable or writable, or until the time limit is reached.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor. With implementations based on select(), a
+/* best effort is made to handle descriptors >=FD_SETSIZE.
+/* .IP request
+/* POLL_FD_READ (wait until readable) or POLL_FD_WRITE (wait
+/* until writable).
+/* .IP time_limit
+/* A positive value specifies a time limit in seconds. A zero
+/* value effects a poll (return immediately). A negative value
+/* means wait until the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition becomes true.
+/* .IP true_res
+/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition is true.
+/* .IP false_res
+/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition is false.
+/* DIAGNOSTICS
+/* Panic: interface violation. All system call errors are fatal
+/* unless specified otherwise.
+/*
+/* readable() and writable() return 1 when the requested
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, zero when
+/* it is false. They never return an error indication.
+/*
+/* read_wait() and write_wait() return zero when the requested
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 (with
+/* errno set to ETIMEDOUT) when it is false.
+/*
+/* poll_fd() returns true_res when the requested POLL_FD_READ
+/* or POLL_FD_WRITE condition is true, false_res when it is
+/* false. When poll_fd() returns a false_res value < 0, it
+/* also sets errno to ETIMEDOUT.
+/* 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/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+ /*
+ * Use poll() with fall-back to select(). MacOSX needs this for devices.
+ */
+#if defined(USE_SYSV_POLL_THEN_SELECT)
+#define poll_fd_sysv poll_fd
+#define USE_SYSV_POLL
+#define USE_BSD_SELECT
+int poll_fd_bsd(int, int, int, int, int);
+
+ /*
+ * Use select() only.
+ */
+#elif defined(USE_BSD_SELECT)
+#define poll_fd_bsd poll_fd
+#undef USE_SYSV_POLL
+
+ /*
+ * Use poll() only.
+ */
+#elif defined(USE_SYSV_POLL)
+#define poll_fd_sysv poll_fd
+
+ /*
+ * Sanity check.
+ */
+#else
+#error "specify USE_SYSV_POLL, USE_BSD_SELECT or USE_SYSV_POLL_THEN_SELECT"
+#endif
+
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+#ifdef USE_BSD_SELECT
+
+/* poll_fd_bsd - block with time_limit until file descriptor is ready */
+
+int poll_fd_bsd(int fd, int request, int time_limit,
+ int true_res, int false_res)
+{
+ fd_set req_fds;
+ fd_set *read_fds;
+ fd_set *write_fds;
+ fd_set except_fds;
+ struct timeval tv;
+ struct timeval *tp;
+ int temp_fd = -1;
+
+ /*
+ * Sanity checks.
+ */
+ if (FD_SETSIZE <= fd) {
+ if ((temp_fd = dup(fd)) < 0 || temp_fd >= FD_SETSIZE)
+ msg_fatal("descriptor %d does not fit FD_SETSIZE %d", fd, FD_SETSIZE);
+ fd = temp_fd;
+ }
+
+ /*
+ * Use select() so we do not depend on alarm() and on signal() handlers.
+ * Restart select() when interrupted by some signal. Some select()
+ * implementations reduce the time to wait when interrupted, which is
+ * exactly what we want.
+ */
+ FD_ZERO(&req_fds);
+ FD_SET(fd, &req_fds);
+ except_fds = req_fds;
+ if (request == POLL_FD_READ) {
+ read_fds = &req_fds;
+ write_fds = 0;
+ } else if (request == POLL_FD_WRITE) {
+ read_fds = 0;
+ write_fds = &req_fds;
+ } else {
+ msg_panic("poll_fd: bad request %d", request);
+ }
+
+ if (time_limit >= 0) {
+ tv.tv_usec = 0;
+ tv.tv_sec = time_limit;
+ tp = &tv;
+ } else {
+ tp = 0;
+ }
+
+ for (;;) {
+ switch (select(fd + 1, read_fds, write_fds, &except_fds, tp)) {
+ case -1:
+ if (errno != EINTR)
+ msg_fatal("select: %m");
+ continue;
+ case 0:
+ if (temp_fd != -1)
+ (void) close(temp_fd);
+ if (false_res < 0)
+ errno = ETIMEDOUT;
+ return (false_res);
+ default:
+ if (temp_fd != -1)
+ (void) close(temp_fd);
+ return (true_res);
+ }
+ }
+}
+
+#endif
+
+#ifdef USE_SYSV_POLL
+
+#ifdef USE_SYSV_POLL_THEN_SELECT
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+ return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res)))
+#else
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+ msg_fatal("poll: %m")
+#endif
+
+/* poll_fd_sysv - block with time_limit until file descriptor is ready */
+
+int poll_fd_sysv(int fd, int request, int time_limit,
+ int true_res, int false_res)
+{
+ struct pollfd pollfd;
+
+ /*
+ * System-V poll() is optimal for polling a few descriptors.
+ */
+#define WAIT_FOR_EVENT (-1)
+
+ pollfd.fd = fd;
+ if (request == POLL_FD_READ) {
+ pollfd.events = POLLIN;
+ } else if (request == POLL_FD_WRITE) {
+ pollfd.events = POLLOUT;
+ } else {
+ msg_panic("poll_fd: bad request %d", request);
+ }
+
+ for (;;) {
+ switch (poll(&pollfd, 1, time_limit < 0 ?
+ WAIT_FOR_EVENT : time_limit * 1000)) {
+ case -1:
+ if (errno != EINTR)
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
+ continue;
+ case 0:
+ if (false_res < 0)
+ errno = ETIMEDOUT;
+ return (false_res);
+ default:
+ if (pollfd.revents & POLLNVAL)
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
+ return (true_res);
+ }
+ }
+}
+
+#endif
diff --git a/src/util/posix_signals.c b/src/util/posix_signals.c
new file mode 100644
index 0000000..8ccddf0
--- /dev/null
+++ b/src/util/posix_signals.c
@@ -0,0 +1,126 @@
+/*++
+/* NAME
+/* posix_signals 3
+/* SUMMARY
+/* POSIX signal handling compatibility
+/* SYNOPSIS
+/* #include <posix_signals.h>
+/*
+/* int sigemptyset(m)
+/* sigset_t *m;
+/*
+/* int sigaddset(set, signum)
+/* sigset_t *set;
+/* int signum;
+/*
+/* int sigprocmask(how, set, old)
+/* int how;
+/* sigset_t *set;
+/* sigset_t *old;
+/*
+/* int sigaction(sig, act, oact)
+/* int sig;
+/* struct sigaction *act;
+/* struct sigaction *oact;
+/* DESCRIPTION
+/* These routines emulate the POSIX signal handling interface.
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <signal.h>
+#include <errno.h>
+
+/* Utility library.*/
+
+#include "posix_signals.h"
+
+#ifdef MISSING_SIGSET_T
+
+int sigemptyset(sigset_t *m)
+{
+ return *m = 0;
+}
+
+int sigaddset(sigset_t *set, int signum)
+{
+ *set |= sigmask(signum);
+ return 0;
+}
+
+int sigprocmask(int how, sigset_t *set, sigset_t *old)
+{
+ int previous;
+
+ if (how == SIG_BLOCK)
+ previous = sigblock(*set);
+ else if (how == SIG_SETMASK)
+ previous = sigsetmask(*set);
+ else if (how == SIG_UNBLOCK) {
+ int m = sigblock(0);
+
+ previous = sigsetmask(m & ~*set);
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (old)
+ *old = previous;
+ return 0;
+}
+
+#endif
+
+#ifdef MISSING_SIGACTION
+
+static struct sigaction actions[NSIG] = {};
+
+static int sighandle(int signum)
+{
+ if (signum == SIGCHLD) {
+ /* XXX If the child is just stopped, don't invoke the handler. */
+ }
+ actions[signum].sa_handler(signum);
+}
+
+int sigaction(int sig, struct sigaction *act, struct sigaction *oact)
+{
+ static int initialized = 0;
+
+ if (!initialized) {
+ int i;
+
+ for (i = 0; i < NSIG; i++)
+ actions[i].sa_handler = SIG_DFL;
+ initialized = 1;
+ }
+ if (sig <= 0 || sig >= NSIG) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (oact)
+ *oact = actions[sig];
+
+ {
+ struct sigvec mine = {
+ sighandle, act->sa_mask,
+ act->sa_flags & SA_RESTART ? SV_INTERRUPT : 0
+ };
+
+ if (sigvec(sig, &mine, NULL))
+ return -1;
+ }
+
+ actions[sig] = *act;
+ return 0;
+}
+
+#endif
diff --git a/src/util/posix_signals.h b/src/util/posix_signals.h
new file mode 100644
index 0000000..12c1664
--- /dev/null
+++ b/src/util/posix_signals.h
@@ -0,0 +1,59 @@
+#ifndef _POSIX_SIGNALS_H_INCLUDED_
+#define _POSIX_SIGNALS_H_INCLUDED_
+/*++
+/* NAME
+/* posix_signals 3h
+/* SUMMARY
+/* POSIX signal handling compatibility
+/* SYNOPSIS
+/* #include <posix_signals.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Compatibility interface.
+ */
+
+#ifdef MISSING_SIGSET_T
+
+typedef int sigset_t;
+
+enum {
+ SIG_BLOCK,
+ SIG_UNBLOCK,
+ SIG_SETMASK
+};
+
+extern int sigemptyset(sigset_t *);
+extern int sigaddset(sigset_t *, int);
+extern int sigprocmask(int, sigset_t *, sigset_t *);
+
+#endif
+
+#ifdef MISSING_SIGACTION
+
+struct sigaction {
+ void (*sa_handler) ();
+ sigset_t sa_mask;
+ int sa_flags;
+};
+
+ /* Possible values for sa_flags. Or them to set multiple. */
+enum {
+ SA_RESTART,
+ SA_NOCLDSTOP = 4 /* drop the = 4. */
+};
+
+extern int sigaction(int, struct sigaction *, struct sigaction *);
+
+#endif
+
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#endif
diff --git a/src/util/printable.c b/src/util/printable.c
new file mode 100644
index 0000000..6c148fd
--- /dev/null
+++ b/src/util/printable.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* printable 3
+/* SUMMARY
+/* mask non-printable characters
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int util_utf8_enable;
+/*
+/* char *printable(buffer, replacement)
+/* char *buffer;
+/* int replacement;
+/*
+/* char *printable_except(buffer, replacement, except)
+/* char *buffer;
+/* int replacement;
+/* const char *except;
+/* DESCRIPTION
+/* printable() replaces non-printable characters
+/* in its input with the given replacement.
+/*
+/* util_utf8_enable controls whether UTF8 is considered printable.
+/* With util_utf8_enable equal to zero, non-ASCII text is replaced.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP replacement
+/* Replacement value for characters in \fIbuffer\fR that do not
+/* pass the ASCII isprint(3) test or that are not valid UTF8.
+/* .IP except
+/* Null-terminated sequence of non-replaced ASCII characters.
+/* 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>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+int util_utf8_enable = 0;
+
+/* printable - binary compatibility */
+
+#undef printable
+
+char *printable(char *, int);
+
+char *printable(char *string, int replacement)
+{
+ return (printable_except(string, replacement, (char *) 0));
+}
+
+/* printable_except - pass through printable or other preserved characters */
+
+char *printable_except(char *string, int replacement, const char *except)
+{
+ unsigned char *cp;
+ int ch;
+
+ /*
+ * XXX Replace invalid UTF8 sequences (too short, over-long encodings,
+ * out-of-range code points, etc). See valid_utf8_string.c.
+ */
+ cp = (unsigned char *) string;
+ while ((ch = *cp) != 0) {
+ if (ISASCII(ch) && (ISPRINT(ch) || (except && strchr(except, ch)))) {
+ /* ok */
+ } else if (util_utf8_enable && ch >= 194 && ch <= 254
+ && cp[1] >= 128 && cp[1] < 192) {
+ /* UTF8; skip the rest of the bytes in the character. */
+ while (cp[1] >= 128 && cp[1] < 192)
+ cp++;
+ } else {
+ /* Not ASCII and not UTF8. */
+ *cp = replacement;
+ }
+ cp++;
+ }
+ return (string);
+}
diff --git a/src/util/rand_sleep.c b/src/util/rand_sleep.c
new file mode 100644
index 0000000..69a8cc8
--- /dev/null
+++ b/src/util/rand_sleep.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* rand_sleep 3
+/* SUMMARY
+/* sleep for randomized interval
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void rand_sleep(delay, variation)
+/* unsigned delay;
+/* unsigned variation;
+/* DESCRIPTION
+/* rand_sleep() blocks the current process for an amount of time
+/* pseudo-randomly chosen from the interval (delay +- variation/2).
+/*
+/* Arguments:
+/* .IP delay
+/* Time to sleep in microseconds.
+/* .IP variation
+/* Variation in microseconds; must not be larger than delay.
+/* DIAGNOSTICS
+/* Panic: interface violation. All system call errors are fatal.
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myrand.h>
+#include <iostuff.h>
+
+/* rand_sleep - block for random time */
+
+void rand_sleep(unsigned delay, unsigned variation)
+{
+ const char *myname = "rand_sleep";
+ unsigned usec;
+
+ /*
+ * Sanity checks.
+ */
+ if (delay == 0)
+ msg_panic("%s: bad delay %d", myname, delay);
+ if (variation > delay)
+ msg_panic("%s: bad variation %d", myname, variation);
+
+ /*
+ * Use the semi-crappy random number generator.
+ */
+ usec = (delay - variation / 2) + variation * (double) myrand() / RAND_MAX;
+ doze(usec);
+}
+
+#ifdef TEST
+
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ int delay;
+ int variation;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 3)
+ msg_fatal("usage: %s delay variation", argv[0]);
+ if ((delay = atoi(argv[1])) <= 0)
+ msg_fatal("bad delay: %s", argv[1]);
+ if ((variation = atoi(argv[2])) < 0)
+ msg_fatal("bad variation: %s", argv[2]);
+ rand_sleep(delay * 1000000, variation * 1000000);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/readlline.c b/src/util/readlline.c
new file mode 100644
index 0000000..015877a
--- /dev/null
+++ b/src/util/readlline.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* readlline 3
+/* SUMMARY
+/* read logical line
+/* SYNOPSIS
+/* #include <readlline.h>
+/*
+/* VSTRING *readllines(buf, fp, lineno, first_line)
+/* VSTRING *buf;
+/* VSTREAM *fp;
+/* int *lineno;
+/* int *first_line;
+/*
+/* VSTRING *readlline(buf, fp, lineno)
+/* VSTRING *buf;
+/* VSTREAM *fp;
+/* int *lineno;
+/* DESCRIPTION
+/* readllines() reads one logical line from the named stream.
+/* .IP "blank lines and comments"
+/* Empty lines and whitespace-only lines are ignored, as
+/* are lines whose first non-whitespace character is a `#'.
+/* .IP "multi-line text"
+/* A logical line starts with non-whitespace text. A line that
+/* starts with whitespace continues a logical line.
+/* .PP
+/* The result value is the input buffer argument or a null pointer
+/* when no input is found.
+/*
+/* readlline() is a backwards-compatibility wrapper.
+/*
+/* Arguments:
+/* .IP buf
+/* A variable-length buffer for input. The result is null terminated.
+/* .IP fp
+/* Handle to an open stream.
+/* .IP lineno
+/* A null pointer, or a pointer to an integer that is incremented
+/* after reading a physical line.
+/* .IP first_line
+/* A null pointer, or a pointer to an integer that will contain
+/* the line number of the first non-blank, non-comment line
+/* in the result logical line.
+/* DIAGNOSTICS
+/* Warning: a continuation line that does not continue preceding text.
+/* The invalid input is ignored, to avoid complicating caller code.
+/* SECURITY
+/* .ad
+/* .fi
+/* readlline() imposes no logical line length limit therefore it
+/* should be used for reading trusted information only.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "readlline.h"
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define END(x) vstring_end(x)
+
+/* readllines - read one logical line */
+
+VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
+{
+ int ch;
+ int next;
+ ssize_t start;
+ char *cp;
+
+ VSTRING_RESET(buf);
+
+ /*
+ * Ignore comment lines, all whitespace lines, and empty lines. Terminate
+ * at EOF or at the beginning of the next logical line.
+ */
+ for (;;) {
+ /* Read one line, possibly not newline terminated. */
+ start = LEN(buf);
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n')
+ VSTRING_ADDCH(buf, ch);
+ if (lineno != 0 && (ch == '\n' || LEN(buf) > start))
+ *lineno += 1;
+ /* Ignore comment line, all whitespace line, or empty line. */
+ for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++)
+ /* void */ ;
+ if (cp == END(buf) || *cp == '#')
+ vstring_truncate(buf, start);
+ else if (start == 0 && lineno != 0 && first_line != 0)
+ *first_line = *lineno;
+ /* Terminate at EOF or at the beginning of the next logical line. */
+ if (ch == VSTREAM_EOF)
+ break;
+ if (LEN(buf) > 0) {
+ if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ vstream_ungetc(fp, next);
+ if (next != '#' && !ISSPACE(next))
+ break;
+ }
+ }
+ VSTRING_TERMINATE(buf);
+
+ /*
+ * Invalid input: continuing text without preceding text. Allowing this
+ * would complicate "postconf -e", which implements its own multi-line
+ * parsing routine. Do not abort, just warn, so that critical programs
+ * like postmap do not leave behind a truncated table.
+ */
+ if (LEN(buf) > 0 && ISSPACE(*STR(buf))) {
+ msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"",
+ VSTREAM_PATH(fp), STR(buf),
+ LEN(buf) > 30 ? "..." : "");
+ return (readllines(buf, fp, lineno, first_line));
+ }
+
+ /*
+ * Done.
+ */
+ return (LEN(buf) > 0 ? buf : 0);
+}
diff --git a/src/util/readlline.h b/src/util/readlline.h
new file mode 100644
index 0000000..d63cf7d
--- /dev/null
+++ b/src/util/readlline.h
@@ -0,0 +1,38 @@
+#ifndef _READLINE_H_INCLUDED_
+#define _READLINE_H_INCLUDED_
+
+/*++
+/* NAME
+/* readlline 3h
+/* SUMMARY
+/* read logical line
+/* SYNOPSIS
+/* #include <readlline.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *readllines(VSTRING *, VSTREAM *, int *, int *);
+
+#define readlline(bp, fp, lp) readllines((bp), (fp), (lp), (int *) 0)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/recv_pass_attr.c b/src/util/recv_pass_attr.c
new file mode 100644
index 0000000..0d6c647
--- /dev/null
+++ b/src/util/recv_pass_attr.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* recv_pass_attr 3
+/* SUMMARY
+/* predicate if string is all numerical
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int recv_pass_attr(fd, attr, timeout, bufsize)
+/* int fd;
+/* HTABLE **attr;
+/* int timeout;
+/* ssize_t bufsize;
+/* DESCRIPTION
+/* recv_pass_attr() receives named attributes over the specified
+/* descriptor. The result value is zero for success, -1 for error.
+/*
+/* Arguments:
+/* .IP fd
+/* The file descriptor to read from.
+/* .IP attr
+/* Pointer to attribute list pointer. The target is set to
+/* zero on error or when the received attribute list is empty,
+/* otherwise it is assigned a pointer to non-empty attribute
+/* list.
+/* .IP timeout
+/* The deadline for receiving all attributes.
+/* .IP bufsize
+/* The read buffer size. Specify 1 to avoid reading past the
+/* end of the attribute list.
+/* 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 <iostuff.h>
+#include <htable.h>
+#include <vstream.h>
+#include <attr.h>
+#include <mymalloc.h>
+#include <listen.h>
+
+/* recv_pass_attr - receive connection attributes */
+
+int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize)
+{
+ VSTREAM *fp;
+ int stream_err;
+
+ /*
+ * Set up a temporary VSTREAM to receive the attributes.
+ *
+ * XXX We use one-character reads to simplify the implementation.
+ */
+ fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(fp,
+ CA_VSTREAM_CTL_BUFSIZE(bufsize),
+ CA_VSTREAM_CTL_TIMEOUT(timeout),
+ CA_VSTREAM_CTL_START_DEADLINE,
+ CA_VSTREAM_CTL_END);
+ stream_err = (attr_scan(fp, ATTR_FLAG_NONE,
+ ATTR_TYPE_HASH, *attr = htable_create(1),
+ ATTR_TYPE_END) < 0
+ || vstream_feof(fp) || vstream_ferror(fp));
+ vstream_fdclose(fp);
+
+ /*
+ * Error reporting and recovery.
+ */
+ if (stream_err) {
+ htable_free(*attr, myfree);
+ *attr = 0;
+ return (-1);
+ } else {
+ if ((*attr)->used == 0) {
+ htable_free(*attr, myfree);
+ *attr = 0;
+ }
+ return (0);
+ }
+}
diff --git a/src/util/ring.c b/src/util/ring.c
new file mode 100644
index 0000000..d4c5f82
--- /dev/null
+++ b/src/util/ring.c
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* ring 3
+/* SUMMARY
+/* circular list management
+/* SYNOPSIS
+/* #include <ring.h>
+/*
+/* void ring_init(list)
+/* RING *list;
+/*
+/* void ring_prepend(list, element)
+/* RING *list;
+/* RING *element;
+/*
+/* void ring_append(list, element)
+/* RING *list;
+/* RING *element;
+/*
+/* RING *ring_pred(element)
+/* RING *element;
+/*
+/* RING *ring_succ(element)
+/* RING *element;
+/*
+/* void ring_detach(element)
+/* RING *element;
+/*
+/* RING_FOREACH(RING *element, RING *head)
+/* DESCRIPTION
+/* This module manages circular, doubly-linked, lists. It provides
+/* operations to initialize a list, to add or remove an element,
+/* and to iterate over a list. Although the documentation appears
+/* to emphasize the special role of the list head, each operation
+/* can be applied to each list member.
+/*
+/* Examples of applications: any sequence of objects such as queue,
+/* unordered list, or stack. Typically, an application embeds a RING
+/* structure into its own data structure, and uses the RING primitives
+/* to maintain the linkage between application-specific data objects.
+/*
+/* ring_init() initializes its argument to a list of just one element.
+/*
+/* ring_append() appends the named element to the named list head.
+/*
+/* ring_prepend() prepends the named element to the named list head.
+/*
+/* ring_succ() returns the list element that follows its argument.
+/*
+/* ring_pred() returns the list element that precedes its argument.
+/*
+/* ring_detach() disconnects a list element from its neighbors
+/* and closes the hole. This routine performs no implicit ring_init()
+/* on the removed element.
+/*
+/* RING_FOREACH() is a macro that expands to a for (... ; ... ; ...)
+/* statement that iterates over each list element in forward order.
+/* Upon completion, the \fIelement\fR variable is set equal to
+/* \fIhead\fR. The list head itself is not treated as a list member.
+/* 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 libraries. */
+
+/* Application-specific. */
+
+#include "ring.h"
+
+/* ring_init - initialize ring head */
+
+void ring_init(ring)
+RING *ring;
+{
+ ring->pred = ring->succ = ring;
+}
+
+/* ring_append - insert entry after ring head */
+
+void ring_append(ring, entry)
+RING *ring;
+RING *entry;
+{
+ entry->succ = ring->succ;
+ entry->pred = ring;
+ ring->succ->pred = entry;
+ ring->succ = entry;
+}
+
+/* ring_prepend - insert new entry before ring head */
+
+void ring_prepend(ring, entry)
+RING *ring;
+RING *entry;
+{
+ entry->pred = ring->pred;
+ entry->succ = ring;
+ ring->pred->succ = entry;
+ ring->pred = entry;
+}
+
+/* ring_detach - remove entry from ring */
+
+void ring_detach(entry)
+RING *entry;
+{
+ RING *succ = entry->succ;
+ RING *pred = entry->pred;
+
+ pred->succ = succ;
+ succ->pred = pred;
+
+ entry->succ = entry->pred = 0;
+}
diff --git a/src/util/ring.h b/src/util/ring.h
new file mode 100644
index 0000000..47749e2
--- /dev/null
+++ b/src/util/ring.h
@@ -0,0 +1,59 @@
+#ifndef _RING_H_INCLUDED_
+#define _RING_H_INCLUDED_
+
+/*++
+/* NAME
+/* ring 3h
+/* SUMMARY
+/* circular list management
+/* SYNOPSIS
+/* #include <ring.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct RING RING;
+
+struct RING {
+ RING *succ; /* successor */
+ RING *pred; /* predecessor */
+};
+
+extern void ring_init(RING *);
+extern void ring_prepend(RING *, RING *);
+extern void ring_append(RING *, RING *);
+extern void ring_detach(RING *);
+
+#define ring_succ(c) ((c)->succ)
+#define ring_pred(c) ((c)->pred)
+
+#define RING_FOREACH(entry, head) \
+ for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry))
+
+ /*
+ * Typically, an application will embed a RING structure into a larger
+ * structure that also contains application-specific members. This approach
+ * gives us the best of both worlds. The application can still use the
+ * generic RING primitives for manipulating RING structures. The macro below
+ * transforms a pointer from RING structure to the structure that contains
+ * it.
+ */
+#define RING_TO_APPL(ring_ptr,app_type,ring_member) \
+ ((app_type *) (((char *) (ring_ptr)) - offsetof(app_type,ring_member)))
+
+/* 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
+/* LAST MODIFICATION
+/* Tue Jan 28 16:50:20 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/safe.h b/src/util/safe.h
new file mode 100644
index 0000000..8b75bf4
--- /dev/null
+++ b/src/util/safe.h
@@ -0,0 +1,30 @@
+#ifndef _SAFE_H_INCLUDED_
+#define _SAFE_H_INCLUDED_
+
+/*++
+/* NAME
+/* safe 3h
+/* SUMMARY
+/* miscellaneous taint checks
+/* SYNOPSIS
+/* #include <safe.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int unsafe(void);
+extern char *safe_getenv(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/safe_getenv.c b/src/util/safe_getenv.c
new file mode 100644
index 0000000..04ca659
--- /dev/null
+++ b/src/util/safe_getenv.c
@@ -0,0 +1,41 @@
+/*++
+/* NAME
+/* safe_getenv 3
+/* SUMMARY
+/* guarded getenv()
+/* SYNOPSIS
+/* #include <safe.h>
+/*
+/* char *safe_getenv(const name)
+/* char *name;
+/* DESCRIPTION
+/* The \fBsafe_getenv\fR() routine reads the named variable from the
+/* environment, provided that the unsafe() routine agrees.
+/* SEE ALSO
+/* unsafe(3), detect non-user privileges
+/* 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 <stdlib.h>
+
+/* Utility library. */
+
+#include "safe.h"
+
+/* safe_getenv - read environment variable with guard */
+
+char *safe_getenv(const char *name)
+{
+ return (unsafe() == 0 ? getenv(name) : 0);
+}
diff --git a/src/util/safe_open.c b/src/util/safe_open.c
new file mode 100644
index 0000000..c7a80cf
--- /dev/null
+++ b/src/util/safe_open.c
@@ -0,0 +1,283 @@
+/*++
+/* NAME
+/* safe_open 3
+/* SUMMARY
+/* safely open or create regular file
+/* SYNOPSIS
+/* #include <safe_open.h>
+/*
+/* VSTREAM *safe_open(path, flags, mode, st, user, group, why)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/* struct stat *st;
+/* uid_t user;
+/* gid_t group;
+/* VSTRING *why;
+/* DESCRIPTION
+/* safe_open() carefully opens or creates a file in a directory
+/* that may be writable by untrusted users. If a file is created
+/* it is given the specified ownership and permission attributes.
+/* If an existing file is opened it must not be a symbolic link,
+/* it must not be a directory, and it must have only one hard link.
+/*
+/* Arguments:
+/* .IP "path, flags, mode"
+/* These arguments are the same as with open(2). The O_EXCL flag
+/* must appear either in combination with O_CREAT, or not at all.
+/* .sp
+/* No change is made to the permissions of an existing file.
+/* .IP st
+/* Null pointer, or pointer to storage for the attributes of the
+/* opened file.
+/* .IP "user, group"
+/* File ownership for a file created by safe_open(). Specify -1
+/* in order to disable user and/or group ownership change.
+/* .sp
+/* No change is made to the ownership of an existing file.
+/* .IP why
+/* A VSTRING pointer for diagnostics.
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/*
+/* A null result means there was a problem. The nature of the
+/* problem is returned via the \fIwhy\fR buffer; when an error
+/* cannot be reported via \fIerrno\fR, the generic value EPERM
+/* (operation not permitted) is used instead.
+/* HISTORY
+/* .fi
+/* .ad
+/* A safe open routine was discussed by Casper Dik in article
+/* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix
+/* (May 18, 1994).
+/*
+/* Olaf Kirch discusses how the lstat()/open()+fstat() test can
+/* be fooled by delaying the open() until the inode found with
+/* lstat() has been re-used for a sensitive file (article
+/* <20000103212443.A5807@monad.swb.de> posted to bugtraq on
+/* Jan 3, 2000). This can be a concern for a set-ugid process
+/* that runs under the control of a user and that can be
+/* manipulated with start/stop signals.
+/* 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>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <safe_open.h>
+#include <warn_stat.h>
+
+/* safe_open_exist - open existing file */
+
+static VSTREAM *safe_open_exist(const char *path, int flags,
+ struct stat * fstat_st, VSTRING *why)
+{
+ struct stat local_statbuf;
+ struct stat lstat_st;
+ int saved_errno;
+ VSTREAM *fp;
+
+ /*
+ * Open an existing file.
+ */
+ if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) {
+ saved_errno = errno;
+ vstring_sprintf(why, "cannot open file: %m");
+ errno = saved_errno;
+ return (0);
+ }
+
+ /*
+ * Examine the modes from the open file: it must have exactly one hard
+ * link (so that someone can't lure us into clobbering a sensitive file
+ * by making a hard link to it), and it must be a non-symlink file.
+ */
+ if (fstat_st == 0)
+ fstat_st = &local_statbuf;
+ if (fstat(vstream_fileno(fp), fstat_st) < 0) {
+ msg_fatal("%s: bad open file status: %m", path);
+ } else if (fstat_st->st_nlink != 1) {
+ vstring_sprintf(why, "file has %d hard links",
+ (int) fstat_st->st_nlink);
+ errno = EPERM;
+ } else if (S_ISDIR(fstat_st->st_mode)) {
+ vstring_sprintf(why, "file is a directory");
+ errno = EISDIR;
+ }
+
+ /*
+ * Look up the file again, this time using lstat(). Compare the fstat()
+ * (open file) modes with the lstat() modes. If there is any difference,
+ * either we followed a symlink while opening an existing file, someone
+ * quickly changed the number of hard links, or someone replaced the file
+ * after the open() call. The link and mode tests aren't really necessary
+ * in daemon processes. Set-uid programs, on the other hand, can be
+ * slowed down by arbitrary amounts, and there it would make sense to
+ * compare even more file attributes, such as the inode generation number
+ * on systems that have one.
+ *
+ * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception
+ * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks
+ * owned by a non-root user. This would open a security hole when
+ * delivering mail to a world-writable mailbox directory.
+ *
+ * Sebastian Krahmer of SuSE brought to my attention that some systems have
+ * changed their semantics of link(symlink, newpath), such that the
+ * result is a hardlink to the symlink. For this reason, we now also
+ * require that the symlink's parent directory is writable only by root.
+ */
+ else if (lstat(path, &lstat_st) < 0) {
+ vstring_sprintf(why, "file status changed unexpectedly: %m");
+ errno = EPERM;
+ } else if (S_ISLNK(lstat_st.st_mode)) {
+ if (lstat_st.st_uid == 0) {
+ VSTRING *parent_buf = vstring_alloc(100);
+ const char *parent_path = sane_dirname(parent_buf, path);
+ struct stat parent_st;
+ int parent_ok;
+
+ parent_ok = (stat(parent_path, &parent_st) == 0 /* not lstat */
+ && parent_st.st_uid == 0
+ && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0);
+ vstring_free(parent_buf);
+ if (parent_ok)
+ return (fp);
+ }
+ vstring_sprintf(why, "file is a symbolic link");
+ errno = EPERM;
+ } else if (fstat_st->st_dev != lstat_st.st_dev
+ || fstat_st->st_ino != lstat_st.st_ino
+#ifdef HAS_ST_GEN
+ || fstat_st->st_gen != lstat_st.st_gen
+#endif
+ || fstat_st->st_nlink != lstat_st.st_nlink
+ || fstat_st->st_mode != lstat_st.st_mode) {
+ vstring_sprintf(why, "file status changed unexpectedly");
+ errno = EPERM;
+ }
+
+ /*
+ * We are almost there...
+ */
+ else {
+ return (fp);
+ }
+
+ /*
+ * End up here in case of fstat()/lstat() problems or inconsistencies.
+ */
+ vstream_fclose(fp);
+ return (0);
+}
+
+/* safe_open_create - create new file */
+
+static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode,
+ struct stat * st, uid_t user, gid_t group, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ /*
+ * Create a non-existing file. This relies on O_CREAT | O_EXCL to not
+ * follow symbolic links.
+ */
+ if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) {
+ vstring_sprintf(why, "cannot create file exclusively: %m");
+ return (0);
+ }
+
+ /*
+ * Optionally look up the file attributes.
+ */
+ if (st != 0 && fstat(vstream_fileno(fp), st) < 0)
+ msg_fatal("%s: bad open file status: %m", path);
+
+ /*
+ * Optionally change ownership after creating a new file. If there is a
+ * problem we should not attempt to delete the file. Something else may
+ * have opened the file in the mean time.
+ */
+#define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
+
+ if (CHANGE_OWNER(user, group)
+ && fchown(vstream_fileno(fp), user, group) < 0) {
+ msg_warn("%s: cannot change file ownership: %m", path);
+ }
+
+ /*
+ * We are almost there...
+ */
+ else {
+ return (fp);
+ }
+
+ /*
+ * End up here in case of trouble.
+ */
+ vstream_fclose(fp);
+ return (0);
+}
+
+/* safe_open - safely open or create file */
+
+VSTREAM *safe_open(const char *path, int flags, mode_t mode,
+ struct stat * st, uid_t user, gid_t group, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ switch (flags & (O_CREAT | O_EXCL)) {
+
+ /*
+ * Open an existing file, carefully.
+ */
+ case 0:
+ return (safe_open_exist(path, flags, st, why));
+
+ /*
+ * Create a new file, carefully.
+ */
+ case O_CREAT | O_EXCL:
+ return (safe_open_create(path, flags, mode, st, user, group, why));
+
+ /*
+ * Open an existing file or create a new one, carefully. When opening
+ * an existing file, we are prepared to deal with "no file" errors
+ * only. When creating a file, we are prepared for "file exists"
+ * errors only. Any other error means we better give up trying.
+ */
+ case O_CREAT:
+ fp = safe_open_exist(path, flags, st, why);
+ if (fp == 0 && errno == ENOENT) {
+ fp = safe_open_create(path, flags, mode, st, user, group, why);
+ if (fp == 0 && errno == EEXIST)
+ fp = safe_open_exist(path, flags, st, why);
+ }
+ return (fp);
+
+ /*
+ * Interface violation. Sorry, but we must be strict.
+ */
+ default:
+ msg_panic("safe_open: O_EXCL flag without O_CREAT flag");
+ }
+}
diff --git a/src/util/safe_open.h b/src/util/safe_open.h
new file mode 100644
index 0000000..88b5344
--- /dev/null
+++ b/src/util/safe_open.h
@@ -0,0 +1,42 @@
+#ifndef _SAFE_OPEN_H_INCLUDED_
+#define _SAFE_OPEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* safe_open 3h
+/* SUMMARY
+/* safely open or create regular file
+/* SYNOPSIS
+/* #include <safe_open.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/stat.h>
+#include <fcntl.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *safe_open(const char *, int, mode_t, struct stat *, uid_t, gid_t, VSTRING *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sane_accept.c b/src/util/sane_accept.c
new file mode 100644
index 0000000..86e3d34
--- /dev/null
+++ b/src/util/sane_accept.c
@@ -0,0 +1,125 @@
+/*++
+/* NAME
+/* sane_accept 3
+/* SUMMARY
+/* sanitize accept() error returns
+/* SYNOPSIS
+/* #include <sane_accept.h>
+/*
+/* int sane_accept(sock, buf, len)
+/* int sock;
+/* struct sockaddr *buf;
+/* SOCKADDR_SIZE *len;
+/* DESCRIPTION
+/* sane_accept() implements the accept(2) socket call, and maps
+/* known harmless error results to EAGAIN.
+/*
+/* If the buf and len arguments are not null, then additional
+/* workarounds may be enabled that depend on the socket type.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* 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/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_accept.h"
+
+/* sane_accept - sanitize accept() error returns */
+
+int sane_accept(int sock, struct sockaddr *sa, SOCKADDR_SIZE *len)
+{
+ static int accept_ok_errors[] = {
+ EAGAIN,
+ ECONNREFUSED,
+ ECONNRESET,
+ EHOSTDOWN,
+ EHOSTUNREACH,
+ EINTR,
+ ENETDOWN,
+ ENETUNREACH,
+ ENOTCONN,
+ EWOULDBLOCK,
+ ENOBUFS, /* HPUX11 */
+ ECONNABORTED,
+#ifdef EPROTO
+ EPROTO, /* SunOS 5.5.1 */
+#endif
+ 0,
+ };
+ int count;
+ int err;
+ int fd;
+
+ /*
+ * XXX Solaris 2.4 accept() returns EPIPE when a UNIX-domain client has
+ * disconnected in the mean time. From then on, UNIX-domain sockets are
+ * hosed beyond recovery. There is no point treating this as a beneficial
+ * error result because the program would go into a tight loop.
+ *
+ * XXX Solaris 2.5.1 accept() returns EPROTO when a TCP client has
+ * disconnected in the mean time. Since there is no connection, it is
+ * safe to map the error code onto EAGAIN.
+ *
+ * XXX LINUX < 2.1 accept() wakes up before the three-way handshake is
+ * complete, so it can fail with ECONNRESET and other "false alarm"
+ * indications.
+ *
+ * XXX FreeBSD 4.2-STABLE accept() returns ECONNABORTED when a UNIX-domain
+ * client has disconnected in the mean time. The data that was sent with
+ * connect() write() close() is lost, even though the write() and close()
+ * reported successful completion. This was fixed shortly before FreeBSD
+ * 4.3.
+ *
+ * XXX HP-UX 11 returns ENOBUFS when the client has disconnected in the mean
+ * time.
+ */
+ if ((fd = accept(sock, sa, len)) < 0) {
+ for (count = 0; (err = accept_ok_errors[count]) != 0; count++) {
+ if (errno == err) {
+ errno = EAGAIN;
+ break;
+ }
+ }
+ }
+
+ /*
+ * XXX Solaris select() produces false read events, so that read() blocks
+ * forever on a blocking socket, and fails with EAGAIN on a non-blocking
+ * socket. Turning on keepalives will fix a blocking socket provided that
+ * the kernel's keepalive timer expires before the Postfix watchdog
+ * timer.
+ *
+ * XXX Work around NAT induced damage by sending a keepalive before an idle
+ * connection is expired. This requires that the kernel keepalive timer
+ * is set to a short time, like 100s.
+ */
+ else if (sa && (sa->sa_family == AF_INET
+#ifdef HAS_IPV6
+ || sa->sa_family == AF_INET6
+#endif
+ )) {
+ int on = 1;
+
+ (void) setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &on, sizeof(on));
+ }
+ return (fd);
+}
diff --git a/src/util/sane_accept.h b/src/util/sane_accept.h
new file mode 100644
index 0000000..84cc360
--- /dev/null
+++ b/src/util/sane_accept.h
@@ -0,0 +1,29 @@
+#ifndef _SANE_ACCEPT_H_
+#define _SANE_ACCEPT_H_
+
+/*++
+/* NAME
+/* sane_accept 3h
+/* SUMMARY
+/* sanitize accept() error returns
+/* SYNOPSIS
+/* #include <sane_accept.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int sane_accept(int, struct sockaddr *, SOCKADDR_SIZE *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sane_basename.c b/src/util/sane_basename.c
new file mode 100644
index 0000000..6c3a4c1
--- /dev/null
+++ b/src/util/sane_basename.c
@@ -0,0 +1,181 @@
+/*++
+/* NAME
+/* sane_basename 3
+/* SUMMARY
+/* split pathname into last component and parent directory
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *sane_basename(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/*
+/* char *sane_dirname(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/* DESCRIPTION
+/* These functions split a pathname into its last component
+/* and its parent directory, excluding any trailing "/"
+/* characters from the input. The result is a pointer to "/"
+/* when the input is all "/" characters, or a pointer to "."
+/* when the input is a null pointer or zero-length string.
+/*
+/* sane_basename() and sane_dirname() differ as follows
+/* from standard basename() and dirname() implementations:
+/* .IP \(bu
+/* They can use caller-provided storage or private storage.
+/* .IP \(bu
+/* They never modify their input.
+/* .PP
+/* sane_basename() returns a pointer to string with the last
+/* pathname component.
+/*
+/* sane_dirname() returns a pointer to string with the parent
+/* directory. The result is a pointer to "." when the input
+/* contains no '/' character.
+/*
+/* Arguments:
+/* .IP buf
+/* Result storage. If a null pointer is specified, each function
+/* uses its own private memory that is overwritten upon each call.
+/* .IP path
+/* The input pathname.
+/* 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 <vstring.h>
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+
+/* sane_basename - skip directory prefix */
+
+char *sane_basename(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *first;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * The pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ first = last - 1;
+ while (first >= path && *first != '/')
+ first--;
+
+ return (STR(vstring_strncpy(bp, first + 1, last - first)));
+}
+
+/* sane_dirname - keep directory prefix */
+
+char *sane_dirname(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * This pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ while (last >= path && *last != '/')
+ last--;
+ if (last < path) /* no '/' */
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Strip trailing '/' characters from dirname (not strictly needed).
+ */
+ while (last > path && *last == '/')
+ last--;
+
+ return (STR(vstring_strncpy(bp, path, last - path + 1)));
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ char *dir;
+ char *base;
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) > 0) {
+ dir = sane_dirname((VSTRING *) 0, STR(buf));
+ base = sane_basename((VSTRING *) 0, STR(buf));
+ vstream_printf("input=\"%s\" dir=\"%s\" base=\"%s\"\n",
+ STR(buf), dir, base);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/sane_basename.in b/src/util/sane_basename.in
new file mode 100644
index 0000000..75d613d
--- /dev/null
+++ b/src/util/sane_basename.in
@@ -0,0 +1,18 @@
+///
+/
+fo2///
+fo2/
+fo2
+///fo2
+/fo2
+///fo2///bar///
+fo2///bar///
+fo2///bar/
+fo2/bar
+
+/usr/lib
+/usr/
+usr
+/
+.
+..
diff --git a/src/util/sane_basename.ref b/src/util/sane_basename.ref
new file mode 100644
index 0000000..8edcfcd
--- /dev/null
+++ b/src/util/sane_basename.ref
@@ -0,0 +1,18 @@
+input="///" dir="/" base="/"
+input="/" dir="/" base="/"
+input="fo2///" dir="." base="fo2"
+input="fo2/" dir="." base="fo2"
+input="fo2" dir="." base="fo2"
+input="///fo2" dir="/" base="fo2"
+input="/fo2" dir="/" base="fo2"
+input="///fo2///bar///" dir="///fo2" base="bar"
+input="fo2///bar///" dir="fo2" base="bar"
+input="fo2///bar/" dir="fo2" base="bar"
+input="fo2/bar" dir="fo2" base="bar"
+input="" dir="." base="."
+input="/usr/lib" dir="/usr" base="lib"
+input="/usr/" dir="/" base="usr"
+input="usr" dir="." base="usr"
+input="/" dir="/" base="/"
+input="." dir="." base="."
+input=".." dir="." base=".."
diff --git a/src/util/sane_connect.c b/src/util/sane_connect.c
new file mode 100644
index 0000000..a15204b
--- /dev/null
+++ b/src/util/sane_connect.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* sane_connect 3
+/* SUMMARY
+/* sanitize connect() results
+/* SYNOPSIS
+/* #include <sane_connect.h>
+/*
+/* int sane_connect(sock, buf, len)
+/* int sock;
+/* struct sockaddr *buf;
+/* SOCKADDR_SIZE *len;
+/* DESCRIPTION
+/* sane_connect() implements the connect(2) socket call, and maps
+/* known harmless error results to EAGAIN.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* 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/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_connect.h"
+
+/* sane_connect - sanitize connect() results */
+
+int sane_connect(int sock, struct sockaddr *sa, SOCKADDR_SIZE len)
+{
+
+ /*
+ * XXX Solaris select() produces false read events, so that read() blocks
+ * forever on a blocking socket, and fails with EAGAIN on a non-blocking
+ * socket. Turning on keepalives will fix a blocking socket provided that
+ * the kernel's keepalive timer expires before the Postfix watchdog
+ * timer.
+ *
+ * XXX Work around NAT induced damage by sending a keepalive before an idle
+ * connection is expired. This requires that the kernel keepalive timer
+ * is set to a short time, like 100s.
+ */
+ if (sa->sa_family == AF_INET) {
+ int on = 1;
+
+ (void) setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &on, sizeof(on));
+ }
+ return (connect(sock, sa, len));
+}
diff --git a/src/util/sane_connect.h b/src/util/sane_connect.h
new file mode 100644
index 0000000..1f023b0
--- /dev/null
+++ b/src/util/sane_connect.h
@@ -0,0 +1,29 @@
+#ifndef _SANE_CONNECT_H_
+#define _SANE_CONNECT_H_
+
+/*++
+/* NAME
+/* sane_connect 3h
+/* SUMMARY
+/* sanitize connect() results
+/* SYNOPSIS
+/* #include <sane_connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int sane_connect(int, struct sockaddr *, SOCKADDR_SIZE);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sane_fsops.h b/src/util/sane_fsops.h
new file mode 100644
index 0000000..28e7f67
--- /dev/null
+++ b/src/util/sane_fsops.h
@@ -0,0 +1,35 @@
+#ifndef _SANE_FSOPS_H_
+#define _SANE_FSOPS_H_
+
+/*++
+/* NAME
+/* sane_rename 3h
+/* SUMMARY
+/* sanitize rename() error returns
+/* SYNOPSIS
+/* #include <sane_rename.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT sane_rename(const char *, const char *);
+extern int WARN_UNUSED_RESULT sane_link(const char *, const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sane_link.c b/src/util/sane_link.c
new file mode 100644
index 0000000..40fd56d
--- /dev/null
+++ b/src/util/sane_link.c
@@ -0,0 +1,72 @@
+/*++
+/* NAME
+/* sane_link 3
+/* SUMMARY
+/* sanitize link() error returns
+/* SYNOPSIS
+/* #include <sane_fsops.h>
+/*
+/* int sane_link(from, to)
+/* const char *from;
+/* const char *to;
+/* DESCRIPTION
+/* sane_link() implements the link(2) system call, and works
+/* around some errors that are possible with NFS file systems.
+/* 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>
+#include <errno.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_fsops.h"
+#include "warn_stat.h"
+
+/* sane_link - sanitize link() error returns */
+
+int sane_link(const char *from, const char *to)
+{
+ const char *myname = "sane_link";
+ int saved_errno;
+ struct stat from_st;
+ struct stat to_st;
+
+ /*
+ * Normal case: link() succeeds.
+ */
+ if (link(from, to) >= 0)
+ return (0);
+
+ /*
+ * Woops. Save errno, and see if the error is an NFS artifact. If it is,
+ * pretend the error never happened.
+ */
+ saved_errno = errno;
+ if (stat(from, &from_st) >= 0 && stat(to, &to_st) >= 0
+ && from_st.st_dev == to_st.st_dev
+ && from_st.st_ino == to_st.st_ino) {
+ msg_info("%s(%s,%s): worked around spurious NFS error",
+ myname, from, to);
+ return (0);
+ }
+
+ /*
+ * Nope, it didn't. Restore errno and report the error.
+ */
+ errno = saved_errno;
+ return (-1);
+}
diff --git a/src/util/sane_rename.c b/src/util/sane_rename.c
new file mode 100644
index 0000000..3b301bd
--- /dev/null
+++ b/src/util/sane_rename.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* sane_rename 3
+/* SUMMARY
+/* sanitize rename() error returns
+/* SYNOPSIS
+/* #include <sane_fsops.h>
+/*
+/* int sane_rename(old, new)
+/* const char *from;
+/* const char *to;
+/* DESCRIPTION
+/* sane_rename() implements the rename(2) system call, and works
+/* around some errors that are possible with NFS file systems.
+/* 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>
+#include <errno.h>
+#include <stdio.h> /* rename(2) syscall in stdio.h? */
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_fsops.h"
+#include "warn_stat.h"
+
+/* sane_rename - sanitize rename() error returns */
+
+int sane_rename(const char *from, const char *to)
+{
+ const char *myname = "sane_rename";
+ int saved_errno;
+ struct stat st;
+
+ /*
+ * Normal case: rename() succeeds.
+ */
+ if (rename(from, to) >= 0)
+ return (0);
+
+ /*
+ * Woops. Save errno, and see if the error is an NFS artifact. If it is,
+ * pretend the error never happened.
+ */
+ saved_errno = errno;
+ if (stat(from, &st) < 0 && stat(to, &st) >= 0) {
+ msg_info("%s(%s,%s): worked around spurious NFS error",
+ myname, from, to);
+ return (0);
+ }
+
+ /*
+ * Nope, it didn't. Restore errno and report the error.
+ */
+ errno = saved_errno;
+ return (-1);
+}
diff --git a/src/util/sane_socketpair.c b/src/util/sane_socketpair.c
new file mode 100644
index 0000000..a889934
--- /dev/null
+++ b/src/util/sane_socketpair.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* sane_socketpair 3
+/* SUMMARY
+/* sanitize socketpair() error returns
+/* SYNOPSIS
+/* #include <sane_socketpair.h>
+/*
+/* int sane_socketpair(domain, type, protocol, result)
+/* int domain;
+/* int type;
+/* int protocol;
+/* int *result;
+/* DESCRIPTION
+/* sane_socketpair() implements the socketpair(2) socket call, and
+/* skips over silly error results such as EINTR.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* 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/socket.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_socketpair.h"
+
+/* sane_socketpair - sanitize socketpair() error returns */
+
+int sane_socketpair(int domain, int type, int protocol, int *result)
+{
+ static int socketpair_ok_errors[] = {
+ EINTR,
+ 0,
+ };
+ int count;
+ int err;
+ int ret;
+
+ /*
+ * Solaris socketpair() can fail with EINTR.
+ */
+ while ((ret = socketpair(domain, type, protocol, result)) < 0) {
+ for (count = 0; /* void */ ; count++) {
+ if ((err = socketpair_ok_errors[count]) == 0)
+ return (ret);
+ if (errno == err) {
+ msg_warn("socketpair: %m (trying again)");
+ sleep(1);
+ break;
+ }
+ }
+ }
+ return (ret);
+}
diff --git a/src/util/sane_socketpair.h b/src/util/sane_socketpair.h
new file mode 100644
index 0000000..d54a73d
--- /dev/null
+++ b/src/util/sane_socketpair.h
@@ -0,0 +1,34 @@
+#ifndef _SANE_SOCKETPAIR_H_
+#define _SANE_SOCKETPAIR_H_
+
+/*++
+/* NAME
+/* sane_socketpair 3h
+/* SUMMARY
+/* sanitize socketpair() error returns
+/* SYNOPSIS
+/* #include <sane_socketpair.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT sane_socketpair(int, int, int, int *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sane_strtol.c b/src/util/sane_strtol.c
new file mode 100644
index 0000000..b7435dd
--- /dev/null
+++ b/src/util/sane_strtol.c
@@ -0,0 +1,59 @@
+/*++
+/* NAME
+/* sane_strtol 3
+/* SUMMARY
+/* strtol() with mandatory errno reset
+/* SYNOPSIS
+/* #include <sane_strtol.h>
+/*
+/* long sane_strtol(
+/* const char *start,
+/* char **restrict end,
+/* int base)
+/*
+/* unsigned long sane_strtoul(
+/* const char *start,
+/* char **restrict end,
+/* int base)
+/* DESCRIPTION
+/* These functions are wrappers around the strtol() and strtoul()
+/* standard library functions that reset errno first, so that a
+/* prior ERANGE error won't cause false errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <sane_strtol.h>
+
+/* sane_strtol - strtol() with mandatory initialization */
+
+long sane_strtol(const char *start, char **end, int base)
+{
+ errno = 0;
+ return (strtol(start, end, base));
+}
+
+/* sane_strtoul - strtoul() with mandatory initialization */
+
+unsigned long sane_strtoul(const char *start, char **end, int base)
+{
+ errno = 0;
+ return (strtoul(start, end, base));
+}
diff --git a/src/util/sane_strtol.h b/src/util/sane_strtol.h
new file mode 100644
index 0000000..ac08316
--- /dev/null
+++ b/src/util/sane_strtol.h
@@ -0,0 +1,26 @@
+/*++
+/* NAME
+/* sane_strtol 3h
+/* SUMMARY
+/* strtol() with mandatory errno reset
+/* SYNOPSIS
+/* #include <sane_strtol.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External API.
+ */
+extern long sane_strtol(const char *start, char **end, int);
+extern unsigned long sane_strtoul(const char *start, char **end, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/util/sane_time.c b/src/util/sane_time.c
new file mode 100644
index 0000000..cc86de2
--- /dev/null
+++ b/src/util/sane_time.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* sane_time 3
+/* SUMMARY
+/* time(2) with backward time jump protection.
+/* SYNOPSIS
+/* #include <sane_time.h>
+/*
+/* time_t sane_time(void)
+/*
+/* DESCRIPTION
+/* This module provides time(2) like call for applications
+/* which need monotonically increasing time function rather
+/* than the real exact time. It eliminates the need for various
+/* workarounds all over the application which would handle
+/* potential problems if time suddenly jumps backward.
+/* Instead we choose to deal with this problem inside this
+/* module and let the application focus on its own tasks.
+/*
+/* sane_time() returns the current timestamp as obtained from
+/* time(2) call, at least most of the time. In case this routine
+/* detects that time has jumped backward, it keeps returning
+/* whatever timestamp it returned before, until this timestamp
+/* and the time(2) timestamp become synchronized again.
+/* Additionally, the returned timestamp is slowly increased to
+/* prevent the faked clock from freezing for too long.
+/* SEE ALSO
+/* time(2) get current time
+/* DIAGNOSTICS
+/* Warning message is logged if backward time jump is detected.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Application-specific. */
+
+#include "sane_time.h"
+
+/*
+ * How many times shall we slow down the real clock when recovering from
+ * time jump.
+ */
+#define SLEW_FACTOR 2
+
+/* sane_time - get current time, protected against time warping */
+
+time_t sane_time(void)
+{
+ time_t now;
+ static time_t last_time, last_real;
+ long delta;
+ static int fraction;
+ static int warned;
+
+ now = time((time_t *) 0);
+
+ if ((delta = now - last_time) < 0 && last_time != 0) {
+ if ((delta = now - last_real) < 0) {
+ msg_warn("%sbackward time jump detected -- slewing clock",
+ warned++ ? "another " : "");
+ } else {
+ delta += fraction;
+ last_time += delta / SLEW_FACTOR;
+ fraction = delta % SLEW_FACTOR;
+ }
+ } else {
+ if (warned) {
+ warned = 0;
+ msg_warn("backward time jump recovered -- back to normality");
+ fraction = 0;
+ }
+ last_time = now;
+ }
+ last_real = now;
+
+ return (last_time);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Repeatedly print current system time and
+ * time returned by sane_time(). Meanwhile, try stepping your system clock
+ * back and forth to see what happens.
+ */
+
+#include <stdlib.h>
+#include <msg_vstream.h>
+#include <iostuff.h> /* doze() */
+
+int main(int argc, char **argv)
+{
+ int delay = 1000000;
+ time_t now;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc == 2 && (delay = atol(argv[1]) * 1000) > 0)
+ /* void */ ;
+ else if (argc != 1)
+ msg_fatal("usage: %s [delay in ms (default 1 second)]", argv[0]);
+
+ for (;;) {
+ now = time((time_t *) 0);
+ vstream_printf("real: %s", ctime(&now));
+ now = sane_time();
+ vstream_printf("fake: %s\n", ctime(&now));
+ vstream_fflush(VSTREAM_OUT);
+ doze(delay);
+ }
+}
+
+#endif
diff --git a/src/util/sane_time.h b/src/util/sane_time.h
new file mode 100644
index 0000000..0fe50d9
--- /dev/null
+++ b/src/util/sane_time.h
@@ -0,0 +1,34 @@
+#ifndef _SANE_TIME_H_
+#define _SANE_TIME_H_
+
+/*++
+/* NAME
+/* sane_time 3h
+/* SUMMARY
+/* time(2) with backward time jump protection
+/* SYNOPSIS
+/* #include <sane_time.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * External interface.
+ */
+extern time_t sane_time(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+#endif
diff --git a/src/util/scan_dir.c b/src/util/scan_dir.c
new file mode 100644
index 0000000..d94c674
--- /dev/null
+++ b/src/util/scan_dir.c
@@ -0,0 +1,216 @@
+/*++
+/* NAME
+/* scan_dir 3
+/* SUMMARY
+/* directory scanning
+/* SYNOPSIS
+/* #include <scan_dir.h>
+/*
+/* SCAN_DIR *scan_dir_open(path)
+/* const char *path;
+/*
+/* char *scan_dir_next(scan)
+/* SCAN_DIR *scan;
+/*
+/* char *scan_dir_path(scan)
+/* SCAN_DIR *scan;
+/*
+/* void scan_push(scan, entry)
+/* SCAN_DIR *scan;
+/* const char *entry;
+/*
+/* SCAN_DIR *scan_pop(scan)
+/* SCAN_DIR *scan;
+/*
+/* SCAN_DIR *scan_dir_close(scan)
+/* SCAN_DIR *scan;
+/* DESCRIPTION
+/* These functions scan directories for names. The "." and
+/* ".." names are skipped. Essentially, this is <dirent>
+/* extended with error handling and with knowledge of the
+/* name of the directory being scanned.
+/*
+/* scan_dir_open() opens the named directory and
+/* returns a handle for subsequent use.
+/*
+/* scan_dir_close() terminates the directory scan, cleans up
+/* and returns a null pointer.
+/*
+/* scan_dir_next() returns the next requested object in the specified
+/* directory. It skips the "." and ".." entries.
+/*
+/* scan_dir_path() returns the name of the directory being scanned.
+/*
+/* scan_dir_push() causes the specified directory scan to enter the
+/* named subdirectory.
+/*
+/* scan_dir_pop() leaves the directory being scanned and returns
+/* to the previous one. The result is the argument, null if no
+/* previous directory information is available.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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>
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#else
+#define dirent direct
+#ifdef HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#ifdef HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#ifdef HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "vstring.h"
+#include "scan_dir.h"
+
+ /*
+ * The interface is based on an opaque structure, so we don't have to expose
+ * the user to the guts. Subdirectory info sits in front of parent directory
+ * info: a simple last-in, first-out list.
+ */
+typedef struct SCAN_INFO SCAN_INFO;
+
+struct SCAN_INFO {
+ char *path; /* directory name */
+ DIR *dir; /* directory structure */
+ SCAN_INFO *parent; /* linkage */
+};
+struct SCAN_DIR {
+ SCAN_INFO *current; /* current scan */
+};
+
+#define SCAN_DIR_PATH(scan) (scan->current->path)
+#define STR(x) vstring_str(x)
+
+/* scan_dir_path - return the path of the directory being read. */
+
+char *scan_dir_path(SCAN_DIR *scan)
+{
+ return (SCAN_DIR_PATH(scan));
+}
+
+/* scan_dir_push - enter directory */
+
+void scan_dir_push(SCAN_DIR *scan, const char *path)
+{
+ const char *myname = "scan_dir_push";
+ SCAN_INFO *info;
+
+ info = (SCAN_INFO *) mymalloc(sizeof(*info));
+ if (scan->current)
+ info->path = concatenate(SCAN_DIR_PATH(scan), "/", path, (char *) 0);
+ else
+ info->path = mystrdup(path);
+ if ((info->dir = opendir(info->path)) == 0)
+ msg_fatal("%s: open directory %s: %m", myname, info->path);
+ if (msg_verbose > 1)
+ msg_info("%s: open %s", myname, info->path);
+ info->parent = scan->current;
+ scan->current = info;
+}
+
+/* scan_dir_pop - leave directory */
+
+SCAN_DIR *scan_dir_pop(SCAN_DIR *scan)
+{
+ const char *myname = "scan_dir_pop";
+ SCAN_INFO *info = scan->current;
+ SCAN_INFO *parent;
+
+ if (info == 0)
+ return (0);
+ parent = info->parent;
+ if (closedir(info->dir))
+ msg_fatal("%s: close directory %s: %m", myname, info->path);
+ if (msg_verbose > 1)
+ msg_info("%s: close %s", myname, info->path);
+ myfree(info->path);
+ myfree((void *) info);
+ scan->current = parent;
+ return (parent ? scan : 0);
+}
+
+/* scan_dir_open - start directory scan */
+
+SCAN_DIR *scan_dir_open(const char *path)
+{
+ SCAN_DIR *scan;
+
+ scan = (SCAN_DIR *) mymalloc(sizeof(*scan));
+ scan->current = 0;
+ scan_dir_push(scan, path);
+ return (scan);
+}
+
+/* scan_dir_next - find next entry */
+
+char *scan_dir_next(SCAN_DIR *scan)
+{
+ const char *myname = "scan_dir_next";
+ SCAN_INFO *info = scan->current;
+ struct dirent *dp;
+
+#define STREQ(x,y) (strcmp((x),(y)) == 0)
+
+ if (info) {
+
+ /*
+ * Fix 20150421: readdir() does not reset errno after reaching the
+ * end-of-directory. This dates back all the way to the initial
+ * implementation of 19970309.
+ */
+ errno = 0;
+ while ((dp = readdir(info->dir)) != 0) {
+ if (STREQ(dp->d_name, ".") || STREQ(dp->d_name, "..")) {
+ if (msg_verbose > 1)
+ msg_info("%s: skip %s", myname, dp->d_name);
+ continue;
+ } else {
+ if (msg_verbose > 1)
+ msg_info("%s: found %s", myname, dp->d_name);
+ return (dp->d_name);
+ }
+ }
+ }
+ return (0);
+}
+
+/* scan_dir_close - terminate directory scan */
+
+SCAN_DIR *scan_dir_close(SCAN_DIR *scan)
+{
+ while (scan->current)
+ scan_dir_pop(scan);
+ myfree((void *) scan);
+ return (0);
+}
diff --git a/src/util/scan_dir.h b/src/util/scan_dir.h
new file mode 100644
index 0000000..8f3bf8b
--- /dev/null
+++ b/src/util/scan_dir.h
@@ -0,0 +1,37 @@
+#ifndef _SCAN_DIR_H_INCLUDED_
+#define _SCAN_DIR_H_INCLUDED_
+
+/*++
+/* NAME
+/* scan_dir 3h
+/* SUMMARY
+/* directory scanner
+/* SYNOPSIS
+/* #include <scan_dir.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * The directory scanner interface.
+ */
+typedef struct SCAN_DIR SCAN_DIR;
+
+extern SCAN_DIR *scan_dir_open(const char *);
+extern char *scan_dir_next(SCAN_DIR *);
+extern char *scan_dir_path(SCAN_DIR *);
+extern void scan_dir_push(SCAN_DIR *, const char *);
+extern SCAN_DIR *scan_dir_pop(SCAN_DIR *);
+extern SCAN_DIR *scan_dir_close(SCAN_DIR *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/select_bug.c b/src/util/select_bug.c
new file mode 100644
index 0000000..9b479ca
--- /dev/null
+++ b/src/util/select_bug.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* select_bug 1
+/* SUMMARY
+/* select test program
+/* SYNOPSIS
+/* select_bug
+/* DESCRIPTION
+/* select_bug forks child processes that perform select()
+/* on a shared socket, and sees if a wakeup affects other
+/* processes selecting on a different socket or stdin.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* 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/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h> /* bzero() prototype for 44BSD */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+
+static pid_t fork_and_read_select(const char *what, int delay, int fd)
+{
+ struct timeval tv;
+ pid_t pid;
+ fd_set readfds;
+
+ switch (pid = fork()) {
+ case -1:
+ msg_fatal("fork: %m");
+ case 0:
+ tv.tv_sec = delay;
+ tv.tv_usec = 0;
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+ switch (select(fd + 1, &readfds, (fd_set *) 0, &readfds, &tv)) {
+ case -1:
+ msg_fatal("select: %m");
+ case 0:
+ msg_info("%s select timed out", what);
+ exit(0);
+ default:
+ msg_info("%s select wakeup", what);
+ exit(0);
+ }
+ default:
+ return (pid);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int pair1[2];
+ int pair2[2];
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+#define DELAY 1
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair1) < 0)
+ msg_fatal("socketpair: %m");
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair2) < 0)
+ msg_fatal("socketpair: %m");
+
+ vstream_printf("Doing multiple select on socket1, then write to it...\n");
+ vstream_fflush(VSTREAM_OUT);
+ fork_and_read_select("socket1", DELAY, pair1[0]); /* one */
+ fork_and_read_select("socket1", DELAY, pair1[0]); /* two */
+ fork_and_read_select("socket2", DELAY, pair2[0]);
+ fork_and_read_select("stdin", DELAY, 0);
+ if (write(pair1[1], "", 1) != 1)
+ msg_fatal("write: %m");
+ while (wait((int *) 0) >= 0)
+ /* void */ ;
+ return (0);
+}
diff --git a/src/util/set_eugid.c b/src/util/set_eugid.c
new file mode 100644
index 0000000..ef35380
--- /dev/null
+++ b/src/util/set_eugid.c
@@ -0,0 +1,70 @@
+/*++
+/* NAME
+/* set_eugid 3
+/* SUMMARY
+/* set effective user and group attributes
+/* SYNOPSIS
+/* #include <set_eugid.h>
+/*
+/* void set_eugid(euid, egid)
+/* uid_t euid;
+/* gid_t egid;
+/*
+/* void SAVE_AND_SET_EUGID(uid, gid)
+/* uid_t uid;
+/* gid_t gid;
+/*
+/* void RESTORE_SAVED_EUGID()
+/* DESCRIPTION
+/* set_eugid() sets the effective user and group process attributes
+/* and updates the process group access list to be just the specified
+/* effective group id.
+/*
+/* SAVE_AND_SET_EUGID() opens a block that executes with the
+/* specified privilege. RESTORE_SAVED_EUGID() closes the block.
+/* DIAGNOSTICS
+/* All system call errors are fatal.
+/* SEE ALSO
+/* seteuid(2), setegid(2), setgroups(2)
+/* 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 <grp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+
+/* set_eugid - set effective user and group attributes */
+
+void set_eugid(uid_t euid, gid_t egid)
+{
+ int saved_errno = errno;
+
+ if (geteuid() != 0)
+ if (seteuid(0))
+ msg_fatal("set_eugid: seteuid(0): %m");
+ if (setegid(egid) < 0)
+ msg_fatal("set_eugid: setegid(%ld): %m", (long) egid);
+ if (setgroups(1, &egid) < 0)
+ msg_fatal("set_eugid: setgroups(%ld): %m", (long) egid);
+ if (euid != 0 && seteuid(euid) < 0)
+ msg_fatal("set_eugid: seteuid(%ld): %m", (long) euid);
+ if (msg_verbose)
+ msg_info("set_eugid: euid %ld egid %ld", (long) euid, (long) egid);
+ errno = saved_errno;
+}
diff --git a/src/util/set_eugid.h b/src/util/set_eugid.h
new file mode 100644
index 0000000..97a523e
--- /dev/null
+++ b/src/util/set_eugid.h
@@ -0,0 +1,43 @@
+#ifndef _SET_EUGID_H_INCLUDED_
+#define _SET_EUGID_H_INCLUDED_
+
+/*++
+/* NAME
+/* set_eugid 3h
+/* SUMMARY
+/* set effective user and group attributes
+/* SYNOPSIS
+/* #include <set_eugid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void set_eugid(uid_t, gid_t);
+
+ /*
+ * The following macros open and close a block that runs at a different
+ * privilege level. To make mistakes with stray curly braces less likely, we
+ * shape the macros below as the head and tail of a do-while loop.
+ */
+#define SAVE_AND_SET_EUGID(uid, gid) do { \
+ uid_t __set_eugid_uid = geteuid(); \
+ gid_t __set_eugid_gid = getegid(); \
+ set_eugid((uid), (gid));
+
+#define RESTORE_SAVED_EUGID() \
+ set_eugid(__set_eugid_uid, __set_eugid_gid); \
+ } while (0)
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/set_ugid.c b/src/util/set_ugid.c
new file mode 100644
index 0000000..bbcb901
--- /dev/null
+++ b/src/util/set_ugid.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* set_ugid 3
+/* SUMMARY
+/* set real, effective and saved user and group attributes
+/* SYNOPSIS
+/* #include <set_ugid.h>
+/*
+/* void set_ugid(uid, gid)
+/* uid_t uid;
+/* gid_t gid;
+/* DESCRIPTION
+/* set_ugid() sets the real, effective and saved user and group process
+/* attributes and updates the process group access list to be just the
+/* user's primary group. This operation is irreversible.
+/* DIAGNOSTICS
+/* All system call errors are fatal.
+/* SEE ALSO
+/* setuid(2), setgid(2), setgroups(2)
+/* 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 <grp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_ugid.h"
+
+/* set_ugid - set real, effective and saved user and group attributes */
+
+void set_ugid(uid_t uid, gid_t gid)
+{
+ int saved_errno = errno;
+
+ if (geteuid() != 0)
+ if (seteuid(0) < 0)
+ msg_fatal("seteuid(0): %m");
+ if (setgid(gid) < 0)
+ msg_fatal("setgid(%ld): %m", (long) gid);
+ if (setgroups(1, &gid) < 0)
+ msg_fatal("setgroups(1, &%ld): %m", (long) gid);
+ if (setuid(uid) < 0)
+ msg_fatal("setuid(%ld): %m", (long) uid);
+ if (msg_verbose > 1)
+ msg_info("setugid: uid %ld gid %ld", (long) uid, (long) gid);
+ errno = saved_errno;
+}
diff --git a/src/util/set_ugid.h b/src/util/set_ugid.h
new file mode 100644
index 0000000..e752beb
--- /dev/null
+++ b/src/util/set_ugid.h
@@ -0,0 +1,29 @@
+#ifndef _SET_UGID_H_INCLUDED_
+#define _SET_UGID_H_INCLUDED_
+
+/*++
+/* NAME
+/* set_ugid 3h
+/* SUMMARY
+/* set real, effective and saved user and group attributes
+/* SYNOPSIS
+/* #include <set_ugid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void set_ugid(uid_t, gid_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/sigdelay.c b/src/util/sigdelay.c
new file mode 100644
index 0000000..6f07a22
--- /dev/null
+++ b/src/util/sigdelay.c
@@ -0,0 +1,118 @@
+/*++
+/* NAME
+/* sigdelay 3
+/* SUMMARY
+/* delay/resume signal delivery
+/* SYNOPSIS
+/* #include <sigdelay.h>
+/*
+/* void sigdelay()
+/*
+/* void sigresume()
+/* DESCRIPTION
+/* sigdelay() delays delivery of signals. Signals that
+/* arrive in the mean time will be queued.
+/*
+/* sigresume() resumes delivery of signals. Signals that have
+/* arrived in the mean time will be delivered.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* The signal queue may be really short (as in: one per signal type).
+/*
+/* Some signals such as SIGKILL cannot be blocked.
+/* 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 <signal.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "posix_signals.h"
+#include "sigdelay.h"
+
+/* Application-specific. */
+
+static sigset_t saved_sigmask;
+static sigset_t block_sigmask;
+static int suspending;
+static int siginit_done;
+
+/* siginit - compute signal mask only once */
+
+static void siginit(void)
+{
+ int sig;
+
+ siginit_done = 1;
+ sigemptyset(&block_sigmask);
+ for (sig = 1; sig < NSIG; sig++)
+ sigaddset(&block_sigmask, sig);
+}
+
+/* sigresume - deliver delayed signals and disable signal delay */
+
+void sigresume(void)
+{
+ if (suspending != 0) {
+ suspending = 0;
+ if (sigprocmask(SIG_SETMASK, &saved_sigmask, (sigset_t *) 0) < 0)
+ msg_fatal("sigresume: sigprocmask: %m");
+ }
+}
+
+/* sigdelay - save signal mask and block all signals */
+
+void sigdelay(void)
+{
+ if (siginit_done == 0)
+ siginit();
+ if (suspending == 0) {
+ suspending = 1;
+ if (sigprocmask(SIG_BLOCK, &block_sigmask, &saved_sigmask) < 0)
+ msg_fatal("sigdelay: sigprocmask: %m");
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - press Ctrl-C twice while signal delivery is delayed, and
+ * see how many signals are delivered when signal delivery is resumed.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+static void gotsig(int sig)
+{
+ printf("Got signal %d\n", sig);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ signal(SIGINT, gotsig);
+ signal(SIGQUIT, gotsig);
+
+ printf("Delaying signal delivery\n");
+ sigdelay();
+ sleep(5);
+ printf("Resuming signal delivery\n");
+ sigresume();
+ exit(0);
+}
+
+#endif
diff --git a/src/util/sigdelay.h b/src/util/sigdelay.h
new file mode 100644
index 0000000..d3b4ea3
--- /dev/null
+++ b/src/util/sigdelay.h
@@ -0,0 +1,31 @@
+#ifndef _SIGDELAY_H_INCLUDED_
+#define _SIGDELAY_H_INCLUDED_
+
+/*++
+/* NAME
+/* sigdelay 3h
+/* SUMMARY
+/* delay/resume signal delivery
+/* SYNOPSIS
+/* #include <sigdelay.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void sigdelay(void);
+extern void sigresume(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/skipblanks.c b/src/util/skipblanks.c
new file mode 100644
index 0000000..fc19284
--- /dev/null
+++ b/src/util/skipblanks.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* skipblanks 3
+/* SUMMARY
+/* skip leading whitespace
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *skipblanks(string)
+/* const char *string;
+/* DESCRIPTION
+/* skipblanks() returns a pointer to the first non-whitespace
+/* character in the specified string, or a pointer to the string
+/* terminator when the string contains all white-space characters.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *skipblanks(const char *string)
+{
+ const char *cp;
+
+ for (cp = string; *cp != 0; cp++)
+ if (!ISSPACE(*cp))
+ break;
+ return ((char *) cp);
+}
diff --git a/src/util/slmdb.c b/src/util/slmdb.c
new file mode 100644
index 0000000..499589d
--- /dev/null
+++ b/src/util/slmdb.c
@@ -0,0 +1,938 @@
+/*++
+/* NAME
+/* slmdb 3
+/* SUMMARY
+/* Simplified LMDB API
+/* SYNOPSIS
+/* #include <slmdb.h>
+/*
+/* int slmdb_init(slmdb, curr_limit, size_incr, hard_limit)
+/* SLMDB *slmdb;
+/* size_t curr_limit;
+/* int size_incr;
+/* size_t hard_limit;
+/*
+/* int slmdb_open(slmdb, path, open_flags, lmdb_flags, slmdb_flags)
+/* SLMDB *slmdb;
+/* const char *path;
+/* int open_flags;
+/* int lmdb_flags;
+/* int slmdb_flags;
+/*
+/* int slmdb_close(slmdb)
+/* SLMDB *slmdb;
+/*
+/* int slmdb_get(slmdb, mdb_key, mdb_value)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/*
+/* int slmdb_put(slmdb, mdb_key, mdb_value, flags)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/* int flags;
+/*
+/* int slmdb_del(slmdb, mdb_key)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/*
+/* int slmdb_cursor_get(slmdb, mdb_key, mdb_value, op)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/* MDB_cursor_op op;
+/* AUXILIARY FUNCTIONS
+/* int slmdb_fd(slmdb)
+/* SLMDB *slmdb;
+/*
+/* size_t slmdb_curr_limit(slmdb)
+/* SLMDB *slmdb;
+/*
+/* int slmdb_control(slmdb, request, ...)
+/* SLMDB *slmdb;
+/* int request;
+/* DESCRIPTION
+/* This module simplifies the LMDB API by hiding recoverable
+/* errors from the application. Details are given in the
+/* section "ERROR RECOVERY".
+/*
+/* slmdb_init() performs mandatory initialization before opening
+/* an LMDB database. The result value is an LMDB status code
+/* (zero in case of success).
+/*
+/* slmdb_open() opens an LMDB database. The result value is
+/* an LMDB status code (zero in case of success).
+/*
+/* slmdb_close() finalizes an optional bulk-mode transaction
+/* and closes a successfully-opened LMDB database. The result
+/* value is an LMDB status code (zero in case of success).
+/*
+/* slmdb_get() is an mdb_get() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_put() is an mdb_put() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_del() is an mdb_del() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_cursor_get() is an mdb_cursor_get() wrapper with
+/* automatic error recovery. The result value is an LMDB
+/* status code (zero in case of success). This wrapper supports
+/* only one cursor per database.
+/*
+/* slmdb_fd() returns the file descriptor for the specified
+/* database. This may be used for file status queries or
+/* application-controlled locking.
+/*
+/* slmdb_curr_limit() returns the current database size limit
+/* for the specified database.
+/*
+/* slmdb_control() specifies optional features. The result is
+/* an LMDB status code (zero in case of success).
+/*
+/* Arguments:
+/* .IP slmdb
+/* Pointer to caller-provided storage.
+/* .IP curr_limit
+/* The initial memory mapping size limit. This limit is
+/* automatically increased when the database becomes full.
+/* .IP size_incr
+/* An integer factor by which the memory mapping size limit
+/* is increased when the database becomes full.
+/* .IP hard_limit
+/* The upper bound for the memory mapping size limit.
+/* .IP path
+/* LMDB database pathname.
+/* .IP open_flags
+/* Flags that control file open operations. Do not specify
+/* locking flags here.
+/* .IP lmdb_flags
+/* Flags that control the LMDB environment. If MDB_NOLOCK is
+/* specified, then each slmdb_get() or slmdb_cursor_get() call
+/* must be protected with a shared (or exclusive) external lock,
+/* and each slmdb_put() or slmdb_del() call must be protected
+/* with an exclusive external lock. A lock may be released
+/* after the call returns. A writer may atomically downgrade
+/* an exclusive lock to shared, but it must obtain an exclusive
+/* lock before making another slmdb(3) write request.
+/* .sp
+/* Note: when a database is opened with MDB_NOLOCK, external
+/* locks such as fcntl() do not protect slmdb(3) requests
+/* within the same process against each other. If a program
+/* cannot avoid making simultaneous slmdb(3) requests, then
+/* it must synchronize these requests with in-process locks,
+/* in addition to the per-process fcntl(2) locks.
+/* .IP slmdb_flags
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP SLMDB_FLAG_BULK
+/* Open the database and create a "bulk" transaction that is
+/* committed when the database is closed. If MDB_NOLOCK is
+/* specified, then the entire transaction must be protected
+/* with a persistent external lock. All slmdb_get(), slmdb_put()
+/* and slmdb_del() requests will be directed to the "bulk"
+/* transaction.
+/* .RE
+/* .IP mdb_key
+/* Pointer to caller-provided lookup key storage.
+/* .IP mdb_value
+/* Pointer to caller-provided value storage.
+/* .IP op
+/* LMDB cursor operation.
+/* .IP request
+/* The start of a list of (name, value) pairs, terminated with
+/* CA_SLMDB_CTL_END. The following text enumerates the symbolic
+/* request names and the corresponding argument types.
+/* .RS
+/* .IP "CA_SLMDB_CTL_LONGJMP_FN(void (*)(void *, int))"
+/* Call-back function pointer. The function is called to repeat
+/* a failed bulk-mode transaction from the start. The arguments
+/* are the application context and the setjmp() or sigsetjmp()
+/* result value.
+/* .IP "CA_SLMDB_CTL_NOTIFY_FN(void (*)(void *, int, ...))"
+/* Call-back function pointer. The function is called to report
+/* successful error recovery. The arguments are the application
+/* context, the MDB error code, and additional arguments that
+/* depend on the error code. Details are given in the section
+/* "ERROR RECOVERY".
+/* .IP "CA_SLMDB_CTL_ASSERT_FN(void (*)(void *, const char *))"
+/* Call-back function pointer. The function is called to
+/* report an LMDB internal assertion failure. The arguments
+/* are the application context, and text that describes the
+/* problem.
+/* .IP "CA_SLMDB_CTL_CB_CONTEXT(void *)"
+/* Application context that is passed in call-back function
+/* calls.
+/* .IP "CA_SLMDB_CTL_API_RETRY_LIMIT(int)"
+/* How many times to recover from LMDB errors within the
+/* execution of a single slmdb(3) API call before giving up.
+/* .IP "CA_SLMDB_CTL_BULK_RETRY_LIMIT(int)"
+/* How many times to recover from a bulk-mode transaction
+/* before giving up.
+/* .RE
+/* ERROR RECOVERY
+/* .ad
+/* .fi
+/* This module automatically repeats failed requests after
+/* recoverable errors, up to the limits specified with
+/* slmdb_control().
+/*
+/* Recoverable errors are reported through an optional
+/* notification function specified with slmdb_control(). With
+/* recoverable MDB_MAP_FULL and MDB_MAP_RESIZED errors, the
+/* additional argument is a size_t value with the updated
+/* current database size limit; with recoverable MDB_READERS_FULL
+/* errors there is no additional argument.
+/* BUGS
+/* Recovery from MDB_MAP_FULL involves resizing the database
+/* memory mapping. According to LMDB documentation this
+/* requires that there is no concurrent activity in the same
+/* database by other threads in the same memory address space.
+/* SEE ALSO
+/* lmdb(3) API manpage (currently, non-existent).
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*
+/* 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
+/*--*/
+
+ /*
+ * DO NOT include other Postfix-specific header files. This LMDB wrapper
+ * must be usable outside Postfix.
+ */
+
+#ifdef HAS_LMDB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Application-specific. */
+
+#include <slmdb.h>
+
+ /*
+ * Minimum LMDB patchlevel.
+ *
+ * LMDB 0.9.11 allows Postfix daemons to log an LMDB error message instead of
+ * falling out of the sky without any explanation. Without such logging,
+ * Postfix with LMDB would be too hard to support.
+ *
+ * LMDB 0.9.10 fixes an information leak where LMDB wrote chunks of up to 4096
+ * bytes of uninitialized heap memory to a database. This was a security
+ * violation because it made information persistent that was not meant to be
+ * persisted, or it was sharing information that was not meant to be shared.
+ *
+ * LMDB 0.9.9 allows Postfix to use external (fcntl()-based) locks, instead of
+ * having to use world-writable LMDB lock files.
+ *
+ * LMDB 0.9.8 allows Postfix to update the database size limit on-the-fly, so
+ * that it can recover from an MDB_MAP_FULL error without having to close
+ * the database. It also allows an application to "pick up" a new database
+ * size limit on-the-fly, so that it can recover from an MDB_MAP_RESIZED
+ * error without having to close the database.
+ *
+ * The database size limit that remains is imposed by the hardware memory
+ * address space (31 or 47 bits, typically) or file system. The LMDB
+ * implementation is supposed to handle databases larger than physical
+ * memory. However, this is not necessarily guaranteed for (bulk)
+ * transactions larger than physical memory.
+ */
+#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 11)
+#error "This Postfix version requires LMDB version 0.9.11 or later"
+#endif
+
+ /*
+ * Error recovery.
+ *
+ * The purpose of the slmdb(3) API is to hide LMDB quirks (recoverable
+ * MAP_FULL, MAP_RESIZED, or MDB_READERS_FULL errors). With these out of the
+ * way, applications can pretend that those quirks don't exist, and focus on
+ * their own job.
+ *
+ * - To recover from a single-transaction LMDB error, each wrapper function
+ * uses tail recursion instead of goto. Since LMDB errors are rare, code
+ * clarity is more important than speed.
+ *
+ * - To recover from a bulk-transaction LMDB error, the error-recovery code
+ * triggers a long jump back into the caller to some pre-arranged point (the
+ * closest thing that C has to exception handling). The application is then
+ * expected to repeat the bulk transaction from scratch.
+ *
+ * When any code aborts a bulk transaction, it must reset slmdb->txn to null
+ * to avoid a use-after-free problem in slmdb_close().
+ */
+
+ /*
+ * Our default retry attempt limits. We allow a few retries per slmdb(3) API
+ * call for non-bulk transactions. We allow a number of bulk-transaction
+ * retries that is proportional to the memory address space.
+ */
+#define SLMDB_DEF_API_RETRY_LIMIT 30 /* Retries per slmdb(3) API call */
+#define SLMDB_DEF_BULK_RETRY_LIMIT \
+ (2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */
+
+ /*
+ * We increment the recursion counter each time we try to recover from
+ * error, and reset the recursion counter when returning to the application
+ * from the slmdb(3) API.
+ */
+#define SLMDB_API_RETURN(slmdb, status) do { \
+ (slmdb)->api_retry_count = 0; \
+ return (status); \
+ } while (0)
+
+ /*
+ * With MDB_NOLOCK, the application uses an external lock for inter-process
+ * synchronization. Because the caller may release the external lock after
+ * an SLMDB API call, each SLMDB API function must use a short-lived
+ * transaction unless the transaction is a bulk-mode transaction.
+ */
+
+/* slmdb_cursor_close - close cursor and its read transaction */
+
+static void slmdb_cursor_close(SLMDB *slmdb)
+{
+ MDB_txn *txn;
+
+ /*
+ * Close the cursor and its read transaction. We can restore it later
+ * from the saved key information.
+ */
+ txn = mdb_cursor_txn(slmdb->cursor);
+ mdb_cursor_close(slmdb->cursor);
+ slmdb->cursor = 0;
+ mdb_txn_abort(txn);
+}
+
+/* slmdb_saved_key_init - initialize saved key info */
+
+static void slmdb_saved_key_init(SLMDB *slmdb)
+{
+ slmdb->saved_key.mv_data = 0;
+ slmdb->saved_key.mv_size = 0;
+ slmdb->saved_key_size = 0;
+}
+
+/* slmdb_saved_key_free - destroy saved key info */
+
+static void slmdb_saved_key_free(SLMDB *slmdb)
+{
+ free(slmdb->saved_key.mv_data);
+ slmdb_saved_key_init(slmdb);
+}
+
+#define HAVE_SLMDB_SAVED_KEY(s) ((s)->saved_key.mv_data != 0)
+
+/* slmdb_saved_key_assign - copy the saved key */
+
+static int slmdb_saved_key_assign(SLMDB *slmdb, MDB_val *key_val)
+{
+
+ /*
+ * Extend the buffer to fit the key, so that we can avoid malloc()
+ * overhead most of the time.
+ */
+ if (slmdb->saved_key_size < key_val->mv_size) {
+ if (slmdb->saved_key.mv_data == 0)
+ slmdb->saved_key.mv_data = malloc(key_val->mv_size);
+ else
+ slmdb->saved_key.mv_data =
+ realloc(slmdb->saved_key.mv_data, key_val->mv_size);
+ if (slmdb->saved_key.mv_data == 0) {
+ slmdb_saved_key_init(slmdb);
+ return (ENOMEM);
+ } else {
+ slmdb->saved_key_size = key_val->mv_size;
+ }
+ }
+
+ /*
+ * Copy the key under the cursor.
+ */
+ memcpy(slmdb->saved_key.mv_data, key_val->mv_data, key_val->mv_size);
+ slmdb->saved_key.mv_size = key_val->mv_size;
+ return (0);
+}
+
+/* slmdb_prepare - LMDB-specific (re)initialization before actual access */
+
+static int slmdb_prepare(SLMDB *slmdb)
+{
+ int status = 0;
+
+ /*
+ * This is called before accessing the database, or after recovery from
+ * an LMDB error. Note: this code cannot recover from errors itself.
+ * slmdb->txn is either the database open() transaction or a
+ * freshly-created bulk-mode transaction. When slmdb_prepare() commits or
+ * aborts commits a transaction, it must set slmdb->txn to null to avoid
+ * a use-after-free error in slmdb_close().
+ *
+ * - With O_TRUNC we make a "drop" request before updating the database.
+ *
+ * - With a bulk-mode transaction we commit when the database is closed.
+ */
+ if (slmdb->open_flags & O_TRUNC) {
+ if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0) {
+ mdb_txn_abort(slmdb->txn);
+ slmdb->txn = 0;
+ return (status);
+ }
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
+ status = mdb_txn_commit(slmdb->txn);
+ slmdb->txn = 0;
+ if (status != 0)
+ return (status);
+ }
+ } else if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
+ mdb_txn_abort(slmdb->txn);
+ slmdb->txn = 0;
+ }
+ slmdb->api_retry_count = 0;
+ return (status);
+}
+
+/* slmdb_recover - recover from LMDB errors */
+
+static int slmdb_recover(SLMDB *slmdb, int status)
+{
+ MDB_envinfo info;
+ int original_status = status;
+
+ /*
+ * This may be needed in non-MDB_NOLOCK mode. Recovery is rare enough
+ * that we don't care about a few wasted cycles.
+ */
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
+
+ /*
+ * Limit the number of recovery attempts per slmdb(3) API request.
+ */
+ if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit)
+ return (status);
+
+ /*
+ * Limit the number of bulk transaction recovery attempts.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0
+ && (slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit)
+ return (status);
+
+ /*
+ * Try to clear the error condition.
+ */
+ switch (status) {
+
+ /*
+ * As of LMDB 0.9.8 when a non-bulk update runs into a "map full"
+ * error, we can resize the environment's memory map and clear the
+ * error condition. The caller should retry immediately.
+ */
+ case MDB_MAP_FULL:
+ /* Can we increase the memory map? Give up if we can't. */
+ if (slmdb->curr_limit < slmdb->hard_limit / slmdb->size_incr) {
+ slmdb->curr_limit = slmdb->curr_limit * slmdb->size_incr;
+ } else if (slmdb->curr_limit < slmdb->hard_limit) {
+ slmdb->curr_limit = slmdb->hard_limit;
+ } else {
+ /* Sorry, we are already maxed out. */
+ break;
+ }
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_MAP_FULL,
+ slmdb->curr_limit);
+ status = mdb_env_set_mapsize(slmdb->env, slmdb->curr_limit);
+ break;
+
+ /*
+ * When a writer resizes the database, read-only applications must
+ * increase their LMDB memory map size limit, too. Otherwise, they
+ * won't be able to read a table after it grows.
+ *
+ * As of LMDB 0.9.8 we can import the new memory map size limit into the
+ * database environment by calling mdb_env_set_mapsize() with a zero
+ * size argument. Then we extract the map size limit for later use.
+ * The caller should retry immediately.
+ */
+ case MDB_MAP_RESIZED:
+ if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) {
+ /* Do not panic. Maps may shrink after bulk update. */
+ mdb_env_info(slmdb->env, &info);
+ slmdb->curr_limit = info.me_mapsize;
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED,
+ slmdb->curr_limit);
+ }
+ break;
+
+ /*
+ * What is it with these built-in hard limits that cause systems to
+ * stop when demand is at its highest? When the system is under
+ * stress it should slow down and keep making progress.
+ */
+ case MDB_READERS_FULL:
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_READERS_FULL);
+ sleep(1);
+ status = 0;
+ break;
+
+ /*
+ * We can't solve this problem. The application should terminate with
+ * a fatal run-time error and the program should be re-run later.
+ */
+ default:
+ break;
+ }
+
+ /*
+ * If we cleared the error condition for a non-bulk transaction, return a
+ * success status. The caller should retry the failed operation
+ * immediately.
+ */
+ if (status == 0 && (slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) {
+
+ /*
+ * We cleared the error condition for a bulk transaction. If the
+ * transaction is not restartable, return the original error. The
+ * caller should terminate with a fatal run-time error, and the
+ * program should be re-run later.
+ */
+ if (slmdb->longjmp_fn == 0)
+ return (original_status);
+
+ /*
+ * Rebuild a bulk transaction from scratch, by making a long jump
+ * back into the caller at some pre-arranged point. In MDB_NOLOCK
+ * mode, there is no need to upgrade a lock to "exclusive", because a
+ * failed write transaction has no side effects.
+ */
+ if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
+ slmdb->lmdb_flags & MDB_RDONLY,
+ &slmdb->txn)) == 0
+ && (status = slmdb_prepare(slmdb)) == 0)
+ slmdb->longjmp_fn(slmdb->cb_context, 1);
+ }
+ return (status);
+}
+
+/* slmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */
+
+static int slmdb_txn_begin(SLMDB *slmdb, int rdonly, MDB_txn **txn)
+{
+ int status;
+
+ if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, rdonly, txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_txn_begin(slmdb, rdonly, txn);
+
+ return (status);
+}
+
+/* slmdb_get - mdb_get() wrapper with LMDB error recovery */
+
+int slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a read transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the lookup.
+ */
+ if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0
+ && status != MDB_NOTFOUND) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_get(slmdb, mdb_key, mdb_value);
+ SLMDB_API_RETURN(slmdb, status);
+ }
+
+ /*
+ * Close the read txn if it's not the bulk-mode txn.
+ */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_put - mdb_put() wrapper with LMDB error recovery */
+
+int slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
+ MDB_val *mdb_value, int flags)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a write transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the update.
+ */
+ if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) {
+ if (status != MDB_KEYEXIST) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
+ SLMDB_API_RETURN(slmdb, status);
+ } else {
+ /* Abort non-bulk transaction only. */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the bulk-mode txn.
+ */
+ if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_del - mdb_del() wrapper with LMDB error recovery */
+
+int slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a write transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the update.
+ */
+ if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) {
+ if (status != MDB_NOTFOUND) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_del(slmdb, mdb_key);
+ SLMDB_API_RETURN(slmdb, status);
+ } else {
+ /* Abort non-bulk transaction only. */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the bulk-mode txn.
+ */
+ if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_del(slmdb, mdb_key);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */
+
+int slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
+ MDB_val *mdb_value, MDB_cursor_op op)
+{
+ MDB_txn *txn;
+ int status = 0;
+
+ /*
+ * TODO: figure how we would recover a failing bulk transaction.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) {
+ if (slmdb->assert_fn)
+ slmdb->assert_fn(slmdb->cb_context,
+ "slmdb_cursor_get: bulk transaction is not supported");
+ return (MDB_PANIC);
+ }
+
+ /*
+ * Open a read transaction and cursor if needed.
+ */
+ if (slmdb->cursor == 0) {
+ if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+ if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) {
+ mdb_txn_abort(txn);
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
+ SLMDB_API_RETURN(slmdb, status);
+ }
+
+ /*
+ * Restore the cursor position from the saved key information.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb) && op != MDB_FIRST)
+ status = mdb_cursor_get(slmdb->cursor, &slmdb->saved_key,
+ (MDB_val *) 0, MDB_SET);
+ }
+
+ /*
+ * Database lookup.
+ */
+ if (status == 0)
+ status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op);
+
+ /*
+ * Save the cursor position if successful. This can fail only with
+ * ENOMEM.
+ *
+ * Close the cursor read transaction if in MDB_NOLOCK mode, because the
+ * caller may release the external lock after we return.
+ */
+ if (status == 0) {
+ status = slmdb_saved_key_assign(slmdb, mdb_key);
+ if (slmdb->lmdb_flags & MDB_NOLOCK)
+ slmdb_cursor_close(slmdb);
+ }
+
+ /*
+ * Handle end-of-database or other error.
+ */
+ else {
+ /* Do not hand-optimize out the slmdb_cursor_close() calls below. */
+ if (status == MDB_NOTFOUND) {
+ slmdb_cursor_close(slmdb);
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
+ } else {
+ slmdb_cursor_close(slmdb);
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
+ SLMDB_API_RETURN(slmdb, status);
+ /* Do not hand-optimize out the above return statement. */
+ }
+ }
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_assert_cb - report LMDB assertion failure */
+
+static void slmdb_assert_cb(MDB_env *env, const char *text)
+{
+ SLMDB *slmdb = (SLMDB *) mdb_env_get_userctx(env);
+
+ if (slmdb->assert_fn)
+ slmdb->assert_fn(slmdb->cb_context, text);
+}
+
+/* slmdb_control - control optional settings */
+
+int slmdb_control(SLMDB *slmdb, int first,...)
+{
+ va_list ap;
+ int status = 0;
+ int reqno;
+ int rc;
+
+ va_start(ap, first);
+ for (reqno = first; status == 0 && reqno != SLMDB_CTL_END; reqno = va_arg(ap, int)) {
+ switch (reqno) {
+ case SLMDB_CTL_LONGJMP_FN:
+ slmdb->longjmp_fn = va_arg(ap, SLMDB_LONGJMP_FN);
+ break;
+ case SLMDB_CTL_NOTIFY_FN:
+ slmdb->notify_fn = va_arg(ap, SLMDB_NOTIFY_FN);
+ break;
+ case SLMDB_CTL_ASSERT_FN:
+ slmdb->assert_fn = va_arg(ap, SLMDB_ASSERT_FN);
+ if ((rc = mdb_env_set_userctx(slmdb->env, (void *) slmdb)) != 0
+ || (rc = mdb_env_set_assert(slmdb->env, slmdb_assert_cb)) != 0)
+ status = rc;
+ break;
+ case SLMDB_CTL_CB_CONTEXT:
+ slmdb->cb_context = va_arg(ap, void *);
+ break;
+ case SLMDB_CTL_API_RETRY_LIMIT:
+ slmdb->api_retry_limit = va_arg(ap, int);
+ break;
+ case SLMDB_CTL_BULK_RETRY_LIMIT:
+ slmdb->bulk_retry_limit = va_arg(ap, int);
+ break;
+ default:
+ status = errno = EINVAL;
+ break;
+ }
+ }
+ va_end(ap);
+ return (status);
+}
+
+/* slmdb_close - wrapper with LMDB error recovery */
+
+int slmdb_close(SLMDB *slmdb)
+{
+ int status = 0;
+
+ /*
+ * Finish an open bulk transaction. If slmdb_recover() returns after a
+ * bulk-transaction error, then it was unable to clear the error
+ * condition, or unable to restart the bulk transaction.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 && slmdb->txn != 0
+ && (status = mdb_txn_commit(slmdb->txn)) != 0)
+ status = slmdb_recover(slmdb, status);
+
+ /*
+ * Clean up after an unfinished sequence() operation.
+ */
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
+
+ mdb_env_close(slmdb->env);
+
+ /*
+ * Clean up the saved key information.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_init - mandatory initialization */
+
+int slmdb_init(SLMDB *slmdb, size_t curr_limit, int size_incr,
+ size_t hard_limit)
+{
+
+ /*
+ * This is a separate operation to keep the slmdb_open() API simple.
+ * Don't allocate resources here. Just store control information,
+ */
+ slmdb->curr_limit = curr_limit;
+ slmdb->size_incr = size_incr;
+ slmdb->hard_limit = hard_limit;
+
+ return (MDB_SUCCESS);
+}
+
+/* slmdb_open - open wrapped LMDB database */
+
+int slmdb_open(SLMDB *slmdb, const char *path, int open_flags,
+ int lmdb_flags, int slmdb_flags)
+{
+ struct stat st;
+ MDB_env *env;
+ MDB_txn *txn;
+ MDB_dbi dbi;
+ int db_fd;
+ int status;
+
+ /*
+ * Create LMDB environment.
+ */
+ if ((status = mdb_env_create(&env)) != 0)
+ return (status);
+
+ /*
+ * Make sure that the memory map has room to store and commit an initial
+ * "drop" transaction as well as fixed database metadata. We have no way
+ * to recover from errors before the first application-level I/O request.
+ */
+#define SLMDB_FUDGE 10240
+
+ if (slmdb->curr_limit < SLMDB_FUDGE)
+ slmdb->curr_limit = SLMDB_FUDGE;
+ if (stat(path, &st) == 0
+ && st.st_size > slmdb->curr_limit - SLMDB_FUDGE) {
+ if (st.st_size > slmdb->hard_limit)
+ slmdb->hard_limit = st.st_size;
+ if (st.st_size < slmdb->hard_limit - SLMDB_FUDGE)
+ slmdb->curr_limit = st.st_size + SLMDB_FUDGE;
+ else
+ slmdb->curr_limit = slmdb->hard_limit;
+ }
+
+ /*
+ * mdb_open() requires a txn, but since the default DB always exists in
+ * an LMDB environment, we usually don't need to do anything else with
+ * the txn. It is currently used for truncate and for bulk transactions.
+ */
+ if ((status = mdb_env_set_mapsize(env, slmdb->curr_limit)) != 0
+ || (status = mdb_env_open(env, path, lmdb_flags, 0644)) != 0
+ || (status = mdb_txn_begin(env, (MDB_txn *) 0,
+ lmdb_flags & MDB_RDONLY, &txn)) != 0
+ || (status = mdb_open(txn, (const char *) 0, 0, &dbi)) != 0
+ || (status = mdb_env_get_fd(env, &db_fd)) != 0) {
+ mdb_env_close(env);
+ return (status);
+ }
+
+ /*
+ * Bundle up.
+ */
+ slmdb->open_flags = open_flags;
+ slmdb->lmdb_flags = lmdb_flags;
+ slmdb->slmdb_flags = slmdb_flags;
+ slmdb->env = env;
+ slmdb->dbi = dbi;
+ slmdb->db_fd = db_fd;
+ slmdb->cursor = 0;
+ slmdb_saved_key_init(slmdb);
+ slmdb->api_retry_count = 0;
+ slmdb->bulk_retry_count = 0;
+ slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT;
+ slmdb->bulk_retry_limit = SLMDB_DEF_BULK_RETRY_LIMIT;
+ slmdb->longjmp_fn = 0;
+ slmdb->notify_fn = 0;
+ slmdb->assert_fn = 0;
+ slmdb->cb_context = 0;
+ slmdb->txn = txn;
+
+ if ((status = slmdb_prepare(slmdb)) != 0)
+ mdb_env_close(env);
+
+ return (status);
+}
+
+#endif
diff --git a/src/util/slmdb.h b/src/util/slmdb.h
new file mode 100644
index 0000000..9469c0f
--- /dev/null
+++ b/src/util/slmdb.h
@@ -0,0 +1,117 @@
+#ifndef _SLMDB_H_INCLUDED_
+#define _SLMDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* slmdb 3h
+/* SUMMARY
+/* LMDB API wrapper
+/* SYNOPSIS
+/* #include <slmdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <setjmp.h>
+
+#ifdef PATH_LMDB_H
+#include PATH_LMDB_H
+#else
+#include <lmdb.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+#ifdef NO_SIGSETJMP
+#define SLMDB_JMP_BUF jmp_buf
+#else
+#define SLMDB_JMP_BUF sigjmp_buf
+#endif
+
+ /*
+ * All data structure members are private.
+ */
+typedef struct {
+ size_t curr_limit; /* database soft size limit */
+ int size_incr; /* database expansion factor */
+ size_t hard_limit; /* database hard size limit */
+ int open_flags; /* open() flags */
+ int lmdb_flags; /* LMDB-specific flags */
+ int slmdb_flags; /* bulk-mode flag */
+ MDB_env *env; /* database environment */
+ MDB_dbi dbi; /* database instance */
+ MDB_txn *txn; /* bulk transaction */
+ int db_fd; /* database file handle */
+ MDB_cursor *cursor; /* iterator */
+ MDB_val saved_key; /* saved cursor key buffer */
+ size_t saved_key_size; /* saved cursor key buffer size */
+ void (*longjmp_fn) (void *, int);/* exception handling */
+ void (*notify_fn) (void *, int,...); /* workaround notification */
+ void (*assert_fn) (void *, const char *); /* assert notification */
+ void *cb_context; /* call-back context */
+ int api_retry_count; /* slmdb(3) API call retry count */
+ int bulk_retry_count; /* bulk_mode retry count */
+ int api_retry_limit; /* slmdb(3) API call retry limit */
+ int bulk_retry_limit; /* bulk_mode retry limit */
+} SLMDB;
+
+#define SLMDB_FLAG_BULK (1 << 0)
+
+extern int slmdb_init(SLMDB *, size_t, int, size_t);
+extern int slmdb_open(SLMDB *, const char *, int, int, int);
+extern int slmdb_get(SLMDB *, MDB_val *, MDB_val *);
+extern int slmdb_put(SLMDB *, MDB_val *, MDB_val *, int);
+extern int slmdb_del(SLMDB *, MDB_val *);
+extern int slmdb_cursor_get(SLMDB *, MDB_val *, MDB_val *, MDB_cursor_op);
+extern int slmdb_control(SLMDB *, int,...);
+extern int slmdb_close(SLMDB *);
+
+#define slmdb_fd(slmdb) ((slmdb)->db_fd)
+#define slmdb_curr_limit(slmdb) ((slmdb)->curr_limit)
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define SLMDB_CTL_END 0
+#define SLMDB_CTL_LONGJMP_FN 1 /* exception handling */
+#define SLMDB_CTL_NOTIFY_FN 2 /* debug logging function */
+#define SLMDB_CTL_CB_CONTEXT 3 /* call-back context */
+#define SLMDB_CTL_API_RETRY_LIMIT 5 /* per slmdb(3) API call */
+#define SLMDB_CTL_BULK_RETRY_LIMIT 6 /* per bulk update */
+#define SLMDB_CTL_ASSERT_FN 7 /* report assertion failure */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_SLMDB_CTL_END SLMDB_CTL_END
+#define CA_SLMDB_CTL_LONGJMP_FN(v) SLMDB_CTL_LONGJMP_FN, CHECK_VAL(SLMDB_CTL, SLMDB_LONGJMP_FN, (v))
+#define CA_SLMDB_CTL_NOTIFY_FN(v) SLMDB_CTL_NOTIFY_FN, CHECK_VAL(SLMDB_CTL, SLMDB_NOTIFY_FN, (v))
+#define CA_SLMDB_CTL_CB_CONTEXT(v) SLMDB_CTL_CB_CONTEXT, CHECK_PTR(SLMDB_CTL, void, (v))
+#define CA_SLMDB_CTL_API_RETRY_LIMIT(v) SLMDB_CTL_API_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v))
+#define CA_SLMDB_CTL_BULK_RETRY_LIMIT(v) SLMDB_CTL_BULK_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v))
+#define CA_SLMDB_CTL_ASSERT_FN(v) SLMDB_CTL_ASSERT_FN, CHECK_VAL(SLMDB_CTL, SLMDB_ASSERT_FN, (v))
+
+typedef void (*SLMDB_NOTIFY_FN) (void *, int,...);
+typedef void (*SLMDB_LONGJMP_FN) (void *, int);
+typedef void (*SLMDB_ASSERT_FN) (void *, const char *);
+
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, int);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_NOTIFY_FN);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_LONGJMP_FN);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_ASSERT_FN);
+CHECK_PTR_HELPER_DCL(SLMDB_CTL, void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+#endif
diff --git a/src/util/sock_addr.c b/src/util/sock_addr.c
new file mode 100644
index 0000000..dc5c4b1
--- /dev/null
+++ b/src/util/sock_addr.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* sock_addr 3
+/* SUMMARY
+/* sockaddr utilities
+/* SYNOPSIS
+/* #include <sock_addr.h>
+/*
+/* int sock_addr_cmp_addr(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int sock_addr_cmp_port(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int SOCK_ADDR_EQ_ADDR(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int SOCK_ADDR_EQ_PORT(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int sock_addr_in_loopback(sa)
+/* const struct sockaddr *sa;
+/* AUXILIARY MACROS
+/* struct sockaddr *SOCK_ADDR_PTR(ptr)
+/* unsigned char SOCK_ADDR_FAMILY(ptr)
+/* unsigned char SOCK_ADDR_LEN(ptr)
+/* unsigned short SOCK_ADDR_PORT(ptr)
+/* unsigned short *SOCK_ADDR_PORTP(ptr)
+/*
+/* struct sockaddr_in *SOCK_ADDR_IN_PTR(ptr)
+/* unsigned char SOCK_ADDR_IN_FAMILY(ptr)
+/* unsigned short SOCK_ADDR_IN_PORT(ptr)
+/* struct in_addr SOCK_ADDR_IN_ADDR(ptr)
+/* struct in_addr IN_ADDR(ptr)
+/*
+/* struct sockaddr_in6 *SOCK_ADDR_IN6_PTR(ptr)
+/* unsigned char SOCK_ADDR_IN6_FAMILY(ptr)
+/* unsigned short SOCK_ADDR_IN6_PORT(ptr)
+/* struct in6_addr SOCK_ADDR_IN6_ADDR(ptr)
+/* struct in6_addr IN6_ADDR(ptr)
+/* DESCRIPTION
+/* These utilities take protocol-independent address structures
+/* and perform protocol-dependent operations on structure members.
+/* Some of the macros described here are called unsafe,
+/* because they evaluate one or more arguments multiple times.
+/*
+/* sock_addr_cmp_addr() or sock_addr_cmp_port() compare the
+/* address family and network address or port fields for
+/* equality, and return indication of the difference between
+/* their arguments: < 0 if the first argument is "smaller",
+/* 0 for equality, and > 0 if the first argument is "larger".
+/*
+/* The unsafe macros SOCK_ADDR_EQ_ADDR() or SOCK_ADDR_EQ_PORT()
+/* compare compare the address family and network address or
+/* port fields for equality, and return non-zero when their
+/* arguments differ.
+/*
+/* sock_addr_in_loopback() determines if the argument specifies
+/* a loopback address.
+/*
+/* The SOCK_ADDR_PTR() macro casts a generic pointer to (struct
+/* sockaddr *). The name is upper case for consistency not
+/* safety. SOCK_ADDR_FAMILY() and SOCK_ADDR_LEN() return the
+/* address family and length of the real structure that hides
+/* inside a generic sockaddr structure. On systems where struct
+/* sockaddr has no sa_len member, SOCK_ADDR_LEN() cannot be
+/* used as lvalue. SOCK_ADDR_PORT() returns the IPv4 or IPv6
+/* port number, in network byte order; it must not be used as
+/* lvalue. SOCK_ADDR_PORTP() returns a pointer to the same.
+/*
+/* The macros SOCK_ADDR_IN{,6}_{PTR,FAMILY,PORT,ADDR}() cast
+/* a generic pointer to a specific socket address structure
+/* pointer, or access a specific socket address structure
+/* member. These can be used as lvalues.
+/*
+/* The unsafe INADDR() and IN6_ADDR() macros dereference a
+/* generic pointer to a specific address structure.
+/* DIAGNOSTICS
+/* Panic: unsupported address family.
+/* 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/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <sock_addr.h>
+
+/* sock_addr_cmp_addr - compare addresses for equality */
+
+int sock_addr_cmp_addr(const struct sockaddr *sa,
+ const struct sockaddr *sb)
+{
+ if (sa->sa_family != sb->sa_family)
+ return (sa->sa_family - sb->sa_family);
+
+ /*
+ * With IPv6 address structures, assume a non-hostile implementation that
+ * stores the address as a contiguous sequence of bits. Any holes in the
+ * sequence would invalidate the use of memcmp().
+ */
+ if (sa->sa_family == AF_INET) {
+ return (SOCK_ADDR_IN_ADDR(sa).s_addr - SOCK_ADDR_IN_ADDR(sb).s_addr);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (memcmp((void *) &(SOCK_ADDR_IN6_ADDR(sa)),
+ (void *) &(SOCK_ADDR_IN6_ADDR(sb)),
+ sizeof(SOCK_ADDR_IN6_ADDR(sa))));
+#endif
+ } else {
+ msg_panic("sock_addr_cmp_addr: unsupported address family %d",
+ sa->sa_family);
+ }
+}
+
+/* sock_addr_cmp_port - compare ports for equality */
+
+int sock_addr_cmp_port(const struct sockaddr *sa,
+ const struct sockaddr *sb)
+{
+ if (sa->sa_family != sb->sa_family)
+ return (sa->sa_family - sb->sa_family);
+
+ if (sa->sa_family == AF_INET) {
+ return (SOCK_ADDR_IN_PORT(sa) - SOCK_ADDR_IN_PORT(sb));
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (SOCK_ADDR_IN6_PORT(sa) - SOCK_ADDR_IN6_PORT(sb));
+#endif
+ } else {
+ msg_panic("sock_addr_cmp_port: unsupported address family %d",
+ sa->sa_family);
+ }
+}
+
+/* sock_addr_in_loopback - determine if address is loopback */
+
+int sock_addr_in_loopback(const struct sockaddr *sa)
+{
+ unsigned long inaddr;
+
+ if (sa->sa_family == AF_INET) {
+ inaddr = ntohl(SOCK_ADDR_IN_ADDR(sa).s_addr);
+ return (IN_CLASSA(inaddr)
+ && ((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT)
+ == IN_LOOPBACKNET);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (IN6_IS_ADDR_LOOPBACK(&SOCK_ADDR_IN6_ADDR(sa)));
+#endif
+ } else {
+ msg_panic("sock_addr_in_loopback: unsupported address family %d",
+ sa->sa_family);
+ }
+}
diff --git a/src/util/sock_addr.h b/src/util/sock_addr.h
new file mode 100644
index 0000000..1f5407a
--- /dev/null
+++ b/src/util/sock_addr.h
@@ -0,0 +1,105 @@
+#ifndef _SOCK_ADDR_EQ_H_INCLUDED_
+#define _SOCK_ADDR_EQ_H_INCLUDED_
+
+/*++
+/* NAME
+/* sock_addr 3h
+/* SUMMARY
+/* socket address utilities
+/* SYNOPSIS
+/* #include <sock_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+
+ /*
+ * External interface.
+ */
+#define SOCK_ADDR_PTR(ptr) ((struct sockaddr *)(ptr))
+#define SOCK_ADDR_FAMILY(ptr) SOCK_ADDR_PTR(ptr)->sa_family
+#ifdef HAS_SA_LEN
+#define SOCK_ADDR_LEN(ptr) SOCK_ADDR_PTR(ptr)->sa_len
+#endif
+
+#define SOCK_ADDR_IN_PTR(sa) ((struct sockaddr_in *)(sa))
+#define SOCK_ADDR_IN_FAMILY(sa) SOCK_ADDR_IN_PTR(sa)->sin_family
+#define SOCK_ADDR_IN_PORT(sa) SOCK_ADDR_IN_PTR(sa)->sin_port
+#define SOCK_ADDR_IN_ADDR(sa) SOCK_ADDR_IN_PTR(sa)->sin_addr
+#define IN_ADDR(ia) (*((struct in_addr *) (ia)))
+
+extern int sock_addr_cmp_addr(const struct sockaddr *, const struct sockaddr *);
+extern int sock_addr_cmp_port(const struct sockaddr *, const struct sockaddr *);
+extern int sock_addr_in_loopback(const struct sockaddr *);
+
+#ifdef HAS_IPV6
+
+#ifndef HAS_SA_LEN
+#define SOCK_ADDR_LEN(sa) \
+ (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))
+#endif
+
+#define SOCK_ADDR_PORT(sa) \
+ (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \
+ SOCK_ADDR_IN6_PORT(sa) : SOCK_ADDR_IN_PORT(sa))
+#define SOCK_ADDR_PORTP(sa) \
+ (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \
+ &SOCK_ADDR_IN6_PORT(sa) : &SOCK_ADDR_IN_PORT(sa))
+
+#define SOCK_ADDR_IN6_PTR(sa) ((struct sockaddr_in6 *)(sa))
+#define SOCK_ADDR_IN6_FAMILY(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_family
+#define SOCK_ADDR_IN6_PORT(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_port
+#define SOCK_ADDR_IN6_ADDR(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_addr
+#define IN6_ADDR(ia) (*((struct in6_addr *) (ia)))
+
+#define SOCK_ADDR_EQ_ADDR(sa, sb) \
+ ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr) \
+ || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \
+ && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \
+ (char *) &(SOCK_ADDR_IN6_ADDR(sb)), \
+ sizeof(SOCK_ADDR_IN6_ADDR(sa))) == 0))
+
+#define SOCK_ADDR_EQ_PORT(sa, sb) \
+ ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb)) \
+ || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \
+ && SOCK_ADDR_IN6_PORT(sa) == SOCK_ADDR_IN6_PORT(sb)))
+
+#else
+
+#ifndef HAS_SA_LEN
+#define SOCK_ADDR_LEN(sa) sizeof(struct sockaddr_in)
+#endif
+
+#define SOCK_ADDR_PORT(sa) SOCK_ADDR_IN_PORT(sa))
+#define SOCK_ADDR_PORTP(sa) &SOCK_ADDR_IN_PORT(sa))
+
+#define SOCK_ADDR_EQ_ADDR(sa, sb) \
+ (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr)
+
+#define SOCK_ADDR_EQ_PORT(sa, sb) \
+ (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb))
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/spawn_command.c b/src/util/spawn_command.c
new file mode 100644
index 0000000..739e012
--- /dev/null
+++ b/src/util/spawn_command.c
@@ -0,0 +1,313 @@
+/*++
+/* NAME
+/* spawn_command 3
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/*
+/* WAIT_STATUS_T spawn_command(key, value, ...)
+/* int key;
+/* DESCRIPTION
+/* spawn_command() runs a command in a child process and returns
+/* the command exit status.
+/*
+/* Arguments:
+/* .IP key
+/* spawn_command() takes a list of macros with arguments,
+/* terminated by CA_SPAWN_CMD_END which has no arguments. The
+/* following is a listing of macros and expected argument
+/* types.
+/* .RS
+/* .IP "CA_SPAWN_CMD_COMMAND(const char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
+/* See also the SPAWN_CMD_SHELL attribute below.
+/* .IP "CA_SPAWN_CMD_ARGV(char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
+/* .IP "CA_SPAWN_CMD_ENV(char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "CA_SPAWN_CMD_EXPORT(char **)"
+/* Null-terminated array of names of environment parameters that can
+/* be exported. By default, everything is exported.
+/* .IP "CA_SPAWN_CMD_STDIN(int)"
+/* .IP "CA_SPAWN_CMD_STDOUT(int)"
+/* .IP "CA_SPAWN_CMD_STDERR(int)"
+/* Each of these specifies I/O redirection of one of the standard file
+/* descriptors for the command.
+/* .IP "CA_SPAWN_CMD_UID(uid_t)"
+/* The user ID to execute the command as. The value -1 is reserved
+/* and cannot be specified.
+/* .IP "CA_SPAWN_CMD_GID(gid_t)"
+/* The group ID to execute the command as. The value -1 is reserved
+/* and cannot be specified.
+/* .IP "CA_SPAWN_CMD_TIME_LIMIT(int)"
+/* The amount of time in seconds the command is allowed to run before
+/* it is terminated with SIGKILL. The default is no time limit.
+/* .IP "CA_SPAWN_CMD_SHELL(const char *)"
+/* The shell to use when executing the command specified with
+/* CA_SPAWN_CMD_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: interface violations (for example, a missing command).
+/*
+/* Fatal error: fork() failure, other system call failures.
+/*
+/* spawn_command() returns the exit status as defined by wait(2).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* exec_command(3) execute command
+/* 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/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <timed_wait.h>
+#include <set_ugid.h>
+#include <argv.h>
+#include <spawn_command.h>
+#include <exec_command.h>
+#include <clean_env.h>
+
+/* Application-specific. */
+
+struct spawn_args {
+ char **argv; /* argument vector */
+ char *command; /* or a plain string */
+ int stdin_fd; /* read stdin here */
+ int stdout_fd; /* write stdout here */
+ int stderr_fd; /* write stderr here */
+ uid_t uid; /* privileges */
+ gid_t gid; /* privileges */
+ char **env; /* extra environment */
+ char **export; /* exportable environment */
+ char *shell; /* command shell */
+ int time_limit; /* command time limit */
+};
+
+/* get_spawn_args - capture the variadic argument list */
+
+static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
+{
+ const char *myname = "get_spawn_args";
+ int key;
+
+ /*
+ * First, set the default values.
+ */
+ args->argv = 0;
+ args->command = 0;
+ args->stdin_fd = -1;
+ args->stdout_fd = -1;
+ args->stderr_fd = -1;
+ args->uid = (uid_t) - 1;
+ args->gid = (gid_t) - 1;
+ args->env = 0;
+ args->export = 0;
+ args->shell = 0;
+ args->time_limit = 0;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
+ switch (key) {
+ case SPAWN_CMD_ARGV:
+ if (args->command)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_COMMAND:
+ if (args->argv)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case SPAWN_CMD_STDIN:
+ args->stdin_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDOUT:
+ args->stdout_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDERR:
+ args->stderr_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_UID:
+ args->uid = va_arg(ap, uid_t);
+ if (args->uid == (uid_t) (-1))
+ msg_panic("spawn_command: request with reserved user ID: -1");
+ break;
+ case SPAWN_CMD_GID:
+ args->gid = va_arg(ap, gid_t);
+ if (args->gid == (gid_t) (-1))
+ msg_panic("spawn_command: request with reserved group ID: -1");
+ break;
+ case SPAWN_CMD_TIME_LIMIT:
+ args->time_limit = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_EXPORT:
+ args->export = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
+ if (args->command == 0 && args->shell != 0)
+ msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
+ myname);
+}
+
+/* spawn_command - execute command with extreme prejudice */
+
+WAIT_STATUS_T spawn_command(int key,...)
+{
+ const char *myname = "spawn_comand";
+ va_list ap;
+ pid_t pid;
+ WAIT_STATUS_T wait_status;
+ struct spawn_args args;
+ char **cpp;
+ ARGV *argv;
+ int err;
+
+ /*
+ * Process the variadic argument list. This also does sanity checks on
+ * what data the caller is passing to us.
+ */
+ va_start(ap, key);
+ get_spawn_args(&args, key, ap);
+ va_end(ap);
+
+ /*
+ * For convenience...
+ */
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ /*
+ * Spawn off a child process and irrevocably change privilege to the
+ * user. This includes revoking all rights on open files (via the close
+ * on exec flag). If we cannot run the command now, try again some time
+ * later.
+ */
+ switch (pid = fork()) {
+
+ /*
+ * Error. Instead of trying again right now, back off, give the
+ * system a chance to recover, and try again later.
+ */
+ case -1:
+ msg_fatal("fork: %m");
+
+ /*
+ * Child. Run the child in a separate process group so that the
+ * parent can kill not just the child but also its offspring.
+ */
+ case 0:
+ if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
+ set_ugid(args.uid, args.gid);
+ setsid();
+
+ /*
+ * Pipe plumbing.
+ */
+ if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
+ || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
+ || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
+ msg_fatal("%s: dup2: %m", myname);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ if (args.export)
+ clean_env(args.export);
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ */
+ closelog();
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, CHARS_SPACE);
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+
+ /*
+ * Parent.
+ */
+ default:
+
+ /*
+ * Be prepared for the situation that the child does not terminate.
+ * Make sure that the child terminates before the parent attempts to
+ * retrieve its exit status, otherwise the parent could become stuck,
+ * and the mail system would eventually run out of exec daemons. Do a
+ * thorough job, and kill not just the child process but also its
+ * offspring.
+ */
+ if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
+ && errno == ETIMEDOUT) {
+ msg_warn("%s: process id %lu: command time limit exceeded",
+ args.command, (unsigned long) pid);
+ kill(-pid, SIGKILL);
+ err = waitpid(pid, &wait_status, 0);
+ }
+ if (err < 0)
+ msg_fatal("wait: %m");
+ return (wait_status);
+ }
+}
diff --git a/src/util/spawn_command.h b/src/util/spawn_command.h
new file mode 100644
index 0000000..7d16413
--- /dev/null
+++ b/src/util/spawn_command.h
@@ -0,0 +1,66 @@
+#ifndef _SPAWN_COMMAND_H_INCLUDED_
+#define _SPAWN_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* spawn_command 3h
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define SPAWN_CMD_END 0 /* terminator */
+#define SPAWN_CMD_ARGV 1 /* command is array */
+#define SPAWN_CMD_COMMAND 2 /* command is string */
+#define SPAWN_CMD_STDIN 3 /* mail_copy() flags */
+#define SPAWN_CMD_STDOUT 4 /* mail_copy() sender */
+#define SPAWN_CMD_STDERR 5 /* mail_copy() recipient */
+#define SPAWN_CMD_UID 6 /* privileges */
+#define SPAWN_CMD_GID 7 /* privileges */
+#define SPAWN_CMD_TIME_LIMIT 8 /* time limit */
+#define SPAWN_CMD_ENV 9 /* extra environment */
+#define SPAWN_CMD_SHELL 10 /* alternative shell */
+#define SPAWN_CMD_EXPORT 11 /* exportable parameters */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_SPAWN_CMD_END SPAWN_CMD_END
+#define CA_SPAWN_CMD_ARGV(v) SPAWN_CMD_ARGV, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_COMMAND(v) SPAWN_CMD_COMMAND, CHECK_CPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_STDIN(v) SPAWN_CMD_STDIN, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_STDOUT(v) SPAWN_CMD_STDOUT, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_STDERR(v) SPAWN_CMD_STDERR, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_UID(v) SPAWN_CMD_UID, CHECK_VAL(CA_SPAWN_CMD, uid_t, (v))
+#define CA_SPAWN_CMD_GID(v) SPAWN_CMD_GID, CHECK_VAL(CA_SPAWN_CMD, gid_t, (v))
+#define CA_SPAWN_CMD_TIME_LIMIT(v) SPAWN_CMD_TIME_LIMIT, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_ENV(v) SPAWN_CMD_ENV, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_SHELL(v) SPAWN_CMD_SHELL, CHECK_CPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_EXPORT(v) SPAWN_CMD_EXPORT, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, uid_t);
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, int);
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, gid_t);
+CHECK_PPTR_HELPER_DCL(CA_SPAWN_CMD, char);
+CHECK_CPTR_HELPER_DCL(CA_SPAWN_CMD, char);
+
+extern WAIT_STATUS_T spawn_command(int,...);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/split_at.c b/src/util/split_at.c
new file mode 100644
index 0000000..c75e902
--- /dev/null
+++ b/src/util/split_at.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* split_at 3
+/* SUMMARY
+/* trivial token splitter
+/* SYNOPSIS
+/* #include <split_at.h>
+/*
+/* char *split_at(string, delimiter)
+/* char *string;
+/* int delimiter
+/*
+/* char *split_at_right(string, delimiter)
+/* char *string;
+/* int delimiter
+/* DESCRIPTION
+/* split_at() null-terminates the \fIstring\fR at the first
+/* occurrence of the \fIdelimiter\fR character found, and
+/* returns a pointer to the remainder.
+/*
+/* split_at_right() looks for the rightmost delimiter
+/* occurrence, but is otherwise identical to split_at().
+/* DIAGNOSTICS
+/* The result is a null pointer when the delimiter character
+/* was not found.
+/* HISTORY
+/* .ad
+/* .fi
+/* A split_at() routine appears in the TCP Wrapper software
+/* by Wietse Venema.
+/* 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 libraries */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "split_at.h"
+
+/* split_at - break string at first delimiter, return remainder */
+
+char *split_at(char *string, int delimiter)
+{
+ char *cp;
+
+ if ((cp = strchr(string, delimiter)) != 0)
+ *cp++ = 0;
+ return (cp);
+}
+
+/* split_at_right - break string at last delimiter, return remainder */
+
+char *split_at_right(char *string, int delimiter)
+{
+ char *cp;
+
+ if ((cp = strrchr(string, delimiter)) != 0)
+ *cp++ = 0;
+ return (cp);
+}
diff --git a/src/util/split_at.h b/src/util/split_at.h
new file mode 100644
index 0000000..2d03ebb
--- /dev/null
+++ b/src/util/split_at.h
@@ -0,0 +1,35 @@
+#ifndef _SPLIT_AT_H_INCLUDED_
+#define _SPLIT_AT_H_INCLUDED_
+
+/*++
+/* NAME
+/* split_at 3h
+/* SUMMARY
+/* trivial token splitter
+/* SYNOPSIS
+/* #include <split_at.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern char *split_at(char *, int);
+extern char *split_at_right(char *, int);
+
+/* HISTORY
+/* .ad
+/* .fi
+/* A split_at() routine appears in the TCP Wrapper software
+/* by Wietse Venema.
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/split_nameval.c b/src/util/split_nameval.c
new file mode 100644
index 0000000..da74fcf
--- /dev/null
+++ b/src/util/split_nameval.c
@@ -0,0 +1,97 @@
+/*++
+/* NAME
+/* split_nameval 3
+/* SUMMARY
+/* name-value splitter
+/* SYNOPSIS
+/* #include <split_nameval.h>
+/*
+/* const char *split_nameval(buf, name, value)
+/* char *buf;
+/* char **name;
+/* char **value;
+/* DESCRIPTION
+/* split_nameval() takes a logical line from readlline() and expects
+/* text of the form "name = value" or "name =". The buffer
+/* argument is broken up into name and value substrings.
+/*
+/* Arguments:
+/* .IP buf
+/* Result from readlline() or equivalent. The buffer is modified.
+/* .IP name
+/* Upon successful completion, this is set to the name
+/* substring.
+/* .IP value
+/* Upon successful completion, this is set to the value
+/* substring.
+/* FEATURES
+/* SEE ALSO
+/* dict(3) mid-level dictionary routines
+/* BUGS
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/*
+/* The result is a null pointer in case of success, a string
+/* describing the error otherwise: missing '=' after attribute
+/* name; missing attribute name.
+/* 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 libraries. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* split_nameval - split text into name and value */
+
+const char *split_nameval(char *buf, char **name, char **value)
+{
+ char *np; /* name substring */
+ char *vp; /* value substring */
+ char *cp;
+ char *ep;
+
+ /*
+ * Ugly macros to make complex expressions less unreadable.
+ */
+#define SKIP(start, var, cond) do { \
+ for (var = start; *var && (cond); var++) \
+ /* void */; \
+ } while (0)
+
+#define TRIM(s) do { \
+ char *p; \
+ for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--) \
+ /* void */; \
+ *p = 0; \
+ } while (0)
+
+ SKIP(buf, np, ISSPACE(*np)); /* find name begin */
+ if (*np == 0 || *np == '=')
+ return ("missing attribute name");
+ SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */
+ SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */
+ if (*cp != '=') /* need '=' */
+ return ("missing '=' after attribute name");
+ *ep = 0; /* terminate name */
+ cp++; /* skip over '=' */
+ SKIP(cp, vp, ISSPACE(*vp)); /* skip leading blanks */
+ TRIM(vp); /* trim trailing blanks */
+ *name = np;
+ *value = vp;
+ return (0);
+}
diff --git a/src/util/split_qnameval.c b/src/util/split_qnameval.c
new file mode 100644
index 0000000..b166125
--- /dev/null
+++ b/src/util/split_qnameval.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* split_qnameval 3
+/* SUMMARY
+/* name-value splitter
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* const char *split_qnameval(buf, name, value)
+/* char *buf;
+/* char **name;
+/* char **value;
+/* DESCRIPTION
+/* split_qnameval() expects text of the form "key = value"
+/* or "key =", where the key may be quoted with backslash or
+/* double quotes. The buffer argument is broken up into the key
+/* and value substrings.
+/*
+/* Arguments:
+/* .IP buf
+/* Result from readlline() or equivalent. The buffer is modified.
+/* .IP key
+/* Upon successful completion, this is set to the key
+/* substring.
+/* .IP value
+/* Upon successful completion, this is set to the value
+/* substring.
+/* SEE ALSO
+/* split_nameval(3) name-value splitter
+/* BUGS
+/* DIAGNOSTICS
+/* The result is a null pointer in case of success, a string
+/* describing the error otherwise: missing '=' after attribute
+/* name; missing attribute name.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* split_qnameval - split "key = value", support quoted key */
+
+const char *split_qnameval(char *buf, char **pkey, char **pvalue)
+{
+ int in_quotes = 0;
+ char *key;
+ char *key_end;
+ char *value;
+
+ for (key = buf; *key && ISSPACE(*key); key++)
+ /* void */ ;
+ if (*key == 0)
+ return ("no key found; expected format: key = value");
+
+ for (key_end = key; *key_end; key_end++) {
+ if (*key_end == '\\') {
+ if (*++key_end == 0)
+ break;
+ } else if (ISSPACE(*key_end) || *key_end == '=') {
+ if (!in_quotes)
+ break;
+ } else if (*key_end == '"') {
+ in_quotes = !in_quotes;
+ }
+ }
+ if (in_quotes) {
+ return ("unbalanced '\"\'");
+ }
+ value = key_end;
+ while (ISSPACE(*value))
+ value++;
+ if (*value != '=')
+ return ("missing '=' after attribute name");
+ *key_end = 0;
+ do {
+ value++;
+ } while (ISSPACE(*value));
+ trimblanks(value, 0)[0] = 0;
+ *pkey = key;
+ *pvalue = value;
+ return (0);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <mymalloc.h>
+
+static int compare(int test_number, const char *what,
+ const char *expect, const char *real)
+{
+ if ((expect == 0 && real == 0)
+ || (expect != 0 && real != 0 && strcmp(expect, real) == 0)) {
+ return (0);
+ } else {
+ msg_warn("test %d: %s mis-match: expect='%s', real='%s'",
+ test_number, what, expect ? expect : "(null)",
+ real ? real : "(null)");
+ return (1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct test_info {
+ const char *input;
+ const char *expect_result;
+ const char *expect_key;
+ const char *expect_value;
+ };
+ static const struct test_info test_info[] = {
+ /* Unquoted keys. */
+ {"xx = yy", 0, "xx", "yy"},
+ {"xx=yy", 0, "xx", "yy"},
+ {"xx =", 0, "xx", ""},
+ {"xx=", 0, "xx", ""},
+ {"xx", "missing '=' after attribute name", 0, 0},
+ /* Quoted keys. */
+ {"\"xx \" = yy", 0, "\"xx \"", "yy"},
+ {"\"xx \"= yy", 0, "\"xx \"", "yy"},
+ {"\"xx \" =", 0, "\"xx \"", ""},
+ {"\"xx \"=", 0, "\"xx \"", ""},
+ {"\"xx \"", "missing '=' after attribute name", 0, 0},
+ {"\"xx ", "unbalanced '\"'", 0, 0},
+ /* Backslash escapes. */
+ {"\"\\\"xx \" = yy", 0, "\"\\\"xx \"", "yy"},
+ {0,},
+ };
+
+ int errs = 0;
+ const struct test_info *tp;
+
+ for (tp = test_info; tp->input != 0; tp++) {
+ const char *result;
+ char *key = 0;
+ char *value = 0;
+ char *buf = mystrdup(tp->input);
+ int test_number = (int) (tp - test_info);
+
+ result = split_qnameval(buf, &key, &value);
+ errs += compare(test_number, "result", tp->expect_result, result);
+ errs += compare(test_number, "key", tp->expect_key, key);
+ errs += compare(test_number, "value", tp->expect_value, value);
+ myfree(buf);
+ }
+ exit(errs);
+}
+
+#endif
diff --git a/src/util/stat_as.c b/src/util/stat_as.c
new file mode 100644
index 0000000..3e05ff7
--- /dev/null
+++ b/src/util/stat_as.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* stat_as 3
+/* SUMMARY
+/* stat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <stat_as.h>
+/*
+/* int stat_as(path, st, euid, egid)
+/* const char *path;
+/* struct stat *st;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* stat_as() looks up the file status of the named \fIpath\fR,
+/* using the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR, and stores the result into the structure pointed
+/* to by \fIst\fR. A -1 result means the lookup failed.
+/* This call follows symbolic links.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* 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>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "stat_as.h"
+#include "warn_stat.h"
+
+/* stat_as - stat file as user */
+
+int stat_as(const char *path, struct stat * st, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int status;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Stat that file.
+ */
+ status = stat(path, st);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (status);
+}
diff --git a/src/util/stat_as.h b/src/util/stat_as.h
new file mode 100644
index 0000000..90bc05d
--- /dev/null
+++ b/src/util/stat_as.h
@@ -0,0 +1,35 @@
+#ifndef _STAT_AS_H_INCLUDED_
+#define _STAT_AS_H_INCLUDED_
+
+/*++
+/* NAME
+/* stat_as 3h
+/* SUMMARY
+/* stat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <stat_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT stat_as(const char *, struct stat *, uid_t, gid_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/strcasecmp.c b/src/util/strcasecmp.c
new file mode 100644
index 0000000..07e9381
--- /dev/null
+++ b/src/util/strcasecmp.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strcasecmp.c 8.1 (Berkeley) 6/4/93";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+int strcasecmp(const char *s1, const char *s2)
+{
+ const unsigned char *us1 = (const unsigned char *) s1;
+ const unsigned char *us2 = (const unsigned char *) s2;
+
+ while (tolower(*us1) == tolower(*us2++))
+ if (*us1++ == '\0')
+ return (0);
+ return (tolower(*us1) - tolower(*--us2));
+}
+
+int strncasecmp(const char *s1, const char *s2, size_t n)
+{
+ if (n != 0) {
+ const unsigned char *us1 = (const unsigned char *) s1;
+ const unsigned char *us2 = (const unsigned char *) s2;
+
+ do {
+ if (tolower(*us1) != tolower(*us2++))
+ return (tolower(*us1) - tolower(*--us2));
+ if (*us1++ == '\0')
+ break;
+ } while (--n != 0);
+ }
+ return (0);
+}
diff --git a/src/util/strcasecmp_utf8.c b/src/util/strcasecmp_utf8.c
new file mode 100644
index 0000000..e3f20df
--- /dev/null
+++ b/src/util/strcasecmp_utf8.c
@@ -0,0 +1,216 @@
+/*++
+/* NAME
+/* strcasecmp_utf8 3
+/* SUMMARY
+/* caseless string comparison
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int strcasecmp_utf8(
+/* const char *s1,
+/* const char *s2)
+/*
+/* int strncasecmp_utf8(
+/* const char *s1,
+/* const char *s2,
+/* ssize_t len)
+/* AUXILIARY FUNCTIONS
+/* int strcasecmp_utf8x(
+/* int flags,
+/* const char *s1,
+/* const char *s2)
+/*
+/* int strncasecmp_utf8x(
+/* int flags,
+/* const char *s1,
+/* const char *s2,
+/* ssize_t len)
+/* DESCRIPTION
+/* strcasecmp_utf8() implements caseless string comparison for
+/* UTF-8 text, with an API similar to strcasecmp(). Only ASCII
+/* characters are casefolded when the code is compiled without
+/* EAI support or when util_utf8_enable is zero.
+/*
+/* strncasecmp_utf8() implements caseless string comparison
+/* for UTF-8 text, with an API similar to strncasecmp(). Only
+/* ASCII characters are casefolded when the code is compiled
+/* without EAI support or when util_utf8_enable is zero.
+/*
+/* strcasecmp_utf8x() and strncasecmp_utf8x() implement a more
+/* complex API that provides the above functionality and more.
+/*
+/* Arguments:
+/* .IP "s1, s2"
+/* Null-terminated strings to be compared.
+/* .IP len
+/* String length before casefolding.
+/* .IP flags
+/* Zero or CASEF_FLAG_UTF8. The latter flag enables UTF-8 case
+/* folding instead of folding only ASCII characters. This flag
+/* is ignored when compiled without EAI support.
+/* SEE ALSO
+/* casefold(), casefold text for caseless comparison.
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+
+static VSTRING *f1; /* casefold result for s1 */
+static VSTRING *f2; /* casefold result for s2 */
+
+/* strcasecmp_utf8_init - initialize */
+
+static void strcasecmp_utf8_init(void)
+{
+ f1 = vstring_alloc(100);
+ f2 = vstring_alloc(100);
+}
+
+/* strcasecmp_utf8x - caseless string comparison */
+
+int strcasecmp_utf8x(int flags, const char *s1, const char *s2)
+{
+
+ /*
+ * Short-circuit optimization for ASCII-only text. This may be slower
+ * than using a cache for all results. We must not expose strcasecmp(3)
+ * to non-ASCII text.
+ */
+ if (allascii(s1) && allascii(s2))
+ return (strcasecmp(s1, s2));
+
+ if (f1 == 0)
+ strcasecmp_utf8_init();
+
+ /*
+ * Cross our fingers and hope that strcmp() remains agnostic of
+ * charactersets and locales.
+ */
+ flags &= CASEF_FLAG_UTF8;
+ casefoldx(flags, f1, s1, -1);
+ casefoldx(flags, f2, s2, -1);
+ return (strcmp(STR(f1), STR(f2)));
+}
+
+/* strncasecmp_utf8x - caseless string comparison */
+
+int strncasecmp_utf8x(int flags, const char *s1, const char *s2,
+ ssize_t len)
+{
+
+ /*
+ * Consider using a cache for all results.
+ */
+ if (f1 == 0)
+ strcasecmp_utf8_init();
+
+ /*
+ * Short-circuit optimization for ASCII-only text. This may be slower
+ * than using a cache for all results. See comments above for limitations
+ * of strcasecmp().
+ */
+ if (allascii_len(s1, len) && allascii_len(s2, len))
+ return (strncasecmp(s1, s2, len));
+
+ /*
+ * Caution: casefolding may change the number of bytes. See comments
+ * above for concerns about strcmp().
+ */
+ flags &= CASEF_FLAG_UTF8;
+ casefoldx(flags, f1, s1, len);
+ casefoldx(flags, f2, s2, len);
+ return (strcmp(STR(f1), STR(f2)));
+}
+
+#ifdef TEST
+#include <stdio.h>
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+#include <argv.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ ARGV *cmd;
+ char **args;
+ int len;
+ int flags;
+ int res;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ flags = CASEF_FLAG_UTF8;
+ util_utf8_enable = 1;
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ vstream_printf("> %s\n", STR(buffer));
+ cmd = argv_split(STR(buffer), CHARS_SPACE);
+ if (cmd->argc == 0 || cmd->argv[0][0] == '#')
+ continue;
+ args = cmd->argv;
+
+ /*
+ * Compare two strings.
+ */
+ if (strcmp(args[0], "compare") == 0 && cmd->argc == 3) {
+ res = strcasecmp_utf8x(flags, args[1], args[2]);
+ vstream_printf("\"%s\" %s \"%s\"\n",
+ args[1],
+ res < 0 ? "<" : res == 0 ? "==" : ">",
+ args[2]);
+ }
+
+ /*
+ * Compare two substrings.
+ */
+ else if (strcmp(args[0], "compare-len") == 0 && cmd->argc == 4
+ && sscanf(args[3], "%d", &len) == 1 && len >= 0) {
+ res = strncasecmp_utf8x(flags, args[1], args[2], len);
+ vstream_printf("\"%.*s\" %s \"%.*s\"\n",
+ len, args[1],
+ res < 0 ? "<" : res == 0 ? "==" : ">",
+ len, args[2]);
+ }
+
+ /*
+ * Usage.
+ */
+ else {
+ vstream_printf("Usage: %s compare <s1> <s2> | compare-len <s1> <s2> <len>\n",
+ argv[0]);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(cmd);
+ }
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/src/util/strcasecmp_utf8_test.in b/src/util/strcasecmp_utf8_test.in
new file mode 100644
index 0000000..8bafbf0
--- /dev/null
+++ b/src/util/strcasecmp_utf8_test.in
@@ -0,0 +1,10 @@
+compare Δημοσθένους.example.com δημοσθένουσ.example.com
+compare Δημοσθένους.example.com ηδμοσθένουσ.example.com
+compare ηδμοσθένουσ.example.com Δημοσθένους.example.com
+compare HeLlO.ExAmPlE.CoM hello.example.com
+compare HeLlO hellp
+compare hellp HeLlO
+compare-len HeLlO hellp 4
+compare-len HeLO help 4
+compare abcde abcdf
+compare YYY€€€XXX yyy€€€xxx
diff --git a/src/util/strcasecmp_utf8_test.ref b/src/util/strcasecmp_utf8_test.ref
new file mode 100644
index 0000000..8c0e1a5
--- /dev/null
+++ b/src/util/strcasecmp_utf8_test.ref
@@ -0,0 +1,20 @@
+> compare Δημοσθένους.example.com δημοσθένουσ.example.com
+"Δημοσθένους.example.com" == "δημοσθένουσ.example.com"
+> compare Δημοσθένους.example.com ηδμοσθένουσ.example.com
+"Δημοσθένους.example.com" < "ηδμοσθένουσ.example.com"
+> compare ηδμοσθένουσ.example.com Δημοσθένους.example.com
+"ηδμοσθένουσ.example.com" > "Δημοσθένους.example.com"
+> compare HeLlO.ExAmPlE.CoM hello.example.com
+"HeLlO.ExAmPlE.CoM" == "hello.example.com"
+> compare HeLlO hellp
+"HeLlO" < "hellp"
+> compare hellp HeLlO
+"hellp" > "HeLlO"
+> compare-len HeLlO hellp 4
+"HeLl" == "hell"
+> compare-len HeLO help 4
+"HeLO" < "help"
+> compare abcde abcdf
+"abcde" < "abcdf"
+> compare YYY€€€XXX yyy€€€xxx
+"YYY€€€XXX" == "yyy€€€xxx"
diff --git a/src/util/stream_connect.c b/src/util/stream_connect.c
new file mode 100644
index 0000000..b8cc624
--- /dev/null
+++ b/src/util/stream_connect.c
@@ -0,0 +1,109 @@
+/*++
+/* NAME
+/* stream_connect 3
+/* SUMMARY
+/* connect to stream listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int stream_connect(path, block_mode, timeout)
+/* const char *path;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* stream_connect() connects to a stream listener for the specified
+/* pathname, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with listener endpoint name.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking stream, or BLOCKING for
+/* blocking mode. However, a stream connection succeeds or fails
+/* immediately.
+/* .IP timeout
+/* This argument is ignored; it is present for compatibility with
+/* other interfaces. Stream connections succeed or fail immediately.
+/* DIAGNOSTICS
+/* The result is -1 in case the connection could not be made.
+/* Fatal errors: other system call failures.
+/* 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>
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stropts.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+
+/* stream_connect - connect to stream listener */
+
+int stream_connect(const char *path, int block_mode, int unused_timeout)
+{
+#ifdef STREAM_CONNECTIONS
+ const char *myname = "stream_connect";
+ int pair[2];
+ int fifo;
+
+ /*
+ * The requested file system object must exist, otherwise we can't reach
+ * the server.
+ */
+ if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0)
+ return (-1);
+
+ /*
+ * This is for {unix,inet}_connect() compatibility.
+ */
+ if (block_mode == BLOCKING)
+ non_blocking(fifo, BLOCKING);
+
+ /*
+ * Create a pipe, and send one pipe end to the server.
+ */
+ if (pipe(pair) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ if (ioctl(fifo, I_SENDFD, pair[1]) < 0)
+ msg_fatal("%s: send file descriptor: %m", myname);
+ close(pair[1]);
+
+ /*
+ * This is for {unix,inet}_connect() compatibility.
+ */
+ if (block_mode == NON_BLOCKING)
+ non_blocking(pair[0], NON_BLOCKING);
+
+ /*
+ * Cleanup.
+ */
+ close(fifo);
+
+ /*
+ * Keep the other end of the pipe.
+ */
+ return (pair[0]);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
diff --git a/src/util/stream_listen.c b/src/util/stream_listen.c
new file mode 100644
index 0000000..b522b76
--- /dev/null
+++ b/src/util/stream_listen.c
@@ -0,0 +1,102 @@
+/*++
+/* NAME
+/* stream_listen 3
+/* SUMMARY
+/* start stream listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int stream_listen(path, backlog, block_mode)
+/* const char *path;
+/* int backlog;
+/* int block_mode;
+/*
+/* int stream_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* This module implements a substitute local IPC for systems that do
+/* not have properly-working UNIX-domain sockets.
+/*
+/* stream_listen() creates a listener endpoint with the specified
+/* permissions, and returns a file descriptor to be used for accepting
+/* connections.
+/*
+/* stream_accept() accepts a connection.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* This argument exists for compatibility and is ignored.
+/* .IP block_mode
+/* Either NON_BLOCKING or BLOCKING. This does not affect the
+/* mode of accepted connections.
+/* .IP fd
+/* File descriptor returned by stream_listen().
+/* DIAGNOSTICS
+/* Fatal errors: stream_listen() aborts upon any system call failure.
+/* stream_accept() leaves all error handling up to the caller.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stropts.h>
+#include <fcntl.h>
+
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "listen.h"
+
+/* stream_listen - create stream listener */
+
+int stream_listen(const char *path, int unused_backlog, int block_mode)
+{
+#ifdef STREAM_CONNECTIONS
+
+ /*
+ * We can't specify a listen backlog, however, sending file descriptors
+ * across a FIFO gives us a backlog buffer of 460 on Solaris 2.4/SPARC.
+ */
+ return (fifo_listen(path, 0622, block_mode));
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+/* stream_accept - accept stream connection */
+
+int stream_accept(int fd)
+{
+#ifdef STREAM_CONNECTIONS
+ struct strrecvfd fdinfo;
+
+ /*
+ * This will return EAGAIN on a non-blocking stream when someone else
+ * snatched the connection from us.
+ */
+ if (ioctl(fd, I_RECVFD, &fdinfo) < 0)
+ return (-1);
+ return (fdinfo.fd);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
diff --git a/src/util/stream_recv_fd.c b/src/util/stream_recv_fd.c
new file mode 100644
index 0000000..5be02e4
--- /dev/null
+++ b/src/util/stream_recv_fd.c
@@ -0,0 +1,120 @@
+/*++
+/* NAME
+/* stream_recv_fd 3
+/* SUMMARY
+/* receive file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int stream_recv_fd(fd)
+/* int fd;
+/* DESCRIPTION
+/* stream_recv_fd() receives a file descriptor via the specified
+/* stream. The result value is the received descriptor.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* DIAGNOSTICS
+/* stream_recv_fd() returns -1 upon failure.
+/* 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> /* includes <sys/types.h> */
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stropts.h>
+#include <fcntl.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* stream_recv_fd - receive file descriptor */
+
+int stream_recv_fd(int fd)
+{
+#ifdef STREAM_CONNECTIONS
+ struct strrecvfd fdinfo;
+
+ /*
+ * This will return EAGAIN on a non-blocking stream when someone else
+ * snatched the connection from us.
+ */
+ if (ioctl(fd, I_RECVFD, &fdinfo) < 0)
+ return (-1);
+ return (fdinfo.fd);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Receive a descriptor (presumably from the
+ * stream_send_fd test program) and copy its content until EOF.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <listen.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ int listen_sock;
+ int client_sock;
+ int client_fd;
+ ssize_t read_count;
+ char buf[1024];
+
+ if (argc != 2
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint", argv[0]);
+
+ if (strcmp(transport, "stream") == 0) {
+ listen_sock = stream_listen(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (listen_sock < 0)
+ msg_fatal("listen %s:%s: %m", transport, endpoint);
+
+ client_sock = stream_accept(listen_sock);
+ if (client_sock < 0)
+ msg_fatal("stream_accept: %m");
+
+ while ((client_fd = stream_recv_fd(client_sock)) >= 0) {
+ msg_info("client_fd = %d", client_fd);
+ while ((read_count = read(client_fd, buf, sizeof(buf))) > 0)
+ write(1, buf, read_count);
+ if (read_count < 0)
+ msg_fatal("read: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/stream_send_fd.c b/src/util/stream_send_fd.c
new file mode 100644
index 0000000..0a9aebf
--- /dev/null
+++ b/src/util/stream_send_fd.c
@@ -0,0 +1,115 @@
+/*++
+/* NAME
+/* stream_send_fd 3
+/* SUMMARY
+/* send file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int stream_send_fd(fd, sendfd)
+/* int fd;
+/* int sendfd;
+/* DESCRIPTION
+/* stream_send_fd() sends a file descriptor over the specified
+/* stream.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* .IP sendfd
+/* The file descriptor to be sent.
+/* DIAGNOSTICS
+/* stream_send_fd() returns -1 upon failure.
+/* 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> /* includes <sys/types.h> */
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stropts.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* stream_send_fd - send file descriptor */
+
+int stream_send_fd(int fd, int sendfd)
+{
+#ifdef STREAM_CONNECTIONS
+ const char *myname = "stream_send_fd";
+
+ if (ioctl(fd, I_SENDFD, sendfd) < 0)
+ msg_fatal("%s: send file descriptor %d: %m", myname, sendfd);
+ return (0);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Open a file and send the descriptor, presumably
+ * to the stream_recv_fd test program.
+ */
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <connect.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ char *path;
+ int server_sock;
+ int client_fd;
+
+ if (argc < 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint file...", argv[0]);
+
+ if (strcmp(transport, "stream") == 0) {
+ server_sock = stream_connect(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (server_sock < 0)
+ msg_fatal("connect %s:%s: %m", transport, endpoint);
+
+ argv += 2;
+ while ((path = *argv++) != 0) {
+ if ((client_fd = open(path, O_RDONLY, 0)) < 0)
+ msg_fatal("open %s: %m", path);
+ msg_info("path=%s client_fd=%d", path, client_fd);
+ if (stream_send_fd(server_sock, client_fd) < 0)
+ msg_fatal("send file descriptor: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/stream_test.c b/src/util/stream_test.c
new file mode 100644
index 0000000..5c8f82f
--- /dev/null
+++ b/src/util/stream_test.c
@@ -0,0 +1,111 @@
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "iostuff.h"
+#include "msg.h"
+#include "msg_vstream.h"
+#include "listen.h"
+#include "connect.h"
+
+#ifdef SUNOS5
+#include <stropts.h>
+
+#define FIFO "/tmp/test-fifo"
+
+static const char *progname;
+
+static void print_fstat(int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ msg_fatal("fstat: %m");
+ vstream_printf("fd %d\n", fd);
+ vstream_printf("dev %ld\n", (long) st.st_dev);
+ vstream_printf("ino %ld\n", (long) st.st_ino);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s [-p] [-n count] [-v]", progname);
+}
+
+int main(int argc, char **argv)
+{
+ int server_fd;
+ int client_fd;
+ int fd;
+ int print_fstats = 0;
+ int count = 1;
+ int ch;
+ int i;
+
+ progname = argv[0];
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "pn:v")) > 0) {
+ switch (ch) {
+ default:
+ usage();
+ case 'p':
+ print_fstats = 1;
+ break;
+ case 'n':
+ if ((count = atoi(optarg)) < 1)
+ usage();
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ server_fd = stream_listen(FIFO, 0, 0);
+ if (readable(server_fd))
+ msg_fatal("server fd is readable after create");
+
+ /*
+ * Connect in client.
+ */
+ for (i = 0; i < count; i++) {
+ msg_info("connect attempt %d", i);
+ if ((client_fd = stream_connect(FIFO, 0, 0)) < 0)
+ msg_fatal("open %s as client: %m", FIFO);
+ if (readable(server_fd))
+ msg_info("server fd is readable after client open");
+ if (close(client_fd) < 0)
+ msg_fatal("close client fd: %m");
+ }
+
+ /*
+ * Accept in server.
+ */
+ for (i = 0; i < count; i++) {
+ msg_info("receive attempt %d", i);
+ if (!readable(server_fd)) {
+ msg_info("wait for server fd to become readable");
+ read_wait(server_fd, -1);
+ }
+ if ((fd = stream_accept(server_fd)) < 0)
+ msg_fatal("receive fd: %m");
+ if (print_fstats)
+ print_fstat(fd);
+ if (close(fd) < 0)
+ msg_fatal("close received fd: %m");
+ }
+ if (close(server_fd) < 0)
+ msg_fatal("close server fd");
+ return (0);
+}
+#else
+int main(int argc, char **argv)
+{
+ return (0);
+}
+#endif
diff --git a/src/util/stream_trigger.c b/src/util/stream_trigger.c
new file mode 100644
index 0000000..4feea5f
--- /dev/null
+++ b/src/util/stream_trigger.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* stream_trigger 3
+/* SUMMARY
+/* wakeup stream server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int stream_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* stream_trigger() wakes up the named stream server by making
+/* a brief connection to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* stream_connect(3), stream client
+/* 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 <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct stream_trigger {
+ int fd;
+ char *service;
+};
+
+/* stream_trigger_event - disconnect from peer */
+
+static void stream_trigger_event(int event, void *context)
+{
+ struct stream_trigger *sp = (struct stream_trigger *) context;
+ static const char *myname = "stream_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, sp->service);
+ event_disable_readwrite(sp->fd);
+ event_cancel_timer(stream_trigger_event, context);
+ if (close(sp->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, sp->service);
+ myfree(sp->service);
+ myfree((void *) sp);
+}
+
+/* stream_trigger - wakeup stream server */
+
+int stream_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "stream_trigger";
+ struct stream_trigger *sp;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = stream_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Stash away context.
+ */
+ sp = (struct stream_trigger *) mymalloc(sizeof(*sp));
+ sp->fd = fd;
+ sp->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(stream_trigger_event, (void *) sp, timeout + 100);
+ event_enable_read(fd, stream_trigger_event, (void *) sp);
+ return (0);
+}
diff --git a/src/util/stringops.h b/src/util/stringops.h
new file mode 100644
index 0000000..8ac177b
--- /dev/null
+++ b/src/util/stringops.h
@@ -0,0 +1,107 @@
+#ifndef _STRINGOPS_H_INCLUDED_
+#define _STRINGOPS_H_INCLUDED_
+
+/*++
+/* NAME
+/* stringops 3h
+/* SUMMARY
+/* string operations
+/* SYNOPSIS
+/* #include <stringops.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern int util_utf8_enable;
+extern char *printable_except(char *, int, const char *);
+extern char *neuter(char *, const char *, int);
+extern char *lowercase(char *);
+extern char *casefoldx(int, VSTRING *, const char *, ssize_t);
+extern char *uppercase(char *);
+extern char *skipblanks(const char *);
+extern char *trimblanks(char *, ssize_t);
+extern char *concatenate(const char *,...);
+extern char *mystrtok(char **, const char *);
+extern char *mystrtokq(char **, const char *, const char *);
+extern char *mystrtokdq(char **, const char *);
+extern char *translit(char *, const char *, const char *);
+
+#define printable(string, replacement) \
+ printable_except((string), (replacement), (char *) 0)
+
+#ifndef HAVE_BASENAME
+#define basename postfix_basename
+extern char *basename(const char *);
+
+#endif
+extern char *sane_basename(VSTRING *, const char *);
+extern char *sane_dirname(VSTRING *, const char *);
+extern VSTRING *unescape(VSTRING *, const char *);
+extern VSTRING *escape(VSTRING *, const char *, ssize_t);
+extern int alldig(const char *);
+extern int allalnum(const char *);
+extern int allprint(const char *);
+extern int allspace(const char *);
+extern int allascii_len(const char *, ssize_t);
+extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **);
+extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **);
+extern int valid_utf8_string(const char *, ssize_t);
+extern size_t balpar(const char *, const char *);
+extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int);
+extern int strcasecmp_utf8x(int, const char *, const char *);
+extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t);
+
+#define EXTPAR_FLAG_NONE (0)
+#define EXTPAR_FLAG_STRIP (1<<0) /* "{ text }" -> "text" */
+#define EXTPAR_FLAG_EXTRACT (1<<1) /* hint from caller's caller */
+
+#define CASEF_FLAG_UTF8 (1<<0)
+#define CASEF_FLAG_APPEND (1<<1)
+
+ /*
+ * Convenience wrappers for most-common use cases.
+ */
+#define allascii(s) allascii_len((s), -1)
+#define casefold(dst, src) \
+ casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), -1)
+#define casefold_len(dst, src, len) \
+ casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), (len))
+#define casefold_append(dst, src) \
+ casefoldx((util_utf8_enable ? CASEF_FLAG_UTF8 : 0) | CASEF_FLAG_APPEND, \
+ (dst), (src), -1)
+
+#define strcasecmp_utf8(s1, s2) \
+ strcasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2))
+#define strncasecmp_utf8(s1, s2, l) \
+ strncasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2), (l))
+
+ /*
+ * Use STRREF(x) instead of x, to shut up compiler warnings when the operand
+ * is a string literal.
+ */
+#define STRREF(x) (&x[0])
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/surrogate.ref b/src/util/surrogate.ref
new file mode 100644
index 0000000..b8ecd8e
--- /dev/null
+++ b/src/util/surrogate.ref
@@ -0,0 +1,55 @@
+./dict_open: error: cidr:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. cidr:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: pcre:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. pcre:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: regexp:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. regexp:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: unix:xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: unix:xx is unavailable. unix:xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: unknown table: unix:xx
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: unix:xx is unavailable. unknown table: unix:xx
+foo: error
+./dict_open: error: texthash:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. texthash:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open database /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. open database /xx: No such file or directory
+foo: error
+./dict_open: error: open database /xx.db: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: hash:/xx is unavailable. open database /xx.db: No such file or directory
+foo: error
diff --git a/src/util/sys_compat.c b/src/util/sys_compat.c
new file mode 100644
index 0000000..8bf8e58
--- /dev/null
+++ b/src/util/sys_compat.c
@@ -0,0 +1,389 @@
+/*++
+/* NAME
+/* sys_compat 3
+/* SUMMARY
+/* compatibility routines
+/* SYNOPSIS
+/* #include <sys_defs.h>
+/*
+/* void closefrom(int lowfd)
+/* int lowfd;
+/*
+/* const char *strerror(err)
+/* int err;
+/*
+/* int setenv(name, value, clobber)
+/* const char *name;
+/* const char *value;
+/* int clobber;
+/*
+/* int unsetenv(name)
+/* const char *name;
+/*
+/* int seteuid(euid)
+/* uid_t euid;
+/*
+/* int setegid(egid)
+/* gid_t euid;
+/*
+/* int mkfifo(path, mode)
+/* char *path;
+/* int mode;
+/*
+/* int waitpid(pid, statusp, options)
+/* int pid;
+/* WAIT_STATUS_T *statusp;
+/* int options;
+/*
+/* int setsid()
+/*
+/* void dup2_pass_on_exec(int oldd, int newd)
+/*
+/* char *inet_ntop(af, src, dst, size)
+/* int af;
+/* const void *src;
+/* char *dst;
+/* SOCKADDR_SIZE size;
+/*
+/* int inet_pton(af, src, dst)
+/* int af;
+/* const char *src;
+/* void *dst;
+/* DESCRIPTION
+/* These routines are compiled for platforms that lack the functionality
+/* or that have broken versions that we prefer to stay away from.
+/* 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"
+
+ /*
+ * ANSI strerror() emulation
+ */
+#ifdef MISSING_STRERROR
+
+extern int errno;
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+#include <vstring.h>
+
+/* strerror - print text corresponding to error */
+
+const char *strerror(int err)
+{
+ static VSTRING *buf;
+
+ if (err < 0 || err >= sys_nerr) {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_sprintf(buf, "Unknown error %d", err);
+ return (vstring_str(buf));
+ } else {
+ return (sys_errlist[errno]);
+ }
+}
+
+#endif
+
+ /*
+ * setenv() emulation on top of putenv().
+ */
+#ifdef MISSING_SETENV
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* setenv - update or insert environment (name,value) pair */
+
+int setenv(const char *name, const char *value, int clobber)
+{
+ char *cp;
+
+ if (clobber == 0 && getenv(name) != 0)
+ return (0);
+ if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0)
+ return (1);
+ sprintf(cp, "%s=%s", name, value);
+ return (putenv(cp));
+}
+
+/* unsetenv - remove all instances of the name */
+
+int unsetenv(const char *name)
+{
+ extern char **environ;
+ ssize_t name_len = strlen(name);
+ char **src_pp;
+ char **dst_pp;
+
+ for (dst_pp = src_pp = environ; *src_pp; src_pp++, dst_pp++) {
+ if (strncmp(*src_pp, name, name_len) == 0
+ && *(*src_pp + name_len) == '=') {
+ dst_pp--;
+ } else if (dst_pp != src_pp) {
+ *dst_pp = *src_pp;
+ }
+ }
+ *dst_pp = 0;
+ return (0);
+}
+
+#endif
+
+ /*
+ * seteuid() and setegid() emulation, the HP-UX way
+ */
+#ifdef MISSING_SETEUID
+#ifdef HAVE_SETRESUID
+#include <unistd.h>
+
+int seteuid(uid_t euid)
+{
+ return setresuid(-1, euid, -1);
+}
+
+#else
+#error MISSING_SETEUID
+#endif
+
+#endif
+
+#ifdef MISSING_SETEGID
+#ifdef HAVE_SETRESGID
+#include <unistd.h>
+
+int setegid(gid_t egid)
+{
+ return setresgid(-1, egid, -1);
+}
+
+#else
+#error MISSING_SETEGID
+#endif
+
+#endif
+
+ /*
+ * mkfifo() emulation - requires superuser privileges
+ */
+#ifdef MISSING_MKFIFO
+
+#include <sys/stat.h>
+
+int mkfifo(char *path, int mode)
+{
+ return mknod(path, (mode & ~_S_IFMT) | _S_IFIFO, 0);
+}
+
+#endif
+
+ /*
+ * waitpid() emulation on top of Berkeley UNIX wait4()
+ */
+#ifdef MISSING_WAITPID
+#ifdef HAS_WAIT4
+
+#include <sys/wait.h>
+#include <errno.h>
+
+int waitpid(int pid, WAIT_STATUS_T *status, int options)
+{
+ if (pid == -1)
+ pid = 0;
+ return wait4(pid, status, options, (struct rusage *) 0);
+}
+
+#else
+#error MISSING_WAITPID
+#endif
+
+#endif
+
+ /*
+ * setsid() emulation, the Berkeley UNIX way
+ */
+#ifdef MISSING_SETSID
+
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef TIOCNOTTY
+
+#include <msg.h>
+
+int setsid(void)
+{
+ int p = getpid();
+ int fd;
+
+ if (setpgrp(p, p))
+ return -1;
+
+ fd = open("/dev/tty", O_RDONLY, 0);
+ if (fd >= 0 || errno != ENXIO) {
+ if (fd < 0) {
+ msg_warn("open /dev/tty: %m");
+ return -1;
+ }
+ if (ioctl(fd, TIOCNOTTY, 0)) {
+ msg_warn("ioctl TIOCNOTTY: %m");
+ return -1;
+ }
+ close(fd);
+ }
+ return 0;
+}
+
+#else
+#error MISSING_SETSID
+#endif
+
+#endif
+
+ /*
+ * dup2_pass_on_exec() - dup2() and clear close-on-exec flag on the result
+ */
+#ifdef DUP2_DUPS_CLOSE_ON_EXEC
+
+#include "iostuff.h"
+
+int dup2_pass_on_exec(int oldd, int newd)
+{
+ int res;
+
+ if ((res = dup2(oldd, newd)) >= 0)
+ close_on_exec(newd, PASS_ON_EXEC);
+
+ return res;
+}
+
+#endif
+
+#ifndef HAS_CLOSEFROM
+
+#include <unistd.h>
+#include <errno.h>
+#include <iostuff.h>
+
+/* closefrom() - closes all file descriptors from the given one up */
+
+int closefrom(int lowfd)
+{
+ int fd_limit = open_limit(0);
+ int fd;
+
+ /*
+ * lowfrom does not have an easy to determine upper limit. A process may
+ * have files open that were inherited from a parent process with a less
+ * restrictive resource limit.
+ */
+ if (lowfd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (fd_limit > 500)
+ fd_limit = 500;
+ for (fd = lowfd; fd < fd_limit; fd++)
+ (void) close(fd);
+
+ return (0);
+}
+
+#endif
+
+#ifdef MISSING_INET_NTOP
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+/* inet_ntop - convert binary address to printable address */
+
+const char *inet_ntop(int af, const void *src, char *dst, SOCKADDR_SIZE size)
+{
+ const unsigned char *addr;
+ char buffer[sizeof("255.255.255.255")];
+ int len;
+
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+ addr = (const unsigned char *) src;
+#if (CHAR_BIT > 8)
+ sprintf(buffer, "%d.%d.%d.%d", addr[0] & 0xff,
+ addr[1] & 0xff, addr[2] & 0xff, addr[3] & 0xff);
+#else
+ sprintf(buffer, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
+#endif
+ if ((len = strlen(buffer)) >= size) {
+ errno = ENOSPC;
+ return (0);
+ } else {
+ memcpy(dst, buffer, len + 1);
+ return (dst);
+ }
+}
+
+#endif
+
+#ifdef MISSING_INET_PTON
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+/* inet_pton - convert printable address to binary address */
+
+int inet_pton(int af, const char *src, void *dst)
+{
+ struct in_addr addr;
+
+ /*
+ * inet_addr() accepts a wider range of input formats than inet_pton();
+ * the former accepts 1-, 2-, or 3-part dotted addresses, while the
+ * latter requires dotted quad form.
+ */
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (-1);
+ } else if ((addr.s_addr = inet_addr(src)) == INADDR_NONE
+ && strcmp(src, "255.255.255.255") != 0) {
+ return (0);
+ } else {
+ memcpy(dst, (void *) &addr, sizeof(addr));
+ return (1);
+ }
+}
+
+#endif
diff --git a/src/util/sys_defs.h b/src/util/sys_defs.h
new file mode 100644
index 0000000..37e460f
--- /dev/null
+++ b/src/util/sys_defs.h
@@ -0,0 +1,1797 @@
+#ifndef _SYS_DEFS_H_INCLUDED_
+#define _SYS_DEFS_H_INCLUDED_
+
+/*++
+/* NAME
+/* sys_defs 3h
+/* SUMMARY
+/* portability header
+/* SYNOPSIS
+/* #include <sys_defs.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Specific platforms. Major release numbers differ for a good reason. So be
+ * a good girl, plan for the future, and at least include the major release
+ * number in the system type (for example, SUNOS5 or FREEBSD2). The system
+ * type is determined by the makedefs shell script in the top-level
+ * directory. Adding support for a new system type means updating the
+ * makedefs script, and adding a section below for the new system.
+ */
+#ifdef SUNOS5
+#define _SVID_GETTOD /* Solaris 2.5, XSH4.2 versus SVID */
+#endif
+#include <sys/types.h>
+
+ /*
+ * 4.4BSD and close derivatives.
+ */
+#if defined(FREEBSD2) || defined(FREEBSD3) || defined(FREEBSD4) \
+ || defined(FREEBSD5) || defined(FREEBSD6) || defined(FREEBSD7) \
+ || defined(FREEBSD8) || defined(FREEBSD9) || defined(FREEBSD10) \
+ || defined(FREEBSD11) || defined(FREEBSD12) || defined(FREEBSD13) \
+ || defined(FREEBSD14) \
+ || defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \
+ || defined(OPENBSD2) || defined(OPENBSD3) || defined(OPENBSD4) \
+ || defined(OPENBSD5) || defined(OPENBSD6) || defined(OPENBSD7) \
+ || defined(NETBSD1) || defined(NETBSD2) || defined(NETBSD3) \
+ || defined(NETBSD4) || defined(NETBSD5) || defined(NETBSD6) \
+ || defined(NETBSD7) | defined(NETBSD8) || defined(NETBSD9) \
+ || defined(NETBSD10) \
+ || defined(EKKOBSD1) || defined(DRAGONFLY)
+#define SUPPORTED
+#include <sys/param.h>
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_SUN_LEN
+#define HAS_FSYNC
+#define HAS_DB
+#define HAS_SA_LEN
+#define NATIVE_DB_TYPE "hash"
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000)
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* sendmail 8.10 */
+#endif
+#if (defined(OpenBSD) && OpenBSD >= 200006)
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* OpenBSD 2.7 */
+#endif
+#ifndef ALIAS_DB_MAP
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ > 299000900)
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#else
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#endif
+#define HAS_POSIX_REGEXP
+#define HAS_ST_GEN /* struct stat contains inode
+ * generation number */
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define HAS_DLOPEN
+#endif
+
+#ifdef FREEBSD2
+#define getsid(p) getpgrp()
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len))
+#endif
+#ifndef CMSG_LEN
+#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+#ifndef CMSG_ALIGN
+#define CMSG_ALIGN(n) ALIGN(n)
+#endif
+#endif /* FREEBSD2 */
+
+#ifdef BSDI4
+/* #define HAS_IPV6 find out interface lookup method */
+#endif
+
+/* __FreeBSD_version version is major+minor */
+
+#if __FreeBSD_version >= 220000
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced 2.1.5 */
+#endif
+
+#if __FreeBSD_version >= 300000
+#define HAS_ISSETUGID
+#define HAS_FUTIMES
+#endif
+
+#if __FreeBSD_version >= 400000
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if __FreeBSD_version >= 420000
+#define HAS_DUPLEX_PIPE /* 4.1 breaks with kqueue(2) */
+#endif
+
+#if (__FreeBSD_version >= 702104 && __FreeBSD_version <= 800000) \
+ || __FreeBSD_version >= 800100
+#define HAS_CLOSEFROM
+#endif
+
+/* OpenBSD version is year+month */
+
+#if OpenBSD >= 199805 /* XXX */
+#define HAS_FUTIMES /* XXX maybe earlier */
+#endif
+
+#if (defined(OpenBSD) && OpenBSD >= 199608 && OpenBSD < 201105)
+#define PREFERRED_RAND_SOURCE "dev:/dev/arandom" /* XXX earlier */
+#endif
+
+#if OpenBSD >= 200000 /* XXX */
+#define HAS_ISSETUGID
+#endif
+
+#if OpenBSD >= 200200 /* XXX */
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if OpenBSD >= 200405 /* 3.5 */
+#define HAS_CLOSEFROM
+#endif
+
+/* __NetBSD_Version__ is major+minor */
+
+#if __NetBSD_Version__ >= 103000000 /* XXX maybe earlier */
+#undef DEF_MAILBOX_LOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* XXX maybe earlier */
+#endif
+
+#if __NetBSD_Version__ >= 105000000
+#define HAS_ISSETUGID /* XXX maybe earlier */
+#endif
+
+#if __NetBSD_Version__ >= 106000000 /* XXX maybe earlier */
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if __NetBSD_Version__ >= 299000900 /* 2.99.9 */
+#define HAS_CLOSEFROM
+#endif
+
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 102000000)
+#define HAS_FUTIMES
+#endif
+
+#if defined(__DragonFly__)
+#define HAS_DEV_URANDOM
+#define HAS_ISSETUGID
+#define HAS_FUTIMES
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#define HAS_DUPLEX_PIPE
+#endif
+
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 105000000) \
+ || (defined(__FreeBSD__) && __FreeBSD__ >= 4) \
+ || (defined(OpenBSD) && OpenBSD >= 200003) \
+ || defined(__DragonFly__) \
+ || defined(USAGI_LIBINET6)
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAVE_GETIFADDRS
+#endif
+
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 300000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 103000000) \
+ || (defined(OpenBSD) && OpenBSD >= 199700) /* OpenBSD 2.0?? */ \
+ || defined(__DragonFly__)
+#define USE_SYSV_POLL
+#endif
+
+#ifndef NO_KQUEUE
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 410000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200000000) \
+ || (defined(OpenBSD) && OpenBSD >= 200105) /* OpenBSD 2.9 */ \
+ || defined(__DragonFly__)
+#define EVENTS_STYLE EVENTS_STYLE_KQUEUE
+#endif
+#endif
+
+#ifndef NO_POSIX_GETPW_R
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 510000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 300000000) \
+ || (defined(OpenBSD) && OpenBSD >= 200811) /* OpenBSD 4.4 */
+#define HAVE_POSIX_GETPW_R
+#endif
+#endif
+
+#endif
+
+ /*
+ * UNIX on MAC.
+ */
+#if defined(RHAPSODY5) || defined(MACOSX)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_SUN_LEN
+#define HAS_FSYNC
+#define HAS_DB
+#define HAS_SA_LEN
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#define HAS_POSIX_REGEXP
+#ifndef NO_NETINFO
+#define HAS_NETINFO
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAVE_GETIFADDRS
+#endif
+#define HAS_FUTIMES /* XXX Guessing */
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#ifndef NO_KQUEUE
+#define EVENTS_STYLE EVENTS_STYLE_KQUEUE
+#define USE_SYSV_POLL_THEN_SELECT
+#endif
+#define USE_MAX_FILES_PER_PROC
+#ifndef NO_POSIX_GETPW_R
+#define HAVE_POSIX_GETPW_R
+#endif
+#define HAS_DLOPEN
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * Ultrix 4.x, a sort of 4.[1-2] BSD system with System V.2 compatibility
+ * and POSIX.
+ */
+#ifdef ULTRIX4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+/* Ultrix by default has only 64 descriptors per process */
+#ifndef FD_SETSIZE
+#define FD_SETSIZE 96
+#endif
+#define _PATH_MAILDIR "/var/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+/* might be set by makedef */
+#ifdef HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#else
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#endif
+extern int optind;
+extern char *optarg;
+extern int opterr;
+extern int h_errno;
+
+#define MISSING_STRFTIME_E
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb"
+#define USE_STATFS
+#define USE_STRUCT_FS_DATA
+#define STATFS_IN_SYS_MOUNT_H
+/* Ultrix misses just S_ISSOCK, the others are there */
+#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (S_IFSOCK))
+#define DUP2_DUPS_CLOSE_ON_EXEC
+#define MISSING_USLEEP
+#define NO_HERRNO
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_COMMAND_DIR "/usr/etc"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+ /*
+ * OSF, then Digital UNIX, then Compaq. A BSD-flavored hybrid.
+ */
+#ifdef OSF1
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define USE_PATHS_H
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+#define HAVE_BASENAME
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases"
+extern int optind; /* XXX use <getopt.h> */
+extern char *optarg; /* XXX use <getopt.h> */
+extern int opterr; /* XXX use <getopt.h> */
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#define HAS_POSIX_REGEXP
+#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
+#define NO_MSGHDR_MSG_CONTROL
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+
+#endif
+
+ /*
+ * SunOS 4.x, a mostly 4.[2-3] BSD system with System V.2 compatibility and
+ * POSIX support.
+ */
+#ifdef SUNOS4
+#define SUPPORTED
+#include <memory.h>
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define UNSAFE_CTYPE
+#define fpos_t long
+#define MISSING_SETENV
+#define MISSING_STRERROR
+#define MISSING_STRTOUL
+#define _PATH_MAILDIR "/var/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/etc:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+extern int optind;
+extern char *optarg;
+extern int opterr;
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb"
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define memmove(d,s,l) bcopy(s,d,l)
+#define NO_HERRNO
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/ucb/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/ucb/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/etc"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define STRCASECMP_IN_STRINGS_H
+#define OCTAL_TO_UNSIGNED(res, str) sscanf((str), "%o", &(res))
+#define size_t unsigned
+#define ssize_t int
+#define getsid getpgrp
+#define NO_SNPRINTF
+#endif
+
+ /*
+ * SunOS 5.x, mostly System V Release 4.
+ */
+#ifdef SUNOS5
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#ifndef NO_NISPLUS
+#define HAS_NISPLUS
+#endif /* NO_NISPLUS */
+#endif
+#define USE_SYS_SOCKIO_H /* Solaris 2.5, changed sys/ioctl.h */
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define INT_MAX_IN_LIMITS_H
+#ifdef STREAM_CONNECTIONS /* avoid UNIX-domain sockets */
+#define LOCAL_LISTEN stream_listen
+#define LOCAL_ACCEPT stream_accept
+#define LOCAL_CONNECT stream_connect
+#define LOCAL_TRIGGER stream_trigger
+#define LOCAL_SEND_FD stream_send_fd
+#define LOCAL_RECV_FD stream_recv_fd
+#endif
+#define HAS_VOLATILE_LOCKS
+#define BROKEN_READ_SELECT_ON_TCP_SOCKET
+#define CANT_WRITE_BEFORE_SENDING_FD
+#ifndef NO_POSIX_REGEXP
+#define HAS_POSIX_REGEXP
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAS_SIOCGLIF
+#endif
+#ifndef NO_CLOSEFROM
+#define HAS_CLOSEFROM
+#endif
+#ifndef NO_DEV_URANDOM
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+#ifndef NO_FUTIMESAT
+#define HAS_FUTIMESAT
+#endif
+#define USE_SYSV_POLL
+#ifndef NO_DEVPOLL
+#define EVENTS_STYLE EVENTS_STYLE_DEVPOLL
+#endif
+#ifndef NO_POSIX_GETPW_R
+#define HAVE_POSIX_GETPW_R
+#define GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS
+#endif
+
+/*
+ * Allow build environment to override paths.
+ */
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+#define HAS_DLOPEN
+#endif
+
+ /*
+ * UnixWare, System Release 4.
+ */
+#ifdef UW7 /* UnixWare 7 */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define STRCASECMP_IN_STRINGS_H
+#define USE_SET_H_ERRNO
+#endif
+
+#ifdef UW21 /* UnixWare 2.1.x */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS */
+#endif
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,NULL)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#endif
+
+ /*
+ * AIX: a SYSV-flavored hybrid. NB: fcntl() and flock() access the same
+ * underlying locking primitives.
+ */
+#if defined(AIX5) || defined(AIX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define USE_PATHS_H
+#ifndef _PATH_BSHELL
+#define _PATH_BSHELL "/bin/sh"
+#endif
+#ifndef _PATH_MAILDIR
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#endif
+#ifndef _PATH_DEFPATH
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#endif
+#ifndef _PATH_STDPATH
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#endif
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/sbin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+ /*
+ * XXX Need CMSG_SPACE() and CMSG_LEN() but don't want to drag in everything
+ * that comes with _LINUX_SOURCE_COMPAT.
+ */
+#include <sys/socket.h>
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len))
+#endif
+#ifndef CMSG_LEN
+#define CMSG_LEN(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+#define BROKEN_AI_PASSIVE_NULL_HOST
+#define BROKEN_AI_NULL_SERVICE
+#define USE_SYSV_POLL
+#define MYMALLOC_FUZZ 1
+#endif
+
+#ifdef AIX4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define RESOLVE_H_NEEDS_STDIO_H
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE size_t
+#define SOCKOPT_SIZE size_t
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define STRCASECMP_IN_STRINGS_H
+#if 0
+extern time_t time(time_t *);
+extern int seteuid(uid_t);
+extern int setegid(gid_t);
+extern int initgroups(const char *, int);
+
+#endif
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/sbin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+#ifdef AIX3
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define RESOLVE_H_NEEDS_STDIO_H
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE size_t
+#define SOCKOPT_SIZE size_t
+#define USE_STATFS
+#define STATFS_IN_SYS_STATFS_H
+#define STRCASECMP_IN_STRINGS_H
+extern time_t time(time_t *);
+extern int seteuid(uid_t);
+extern int setegid(gid_t);
+extern int initgroups(const char *, int);
+
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+ /*
+ * IRIX, a mix of System V Releases.
+ */
+#if defined(IRIX5) || defined(IRIX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/bsd"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/bsd"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define USE_SYS_SOCKIO_H /* XXX check */
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/bsd"
+#define FIONREAD_IN_SYS_FILIO_H /* XXX check */
+#define DBM_NO_TRAILING_NULL /* XXX check */
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+#if defined(IRIX5)
+#define MISSING_USLEEP
+#endif
+
+#if defined(IRIX6)
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+#define HAS_POSIX_REGEXP
+#define PIPES_CANT_FIONREAD
+#endif
+
+ /*
+ * LINUX.
+ */
+#if defined(LINUX2) || defined(LINUX3) || defined(LINUX4) || defined(LINUX5) \
+ || defined(LINUX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#include <features.h>
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#ifdef __GLIBC_PREREQ
+#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) __GLIBC_PREREQ(maj, min)
+#else
+#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) \
+ (defined(__GLIBC__) && \
+ ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min)))
+#endif
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 1)
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#else
+#define NO_SNPRINTF
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 4)
+/* Really 2.3.3 or later, but there's no __GLIBC_MICRO version macro. */
+#define HAVE_GETIFADDRS
+#else
+#define HAS_PROCNET_IFINET6
+#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6"
+#endif
+#endif
+#include <linux/version.h>
+#if !defined(KERNEL_VERSION)
+#define KERNEL_VERSION(a,b,c) (LINUX_VERSION_CODE + 1)
+#endif
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)) \
+ || (defined(__GLIBC__) && __GLIBC__ < 2)
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#else
+#define CANT_WRITE_BEFORE_SENDING_FD
+#endif
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced in 1.1 */
+#ifndef NO_EPOLL
+#define EVENTS_STYLE EVENTS_STYLE_EPOLL /* introduced in 2.5 */
+#endif
+#define USE_SYSV_POLL
+#ifndef NO_POSIX_GETPW_R
+#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) \
+ || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 1) \
+ || (defined(_BSD_SOURCE) && _BSD_SOURCE >= 1) \
+ || (defined(_SVID_SOURCE) && _SVID_SOURCE >= 1) \
+ || (defined(_POSIX_SOURCE) && _POSIX_SOURCE >= 1)
+#define HAVE_POSIX_GETPW_R
+#endif
+#endif
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 34)
+#define HAS_CLOSEFROM
+#endif
+
+#endif
+
+#ifdef LINUX1
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "dotlock" /* verified RedHat 3.03 */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H /* maybe unnecessary */
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#endif
+
+ /*
+ * GNU.
+ */
+#ifdef GNU0
+#define SUPPORTED
+#include <features.h>
+#define USE_PATHS_H
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#ifdef DEBIAN
+#define NATIVE_DAEMON_DIR "/usr/lib/postfix"
+#ifndef DEF_MANPAGE_DIR
+#define DEF_MANPAGE_DIR "/usr/share/man"
+#endif
+#ifndef DEF_SAMPLE_DIR
+#define DEF_SAMPLE_DIR "/usr/share/doc/postfix/examples"
+#endif
+#ifndef DEF_README_DIR
+#define DEF_README_DIR "/usr/share/doc/postfix"
+#endif
+#else
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#ifdef __FreeBSD_kernel__
+#define HAS_DUPLEX_PIPE
+#define HAS_ISSETUGID
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#ifdef __FreeBSD_kernel__
+#define HAVE_GETIFADDRS
+#else
+#define HAS_PROCNET_IFINET6
+#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6"
+#endif
+#endif
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * HPUX11 was copied from HPUX10, but can perhaps be trimmed down a bit.
+ */
+#ifdef HPUX11
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETENV
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_DEFPATH "/usr/bin"
+#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno; /* <netdb.h> imports too much stuff */
+
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+#ifdef HPUX10
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETENV
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_DEFPATH "/usr/bin"
+#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno; /* <netdb.h> imports too much stuff */
+
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_SHL_LOAD
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+#ifdef HPUX9
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define MISSING_SETENV
+#define MISSING_RLIMIT_FSIZE
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/usr/lib/aliases"
+#define ROOT_PATH "/bin:/usr/bin:/etc"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/usr/mail"
+#define _PATH_DEFPATH "/bin:/usr/bin"
+#define _PATH_STDPATH "/bin:/usr/bin:/etc"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno;
+
+#define USE_ULIMIT /* no setrlimit() */
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_SHL_LOAD
+#define NATIVE_SENDMAIL_PATH "/usr/bin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+ /*
+ * NEXTSTEP3, without -lposix, because its naming service is broken.
+ */
+#ifdef NEXTSTEP3
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FLOCK_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define USE_STATFS
+#define HAVE_SYS_DIR_H
+#define STATFS_IN_SYS_VFS_H
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_NETINFO
+#define MISSING_SETENV_PUTENV
+#define MISSING_MKFIFO
+#define MISSING_SIGSET_T
+#define MISSING_SIGACTION
+#define MISSING_STD_FILENOS
+#define MISSING_SETSID
+#define MISSING_WAITPID
+#define MISSING_UTIMBUF
+#define HAS_WAIT4
+#define WAIT_STATUS_T union wait
+#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x))
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb"
+#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP "netinfo:/aliases"
+#include <libc.h>
+#define MISSING_POSIX_S_IS
+#define MISSING_POSIX_S_MODES
+/* It's amazing what is all missing... */
+#define isascii(c) ((unsigned)(c)<=0177)
+extern int opterr;
+typedef unsigned short mode_t;
+
+#define MISSING_PID_T
+#define MISSING_STRFTIME_E
+#define FD_CLOEXEC 1
+#define O_NONBLOCK O_NDELAY
+#define WEXITSTATUS(x) ((x).w_retcode)
+#define WTERMSIG(x) ((x).w_termsig)
+#endif
+
+ /*
+ * OPENSTEP does not have posix (some fix...)
+ */
+#ifdef OPENSTEP4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FLOCK_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define USE_STATFS
+#define HAVE_SYS_DIR_H
+#define STATFS_IN_SYS_VFS_H
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_NETINFO
+#define MISSING_SETENV_PUTENV
+#define MISSING_MKFIFO
+#define MISSING_SIGSET_T
+#define MISSING_SIGACTION
+#define MISSING_STD_FILENOS
+#define MISSING_SETSID
+#define MISSING_WAITPID
+#define MISSING_UTIMBUF
+#define HAS_WAIT4
+#define WAIT_STATUS_T union wait
+#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x))
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb"
+#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP "netinfo:/aliases"
+#include <libc.h>
+#define MISSING_POSIX_S_IS
+#define MISSING_POSIX_S_MODES
+/* It's amazing what is all missing... */
+#define isascii(c) ((unsigned)(c)<=0177)
+extern int opterr;
+typedef unsigned short mode_t;
+
+#define MISSING_PID_T
+#define MISSING_STRFTIME_E
+#define FD_CLOEXEC 1
+#define O_NONBLOCK O_NDELAY
+#define WEXITSTATUS(x) ((x).w_retcode)
+#define WTERMSIG(x) ((x).w_termsig)
+#endif
+
+#ifdef ReliantUnix543
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define FIONREAD_IN_SYS_FILIO_H
+#define USE_SYS_SOCKIO_H
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases"
+extern int optind; /* XXX use <getopt.h> */
+extern char *optarg; /* XXX use <getopt.h> */
+extern int opterr; /* XXX use <getopt.h> */
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define MISSING_USLEEP
+#endif
+
+#ifdef DCOSX1 /* Siemens Pyramid */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+/* Uncomment the following line if you have NIS package installed */
+/* #define HAS_NIS */
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,NULL)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#ifndef S_ISSOCK
+#define S_ISSOCK(mode) ((mode&0xF000) == 0xC000)
+#endif
+#endif
+
+#ifdef SCO5
+#define SUPPORTED
+#include <sys/socket.h>
+extern int h_errno;
+
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin"
+#define USE_PATHS_H
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define DBM_NO_TRAILING_NULL
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/etc:/usr/bin:/tcb/bin"
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define MISSING_SETENV
+#define STRCASECMP_IN_STRINGS_H
+/* SCO5 misses just S_ISSOCK, the others are there
+ * Use C_ISSOCK definition from cpio.h.
+ */
+#include <cpio.h>
+#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (C_ISSOCK))
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#endif
+
+ /*
+ * We're not going to try to guess like configure does.
+ */
+#ifndef SUPPORTED
+#error "unsupported platform"
+#endif
+
+ /*
+ * Allow command line flags to override native settings
+ */
+#ifndef DEF_COMMAND_DIR
+#ifdef NATIVE_COMMAND_DIR
+#define DEF_COMMAND_DIR NATIVE_COMMAND_DIR
+#endif
+#endif
+
+#ifndef DEF_DAEMON_DIR
+#ifdef NATIVE_DAEMON_DIR
+#define DEF_DAEMON_DIR NATIVE_DAEMON_DIR
+#endif
+#endif
+
+#ifndef DEF_SENDMAIL_PATH
+#ifdef NATIVE_SENDMAIL_PATH
+#define DEF_SENDMAIL_PATH NATIVE_SENDMAIL_PATH
+#endif
+#endif
+
+#ifndef DEF_MAILQ_PATH
+#ifdef NATIVE_MAILQ_PATH
+#define DEF_MAILQ_PATH NATIVE_MAILQ_PATH
+#endif
+#endif
+
+#ifndef DEF_NEWALIAS_PATH
+#ifdef NATIVE_NEWALIAS_PATH
+#define DEF_NEWALIAS_PATH NATIVE_NEWALIAS_PATH
+#endif
+#endif
+
+#ifndef DEF_DB_TYPE
+#define DEF_DB_TYPE NATIVE_DB_TYPE
+#endif
+
+#define CAST_ANY_PTR_TO_INT(cptr) ((int) (long) (cptr))
+#define CAST_INT_TO_VOID_PTR(ival) ((void *) (long) (ival))
+
+#ifdef DUP2_DUPS_CLOSE_ON_EXEC
+/* dup2_pass_on_exec() can be found in util/sys_compat.c */
+extern int dup2_pass_on_exec(int oldd, int newd);
+
+#define DUP2 dup2_pass_on_exec
+#else
+#define DUP2 dup2
+#endif
+
+#ifdef PREPEND_PLUS_TO_OPTSTRING
+#define GETOPT(argc, argv, str) getopt((argc), (argv), "+" str)
+#else
+#define GETOPT(argc, argv, str) getopt((argc), (argv), (str))
+#endif
+#define OPTIND (optind > 0 ? optind : 1)
+
+#if !defined(__UCLIBC__) && !defined(NO_RES_SEND)
+#define HAVE_RES_SEND
+#else
+#undef HAVE_RES_SEND
+#endif
+
+ /*
+ * Check for required but missing definitions.
+ */
+#if !defined(HAS_FCNTL_LOCK) && !defined(HAS_FLOCK_LOCK)
+#error "define HAS_FCNTL_LOCK and/or HAS_FLOCK_LOCK"
+#endif
+
+#if !defined(DEF_MAILBOX_LOCK)
+#error "define DEF_MAILBOX_LOCK"
+#endif
+
+#if !defined(INTERNAL_LOCK)
+#error "define INTERNAL_LOCK"
+#endif
+
+#if defined(USE_STATFS) && defined(USE_STATVFS)
+#error "define USE_STATFS or USE_STATVFS, not both"
+#endif
+
+#if !defined(USE_STATFS) && !defined(USE_STATVFS)
+#error "define USE_STATFS or USE_STATVFS"
+#endif
+
+ /*
+ * Defaults for systems that pre-date IPv6 support.
+ */
+#ifndef HAS_IPV6
+#include <sys/socket.h>
+#define EMULATE_IPV4_ADDRINFO
+#define MISSING_INET_PTON
+#define MISSING_INET_NTOP
+extern const char *inet_ntop(int, const void *, char *, SOCKADDR_SIZE);
+extern int inet_pton(int, const char *, void *);
+
+#endif
+
+ /*
+ * Workaround: after a watchdog alarm signal, wake up from select/poll/etc.
+ * by writing to a pipe. Solaris needs this, and HP-UX apparently, too. The
+ * run-time cost is negligible so we just turn it on for all systems. As a
+ * side benefit, making this code system-independent will simplify the
+ * detection of bit-rot problems.
+ */
+#ifndef NO_WATCHDOG_PIPE
+#define USE_WATCHDOG_PIPE
+#endif
+
+ /*
+ * If we don't have defined a preferred random device above, but the system
+ * has /dev/urandom, then we use that.
+ */
+#if !defined(PREFERRED_RAND_SOURCE) && defined(HAS_DEV_URANDOM)
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * Defaults for systems without kqueue, /dev/poll or epoll support.
+ * master/multi-server.c and *qmgr/qmgr_transport.c depend on this.
+ */
+#if !defined(EVENTS_STYLE)
+#define EVENTS_STYLE EVENTS_STYLE_SELECT
+#endif
+
+#define EVENTS_STYLE_SELECT 1 /* Traditional BSD select */
+#define EVENTS_STYLE_KQUEUE 2 /* FreeBSD kqueue */
+#define EVENTS_STYLE_DEVPOLL 3 /* Solaris /dev/poll */
+#define EVENTS_STYLE_EPOLL 4 /* Linux epoll */
+
+ /*
+ * We use poll() for read/write time limit enforcement on modern systems. We
+ * use select() on historical systems without poll() support. And on systems
+ * where poll() is not implemented for some file handle types, we try to use
+ * select() as a fall-back solution (MacOS X needs this).
+ */
+#if !defined(USE_SYSV_POLL) && !defined(USE_SYSV_POLL_THEN_SELECT)
+#define USE_BSD_SELECT
+#endif
+
+ /*
+ * The Postfix 2.9 post-install workaround assumes that the inet_protocols
+ * default value is "ipv4" when Postfix is compiled without IPv6 support.
+ */
+#ifndef DEF_INET_PROTOCOLS
+#ifdef HAS_IPV6
+#define DEF_INET_PROTOCOLS INET_PROTO_NAME_ALL
+#else
+#define DEF_INET_PROTOCOLS INET_PROTO_NAME_IPV4
+#endif
+#endif
+
+ /*
+ * Defaults for systems that pre-date POSIX socklen_t.
+ */
+#ifndef SOCKADDR_SIZE
+#define SOCKADDR_SIZE int
+#endif
+
+#ifndef SOCKOPT_SIZE
+#define SOCKOPT_SIZE int
+#endif
+
+ /*
+ * Defaults for normal systems.
+ */
+#ifndef LOCAL_LISTEN
+#define LOCAL_LISTEN unix_listen
+#define LOCAL_ACCEPT unix_accept
+#define LOCAL_CONNECT unix_connect
+#define LOCAL_TRIGGER unix_trigger
+#define LOCAL_SEND_FD unix_send_fd
+#define LOCAL_RECV_FD unix_recv_fd
+#endif
+
+#if !defined (HAVE_SYS_NDIR_H) && !defined (HAVE_SYS_DIR_H) \
+ && !defined (HAVE_NDIR_H)
+#define HAVE_DIRENT_H
+#endif
+
+#ifndef WAIT_STATUS_T
+typedef int WAIT_STATUS_T;
+
+#define NORMAL_EXIT_STATUS(status) ((status) == 0)
+#endif
+
+#ifdef NO_POSIX_GETPW_R
+#undef HAVE_POSIX_GETPW_R
+#endif
+
+#ifdef NO_DB
+#undef HAS_DB
+#endif
+
+#ifndef OCTAL_TO_UNSIGNED
+#define OCTAL_TO_UNSIGNED(res, str) ((res) = strtoul((str), (char **) 0, 8))
+#endif
+
+ /*
+ * Avoid useless type mis-matches when using sizeof in an integer context.
+ */
+#define INT_SIZEOF(foo) ((int) sizeof(foo))
+
+ /*
+ * Turn on the compatibility stuff.
+ */
+#ifdef MISSING_UTIMBUF
+struct utimbuf {
+ time_t actime;
+ time_t modtime;
+};
+
+#endif
+
+#ifdef MISSING_STRERROR
+extern const char *strerror(int);
+
+#endif
+
+#if defined (MISSING_SETENV) || defined (MISSING_SETENV_PUTENV)
+extern int setenv(const char *, const char *, int);
+
+#endif
+
+#ifdef MISSING_SETEUID
+extern int seteuid(uid_t euid);
+
+#endif
+
+#ifdef MISSING_SETEGID
+extern int setegid(gid_t egid);
+
+#endif
+
+#ifdef MISSING_MKFIFO
+extern int mkfifo(char *, int);
+
+#endif
+
+#ifdef MISSING_WAITPID
+extern int waitpid(int, WAIT_STATUS_T *status, int options);
+
+#endif
+
+#ifdef MISSING_SETSID
+extern int setsid(void);
+
+#endif
+
+#ifndef HAS_CLOSEFROM
+extern int closefrom(int);
+
+#endif
+
+#ifdef MISSING_STD_FILENOS
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+#endif
+
+#ifdef MISSING_PID_T
+typedef int pid_t;
+
+#endif
+
+#ifdef MISSING_POSIX_S_IS
+#define S_ISBLK(mode) (((mode) & (_S_IFMT)) == (_S_IFBLK))
+#define S_ISCHR(mode) (((mode) & (_S_IFMT)) == (_S_IFCHR))
+#define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR))
+#define S_ISSOCK(mode) (((mode) & (_S_IFMT)) == (_S_IFSOCK))
+#define S_ISFIFO(mode) (((mode) & (_S_IFMT)) == (_S_IFIFO))
+#define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG))
+#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK))
+#endif
+
+#ifdef MISSING_POSIX_S_MODES
+#define S_IRUSR _S_IRUSR
+#define S_IRGRP 0000040
+#define S_IROTH 0000004
+#define S_IWUSR _S_IWUSR
+#define S_IWGRP 0000020
+#define S_IWOTH 0000002
+#define S_IXUSR _S_IXUSR
+#define S_IXGRP 0000010
+#define S_IXOTH 0000001
+#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
+#endif
+
+ /*
+ * Memory alignment of memory allocator results. By default we align for
+ * doubles.
+ */
+#ifndef ALIGN_TYPE
+#if defined(__hpux) && defined(__ia64)
+#define ALIGN_TYPE __float80
+#elif defined(__ia64__)
+#define ALIGN_TYPE long double
+#else
+#define ALIGN_TYPE double
+#endif
+#endif
+
+ /*
+ * Clang-style attribute tests.
+ *
+ * XXX Without the unconditional test below, gcc 4.6 will barf on ``elif
+ * defined(__clang__) && __has_attribute(__whatever__)'' with error message
+ * ``missing binary operator before token "("''.
+ */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif /* __has_attribute */
+
+ /*
+ * Need to specify what functions never return, so that the compiler can
+ * warn for missing initializations and other trouble. However, OPENSTEP4
+ * gcc 2.7.x cannot handle this so we define this only if NORETURN isn't
+ * already defined above.
+ *
+ * Data point: gcc 2.7.2 has __attribute__ (Wietse Venema) but gcc 2.6.3 does
+ * not (Clive Jones). So we'll set the threshold at 2.7.
+ */
+#ifndef NORETURN
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define NORETURN void __attribute__((__noreturn__))
+#elif defined(__clang__) && __has_attribute(__noreturn__)
+#define NORETURN void __attribute__((__noreturn__))
+#else
+#define NORETURN void
+#endif
+#endif /* NORETURN */
+
+ /*
+ * Turn on format string argument checking. This is more accurate than
+ * printfck, but it misses #ifdef-ed code. XXX I am just guessing at what
+ * gcc versions support this. In order to turn this off for some platforms,
+ * specify #define PRINTFLIKE and #define SCANFLIKE in the system-dependent
+ * sections above.
+ */
+#ifndef PRINTFLIKE
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define PRINTFLIKE(x,y) __attribute__ ((format (printf, (x), (y))))
+#elif defined(__clang__) && __has_attribute(__format__)
+#define PRINTFLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y))))
+#else
+#define PRINTFLIKE(x,y)
+#endif
+#endif /* PRINTFLIKE */
+
+#ifndef SCANFLIKE
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define SCANFLIKE(x,y) __attribute__ ((format (scanf, (x), (y))))
+#elif defined(__clang__) && __has_attribute(__format__)
+#define SCANFLIKE(x,y) __attribute__ ((__format__ (__scanf__, (x), (y))))
+#else
+#define SCANFLIKE(x,y)
+#endif
+#endif /* SCANFLIKE */
+
+ /*
+ * Some gcc implementations don't grok these attributes with pointer to
+ * function. Again, wild guess of what is supported. To override, specify
+ * #define PRINTFPTRLIKE in the system-dependent sections above.
+ */
+#ifndef PRINTFPTRLIKE
+#if (__GNUC__ >= 3) /* XXX Rough estimate */
+#define PRINTFPTRLIKE(x,y) PRINTFLIKE(x,y)
+#elif defined(__clang__) && __has_attribute(__format__)
+#define PRINTFPTRLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y))))
+#else
+#define PRINTFPTRLIKE(x,y)
+#endif
+#endif
+
+ /*
+ * Compiler optimization hint. This makes sense only for code in a
+ * performance-critical loop.
+ */
+#ifndef EXPECTED
+#if defined(__GNUC__) && (__GNUC__ > 2)
+#define EXPECTED(x) __builtin_expect(!!(x), 1)
+#define UNEXPECTED(x) __builtin_expect(!!(x), 0)
+#else
+#define EXPECTED(x) (x)
+#define UNEXPECTED(x) (x)
+#endif
+#endif
+
+ /*
+ * Warn about ignored function result values that must never be ignored.
+ * Typically, this is for error results from "read" functions that normally
+ * write to output parameters (for example, stat- or scanf-like functions)
+ * or from functions that have other useful side effects (for example,
+ * fseek- or rename-like functions).
+ *
+ * DO NOT use this for functions that write to a stream; it is entirely
+ * legitimate to detect write errors with fflush() or fclose() only. On the
+ * other hand most (but not all) functions that read from a stream must
+ * never ignore result values.
+ *
+ * XXX Prepending "(void)" won't shut up GCC. Clang behaves as expected.
+ */
+#if ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ > 3)
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#elif defined(__clang__) && __has_attribute(warn_unused_result)
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+#define WARN_UNUSED_RESULT
+#endif
+
+ /*
+ * ISO C says that the "volatile" qualifier protects against optimizations
+ * that cause longjmp() to clobber local variables.
+ */
+#ifndef NOCLOBBER
+#define NOCLOBBER volatile
+#endif
+
+ /*
+ * Bit banging!! There is no official constant that defines the INT_MAX
+ * equivalent for off_t, ssize_t, etc. Wietse came up with the following
+ * macro that works as long as off_t, ssize_t, etc. use one's or two's
+ * complement logic (that is, the maximum value is binary 01...1). Don't use
+ * right-shift for signed types: the result is implementation-defined.
+ */
+#include <limits.h>
+#define __MAXINT__(T) ((T) ~(((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)))
+#ifndef OFF_T_MAX
+#define OFF_T_MAX __MAXINT__(off_t)
+#endif
+
+#ifndef SSIZE_T_MAX
+#define SSIZE_T_MAX __MAXINT__(ssize_t)
+#endif
+
+ /*
+ * Consistent enforcement of size limits.
+ */
+#define ENFORCING_SIZE_LIMIT(param) ((param) > 0)
+
+ /*
+ * Don't mix socket message send/receive calls with socket stream read/write
+ * calls. The fact that you can get away with it only on some stacks implies
+ * that there is no long-term guarantee.
+ */
+#ifndef CAN_WRITE_BEFORE_SENDING_FD
+#define CANT_WRITE_BEFORE_SENDING_FD
+#endif
+
+ /*
+ * FreeBSD sendmsg(2) says that after sending a file descriptor, the sender
+ * must not immediately close the descriptor, otherwise it may close the
+ * descriptor before it is actually sent.
+ */
+#ifndef DONT_WAIT_AFTER_SENDING_FD
+#define MUST_READ_AFTER_SENDING_FD
+#endif
+
+ /*
+ * Hope for the best.
+ */
+#ifndef UINT32_TYPE
+#define UINT32_TYPE uint32_t
+#define UINT16_TYPE uint16_t
+#endif
+#define UINT32_SIZE 4
+#define UINT16_SIZE 2
+
+ /*
+ * For the sake of clarity.
+ */
+#ifndef HAVE_CONST_CHAR_STAR
+typedef const char *CONST_CHAR_STAR;
+
+#endif
+
+ /*
+ * Safety. On some systems, ctype.h misbehaves with non-ASCII or negative
+ * characters. More importantly, Postfix uses the ISXXX() macros to ensure
+ * protocol compliance, so we have to rule out non-ASCII characters.
+ *
+ * XXX The (unsigned char) casts in isalnum() etc arguments are unnecessary
+ * because the ISASCII() guard already ensures that the values are
+ * non-negative; the casts are done anyway to shut up chatty compilers.
+ */
+#define ISASCII(c) isascii(_UCHAR_(c))
+#define _UCHAR_(c) ((unsigned char)(c))
+#define ISALNUM(c) (ISASCII(c) && isalnum((unsigned char)(c)))
+#define ISALPHA(c) (ISASCII(c) && isalpha((unsigned char)(c)))
+#define ISCNTRL(c) (ISASCII(c) && iscntrl((unsigned char)(c)))
+#define ISDIGIT(c) (ISASCII(c) && isdigit((unsigned char)(c)))
+#define ISGRAPH(c) (ISASCII(c) && isgraph((unsigned char)(c)))
+#define ISLOWER(c) (ISASCII(c) && islower((unsigned char)(c)))
+#define ISPRINT(c) (ISASCII(c) && isprint((unsigned char)(c)))
+#define ISPUNCT(c) (ISASCII(c) && ispunct((unsigned char)(c)))
+#define ISSPACE(c) (ISASCII(c) && isspace((unsigned char)(c)))
+#define ISUPPER(c) (ISASCII(c) && isupper((unsigned char)(c)))
+#define TOLOWER(c) (ISUPPER(c) ? tolower((unsigned char)(c)) : (c))
+#define TOUPPER(c) (ISLOWER(c) ? toupper((unsigned char)(c)) : (c))
+
+ /*
+ * Character sets for parsing.
+ */
+#define CHARS_COMMA_SP ", \t\r\n" /* list separator */
+#define CHARS_SPACE " \t\r\n" /* word separator */
+#define CHARS_BRACE "{}" /* grouping */
+
+ /*
+ * Scaffolding. I don't want to lose messages while the program is under
+ * development.
+ */
+extern int REMOVE(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/testdb b/src/util/testdb
new file mode 100644
index 0000000..097c6c0
--- /dev/null
+++ b/src/util/testdb
@@ -0,0 +1,2 @@
+foo fooval
+bar barval
diff --git a/src/util/timecmp.c b/src/util/timecmp.c
new file mode 100644
index 0000000..607a9ae
--- /dev/null
+++ b/src/util/timecmp.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* timecmp 3
+/* SUMMARY
+/* compare two time_t values
+/* SYNOPSIS
+/* #include <timecmp.h>
+/*
+/* int timecmp(t1, t2)
+/* time_t t1;
+/* time_t t2;
+/* DESCRIPTION
+/* The timecmp() function return an integer greater than, equal to, or
+/* less than 0, according as the time t1 is greater than, equal to, or
+/* less than the time t2. The comparison is made in a manner that is
+/* insensitive to clock wrap-around, provided the underlying times are
+/* within half of the time interval between the smallest and largest
+/* representable time values.
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+#include "timecmp.h"
+
+/* timecmp - wrap-safe time_t comparison */
+
+int timecmp(time_t t1, time_t t2)
+{
+ time_t delta = t1 - t2;
+
+ if (delta == 0)
+ return 0;
+
+#define UNSIGNED(type) ( ((type)-1) > ((type)0) )
+
+ /*
+ * With a constant switch value, the compiler will emit only the code for
+ * the correct case, so the signed/unsigned test happens at compile time.
+ */
+ switch (UNSIGNED(time_t) ? 0 : 1) {
+ case 0:
+ return ((2 * delta > delta) ? 1 : -1);
+ case 1:
+ return ((delta > (time_t) 0) ? 1 : -1);
+ }
+}
+
+#ifdef TEST
+#include <assert.h>
+
+ /*
+ * Bit banging!! There is no official constant that defines the INT_MAX
+ * equivalent of the off_t type. Wietse came up with the following macro
+ * that works as long as off_t is some two's complement number.
+ *
+ * Note, however, that C99 permits signed integer representations other than
+ * two's complement.
+ */
+#include <limits.h>
+#define __MAXINT__(T) ((T) (((((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)) ^ ((T) -1))))
+
+int main(void)
+{
+ time_t now = time((time_t *) 0);
+
+ /* Test that it works for normal times */
+ assert(timecmp(now + 10, now) > 0);
+ assert(timecmp(now, now) == 0);
+ assert(timecmp(now - 10, now) < 0);
+
+ /* Test that it works at a boundary time */
+ if (UNSIGNED(time_t))
+ now = (time_t) -1;
+ else
+ now = __MAXINT__(time_t);
+
+ assert(timecmp(now + 10, now) > 0);
+ assert(timecmp(now, now) == 0);
+ assert(timecmp(now - 10, now) < 0);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/timecmp.h b/src/util/timecmp.h
new file mode 100644
index 0000000..b6efeab
--- /dev/null
+++ b/src/util/timecmp.h
@@ -0,0 +1,37 @@
+#ifndef _TIMECMP_H_INCLUDED_
+#define _TIMECMP_H_INCLUDED_
+
+#include <time.h>
+
+/*++
+/* NAME
+/* timecmp 3h
+/* SUMMARY
+/* compare two time_t values
+/* SYNOPSIS
+/* #include <timecmp.h>
+/*
+/* int timecmp(t1, t2)
+/* time_t t1;
+/* time_t t2;
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int timecmp(time_t, time_t);
+
+/* 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+#endif
diff --git a/src/util/timed_connect.c b/src/util/timed_connect.c
new file mode 100644
index 0000000..962545f
--- /dev/null
+++ b/src/util/timed_connect.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* timed_connect 3
+/* SUMMARY
+/* connect operation with timeout
+/* SYNOPSIS
+/* #include <sys/socket.h>
+/* #include <timed_connect.h>
+/*
+/* int timed_connect(fd, buf, buf_len, timeout)
+/* int fd;
+/* struct sockaddr *buf;
+/* int buf_len;
+/* int timeout;
+/* DESCRIPTION
+/* timed_connect() implement a BSD socket connect() operation that is
+/* bounded in time.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE. This descriptor
+/* must be set to non-blocking mode prior to calling timed_connect().
+/* .IP buf
+/* Socket address buffer pointer.
+/* .IP buf_len
+/* Size of socket address buffer.
+/* .IP timeout
+/* The deadline in seconds. This must be a number > 0.
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a blocking connect(2)
+/* operation.
+/* WARNINGS
+/* .ad
+/* .fi
+/* A common error is to call timed_connect() without enabling
+/* non-blocking I/O on the socket. In that case, the \fItimeout\fR
+/* parameter takes no effect.
+/* 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/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "sane_connect.h"
+#include "timed_connect.h"
+
+/* timed_connect - connect with deadline */
+
+int timed_connect(int sock, struct sockaddr *sa, int len, int timeout)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Sanity check. Just like with timed_wait(), the timeout must be a
+ * positive number.
+ */
+ if (timeout <= 0)
+ msg_panic("timed_connect: bad timeout: %d", timeout);
+
+ /*
+ * Start the connection, and handle all possible results.
+ */
+ if (sane_connect(sock, sa, len) == 0)
+ return (0);
+ if (errno != EINPROGRESS)
+ return (-1);
+
+ /*
+ * A connection is in progress. Wait for a limited amount of time for
+ * something to happen. If nothing happens, report an error.
+ */
+ if (write_wait(sock, timeout) < 0)
+ return (-1);
+
+ /*
+ * Something happened. Some Solaris 2 versions have getsockopt() itself
+ * return the error, instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
diff --git a/src/util/timed_connect.h b/src/util/timed_connect.h
new file mode 100644
index 0000000..76ac715
--- /dev/null
+++ b/src/util/timed_connect.h
@@ -0,0 +1,31 @@
+#ifndef _TIMED_CONNECT_H_INCLUDED_
+#define _TIMED_CONNECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* timed_connect 3h
+/* SUMMARY
+/* connect operation with timeout
+/* SYNOPSIS
+/* #include <sys/socket.h>
+/* #include <timed_connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int timed_connect(int, struct sockaddr *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/timed_read.c b/src/util/timed_read.c
new file mode 100644
index 0000000..fdae8fa
--- /dev/null
+++ b/src/util/timed_read.c
@@ -0,0 +1,86 @@
+/*++
+/* NAME
+/* timed_read 3
+/* SUMMARY
+/* read operation with pre-read timeout
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t timed_read(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* timed_read() performs a read() operation when the specified
+/* descriptor becomes readable within a user-specified deadline.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Read buffer pointer.
+/* .IP len
+/* Read buffer size.
+/* .IP timeout
+/* The deadline in seconds. If this is <= 0, the deadline feature
+/* is disabled.
+/* .IP context
+/* Application context. This parameter is unused. It exists only
+/* for the sake of VSTREAM compatibility.
+/* DIAGNOSTICS
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a read(2) operation.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* timed_read - read with deadline */
+
+ssize_t timed_read(int fd, void *buf, size_t len,
+ int timeout, void *unused_context)
+{
+ ssize_t ret;
+
+ /*
+ * Wait for a limited amount of time for something to happen. If nothing
+ * happens, report an ETIMEDOUT error.
+ *
+ * XXX Solaris 8 read() fails with EAGAIN after read-select() returns
+ * success.
+ */
+ for (;;) {
+ if (timeout > 0 && read_wait(fd, timeout) < 0)
+ return (-1);
+ if ((ret = read(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) {
+ msg_warn("read() returns EAGAIN on a readable file descriptor!");
+ msg_warn("pausing to avoid going into a tight select/read loop!");
+ sleep(1);
+ continue;
+ } else if (ret < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return (ret);
+ }
+ }
+}
diff --git a/src/util/timed_wait.c b/src/util/timed_wait.c
new file mode 100644
index 0000000..45fbb69
--- /dev/null
+++ b/src/util/timed_wait.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* timed_wait 3
+/* SUMMARY
+/* wait operations with timeout
+/* SYNOPSIS
+/* #include <timed_wait.h>
+/*
+/* int timed_waitpid(pid, statusp, options, time_limit)
+/* pid_t pid;
+/* WAIT_STATUS_T *statusp;
+/* int options;
+/* int time_limit;
+/* DESCRIPTION
+/* \fItimed_waitpid\fR() waits at most \fItime_limit\fR seconds
+/* for process termination.
+/*
+/* Arguments:
+/* .IP "pid, statusp, options"
+/* The process ID, status pointer and options passed to waitpid(3).
+/* .IP time_limit
+/* The time in seconds that timed_waitpid() will wait.
+/* This must be a number > 0.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* When the time limit is exceeded, the result is -1 and errno
+/* is set to ETIMEDOUT. Otherwise, the result value is the result
+/* from the underlying waitpid() routine.
+/* BUGS
+/* If there were a \fIportable\fR way to select() on process status
+/* information, these routines would not have to use a steenkeeng
+/* alarm() timer and signal() handler.
+/* 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/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <posix_signals.h>
+#include <timed_wait.h>
+
+/* Application-specific. */
+
+static int timed_wait_expired;
+
+/* timed_wait_alarm - timeout handler */
+
+static void timed_wait_alarm(int unused_sig)
+{
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. This code
+ * is here only so that we can break out of waitpid(). Don't put any code
+ * here other than for setting a global flag.
+ */
+ timed_wait_expired = 1;
+}
+
+/* timed_waitpid - waitpid with time limit */
+
+int timed_waitpid(pid_t pid, WAIT_STATUS_T *statusp, int options,
+ int time_limit)
+{
+ const char *myname = "timed_waitpid";
+ struct sigaction action;
+ struct sigaction old_action;
+ int time_left;
+ int wpid;
+
+ /*
+ * Sanity checks.
+ */
+ if (time_limit <= 0)
+ msg_panic("%s: bad time limit: %d", myname, time_limit);
+
+ /*
+ * Set up a timer.
+ */
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ action.sa_handler = timed_wait_alarm;
+ if (sigaction(SIGALRM, &action, &old_action) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ timed_wait_expired = 0;
+ time_left = alarm(time_limit);
+
+ /*
+ * Wait for only a limited amount of time.
+ */
+ if ((wpid = waitpid(pid, statusp, options)) < 0 && timed_wait_expired)
+ errno = ETIMEDOUT;
+
+ /*
+ * Cleanup.
+ */
+ alarm(0);
+ if (sigaction(SIGALRM, &old_action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (time_left)
+ alarm(time_left);
+
+ return (wpid);
+}
diff --git a/src/util/timed_wait.h b/src/util/timed_wait.h
new file mode 100644
index 0000000..6a153de
--- /dev/null
+++ b/src/util/timed_wait.h
@@ -0,0 +1,35 @@
+#ifndef _TIMED_WAIT_H_INCLUDED_
+#define _TIMED_WAIT_H_INCLUDED_
+
+/*++
+/* NAME
+/* timed_wait 3h
+/* SUMMARY
+/* wait operations with timeout
+/* SYNOPSIS
+/* #include <timed_wait.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int WARN_UNUSED_RESULT timed_waitpid(pid_t, WAIT_STATUS_T *, int, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/timed_write.c b/src/util/timed_write.c
new file mode 100644
index 0000000..220b4c4
--- /dev/null
+++ b/src/util/timed_write.c
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* timed_write 3
+/* SUMMARY
+/* write operation with pre-write timeout
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t timed_write(fd, buf, len, timeout, context)
+/* int fd;
+/* const void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* timed_write() performs a write() operation when the specified
+/* descriptor becomes writable within a user-specified deadline.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Write buffer pointer.
+/* .IP len
+/* Write buffer size.
+/* .IP timeout
+/* The deadline in seconds. If this is <= 0, the deadline feature
+/* is disabled.
+/* .IP context
+/* Application context. This parameter is unused. It exists only
+/* for the sake of VSTREAM compatibility.
+/* DIAGNOSTICS
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a write(2) operation.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* timed_write - write with deadline */
+
+ssize_t timed_write(int fd, const void *buf, size_t len,
+ int timeout, void *unused_context)
+{
+ ssize_t ret;
+
+ /*
+ * Wait for a limited amount of time for something to happen. If nothing
+ * happens, report an ETIMEDOUT error.
+ *
+ * XXX Solaris 8 read() fails with EAGAIN after read-select() returns
+ * success. The code below exists just in case their write implementation
+ * is equally broken.
+ *
+ * This condition may also be found on systems where select() returns
+ * success on pipes with less than PIPE_BUF bytes of space, and with
+ * badly designed software where multiple writers are fighting for access
+ * to the same resource.
+ */
+ for (;;) {
+ if (timeout > 0 && write_wait(fd, timeout) < 0)
+ return (-1);
+ if ((ret = write(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) {
+ msg_warn("write() returns EAGAIN on a writable file descriptor!");
+ msg_warn("pausing to avoid going into a tight select/write loop!");
+ sleep(1);
+ continue;
+ } else if (ret < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return (ret);
+ }
+ }
+}
diff --git a/src/util/translit.c b/src/util/translit.c
new file mode 100644
index 0000000..ba04bf2
--- /dev/null
+++ b/src/util/translit.c
@@ -0,0 +1,86 @@
+/*++
+/* NAME
+/* translit 3
+/* SUMMARY
+/* transliterate characters
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *translit(buf, original, replacement)
+/* char *buf;
+/* char *original;
+/* char *replacement;
+/* DESCRIPTION
+/* translit() takes a null-terminated string, and replaces characters
+/* given in its \fIoriginal\fR argument by the corresponding characters
+/* in the \fIreplacement\fR string. The result value is the \fIbuf\fR
+/* argument.
+/* BUGS
+/* Cannot replace null characters.
+/* 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 "stringops.h"
+
+char *translit(char *string, const char *original, const char *replacement)
+{
+ char *cp;
+ const char *op;
+
+ /*
+ * For large inputs, should use a lookup table.
+ */
+ for (cp = string; *cp != 0; cp++) {
+ for (op = original; *op != 0; op++) {
+ if (*cp == *op) {
+ *cp = replacement[op - original];
+ break;
+ }
+ }
+ }
+ return (string);
+}
+
+#ifdef TEST
+
+ /*
+ * Usage: translit string1 string2
+ *
+ * test program to perform the most basic operation of the UNIX tr command.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+#define STR vstring_str
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ if (argc != 3)
+ msg_fatal("usage: %s string1 string2", argv[0]);
+ while (vstring_fgets(buf, VSTREAM_IN))
+ vstream_fputs(translit(STR(buf), argv[1], argv[2]), VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/trigger.h b/src/util/trigger.h
new file mode 100644
index 0000000..e716d53
--- /dev/null
+++ b/src/util/trigger.h
@@ -0,0 +1,34 @@
+#ifndef _TRIGGER_H_INCLUDED_
+#define _TRIGGER_H_INCLUDED_
+
+/*++
+/* NAME
+/* trigger 3h
+/* SUMMARY
+/* client interface file
+/* SYNOPSIS
+/* #include <trigger.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int unix_trigger(const char *, const char *, ssize_t, int);
+extern int inet_trigger(const char *, const char *, ssize_t, int);
+extern int fifo_trigger(const char *, const char *, ssize_t, int);
+extern int stream_trigger(const char *, const char *, ssize_t, int);
+extern int pass_trigger(const char *, const char *, ssize_t, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/trimblanks.c b/src/util/trimblanks.c
new file mode 100644
index 0000000..a4d9cb2
--- /dev/null
+++ b/src/util/trimblanks.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* trimblanks 3
+/* SUMMARY
+/* skip leading whitespace
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *trimblanks(string, len)
+/* char *string;
+/* ssize_t len;
+/* DESCRIPTION
+/* trimblanks() returns a pointer to the beginning of the trailing
+/* whitespace in \fIstring\fR, or a pointer to the string terminator
+/* when the string contains no trailing whitespace.
+/* The \fIlen\fR argument is either zero or the string 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *trimblanks(char *string, ssize_t len)
+{
+ char *curr;
+
+ if (len) {
+ curr = string + len;
+ } else {
+ for (curr = string; *curr != 0; curr++)
+ /* void */ ;
+ }
+ while (curr > string && ISSPACE(curr[-1]))
+ curr -= 1;
+ return (curr);
+}
diff --git a/src/util/unescape.c b/src/util/unescape.c
new file mode 100644
index 0000000..4eacbba
--- /dev/null
+++ b/src/util/unescape.c
@@ -0,0 +1,208 @@
+/*++
+/* NAME
+/* unescape 3
+/* SUMMARY
+/* translate C-like escape sequences
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* VSTRING *unescape(result, input)
+/* VSTRING *result;
+/* const char *input;
+/*
+/* VSTRING *escape(result, input, len)
+/* VSTRING *result;
+/* const char *input;
+/* ssize_t len;
+/* DESCRIPTION
+/* unescape() translates C-like escape sequences in the null-terminated
+/* string \fIinput\fR and places the result in \fIresult\fR. The result
+/* is null-terminated, and is the function result value.
+/*
+/* escape() does the reverse transformation.
+/*
+/* Escape sequences and their translations:
+/* .IP \ea
+/* Bell character.
+/* .IP \eb
+/* Backspace character.
+/* .IP \ef
+/* formfeed character.
+/* .IP \en
+/* newline character
+/* .IP \er
+/* Carriage-return character.
+/* .IP \et
+/* Horizontal tab character.
+/* .IP \ev
+/* Vertical tab character.
+/* .IP \e\e
+/* Backslash character.
+/* .IP \e\fInum\fR
+/* 8-bit character whose ASCII value is the 1..3 digit
+/* octal number \fInum\fR.
+/* .IP \e\fIother\fR
+/* The backslash character is discarded.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <stringops.h>
+
+/* unescape - process escape sequences */
+
+VSTRING *unescape(VSTRING *result, const char *data)
+{
+ int ch;
+ int oval;
+ int i;
+
+#define UCHAR(cp) ((unsigned char *) (cp))
+#define ISOCTAL(ch) (ISDIGIT(ch) && (ch) != '8' && (ch) != '9')
+
+ VSTRING_RESET(result);
+
+ while ((ch = *UCHAR(data++)) != 0) {
+ if (ch == '\\') {
+ if ((ch = *UCHAR(data++)) == 0)
+ break;
+ switch (ch) {
+ case 'a': /* \a -> audible bell */
+ ch = '\a';
+ break;
+ case 'b': /* \b -> backspace */
+ ch = '\b';
+ break;
+ case 'f': /* \f -> formfeed */
+ ch = '\f';
+ break;
+ case 'n': /* \n -> newline */
+ ch = '\n';
+ break;
+ case 'r': /* \r -> carriagereturn */
+ ch = '\r';
+ break;
+ case 't': /* \t -> horizontal tab */
+ ch = '\t';
+ break;
+ case 'v': /* \v -> vertical tab */
+ ch = '\v';
+ break;
+ case '0': /* \nnn -> ASCII value */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ for (oval = ch - '0', i = 0;
+ i < 2 && (ch = *UCHAR(data)) != 0 && ISOCTAL(ch);
+ i++, data++) {
+ oval = (oval << 3) | (ch - '0');
+ }
+ ch = oval;
+ break;
+ default: /* \any -> any */
+ break;
+ }
+ }
+ VSTRING_ADDCH(result, ch);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* escape - reverse transformation */
+
+VSTRING *escape(VSTRING *result, const char *data, ssize_t len)
+{
+ int ch;
+
+ VSTRING_RESET(result);
+ while (len-- > 0) {
+ ch = *UCHAR(data++);
+ if (ISASCII(ch)) {
+ if (ISPRINT(ch)) {
+ if (ch == '\\')
+ VSTRING_ADDCH(result, ch);
+ VSTRING_ADDCH(result, ch);
+ continue;
+ } else if (ch == '\a') { /* \a -> audible bell */
+ vstring_strcat(result, "\\a");
+ continue;
+ } else if (ch == '\b') { /* \b -> backspace */
+ vstring_strcat(result, "\\b");
+ continue;
+ } else if (ch == '\f') { /* \f -> formfeed */
+ vstring_strcat(result, "\\f");
+ continue;
+ } else if (ch == '\n') { /* \n -> newline */
+ vstring_strcat(result, "\\n");
+ continue;
+ } else if (ch == '\r') { /* \r -> carriagereturn */
+ vstring_strcat(result, "\\r");
+ continue;
+ } else if (ch == '\t') { /* \t -> horizontal tab */
+ vstring_strcat(result, "\\t");
+ continue;
+ } else if (ch == '\v') { /* \v -> vertical tab */
+ vstring_strcat(result, "\\v");
+ continue;
+ }
+ }
+ vstring_sprintf_append(result, "\\%03o", ch);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(10);
+ VSTRING *out = vstring_alloc(10);
+ int un_escape = 1;
+
+ if (argc > 2 || (argc > 1 && (un_escape = strcmp(argv[1], "-e"))) != 0)
+ msg_fatal("usage: %s [-e (escape)]", argv[0]);
+
+ if (un_escape) {
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ unescape(out, vstring_str(in));
+ vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ }
+ } else {
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ escape(out, vstring_str(in), VSTRING_LEN(in));
+ vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ }
+ }
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unescape.in b/src/util/unescape.in
new file mode 100644
index 0000000..41f24a7
--- /dev/null
+++ b/src/util/unescape.in
@@ -0,0 +1,4 @@
+\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+\1\2\3\4\5\6\7\8\9
+\1234\2345\3456\4567
+rcpt to:<wietse@\317\200.porcupine.org>
diff --git a/src/util/unescape.ref b/src/util/unescape.ref
new file mode 100644
index 0000000..db16fa8
--- /dev/null
+++ b/src/util/unescape.ref
@@ -0,0 +1,11 @@
+0000000 \a \b c d e \f g h i j k l m \n o p
+ 007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160
+0000020 q \r s \t u \v w x y z \n 001 002 003 004 005
+ 161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005
+0000040 006 \a 8 9 \n S 4 234 5 345 6 . 7 \n r c
+ 006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143
+0000060 p t t o : < w i e t s e @ π **
+ 160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200
+0000100 . p o r c u p i n e . o r g > \n
+ 056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012
+0000120
diff --git a/src/util/unix_connect.c b/src/util/unix_connect.c
new file mode 100644
index 0000000..cbd8c0d
--- /dev/null
+++ b/src/util/unix_connect.c
@@ -0,0 +1,110 @@
+/*++
+/* NAME
+/* unix_connect 3
+/* SUMMARY
+/* connect to UNIX-domain listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int unix_connect(addr, block_mode, timeout)
+/* const char *addr;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* unix_connect() connects to a listener in the UNIX domain at the
+/* specified address, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP addr
+/* Null-terminated string with connection destination.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP timeout
+/* Bounds the number of seconds that the operation may take. Specify
+/* a value <= 0 to disable the time limit.
+/* DIAGNOSTICS
+/* The result is -1 in case the connection could not be made.
+/* Fatal errors: other system call failures.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "sane_connect.h"
+#include "connect.h"
+#include "timed_connect.h"
+
+/* unix_connect - connect to UNIX-domain listener */
+
+int unix_connect(const char *addr, int block_mode, int timeout)
+{
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t len = strlen(addr);
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if (len >= sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", addr);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = len + 1;
+#endif
+ memcpy(sun.sun_path, addr, len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return (-1);
+
+ /*
+ * Timed connect.
+ */
+ if (timeout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ if (timed_connect(sock, (struct sockaddr *) &sun, sizeof(sun), timeout) < 0) {
+ close(sock);
+ return (-1);
+ }
+ if (block_mode != NON_BLOCKING)
+ non_blocking(sock, block_mode);
+ return (sock);
+ }
+
+ /*
+ * Maybe block until connected.
+ */
+ else {
+ non_blocking(sock, block_mode);
+ if (sane_connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0
+ && errno != EINPROGRESS) {
+ close(sock);
+ return (-1);
+ }
+ return (sock);
+ }
+}
diff --git a/src/util/unix_dgram_connect.c b/src/util/unix_dgram_connect.c
new file mode 100644
index 0000000..3df8963
--- /dev/null
+++ b/src/util/unix_dgram_connect.c
@@ -0,0 +1,91 @@
+/*++
+/* NAME
+/* unix_dgram_connect 3
+/* SUMMARY
+/* connect to UNIX-domain datagram server
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int unix_dgram_connect(
+/* const char *path,
+/* int block_mode)
+/* DESCRIPTION
+/* unix_dgram_connect() connects to the specified UNIX-domain
+/* datagram server, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.`
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: path too large, can't create socket.
+/*
+/* Other errors result in a -1 result value, with errno indicating
+/* why the service is unavailable.
+/* .sp
+/* ENOENT: the named socket does not exist.
+/* .sp
+/* ECONNREFUSED: the named socket is not open.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* unix_dgram_connect - connect to UNIX-domain datagram service */
+
+int unix_dgram_connect(const char *path, int block_mode)
+{
+ const char myname[] = "unix_dgram_connect";
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t path_len;
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if ((path_len = strlen(path)) >= sizeof(sun.sun_path))
+ msg_fatal("%s: unix-domain name too long: %s", myname, path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+ if (connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) {
+ close(sock);
+ return (-1);
+ }
+ non_blocking(sock, block_mode);
+ return (sock);
+}
diff --git a/src/util/unix_dgram_listen.c b/src/util/unix_dgram_listen.c
new file mode 100644
index 0000000..e73ad4e
--- /dev/null
+++ b/src/util/unix_dgram_listen.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* unix_dgram_listen 3
+/* SUMMARY
+/* listen to UNIX-domain datagram server
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int unix_dgram_listen(
+/* const char *path,
+/* int block_mode)
+/* DESCRIPTION
+/* unix_dgram_listen() binds to the specified UNIX-domain
+/* datagram endpoint, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: path too large, can't create socket.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <listen.h>
+#include <msg.h>
+
+/* unix_dgram_listen - bind to UNIX-domain datagram endpoint */
+
+int unix_dgram_listen(const char *path, int block_mode)
+{
+ const char myname[] = "unix_dgram_listen";
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t path_len;
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if ((path_len = strlen(path)) >= sizeof(sun.sun_path))
+ msg_fatal("%s: unix-domain name too long: %s", myname, path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len + 1);
+
+ /*
+ * Create a 'server' socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+ if (unlink(path) < 0 && errno != ENOENT)
+ msg_fatal( "remove %s: %m", path);
+ if (bind(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0)
+ msg_fatal( "bind: %s: %m", path);
+#ifdef FCHMOD_UNIX_SOCKETS
+ if (fchmod(sock, 0666) < 0)
+ msg_fatal("fchmod socket %s: %m", path);
+#else
+ if (chmod(path, 0666) < 0)
+ msg_fatal("chmod socket %s: %m", path);
+#endif
+ non_blocking(sock, block_mode);
+ return (sock);
+}
diff --git a/src/util/unix_listen.c b/src/util/unix_listen.c
new file mode 100644
index 0000000..6440406
--- /dev/null
+++ b/src/util/unix_listen.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* unix_listen 3
+/* SUMMARY
+/* start UNIX-domain listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int unix_listen(addr, backlog, block_mode)
+/* const char *addr;
+/* int backlog;
+/* int block_mode;
+/*
+/* int unix_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* The \fBunix_listen\fR() routine starts a listener in the UNIX domain
+/* on the specified address, with the specified backlog, and returns
+/* the resulting file descriptor.
+/*
+/* unix_accept() accepts a connection and sanitizes error results.
+/*
+/* Arguments:
+/* .IP addr
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* This argument is passed on to the \fIlisten(2)\fR routine.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP fd
+/* File descriptor returned by unix_listen().
+/* DIAGNOSTICS
+/* Fatal errors: unix_listen() aborts upon any system call failure.
+/* unix_accept() leaves all error handling up to the caller.
+/* 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 interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "sane_accept.h"
+
+/* unix_listen - create UNIX-domain listener */
+
+int unix_listen(const char *addr, int backlog, int block_mode)
+{
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t len = strlen(addr);
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if (len >= sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", addr);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = len + 1;
+#endif
+ memcpy(sun.sun_path, addr, len + 1);
+
+ /*
+ * Create a listener socket. Do whatever we can so we don't run into
+ * trouble when this process is restarted after crash.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ if (unlink(addr) < 0 && errno != ENOENT)
+ msg_fatal("remove %s: %m", addr);
+ if (bind(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0)
+ msg_fatal("bind: %s: %m", addr);
+#ifdef FCHMOD_UNIX_SOCKETS
+ if (fchmod(sock, 0666) < 0)
+ msg_fatal("fchmod socket %s: %m", addr);
+#else
+ if (chmod(addr, 0666) < 0)
+ msg_fatal("chmod socket %s: %m", addr);
+#endif
+ non_blocking(sock, block_mode);
+ if (listen(sock, backlog) < 0)
+ msg_fatal("listen: %m");
+ return (sock);
+}
+
+/* unix_accept - accept connection */
+
+int unix_accept(int fd)
+{
+ return (sane_accept(fd, (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0));
+}
diff --git a/src/util/unix_pass_fd_fix.c b/src/util/unix_pass_fd_fix.c
new file mode 100644
index 0000000..9522e61
--- /dev/null
+++ b/src/util/unix_pass_fd_fix.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* unix_pass_fd_fix 3
+/* SUMMARY
+/* file descriptor passing bug workarounds
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void set_unix_pass_fd_fix(workarounds)
+/* const char *workarounds;
+/* DESCRIPTION
+/* This module supports programmatic control over workarounds
+/* for sending or receiving file descriptors over UNIX-domain
+/* sockets.
+/*
+/* set_unix_pass_fd_fix() takes a list of workarounds in external
+/* form, and stores their internal representation. The result
+/* is used by unix_send_fd() and unix_recv_fd().
+/*
+/* Arguments:
+/* .IP workarounds
+/* List of zero or more of the following, separated by comma
+/* or whitespace.
+/* .RS
+/* .IP cmsg_len
+/* Send the CMSG_LEN of the file descriptor, instead of
+/* the total message buffer length.
+/* .RE
+/* SEE ALSO
+/* unix_send_fd(3) send file descriptor
+/* unix_recv_fd(3) receive file descriptor
+/* DIAGNOSTICS
+/* Fatal errors: non-existent workaround.
+/* 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 <iostuff.h>
+#include <name_mask.h>
+
+int unix_pass_fd_fix = 0;
+
+/* set_unix_pass_fd_fix - set workaround programmatically */
+
+void set_unix_pass_fd_fix(const char *workarounds)
+{
+ const static NAME_MASK table[] = {
+ "cmsg_len", UNIX_PASS_FD_FIX_CMSG_LEN,
+ 0,
+ };
+
+ unix_pass_fd_fix = name_mask("descriptor passing workarounds",
+ table, workarounds);
+}
diff --git a/src/util/unix_recv_fd.c b/src/util/unix_recv_fd.c
new file mode 100644
index 0000000..3410cff
--- /dev/null
+++ b/src/util/unix_recv_fd.c
@@ -0,0 +1,178 @@
+/*++
+/* NAME
+/* unix_recv_fd 3
+/* SUMMARY
+/* receive file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int unix_recv_fd(fd)
+/* int fd;
+/* DESCRIPTION
+/* unix_recv_fd() receives a file descriptor via the specified
+/* UNIX-domain socket. The result value is the received descriptor.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* DIAGNOSTICS
+/* unix_recv_fd() returns -1 upon failure.
+/* 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> /* includes <sys/types.h> */
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* unix_recv_fd - receive file descriptor */
+
+int unix_recv_fd(int fd)
+{
+ const char *myname = "unix_recv_fd";
+
+ /*
+ * This code does not work with version <2.2 Linux kernels, and it does
+ * not compile with version <2 Linux libraries.
+ */
+#ifdef CANT_USE_SEND_RECV_MSG
+ msg_warn("%s: your system has no support for file descriptor passing",
+ myname);
+ return (-1);
+#else
+ struct msghdr msg;
+ int newfd;
+ struct iovec iov[1];
+ char buf[1];
+
+ /*
+ * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
+ * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for
+ * portability to some LP64 environments. See also unix_send_fd.c.
+ */
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr just_for_alignment;
+ char control[CMSG_SPACE(sizeof(newfd))];
+ } control_un;
+ struct cmsghdr *cmptr;
+
+ memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */
+ msg.msg_control = control_un.control;
+ if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) {
+ msg.msg_controllen = CMSG_LEN(sizeof(newfd)); /* Fix 200506 */
+ } else {
+ msg.msg_controllen = sizeof(control_un.control); /* normal */
+ }
+#else
+ msg.msg_accrights = (char *) &newfd;
+ msg.msg_accrightslen = sizeof(newfd);
+#endif
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+
+ /*
+ * XXX We don't want to pass any data, just a file descriptor. However,
+ * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble: we need
+ * to read_wait() before we can receive the descriptor, and the code
+ * fails after the first descriptor when we attempt to receive a sequence
+ * of descriptors.
+ */
+ iov->iov_base = buf;
+ iov->iov_len = sizeof(buf);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ if (recvmsg(fd, &msg, 0) < 0)
+ return (-1);
+
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ if ((cmptr = CMSG_FIRSTHDR(&msg)) != 0
+ && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))) {
+ if (cmptr->cmsg_level != SOL_SOCKET)
+ msg_fatal("%s: control level %d != SOL_SOCKET",
+ myname, cmptr->cmsg_level);
+ if (cmptr->cmsg_type != SCM_RIGHTS)
+ msg_fatal("%s: control type %d != SCM_RIGHTS",
+ myname, cmptr->cmsg_type);
+ return (*(int *) CMSG_DATA(cmptr));
+ } else
+ return (-1);
+#else
+ if (msg.msg_accrightslen == sizeof(newfd))
+ return (newfd);
+ else
+ return (-1);
+#endif
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Receive a descriptor (presumably from the
+ * unix_send_fd test program) and copy its content until EOF.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <listen.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ int listen_sock;
+ int client_sock;
+ int client_fd;
+ ssize_t read_count;
+ char buf[1024];
+
+ if (argc < 2 || argc > 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint [workaround]", argv[0]);
+
+ if (strcmp(transport, "unix") == 0) {
+ listen_sock = unix_listen(endpoint, 10, BLOCKING);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (listen_sock < 0)
+ msg_fatal("listen %s:%s: %m", transport, endpoint);
+
+ client_sock = accept(listen_sock, (struct sockaddr *) 0, (SOCKADDR_SIZE) 0);
+ if (client_sock < 0)
+ msg_fatal("accept: %m");
+
+ set_unix_pass_fd_fix(argv[2] ? argv[2] : "");
+
+ while ((client_fd = unix_recv_fd(client_sock)) >= 0) {
+ msg_info("client_fd = %d, fix=%d", client_fd, unix_pass_fd_fix);
+ while ((read_count = read(client_fd, buf, sizeof(buf))) > 0)
+ write(1, buf, read_count);
+ if (read_count < 0)
+ msg_fatal("read: %m");
+ close(client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unix_send_fd.c b/src/util/unix_send_fd.c
new file mode 100644
index 0000000..1998a7c
--- /dev/null
+++ b/src/util/unix_send_fd.c
@@ -0,0 +1,193 @@
+/*++
+/* NAME
+/* unix_send_fd 3
+/* SUMMARY
+/* send file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int unix_send_fd(fd, sendfd)
+/* int fd;
+/* int sendfd;
+/* DESCRIPTION
+/* unix_send_fd() sends a file descriptor over the specified
+/* UNIX-domain socket.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* .IP sendfd
+/* The file descriptor to be sent.
+/* DIAGNOSTICS
+/* unix_send_fd() returns -1 upon failure.
+/* 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> /* includes <sys/types.h> */
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* unix_send_fd - send file descriptor */
+
+int unix_send_fd(int fd, int sendfd)
+{
+
+ /*
+ * This code does not work with version <2.2 Linux kernels, and it does
+ * not compile with version <2 Linux libraries.
+ */
+#ifdef CANT_USE_SEND_RECV_MSG
+ const char *myname = "unix_send_fd";
+
+ msg_warn("%s: your system has no support for file descriptor passing",
+ myname);
+ return (-1);
+#else
+ struct msghdr msg;
+ struct iovec iov[1];
+
+ /*
+ * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
+ * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for
+ * portability to some LP64 environments. See also unix_recv_fd.c.
+ */
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr just_for_alignment;
+ char control[CMSG_SPACE(sizeof(sendfd))];
+ } control_un;
+ struct cmsghdr *cmptr;
+
+ memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */
+ msg.msg_control = control_un.control;
+ if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) {
+ msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */
+ } else {
+ msg.msg_controllen = sizeof(control_un.control); /* normal */
+ }
+ cmptr = CMSG_FIRSTHDR(&msg);
+ cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd));
+ cmptr->cmsg_level = SOL_SOCKET;
+ cmptr->cmsg_type = SCM_RIGHTS;
+ *(int *) CMSG_DATA(cmptr) = sendfd;
+#else
+ msg.msg_accrights = (char *) &sendfd;
+ msg.msg_accrightslen = sizeof(sendfd);
+#endif
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+
+ /*
+ * XXX We don't want to pass any data, just a file descriptor. However,
+ * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the
+ * comments in the unix_recv_fd() routine.
+ */
+ iov->iov_base = "";
+ iov->iov_len = 1;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ /*
+ * The CMSG_LEN send/receive workaround was originally developed for
+ * OpenBSD 3.6 on SPARC64. After the workaround was verified to not break
+ * Solaris 8 on SPARC64, it was hard-coded with Postfix 2.3 for all
+ * platforms because of increasing pressure to work on other things. The
+ * workaround does nothing for 32-bit systems.
+ *
+ * The investigation was reopened with Postfix 2.7 because the workaround
+ * broke with NetBSD 5.0 on 64-bit architectures. This time it was found
+ * that OpenBSD <= 4.3 on AMD64 and SPARC64 needed the workaround for
+ * sending only. The following platforms worked with and without the
+ * workaround: OpenBSD 4.5 on AMD64 and SPARC64, FreeBSD 7.2 on AMD64,
+ * Solaris 8 on SPARC64, and Linux 2.6-11 on x86_64.
+ *
+ * As this appears to have been an OpenBSD-specific problem, we revert to
+ * the Postfix 2.2 behavior. Instead of hard-coding the workaround for
+ * all platforms, we now detect sendmsg() errors at run time and turn on
+ * the workaround dynamically.
+ *
+ * The workaround was made run-time configurable to investigate the problem
+ * on multiple platforms. Though set_unix_pass_fd_fix() is over-kill for
+ * this specific problem, it is left in place so that it can serve as an
+ * example of how to add run-time configurable workarounds to Postfix.
+ */
+ if (sendmsg(fd, &msg, 0) >= 0)
+ return (0);
+ if (unix_pass_fd_fix == 0) {
+ if (msg_verbose)
+ msg_info("sendmsg error (%m). Trying CMSG_LEN workaround.");
+ unix_pass_fd_fix = UNIX_PASS_FD_FIX_CMSG_LEN;
+ return (unix_send_fd(fd, sendfd));
+ } else {
+ return (-1);
+ }
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Open a file and send the descriptor, presumably
+ * to the unix_recv_fd test program.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <split_at.h>
+#include <connect.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ char *path;
+ int server_sock;
+ int client_fd;
+
+ msg_verbose = 1;
+
+ if (argc < 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint file...", argv[0]);
+
+ if (strcmp(transport, "unix") == 0) {
+ server_sock = unix_connect(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (server_sock < 0)
+ msg_fatal("connect %s:%s: %m", transport, endpoint);
+
+ argv += 2;
+ while ((path = *argv++) != 0) {
+ if ((client_fd = open(path, O_RDONLY, 0)) < 0)
+ msg_fatal("open %s: %m", path);
+ msg_info("path=%s fd=%d", path, client_fd);
+ if (unix_send_fd(server_sock, client_fd) < 0)
+ msg_fatal("send file descriptor: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unix_trigger.c b/src/util/unix_trigger.c
new file mode 100644
index 0000000..59a18ff
--- /dev/null
+++ b/src/util/unix_trigger.c
@@ -0,0 +1,131 @@
+/*++
+/* NAME
+/* unix_trigger 3
+/* SUMMARY
+/* wakeup UNIX-domain server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int unix_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* unix_trigger() wakes up the named UNIX-domain server by making
+/* a brief connection to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* unix_connect(3), UNIX-domain client
+/* 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/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct unix_trigger {
+ int fd;
+ char *service;
+};
+
+/* unix_trigger_event - disconnect from peer */
+
+static void unix_trigger_event(int event, void *context)
+{
+ struct unix_trigger *up = (struct unix_trigger *) context;
+ static const char *myname = "unix_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, up->service);
+ event_disable_readwrite(up->fd);
+ event_cancel_timer(unix_trigger_event, context);
+ if (close(up->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, up->service);
+ myfree(up->service);
+ myfree((void *) up);
+}
+
+/* unix_trigger - wakeup UNIX-domain server */
+
+int unix_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "unix_trigger";
+ struct unix_trigger *up;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = unix_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Stash away context.
+ */
+ up = (struct unix_trigger *) mymalloc(sizeof(*up));
+ up->fd = fd;
+ up->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(unix_trigger_event, (void *) up, timeout + 100);
+ event_enable_read(fd, unix_trigger_event, (void *) up);
+ return (0);
+}
diff --git a/src/util/unsafe.c b/src/util/unsafe.c
new file mode 100644
index 0000000..5d307c9
--- /dev/null
+++ b/src/util/unsafe.c
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/* unsafe 3
+/* SUMMARY
+/* are we running at non-user privileges
+/* SYNOPSIS
+/* #include <safe.h>
+/*
+/* int unsafe()
+/* DESCRIPTION
+/* The \fBunsafe()\fR routine attempts to determine if the process
+/* (runs with privileges or has access to information) that the
+/* controlling user has no access to. The purpose is to prevent
+/* misuse of privileges, including access to protected information.
+/*
+/* The result is always false when both of the following conditions
+/* are true:
+/* .IP \(bu
+/* The real UID is zero.
+/* .IP \(bu
+/* The effective UID is zero.
+/* .PP
+/* Otherwise, the result is true if any of the following conditions
+/* is true:
+/* .IP \(bu
+/* The issetuid kernel flag is non-zero (on systems that support
+/* this concept).
+/* .IP \(bu
+/* The real and effective user id differ.
+/* .IP \(bu
+/* The real and effective group id differ.
+/* 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 <unistd.h>
+
+/* Utility library. */
+
+#include "safe.h"
+
+/* unsafe - can we trust user-provided environment, working directory, etc. */
+
+int unsafe(void)
+{
+
+ /*
+ * The super-user is trusted.
+ */
+ if (getuid() == 0 && geteuid() == 0)
+ return (0);
+
+ /*
+ * Danger: don't trust inherited process attributes, and don't leak
+ * privileged info that the parent has no access to.
+ */
+ return (geteuid() != getuid()
+#ifdef HAS_ISSETUGID
+ || issetugid()
+#endif
+ || getgid() != getegid());
+}
diff --git a/src/util/uppercase.c b/src/util/uppercase.c
new file mode 100644
index 0000000..9c61622
--- /dev/null
+++ b/src/util/uppercase.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* uppercase 3
+/* SUMMARY
+/* map lowercase characters to uppercase
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *uppercase(buf)
+/* char *buf;
+/* DESCRIPTION
+/* uppercase() replaces lowercase characters in its null-terminated
+/* input by their uppercase equivalent.
+/* 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 <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *uppercase(char *string)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *cp) != 0; cp++)
+ if (ISLOWER(ch))
+ *cp = TOUPPER(ch);
+ return (string);
+}
diff --git a/src/util/username.c b/src/util/username.c
new file mode 100644
index 0000000..680161c
--- /dev/null
+++ b/src/util/username.c
@@ -0,0 +1,47 @@
+/*++
+/* NAME
+/* username 3
+/* SUMMARY
+/* lookup name of real user
+/* SYNOPSIS
+/* #include <username.h>
+/*
+/* const char *username()
+/* DESCRIPTION
+/* username() jumps whatever system-specific hoops it takes to
+/* get the name of the user who started the process. The result
+/* is volatile. Make a copy if it is to be used for an appreciable
+/* amount of time.
+/* 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 <pwd.h>
+
+/* Utility library. */
+
+#include "username.h"
+
+/* username - get name of user */
+
+const char *username(void)
+{
+ uid_t uid;
+ struct passwd *pwd;
+
+ uid = getuid();
+ if ((pwd = getpwuid(uid)) == 0)
+ return (0);
+ return (pwd->pw_name);
+}
diff --git a/src/util/username.h b/src/util/username.h
new file mode 100644
index 0000000..648be45
--- /dev/null
+++ b/src/util/username.h
@@ -0,0 +1,29 @@
+#ifndef _USERNAME_H_INCLUDED_
+#define _USERNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* username 3h
+/* SUMMARY
+/* lookup name of real user
+/* SYNOPSIS
+/* #include <username.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *username(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c
new file mode 100644
index 0000000..8b234c4
--- /dev/null
+++ b/src/util/valid_hostname.c
@@ -0,0 +1,412 @@
+/*++
+/* NAME
+/* valid_hostname 3
+/* SUMMARY
+/* network name validation
+/* SYNOPSIS
+/* #include <valid_hostname.h>
+/*
+/* int valid_hostname(name, gripe)
+/* const char *name;
+/* int gripe;
+/*
+/* int valid_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_ipv4_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_ipv6_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_hostport(port, gripe)
+/* const char *port;
+/* int gripe;
+/* DESCRIPTION
+/* valid_hostname() scrutinizes a hostname: the name should
+/* be no longer than VALID_HOSTNAME_LEN characters, should
+/* contain only letters, digits, dots and hyphens, no adjacent
+/* dots, no leading or trailing dots or hyphens, no labels
+/* longer than VALID_LABEL_LEN characters, and it should not
+/* be all numeric.
+/*
+/* valid_hostaddr() requires that the input is a valid string
+/* representation of an IPv4 or IPv6 network address as
+/* described next.
+/*
+/* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement
+/* protocol-specific address syntax checks. A valid IPv4
+/* address is in dotted-quad decimal form. A valid IPv6 address
+/* has 16-bit hexadecimal fields separated by ":", and does not
+/* include the RFC 2821 style "IPv6:" prefix.
+/*
+/* These routines operate silently unless the gripe parameter
+/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE
+/* provide suitable constants.
+/*
+/* valid_hostport() requires that the input is a valid string
+/* representation of a TCP or UDP port number.
+/* BUGS
+/* valid_hostmumble() does not guarantee that string lengths
+/* fit the buffer sizes defined in myaddrinfo(3h).
+/* DIAGNOSTICS
+/* All functions return zero if they disagree with the input.
+/* SEE ALSO
+/* RFC 952, RFC 1123, RFC 1035, RFC 2373.
+/* 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>
+#include <ctype.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "valid_hostname.h"
+
+/* valid_hostname - screen out bad hostnames */
+
+int valid_hostname(const char *name, int flags)
+{
+ const char *myname = "valid_hostname";
+ const char *cp;
+ int label_length = 0;
+ int label_count = 0;
+ int non_numeric = 0;
+ int ch;
+ int gripe = flags & DO_GRIPE;
+
+ /*
+ * Trivial cases first.
+ */
+ if (*name == 0) {
+ if (gripe)
+ msg_warn("%s: empty hostname", myname);
+ return (0);
+ }
+
+ /*
+ * Find bad characters or label lengths. Find adjacent delimiters.
+ */
+ for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ISALNUM(ch) || ch == '_') { /* grr.. */
+ if (label_length == 0)
+ label_count++;
+ label_length++;
+ if (label_length > VALID_LABEL_LEN) {
+ if (gripe)
+ msg_warn("%s: hostname label too long: %.100s", myname, name);
+ return (0);
+ }
+ if (!ISDIGIT(ch))
+ non_numeric = 1;
+ } else if ((flags & DO_WILDCARD) && ch == '*') {
+ if (label_length || label_count || (cp[1] && cp[1] != '.')) {
+ if (gripe)
+ msg_warn("%s: '*' can be the first label only: %.100s", myname, name);
+ return (0);
+ }
+ label_count++;
+ label_length++;
+ non_numeric = 1;
+ } else if (ch == '.') {
+ if (label_length == 0 || cp[1] == 0) {
+ if (gripe)
+ msg_warn("%s: misplaced delimiter: %.100s", myname, name);
+ return (0);
+ }
+ label_length = 0;
+ } else if (ch == '-') {
+ non_numeric = 1;
+ label_length++;
+ if (label_length == 1 || cp[1] == 0 || cp[1] == '.') {
+ if (gripe)
+ msg_warn("%s: misplaced hyphen: %.100s", myname, name);
+ return (0);
+ }
+ }
+#ifdef SLOPPY_VALID_HOSTNAME
+ else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) {
+ non_numeric = 0;
+ break;
+ }
+#endif
+ else {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, name);
+ return (0);
+ }
+ }
+
+ if (non_numeric == 0) {
+ if (gripe)
+ msg_warn("%s: numeric hostname: %.100s", myname, name);
+#ifndef SLOPPY_VALID_HOSTNAME
+ return (0);
+#endif
+ }
+ if (cp - name > VALID_HOSTNAME_LEN) {
+ if (gripe)
+ msg_warn("%s: bad length %d for %.100s...",
+ myname, (int) (cp - name), name);
+ return (0);
+ }
+ return (1);
+}
+
+/* valid_hostaddr - verify numerical address syntax */
+
+int valid_hostaddr(const char *addr, int gripe)
+{
+ const char *myname = "valid_hostaddr";
+
+ /*
+ * Trivial cases first.
+ */
+ if (*addr == 0) {
+ if (gripe)
+ msg_warn("%s: empty address", myname);
+ return (0);
+ }
+
+ /*
+ * Protocol-dependent processing next.
+ */
+ if (strchr(addr, ':') != 0)
+ return (valid_ipv6_hostaddr(addr, gripe));
+ else
+ return (valid_ipv4_hostaddr(addr, gripe));
+}
+
+/* valid_ipv4_hostaddr - test dotted quad string for correctness */
+
+int valid_ipv4_hostaddr(const char *addr, int gripe)
+{
+ const char *cp;
+ const char *myname = "valid_ipv4_hostaddr";
+ int in_byte = 0;
+ int byte_count = 0;
+ int byte_val = 0;
+ int ch;
+
+#define BYTES_NEEDED 4
+
+ /*
+ * Scary code to avoid sscanf() overflow nasties.
+ *
+ * This routine is called by valid_ipv6_hostaddr(). It must not call that
+ * routine, to avoid deadly recursion.
+ */
+ for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ISDIGIT(ch)) {
+ if (in_byte == 0) {
+ in_byte = 1;
+ byte_val = 0;
+ byte_count++;
+ }
+ byte_val *= 10;
+ byte_val += ch - '0';
+ if (byte_val > 255) {
+ if (gripe)
+ msg_warn("%s: invalid octet value: %.100s", myname, addr);
+ return (0);
+ }
+ } else if (ch == '.') {
+ if (in_byte == 0 || cp[1] == 0) {
+ if (gripe)
+ msg_warn("%s: misplaced dot: %.100s", myname, addr);
+ return (0);
+ }
+ /* XXX Allow 0.0.0.0 but not 0.1.2.3 */
+ if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) {
+ if (gripe)
+ msg_warn("%s: bad initial octet value: %.100s", myname, addr);
+ return (0);
+ }
+ in_byte = 0;
+ } else {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, addr);
+ return (0);
+ }
+ }
+
+ if (byte_count != BYTES_NEEDED) {
+ if (gripe)
+ msg_warn("%s: invalid octet count: %.100s", myname, addr);
+ return (0);
+ }
+ return (1);
+}
+
+/* valid_ipv6_hostaddr - validate IPv6 address syntax */
+
+int valid_ipv6_hostaddr(const char *addr, int gripe)
+{
+ const char *myname = "valid_ipv6_hostaddr";
+ int null_field = 0;
+ int field = 0;
+ unsigned char *cp = (unsigned char *) addr;
+ int len = 0;
+
+ /*
+ * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I
+ * am not confident that everyone's system library routines are robust
+ * enough, like buffer overflow free. Remember, the valid_hostmumble()
+ * routines are meant to protect Postfix against malformed information in
+ * data received from the network.
+ *
+ * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7,
+ * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same.
+ *
+ * Note: the character position is advanced inside the loop. I have added
+ * comments to show why we can't get stuck.
+ */
+ for (;;) {
+ switch (*cp) {
+ case 0:
+ /* Terminate the loop. */
+ if (field < 2) {
+ if (gripe)
+ msg_warn("%s: too few `:' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else if (len == 0 && null_field != field - 1) {
+ if (gripe)
+ msg_warn("%s: bad null last field in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else
+ return (1);
+ case '.':
+ /* Terminate the loop. */
+ if (field < 2 || field > 6) {
+ if (gripe)
+ msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else
+ /* NOT: valid_hostaddr(). Avoid recursion. */
+ return (valid_ipv4_hostaddr((char *) cp - len, gripe));
+ case ':':
+ /* Advance by exactly 1 character position or terminate. */
+ if (field == 0 && len == 0 && ISALNUM(cp[1])) {
+ if (gripe)
+ msg_warn("%s: bad null first field in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ field++;
+ if (field > 7) {
+ if (gripe)
+ msg_warn("%s: too many `:' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ cp++;
+ len = 0;
+ if (*cp == ':') {
+ if (null_field > 0) {
+ if (gripe)
+ msg_warn("%s: too many `::' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ null_field = field;
+ }
+ break;
+ default:
+ /* Advance by at least 1 character position or terminate. */
+ len = strspn((char *) cp, "0123456789abcdefABCDEF");
+ if (len /* - strspn((char *) cp, "0") */ > 4) {
+ if (gripe)
+ msg_warn("%s: malformed IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ if (len <= 0) {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s",
+ myname, *cp, addr);
+ return (0);
+ }
+ cp += len;
+ break;
+ }
+ }
+}
+
+/* valid_hostport - validate numeric port */
+
+int valid_hostport(const char *str, int gripe)
+{
+ const char *myname = "valid_hostport";
+ int port;
+
+ if (str[0] == '0' && str[1] != 0) {
+ if (gripe)
+ msg_warn("%s: leading zero in port number: %.100s", myname, str);
+ return (0);
+ }
+ if (alldig(str) == 0) {
+ if (gripe)
+ msg_warn("%s: non-numeric port number: %.100s", myname, str);
+ return (0);
+ }
+ if (strlen(str) > strlen("65535")
+ || (port = atoi(str)) > 65535 || port < 0) {
+ if (gripe)
+ msg_warn("%s: out-of-range port number: %.100s", myname, str);
+ return (0);
+ }
+ return (1);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - reads hostnames from stdin, reports invalid hostnames to
+ * stderr.
+ */
+#include <stdlib.h>
+
+#include "vstring.h"
+#include "vstream.h"
+#include "vstring_vstream.h"
+#include "msg_vstream.h"
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ msg_info("testing: \"%s\"", vstring_str(buffer));
+ valid_hostname(vstring_str(buffer), DO_GRIPE);
+ valid_hostaddr(vstring_str(buffer), DO_GRIPE);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/valid_hostname.h b/src/util/valid_hostname.h
new file mode 100644
index 0000000..463bc6e
--- /dev/null
+++ b/src/util/valid_hostname.h
@@ -0,0 +1,41 @@
+#ifndef _VALID_HOSTNAME_H_INCLUDED_
+#define _VALID_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_hostname 3h
+/* SUMMARY
+/* validate hostname
+/* SYNOPSIS
+/* #include <valid_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+#define VALID_HOSTNAME_LEN 255 /* RFC 1035 */
+#define VALID_LABEL_LEN 63 /* RFC 1035 */
+
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+#define DONT_WILDCARD 0
+#define DO_WILDCARD (1<<1)
+
+extern int valid_hostname(const char *, int);
+extern int valid_hostaddr(const char *, int);
+extern int valid_ipv4_hostaddr(const char *, int);
+extern int valid_ipv6_hostaddr(const char *, int);
+extern int valid_hostport(const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/valid_hostname.in b/src/util/valid_hostname.in
new file mode 100644
index 0000000..608c0d1
--- /dev/null
+++ b/src/util/valid_hostname.in
@@ -0,0 +1,55 @@
+123456789012345678901234567890123456789012345678901234567890123
+1234567890123456789012345678901234567890123456789012345678901234
+a.123456789012345678901234567890123456789012345678901234567890123.b
+a.1234567890123456789012345678901234567890123456789012345678901234.b
+1.2.3.4
+321.255.255.255
+0.0.0.0
+255.255.255.255
+0.255.255.255
+1.2.3.321
+1.2.3
+1.2.3.4.5
+1..2.3.4
+.1.2.3.4
+1.2.3.4.5.
+1
+.
+
+321
+f
+f.2.3.4
+1f.2.3.4
+f1.2.3.4
+1.2f.3.4
+1.f2.3.4
+1.2.3.4f
+1.2.3.f4
+1.2.3.f
+-.a.b
+a.-.b
+a.b.-
+-aa.b.b
+aa-.b.b
+a.-bb.b
+a.bb-.b
+a.b.-bb
+a.b.bb-
+a-a.b-b
+:
+::
+:::
+a::
+::a
+::1.2.3.4
+a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:1.2.3.
+a:a:a:a:a:a:1.2.3
+a:a:a:a:a:a:a:a
+a:a:a:a:a:a:a:a:a
+g:a:a:a:a:a:a:a
+a::b
+:a::b
+a::b:
diff --git a/src/util/valid_hostname.ref b/src/util/valid_hostname.ref
new file mode 100644
index 0000000..08b23b8
--- /dev/null
+++ b/src/util/valid_hostname.ref
@@ -0,0 +1,143 @@
+./valid_hostname: testing: "123456789012345678901234567890123456789012345678901234567890123"
+./valid_hostname: warning: valid_hostname: numeric hostname: 123456789012345678901234567890123456789012345678901234567890123
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 123456789012345678901234567890123456789012345678901234567890123
+./valid_hostname: testing: "1234567890123456789012345678901234567890123456789012345678901234"
+./valid_hostname: warning: valid_hostname: hostname label too long: 1234567890123456789012345678901234567890123456789012345678901234
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1234567890123456789012345678901234567890123456789012345678901234
+./valid_hostname: testing: "a.123456789012345678901234567890123456789012345678901234567890123.b"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.123456789012345678901234567890123456789012345678901234567890123.b
+./valid_hostname: testing: "a.1234567890123456789012345678901234567890123456789012345678901234.b"
+./valid_hostname: warning: valid_hostname: hostname label too long: a.1234567890123456789012345678901234567890123456789012345678901234.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.1234567890123456789012345678901234567890123456789012345678901234.b
+./valid_hostname: testing: "1.2.3.4"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4
+./valid_hostname: testing: "321.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 321.255.255.255
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321.255.255.255
+./valid_hostname: testing: "0.0.0.0"
+./valid_hostname: warning: valid_hostname: numeric hostname: 0.0.0.0
+./valid_hostname: testing: "255.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 255.255.255.255
+./valid_hostname: testing: "0.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 0.255.255.255
+./valid_hostname: warning: valid_ipv4_hostaddr: bad initial octet value: 0.255.255.255
+./valid_hostname: testing: "1.2.3.321"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.321
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1.2.3.321
+./valid_hostname: testing: "1.2.3"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3
+./valid_hostname: testing: "1.2.3.4.5"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4.5
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3.4.5
+./valid_hostname: testing: "1..2.3.4"
+./valid_hostname: warning: valid_hostname: misplaced delimiter: 1..2.3.4
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1..2.3.4
+./valid_hostname: testing: ".1.2.3.4"
+./valid_hostname: warning: valid_hostname: misplaced delimiter: .1.2.3.4
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .1.2.3.4
+./valid_hostname: testing: "1.2.3.4.5."
+./valid_hostname: warning: valid_hostname: misplaced delimiter: 1.2.3.4.5.
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.4.5.
+./valid_hostname: testing: "1"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1
+./valid_hostname: testing: "."
+./valid_hostname: warning: valid_hostname: misplaced delimiter: .
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .
+./valid_hostname: testing: ""
+./valid_hostname: warning: valid_hostname: empty hostname
+./valid_hostname: warning: valid_hostaddr: empty address
+./valid_hostname: testing: "321"
+./valid_hostname: warning: valid_hostname: numeric hostname: 321
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321
+./valid_hostname: testing: "f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f
+./valid_hostname: testing: "f.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f.2.3.4
+./valid_hostname: testing: "1f.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1f.2.3.4
+./valid_hostname: testing: "f1.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f1.2.3.4
+./valid_hostname: testing: "1.2f.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2f.3.4
+./valid_hostname: testing: "1.f2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.f2.3.4
+./valid_hostname: testing: "1.2.3.4f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.4f
+./valid_hostname: testing: "1.2.3.f4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f4
+./valid_hostname: testing: "1.2.3.f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f
+./valid_hostname: testing: "-.a.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: -.a.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -.a.b
+./valid_hostname: testing: "a.-.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-.b
+./valid_hostname: testing: "a.b.-"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-
+./valid_hostname: testing: "-aa.b.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: -aa.b.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -aa.b.b
+./valid_hostname: testing: "aa-.b.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: aa-.b.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): aa-.b.b
+./valid_hostname: testing: "a.-bb.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-bb.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-bb.b
+./valid_hostname: testing: "a.bb-.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.bb-.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.bb-.b
+./valid_hostname: testing: "a.b.-bb"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-bb
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-bb
+./valid_hostname: testing: "a.b.bb-"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.bb-
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.bb-
+./valid_hostname: testing: "a-a.b-b"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a-a.b-b
+./valid_hostname: testing: ":"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :
+./valid_hostname: warning: valid_ipv6_hostaddr: too few `:' in IPv6 address: :
+./valid_hostname: testing: "::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::
+./valid_hostname: testing: ":::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :::
+./valid_hostname: warning: valid_ipv6_hostaddr: too many `::' in IPv6 address: :::
+./valid_hostname: testing: "a::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::
+./valid_hostname: testing: "::a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::a
+./valid_hostname: testing: "::1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:1.2.3.4
+./valid_hostname: warning: valid_ipv6_hostaddr: malformed IPv4-in-IPv6 address: a:a:a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3."
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3
+./valid_hostname: testing: "a:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a
+./valid_hostname: testing: "a:a:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a:a
+./valid_hostname: warning: valid_ipv6_hostaddr: too many `:' in IPv6 address: a:a:a:a:a:a:a:a:a
+./valid_hostname: testing: "g:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): g:a:a:a:a:a:a:a
+./valid_hostname: warning: valid_ipv6_hostaddr: invalid character 103(decimal) in IPv6 address: g:a:a:a:a:a:a:a
+./valid_hostname: testing: "a::b"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b
+./valid_hostname: testing: ":a::b"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :a::b
+./valid_hostname: warning: valid_ipv6_hostaddr: bad null first field in IPv6 address: :a::b
+./valid_hostname: testing: "a::b:"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b:
+./valid_hostname: warning: valid_ipv6_hostaddr: bad null last field in IPv6 address: a::b:
diff --git a/src/util/valid_utf8_hostname.c b/src/util/valid_utf8_hostname.c
new file mode 100644
index 0000000..3d6922a
--- /dev/null
+++ b/src/util/valid_utf8_hostname.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* valid_utf8_hostname 3
+/* SUMMARY
+/* validate (maybe UTF-8) domain name
+/* SYNOPSIS
+/* #include <valid_utf8_hostname.h>
+/*
+/* int valid_utf8_hostname(
+/* int enable_utf8,
+/* const char *domain,
+/* int gripe)
+/* DESCRIPTION
+/* valid_utf8_hostname() is a wrapper around valid_hostname().
+/* If EAI support is compiled in, and enable_utf8 is true, the
+/* name is converted from UTF-8 to ASCII per IDNA rules, before
+/* invoking valid_hostname().
+/* SEE ALSO
+/* valid_hostname(3) STD3 hostname validation.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Warnings: malformed domain name.
+/* 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 <mymalloc.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <midna_domain.h>
+#include <valid_utf8_hostname.h>
+
+/* valid_utf8_hostname - validate internationalized domain name */
+
+int valid_utf8_hostname(int enable_utf8, const char *name, int gripe)
+{
+ static const char myname[] = "valid_utf8_hostname";
+
+ /*
+ * Trivial cases first.
+ */
+ if (*name == 0) {
+ if (gripe)
+ msg_warn("%s: empty domain name", myname);
+ return (0);
+ }
+
+ /*
+ * Convert non-ASCII domain name to ASCII and validate the result per
+ * STD3. midna_domain_to_ascii() applies valid_hostname() to the result.
+ * Propagate the gripe parameter for better diagnostics (note that
+ * midna_domain_to_ascii() logs a problem only when the result is not
+ * cached).
+ */
+#ifndef NO_EAI
+ if (enable_utf8 && !allascii(name)) {
+ if (midna_domain_to_ascii(name) == 0) {
+ if (gripe)
+ msg_warn("%s: malformed UTF-8 domain name", myname);
+ return (0);
+ } else {
+ return (1);
+ }
+ }
+#endif
+
+ /*
+ * Validate ASCII name per STD3.
+ */
+ return (valid_hostname(name, gripe));
+}
diff --git a/src/util/valid_utf8_hostname.h b/src/util/valid_utf8_hostname.h
new file mode 100644
index 0000000..0c0b41f
--- /dev/null
+++ b/src/util/valid_utf8_hostname.h
@@ -0,0 +1,35 @@
+#ifndef _VALID_UTF8_HOSTNAME_H_INCLUDED_
+#define _VALID_UTF8_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_utf8_hostname 3h
+/* SUMMARY
+/* validate (maybe UTF-8) domain name
+/* SYNOPSIS
+/* #include <valid_utf8_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <valid_hostname.h>
+
+ /*
+ * External interface
+ */
+extern int valid_utf8_hostname(int, const char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/valid_utf8_string.c b/src/util/valid_utf8_string.c
new file mode 100644
index 0000000..96b5b4d
--- /dev/null
+++ b/src/util/valid_utf8_string.c
@@ -0,0 +1,139 @@
+/*++
+/* NAME
+/* valid_utf8_string 3
+/* SUMMARY
+/* predicate if string is valid UTF-8
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int valid_utf8_string(str, len)
+/* const char *str;
+/* ssize_t len;
+/* DESCRIPTION
+/* valid_utf8_string() determines if a string satisfies the UTF-8
+/* definition in RFC 3629. That is, it contains proper encodings
+/* of code points U+0000..U+10FFFF, excluding over-long encodings
+/* and excluding U+D800..U+DFFF surrogates.
+/*
+/* A zero-length string is considered valid.
+/* DIAGNOSTICS
+/* The result value is zero when the caller specifies a negative
+/* length, or a string that violates RFC 3629, for example a
+/* string that is truncated in the middle of a multi-byte
+/* sequence.
+/* BUGS
+/* But wait, there is more. Code points in the range U+FDD0..U+FDEF
+/* and ending in FFFE or FFFF are non-characters in UNICODE. This
+/* function does not block these.
+/* SEE ALSO
+/* RFC 3629
+/* 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 <stringops.h>
+
+/* valid_utf8_string - validate string according to RFC 3629 */
+
+int valid_utf8_string(const char *str, ssize_t len)
+{
+ const unsigned char *end = (const unsigned char *) str + len;
+ const unsigned char *cp;
+ unsigned char c0, ch;
+
+ if (len < 0)
+ return (0);
+ if (len <= 0)
+ return (1);
+
+ /*
+ * Optimized for correct input, time, space, and for CPUs that have a
+ * decent number of registers.
+ */
+ for (cp = (const unsigned char *) str; cp < end; cp++) {
+ /* Single-byte encodings. */
+ if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) {
+ /* void */ ;
+ }
+ /* Two-byte encodings. */
+ else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) {
+ /* Exclude over-long encodings. */
+ if (UNEXPECTED(c0 < 0xc2)
+ || UNEXPECTED(cp + 1 >= end)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Three-byte encodings. */
+ else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) {
+ if (UNEXPECTED(cp + 2 >= end)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80))
+ /* Exclude U+D800..U+DFFF. */
+ || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Four-byte encodings. */
+ else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) {
+ if (UNEXPECTED(cp + 3 >= end)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80))
+ /* Exclude code points above U+10FFFF. */
+ || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Invalid: c0 >= 0xf5 */
+ else {
+ return (0);
+ }
+ }
+ return (1);
+}
+
+ /*
+ * Stand-alone test program. Each string is a line without line terminator.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(1);
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ?
+ '!' : ' ');
+ vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf));
+ vstream_printf("\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/vbuf.c b/src/util/vbuf.c
new file mode 100644
index 0000000..924e230
--- /dev/null
+++ b/src/util/vbuf.c
@@ -0,0 +1,238 @@
+/*++
+/* NAME
+/* vbuf 3
+/* SUMMARY
+/* generic buffer package
+/* SYNOPSIS
+/* #include <vbuf.h>
+/*
+/* int VBUF_GET(bp)
+/* VBUF *bp;
+/*
+/* int VBUF_PUT(bp, ch)
+/* VBUF *bp;
+/* int ch;
+/*
+/* int VBUF_SPACE(bp, len)
+/* VBUF *bp;
+/* ssize_t len;
+/*
+/* int vbuf_unget(bp, ch)
+/* VBUF *bp;
+/* int ch;
+/*
+/* ssize_t vbuf_read(bp, buf, len)
+/* VBUF *bp;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vbuf_write(bp, buf, len)
+/* VBUF *bp;
+/* const void *buf;
+/* ssize_t len;
+/*
+/* int vbuf_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_eof(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_timeout(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_clearerr(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_rd_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_wr_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_rd_timeout(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_wr_timeout(bp)
+/* VBUF *bp;
+/* DESCRIPTION
+/* This module implements a buffer with read/write primitives that
+/* automatically handle buffer-empty or buffer-full conditions.
+/* The application is expected to provide callback routines that run
+/* when the read-write primitives detect a buffer-empty/full condition.
+/*
+/* VBUF buffers provide primitives to store and retrieve characters,
+/* and to look up buffer status information.
+/* By design, VBUF buffers provide no explicit primitives for buffer
+/* memory management. This is left to the application to avoid any bias
+/* toward specific management models. The application is free to use
+/* whatever strategy suits best: memory-resident buffer, memory mapped
+/* file, or stdio-like window to an open file.
+/*
+/* VBUF_GET() returns the next character from the specified buffer,
+/* or VBUF_EOF when none is available. VBUF_GET() is an unsafe macro
+/* that evaluates its argument more than once.
+/*
+/* VBUF_PUT() stores one character into the specified buffer. The result
+/* is the stored character, or VBUF_EOF in case of problems. VBUF_PUT()
+/* is an unsafe macro that evaluates its arguments more than once.
+/*
+/* VBUF_SPACE() requests that the requested amount of buffer space be
+/* made available, so that it can be accessed without using VBUF_PUT().
+/* The result value is 0 for success, VBUF_EOF for problems.
+/* VBUF_SPACE() is an unsafe macro that evaluates its arguments more
+/* than once. VBUF_SPACE() does not support read-only streams.
+/*
+/* vbuf_unget() provides at least one character of pushback, and returns
+/* the pushed back character, or VBUF_EOF in case of problems. It is
+/* an error to call vbuf_unget() on a buffer before reading any data
+/* from it. vbuf_unget() clears the buffer's end-of-file indicator upon
+/* success, and sets the buffer's error indicator when an attempt is
+/* made to push back a non-character value.
+/*
+/* vbuf_read() and vbuf_write() do bulk I/O. The result value is the
+/* number of bytes transferred. A short count is returned in case of
+/* an error.
+/*
+/* vbuf_timeout() is a macro that returns non-zero if a timeout error
+/* condition was detected while reading or writing the buffer. The
+/* error status can be reset by calling vbuf_clearerr().
+/*
+/* vbuf_err() is a macro that returns non-zero if a non-EOF error
+/* (including timeout) condition was detected while reading or writing
+/* the buffer. The error status can be reset by calling vbuf_clearerr().
+/*
+/* The vbuf_rd_mumble() and vbuf_wr_mumble() macros report on
+/* read and write error conditions, respectively.
+/*
+/* vbuf_eof() is a macro that returns non-zero if an end-of-file
+/* condition was detected while reading or writing the buffer. The error
+/* status can be reset by calling vbuf_clearerr().
+/* APPLICATION CALLBACK SYNOPSIS
+/* int get_ready(bp)
+/* VBUF *bp;
+/*
+/* int put_ready(bp)
+/* VBUF *bp;
+/*
+/* int space(bp, len)
+/* VBUF *bp;
+/* ssize_t len;
+/* APPLICATION CALLBACK DESCRIPTION
+/* .ad
+/* .fi
+/* get_ready() is called when VBUF_GET() detects a buffer-empty condition.
+/* The result is zero when more data could be read, VBUF_EOF otherwise.
+/*
+/* put_ready() is called when VBUF_PUT() detects a buffer-full condition.
+/* The result is zero when the buffer could be flushed, VBUF_EOF otherwise.
+/*
+/* space() performs whatever magic necessary to make at least \fIlen\fR
+/* bytes available for access without using VBUF_PUT(). The result is 0
+/* in case of success, VBUF_EOF otherwise.
+/* SEE ALSO
+/* vbuf(3h) layout of the VBUF data structure.
+/* 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 "vbuf.h"
+
+/* vbuf_unget - implement at least one character pushback */
+
+int vbuf_unget(VBUF *bp, int ch)
+{
+ if ((ch & 0xff) != ch || -bp->cnt >= bp->len) {
+ bp->flags |= VBUF_FLAG_RD_ERR; /* This error affects reads! */
+ return (VBUF_EOF);
+ } else {
+ bp->cnt--;
+ bp->flags &= ~VBUF_FLAG_EOF;
+ return (*--bp->ptr = ch);
+ }
+}
+
+/* vbuf_get - handle read buffer empty condition */
+
+int vbuf_get(VBUF *bp)
+{
+ return (bp->get_ready(bp) ?
+ ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp));
+}
+
+/* vbuf_put - handle write buffer full condition */
+
+int vbuf_put(VBUF *bp, int ch)
+{
+ return (bp->put_ready(bp) ? VBUF_EOF : VBUF_PUT(bp, ch));
+}
+
+/* vbuf_read - bulk read from buffer */
+
+ssize_t vbuf_read(VBUF *bp, void *buf, ssize_t len)
+{
+ ssize_t count;
+ void *cp;
+ ssize_t n;
+
+#if 0
+ for (count = 0; count < len; count++)
+ if ((buf[count] = VBUF_GET(bp)) < 0)
+ break;
+ return (count);
+#else
+ for (cp = buf, count = len; count > 0; cp += n, count -= n) {
+ if (bp->cnt >= 0 && bp->get_ready(bp))
+ break;
+ n = (count < -bp->cnt ? count : -bp->cnt);
+ memcpy(cp, bp->ptr, n);
+ bp->ptr += n;
+ bp->cnt += n;
+ }
+ return (len - count);
+#endif
+}
+
+/* vbuf_write - bulk write to buffer */
+
+ssize_t vbuf_write(VBUF *bp, const void *buf, ssize_t len)
+{
+ ssize_t count;
+ const void *cp;
+ ssize_t n;
+
+#if 0
+ for (count = 0; count < len; count++)
+ if (VBUF_PUT(bp, buf[count]) < 0)
+ break;
+ return (count);
+#else
+ for (cp = buf, count = len; count > 0; cp += n, count -= n) {
+ if (bp->cnt <= 0 && bp->put_ready(bp) != 0)
+ break;
+ n = (count < bp->cnt ? count : bp->cnt);
+ memcpy(bp->ptr, cp, n);
+ bp->ptr += n;
+ bp->cnt -= n;
+ }
+ return (len - count);
+#endif
+}
diff --git a/src/util/vbuf.h b/src/util/vbuf.h
new file mode 100644
index 0000000..149fe0c
--- /dev/null
+++ b/src/util/vbuf.h
@@ -0,0 +1,110 @@
+#ifndef _VBUF_H_INCLUDED_
+#define _VBUF_H_INCLUDED_
+
+/*++
+/* NAME
+/* vbuf 3h
+/* SUMMARY
+/* generic buffer
+/* SYNOPSIS
+/* #include <vbuf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * The VBUF buffer is defined by 1) its structure, by 2) the VBUF_GET() and
+ * 3) VBUF_PUT() operations that automatically handle buffer empty and
+ * buffer full conditions, and 4) by the VBUF_SPACE() operation that allows
+ * the user to reserve buffer space ahead of time, to allow for situations
+ * where calling VBUF_PUT() is not possible or desirable.
+ *
+ * The VBUF buffer does not specify primitives for memory allocation or
+ * deallocation. The purpose is to allow different applications to have
+ * different strategies: a memory-resident buffer; a memory-mapped file; or
+ * a stdio-like window to an open file. Each application provides its own
+ * get(), put() and space() methods that perform the necessary magic.
+ *
+ * This interface is pretty normal. With one exception: the number of bytes
+ * left to read is negated. This is done so that we can change direction
+ * between reading and writing on the fly. The alternative would be to use
+ * separate read and write counters per buffer.
+ */
+typedef struct VBUF VBUF;
+typedef int (*VBUF_GET_READY_FN) (VBUF *);
+typedef int (*VBUF_PUT_READY_FN) (VBUF *);
+typedef int (*VBUF_SPACE_FN) (VBUF *, ssize_t);
+
+struct VBUF {
+ int flags; /* status, see below */
+ unsigned char *data; /* variable-length buffer */
+ ssize_t len; /* buffer length */
+ ssize_t cnt; /* bytes left to read/write */
+ unsigned char *ptr; /* read/write position */
+ VBUF_GET_READY_FN get_ready; /* read buffer empty action */
+ VBUF_PUT_READY_FN put_ready; /* write buffer full action */
+ VBUF_SPACE_FN space; /* request for buffer space */
+};
+
+ /*
+ * Typically, an application will embed a VBUF structure into a larger
+ * structure that also contains application-specific members. This approach
+ * gives us the best of both worlds. The application can still use the
+ * generic VBUF primitives for reading and writing VBUFs. The macro below
+ * transforms a pointer from VBUF structure to the structure that contains
+ * it.
+ */
+#define VBUF_TO_APPL(vbuf_ptr,app_type,vbuf_member) \
+ ((app_type *) (((char *) (vbuf_ptr)) - offsetof(app_type,vbuf_member)))
+
+ /*
+ * Buffer status management.
+ */
+#define VBUF_FLAG_RD_ERR (1<<0) /* read error */
+#define VBUF_FLAG_WR_ERR (1<<1) /* write error */
+#define VBUF_FLAG_ERR (VBUF_FLAG_RD_ERR | VBUF_FLAG_WR_ERR)
+#define VBUF_FLAG_EOF (1<<2) /* end of data */
+#define VBUF_FLAG_RD_TIMEOUT (1<<3) /* read timeout */
+#define VBUF_FLAG_WR_TIMEOUT (1<<4) /* write timeout */
+#define VBUF_FLAG_TIMEOUT (VBUF_FLAG_RD_TIMEOUT | VBUF_FLAG_WR_TIMEOUT)
+#define VBUF_FLAG_BAD (VBUF_FLAG_ERR | VBUF_FLAG_EOF | VBUF_FLAG_TIMEOUT)
+#define VBUF_FLAG_FIXED (1<<5) /* fixed-size buffer */
+
+#define vbuf_rd_error(v) ((v)->flags & (VBUF_FLAG_RD_ERR | VBUF_FLAG_RD_TIMEOUT))
+#define vbuf_wr_error(v) ((v)->flags & (VBUF_FLAG_WR_ERR | VBUF_FLAG_WR_TIMEOUT))
+#define vbuf_rd_timeout(v) ((v)->flags & VBUF_FLAG_RD_TIMEOUT)
+#define vbuf_wr_timeout(v) ((v)->flags & VBUF_FLAG_WR_TIMEOUT)
+
+#define vbuf_error(v) ((v)->flags & (VBUF_FLAG_ERR | VBUF_FLAG_TIMEOUT))
+#define vbuf_eof(v) ((v)->flags & VBUF_FLAG_EOF)
+#define vbuf_timeout(v) ((v)->flags & VBUF_FLAG_TIMEOUT)
+#define vbuf_clearerr(v) ((v)->flags &= ~VBUF_FLAG_BAD)
+
+ /*
+ * Buffer I/O-like operations and results.
+ */
+#define VBUF_GET(v) ((v)->cnt < 0 ? ++(v)->cnt, \
+ (int) *(v)->ptr++ : vbuf_get(v))
+#define VBUF_PUT(v,c) ((v)->cnt > 0 ? --(v)->cnt, \
+ (int) (*(v)->ptr++ = (c)) : vbuf_put((v),(c)))
+#define VBUF_SPACE(v,n) ((v)->space((v),(n)))
+
+#define VBUF_EOF (-1) /* no more space or data */
+
+extern int vbuf_get(VBUF *);
+extern int vbuf_put(VBUF *, int);
+extern int vbuf_unget(VBUF *, int);
+extern ssize_t vbuf_read(VBUF *, void *, ssize_t);
+extern ssize_t vbuf_write(VBUF *, const void *, ssize_t);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/vbuf_print.c b/src/util/vbuf_print.c
new file mode 100644
index 0000000..d7a323f
--- /dev/null
+++ b/src/util/vbuf_print.c
@@ -0,0 +1,385 @@
+/*++
+/* NAME
+/* vbuf_print 3
+/* SUMMARY
+/* formatted print to generic buffer
+/* SYNOPSIS
+/* #include <stdarg.h>
+/* #include <vbuf_print.h>
+/*
+/* VBUF *vbuf_print(bp, format, ap)
+/* VBUF *bp;
+/* const char *format;
+/* va_list ap;
+/* DESCRIPTION
+/* vbuf_print() appends data to the named buffer according to its
+/* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e,
+/* f and g format types, the l modifier, field width and precision,
+/* sign, and padding with zeros or spaces.
+/*
+/* In addition, vbuf_print() recognizes the %m format specifier
+/* and expands it to the error message corresponding to the current
+/* value of the global \fIerrno\fR variable.
+/* REENTRANCY
+/* .ad
+/* .fi
+/* vbuf_print() allocates a static buffer. After completion
+/* of the first vbuf_print() call, this buffer is safe for
+/* reentrant vbuf_print() calls by (asynchronous) terminating
+/* signal handlers or by (synchronous) terminating error
+/* handlers. vbuf_print() initialization typically happens
+/* upon the first formatted output to a VSTRING or VSTREAM.
+/*
+/* However, it is up to the caller to ensure that the destination
+/* VSTREAM or VSTRING buffer is protected against reentrant usage.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h> /* 44bsd stdarg.h uses abort() */
+#include <stdio.h> /* sprintf() prototype */
+#include <float.h> /* range of doubles */
+#include <errno.h>
+#include <limits.h> /* CHAR_BIT, INT_MAX */
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vbuf.h"
+#include "vstring.h"
+#include "vbuf_print.h"
+
+ /*
+ * What we need here is a *sprintf() routine that can ask for more room (as
+ * in 4.4 BSD). However, that functionality is not widely available, and I
+ * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
+ *
+ * Postfix vbuf_print() was implemented when many mainstream systems had no
+ * usable snprintf() implementation (usable means: return the length,
+ * excluding terminator, that the output would have if the buffer were large
+ * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
+ * distinguish between formatting error and buffer size error, while SUN had
+ * no snprintf() implementation before Solaris 2.6 (1997).
+ *
+ * For the above reasons, vbuf_print() was implemented with sprintf() and a
+ * generously-sized output buffer. Current vbuf_print() implementations use
+ * snprintf(), and report an error if the output does not fit (in that case,
+ * the old sprintf()-based implementation would have had a buffer overflow
+ * vulnerability). The old implementation is still available for building
+ * Postfix on ancient systems.
+ *
+ * Guessing the output size of a string (%s) conversion is not hard. The
+ * problem is with numerical results. Instead of making an accurate guess we
+ * take a wide margin when reserving space. The INT_SPACE margin should be
+ * large enough to hold the result from any (octal, hex, decimal) integer
+ * conversion that has no explicit width or precision specifiers. With
+ * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
+ * just to be sure.
+ */
+#define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2)
+#define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
+#define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2)
+
+ /*
+ * Helper macros... Note that there is no need to check the result from
+ * VSTRING_SPACE() because that always succeeds or never returns.
+ */
+#ifndef NO_SNPRINTF
+#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+ ssize_t _ret; \
+ if (VBUF_SPACE((bp), (sz)) != 0) \
+ return (bp); \
+ _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
+ if (_ret < 0) \
+ msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
+ if (_ret >= (bp)->cnt) \
+ msg_panic("%s: output for '%s' exceeds space %ld", \
+ myname, mystrdup(fmt), (long) (bp)->cnt); \
+ VBUF_SKIP(bp); \
+ } while (0)
+#else
+#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+ if (VBUF_SPACE((bp), (sz)) != 0) \
+ return (bp); \
+ sprintf((char *) (bp)->ptr, (fmt), (arg)); \
+ VBUF_SKIP(bp); \
+ } while (0)
+#endif
+
+#define VBUF_SKIP(bp) do { \
+ while ((bp)->cnt > 0 && *(bp)->ptr) \
+ (bp)->ptr++, (bp)->cnt--; \
+ } while (0)
+
+#define VSTRING_ADDNUM(vp, n) do { \
+ VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
+ } while (0)
+
+#define VBUF_STRCAT(bp, s) do { \
+ unsigned char *_cp = (unsigned char *) (s); \
+ int _ch; \
+ while ((_ch = *_cp++) != 0) \
+ VBUF_PUT((bp), _ch); \
+ } while (0)
+
+/* vbuf_print - format string, vsprintf-like interface */
+
+VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap)
+{
+ const char *myname = "vbuf_print";
+ static VSTRING *fmt; /* format specifier */
+ unsigned char *cp;
+ int width; /* width and numerical precision */
+ int prec; /* are signed for overflow defense */
+ unsigned long_flag; /* long or plain integer */
+ int ch;
+ char *s;
+ int saved_errno = errno; /* VBUF_SPACE() may clobber it */
+
+ /*
+ * Assume that format strings are short.
+ */
+ if (fmt == 0)
+ fmt = vstring_alloc(INT_SPACE);
+
+ /*
+ * Iterate over characters in the format string, picking up arguments
+ * when format specifiers are found.
+ */
+ for (cp = (unsigned char *) format; *cp; cp++) {
+ if (*cp != '%') {
+ VBUF_PUT(bp, *cp); /* ordinary character */
+ } else if (cp[1] == '%') {
+ VBUF_PUT(bp, *cp++); /* %% becomes % */
+ } else {
+
+ /*
+ * Handle format specifiers one at a time, since we can only deal
+ * with arguments one at a time. Try to determine the end of the
+ * format specifier. We do not attempt to fully parse format
+ * strings, since we are ging to let sprintf() do the hard work.
+ * In regular expression notation, we recognize:
+ *
+ * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
+ *
+ * which includes some combinations that do not make sense. Garbage
+ * in, garbage out.
+ */
+ VSTRING_RESET(fmt); /* clear format string */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '-') /* left-adjusted field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '+') /* signed field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '0') /* zero-padded field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '*') { /* dynamic field width */
+ width = va_arg(ap, int);
+ if (width < 0) {
+ msg_warn("%s: bad width %d in %.50s",
+ myname, width, format);
+ width = 0;
+ } else
+ VSTRING_ADDNUM(fmt, width);
+ cp++;
+ } else { /* hard-coded field width */
+ for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
+ int digit = ch - '0';
+
+ if (width > INT_MAX / 10
+ || (width *= 10) > INT_MAX - digit)
+ msg_panic("%s: bad width %d... in %.50s",
+ myname, width, format);
+ width += digit;
+ VSTRING_ADDCH(fmt, ch);
+ }
+ }
+ if (*cp == '.') { /* width/precision separator */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '*') { /* dynamic precision */
+ prec = va_arg(ap, int);
+ if (prec < 0) {
+ msg_warn("%s: bad precision %d in %.50s",
+ myname, prec, format);
+ prec = -1;
+ } else
+ VSTRING_ADDNUM(fmt, prec);
+ cp++;
+ } else { /* hard-coded precision */
+ for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
+ int digit = ch - '0';
+
+ if (prec > INT_MAX / 10
+ || (prec *= 10) > INT_MAX - digit)
+ msg_panic("%s: bad precision %d... in %.50s",
+ myname, prec, format);
+ prec += digit;
+ VSTRING_ADDCH(fmt, ch);
+ }
+ }
+ } else {
+ prec = -1;
+ }
+ if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == 0) /* premature end, punt */
+ break;
+ VSTRING_ADDCH(fmt, *cp); /* type (checked below) */
+ VSTRING_TERMINATE(fmt); /* null terminate */
+
+ /*
+ * Execute the format string - let sprintf() do the hard work for
+ * non-trivial cases only. For simple string conversions and for
+ * long string conversions, do a direct copy to the output
+ * buffer.
+ */
+ switch (*cp) {
+ case 's': /* string-valued argument */
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ s = va_arg(ap, char *);
+ if (prec >= 0 || (width > 0 && width > strlen(s))) {
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), s);
+ } else {
+ VBUF_STRCAT(bp, s);
+ }
+ break;
+ case 'c': /* integral-valued argument */
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ /* FALLTHROUGH */
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ if (long_flag)
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), va_arg(ap, long));
+ else
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), va_arg(ap, int));
+ break;
+ case 'e': /* float-valued argument */
+ case 'f':
+ case 'g':
+ /* C99 *printf ignore the 'l' modifier. */
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
+ vstring_str(fmt), va_arg(ap, double));
+ break;
+ case 'm':
+ /* Ignore the 'l' modifier, width and precision. */
+ VBUF_STRCAT(bp, saved_errno ?
+ strerror(saved_errno) : "Application error");
+ break;
+ case 'p':
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
+ vstring_str(fmt), va_arg(ap, char *));
+ break;
+ default: /* anything else is bad */
+ msg_panic("vbuf_print: unknown format type: %c", *cp);
+ /* NOTREACHED */
+ break;
+ }
+ }
+ }
+ return (bp);
+}
+
+#ifdef TEST
+#include <argv.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *ibuf = vstring_alloc(100);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) {
+ ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE);
+ char *cp;
+
+ if (args->argc == 0 || *(cp = args->argv[0]) == '#') {
+ /* void */ ;
+ } else if (args->argc != 2 || *cp != '%') {
+ msg_warn("usage: format number");
+ } else {
+ char *fmt = cp++;
+ int lflag;
+
+ /* Determine the vstring_sprintf() argument type. */
+ cp += strspn(cp, "+-*0123456789.");
+ if ((lflag = (*cp == 'l')) != 0)
+ cp++;
+ if (cp[1] != 0) {
+ msg_warn("bad format: \"%s\"", fmt);
+ } else {
+ VSTRING *obuf = vstring_alloc(1);
+ char *val = args->argv[1];
+
+ /* Test the worst-case memory allocation. */
+#ifdef CA_VSTRING_CTL_EXACT
+ vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END);
+#endif
+ switch (*cp) {
+ case 'c':
+ case 'd':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ if (lflag)
+ vstring_sprintf(obuf, fmt, atol(val));
+ else
+ vstring_sprintf(obuf, fmt, atoi(val));
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ case 's':
+ vstring_sprintf(obuf, fmt, val);
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ case 'f':
+ case 'g':
+ vstring_sprintf(obuf, fmt, atof(val));
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ default:
+ msg_warn("bad format: \"%s\"", fmt);
+ break;
+ }
+ vstring_free(obuf);
+ }
+ }
+ argv_free(args);
+ }
+ vstring_free(ibuf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vbuf_print.h b/src/util/vbuf_print.h
new file mode 100644
index 0000000..32549c1
--- /dev/null
+++ b/src/util/vbuf_print.h
@@ -0,0 +1,40 @@
+#ifndef _VBUF_PRINT_H_INCLUDED_
+#define _VBUF_PRINT_H_INCLUDED_
+
+/*++
+/* NAME
+/* vbuf_print 3h
+/* SUMMARY
+/* formatted print to generic buffer
+/* SYNOPSIS
+/* #include <vbuf_print.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+
+ /*
+ * External interface.
+ */
+extern VBUF *vbuf_print(VBUF *, const char *, va_list);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/vbuf_print_test.in b/src/util/vbuf_print_test.in
new file mode 100644
index 0000000..5ed13dc
--- /dev/null
+++ b/src/util/vbuf_print_test.in
@@ -0,0 +1,33 @@
+# Check width and precision.
+%-30.20s 123456789012345678901234567890
+%30.20s 123456789012345678901234567890
+
+%d 123456789
+%+10d 123456789
+%-10d 123456789
+%10d 123456789
+%10.10d 123456789
+
+%+ld 123456789
+%-ld 123456789
+%ld 123456789
+%10ld 123456789
+%10.10ld 123456789
+
+%+lo 123456789
+%-lo 123456789
+%lo 123456789
+%10lo 123456789
+%10.10lo 123456789
+
+%f 1e308
+%.100f 1e308
+%g 1e308
+%.309g 1e308
+
+%s foo
+%0s foo
+%.0s foo
+%10s foo
+%+10s foo
+%-10s foo
diff --git a/src/util/vbuf_print_test.ref b/src/util/vbuf_print_test.ref
new file mode 100644
index 0000000..346c919
--- /dev/null
+++ b/src/util/vbuf_print_test.ref
@@ -0,0 +1,27 @@
+./vbuf_print: "12345678901234567890 "
+./vbuf_print: " 12345678901234567890"
+./vbuf_print: "123456789"
+./vbuf_print: "+123456789"
+./vbuf_print: "123456789 "
+./vbuf_print: " 123456789"
+./vbuf_print: "0123456789"
+./vbuf_print: "+123456789"
+./vbuf_print: "123456789"
+./vbuf_print: "123456789"
+./vbuf_print: " 123456789"
+./vbuf_print: "0123456789"
+./vbuf_print: "726746425"
+./vbuf_print: "726746425"
+./vbuf_print: "726746425"
+./vbuf_print: " 726746425"
+./vbuf_print: "0726746425"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.000000"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+./vbuf_print: "1e+308"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336"
+./vbuf_print: "foo"
+./vbuf_print: "foo"
+./vbuf_print: ""
+./vbuf_print: " foo"
+./vbuf_print: " foo"
+./vbuf_print: "foo "
diff --git a/src/util/vstream.c b/src/util/vstream.c
new file mode 100644
index 0000000..b4f9fbb
--- /dev/null
+++ b/src/util/vstream.c
@@ -0,0 +1,2046 @@
+/*++
+/* NAME
+/* vstream 3
+/* SUMMARY
+/* light-weight buffered I/O package
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_fopen(path, flags, mode)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/*
+/* VSTREAM *vstream_fdopen(fd, flags)
+/* int fd;
+/* int flags;
+/*
+/* VSTREAM *vstream_memopen(string, flags)
+/* VSTRING *string;
+/* int flags;
+/*
+/* VSTREAM *vstream_memreopen(stream, string, flags)
+/* VSTREAM *stream;
+/* VSTRING *string;
+/* int flags;
+/*
+/* int vstream_fclose(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fdclose(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_printf(format, ...)
+/* const char *format;
+/*
+/* VSTREAM *vstream_fprintf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* int VSTREAM_GETC(stream)
+/* VSTREAM *stream;
+/*
+/* int VSTREAM_PUTC(ch, stream)
+/* int ch;
+/*
+/* int VSTREAM_GETCHAR(void)
+/*
+/* int VSTREAM_PUTCHAR(ch)
+/* int ch;
+/*
+/* int vstream_ungetc(stream, ch)
+/* VSTREAM *stream;
+/* int ch;
+/*
+/* int vstream_fputs(str, stream)
+/* const char *str;
+/* VSTREAM *stream;
+/*
+/* off_t vstream_ftell(stream)
+/* VSTREAM *stream;
+/*
+/* off_t vstream_fseek(stream, offset, whence)
+/* VSTREAM *stream;
+/* off_t offset;
+/* int whence;
+/*
+/* int vstream_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fpurge(stream, direction)
+/* VSTREAM *stream;
+/* int direction;
+/*
+/* ssize_t vstream_fread(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fwrite(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_app(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_buf(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* void vstream_control(stream, name, ...)
+/* VSTREAM *stream;
+/* int name;
+/*
+/* int vstream_fileno(stream)
+/* VSTREAM *stream;
+/*
+/* const ssize_t vstream_req_bufsize(stream)
+/* VSTREAM *stream;
+/*
+/* void *vstream_context(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ferror(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ftimeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_feof(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_clearerr(stream)
+/* VSTREAM *stream;
+/*
+/* const char *VSTREAM_PATH(stream)
+/* VSTREAM *stream;
+/*
+/* char *vstream_vprintf(format, ap)
+/* const char *format;
+/* va_list *ap;
+/*
+/* char *vstream_vfprintf(stream, format, ap)
+/* VSTREAM *stream;
+/* const char *format;
+/* va_list *ap;
+/*
+/* ssize_t vstream_bufstat(stream, command)
+/* VSTREAM *stream;
+/* int command;
+/*
+/* ssize_t vstream_peek(stream)
+/* VSTREAM *stream;
+/*
+/* const char *vstream_peek_data(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_setjmp(stream)
+/* VSTREAM *stream;
+/*
+/* void vstream_longjmp(stream, val)
+/* VSTREAM *stream;
+/* int val;
+/*
+/* time_t vstream_ftime(stream)
+/* VSTREAM *stream;
+/*
+/* struct timeval vstream_ftimeval(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fstat(stream, flags)
+/* VSTREAM *stream;
+/* int flags;
+/* DESCRIPTION
+/* The \fIvstream\fR module implements light-weight buffered I/O
+/* similar to the standard I/O routines.
+/*
+/* The interface is implemented in terms of VSTREAM structure
+/* pointers, also called streams. For convenience, three streams
+/* are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These
+/* streams are connected to the standard input, output and error
+/* file descriptors, respectively.
+/*
+/* Although the interface is patterned after the standard I/O
+/* library, there are some major differences:
+/* .IP \(bu
+/* File descriptors are not limited to the range 0..255. This
+/* was reason #1 to write these routines in the first place.
+/* .IP \(bu
+/* The application can switch between reading and writing on
+/* the same stream without having to perform a flush or seek
+/* operation, and can change write position without having to
+/* flush. This was reason #2. Upon position or direction change,
+/* unread input is discarded, and unwritten output is flushed
+/* automatically. Exception: with double-buffered streams, unread
+/* input is not discarded upon change of I/O direction, and
+/* output flushing is delayed until the read buffer must be refilled.
+/* .IP \(bu
+/* A bidirectional stream can read and write with the same buffer
+/* and file descriptor, or it can have separate read/write
+/* buffers and/or file descriptors.
+/* .IP \(bu
+/* No automatic flushing of VSTREAM_OUT upon program exit, or of
+/* VSTREAM_ERR at any time. No unbuffered or line buffered modes.
+/* This functionality may be added when it is really needed.
+/* .PP
+/* vstream_fopen() opens the named file and associates a buffered
+/* stream with it. The \fIpath\fR, \fIflags\fR and \fImode\fR
+/* arguments are passed on to the open(2) routine. The result is
+/* a null pointer in case of problems. The \fIpath\fR argument is
+/* copied and can be looked up with VSTREAM_PATH().
+/*
+/* vstream_fdopen() takes an open file and associates a buffered
+/* stream with it. The \fIflags\fR argument specifies how the file
+/* was opened. vstream_fdopen() either succeeds or never returns.
+/*
+/* vstream_memopen() opens a VSTRING as a stream. The \fIflags\fR
+/* argument must specify one of O_RDONLY, O_WRONLY, or O_APPEND.
+/* vstream_memopen() either succeeds or never returns. Streams
+/* opened with vstream_memopen() have limitations: they can't
+/* be opened in read/write mode, they can't seek beyond the
+/* end of the VSTRING, and they don't support vstream_control()
+/* methods that manipulate buffers, file descriptors, or I/O
+/* functions. After a VSTRING is opened for writing, its content
+/* will be in an indeterminate state while the stream is open,
+/* and will be null-terminated when the stream is closed.
+/*
+/* vstream_memreopen() reopens a memory stream. When the
+/* \fIstream\fR argument is a null pointer, the behavior is that
+/* of vstream_memopen().
+/*
+/* vstream_fclose() closes the named buffered stream. The result
+/* is 0 in case of success, VSTREAM_EOF in case of problems.
+/* vstream_fclose() reports the same errors as vstream_ferror().
+/*
+/* vstream_fdclose() leaves the file(s) open but is otherwise
+/* identical to vstream_fclose().
+/*
+/* vstream_fprintf() formats its arguments according to the
+/* \fIformat\fR argument and writes the result to the named stream.
+/* The result is the stream argument. It understands the s, c, d, u,
+/* o, x, X, e, f and g format types, the l modifier, field width and
+/* precision, sign, and padding with zeros or spaces. In addition,
+/* vstream_fprintf() recognizes the %m format specifier and expands
+/* it to the error message corresponding to the current value of the
+/* global \fIerrno\fR variable.
+/*
+/* vstream_printf() performs formatted output to the standard output
+/* stream.
+/*
+/* VSTREAM_GETC() reads the next character from the named stream.
+/* The result is VSTREAM_EOF when end-of-file is reached or if a read
+/* error was detected. VSTREAM_GETC() is an unsafe macro that
+/* evaluates some arguments more than once.
+/*
+/* VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN).
+/*
+/* VSTREAM_PUTC() appends the specified character to the specified
+/* stream. The result is the stored character, or VSTREAM_EOF in
+/* case of problems. VSTREAM_PUTC() is an unsafe macro that
+/* evaluates some arguments more than once.
+/*
+/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT).
+/*
+/* vstream_ungetc() pushes back a character onto the specified stream
+/* and returns the character, or VSTREAM_EOF in case of problems.
+/* It is an error to push back before reading (or immediately after
+/* changing the stream offset via vstream_fseek()). Upon successful
+/* return, vstream_ungetc() clears the end-of-file stream flag.
+/*
+/* vstream_fputs() appends the given null-terminated string to the
+/* specified buffered stream. The result is 0 in case of success,
+/* VSTREAM_EOF in case of problems.
+/*
+/* vstream_ftell() returns the file offset for the specified stream,
+/* -1 if the stream is connected to a non-seekable file.
+/*
+/* vstream_fseek() changes the file position for the next read or write
+/* operation. Unwritten output is flushed. With unidirectional streams,
+/* unread input is discarded. The \fIoffset\fR argument specifies the file
+/* position from the beginning of the file (\fIwhence\fR is SEEK_SET),
+/* from the current file position (\fIwhence\fR is SEEK_CUR), or from
+/* the file end (SEEK_END). The result value is the file offset
+/* from the beginning of the file, -1 in case of problems.
+/*
+/* vstream_fflush() flushes unwritten data to a file that was
+/* opened in read-write or write-only mode.
+/* vstream_fflush() returns 0 in case of success, VSTREAM_EOF in
+/* case of problems. It is an error to flush a read-only stream.
+/* vstream_fflush() reports the same errors as vstream_ferror().
+/*
+/* vstream_fpurge() discards the contents of the stream buffer.
+/* If direction is VSTREAM_PURGE_READ, it discards unread data,
+/* else if direction is VSTREAM_PURGE_WRITE, it discards unwritten
+/* data. In the case of a double-buffered stream, if direction is
+/* VSTREAM_PURGE_BOTH, it discards the content of both the read
+/* and write buffers. vstream_fpurge() returns 0 in case of success,
+/* VSTREAM_EOF in case of problems.
+/*
+/* vstream_fread() and vstream_fwrite() perform unformatted I/O
+/* on the named stream. The result value is the number of bytes
+/* transferred. A short count is returned in case of end-of-file
+/* or error conditions.
+/*
+/* vstream_fread_buf() resets the buffer write position,
+/* allocates space for the specified number of bytes in the
+/* buffer, reads the bytes from the specified VSTREAM, and
+/* adjusts the buffer write position. The buffer is NOT
+/* null-terminated. The result value is as with vstream_fread().
+/* NOTE: do not skip calling vstream_fread_buf() when len == 0.
+/* This function has side effects including resetting the buffer
+/* write position, and skipping the call would invalidate the
+/* buffer state.
+/*
+/* vstream_fread_app() is like vstream_fread_buf() but appends
+/* to existing buffer content, instead of writing over it.
+/*
+/* vstream_control() allows the user to fine tune the behavior of
+/* the specified stream. The arguments are a list of macros with
+/* zero or more arguments, terminated with CA_VSTREAM_CTL_END
+/* which has none. The following lists the names and the types
+/* of the corresponding value arguments.
+/* .IP "CA_VSTREAM_CTL_READ_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_read(3) function,
+/* for example, a read function that performs decryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* 0 upon EOF, and -1 upon error with errno set appropriately.
+/* .IP "CA_VSTREAM_CTL_WRITE_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_write(3) function,
+/* for example, a write function that performs encryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* and -1 upon error with errno set appropriately. Instead of -1 it may
+/* also return 0, e.g., upon remote party-initiated protocol shutdown.
+/* .IP "CA_VSTREAM_CTL_CONTEXT(void *)"
+/* The argument specifies application context that is passed on to
+/* the application-specified read/write routines. No copy is made.
+/* .IP "CA_VSTREAM_CTL_PATH(const char *)"
+/* Updates the stored pathname of the specified stream. The pathname
+/* is copied.
+/* .IP "CA_VSTREAM_CTL_DOUBLE (no arguments)"
+/* Use separate buffers for reading and for writing. This prevents
+/* unread input from being discarded upon change of I/O direction.
+/* .IP "CA_VSTREAM_CTL_READ_FD(int)"
+/* The argument specifies the file descriptor to be used for reading.
+/* This feature is limited to double-buffered streams, and makes the
+/* stream non-seekable.
+/* .IP "CA_VSTREAM_CTL_WRITE_FD(int)"
+/* The argument specifies the file descriptor to be used for writing.
+/* This feature is limited to double-buffered streams, and makes the
+/* stream non-seekable.
+/* .IP "CA_VSTREAM_CTL_SWAP_FD(VSTREAM *)"
+/* The argument specifies a VSTREAM pointer; the request swaps the
+/* file descriptor members of the two streams. This feature is limited
+/* to streams that are both double-buffered or both single-buffered.
+/* .IP "CA_VSTREAM_CTL_DUPFD(int)"
+/* The argument specifies a minimum file descriptor value. If
+/* the actual stream's file descriptors are below the minimum,
+/* reallocate the descriptors to the first free value greater
+/* than or equal to the minimum. The VSTREAM_CTL_DUPFD macro
+/* is defined only on systems with fcntl() F_DUPFD support.
+/* .IP "CA_VSTREAM_CTL_WAITPID_FN(int (*)(pid_t, WAIT_STATUS_T *, int))"
+/* A pointer to function that behaves like waitpid(). This information
+/* is used by the vstream_pclose() routine.
+/* .IP "CA_VSTREAM_CTL_TIMEOUT(int)"
+/* The deadline for a descriptor to become readable in case of a read
+/* request, or writable in case of a write request. Specify a value
+/* of 0 to disable deadlines.
+/* .IP "CA_VSTREAM_CTL_EXCEPT (no arguments)"
+/* Enable exception handling with vstream_setjmp() and vstream_longjmp().
+/* This involves allocation of additional memory that normally isn't
+/* used.
+/* .IP "CA_VSTREAM_CTL_BUFSIZE(ssize_t)"
+/* Specify a non-default buffer size for the next read(2) or
+/* write(2) operation, or zero to implement a no-op. Requests
+/* to reduce the buffer size are silently ignored (i.e. any
+/* positive value <= vstream_req_bufsize()). To get a buffer
+/* size smaller than VSTREAM_BUFSIZE, make the VSTREAM_CTL_BUFSIZE
+/* request before the first stream read or write operation
+/* (i.e., vstream_req_bufsize() returns zero). Requests to
+/* change a fixed-size buffer (i.e., VSTREAM_ERR) are not
+/* allowed.
+/*
+/* NOTE: the vstream_*printf() routines may silently expand a
+/* buffer, so that the result of some %letter specifiers can
+/* be written to contiguous memory.
+/* .IP CA_VSTREAM_CTL_START_DEADLINE (no arguments)
+/* Change the VSTREAM_CTL_TIMEOUT behavior, to a deadline for
+/* the total amount of time for all subsequent file descriptor
+/* read or write operations, and recharge the deadline timer.
+/* .IP CA_VSTREAM_CTL_STOP_DEADLINE (no arguments)
+/* Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e.
+/* a time limit for individual file descriptor read or write
+/* operations.
+/* .IP CA_VSTREAM_CTL_MIN_DATA_RATE (int)
+/* When the DEADLINE is enabled, the amount of data that must
+/* be transferred to add 1 second to the deadline. However,
+/* the deadline will never exceed the timeout specified with
+/* VSTREAM_CTL_TIMEOUT. A zero value requests no update to the
+/* deadline as data is transferred; that is appropriate for
+/* request/reply interactions.
+/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments)
+/* Transfer ownership of the VSTRING that was opened with
+/* vstream_memopen() etc. to the stream, so that the VSTRING
+/* is automatically destroyed when the stream is closed.
+/* .PP
+/* vstream_fileno() gives access to the file handle associated with
+/* a buffered stream. With streams that have separate read/write
+/* file descriptors, the result is the current descriptor.
+/*
+/* vstream_req_bufsize() returns the buffer size that will be
+/* used for the next read(2) or write(2) operation on the named
+/* stream. A zero result means that the next read(2) or write(2)
+/* operation will use the default buffer size (VSTREAM_BUFSIZE).
+/*
+/* vstream_context() returns the application context that is passed on to
+/* the application-specified read/write routines.
+/*
+/* VSTREAM_PATH() is an unsafe macro that returns the name stored
+/* with vstream_fopen() or with vstream_control(). The macro is
+/* unsafe because it evaluates some arguments more than once.
+/*
+/* vstream_feof() returns non-zero when a previous operation on the
+/* specified stream caused an end-of-file condition.
+/* Although further read requests after EOF may complete
+/* successfully, vstream_feof() will keep returning non-zero
+/* until vstream_clearerr() is called for that stream.
+/*
+/* vstream_ferror() returns non-zero when a previous operation on the
+/* specified stream caused a non-EOF error condition, including timeout.
+/* After a non-EOF error on a stream, no I/O request will
+/* complete until after vstream_clearerr() is called for that stream.
+/*
+/* vstream_ftimeout() returns non-zero when a previous operation on the
+/* specified stream caused a timeout error condition. See
+/* vstream_ferror() for error persistence details.
+/*
+/* vstream_clearerr() resets the timeout, error and end-of-file indication
+/* of the specified stream, and returns no useful result.
+/*
+/* vstream_vfprintf() provides an alternate interface
+/* for formatting an argument list according to a format string.
+/*
+/* vstream_vprintf() provides a similar alternative interface.
+/*
+/* vstream_bufstat() provides input and output buffer status
+/* information. The command is one of the following:
+/* .IP VSTREAM_BST_IN_PEND
+/* Return the number of characters that can be read without
+/* refilling the read buffer.
+/* .IP VSTREAM_BST_OUT_PEND
+/* Return the number of characters that are waiting in the
+/* write buffer.
+/* .PP
+/* vstream_peek() returns the number of characters that can be
+/* read from the named stream without refilling the read buffer.
+/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND).
+/*
+/* vstream_peek_data() returns a pointer to the unread bytes
+/* that exist according to vstream_peek(), or null if no unread
+/* bytes are available.
+/*
+/* vstream_setjmp() saves processing context and makes that context
+/* available for use with vstream_longjmp(). Normally, vstream_setjmp()
+/* returns zero. A non-zero result means that vstream_setjmp() returned
+/* through a vstream_longjmp() call; the result is the \fIval\fR argument
+/* given to vstream_longjmp().
+/*
+/* NB: non-local jumps such as vstream_longjmp() are not safe
+/* for jumping out of any routine that manipulates VSTREAM data.
+/* longjmp() like calls are best avoided in signal handlers.
+/*
+/* vstream_ftime() returns the time of initialization, the last buffer
+/* fill operation, or the last buffer flush operation for the specified
+/* stream. This information is maintained only when stream timeouts are
+/* enabled.
+/*
+/* vstream_ftimeval() is like vstream_ftime() but returns more
+/* detail.
+/*
+/* vstream_rd_mumble() and vstream_wr_mumble() report on
+/* read and write error conditions, respectively.
+/*
+/* vstream_fstat() queries stream status information about
+/* user-requested features. The \fIflags\fR argument is the
+/* bitwise OR of one or more of the following, and the result
+/* value is the bitwise OR of the features that are activated.
+/* .IP VSTREAM_FLAG_DEADLINE
+/* The deadline feature is activated.
+/* .IP VSTREAM_FLAG_DOUBLE
+/* The double-buffering feature is activated.
+/* .IP VSTREAM_FLAG_MEMORY
+/* The stream is connected to a VSTRING buffer.
+/* .IP VSTREAM_FLAG_OWN_VSTRING
+/* The stream 'owns' the VSTRING buffer, and is responsible
+/* for cleaning up when the stream is closed.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory.
+/* SEE ALSO
+/* timed_read(3) default read routine
+/* timed_write(3) default write routine
+/* vbuf_print(3) formatting engine
+/* setjmp(3) non-local jumps
+/* BUGS
+/* Should use mmap() on reasonable systems.
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#define VSTRING_INTERNAL
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "vbuf_print.h"
+#include "iostuff.h"
+#include "vstring.h"
+#include "vstream.h"
+
+/* Application-specific. */
+
+ /*
+ * Forward declarations.
+ */
+static int vstream_buf_get_ready(VBUF *);
+static int vstream_buf_put_ready(VBUF *);
+static int vstream_buf_space(VBUF *, ssize_t);
+
+ /*
+ * Initialization of the three pre-defined streams. Pre-allocate a static
+ * I/O buffer for the standard error stream, so that the error handler can
+ * produce a diagnostic even when memory allocation fails.
+ */
+static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE];
+
+VSTREAM vstream_fstd[] = {
+ {{
+ 0, /* flags */
+ 0, 0, 0, 0, /* buffer */
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDIN_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ 0, /* flags */
+ 0, 0, 0, 0, /* buffer */
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDOUT_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE,
+ vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf,
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDERR_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ VSTREAM_BUFSIZE,},
+};
+
+#define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR)
+
+ /*
+ * A bunch of macros to make some expressions more readable. XXX We're
+ * assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2.
+ */
+#define VSTREAM_ACC_MASK(f) ((f) & (O_APPEND | O_WRONLY | O_RDWR))
+
+#define VSTREAM_CAN_READ(f) (VSTREAM_ACC_MASK(f) == O_RDONLY \
+ || VSTREAM_ACC_MASK(f) == O_RDWR)
+#define VSTREAM_CAN_WRITE(f) (VSTREAM_ACC_MASK(f) & O_WRONLY \
+ || VSTREAM_ACC_MASK(f) & O_RDWR \
+ || VSTREAM_ACC_MASK(f) & O_APPEND)
+
+#define VSTREAM_BUF_COUNT(bp, n) \
+ ((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n))
+
+#define VSTREAM_BUF_AT_START(bp) { \
+ (bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \
+ (bp)->ptr = (bp)->data; \
+ }
+
+#define VSTREAM_BUF_AT_OFFSET(bp, offset) { \
+ (bp)->ptr = (bp)->data + (offset); \
+ (bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \
+ }
+
+#define VSTREAM_BUF_AT_END(bp) { \
+ (bp)->cnt = 0; \
+ (bp)->ptr = (bp)->data + (bp)->len; \
+ }
+
+#define VSTREAM_BUF_ZERO(bp) { \
+ (bp)->flags = 0; \
+ (bp)->data = (bp)->ptr = 0; \
+ (bp)->len = (bp)->cnt = 0; \
+ }
+
+#define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \
+ (bp)->get_ready = (get_action); \
+ (bp)->put_ready = (put_action); \
+ (bp)->space = (space_action); \
+ }
+
+#define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \
+ stream->buffer = stream->buf; \
+ stream->filedes = stream->fd; \
+ }
+
+#define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \
+ stream->buffer.flags = stream->buf.flags; \
+ stream->buf = stream->buffer; \
+ stream->fd = stream->filedes; \
+ } while(0)
+
+#define VSTREAM_FORK_STATE(stream, buffer, filedes) { \
+ stream->buffer = stream->buf; \
+ stream->filedes = stream->fd; \
+ stream->buffer.data = stream->buffer.ptr = 0; \
+ stream->buffer.len = stream->buffer.cnt = 0; \
+ stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \
+ };
+
+#define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE)
+#define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE)
+
+#define VSTREAM_FFLUSH_SOME(stream) \
+ vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt)
+
+/* Note: this does not change a negative result into a zero result. */
+#define VSTREAM_SUB_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec - (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).tv_usec < 0) { \
+ (x).tv_usec += 1000000; \
+ (x).tv_sec -= 1; \
+ } \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+#define VSTREAM_ADD_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec + (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec + (z).tv_usec; \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+/* vstream_buf_init - initialize buffer */
+
+static void vstream_buf_init(VBUF *bp, int flags)
+{
+
+ /*
+ * Initialize the buffer such that the first data access triggers a
+ * buffer boundary action.
+ */
+ VSTREAM_BUF_ZERO(bp);
+ VSTREAM_BUF_ACTIONS(bp,
+ VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0,
+ VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0,
+ vstream_buf_space);
+}
+
+/* vstream_buf_alloc - allocate buffer memory */
+
+static void vstream_buf_alloc(VBUF *bp, ssize_t len)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t used = bp->ptr - bp->data;
+ const char *myname = "vstream_buf_alloc";
+
+ if (len < bp->len)
+ msg_panic("%s: attempt to shrink buffer", myname);
+ if (bp->flags & VSTREAM_FLAG_FIXED)
+ msg_panic("%s: unable to extend fixed-size buffer", myname);
+
+ /*
+ * Late buffer allocation allows the user to override the default policy.
+ * If a buffer already exists, allow for the presence of (output) data.
+ */
+ bp->data = (unsigned char *)
+ (bp->data ? myrealloc((void *) bp->data, len) : mymalloc(len));
+ if (bp->flags & VSTREAM_FLAG_MEMORY)
+ memset(bp->data + bp->len, 0, len - bp->len);
+ bp->len = len;
+ if (bp->flags & VSTREAM_FLAG_READ) {
+ bp->ptr = bp->data + used;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ } else {
+ VSTREAM_BUF_AT_OFFSET(bp, used);
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ }
+}
+
+/* vstream_buf_wipe - reset buffer to initial state */
+
+static void vstream_buf_wipe(VBUF *bp)
+{
+ if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data)
+ myfree((void *) bp->data);
+ VSTREAM_BUF_ZERO(bp);
+ VSTREAM_BUF_ACTIONS(bp, 0, 0, 0);
+}
+
+/* vstream_fflush_some - flush some buffered data */
+
+static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush)
+{
+ const char *myname = "vstream_fflush_some";
+ VBUF *bp = &stream->buf;
+ ssize_t used;
+ ssize_t left_over;
+ void *data;
+ ssize_t len;
+ ssize_t n;
+ int timeout;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+
+ /*
+ * Sanity checks. It is illegal to flush a read-only stream. Otherwise,
+ * if there is buffered input, discard the input. If there is buffered
+ * output, require that the amount to flush is larger than the amount to
+ * keep, so that we can memcpy() the residue.
+ */
+ if (bp->put_ready == 0)
+ msg_panic("%s: read-only stream", myname);
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_READ: /* discard input */
+ VSTREAM_BUF_AT_END(bp);
+ /* FALLTHROUGH */
+ case 0: /* flush after seek? */
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+ case VSTREAM_FLAG_WRITE: /* output buffered */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+ used = bp->len - bp->cnt;
+ left_over = used - to_flush;
+
+ if (msg_verbose > 2 && stream != VSTREAM_ERR)
+ msg_info("%s: fd %d flush %ld", myname, stream->fd, (long) to_flush);
+ if (to_flush < 0 || left_over < 0)
+ msg_panic("%s: bad to_flush %ld", myname, (long) to_flush);
+ if (to_flush < left_over)
+ msg_panic("%s: to_flush < left_over", myname);
+ if (to_flush == 0)
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+ if (bp->flags & VSTREAM_FLAG_ERR)
+ return (VSTREAM_EOF);
+
+ /*
+ * When flushing a buffer, allow for partial writes. These can happen
+ * while talking to a network. Update the cached file seek position, if
+ * any.
+ *
+ * When deadlines are enabled, we count the elapsed time for each write
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between write operations. Keep in
+ * mind that a receiver may not be able to keep up when a sender suddenly
+ * floods it with a lot of data as it tries to catch up with a deadline.
+ */
+ for (data = (void *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_WR_ERR | VSTREAM_FLAG_WR_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ if (len == to_flush)
+ GETTIMEOFDAY(&before);
+ else
+ before = stream->iotime;
+ } else
+ timeout = stream->timeout;
+ if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_WR_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ }
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush)
+ msg_info("%s: %d flushed %ld/%ld", myname, stream->fd,
+ (long) n, (long) to_flush);
+ }
+ if (bp->flags & VSTREAM_FLAG_SEEK)
+ stream->offset += to_flush;
+
+ /*
+ * Allow for partial buffer flush requests. We use memcpy() for reasons
+ * of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-).
+ * This is OK because we have already verified that the to_flush count is
+ * larger than the left_over count.
+ */
+ if (left_over > 0)
+ memcpy(bp->data, bp->data + to_flush, left_over);
+ bp->cnt += to_flush;
+ bp->ptr -= to_flush;
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+}
+
+/* vstream_fflush_delayed - delayed stream flush for double-buffered stream */
+
+static int vstream_fflush_delayed(VSTREAM *stream)
+{
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE)
+ msg_panic("vstream_fflush_delayed: bad flags");
+
+ /*
+ * Temporarily swap buffers and flush unwritten data. This may seem like
+ * a lot of work, but it's peanuts compared to the write(2) call that we
+ * already have avoided. For example, delayed flush is never used on a
+ * non-pipelined SMTP connection.
+ */
+ stream->buf.flags &= ~VSTREAM_FLAG_READ;
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+
+ status = VSTREAM_FFLUSH_SOME(stream);
+
+ stream->buf.flags &= ~VSTREAM_FLAG_WRITE;
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ stream->buf.flags |= VSTREAM_FLAG_READ;
+ VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
+
+ return (status);
+}
+
+/* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */
+
+static int vstream_buf_get_ready(VBUF *bp)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ const char *myname = "vstream_buf_get_ready";
+ ssize_t n;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+ int timeout;
+
+ /*
+ * Detect a change of I/O direction or position. If so, flush any
+ * unwritten output immediately when the stream is single-buffered, or
+ * when the stream is double-buffered and the read buffer is empty.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_WRITE: /* change direction */
+ if (bp->ptr > bp->data)
+ if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0
+ || stream->read_buf.cnt >= 0)
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (VSTREAM_EOF);
+ bp->flags &= ~VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE) {
+ VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
+ if (bp->cnt < 0)
+ return (0);
+ }
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_READ: /* no change */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * If this is the first GET operation, allocate a buffer. Late buffer
+ * allocation gives the application a chance to override the default
+ * buffering policy.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize)
+ vstream_buf_alloc(bp, stream->req_bufsize);
+
+ /*
+ * If the stream is double-buffered and the write buffer is not empty,
+ * this is the time to flush the write buffer. Delayed flushes reduce
+ * system call overhead, and on TCP sockets, avoid triggering Nagle's
+ * algorithm.
+ */
+ if ((bp->flags & VSTREAM_FLAG_DOUBLE)
+ && stream->write_buf.len > stream->write_buf.cnt)
+ if (vstream_fflush_delayed(stream))
+ return (VSTREAM_EOF);
+
+ /*
+ * Did we receive an EOF indication?
+ */
+ if (bp->flags & VSTREAM_FLAG_EOF)
+ return (VSTREAM_EOF);
+
+ /*
+ * Fill the buffer with as much data as we can handle, or with as much
+ * data as is available right now, whichever is less. Update the cached
+ * file seek position, if any.
+ *
+ * When deadlines are enabled, we count the elapsed time for each read
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between read operations. Keep in
+ * mind that a sender may get blocked, and may not be able to keep up
+ * when a receiver suddenly wants to read a lot of data as it tries to
+ * catch up with a deadline.
+ */
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_RD_ERR | VSTREAM_FLAG_RD_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ GETTIMEOFDAY(&before);
+ } else
+ timeout = stream->timeout;
+ switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) {
+ case -1:
+ bp->flags |= VSTREAM_FLAG_RD_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_RD_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ case 0:
+ bp->flags |= VSTREAM_FLAG_EOF;
+ return (VSTREAM_EOF);
+ default:
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n);
+ bp->cnt = -n;
+ bp->ptr = bp->data;
+ if (bp->flags & VSTREAM_FLAG_SEEK)
+ stream->offset += n;
+ return (0);
+ }
+}
+
+/* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */
+
+static int vstream_buf_put_ready(VBUF *bp)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ const char *myname = "vstream_buf_put_ready";
+
+ /*
+ * Sanity checks. Detect a change of I/O direction or position. If so,
+ * discard unread input, and reset the buffer to the beginning.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_READ: /* change direction */
+ bp->flags &= ~VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+ else
+ VSTREAM_BUF_AT_START(bp);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE: /* no change */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Remember the direction. If this is the first PUT operation for this
+ * stream or if the buffer is smaller than the requested size, allocate a
+ * new buffer; obviously there is no data to be flushed yet. Otherwise,
+ * flush the buffer.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize) {
+ vstream_buf_alloc(bp, stream->req_bufsize);
+ } else if (bp->cnt <= 0) {
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (VSTREAM_EOF);
+ }
+ return (0);
+}
+
+/* vstream_buf_space - reserve space ahead of time */
+
+static int vstream_buf_space(VBUF *bp, ssize_t want)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t used;
+ ssize_t incr;
+ ssize_t shortage;
+ const char *myname = "vstream_buf_space";
+
+ /*
+ * Sanity checks. Reserving space implies writing. It is illegal to write
+ * to a read-only stream. Detect a change of I/O direction or position.
+ * If so, reset the buffer to the beginning.
+ */
+ if (bp->put_ready == 0)
+ msg_panic("%s: read-only stream", myname);
+ if (want < 0)
+ msg_panic("%s: bad length %ld", myname, (long) want);
+ switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_READ: /* change direction */
+ bp->flags &= ~VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+ else
+ VSTREAM_BUF_AT_START(bp);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE: /* no change */
+ break;
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * See if enough space is available. If not, flush a multiple of
+ * VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of
+ * VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt
+ * to keep file updates block-aligned for better performance.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base))
+#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base)
+
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (want > bp->cnt) {
+ if ((used = bp->len - bp->cnt) > stream->req_bufsize)
+ if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize)))
+ return (VSTREAM_EOF);
+ if ((shortage = (want - bp->cnt)) > 0) {
+ if ((bp->flags & VSTREAM_FLAG_FIXED)
+ || shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ } else {
+ incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize);
+ vstream_buf_alloc(bp, bp->len + incr);
+ }
+ }
+ }
+ return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */
+}
+
+/* vstream_fpurge - discard unread or unwritten content */
+
+int vstream_fpurge(VSTREAM *stream, int direction)
+{
+ const char *myname = "vstream_fpurge";
+ VBUF *bp = &stream->buf;
+
+#define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \
+ VSTREAM_BUF_AT_START((b))
+#define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \
+ VSTREAM_BUF_AT_END((b))
+
+ /*
+ * To discard all unread contents, position the read buffer at its end,
+ * so that we skip over any unread data, and so that the next read
+ * operation will refill the buffer.
+ *
+ * To discard all unwritten content, position the write buffer at its
+ * beginning, so that the next write operation clobbers any unwritten
+ * data.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_READ_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_READ:
+ VSTREAM_MAYBE_PURGE_READ(direction, bp);
+ break;
+ case VSTREAM_FLAG_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ break;
+ case VSTREAM_FLAG_WRITE_DOUBLE:
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, bp);
+ break;
+ case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE:
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Invalidate the cached file seek position.
+ */
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ stream->offset = 0;
+
+ return (0);
+}
+
+/* vstream_fseek - change I/O position */
+
+off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence)
+{
+ const char *myname = "vstream_fseek";
+ VBUF *bp = &stream->buf;
+
+ /*
+ * TODO: support data length (data length != buffer length). Without data
+ * length information, Without explicit data length information,
+ * vstream_memopen(O_RDONLY) has to set the VSTREAM buffer length to the
+ * vstring payload length to avoid accessing unwritten data after
+ * vstream_fseek(), because for lseek() compatibility, vstream_fseek()
+ * must allow seeking past the end of a file.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data);
+ else if (whence == SEEK_END)
+ offset += bp->len;
+ if (offset < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE))
+ vstream_buf_space(bp, offset - bp->len);
+ VSTREAM_BUF_AT_OFFSET(bp, offset);
+ return (offset);
+ }
+
+ /*
+ * Flush any unwritten output. Discard any unread input. Position the
+ * buffer at the end, so that the next GET or PUT operation triggers a
+ * buffer boundary action.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_WRITE:
+ if (bp->ptr > bp->data) {
+ if (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data); /* add unwritten data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (-1);
+ }
+ VSTREAM_BUF_AT_END(bp);
+ break;
+ case VSTREAM_FLAG_READ:
+ if (whence == SEEK_CUR)
+ offset += bp->cnt; /* subtract unread data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ /* FALLTHROUGH */
+ case 0:
+ VSTREAM_BUF_AT_END(bp);
+ break;
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Clear the read/write flags to inform the buffer boundary action
+ * routines that we may have changed I/O position.
+ */
+ bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE);
+
+ /*
+ * Shave an unnecessary system call.
+ */
+ if (bp->flags & VSTREAM_FLAG_NSEEK) {
+ errno = ESPIPE;
+ return (-1);
+ }
+
+ /*
+ * Update the cached file seek position.
+ */
+ if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) {
+ if (errno == ESPIPE)
+ bp->flags |= VSTREAM_FLAG_NSEEK;
+ } else {
+ bp->flags |= VSTREAM_FLAG_SEEK;
+ }
+ bp->flags &= ~VSTREAM_FLAG_EOF;
+ return (stream->offset);
+}
+
+/* vstream_ftell - return file offset */
+
+off_t vstream_ftell(VSTREAM *stream)
+{
+ VBUF *bp = &stream->buf;
+
+ /*
+ * Special case for memory buffer.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ return (bp->ptr - bp->data);
+
+ /*
+ * Shave an unnecessary syscall.
+ */
+ if (bp->flags & VSTREAM_FLAG_NSEEK) {
+ errno = ESPIPE;
+ return (-1);
+ }
+
+ /*
+ * Use the cached file offset when available. This is the offset after
+ * the last read, write or seek operation.
+ */
+ if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) {
+ if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) {
+ bp->flags |= VSTREAM_FLAG_NSEEK;
+ return (-1);
+ }
+ bp->flags |= VSTREAM_FLAG_SEEK;
+ }
+
+ /*
+ * If this is a read buffer, subtract the number of unread bytes from the
+ * cached offset. Remember that read counts are negative.
+ */
+ if (bp->flags & VSTREAM_FLAG_READ)
+ return (stream->offset + bp->cnt);
+
+ /*
+ * If this is a write buffer, add the number of unwritten bytes to the
+ * cached offset.
+ */
+ if (bp->flags & VSTREAM_FLAG_WRITE)
+ return (stream->offset + (bp->ptr - bp->data));
+
+ /*
+ * Apparently, this is a new buffer, or a buffer after seek, so there is
+ * no need to account for unread or unwritten data.
+ */
+ return (stream->offset);
+}
+
+/* vstream_subopen - initialize everything except buffers and I/O handlers */
+
+static VSTREAM *vstream_subopen(void)
+{
+ VSTREAM *stream;
+
+ /* Note: memset() is not a portable way to initialize non-integer types. */
+ stream = (VSTREAM *) mymalloc(sizeof(*stream));
+ stream->offset = 0;
+ stream->path = 0;
+ stream->pid = 0;
+ stream->waitpid_fn = 0;
+ stream->timeout = 0;
+ stream->context = 0;
+ stream->jbuf = 0;
+ stream->iotime.tv_sec = stream->iotime.tv_usec = 0;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ stream->req_bufsize = 0;
+ stream->vstring = 0;
+ stream->min_data_rate = 0;
+ return (stream);
+}
+
+/* vstream_fdopen - add buffering to pre-opened stream */
+
+VSTREAM *vstream_fdopen(int fd, int flags)
+{
+ VSTREAM *stream;
+
+ /*
+ * Sanity check.
+ */
+ if (fd < 0)
+ msg_panic("vstream_fdopen: bad file %d", fd);
+
+ /*
+ * Initialize buffers etc. but do as little as possible. Late buffer
+ * allocation etc. gives the application a chance to override default
+ * policies. Either this, or the vstream*open() routines would have to
+ * have a really ugly interface with lots of mostly-unused arguments (can
+ * you say VMS?).
+ */
+ stream = vstream_subopen();
+ stream->fd = fd;
+ stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_RW_FN) timed_read : 0;
+ stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_RW_FN) timed_write : 0;
+ vstream_buf_init(&stream->buf, flags);
+ return (stream);
+}
+
+/* vstream_fopen - open buffered file stream */
+
+VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode)
+{
+ VSTREAM *stream;
+ int fd;
+
+ if ((fd = open(path, flags, mode)) < 0) {
+ return (0);
+ } else {
+ stream = vstream_fdopen(fd, flags);
+ stream->path = mystrdup(path);
+ return (stream);
+ }
+}
+
+/* vstream_fflush - flush write buffer */
+
+int vstream_fflush(VSTREAM *stream)
+{
+
+ /*
+ * With VSTRING, the write pointer must be positioned behind the end of
+ * data. But vstream_fseek() changes the write position, and causes the
+ * data length to be forgotten. Before flushing to vstream, remember the
+ * current write position, move the write pointer and do what needs to be
+ * done, then move the write pointer back to the saved location.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (stream->buf.flags & VSTREAM_FLAG_WRITE) {
+ VSTRING *string = stream->vstring;
+
+#ifdef PENDING_VSTREAM_FSEEK_FOR_MEMORY
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, stream->buf.data_len);
+#endif
+ memcpy(&string->vbuf, &stream->buf, sizeof(stream->buf));
+ string->vbuf.flags &= VSTRING_FLAG_MASK;
+ VSTRING_TERMINATE(string);
+ }
+ return (0);
+ }
+ if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE)
+ == VSTREAM_FLAG_READ_DOUBLE
+ && stream->write_buf.len > stream->write_buf.cnt)
+ vstream_fflush_delayed(stream);
+ return (VSTREAM_FFLUSH_SOME(stream));
+}
+
+/* vstream_fclose - close buffered stream */
+
+int vstream_fclose(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->pid != 0)
+ msg_panic("vstream_fclose: stream has process");
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ || ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0
+ && stream->fd >= 0))
+ vstream_fflush(stream);
+ /* Do not remove: vstream_fdclose() depends on this error test. */
+ err = vstream_ferror(stream);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ if (stream->read_fd >= 0)
+ err |= close(stream->read_fd);
+ if (stream->write_fd != stream->read_fd)
+ if (stream->write_fd >= 0)
+ err |= close(stream->write_fd);
+ vstream_buf_wipe(&stream->read_buf);
+ vstream_buf_wipe(&stream->write_buf);
+ stream->buf = stream->read_buf;
+ } else {
+ if (stream->fd >= 0)
+ err |= close(stream->fd);
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ vstream_buf_wipe(&stream->buf);
+ }
+ if (stream->path)
+ myfree(stream->path);
+ if (stream->jbuf)
+ myfree((void *) stream->jbuf);
+ if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING))
+ vstring_free(stream->vstring);
+ if (!VSTREAM_STATIC(stream))
+ myfree((void *) stream);
+ return (err ? VSTREAM_EOF : 0);
+}
+
+/* vstream_fdclose - close stream, leave file(s) open */
+
+int vstream_fdclose(VSTREAM *stream)
+{
+
+ /*
+ * Flush unwritten output, just like vstream_fclose(). Errors are
+ * reported by vstream_fclose().
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
+ (void) vstream_fflush(stream);
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ stream->fd = stream->read_fd = stream->write_fd = -1;
+ } else {
+ stream->fd = -1;
+ }
+ return (vstream_fclose(stream));
+}
+
+/* vstream_printf - formatted print to stdout */
+
+VSTREAM *vstream_printf(const char *fmt,...)
+{
+ VSTREAM *stream = VSTREAM_OUT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vbuf_print(&stream->buf, fmt, ap);
+ va_end(ap);
+ return (stream);
+}
+
+/* vstream_fprintf - formatted print to buffered stream */
+
+VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vbuf_print(&stream->buf, fmt, ap);
+ va_end(ap);
+ return (stream);
+}
+
+/* vstream_fputs - write string to stream */
+
+int vstream_fputs(const char *str, VSTREAM *stream)
+{
+ int ch;
+
+ while ((ch = *str++) != 0)
+ if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF)
+ return (VSTREAM_EOF);
+ return (0);
+}
+
+/* vstream_fread_buf - unformatted read to VSTRING */
+
+ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_RESET(vp);
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_str(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, ret);
+ return (ret);
+}
+
+/* vstream_fread_app - unformatted read to VSTRING */
+
+ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_end(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret);
+ return (ret);
+}
+
+/* vstream_control - fine control */
+
+void vstream_control(VSTREAM *stream, int name,...)
+{
+ const char *myname = "vstream_control";
+ va_list ap;
+ int floor;
+ int old_fd;
+ ssize_t req_bufsize = 0;
+ VSTREAM *stream2;
+ int min_data_rate;
+
+#define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0)
+
+ /*
+ * A crude 'allow' filter for memory streams.
+ */
+ int memory_ops =
+ ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT)
+ | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT)
+ | (1 << VSTREAM_CTL_OWN_VSTRING));
+
+ for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ && (memory_ops & (1 << name)) == 0)
+ msg_panic("%s: memory stream does not support VSTREAM_CTL_%d",
+ VSTREAM_PATH(stream), name);
+ switch (name) {
+ case VSTREAM_CTL_READ_FN:
+ stream->read_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_WRITE_FN:
+ stream->write_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_CONTEXT:
+ stream->context = va_arg(ap, void *);
+ break;
+ case VSTREAM_CTL_PATH:
+ if (stream->path)
+ myfree(stream->path);
+ stream->path = mystrdup(va_arg(ap, char *));
+ break;
+ case VSTREAM_CTL_DOUBLE:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) {
+ stream->buf.flags |= VSTREAM_FLAG_DOUBLE;
+ if (stream->buf.flags & VSTREAM_FLAG_READ) {
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ VSTREAM_FORK_STATE(stream, write_buf, write_fd);
+ } else {
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ VSTREAM_FORK_STATE(stream, read_buf, read_fd);
+ }
+ }
+ break;
+ case VSTREAM_CTL_READ_FD:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
+ msg_panic("VSTREAM_CTL_READ_FD requires double buffering");
+ stream->read_fd = va_arg(ap, int);
+ stream->buf.flags |= VSTREAM_FLAG_NSEEK;
+ break;
+ case VSTREAM_CTL_WRITE_FD:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
+ msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering");
+ stream->write_fd = va_arg(ap, int);
+ stream->buf.flags |= VSTREAM_FLAG_NSEEK;
+ break;
+ case VSTREAM_CTL_SWAP_FD:
+ stream2 = va_arg(ap, VSTREAM *);
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE)
+ != (stream2->buf.flags & VSTREAM_FLAG_DOUBLE))
+ msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between "
+ "single-buffered and double-buffered streams");
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ SWAP(int, stream->read_fd, stream2->read_fd);
+ SWAP(int, stream->write_fd, stream2->write_fd);
+ stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ?
+ stream->write_fd : stream->read_fd);
+ } else {
+ SWAP(int, stream->fd, stream2->fd);
+ }
+ break;
+ case VSTREAM_CTL_TIMEOUT:
+ if (stream->timeout == 0)
+ GETTIMEOFDAY(&stream->iotime);
+ stream->timeout = va_arg(ap, int);
+ if (stream->timeout < 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ break;
+ case VSTREAM_CTL_EXCEPT:
+ if (stream->jbuf == 0)
+ stream->jbuf =
+ (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF));
+ break;
+
+#ifdef VSTREAM_CTL_DUPFD
+
+#define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \
+ if (((backup) = (fd)) < floor) { \
+ if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \
+ msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \
+ (void) close(backup); \
+ } \
+ } while (0)
+
+ case VSTREAM_CTL_DUPFD:
+ floor = va_arg(ap, int);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor);
+ if (stream->write_fd == old_fd)
+ stream->write_fd = stream->read_fd;
+ else
+ VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor);
+ stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ?
+ stream->read_fd : stream->write_fd;
+ } else {
+ VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor);
+ }
+ break;
+#endif
+
+ /*
+ * Postpone memory (re)allocation until the space is needed.
+ */
+ case VSTREAM_CTL_BUFSIZE:
+ req_bufsize = va_arg(ap, ssize_t);
+ /* Heuristic to detect missing (ssize_t) type cast on LP64 hosts. */
+ if (req_bufsize < 0 || req_bufsize > INT_MAX)
+ msg_panic("unreasonable VSTREAM_CTL_BUFSIZE request: %ld",
+ (long) req_bufsize);
+ if ((stream->buf.flags & VSTREAM_FLAG_FIXED) == 0
+ && req_bufsize > stream->req_bufsize) {
+ if (msg_verbose)
+ msg_info("fd=%d: stream buffer size old=%ld new=%ld",
+ vstream_fileno(stream),
+ (long) stream->req_bufsize,
+ (long) req_bufsize);
+ stream->req_bufsize = req_bufsize;
+ }
+ break;
+
+ /*
+ * Make no gettimeofday() etc. system call until we really know
+ * that we need to do I/O. This avoids a performance hit when
+ * sending or receiving body content one line at a time.
+ */
+ case VSTREAM_CTL_STOP_DEADLINE:
+ stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE;
+ break;
+ case VSTREAM_CTL_START_DEADLINE:
+ if (stream->timeout <= 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ stream->buf.flags |= VSTREAM_FLAG_DEADLINE;
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ break;
+ case VSTREAM_CTL_MIN_DATA_RATE:
+ min_data_rate = va_arg(ap, int);
+ if (min_data_rate < 0)
+ msg_panic("%s: bad min_data_rate %d", myname, min_data_rate);
+ stream->min_data_rate = min_data_rate;
+ break;
+ case VSTREAM_CTL_OWN_VSTRING:
+ if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("%s: operation on non-VSTRING stream", myname);
+ stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING;
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+/* vstream_vprintf - formatted print to stdout */
+
+VSTREAM *vstream_vprintf(const char *format, va_list ap)
+{
+ VSTREAM *vp = VSTREAM_OUT;
+
+ vbuf_print(&vp->buf, format, ap);
+ return (vp);
+}
+
+/* vstream_vfprintf - formatted print engine */
+
+VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap)
+{
+ vbuf_print(&vp->buf, format, ap);
+ return (vp);
+}
+
+/* vstream_bufstat - get stream buffer status */
+
+ssize_t vstream_bufstat(VSTREAM *vp, int command)
+{
+ VBUF *bp;
+
+ switch (command & VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_IN:
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->read_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? -bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ case VSTREAM_BST_FLAG_OUT:
+ if (vp->buf.flags & VSTREAM_FLAG_WRITE) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->write_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? bp->len - bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ }
+ msg_panic("vstream_bufstat: unknown command: %d", command);
+}
+
+#undef vstream_peek /* API binary compatibility. */
+
+/* vstream_peek - peek at a stream */
+
+ssize_t vstream_peek(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return (-vp->buf.cnt);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return (-vp->read_buf.cnt);
+ } else {
+ return (0);
+ }
+}
+
+/* vstream_peek_data - peek at unread data */
+
+const char *vstream_peek_data(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return ((const char *) vp->buf.ptr);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return ((const char *) vp->read_buf.ptr);
+ } else {
+ return (0);
+ }
+}
+
+/* vstream_memopen - open a VSTRING */
+
+VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags)
+{
+ if (stream == 0)
+ stream = vstream_subopen();
+ else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("vstream_memreopen: cannot reopen non-memory stream");
+ stream->fd = -1;
+ stream->read_fn = 0;
+ stream->write_fn = 0;
+ stream->vstring = string;
+ memcpy(&stream->buf, &stream->vstring->vbuf, sizeof(stream->buf));
+ stream->buf.flags |= VSTREAM_FLAG_MEMORY;
+ switch (VSTREAM_ACC_MASK(flags)) {
+ case O_RDONLY:
+ stream->buf.flags |= VSTREAM_FLAG_READ;
+ /* Prevent reading unwritten data after vstream_fseek(). */
+ stream->buf.len = stream->buf.ptr - stream->buf.data;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_WRONLY:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_APPEND:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf,
+ stream->buf.ptr - stream->buf.data);
+ break;
+ default:
+ msg_panic("vstream_memopen: flags must be one of "
+ "O_RDONLY, O_WRONLY, or O_APPEND");
+ }
+ return (stream);
+}
+
+#ifdef TEST
+
+static void copy_line(ssize_t bufsize)
+{
+ int c;
+
+ /*
+ * Demonstrates that VSTREAM_CTL_BUFSIZE increases the buffer size, but
+ * does not decrease it. Uses VSTREAM_ERR for non-test output to avoid
+ * interfering with the test.
+ */
+ vstream_fprintf(VSTREAM_ERR, "buffer size test: copy text with %ld buffer size, ignore requests to shrink\n",
+ (long) bufsize);
+ vstream_fflush(VSTREAM_ERR);
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) {
+ VSTREAM_PUTC(c, VSTREAM_OUT);
+ if (c == '\n')
+ break;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fprintf(VSTREAM_ERR, "actual read/write buffer sizes: %ld/%ld\n\n",
+ (long) VSTREAM_IN->buf.len, (long) VSTREAM_OUT->buf.len);
+ vstream_fflush(VSTREAM_ERR);
+}
+
+static void printf_number(void)
+{
+
+ /*
+ * Demonstrates that vstream_printf() use vbuf_print().
+ */
+ vstream_printf("formatting test: print a number\n");
+ vstream_printf("%d\n\n", 1234567890);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+static void do_memory_stream(void)
+{
+ VSTRING *buf = vstring_alloc(1);
+ VSTREAM *fp;
+ off_t offset;
+ int ch;
+
+ /*
+ * Preload the string.
+ */
+ vstream_printf("memory stream test prep: prefill the VSTRING\n");
+ vstring_strcpy(buf, "01234567");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM in write-only mode, and clobber it.
+ */
+ vstream_printf("memory stream test: open the VSTRING for writing, overwrite, close\n");
+ fp = vstream_memopen(buf, O_WRONLY);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, "hallo");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fclose(fp);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM for append. vstream_memopen() sets the
+ * buffer length to the VSTRING buffer length, and positions the write
+ * pointer at the VSTRING write position. Write some content, then
+ * overwrite one character.
+ */
+ vstream_printf("memory stream test: open the VSTRING for append, write multiple, then overwrite 1\n");
+ fp = vstream_memopen(buf, O_APPEND);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, " world");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ if (vstream_fflush(fp))
+ msg_fatal("vstream_fflush: %m");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+
+ /*
+ * While the stream is still open, replace the second character.
+ */
+ vstream_printf("replace second character and close\n");
+ if ((offset = vstream_fseek(fp, 1, SEEK_SET)) != 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) 1);
+ VSTREAM_PUTC('e', fp);
+
+ /*
+ * Skip to the end of the content, so that vstream_fflush() will update
+ * the VSTRING with the right content length.
+ */
+ if ((offset = vstream_fseek(fp, VSTRING_LEN(buf), SEEK_SET)) != VSTRING_LEN(buf))
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) VSTRING_LEN(buf));
+ vstream_fclose(fp);
+
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * TODO: test that in write/append mode, seek past the end of data will
+ * result in zero-filled space.
+ */
+
+ /*
+ * Test: Open the VSTRING for reading. This time, vstream_memopen() will
+ * set the VSTREAM buffer length to the content length of the VSTRING, so
+ * that it won't attempt to read past the end of the content.
+ */
+ vstream_printf("memory stream test: open VSTRING for reading, then read\n");
+ fp = vstream_memopen(buf, O_RDONLY);
+ vstream_printf("initial memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("reading memory VSTREAM: ");
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ VSTREAM_PUTCHAR(ch);
+ VSTREAM_PUTCHAR('\n');
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("seeking to offset %ld should work: ",
+ (long) fp->buf.len + 1);
+ vstream_fflush(VSTREAM_OUT);
+ if ((offset = vstream_fseek(fp, fp->buf.len + 1, SEEK_SET)) != fp->buf.len + 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) fp->buf.len + 1);
+ vstream_printf("PASS\n");
+ vstream_fflush(VSTREAM_OUT);
+ vstream_printf("VSTREAM_GETC should return VSTREAM_EOF\n");
+ ch = VSTREAM_GETC(fp);
+ if (ch != VSTREAM_EOF)
+ msg_panic("unexpected vstream_fseek VSTREAM_GETC return: %d, expected: %d",
+ ch, VSTREAM_EOF);
+ vstream_printf("PASS\n");
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fclose(fp);
+ vstring_free(buf);
+}
+
+ /*
+ * Exercise some of the features.
+ */
+
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Test buffer expansion and shrinking. Formatted print may silently
+ * expand the write buffer and cause multiple bytes to be written.
+ */
+ copy_line(1); /* one-byte read/write */
+ copy_line(2); /* two-byte read/write */
+ copy_line(1); /* two-byte read/write */
+ printf_number(); /* multi-byte write */
+ do_memory_stream();
+
+ exit(0);
+}
+
+#endif
diff --git a/src/util/vstream.h b/src/util/vstream.h
new file mode 100644
index 0000000..23688c7
--- /dev/null
+++ b/src/util/vstream.h
@@ -0,0 +1,293 @@
+#ifndef _VSTREAM_H_INCLUDED_
+#define _VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstream 3h
+/* SUMMARY
+/* simple buffered I/O package
+/* SYNOPSIS
+/* #include <vstream.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+#include <check_arg.h>
+
+ /*
+ * Simple buffered stream. The members of this structure are not part of the
+ * official interface and can change without prior notice.
+ */
+typedef ssize_t (*VSTREAM_RW_FN) (int, void *, size_t, int, void *);
+typedef pid_t(*VSTREAM_WAITPID_FN) (pid_t, WAIT_STATUS_T *, int);
+
+#ifdef NO_SIGSETJMP
+#define VSTREAM_JMP_BUF jmp_buf
+#else
+#define VSTREAM_JMP_BUF sigjmp_buf
+#endif
+
+typedef struct VSTREAM {
+ VBUF buf; /* generic intelligent buffer */
+ int fd; /* file handle, no 256 limit */
+ VSTREAM_RW_FN read_fn; /* buffer fill action */
+ VSTREAM_RW_FN write_fn; /* buffer flush action */
+ ssize_t req_bufsize; /* requested read/write buffer size */
+ void *context; /* application context */
+ off_t offset; /* cached seek info */
+ char *path; /* give it at least try */
+ int read_fd; /* read channel (double-buffered) */
+ int write_fd; /* write channel (double-buffered) */
+ VBUF read_buf; /* read buffer (double-buffered) */
+ VBUF write_buf; /* write buffer (double-buffered) */
+ pid_t pid; /* vstream_popen/close() */
+ VSTREAM_WAITPID_FN waitpid_fn; /* vstream_popen/close() */
+ int timeout; /* read/write timeout */
+ VSTREAM_JMP_BUF *jbuf; /* exception handling */
+ struct timeval iotime; /* time of last fill/flush */
+ struct timeval time_limit; /* read/write time limit */
+ int min_data_rate; /* min data rate for time limit */
+ struct VSTRING *vstring; /* memory-backed stream */
+} VSTREAM;
+
+extern VSTREAM vstream_fstd[]; /* pre-defined streams */
+
+#define VSTREAM_IN (&vstream_fstd[0])
+#define VSTREAM_OUT (&vstream_fstd[1])
+#define VSTREAM_ERR (&vstream_fstd[2])
+
+#define VSTREAM_FLAG_RD_ERR VBUF_FLAG_RD_ERR /* read error */
+#define VSTREAM_FLAG_WR_ERR VBUF_FLAG_WR_ERR /* write error */
+#define VSTREAM_FLAG_RD_TIMEOUT VBUF_FLAG_RD_TIMEOUT /* read timeout */
+#define VSTREAM_FLAG_WR_TIMEOUT VBUF_FLAG_WR_TIMEOUT /* write timeout */
+
+#define VSTREAM_FLAG_ERR VBUF_FLAG_ERR /* some I/O error */
+#define VSTREAM_FLAG_EOF VBUF_FLAG_EOF /* end of file */
+#define VSTREAM_FLAG_TIMEOUT VBUF_FLAG_TIMEOUT /* timeout error */
+#define VSTREAM_FLAG_FIXED VBUF_FLAG_FIXED /* fixed-size buffer */
+#define VSTREAM_FLAG_BAD VBUF_FLAG_BAD
+
+/* Flags 1<<24 and above are reserved for VSTRING. */
+#define VSTREAM_FLAG_READ (1<<8) /* read buffer */
+#define VSTREAM_FLAG_WRITE (1<<9) /* write buffer */
+#define VSTREAM_FLAG_SEEK (1<<10) /* seek info valid */
+#define VSTREAM_FLAG_NSEEK (1<<11) /* can't seek this file */
+#define VSTREAM_FLAG_DOUBLE (1<<12) /* double buffer */
+#define VSTREAM_FLAG_DEADLINE (1<<13) /* deadline active */
+#define VSTREAM_FLAG_MEMORY (1<<14) /* internal stream */
+#define VSTREAM_FLAG_OWN_VSTRING (1<<15)/* owns VSTRING resource */
+
+#define VSTREAM_PURGE_READ (1<<0) /* flush unread data */
+#define VSTREAM_PURGE_WRITE (1<<1) /* flush unwritten data */
+#define VSTREAM_PURGE_BOTH (VSTREAM_PURGE_READ|VSTREAM_PURGE_WRITE)
+
+#define VSTREAM_BUFSIZE 4096
+
+extern VSTREAM *vstream_fopen(const char *, int, mode_t);
+extern int vstream_fclose(VSTREAM *);
+extern off_t WARN_UNUSED_RESULT vstream_fseek(VSTREAM *, off_t, int);
+extern off_t vstream_ftell(VSTREAM *);
+extern int vstream_fpurge(VSTREAM *, int);
+extern int vstream_fflush(VSTREAM *);
+extern int vstream_fputs(const char *, VSTREAM *);
+extern VSTREAM *vstream_fdopen(int, int);
+extern int vstream_fdclose(VSTREAM *);
+
+#define vstream_fread(v, b, n) vbuf_read(&(v)->buf, (b), (n))
+#define vstream_fwrite(v, b, n) vbuf_write(&(v)->buf, (b), (n))
+
+#define VSTREAM_PUTC(ch, vp) VBUF_PUT(&(vp)->buf, (ch))
+#define VSTREAM_GETC(vp) VBUF_GET(&(vp)->buf)
+#define vstream_ungetc(vp, ch) vbuf_unget(&(vp)->buf, (ch))
+#define VSTREAM_EOF VBUF_EOF
+
+#define VSTREAM_PUTCHAR(ch) VSTREAM_PUTC((ch), VSTREAM_OUT)
+#define VSTREAM_GETCHAR() VSTREAM_GETC(VSTREAM_IN)
+
+#define vstream_fileno(vp) ((vp)->fd)
+#define vstream_req_bufsize(vp) ((const ssize_t) ((vp)->req_bufsize))
+#define vstream_context(vp) ((vp)->context)
+#define vstream_rd_error(vp) vbuf_rd_error(&(vp)->buf)
+#define vstream_wr_error(vp) vbuf_wr_error(&(vp)->buf)
+#define vstream_ferror(vp) vbuf_error(&(vp)->buf)
+#define vstream_feof(vp) vbuf_eof(&(vp)->buf)
+#define vstream_rd_timeout(vp) vbuf_rd_timeout(&(vp)->buf)
+#define vstream_wr_timeout(vp) vbuf_wr_timeout(&(vp)->buf)
+#define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf)
+#define vstream_clearerr(vp) vbuf_clearerr(&(vp)->buf)
+#define VSTREAM_PATH(vp) ((vp)->path ? (const char *) (vp)->path : "unknown_stream")
+#define vstream_ftime(vp) ((time_t) ((vp)->iotime.tv_sec))
+#define vstream_ftimeval(vp) ((vp)->iotime)
+
+#define vstream_fstat(vp, fl) ((vp)->buf.flags & (fl))
+
+extern ssize_t vstream_fread_buf(VSTREAM *, struct VSTRING *, ssize_t);
+extern ssize_t vstream_fread_app(VSTREAM *, struct VSTRING *, ssize_t);
+extern void vstream_control(VSTREAM *, int,...);
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define VSTREAM_CTL_END 0
+#define VSTREAM_CTL_READ_FN 1
+#define VSTREAM_CTL_WRITE_FN 2
+#define VSTREAM_CTL_PATH 3
+#define VSTREAM_CTL_DOUBLE 4
+#define VSTREAM_CTL_READ_FD 5
+#define VSTREAM_CTL_WRITE_FD 6
+#define VSTREAM_CTL_WAITPID_FN 7
+#define VSTREAM_CTL_TIMEOUT 8
+#define VSTREAM_CTL_EXCEPT 9
+#define VSTREAM_CTL_CONTEXT 10
+#ifdef F_DUPFD
+#define VSTREAM_CTL_DUPFD 11
+#endif
+#define VSTREAM_CTL_BUFSIZE 12
+#define VSTREAM_CTL_SWAP_FD 13
+#define VSTREAM_CTL_START_DEADLINE 14
+#define VSTREAM_CTL_STOP_DEADLINE 15
+#define VSTREAM_CTL_OWN_VSTRING 16
+#define VSTREAM_CTL_MIN_DATA_RATE 17
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_VSTREAM_CTL_END VSTREAM_CTL_END
+#define CA_VSTREAM_CTL_READ_FN(v) VSTREAM_CTL_READ_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v))
+#define CA_VSTREAM_CTL_WRITE_FN(v) VSTREAM_CTL_WRITE_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v))
+#define CA_VSTREAM_CTL_PATH(v) VSTREAM_CTL_PATH, CHECK_CPTR(VSTREAM_CTL, char, (v))
+#define CA_VSTREAM_CTL_DOUBLE VSTREAM_CTL_DOUBLE
+#define CA_VSTREAM_CTL_READ_FD(v) VSTREAM_CTL_READ_FD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_WRITE_FD(v) VSTREAM_CTL_WRITE_FD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_WAITPID_FN(v) VSTREAM_CTL_WAITPID_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_WAITPID_FN, (v))
+#define CA_VSTREAM_CTL_TIMEOUT(v) VSTREAM_CTL_TIMEOUT, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_EXCEPT VSTREAM_CTL_EXCEPT
+#define CA_VSTREAM_CTL_CONTEXT(v) VSTREAM_CTL_CONTEXT, CHECK_PTR(VSTREAM_CTL, void, (v))
+#ifdef F_DUPFD
+#define CA_VSTREAM_CTL_DUPFD(v) VSTREAM_CTL_DUPFD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#endif
+#define CA_VSTREAM_CTL_BUFSIZE(v) VSTREAM_CTL_BUFSIZE, CHECK_VAL(VSTREAM_CTL, ssize_t, (v))
+#define CA_VSTREAM_CTL_SWAP_FD(v) VSTREAM_CTL_SWAP_FD, CHECK_PTR(VSTREAM_CTL, VSTREAM, (v))
+#define CA_VSTREAM_CTL_START_DEADLINE VSTREAM_CTL_START_DEADLINE
+#define CA_VSTREAM_CTL_STOP_DEADLINE VSTREAM_CTL_STOP_DEADLINE
+#define CA_VSTREAM_CTL_MIN_DATA_RATE(v) VSTREAM_CTL_MIN_DATA_RATE, CHECK_VAL(VSTREAM_CTL, int, (v))
+
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, ssize_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, int);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_WAITPID_FN);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_RW_FN);
+CHECK_PTR_HELPER_DCL(VSTREAM_CTL, void);
+CHECK_PTR_HELPER_DCL(VSTREAM_CTL, VSTREAM);
+CHECK_CPTR_HELPER_DCL(VSTREAM_CTL, char);
+
+extern VSTREAM *PRINTFLIKE(1, 2) vstream_printf(const char *,...);
+extern VSTREAM *PRINTFLIKE(2, 3) vstream_fprintf(VSTREAM *, const char *,...);
+
+extern VSTREAM *vstream_popen(int,...);
+extern int vstream_pclose(VSTREAM *);
+
+#define vstream_ispipe(vp) ((vp)->pid != 0)
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define VSTREAM_POPEN_END 0 /* terminator */
+#define VSTREAM_POPEN_COMMAND 1 /* command is string */
+#define VSTREAM_POPEN_ARGV 2 /* command is array */
+#define VSTREAM_POPEN_UID 3 /* privileges */
+#define VSTREAM_POPEN_GID 4 /* privileges */
+#define VSTREAM_POPEN_ENV 5 /* extra environment */
+#define VSTREAM_POPEN_SHELL 6 /* alternative shell */
+#define VSTREAM_POPEN_WAITPID_FN 7 /* child catcher, waitpid() compat. */
+#define VSTREAM_POPEN_EXPORT 8 /* exportable environment */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_VSTREAM_POPEN_END VSTREAM_POPEN_END
+#define CA_VSTREAM_POPEN_COMMAND(v) VSTREAM_POPEN_COMMAND, CHECK_CPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_ARGV(v) VSTREAM_POPEN_ARGV, CHECK_PPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_UID(v) VSTREAM_POPEN_UID, CHECK_VAL(VSTREAM_PPN, uid_t, (v))
+#define CA_VSTREAM_POPEN_GID(v) VSTREAM_POPEN_GID, CHECK_VAL(VSTREAM_PPN, gid_t, (v))
+#define CA_VSTREAM_POPEN_ENV(v) VSTREAM_POPEN_ENV, CHECK_PPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_SHELL(v) VSTREAM_POPEN_SHELL, CHECK_CPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_WAITPID_FN(v) VSTREAM_POPEN_WAITPID_FN, CHECK_VAL(VSTREAM_PPN, VSTREAM_WAITPID_FN, (v))
+#define CA_VSTREAM_POPEN_EXPORT(v) VSTREAM_POPEN_EXPORT, CHECK_PPTR(VSTREAM_PPN, char, (v))
+
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, uid_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, gid_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, VSTREAM_WAITPID_FN);
+CHECK_PPTR_HELPER_DCL(VSTREAM_PPN, char);
+CHECK_CPTR_HELPER_DCL(VSTREAM_PPN, char);
+
+extern VSTREAM *vstream_vprintf(const char *, va_list);
+extern VSTREAM *vstream_vfprintf(VSTREAM *, const char *, va_list);
+
+extern ssize_t vstream_peek(VSTREAM *);
+extern ssize_t vstream_bufstat(VSTREAM *, int);
+
+#define VSTREAM_BST_FLAG_IN (1<<0)
+#define VSTREAM_BST_FLAG_OUT (1<<1)
+#define VSTREAM_BST_FLAG_PEND (1<<2)
+
+#define VSTREAM_BST_MASK_DIR (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_OUT)
+#define VSTREAM_BST_IN_PEND (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_PEND)
+#define VSTREAM_BST_OUT_PEND (VSTREAM_BST_FLAG_OUT | VSTREAM_BST_FLAG_PEND)
+
+#define vstream_peek(vp) vstream_bufstat((vp), VSTREAM_BST_IN_PEND)
+
+extern const char *vstream_peek_data(VSTREAM *);
+
+ /*
+ * Exception handling. We use pointer to jmp_buf to avoid a lot of unused
+ * baggage for streams that don't need this functionality.
+ *
+ * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can
+ * avoid surprises in code that manipulates signals, but unfortunately some
+ * systems have bugs in their implementation.
+ */
+#ifdef NO_SIGSETJMP
+#define vstream_setjmp(stream) setjmp((stream)->jbuf[0])
+#define vstream_longjmp(stream, val) longjmp((stream)->jbuf[0], (val))
+#else
+#define vstream_setjmp(stream) sigsetjmp((stream)->jbuf[0], 1)
+#define vstream_longjmp(stream, val) siglongjmp((stream)->jbuf[0], (val))
+#endif
+
+ /*
+ * Tweaks and workarounds.
+ */
+extern int vstream_tweak_sock(VSTREAM *);
+extern int vstream_tweak_tcp(VSTREAM *);
+
+#define vstream_flags(stream) ((const int) (stream)->buf.flags)
+
+ /*
+ * Read/write VSTRING memory.
+ */
+#define vstream_memopen(string, flags) \
+ vstream_memreopen((VSTREAM *) 0, (string), (flags))
+VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/vstream_popen.c b/src/util/vstream_popen.c
new file mode 100644
index 0000000..d00d49e
--- /dev/null
+++ b/src/util/vstream_popen.c
@@ -0,0 +1,363 @@
+/*++
+/* NAME
+/* vstream_popen 3
+/* SUMMARY
+/* open stream to child process
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_popen(flags, key, value, ...)
+/* int flags;
+/* int key;
+/*
+/* int vstream_pclose(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* vstream_popen() opens a one-way or two-way stream to a user-specified
+/* command, which is executed by a child process. The \fIflags\fR
+/* argument is as with vstream_fopen(). The child's standard input and
+/* standard output are redirected to the stream, which is based on a
+/* socketpair or other suitable local IPC. vstream_popen() takes a list
+/* of macros with zero or more arguments, terminated by
+/* CA_VSTREAM_POPEN_END. The following is a listing of macros
+/* with the expected argument type.
+/* .RS
+/* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
+/* .IP "CA_VSTREAM_POPEN_ARGV(char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
+/* See also the CA_VSTREAM_POPEN_SHELL attribute below.
+/* .IP "CA_VSTREAM_POPEN_ENV(char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "CA_VSTREAM_POPEN_EXPORT(char **)"
+/* This argument is passed to clean_env().
+/* Null-terminated array of names of environment parameters
+/* that can be exported. By default, everything is exported.
+/* .IP "CA_VSTREAM_POPEN_UID(uid_t)"
+/* The user ID to execute the command as. The user ID must be non-zero.
+/* .IP "CA_VSTREAM_POPEN_GID(gid_t)"
+/* The group ID to execute the command as. The group ID must be non-zero.
+/* .IP "CA_VSTREAM_POPEN_SHELL(const char *)"
+/* The shell to use when executing the command specified with
+/* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))"
+/* waitpid()-like function to reap the child exit status when
+/* vstream_pclose() is called.
+/* .RE
+/* .PP
+/* vstream_pclose() closes the named stream and returns the child
+/* exit status. It is an error to specify a stream that was not
+/* returned by vstream_popen() or that is no longer open.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory.
+/*
+/* vstream_popen() returns a null pointer in case of trouble.
+/* The nature of the problem is specified via the \fIerrno\fR
+/* global variable.
+/*
+/* vstream_pclose() returns -1 in case of trouble.
+/* The nature of the problem is specified via the \fIerrno\fR
+/* global variable.
+/* SEE ALSO
+/* vstream(3) light-weight buffered I/O
+/* BUGS
+/* The interface, stolen from popen()/pclose(), ignores errors
+/* returned when the stream is closed, and does not distinguish
+/* between exit status codes and kill signals.
+/* 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/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <exec_command.h>
+#include <vstream.h>
+#include <argv.h>
+#include <set_ugid.h>
+#include <clean_env.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+typedef struct VSTREAM_POPEN_ARGS {
+ char **argv;
+ char *command;
+ uid_t uid;
+ gid_t gid;
+ int privileged;
+ char **env;
+ char **export;
+ char *shell;
+ VSTREAM_WAITPID_FN waitpid_fn;
+} VSTREAM_POPEN_ARGS;
+
+/* vstream_parse_args - get arguments from variadic list */
+
+static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap)
+{
+ const char *myname = "vstream_parse_args";
+ int key;
+
+ /*
+ * First, set the default values (on all non-zero entries)
+ */
+ args->argv = 0;
+ args->command = 0;
+ args->uid = 0;
+ args->gid = 0;
+ args->privileged = 0;
+ args->env = 0;
+ args->export = 0;
+ args->shell = 0;
+ args->waitpid_fn = 0;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) {
+ switch (key) {
+ case VSTREAM_POPEN_ARGV:
+ if (args->command != 0)
+ msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_COMMAND:
+ if (args->argv != 0)
+ msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case VSTREAM_POPEN_UID:
+ args->privileged = 1;
+ args->uid = va_arg(ap, uid_t);
+ break;
+ case VSTREAM_POPEN_GID:
+ args->privileged = 1;
+ args->gid = va_arg(ap, gid_t);
+ break;
+ case VSTREAM_POPEN_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_EXPORT:
+ args->export = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ case VSTREAM_POPEN_WAITPID_FN:
+ args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname);
+ if (args->privileged != 0 && args->uid == 0)
+ msg_panic("%s: privileged uid", myname);
+ if (args->privileged != 0 && args->gid == 0)
+ msg_panic("%s: privileged gid", myname);
+}
+
+/* vstream_popen - open stream to child process */
+
+VSTREAM *vstream_popen(int flags,...)
+{
+ const char *myname = "vstream_popen";
+ VSTREAM_POPEN_ARGS args;
+ va_list ap;
+ VSTREAM *stream;
+ int sockfd[2];
+ int pid;
+ int fd;
+ ARGV *argv;
+ char **cpp;
+
+ va_start(ap, flags);
+ vstream_parse_args(&args, ap);
+ va_end(ap);
+
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ if (duplex_pipe(sockfd) < 0)
+ return (0);
+
+ switch (pid = fork()) {
+ case -1: /* error */
+ (void) close(sockfd[0]);
+ (void) close(sockfd[1]);
+ return (0);
+ case 0: /* child */
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ if (close(sockfd[1]))
+ msg_warn("close: %m");
+ for (fd = 0; fd < 2; fd++)
+ if (sockfd[0] != fd)
+ if (DUP2(sockfd[0], fd) < 0)
+ msg_fatal("dup2: %m");
+ if (sockfd[0] >= 2 && close(sockfd[0]))
+ msg_warn("close: %m");
+
+ /*
+ * Don't try to become someone else unless the user specified it.
+ */
+ if (args.privileged)
+ set_ugid(args.uid, args.gid);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ if (args.export)
+ clean_env(args.export);
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ */
+ closelog();
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, CHARS_SPACE);
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+ default: /* parent */
+ if (close(sockfd[0]))
+ msg_warn("close: %m");
+ stream = vstream_fdopen(sockfd[1], flags);
+ stream->waitpid_fn = args.waitpid_fn;
+ stream->pid = pid;
+ return (stream);
+ }
+}
+
+/* vstream_pclose - close stream to child process */
+
+int vstream_pclose(VSTREAM *stream)
+{
+ pid_t saved_pid = stream->pid;
+ VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn;
+ pid_t pid;
+ WAIT_STATUS_T wait_status;
+
+ /*
+ * Close the pipe. Don't trigger an alarm in vstream_fclose().
+ */
+ if (saved_pid == 0)
+ msg_panic("vstream_pclose: stream has no process");
+ stream->pid = 0;
+ vstream_fclose(stream);
+
+ /*
+ * Reap the child exit status.
+ */
+ do {
+ if (saved_waitpid_fn != 0)
+ pid = saved_waitpid_fn(saved_pid, &wait_status, 0);
+ else
+ pid = waitpid(saved_pid, &wait_status, 0);
+ } while (pid == -1 && errno == EINTR);
+ return (pid == -1 ? -1 :
+ WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) :
+ WEXITSTATUS(wait_status));
+}
+
+#ifdef TEST
+
+#include <fcntl.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+ /*
+ * Test program. Run a command and copy lines one by one.
+ */
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTREAM *stream;
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if (argc < 2)
+ msg_fatal("usage: %s 'command'", argv[0]);
+
+ /*
+ * Open stream to child process.
+ */
+ if ((stream = vstream_popen(O_RDWR,
+ VSTREAM_POPEN_ARGV, argv + 1,
+ VSTREAM_POPEN_END)) == 0)
+ msg_fatal("vstream_popen: %m");
+
+ /*
+ * Copy loop, one line at a time.
+ */
+ while (vstring_fgets(buf, stream) != 0) {
+ if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf))
+ != VSTRING_LEN(buf))
+ msg_fatal("vstream_fwrite: %m");
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("vstream_fflush: %m");
+ if (vstring_fgets(buf, VSTREAM_IN) == 0)
+ break;
+ if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf))
+ != VSTRING_LEN(buf))
+ msg_fatal("vstream_fwrite: %m");
+ }
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ if ((status = vstream_pclose(stream)) != 0)
+ msg_warn("exit status: %d", status);
+
+ exit(status);
+}
+
+#endif
diff --git a/src/util/vstream_test.in b/src/util/vstream_test.in
new file mode 100644
index 0000000..b6687c8
--- /dev/null
+++ b/src/util/vstream_test.in
@@ -0,0 +1,3 @@
+abcdef
+ghijkl
+mnopqr
diff --git a/src/util/vstream_test.ref b/src/util/vstream_test.ref
new file mode 100644
index 0000000..eaa9952
--- /dev/null
+++ b/src/util/vstream_test.ref
@@ -0,0 +1,41 @@
+buffer size test: copy text with 1 buffer size, ignore requests to shrink
+abcdef
+actual read/write buffer sizes: 1/1
+
+buffer size test: copy text with 2 buffer size, ignore requests to shrink
+ghijkl
+actual read/write buffer sizes: 2/2
+
+buffer size test: copy text with 1 buffer size, ignore requests to shrink
+mnopqr
+actual read/write buffer sizes: 2/2
+
+formatting test: print a number
+1234567890
+
+memory stream test prep: prefill the VSTRING
+VSTRING content length: 8/8, content: 01234567
+
+memory stream test: open the VSTRING for writing, overwrite, close
+initial memory VSTREAM write offset: 0/8
+final memory VSTREAM write offset: 5/8
+VSTRING content length: 5/8, content: hallo
+
+memory stream test: open the VSTRING for append, write multiple, then overwrite 1
+initial memory VSTREAM write offset: 5/8
+final memory VSTREAM write offset: 11/16
+VSTRING content length: 11/16, content: hallo world
+
+replace second character and close
+VSTRING content length: 11/16, content: hello world
+
+memory stream test: open VSTRING for reading, then read
+initial memory VSTREAM read offset: 0/11
+reading memory VSTREAM: hello world
+final memory VSTREAM read offset: 11/11
+seeking to offset 12 should work: PASS
+VSTREAM_GETC should return VSTREAM_EOF
+PASS
+final memory VSTREAM read offset: 12/11
+VSTRING content length: 11/16, content: hello world
+
diff --git a/src/util/vstream_tweak.c b/src/util/vstream_tweak.c
new file mode 100644
index 0000000..7100bc6
--- /dev/null
+++ b/src/util/vstream_tweak.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* vstream_tweak 3
+/* SUMMARY
+/* performance tweaks
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_tweak_sock(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_tweak_tcp(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* vstream_tweak_sock() does a best effort to boost your
+/* network performance on the specified generic stream.
+/*
+/* vstream_tweak_tcp() does a best effort to boost your
+/* Internet performance on the specified TCP stream.
+/*
+/* Arguments:
+/* .IP stream
+/* The stream being boosted.
+/* DIAGNOSTICS
+/* Panics: interface violations.
+/* 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>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Application-specific. */
+
+#ifdef HAS_IPV6
+#define SOCKADDR_STORAGE struct sockaddr_storage
+#else
+#define SOCKADDR_STORAGE struct sockaddr
+#endif
+
+/* vstream_tweak_sock - boost your generic network performance */
+
+int vstream_tweak_sock(VSTREAM *fp)
+{
+ SOCKADDR_STORAGE ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE sa_length = sizeof(ss);
+ int ret;
+
+ /*
+ * If the caller doesn't know if this socket is AF_LOCAL, AF_INET, etc.,
+ * figure it out for them.
+ */
+ if ((ret = getsockname(vstream_fileno(fp), sa, &sa_length)) >= 0) {
+ switch (sa->sa_family) {
+#ifdef AF_INET6
+ case AF_INET6:
+#endif
+ case AF_INET:
+ ret = vstream_tweak_tcp(fp);
+ break;
+ }
+ }
+ return (ret);
+}
+
+/* vstream_tweak_tcp - boost your TCP performance */
+
+int vstream_tweak_tcp(VSTREAM *fp)
+{
+ const char *myname = "vstream_tweak_tcp";
+ int mss = 0;
+ SOCKOPT_SIZE mss_len = sizeof(mss);
+ int err;
+
+ /*
+ * Avoid Nagle delays when VSTREAM buffers are smaller than the MSS.
+ *
+ * Forcing TCP_NODELAY to be "always on" would hurt performance in the
+ * common case where VSTREAM buffers are larger than the MSS.
+ *
+ * Instead we ask the kernel what the current MSS is, and take appropriate
+ * action. Linux <= 2.2 getsockopt(TCP_MAXSEG) always returns zero (or
+ * whatever value was stored last with setsockopt()).
+ *
+ * Some ancient FreeBSD kernels don't report 'host unreachable' errors with
+ * getsockopt(SO_ERROR), and then treat getsockopt(TCP_MAXSEG) as a NOOP,
+ * leaving the mss parameter value unchanged. To work around these two
+ * getsockopt() bugs we set mss = 0, which is a harmless value.
+ */
+ if ((err = getsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_MAXSEG,
+ (void *) &mss, &mss_len)) < 0
+ && errno != ECONNRESET) {
+ msg_warn("%s: getsockopt TCP_MAXSEG: %m", myname);
+ return (err);
+ }
+ if (msg_verbose)
+ msg_info("%s: TCP_MAXSEG %d", myname, mss);
+
+ /*
+ * Fix for recent Postfix versions: increase the VSTREAM buffer size if
+ * it is smaller than the MSS. Note: the MSS may change when the route
+ * changes and IP path MTU discovery is turned on, so we choose a
+ * somewhat larger buffer.
+ *
+ * Note: as of 20120527, the CA_VSTREAM_CTL_BUFSIZE request can reduce the
+ * stream buffer size to less than VSTREAM_BUFSIZE, when the request is
+ * made before the first stream read or write operation. We don't want to
+ * reduce the buffer size.
+ *
+ * As of 20190820 we increase the mss size multiplier from 2x to 4x, because
+ * some LINUX loopback TCP stacks report an MSS of 21845 which is 3x
+ * smaller than the MTU of 65536. Even with a VSTREAM buffer 2x the
+ * reported MSS size, performance would suck due to Nagle or delayed ACK
+ * delays.
+ */
+#define EFF_BUFFER_SIZE(fp) (vstream_req_bufsize(fp) ? \
+ vstream_req_bufsize(fp) : VSTREAM_BUFSIZE)
+
+#ifdef CA_VSTREAM_CTL_BUFSIZE
+ if (mss > EFF_BUFFER_SIZE(fp) / 4) {
+ if (mss < INT_MAX / 2)
+ mss *= 2;
+ if (mss < INT_MAX / 2)
+ mss *= 2;
+ vstream_control(fp,
+ CA_VSTREAM_CTL_BUFSIZE(mss),
+ CA_VSTREAM_CTL_END);
+ }
+
+ /*
+ * Workaround for older Postfix versions: turn on TCP_NODELAY if the
+ * VSTREAM buffer size is smaller than the MSS.
+ */
+#else
+ if (mss > VSTREAM_BUFSIZE) {
+ int nodelay = 1;
+
+ if ((err = setsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_NODELAY,
+ (void *) &nodelay, sizeof(nodelay))) < 0
+ && errno != ECONNRESET)
+ msg_warn("%s: setsockopt TCP_NODELAY: %m", myname);
+ }
+#endif
+ return (err);
+}
diff --git a/src/util/vstring.c b/src/util/vstring.c
new file mode 100644
index 0000000..43897eb
--- /dev/null
+++ b/src/util/vstring.c
@@ -0,0 +1,719 @@
+/*++
+/* NAME
+/* vstring 3
+/* SUMMARY
+/* arbitrary-length string manager
+/* SYNOPSIS
+/* #include <vstring.h>
+/*
+/* VSTRING *vstring_alloc(len)
+/* ssize_t len;
+/*
+/* vstring_ctl(vp, type, value, ..., VSTRING_CTL_END)
+/* VSTRING *vp;
+/* int type;
+/*
+/* VSTRING *vstring_free(vp)
+/* VSTRING *vp;
+/*
+/* char *vstring_str(vp)
+/* VSTRING *vp;
+/*
+/* ssize_t VSTRING_LEN(vp)
+/* VSTRING *vp;
+/*
+/* char *vstring_end(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_ADDCH(vp, ch)
+/* VSTRING *vp;
+/* int ch;
+/*
+/* int VSTRING_SPACE(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* ssize_t vstring_avail(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_truncate(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_set_payload_size(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* void VSTRING_RESET(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_TERMINATE(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_SKIP(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_strcpy(vp, src)
+/* VSTRING *vp;
+/* const char *src;
+/*
+/* VSTRING *vstring_strncpy(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_strcat(vp, src)
+/* VSTRING *vp;
+/* const char *src;
+/*
+/* VSTRING *vstring_strncat(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_memcpy(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_memcat(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* char *vstring_memchr(vp, ch)
+/* VSTRING *vp;
+/* int ch;
+/*
+/* VSTRING *vstring_insert(vp, start, src, len)
+/* VSTRING *vp;
+/* ssize_t start;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_prepend(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_sprintf(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_sprintf_append(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_sprintf_prepend(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_vsprintf(vp, format, ap)
+/* VSTRING *vp;
+/* const char *format;
+/* va_list ap;
+/*
+/* VSTRING *vstring_vsprintf_append(vp, format, ap)
+/* VSTRING *vp;
+/* const char *format;
+/* va_list ap;
+/* AUXILIARY FUNCTIONS
+/* char *vstring_export(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_import(str)
+/* char *str;
+/* DESCRIPTION
+/* The functions and macros in this module implement arbitrary-length
+/* strings and common operations on those strings. The strings do not
+/* need to be null terminated and may contain arbitrary binary data.
+/* The strings manage their own memory and grow automatically when full.
+/* The optional string null terminator does not add to the string length.
+/*
+/* vstring_alloc() allocates storage for a variable-length string
+/* of at least "len" bytes. The minimal length is 1. The result
+/* is a null-terminated string of length zero.
+/*
+/* vstring_ctl() gives additional control over VSTRING behavior.
+/* The function takes a VSTRING pointer and a list of zero or
+/* more macros with zer or more arguments, terminated with
+/* CA_VSTRING_CTL_END which has none.
+/* .IP "CA_VSTRING_CTL_MAXLEN(ssize_t len)"
+/* Specifies a hard upper limit on a string's length. When the
+/* length would be exceeded, the program simulates a memory
+/* allocation problem (i.e. it terminates through msg_fatal()).
+/* This functionality is currently unimplemented.
+/* .IP "CA_VSTRING_CTL_EXACT (no argument)"
+/* Allocate the requested amounts, instead of rounding up.
+/* This should be used for tests only.
+/* .IP "CA_VSTRING_CTL_END (no argument)"
+/* Specifies the end of the argument list. Forgetting to terminate
+/* the argument list may cause the program to crash.
+/* .PP
+/* VSTRING_SPACE() ensures that the named string has room for
+/* "len" more characters. VSTRING_SPACE() is an unsafe macro
+/* that either returns zero or never returns.
+/*
+/* vstring_avail() returns the number of bytes that can be placed
+/* into the buffer before the buffer would need to grow.
+/*
+/* vstring_free() reclaims storage for a variable-length string.
+/* It conveniently returns a null pointer.
+/*
+/* vstring_str() is a macro that returns the string value
+/* of a variable-length string. It is a safe macro that
+/* evaluates its argument only once.
+/*
+/* VSTRING_LEN() is a macro that returns the current length of
+/* its argument (i.e. the distance from the start of the string
+/* to the current write position). VSTRING_LEN() is an unsafe macro
+/* that evaluates its argument more than once.
+/*
+/* vstring_end() is a macro that returns the current write position of
+/* its argument. It is a safe macro that evaluates its argument only once.
+/*
+/* VSTRING_ADDCH() adds a character to a variable-length string
+/* and extends the string if it fills up. \fIvs\fP is a pointer
+/* to a VSTRING structure; \fIch\fP the character value to be written.
+/* The result is the written character.
+/* Note that VSTRING_ADDCH() is an unsafe macro that evaluates some
+/* arguments more than once. The result is NOT null-terminated.
+/*
+/* vstring_truncate() truncates the named string to the specified
+/* length. If length is negative, the trailing portion is kept.
+/* The operation has no effect when the string is shorter.
+/* The string is not null-terminated.
+/*
+/* vstring_set_payload_size() sets the number of 'used' bytes
+/* in the named buffer's metadata. This determines the buffer
+/* write position and the VSTRING_LEN() result. The payload
+/* size must be within the closed range [0, number of allocated
+/* bytes]. The typical usage is to request buffer space with
+/* VSTRING_SPACE(), to use some non-VSTRING operations to write
+/* to the buffer, and to call vstring_set_payload_size() to
+/* update buffer metadata, perhaps followed by VSTRING_TERMINATE().
+/*
+/* VSTRING_RESET() is a macro that resets the write position of its
+/* string argument to the very beginning. Note that VSTRING_RESET()
+/* is an unsafe macro that evaluates some arguments more than once.
+/* The result is NOT null-terminated.
+/*
+/* VSTRING_TERMINATE() null-terminates its string argument.
+/* VSTRING_TERMINATE() is an unsafe macro that evaluates some
+/* arguments more than once.
+/* VSTRING_TERMINATE() does not return an interesting result.
+/*
+/* VSTRING_SKIP() is a macro that moves the write position to the first
+/* null byte after the current write position. VSTRING_SKIP() is an unsafe
+/* macro that evaluates some arguments more than once.
+/*
+/* vstring_strcpy() copies a null-terminated string to a variable-length
+/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is null-terminated.
+/*
+/* vstring_strncpy() copies at most \fIlen\fR characters. Otherwise it is
+/* identical to vstring_strcpy().
+/*
+/* vstring_strcat() appends a null-terminated string to a variable-length
+/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is null-terminated.
+/*
+/* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is
+/* identical to vstring_strcat().
+/*
+/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
+/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
+/* vstring_memchr() locates a byte in a variable-length string.
+/*
+/* vstring_insert() inserts a buffer content into a variable-length
+/* string at the specified start position. The result is
+/* null-terminated.
+/*
+/* vstring_prepend() prepends a buffer content to a variable-length
+/* string. The result is null-terminated.
+/*
+/* vstring_sprintf() produces a formatted string according to its
+/* \fIformat\fR argument. See vstring_vsprintf() for details.
+/*
+/* vstring_sprintf_append() is like vstring_sprintf(), but appends
+/* to the end of the result buffer.
+/*
+/* vstring_sprintf_append() is like vstring_sprintf(), but prepends
+/* to the beginning of the result buffer.
+/*
+/* vstring_vsprintf() returns a null-terminated string according to
+/* the \fIformat\fR argument. It understands the s, c, d, u,
+/* o, x, X, p, e, f and g format types, the l modifier, field width
+/* and precision, sign, and null or space padding. This module
+/* can format strings as large as available memory permits.
+/*
+/* vstring_vsprintf_append() is like vstring_vsprintf(), but appends
+/* to the end of the result buffer.
+/*
+/* In addition to stdio-like format specifiers, vstring_vsprintf()
+/* recognizes %m and expands it to the corresponding errno text.
+/*
+/* vstring_export() extracts the string value from a VSTRING.
+/* The VSTRING is destroyed. The result should be passed to myfree().
+/*
+/* vstring_import() takes a `bare' string and converts it to
+/* a VSTRING. The string argument must be obtained from mymalloc().
+/* The string argument is not copied.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure.
+/* BUGS
+/* Auto-resizing may change the address of the string data in
+/* a vstring structure. Beware of dangling pointers.
+/* HISTORY
+/* .ad
+/* .fi
+/* A vstring module appears in the UNPROTO software by Wietse Venema.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+#include <stddef.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#define VSTRING_INTERNAL
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "vbuf_print.h"
+#include "vstring.h"
+
+/* vstring_extend - variable-length string buffer extension policy */
+
+static void vstring_extend(VBUF *bp, ssize_t incr)
+{
+ size_t used = bp->ptr - bp->data;
+ ssize_t new_len;
+
+ /*
+ * Note: vp->vbuf.len is the current buffer size (both on entry and on
+ * exit of this routine). We round up the increment size to the buffer
+ * size to avoid silly little buffer increments. With really large
+ * strings we might want to abandon the length doubling strategy, and go
+ * to fixed increments.
+ *
+ * The length overflow tests here and in vstring_alloc() should protect us
+ * against all length overflow problems within vstring library routines.
+ *
+ * Safety net: add a gratuitous null terminator so that C-style string
+ * operations won't scribble past the end.
+ */
+ if ((bp->flags & VSTRING_FLAG_EXACT) == 0 && bp->len > incr)
+ incr = bp->len;
+ if (bp->len > SSIZE_T_MAX - incr - 1)
+ msg_fatal("vstring_extend: length overflow");
+ new_len = bp->len + incr;
+ bp->data = (unsigned char *) myrealloc((void *) bp->data, new_len + 1);
+ bp->data[new_len] = 0;
+ bp->len = new_len;
+ bp->ptr = bp->data + used;
+ bp->cnt = bp->len - used;
+}
+
+/* vstring_buf_get_ready - vbuf callback for read buffer empty condition */
+
+static int vstring_buf_get_ready(VBUF *unused_buf)
+{
+ return (VBUF_EOF); /* be VSTREAM-friendly */
+}
+
+/* vstring_buf_put_ready - vbuf callback for write buffer full condition */
+
+static int vstring_buf_put_ready(VBUF *bp)
+{
+ vstring_extend(bp, 1);
+ return (0);
+}
+
+/* vstring_buf_space - vbuf callback to reserve space */
+
+static int vstring_buf_space(VBUF *bp, ssize_t len)
+{
+ ssize_t need;
+
+ if (len < 0)
+ msg_panic("vstring_buf_space: bad length %ld", (long) len);
+ if ((need = len - bp->cnt) > 0)
+ vstring_extend(bp, need);
+ return (0);
+}
+
+/* vstring_alloc - create variable-length string */
+
+VSTRING *vstring_alloc(ssize_t len)
+{
+ VSTRING *vp;
+
+ /*
+ * Safety net: add a gratuitous null terminator so that C-style string
+ * operations won't scribble past the end.
+ */
+ if (len < 1 || len > SSIZE_T_MAX - 1)
+ msg_panic("vstring_alloc: bad length %ld", (long) len);
+ vp = (VSTRING *) mymalloc(sizeof(*vp));
+ vp->vbuf.flags = 0;
+ vp->vbuf.len = 0;
+ vp->vbuf.data = (unsigned char *) mymalloc(len + 1);
+ vp->vbuf.data[len] = 0;
+ vp->vbuf.len = len;
+ VSTRING_RESET(vp);
+ vp->vbuf.data[0] = 0;
+ vp->vbuf.get_ready = vstring_buf_get_ready;
+ vp->vbuf.put_ready = vstring_buf_put_ready;
+ vp->vbuf.space = vstring_buf_space;
+ return (vp);
+}
+
+/* vstring_free - destroy variable-length string */
+
+VSTRING *vstring_free(VSTRING *vp)
+{
+ if (vp->vbuf.data)
+ myfree((void *) vp->vbuf.data);
+ myfree((void *) vp);
+ return (0);
+}
+
+/* vstring_ctl - modify memory management policy */
+
+void vstring_ctl(VSTRING *vp,...)
+{
+ va_list ap;
+ int code;
+
+ va_start(ap, vp);
+ while ((code = va_arg(ap, int)) != VSTRING_CTL_END) {
+ switch (code) {
+ default:
+ msg_panic("vstring_ctl: unknown code: %d", code);
+ case VSTRING_CTL_EXACT:
+ vp->vbuf.flags |= VSTRING_FLAG_EXACT;
+ break;
+ }
+ }
+ va_end(ap);
+}
+
+/* vstring_truncate - truncate string */
+
+VSTRING *vstring_truncate(VSTRING *vp, ssize_t len)
+{
+ ssize_t move;
+
+ if (len < 0) {
+ len = (-len);
+ if ((move = VSTRING_LEN(vp) - len) > 0)
+ memmove(vstring_str(vp), vstring_str(vp) + move, len);
+ }
+ if (len < VSTRING_LEN(vp))
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_set_payload_size - public version of VSTRING_AT_OFFSET */
+
+VSTRING *vstring_set_payload_size(VSTRING *vp, ssize_t len)
+{
+ if (len < 0 || len > vp->vbuf.len)
+ msg_panic("vstring_set_payload_size: invalid offset: %ld", (long) len);
+ if (vp->vbuf.data[vp->vbuf.len] != 0)
+ msg_panic("vstring_set_payload_size: no safety null byte");
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_strcpy - copy string */
+
+VSTRING *vstring_strcpy(VSTRING *vp, const char *src)
+{
+ VSTRING_RESET(vp);
+
+ while (*src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strncpy - copy string of limited length */
+
+VSTRING *vstring_strncpy(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_RESET(vp);
+
+ while (len-- > 0 && *src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strcat - append string */
+
+VSTRING *vstring_strcat(VSTRING *vp, const char *src)
+{
+ while (*src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strncat - append string of limited length */
+
+VSTRING *vstring_strncat(VSTRING *vp, const char *src, ssize_t len)
+{
+ while (len-- > 0 && *src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_memcpy - copy buffer of limited length */
+
+VSTRING *vstring_memcpy(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_RESET(vp);
+
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_str(vp), src, len);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_memcat - append buffer of limited length */
+
+VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_end(vp), src, len);
+ len += VSTRING_LEN(vp);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_memchr - locate byte in buffer */
+
+char *vstring_memchr(VSTRING *vp, int ch)
+{
+ unsigned char *cp;
+
+ for (cp = (unsigned char *) vstring_str(vp); cp < (unsigned char *) vstring_end(vp); cp++)
+ if (*cp == ch)
+ return ((char *) cp);
+ return (0);
+}
+
+/* vstring_insert - insert text into string */
+
+VSTRING *vstring_insert(VSTRING *vp, ssize_t start, const char *buf, ssize_t len)
+{
+ ssize_t new_len;
+
+ /*
+ * Sanity check.
+ */
+ if (start < 0 || start >= VSTRING_LEN(vp))
+ msg_panic("vstring_insert: bad start %ld", (long) start);
+ if (len < 0)
+ msg_panic("vstring_insert: bad length %ld", (long) len);
+
+ /*
+ * Move the existing content and copy the new content.
+ */
+ new_len = VSTRING_LEN(vp) + len;
+ VSTRING_SPACE(vp, len);
+ memmove(vstring_str(vp) + start + len, vstring_str(vp) + start,
+ VSTRING_LEN(vp) - start);
+ memcpy(vstring_str(vp) + start, buf, len);
+ VSTRING_AT_OFFSET(vp, new_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_prepend - prepend text to string */
+
+VSTRING *vstring_prepend(VSTRING *vp, const char *buf, ssize_t len)
+{
+ ssize_t new_len;
+
+ /*
+ * Sanity check.
+ */
+ if (len < 0)
+ msg_panic("vstring_prepend: bad length %ld", (long) len);
+
+ /*
+ * Move the existing content and copy the new content.
+ */
+ new_len = VSTRING_LEN(vp) + len;
+ VSTRING_SPACE(vp, len);
+ memmove(vstring_str(vp) + len, vstring_str(vp), VSTRING_LEN(vp));
+ memcpy(vstring_str(vp), buf, len);
+ VSTRING_AT_OFFSET(vp, new_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_export - VSTRING to bare string */
+
+char *vstring_export(VSTRING *vp)
+{
+ char *cp;
+
+ cp = (char *) vp->vbuf.data;
+ vp->vbuf.data = 0;
+ myfree((void *) vp);
+ return (cp);
+}
+
+/* vstring_import - bare string to vstring */
+
+VSTRING *vstring_import(char *str)
+{
+ VSTRING *vp;
+ ssize_t len;
+
+ vp = (VSTRING *) mymalloc(sizeof(*vp));
+ len = strlen(str);
+ vp->vbuf.flags = 0;
+ vp->vbuf.len = 0;
+ vp->vbuf.data = (unsigned char *) str;
+ vp->vbuf.len = len + 1;
+ VSTRING_AT_OFFSET(vp, len);
+ vp->vbuf.get_ready = vstring_buf_get_ready;
+ vp->vbuf.put_ready = vstring_buf_put_ready;
+ vp->vbuf.space = vstring_buf_space;
+ return (vp);
+}
+
+/* vstring_sprintf - formatted string */
+
+VSTRING *vstring_sprintf(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vp = vstring_vsprintf(vp, format, ap);
+ va_end(ap);
+ return (vp);
+}
+
+/* vstring_vsprintf - format string, vsprintf-like interface */
+
+VSTRING *vstring_vsprintf(VSTRING *vp, const char *format, va_list ap)
+{
+ VSTRING_RESET(vp);
+ vbuf_print(&vp->vbuf, format, ap);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_sprintf_append - append formatted string */
+
+VSTRING *vstring_sprintf_append(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vp = vstring_vsprintf_append(vp, format, ap);
+ va_end(ap);
+ return (vp);
+}
+
+/* vstring_vsprintf_append - format + append string, vsprintf-like interface */
+
+VSTRING *vstring_vsprintf_append(VSTRING *vp, const char *format, va_list ap)
+{
+ vbuf_print(&vp->vbuf, format, ap);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_sprintf_prepend - format + prepend string, vsprintf-like interface */
+
+VSTRING *vstring_sprintf_prepend(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+ ssize_t old_len = VSTRING_LEN(vp);
+ ssize_t result_len;
+
+ /* Construct: old|new|free */
+ va_start(ap, format);
+ vp = vstring_vsprintf_append(vp, format, ap);
+ va_end(ap);
+ result_len = VSTRING_LEN(vp);
+
+ /* Construct: old|new|old|free */
+ VSTRING_SPACE(vp, old_len);
+ vstring_memcat(vp, vstring_str(vp), old_len);
+
+ /* Construct: new|old|free */
+ memmove(vstring_str(vp), vstring_str(vp) + old_len, result_len);
+ VSTRING_AT_OFFSET(vp, result_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - concatenate all command-line arguments into one string.
+ */
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(1);
+ int n;
+
+ /*
+ * Report the location of the gratuitous null terminator.
+ */
+ for (n = 1; n <= 5; n++) {
+ VSTRING_ADDCH(vp, 'x');
+ printf("payload/buffer size %d/%ld, strlen() %ld\n",
+ n, (long) (vp)->vbuf.len, (long) strlen(vstring_str(vp)));
+ }
+
+ VSTRING_RESET(vp);
+ while (argc-- > 0) {
+ vstring_strcat(vp, *argv++);
+ vstring_strcat(vp, ".");
+ }
+ printf("argv concatenated: %s\n", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vstring.h b/src/util/vstring.h
new file mode 100644
index 0000000..49fd960
--- /dev/null
+++ b/src/util/vstring.h
@@ -0,0 +1,125 @@
+#ifndef _VSTRING_H_INCLUDED_
+#define _VSTRING_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstring 3h
+/* SUMMARY
+/* arbitrary-length string manager
+/* SYNOPSIS
+/* #include "vstring.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+#include <check_arg.h>
+
+ /*
+ * We can't allow bare VBUFs in the interface, because VSTRINGs have a
+ * specific initialization and destruction sequence.
+ */
+typedef struct VSTRING {
+ VBUF vbuf;
+} VSTRING;
+
+extern VSTRING *vstring_alloc(ssize_t);
+extern void vstring_ctl(VSTRING *,...);
+extern VSTRING *vstring_truncate(VSTRING *, ssize_t);
+extern VSTRING *vstring_set_payload_size(VSTRING *, ssize_t);
+extern VSTRING *vstring_free(VSTRING *);
+extern VSTRING *vstring_strcpy(VSTRING *, const char *);
+extern VSTRING *vstring_strncpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_strcat(VSTRING *, const char *);
+extern VSTRING *vstring_strncat(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_memcpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_memcat(VSTRING *, const char *, ssize_t);
+extern char *vstring_memchr(VSTRING *, int);
+extern VSTRING *vstring_insert(VSTRING *, ssize_t, const char *, ssize_t);
+extern VSTRING *vstring_prepend(VSTRING *, const char *, ssize_t);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_prepend(VSTRING *, const char *,...);
+extern char *vstring_export(VSTRING *);
+extern VSTRING *vstring_import(char *);
+
+/* Legacy API: constant plus type-unchecked argument. */
+#define VSTRING_CTL_EXACT 2
+#define VSTRING_CTL_END 0
+
+/* Safer API: type-checked arguments. */
+#define CA_VSTRING_CTL_END VSTRING_CTL_END
+#define CA_VSTRING_CTL_EXACT VSTRING_CTL_EXACT
+
+CHECK_VAL_HELPER_DCL(VSTRING_CTL, ssize_t);
+
+/* Flags 24..31 are reserved for VSTRING. */
+#define VSTRING_FLAG_EXACT (1<<24) /* exact allocation for tests */
+#define VSTRING_FLAG_MASK (255 << 24)
+
+ /*
+ * Macros. Unsafe macros have UPPERCASE names.
+ */
+#define VSTRING_SPACE(vp, len) ((vp)->vbuf.space(&(vp)->vbuf, (len)))
+#define vstring_str(vp) ((char *) (vp)->vbuf.data)
+#define VSTRING_LEN(vp) ((ssize_t) ((vp)->vbuf.ptr - (vp)->vbuf.data))
+#define vstring_end(vp) ((char *) (vp)->vbuf.ptr)
+#define VSTRING_TERMINATE(vp) do { \
+ *(vp)->vbuf.ptr = 0; \
+ } while (0)
+#define VSTRING_RESET(vp) do { \
+ (vp)->vbuf.ptr = (vp)->vbuf.data; \
+ (vp)->vbuf.cnt = (vp)->vbuf.len; \
+ } while (0)
+#define VSTRING_ADDCH(vp, ch) VBUF_PUT(&(vp)->vbuf, ch)
+#define VSTRING_SKIP(vp) do { \
+ while ((vp)->vbuf.cnt > 0 && *(vp)->vbuf.ptr) \
+ (vp)->vbuf.ptr++, (vp)->vbuf.cnt--; \
+ } while (0)
+#define vstring_avail(vp) ((vp)->vbuf.cnt)
+
+ /*
+ * The following macro is not part of the public interface, because it can
+ * really screw up a buffer by positioning past allocated memory.
+ */
+#ifdef VSTRING_INTERNAL
+#define VSTRING_AT_OFFSET(vp, offset) do { \
+ (vp)->vbuf.ptr = (vp)->vbuf.data + (offset); \
+ (vp)->vbuf.cnt = (vp)->vbuf.len - (offset); \
+ } while (0)
+#endif
+
+extern VSTRING *vstring_vsprintf(VSTRING *, const char *, va_list);
+extern VSTRING *vstring_vsprintf_append(VSTRING *, const char *, va_list);
+
+/* BUGS
+/* Auto-resizing may change the address of the string data in
+/* a vstring structure. Beware of dangling pointers.
+/* HISTORY
+/* .ad
+/* .fi
+/* A vstring module appears in the UNPROTO software by Wietse Venema.
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/vstring_test.ref b/src/util/vstring_test.ref
new file mode 100644
index 0000000..54c54cf
--- /dev/null
+++ b/src/util/vstring_test.ref
@@ -0,0 +1,6 @@
+payload/buffer size 1/1, strlen() 1
+payload/buffer size 2/2, strlen() 2
+payload/buffer size 3/4, strlen() 4
+payload/buffer size 4/4, strlen() 4
+payload/buffer size 5/8, strlen() 8
+argv concatenated: ./vstring.one.two.three.
diff --git a/src/util/vstring_vstream.c b/src/util/vstring_vstream.c
new file mode 100644
index 0000000..451cc50
--- /dev/null
+++ b/src/util/vstring_vstream.c
@@ -0,0 +1,267 @@
+/*++
+/* NAME
+/* vstring_vstream 3
+/* SUMMARY
+/* auto-resizing string library, standard I/O interface
+/* SYNOPSIS
+/* #include <vstring_vstream.h>
+/*
+/* int vstring_get_flags(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_nonl(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_null(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_null_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/* CONVENIENCE API
+/* int vstring_get(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_nonl(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_null(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/*
+/* int vstring_get_nonl_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/*
+/* int vstring_get_null_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* DESCRIPTION
+/* The routines in this module each read one newline or null-terminated
+/* string from an input stream. In all cases the result is either the
+/* last character read, typically the record terminator, or VSTREAM_EOF.
+/* The flags argument is VSTRING_GET_FLAG_NONE (default) or
+/* VSTRING_GET_FLAG_APPEND (append instead of overwrite).
+/*
+/* vstring_get_flags() reads one line from the named stream, including the
+/* terminating newline character if present.
+/*
+/* vstring_get_flags_nonl() reads a line from the named stream and strips
+/* the trailing newline character.
+/*
+/* vstring_get_flags_null() reads a null-terminated string from the named
+/* stream.
+/*
+/* the vstring_get_flags<whatever>_bound() routines read no more
+/* than \fIbound\fR characters. Otherwise they behave like the
+/* unbounded versions documented above.
+/*
+/* The functions without _flags in their name accept the same
+/* arguments except flags. These functions use the default
+/* flags value.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure.
+/* Panic: improper string bound.
+/* 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 <stdio.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "vstream.h"
+#include "vstring_vstream.h"
+
+ /*
+ * Macro to return the last character added to a VSTRING, for consistency.
+ */
+#define VSTRING_GET_RESULT(vp, baselen) \
+ (VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF)
+
+/* vstring_get_flags - read line from file, keep newline */
+
+int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
+ VSTRING_ADDCH(vp, c);
+ if (c == '\n')
+ break;
+ }
+ VSTRING_TERMINATE(vp);
+ return (VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_nonl - read line from file, strip newline */
+
+int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_null - read null-terminated string from file */
+
+int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_bound - read line from file, keep newline, up to bound */
+
+int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
+ VSTRING_ADDCH(vp, c);
+ if (c == '\n')
+ break;
+ }
+ VSTRING_TERMINATE(vp);
+ return (VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */
+
+int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_null_bound - read null-terminated string from file */
+
+int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: copy the source to this module to stdout.
+ */
+#include <fcntl.h>
+
+#define TEXT_VSTREAM "vstring_vstream.c"
+
+int main(void)
+{
+ VSTRING *vp = vstring_alloc(1);
+ VSTREAM *fp;
+
+ if ((fp = vstream_fopen(TEXT_VSTREAM, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", TEXT_VSTREAM);
+ while (vstring_fgets(vp, fp))
+ vstream_fprintf(VSTREAM_OUT, "%s", vstring_str(vp));
+ vstream_fclose(fp);
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vstring_vstream.h b/src/util/vstring_vstream.h
new file mode 100644
index 0000000..e0dc801
--- /dev/null
+++ b/src/util/vstring_vstream.h
@@ -0,0 +1,82 @@
+#ifndef _VSTRING_VSTREAM_H_INCLUDED_
+#define _VSTRING_VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstring_vstream 3h
+/* SUMMARY
+/* auto-resizing string library
+/* SYNOPSIS
+/* #include <vstring_vstream.h>
+/* DESCRIPTION
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define VSTRING_GET_FLAG_NONE (0)
+#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */
+
+extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t);
+
+ /*
+ * Convenience aliases for most use cases.
+ */
+#define vstring_get(string, stream) \
+ vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_nonl(string, stream) \
+ vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_null(string, stream) \
+ vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE)
+
+#define vstring_get_bound(string, stream, size) \
+ vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_nonl_bound(string, stream, size) \
+ vstring_get_flags_nonl_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_null_bound(string, stream, size) \
+ vstring_get_flags_null_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
+
+ /*
+ * Backwards compatibility for code that still uses the vstring_fgets()
+ * interface. Unfortunately we can't change the macro name to upper case.
+ */
+#define vstring_fgets(s, p) \
+ (vstring_get((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_nonl(s, p) \
+ (vstring_get_nonl((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_null(s, p) \
+ (vstring_get_null((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_bound(s, p, l) \
+ (vstring_get_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_nonl_bound(s, p, l) \
+ (vstring_get_nonl_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s))
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/warn_stat.c b/src/util/warn_stat.c
new file mode 100644
index 0000000..fd885d9
--- /dev/null
+++ b/src/util/warn_stat.c
@@ -0,0 +1,101 @@
+/*++
+/* NAME
+/* warn_stat 3
+/* SUMMARY
+/* baby-sit stat() error returns
+/* SYNOPSIS
+/* #include <warn_stat.h>
+/*
+/* int warn_stat(path, st)
+/* const char *path;
+/* struct stat *st;
+/*
+/* int warn_lstat(path, st)
+/* const char *path;
+/* struct stat *st;
+/*
+/* int warn_fstat(fd, st)
+/* int fd;
+/* struct stat *st;
+/* DESCRIPTION
+/* warn_stat(), warn_fstat() and warn_lstat() wrap the stat(),
+/* fstat() and lstat() system calls with code that logs a
+/* diagnosis for common error cases.
+/* 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>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#define WARN_STAT_INTERNAL
+#include <warn_stat.h>
+
+/* diagnose_stat - log stat warning */
+
+static void diagnose_stat(void)
+{
+ struct stat st;
+
+ /*
+ * When *stat() fails with EOVERFLOW, and the interface uses 32-bit data
+ * types, suggest that the program be recompiled with larger data types.
+ */
+#ifdef EOVERFLOW
+ if (errno == EOVERFLOW && sizeof(st.st_size) == 4) {
+ msg_warn("this program was built for 32-bit file handles, "
+ "but some number does not fit in 32 bits");
+ msg_warn("possible solution: recompile in 64-bit mode, or "
+ "recompile in 32-bit mode with 'large file' support");
+ }
+#endif
+}
+
+/* warn_stat - stat with warning */
+
+int warn_stat(const char *path, struct stat * st)
+{
+ int ret;
+
+ ret = stat(path, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
+
+/* warn_lstat - lstat with warning */
+
+int warn_lstat(const char *path, struct stat * st)
+{
+ int ret;
+
+ ret = lstat(path, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
+
+/* warn_fstat - fstat with warning */
+
+int warn_fstat(int fd, struct stat * st)
+{
+ int ret;
+
+ ret = fstat(fd, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
diff --git a/src/util/warn_stat.h b/src/util/warn_stat.h
new file mode 100644
index 0000000..92ddce0
--- /dev/null
+++ b/src/util/warn_stat.h
@@ -0,0 +1,38 @@
+#ifndef _WARN_STAT_H_
+#define _WARN_STAT_H_
+
+/*++
+/* NAME
+/* warn_stat 3h
+/* SUMMARY
+/* baby-sit stat() error returns
+/* SYNOPSIS
+/* #include <warn_stat.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef WARN_STAT_INTERNAL
+#define stat(p, s) warn_stat((p), (s))
+#define lstat(p, s) warn_lstat((p), (s))
+#define fstat(f, s) warn_fstat((f), (s))
+#endif
+
+extern int warn_stat(const char *path, struct stat *);
+extern int warn_lstat(const char *path, struct stat *);
+extern int warn_fstat(int, struct stat *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/watchdog.c b/src/util/watchdog.c
new file mode 100644
index 0000000..3ec1fbc
--- /dev/null
+++ b/src/util/watchdog.c
@@ -0,0 +1,314 @@
+/*++
+/* NAME
+/* watchdog 3
+/* SUMMARY
+/* watchdog timer
+/* SYNOPSIS
+/* #include <watchdog.h>
+/*
+/* WATCHDOG *watchdog_create(timeout, action, context)
+/* unsigned timeout;
+/* void (*action)(WATCHDOG *watchdog, char *context);
+/* char *context;
+/*
+/* void watchdog_start(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_stop(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_destroy(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_pat()
+/* DESCRIPTION
+/* This module implements watchdog timers that are based on ugly
+/* UNIX alarm timers. The module is designed to survive systems
+/* with clocks that jump occasionally.
+/*
+/* Watchdog timers can be stacked. Only one watchdog timer can be
+/* active at a time. Only the last created watchdog timer can be
+/* manipulated. Watchdog timers must be destroyed in reverse order
+/* of creation.
+/*
+/* watchdog_create() suspends the current watchdog timer, if any,
+/* and instantiates a new watchdog timer.
+/*
+/* watchdog_start() starts or restarts the watchdog timer.
+/*
+/* watchdog_stop() stops the watchdog timer.
+/*
+/* watchdog_destroy() stops the watchdog timer, and resumes the
+/* watchdog timer instance that was suspended by watchdog_create().
+/*
+/* watchdog_pat() pats the watchdog, so it stays quiet.
+/*
+/* Arguments:
+/* .IP timeout
+/* The watchdog time limit. When the watchdog timer runs, the
+/* process must invoke watchdog_start(), watchdog_stop() or
+/* watchdog_destroy() before the time limit is reached.
+/* .IP action
+/* A null pointer, or pointer to function that is called when the
+/* watchdog alarm goes off. The default action is to terminate
+/* the process with a fatal error.
+/* .IP context
+/* Application context that is passed to the action routine.
+/* .IP watchdog
+/* Must be a pointer to the most recently created watchdog instance.
+/* This argument is checked upon each call.
+/* BUGS
+/* UNIX alarm timers are not stackable, so there can be at most one
+/* watchdog instance active at any given time.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, system call failure.
+/* Panics: interface violations.
+/* 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 <unistd.h>
+#include <signal.h>
+#include <posix_signals.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <killme_after.h>
+#include <watchdog.h>
+
+/* Application-specific. */
+
+ /*
+ * Rather than having one timer that goes off when it is too late, we break
+ * up the time limit into smaller intervals so that we can deal with clocks
+ * that jump occasionally.
+ */
+#define WATCHDOG_STEPS 3
+
+ /*
+ * UNIX alarms are not stackable, but we can save and restore state, so that
+ * watchdogs can at least be nested, sort of.
+ */
+struct WATCHDOG {
+ unsigned timeout; /* our time resolution */
+ WATCHDOG_FN action; /* application routine */
+ char *context; /* application context */
+ int trip_run; /* number of successive timeouts */
+ WATCHDOG *saved_watchdog; /* saved state */
+ struct sigaction saved_action; /* saved state */
+ unsigned saved_time; /* saved state */
+};
+
+ /*
+ * However, only one watchdog instance can be current, and the caller has to
+ * restore state before a prior watchdog instance can be manipulated.
+ */
+static WATCHDOG *watchdog_curr;
+
+ /*
+ * Workaround for systems where the alarm signal does not wakeup the event
+ * machinery, and therefore does not restart the watchdog timer in the
+ * single_server etc. skeletons. The symptom is that programs abort when the
+ * watchdog timeout is less than the max_idle time.
+ */
+#ifdef USE_WATCHDOG_PIPE
+#include <errno.h>
+#include <iostuff.h>
+#include <events.h>
+
+static int watchdog_pipe[2];
+
+/* watchdog_read - read event pipe */
+
+static void watchdog_read(int unused_event, void *unused_context)
+{
+ char ch;
+
+ while (read(watchdog_pipe[0], &ch, 1) > 0)
+ /* void */ ;
+}
+
+#endif /* USE_WATCHDOG_PIPE */
+
+/* watchdog_event - handle timeout event */
+
+static void watchdog_event(int unused_sig)
+{
+ const char *myname = "watchdog_event";
+ WATCHDOG *wp;
+
+ /*
+ * This routine runs as a signal handler. We should not do anything that
+ * could involve memory allocation/deallocation, but exiting without
+ * proper explanation would be unacceptable. For this reason, msg(3) was
+ * made safe for usage by signal handlers that terminate the process.
+ */
+ if ((wp = watchdog_curr) == 0)
+ msg_panic("%s: no instance", myname);
+ if (msg_verbose > 1)
+ msg_info("%s: %p %d", myname, (void *) wp, wp->trip_run);
+ if (++(wp->trip_run) < WATCHDOG_STEPS) {
+#ifdef USE_WATCHDOG_PIPE
+ int saved_errno = errno;
+
+ /* Wake up the events(3) engine. */
+ if (write(watchdog_pipe[1], "", 1) != 1)
+ msg_warn("%s: write watchdog_pipe: %m", myname);
+ errno = saved_errno;
+#endif
+ alarm(wp->timeout);
+ } else {
+ if (wp->action)
+ wp->action(wp, wp->context);
+ else {
+ killme_after(5);
+#ifdef TEST
+ pause();
+#endif
+ msg_fatal("watchdog timeout");
+ }
+ }
+}
+
+/* watchdog_create - create watchdog instance */
+
+WATCHDOG *watchdog_create(unsigned timeout, WATCHDOG_FN action, char *context)
+{
+ const char *myname = "watchdog_create";
+ struct sigaction sig_action;
+ WATCHDOG *wp;
+
+ wp = (WATCHDOG *) mymalloc(sizeof(*wp));
+ if ((wp->timeout = timeout / WATCHDOG_STEPS) == 0)
+ msg_panic("%s: timeout %d is too small", myname, timeout);
+ wp->action = action;
+ wp->context = context;
+ wp->saved_watchdog = watchdog_curr;
+ wp->saved_time = alarm(0);
+ sigemptyset(&sig_action.sa_mask);
+#ifdef SA_RESTART
+ sig_action.sa_flags = SA_RESTART;
+#else
+ sig_action.sa_flags = 0;
+#endif
+ sig_action.sa_handler = watchdog_event;
+ if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (msg_verbose > 1)
+ msg_info("%s: %p %d", myname, (void *) wp, timeout);
+#ifdef USE_WATCHDOG_PIPE
+ if (watchdog_curr == 0) {
+ if (pipe(watchdog_pipe) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ non_blocking(watchdog_pipe[0], NON_BLOCKING);
+ non_blocking(watchdog_pipe[1], NON_BLOCKING);
+ close_on_exec(watchdog_pipe[0], CLOSE_ON_EXEC); /* Fix 20190126 */
+ close_on_exec(watchdog_pipe[1], CLOSE_ON_EXEC); /* Fix 20190126 */
+ event_enable_read(watchdog_pipe[0], watchdog_read, (void *) 0);
+ }
+#endif
+ return (watchdog_curr = wp);
+}
+
+/* watchdog_destroy - destroy watchdog instance, restore state */
+
+void watchdog_destroy(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_destroy";
+
+ watchdog_stop(wp);
+ watchdog_curr = wp->saved_watchdog;
+ if (sigaction(SIGALRM, &wp->saved_action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (wp->saved_time)
+ alarm(wp->saved_time);
+ myfree((void *) wp);
+#ifdef USE_WATCHDOG_PIPE
+ if (watchdog_curr == 0) {
+ event_disable_readwrite(watchdog_pipe[0]);
+ (void) close(watchdog_pipe[0]);
+ (void) close(watchdog_pipe[1]);
+ }
+#endif
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_start - enable watchdog timer */
+
+void watchdog_start(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_start";
+
+ if (wp != watchdog_curr)
+ msg_panic("%s: wrong watchdog instance", myname);
+ wp->trip_run = 0;
+ alarm(wp->timeout);
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_stop - disable watchdog timer */
+
+void watchdog_stop(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_stop";
+
+ if (wp != watchdog_curr)
+ msg_panic("%s: wrong watchdog instance", myname);
+ alarm(0);
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_pat - pat the dog so it stays quiet */
+
+void watchdog_pat(void)
+{
+ const char *myname = "watchdog_pat";
+
+ if (watchdog_curr)
+ watchdog_curr->trip_run = 0;
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) watchdog_curr);
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ WATCHDOG *wp;
+
+ msg_verbose = 2;
+
+ wp = watchdog_create(10, (WATCHDOG_FN) 0, (void *) 0);
+ watchdog_start(wp);
+ do {
+ watchdog_pat();
+ } while (VSTREAM_GETCHAR() != VSTREAM_EOF);
+ watchdog_destroy(wp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/watchdog.h b/src/util/watchdog.h
new file mode 100644
index 0000000..ee01faf
--- /dev/null
+++ b/src/util/watchdog.h
@@ -0,0 +1,36 @@
+#ifndef _WATCHDOG_H_INCLUDED_
+#define _WATCHDOG_H_INCLUDED_
+
+/*++
+/* NAME
+/* watchdog 3h
+/* SUMMARY
+/* watchdog timer
+/* SYNOPSIS
+/* #include "watchdog.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * External interface.
+ */
+typedef struct WATCHDOG WATCHDOG;
+typedef void (*WATCHDOG_FN) (WATCHDOG *, char *);
+extern WATCHDOG *watchdog_create(unsigned, WATCHDOG_FN, char *);
+extern void watchdog_start(WATCHDOG *);
+extern void watchdog_stop(WATCHDOG *);
+extern void watchdog_destroy(WATCHDOG *);
+extern void watchdog_pat(void);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/write_buf.c b/src/util/write_buf.c
new file mode 100644
index 0000000..968a468
--- /dev/null
+++ b/src/util/write_buf.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* write_buf 3
+/* SUMMARY
+/* write buffer or bust
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t write_buf(fd, buf, len, timeout)
+/* int fd;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* write_buf() writes a buffer to the named stream in as many
+/* fragments as needed, and returns the number of bytes written,
+/* which is always the number requested or an error indication.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Bounds the time in seconds to wait until \fIfd\fD becomes writable.
+/* A value <= 0 means do not wait; this is useful only when \fIfd\fR
+/* uses blocking I/O.
+/* DIAGNOSTICS
+/* write_buf() returns -1 in case of trouble. The global \fIerrno\fR
+/* variable reflects the nature of the problem.
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* write_buf - write buffer or bust */
+
+ssize_t write_buf(int fd, const char *buf, ssize_t len, int timeout)
+{
+ const char *start = buf;
+ ssize_t count;
+ time_t expire;
+ int time_left = timeout;
+
+ if (time_left > 0)
+ expire = time((time_t *) 0) + time_left;
+
+ while (len > 0) {
+ if (time_left > 0 && write_wait(fd, time_left) < 0)
+ return (-1);
+ if ((count = write(fd, buf, len)) < 0) {
+ if ((errno == EAGAIN && time_left > 0) || errno == EINTR)
+ /* void */ ;
+ else
+ return (-1);
+ } else {
+ buf += count;
+ len -= count;
+ }
+ if (len > 0 && time_left > 0) {
+ time_left = expire - time((time_t *) 0);
+ if (time_left <= 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ }
+ }
+ return (buf - start);
+}
diff --git a/src/verify/.indent.pro b/src/verify/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/verify/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/verify/Makefile.in b/src/verify/Makefile.in
new file mode 100644
index 0000000..b5ba328
--- /dev/null
+++ b/src/verify/Makefile.in
@@ -0,0 +1,98 @@
+SHELL = /bin/sh
+SRCS = verify.c
+OBJS = verify.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = verify
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+verify.o: ../../include/argv.h
+verify.o: ../../include/attr.h
+verify.o: ../../include/check_arg.h
+verify.o: ../../include/cleanup_user.h
+verify.o: ../../include/data_redirect.h
+verify.o: ../../include/deliver_request.h
+verify.o: ../../include/dict.h
+verify.o: ../../include/dict_cache.h
+verify.o: ../../include/dict_ht.h
+verify.o: ../../include/dsn.h
+verify.o: ../../include/events.h
+verify.o: ../../include/htable.h
+verify.o: ../../include/int_filt.h
+verify.o: ../../include/iostuff.h
+verify.o: ../../include/mail_conf.h
+verify.o: ../../include/mail_params.h
+verify.o: ../../include/mail_proto.h
+verify.o: ../../include/mail_server.h
+verify.o: ../../include/mail_version.h
+verify.o: ../../include/msg.h
+verify.o: ../../include/msg_stats.h
+verify.o: ../../include/myflock.h
+verify.o: ../../include/mymalloc.h
+verify.o: ../../include/nvtable.h
+verify.o: ../../include/post_mail.h
+verify.o: ../../include/recipient_list.h
+verify.o: ../../include/set_eugid.h
+verify.o: ../../include/smtputf8.h
+verify.o: ../../include/split_at.h
+verify.o: ../../include/stringops.h
+verify.o: ../../include/sys_defs.h
+verify.o: ../../include/vbuf.h
+verify.o: ../../include/verify_clnt.h
+verify.o: ../../include/verify_sender_addr.h
+verify.o: ../../include/vstream.h
+verify.o: ../../include/vstring.h
+verify.o: verify.c
diff --git a/src/verify/verify.c b/src/verify/verify.c
new file mode 100644
index 0000000..fea131c
--- /dev/null
+++ b/src/verify/verify.c
@@ -0,0 +1,776 @@
+/*++
+/* NAME
+/* verify 8
+/* SUMMARY
+/* Postfix address verification server
+/* SYNOPSIS
+/* \fBverify\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBverify\fR(8) address verification server maintains a record
+/* of what recipient addresses are known to be deliverable or
+/* undeliverable.
+/*
+/* Addresses are verified by injecting probe messages into the
+/* Postfix queue. Probe messages are run through all the routing
+/* and rewriting machinery except for final delivery, and are
+/* discarded rather than being deferred or bounced.
+/*
+/* Address verification relies on the answer from the nearest
+/* MTA for the specified address, and will therefore not detect
+/* all undeliverable addresses.
+/*
+/* The \fBverify\fR(8) server is designed to run under control
+/* by the Postfix
+/* master server. It maintains an optional persistent database.
+/* To avoid being interrupted by "postfix stop" in the middle
+/* of a database update, the process runs in a separate process
+/* group.
+/*
+/* The \fBverify\fR(8) server implements the following requests:
+/* .IP "\fBupdate\fI address status text\fR"
+/* Update the status and text of the specified address.
+/* .IP "\fBquery\fI address\fR"
+/* Look up the \fIstatus\fR and \fItext\fR for the specified
+/* \fIaddress\fR.
+/* If the status is unknown, a probe is sent and an "in progress"
+/* status is returned.
+/* SECURITY
+/* .ad
+/* .fi
+/* The address verification server is not security-sensitive. It does
+/* not talk to the network, and it does not talk to local users.
+/* The verify server can run chrooted at fixed low privilege.
+/*
+/* The address verification server can be coerced to store
+/* unlimited amounts of garbage. Limiting the cache expiry
+/* time
+/* trades one problem (disk space exhaustion) for another
+/* one (poor response time to client requests).
+/*
+/* With Postfix version 2.5 and later, the \fBverify\fR(8)
+/* server no longer uses root privileges when opening the
+/* \fBaddress_verify_map\fR cache file. The file should now
+/* be stored under the Postfix-owned \fBdata_directory\fR. As
+/* a migration aid, an attempt to open a cache file under a
+/* non-Postfix directory is redirected to the Postfix-owned
+/* \fBdata_directory\fR, and a warning is logged.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* Address verification probe messages add additional traffic
+/* to the mail queue.
+/* Recipient verification may cause an increased load on
+/* down-stream servers in the case of a dictionary attack or
+/* a flood of backscatter bounces.
+/* Sender address verification may cause your site to be
+/* denylisted by some providers.
+/*
+/* If the persistent database ever gets corrupted then the world
+/* comes to an end and human intervention is needed. This violates
+/* a basic Postfix principle.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* as \fBverify\fR(8)
+/* processes are long-lived. Use the command "\fBpostfix reload\fR" after
+/* a configuration change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* PROBE MESSAGE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR"
+/* The sender address to use in address verification probes; prior
+/* to Postfix 2.5 the default was "postmaster".
+/* .PP
+/* Available with Postfix 2.9 and later:
+/* .IP "\fBaddress_verify_sender_ttl (0s)\fR"
+/* The time between changes in the time-dependent portion of address
+/* verification probe sender addresses.
+/* CACHE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBaddress_verify_map (see 'postconf -d' output)\fR"
+/* Lookup table for persistent address verification status
+/* storage.
+/* .IP "\fBaddress_verify_positive_expire_time (31d)\fR"
+/* The time after which a successful probe expires from the address
+/* verification cache.
+/* .IP "\fBaddress_verify_positive_refresh_time (7d)\fR"
+/* The time after which a successful address verification probe needs
+/* to be refreshed.
+/* .IP "\fBaddress_verify_negative_cache (yes)\fR"
+/* Enable caching of failed address verification probe results.
+/* .IP "\fBaddress_verify_negative_expire_time (3d)\fR"
+/* The time after which a failed probe expires from the address
+/* verification cache.
+/* .IP "\fBaddress_verify_negative_refresh_time (3h)\fR"
+/* The time after which a failed address verification probe needs to
+/* be refreshed.
+/* .PP
+/* Available with Postfix 2.7 and later:
+/* .IP "\fBaddress_verify_cache_cleanup_interval (12h)\fR"
+/* The amount of time between \fBverify\fR(8) address verification
+/* database cleanup runs.
+/* PROBE MESSAGE ROUTING CONTROLS
+/* .ad
+/* .fi
+/* By default, probe messages are delivered via the same route
+/* as regular messages. The following parameters can be used to
+/* override specific message routing mechanisms.
+/* .IP "\fBaddress_verify_relayhost ($relayhost)\fR"
+/* Overrides the relayhost parameter setting for address verification
+/* probes.
+/* .IP "\fBaddress_verify_transport_maps ($transport_maps)\fR"
+/* Overrides the transport_maps parameter setting for address verification
+/* probes.
+/* .IP "\fBaddress_verify_local_transport ($local_transport)\fR"
+/* Overrides the local_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_virtual_transport ($virtual_transport)\fR"
+/* Overrides the virtual_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_relay_transport ($relay_transport)\fR"
+/* Overrides the relay_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_default_transport ($default_transport)\fR"
+/* Overrides the default_transport parameter setting for address
+/* verification probes.
+/* .PP
+/* Available in Postfix 2.3 and later:
+/* .IP "\fBaddress_verify_sender_dependent_relayhost_maps ($sender_dependent_relayhost_maps)\fR"
+/* Overrides the sender_dependent_relayhost_maps parameter setting for address
+/* verification probes.
+/* .PP
+/* Available in Postfix 2.7 and later:
+/* .IP "\fBaddress_verify_sender_dependent_default_transport_maps ($sender_dependent_default_transport_maps)\fR"
+/* Overrides the sender_dependent_default_transport_maps parameter
+/* setting for address verification probes.
+/* SMTPUTF8 CONTROLS
+/* .ad
+/* .fi
+/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+/* .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 "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .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 "\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 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtpd(8), Postfix SMTP server
+/* cleanup(8), enqueue Postfix message
+/* postconf(5), configuration parameters
+/* 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_VERIFICATION_README, address verification howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 2.1.
+/* 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/stat.h>
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <dict_ht.h>
+#include <dict_cache.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <set_eugid.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <post_mail.h>
+#include <data_redirect.h>
+#include <verify_clnt.h>
+#include <verify_sender_addr.h>
+
+/* Server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+char *var_verify_map;
+int var_verify_pos_exp;
+int var_verify_pos_try;
+int var_verify_neg_exp;
+int var_verify_neg_try;
+int var_verify_scan_cache;
+
+ /*
+ * State.
+ */
+static DICT_CACHE *verify_map;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define STREQ(x,y) (strcmp(x,y) == 0)
+
+ /*
+ * The address verification database consists of (address, data) tuples. The
+ * format of the data field is "status:probed:updated:text". The meaning of
+ * each field is:
+ *
+ * status: one of the four recipient status codes (OK, DEFER, BOUNCE or TODO).
+ * In the case of TODO, we have no information about the address, and the
+ * address is being probed.
+ *
+ * probed: if non-zero, the time the currently outstanding address probe was
+ * sent. If zero, there is no outstanding address probe.
+ *
+ * updated: if non-zero, the time the address probe result was received. If
+ * zero, we have no information about the address, and the address is being
+ * probed.
+ *
+ * text: descriptive text from delivery agents etc.
+ */
+
+ /*
+ * Quick test to see status without parsing the whole entry.
+ */
+#define STATUS_FROM_RAW_ENTRY(e) atoi(e)
+
+/* verify_make_entry - construct table entry */
+
+static void verify_make_entry(VSTRING *buf, int status, long probed,
+ long updated, const char *text)
+{
+ vstring_sprintf(buf, "%d:%ld:%ld:%s", status, probed, updated, text);
+}
+
+/* verify_parse_entry - parse table entry */
+
+static int verify_parse_entry(char *buf, int *status, long *probed,
+ long *updated, char **text)
+{
+ char *probed_text;
+ char *updated_text;
+
+ if ((probed_text = split_at(buf, ':')) != 0
+ && (updated_text = split_at(probed_text, ':')) != 0
+ && (*text = split_at(updated_text, ':')) != 0
+ && alldig(buf)
+ && alldig(probed_text)
+ && alldig(updated_text)) {
+ *probed = atol(probed_text);
+ *updated = atol(updated_text);
+ *status = atoi(buf);
+
+ /*
+ * Coverity 200604: the code incorrectly tested (probed || updated),
+ * so that the sanity check never detected all-zero time stamps. Such
+ * records are never written. If we read a record with all-zero time
+ * stamps, then something is badly broken.
+ */
+ if ((*status == DEL_RCPT_STAT_OK
+ || *status == DEL_RCPT_STAT_DEFER
+ || *status == DEL_RCPT_STAT_BOUNCE
+ || *status == DEL_RCPT_STAT_TODO)
+ && (*probed || *updated))
+ return (0);
+ }
+ msg_warn("bad address verify table entry: %.100s", buf);
+ return (-1);
+}
+
+/* verify_stat2name - status to name */
+
+static const char *verify_stat2name(int addr_status)
+{
+ if (addr_status == DEL_RCPT_STAT_OK)
+ return ("deliverable");
+ if (addr_status == DEL_RCPT_STAT_DEFER)
+ return ("undeliverable");
+ if (addr_status == DEL_RCPT_STAT_BOUNCE)
+ return ("undeliverable");
+ return (0);
+}
+
+/* verify_update_service - update address service */
+
+static void verify_update_service(VSTREAM *client_stream)
+{
+ VSTRING *buf = vstring_alloc(10);
+ VSTRING *addr = vstring_alloc(10);
+ int addr_status;
+ VSTRING *text = vstring_alloc(10);
+ const char *status_name;
+ const char *raw_data;
+ long probed;
+ long updated;
+
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, &addr_status),
+ RECV_ATTR_STR(MAIL_ATTR_WHY, text),
+ ATTR_TYPE_END) == 3) {
+ /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */
+ translit(STR(addr), ":", "_");
+ if ((status_name = verify_stat2name(addr_status)) == 0) {
+ msg_warn("bad recipient status %d for recipient %s",
+ addr_status, STR(addr));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD),
+ ATTR_TYPE_END);
+ } else {
+
+ /*
+ * Robustness: don't allow a failed probe to clobber an OK
+ * address before it expires. The failed probe is ignored so that
+ * the address will be re-probed upon the next query. As long as
+ * some probes succeed the address will remain cached as OK.
+ */
+ if (addr_status == DEL_RCPT_STAT_OK
+ || (raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0
+ || STATUS_FROM_RAW_ENTRY(raw_data) != DEL_RCPT_STAT_OK) {
+ probed = 0;
+ updated = (long) time((time_t *) 0);
+ printable(STR(text), '?');
+ verify_make_entry(buf, addr_status, probed, updated, STR(text));
+ if (msg_verbose)
+ msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s",
+ STR(addr), addr_status, probed, updated, STR(text));
+ dict_cache_update(verify_map, STR(addr), STR(buf));
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK),
+ ATTR_TYPE_END);
+ }
+ }
+ vstring_free(buf);
+ vstring_free(addr);
+ vstring_free(text);
+}
+
+/* verify_post_mail_fclose_action - callback */
+
+static void verify_post_mail_fclose_action(int unused_status,
+ void *unused_context)
+{
+ /* no code here, we just need to avoid blocking in post_mail_fclose() */
+}
+
+/* verify_post_mail_action - callback */
+
+static void verify_post_mail_action(VSTREAM *stream, void *context)
+{
+
+ /*
+ * Probe messages need no body content, because they are never delivered,
+ * deferred, or bounced.
+ */
+ if (stream != 0)
+ post_mail_fclose_async(stream, verify_post_mail_fclose_action, context);
+}
+
+/* verify_query_service - query address status */
+
+static void verify_query_service(VSTREAM *client_stream)
+{
+ VSTRING *addr = vstring_alloc(10);
+ VSTRING *get_buf = 0;
+ VSTRING *put_buf = 0;
+ const char *raw_data;
+ int addr_status;
+ long probed;
+ long updated;
+ char *text;
+
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, addr),
+ ATTR_TYPE_END) == 1) {
+ long now = (long) time((time_t *) 0);
+
+ /*
+ * Produce a default record when no usable record exists.
+ *
+ * If negative caching is disabled, purge an expired record from the
+ * database.
+ *
+ * XXX Assume that a probe is lost if no response is received in 1000
+ * seconds. If this number is too small the queue will slowly fill up
+ * with delayed probes.
+ *
+ * XXX Maintain a moving average for the probe turnaround time, and
+ * allow probe "retransmission" when a probe is outstanding for, say
+ * some minimal amount of time (1000 sec) plus several times the
+ * observed probe turnaround time. This causes probing to back off
+ * when the mail system becomes congested.
+ */
+#define POSITIVE_ENTRY_EXPIRED(addr_status, updated) \
+ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_exp < now)
+#define NEGATIVE_ENTRY_EXPIRED(addr_status, updated) \
+ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_exp < now)
+#define PROBE_TTL 1000
+
+ /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */
+ translit(STR(addr), ":", "_");
+ if ((raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 /* not found */
+ || ((get_buf = vstring_alloc(10)),
+ vstring_strcpy(get_buf, raw_data), /* malformed */
+ verify_parse_entry(STR(get_buf), &addr_status, &probed,
+ &updated, &text) < 0)
+ || (now - probed > PROBE_TTL /* safe to probe */
+ && (POSITIVE_ENTRY_EXPIRED(addr_status, updated)
+ || NEGATIVE_ENTRY_EXPIRED(addr_status, updated)))) {
+ addr_status = DEL_RCPT_STAT_TODO;
+ probed = 0;
+ updated = 0;
+ text = "Address verification in progress";
+ if (raw_data != 0 && var_verify_neg_cache == 0)
+ dict_cache_delete(verify_map, STR(addr));
+ }
+ if (msg_verbose)
+ msg_info("GOT %s status=%d probed=%ld updated=%ld text=%s",
+ STR(addr), addr_status, probed, updated, text);
+
+ /*
+ * Respond to the client.
+ */
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK),
+ SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, text),
+ ATTR_TYPE_END);
+
+ /*
+ * Send a new probe when the information needs to be refreshed.
+ *
+ * XXX For an initial proof of concept implementation, use synchronous
+ * mail submission. This needs to be made async for high-volume
+ * sites, which makes it even more interesting to eliminate duplicate
+ * queries while a probe is being built.
+ *
+ * If negative caching is turned off, update the database only when
+ * refreshing an existing entry.
+ */
+#define POSITIVE_REFRESH_NEEDED(addr_status, updated) \
+ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_try < now)
+#define NEGATIVE_REFRESH_NEEDED(addr_status, updated) \
+ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_try < now)
+
+ if (now - probed > PROBE_TTL
+ && (POSITIVE_REFRESH_NEEDED(addr_status, updated)
+ || NEGATIVE_REFRESH_NEEDED(addr_status, updated))) {
+ if (msg_verbose)
+ msg_info("PROBE %s status=%d probed=%ld updated=%ld",
+ STR(addr), addr_status, now, updated);
+ post_mail_fopen_async(make_verify_sender_addr(), STR(addr),
+ MAIL_SRC_MASK_VERIFY,
+ DEL_REQ_FLAG_MTA_VRFY,
+ SMTPUTF8_FLAG_NONE,
+ (VSTRING *) 0,
+ verify_post_mail_action,
+ (void *) 0);
+ if (updated != 0 || var_verify_neg_cache != 0) {
+ put_buf = vstring_alloc(10);
+ verify_make_entry(put_buf, addr_status, now, updated, text);
+ if (msg_verbose)
+ msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s",
+ STR(addr), addr_status, now, updated, text);
+ dict_cache_update(verify_map, STR(addr), STR(put_buf));
+ }
+ }
+ }
+ vstring_free(addr);
+ if (get_buf)
+ vstring_free(get_buf);
+ if (put_buf)
+ vstring_free(put_buf);
+}
+
+/* verify_cache_validator - cache cleanup validator */
+
+static int verify_cache_validator(const char *addr, const char *raw_data,
+ void *context)
+{
+ VSTRING *get_buf = (VSTRING *) context;
+ int addr_status;
+ long probed;
+ long updated;
+ char *text;
+ long now = (long) event_time();
+
+#define POS_OR_NEG_ENTRY_EXPIRED(stat, stamp) \
+ (POSITIVE_ENTRY_EXPIRED((stat), (stamp)) \
+ || NEGATIVE_ENTRY_EXPIRED((stat), (stamp)))
+
+ vstring_strcpy(get_buf, raw_data);
+ return (verify_parse_entry(STR(get_buf), &addr_status, /* syntax OK */
+ &probed, &updated, &text) == 0
+ && (now - probed < PROBE_TTL /* probe in progress */
+ || !POS_OR_NEG_ENTRY_EXPIRED(addr_status, updated)));
+}
+
+/* verify_service - perform service for client */
+
+static void verify_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+ VSTRING *request = vstring_alloc(10);
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the address verification service. All connection-management stuff
+ * is handled by the common code in multi_server.c.
+ */
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, request),
+ ATTR_TYPE_END) == 1) {
+ if (STREQ(STR(request), VRFY_REQ_UPDATE)) {
+ verify_update_service(client_stream);
+ } else if (STREQ(STR(request), VRFY_REQ_QUERY)) {
+ verify_query_service(client_stream);
+ } else {
+ msg_warn("unrecognized request: \"%s\", ignored", STR(request));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD),
+ ATTR_TYPE_END);
+ }
+ }
+ vstream_fflush(client_stream);
+ vstring_free(request);
+}
+
+/* verify_dump - dump some statistics */
+
+static void verify_dump(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Dump preliminary cache cleanup statistics when the process commits
+ * suicide while a cache cleanup run is in progress. We can't currently
+ * distinguish between "postfix reload" (we should restart) or "maximal
+ * idle time reached" (we could finish the cache cleanup first).
+ */
+ dict_cache_close(verify_map);
+ verify_map = 0;
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * If the database is in volatile memory only, prevent automatic process
+ * suicide after a limited number of client requests or after a limited
+ * amount of idle time.
+ */
+ if (*var_verify_map == 0) {
+ var_use_limit = 0;
+ var_idle_limit = 0;
+ }
+
+ /*
+ * Start the cache cleanup thread.
+ */
+ if (var_verify_scan_cache > 0) {
+ int cache_flags;
+
+ cache_flags = DICT_CACHE_FLAG_STATISTICS;
+ if (msg_verbose)
+ cache_flags |= DICT_CACHE_FLAG_VERBOSE;
+ dict_cache_control(verify_map,
+ CA_DICT_CACHE_CTL_FLAGS(cache_flags),
+ CA_DICT_CACHE_CTL_INTERVAL(var_verify_scan_cache),
+ CA_DICT_CACHE_CTL_VALIDATOR(verify_cache_validator),
+ CA_DICT_CACHE_CTL_CONTEXT((void *) vstring_alloc(100)),
+ CA_DICT_CACHE_CTL_END);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ mode_t saved_mask;
+ VSTRING *redirect;
+
+ /*
+ * Never, ever, get killed by a master signal, as that would corrupt the
+ * database when we're in the middle of an update.
+ */
+ setsid();
+
+ /*
+ * Security: don't create root-owned files that contain untrusted data.
+ * And don't create Postfix-owned files in root-owned directories,
+ * either. We want a correct relationship between (file/directory)
+ * ownership and (file/directory) content.
+ *
+ * XXX Non-root open can violate the principle of least surprise: Postfix
+ * can't open an *SQL config file for database read-write access, even
+ * though it can open that same control file for database read-only
+ * access.
+ *
+ * The solution is to query a map type and obtain its properties before
+ * opening it. A clean solution is to add a dict_info() API that is
+ * similar to dict_open() except it returns properties (dict flags) only.
+ * A pragmatic solution is to overload the existing API and have
+ * dict_open() return a dummy map when given a null map name.
+ *
+ * However, the proxymap daemon has been opening *SQL maps as non-root for
+ * years now without anyone complaining, let's not solve a problem that
+ * doesn't exist.
+ */
+ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
+ redirect = vstring_alloc(100);
+
+ /*
+ * Keep state in persistent (external) or volatile (internal) map.
+ *
+ * Start the cache cleanup thread after permanently dropping privileges.
+ */
+#define VERIFY_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE \
+ | DICT_FLAG_OPEN_LOCK | DICT_FLAG_UTF8_REQUEST)
+
+ saved_mask = umask(022);
+ verify_map =
+ dict_cache_open(*var_verify_map ?
+ data_redirect_map(redirect, var_verify_map) :
+ "internal:verify",
+ O_CREAT | O_RDWR, VERIFY_DICT_OPEN_FLAGS);
+ (void) umask(saved_mask);
+
+ /*
+ * Clean up and restore privilege.
+ */
+ vstring_free(redirect);
+ RESTORE_SAVED_EUGID();
+}
+
+/* post_accept_init - announce our protocol */
+
+static void post_accept_init(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_VERIFY),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
+ VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_VERIFY_POS_EXP, DEF_VERIFY_POS_EXP, &var_verify_pos_exp, 1, 0,
+ VAR_VERIFY_POS_TRY, DEF_VERIFY_POS_TRY, &var_verify_pos_try, 1, 0,
+ VAR_VERIFY_NEG_EXP, DEF_VERIFY_NEG_EXP, &var_verify_neg_exp, 1, 0,
+ VAR_VERIFY_NEG_TRY, DEF_VERIFY_NEG_TRY, &var_verify_neg_try, 1, 0,
+ VAR_VERIFY_SCAN_CACHE, DEF_VERIFY_SCAN_CACHE, &var_verify_scan_cache, 0, 0,
+ VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, verify_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_POST_ACCEPT(post_accept_init),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_EXIT(verify_dump),
+ 0);
+}
diff --git a/src/virtual/.indent.pro b/src/virtual/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/virtual/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/virtual/.printfck b/src/virtual/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/virtual/.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/virtual/Makefile.in b/src/virtual/Makefile.in
new file mode 100644
index 0000000..31fff0e
--- /dev/null
+++ b/src/virtual/Makefile.in
@@ -0,0 +1,234 @@
+SHELL = /bin/sh
+SRCS = virtual.c mailbox.c recipient.c deliver_attr.c maildir.c unknown.c
+OBJS = virtual.o mailbox.o recipient.o deliver_attr.o maildir.o unknown.o
+HDRS = virtual.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+PROG = virtual
+TESTPROG=
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../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)
+
+tests:
+
+root_tests:
+
+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
+
+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'
+deliver_attr.o: ../../include/argv.h
+deliver_attr.o: ../../include/attr.h
+deliver_attr.o: ../../include/check_arg.h
+deliver_attr.o: ../../include/deliver_request.h
+deliver_attr.o: ../../include/dict.h
+deliver_attr.o: ../../include/dsn.h
+deliver_attr.o: ../../include/dsn_buf.h
+deliver_attr.o: ../../include/htable.h
+deliver_attr.o: ../../include/maps.h
+deliver_attr.o: ../../include/mbox_conf.h
+deliver_attr.o: ../../include/msg.h
+deliver_attr.o: ../../include/msg_stats.h
+deliver_attr.o: ../../include/myflock.h
+deliver_attr.o: ../../include/mymalloc.h
+deliver_attr.o: ../../include/nvtable.h
+deliver_attr.o: ../../include/recipient_list.h
+deliver_attr.o: ../../include/sys_defs.h
+deliver_attr.o: ../../include/vbuf.h
+deliver_attr.o: ../../include/vstream.h
+deliver_attr.o: ../../include/vstring.h
+deliver_attr.o: deliver_attr.c
+deliver_attr.o: virtual.h
+mailbox.o: ../../include/argv.h
+mailbox.o: ../../include/attr.h
+mailbox.o: ../../include/bounce.h
+mailbox.o: ../../include/check_arg.h
+mailbox.o: ../../include/defer.h
+mailbox.o: ../../include/deliver_request.h
+mailbox.o: ../../include/dict.h
+mailbox.o: ../../include/dsn.h
+mailbox.o: ../../include/dsn_buf.h
+mailbox.o: ../../include/dsn_util.h
+mailbox.o: ../../include/htable.h
+mailbox.o: ../../include/mail_addr_find.h
+mailbox.o: ../../include/mail_addr_form.h
+mailbox.o: ../../include/mail_copy.h
+mailbox.o: ../../include/mail_params.h
+mailbox.o: ../../include/maps.h
+mailbox.o: ../../include/mbox_conf.h
+mailbox.o: ../../include/mbox_open.h
+mailbox.o: ../../include/msg.h
+mailbox.o: ../../include/msg_stats.h
+mailbox.o: ../../include/myflock.h
+mailbox.o: ../../include/mymalloc.h
+mailbox.o: ../../include/nvtable.h
+mailbox.o: ../../include/recipient_list.h
+mailbox.o: ../../include/safe_open.h
+mailbox.o: ../../include/sent.h
+mailbox.o: ../../include/set_eugid.h
+mailbox.o: ../../include/stringops.h
+mailbox.o: ../../include/sys_defs.h
+mailbox.o: ../../include/vbuf.h
+mailbox.o: ../../include/vstream.h
+mailbox.o: ../../include/vstring.h
+mailbox.o: mailbox.c
+mailbox.o: virtual.h
+maildir.o: ../../include/argv.h
+maildir.o: ../../include/attr.h
+maildir.o: ../../include/bounce.h
+maildir.o: ../../include/check_arg.h
+maildir.o: ../../include/defer.h
+maildir.o: ../../include/deliver_request.h
+maildir.o: ../../include/dict.h
+maildir.o: ../../include/dsn.h
+maildir.o: ../../include/dsn_buf.h
+maildir.o: ../../include/dsn_util.h
+maildir.o: ../../include/get_hostname.h
+maildir.o: ../../include/htable.h
+maildir.o: ../../include/mail_copy.h
+maildir.o: ../../include/mail_params.h
+maildir.o: ../../include/make_dirs.h
+maildir.o: ../../include/maps.h
+maildir.o: ../../include/mbox_conf.h
+maildir.o: ../../include/mbox_open.h
+maildir.o: ../../include/msg.h
+maildir.o: ../../include/msg_stats.h
+maildir.o: ../../include/myflock.h
+maildir.o: ../../include/mymalloc.h
+maildir.o: ../../include/nvtable.h
+maildir.o: ../../include/recipient_list.h
+maildir.o: ../../include/safe_open.h
+maildir.o: ../../include/sane_fsops.h
+maildir.o: ../../include/sent.h
+maildir.o: ../../include/set_eugid.h
+maildir.o: ../../include/stringops.h
+maildir.o: ../../include/sys_defs.h
+maildir.o: ../../include/vbuf.h
+maildir.o: ../../include/vstream.h
+maildir.o: ../../include/vstring.h
+maildir.o: ../../include/warn_stat.h
+maildir.o: maildir.c
+maildir.o: virtual.h
+recipient.o: ../../include/argv.h
+recipient.o: ../../include/attr.h
+recipient.o: ../../include/bounce.h
+recipient.o: ../../include/check_arg.h
+recipient.o: ../../include/deliver_request.h
+recipient.o: ../../include/dict.h
+recipient.o: ../../include/dsn.h
+recipient.o: ../../include/dsn_buf.h
+recipient.o: ../../include/htable.h
+recipient.o: ../../include/maps.h
+recipient.o: ../../include/mbox_conf.h
+recipient.o: ../../include/msg.h
+recipient.o: ../../include/msg_stats.h
+recipient.o: ../../include/myflock.h
+recipient.o: ../../include/mymalloc.h
+recipient.o: ../../include/nvtable.h
+recipient.o: ../../include/recipient_list.h
+recipient.o: ../../include/stringops.h
+recipient.o: ../../include/sys_defs.h
+recipient.o: ../../include/vbuf.h
+recipient.o: ../../include/vstream.h
+recipient.o: ../../include/vstring.h
+recipient.o: recipient.c
+recipient.o: virtual.h
+unknown.o: ../../include/argv.h
+unknown.o: ../../include/attr.h
+unknown.o: ../../include/bounce.h
+unknown.o: ../../include/check_arg.h
+unknown.o: ../../include/deliver_request.h
+unknown.o: ../../include/dict.h
+unknown.o: ../../include/dsn.h
+unknown.o: ../../include/dsn_buf.h
+unknown.o: ../../include/htable.h
+unknown.o: ../../include/maps.h
+unknown.o: ../../include/mbox_conf.h
+unknown.o: ../../include/msg.h
+unknown.o: ../../include/msg_stats.h
+unknown.o: ../../include/myflock.h
+unknown.o: ../../include/mymalloc.h
+unknown.o: ../../include/nvtable.h
+unknown.o: ../../include/recipient_list.h
+unknown.o: ../../include/sys_defs.h
+unknown.o: ../../include/vbuf.h
+unknown.o: ../../include/vstream.h
+unknown.o: ../../include/vstring.h
+unknown.o: unknown.c
+unknown.o: virtual.h
+virtual.o: ../../include/argv.h
+virtual.o: ../../include/attr.h
+virtual.o: ../../include/check_arg.h
+virtual.o: ../../include/deliver_completed.h
+virtual.o: ../../include/deliver_request.h
+virtual.o: ../../include/dict.h
+virtual.o: ../../include/dsn.h
+virtual.o: ../../include/dsn_buf.h
+virtual.o: ../../include/flush_clnt.h
+virtual.o: ../../include/htable.h
+virtual.o: ../../include/iostuff.h
+virtual.o: ../../include/mail_addr_find.h
+virtual.o: ../../include/mail_addr_form.h
+virtual.o: ../../include/mail_conf.h
+virtual.o: ../../include/mail_params.h
+virtual.o: ../../include/mail_queue.h
+virtual.o: ../../include/mail_server.h
+virtual.o: ../../include/mail_version.h
+virtual.o: ../../include/maps.h
+virtual.o: ../../include/mbox_conf.h
+virtual.o: ../../include/msg.h
+virtual.o: ../../include/msg_stats.h
+virtual.o: ../../include/myflock.h
+virtual.o: ../../include/mymalloc.h
+virtual.o: ../../include/nvtable.h
+virtual.o: ../../include/recipient_list.h
+virtual.o: ../../include/set_eugid.h
+virtual.o: ../../include/sys_defs.h
+virtual.o: ../../include/vbuf.h
+virtual.o: ../../include/vstream.h
+virtual.o: ../../include/vstring.h
+virtual.o: virtual.c
+virtual.o: virtual.h
diff --git a/src/virtual/deliver_attr.c b/src/virtual/deliver_attr.c
new file mode 100644
index 0000000..3a5c0af
--- /dev/null
+++ b/src/virtual/deliver_attr.c
@@ -0,0 +1,90 @@
+/*++
+/* NAME
+/* deliver_attr 3
+/* SUMMARY
+/* initialize message delivery attributes
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* void deliver_attr_init(attrp)
+/* DELIVER_ATTR *attrp;
+/*
+/* void deliver_attr_dump(attrp)
+/* DELIVER_ATTR *attrp;
+/*
+/* void deliver_attr_free(attrp)
+/* DELIVER_ATTR *attrp;
+/* DESCRIPTION
+/* deliver_attr_init() initializes a structure with message delivery
+/* attributes to a known initial state (all zeros).
+/*
+/* deliver_attr_dump() logs the contents of the given attribute list.
+/*
+/* deliver_attr_free() releases memory that was allocated by
+/* deliver_attr_init().
+/* 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 <vstream.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_attr_init - set message delivery attributes to all-zero state */
+
+void deliver_attr_init(DELIVER_ATTR *attrp)
+{
+ attrp->level = 0;
+ attrp->fp = 0;
+ attrp->queue_name = 0;
+ attrp->queue_id = 0;
+ attrp->offset = 0;
+ attrp->sender = 0;
+ RECIPIENT_ASSIGN(&(attrp->rcpt), 0, 0, 0, 0, 0);
+ attrp->user = 0;
+ attrp->delivered = 0;
+ attrp->relay = 0;
+ attrp->why = dsb_create();
+}
+
+/* deliver_attr_dump - log message delivery attributes */
+
+void deliver_attr_dump(DELIVER_ATTR *attrp)
+{
+ msg_info("level: %d", attrp->level);
+ msg_info("path: %s", VSTREAM_PATH(attrp->fp));
+ msg_info("fp: 0x%lx", (long) attrp->fp);
+ msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null");
+ msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null");
+ msg_info("offset: %ld", attrp->offset);
+ msg_info("sender: %s", attrp->sender ? attrp->sender : "null");
+ msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null");
+ msg_info("user: %s", attrp->user ? attrp->user : "null");
+ msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null");
+ msg_info("relay: %s", attrp->relay ? attrp->relay : "null");
+ msg_info("why: %s", attrp->why ? "buffer" : "null");
+}
+
+
+/* deliver_attr_free - release storage */
+
+void deliver_attr_free(DELIVER_ATTR *attrp)
+{
+ dsb_free(attrp->why);
+}
diff --git a/src/virtual/mailbox.c b/src/virtual/mailbox.c
new file mode 100644
index 0000000..19afca8
--- /dev/null
+++ b/src/virtual/mailbox.c
@@ -0,0 +1,283 @@
+/*++
+/* NAME
+/* mailbox 3
+/* SUMMARY
+/* mailbox delivery
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_mailbox(state, usr_attr, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_mailbox() delivers to UNIX-style mailbox or to maildir.
+/*
+/* A zero result means that the named user was not found.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* .IP statusp
+/* Delivery status: see below.
+/* DIAGNOSTICS
+/* The message delivery status is non-zero when delivery should be tried
+/* again.
+/* 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/stat.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <set_eugid.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <mbox_open.h>
+#include <defer.h>
+#include <sent.h>
+#include <mail_params.h>
+#include <mail_addr_find.h>
+#include <dsn_util.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+#define YES 1
+#define NO 0
+
+/* deliver_mailbox_file - deliver to recipient mailbox */
+
+static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_mailbox_file";
+ DSN_BUF *why = state.msg_attr.why;
+ MBOX *mp;
+ int mail_copy_status;
+ int deliver_status;
+ int copy_flags;
+ struct stat st;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Don't deliver trace-only requests.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to mailbox");
+ return (sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+
+ /*
+ * Lock the mailbox and open/create the mailbox file.
+ *
+ * Write the file as the recipient, so that file quota work.
+ */
+ copy_flags = MAIL_COPY_MBOX;
+
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR, &st, -1, -1,
+ virtual_mbox_lock_mask, "4.2.0", why);
+ if (mp != 0) {
+ if (S_ISREG(st.st_mode) == 0) {
+ vstream_fclose(mp->fp);
+ msg_warn("recipient %s: destination %s is not a regular file",
+ state.msg_attr.rcpt.address, usr_attr.mailbox);
+ dsb_simple(why, "5.3.5", "mail system configuration error");
+ } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) {
+ vstream_fclose(mp->fp);
+ dsb_simple(why, "4.2.0",
+ "destination %s is not owned by recipient", usr_attr.mailbox);
+ msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
+ VAR_STRICT_MBOX_OWNER);
+ } else {
+ if (vstream_fseek(mp->fp, (off_t) 0, SEEK_END) < 0)
+ msg_fatal("%s: seek mailbox file %s: %m",
+ myname, VSTREAM_PATH(mp->fp));
+ mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
+ copy_flags, "\n", why);
+ }
+ mbox_release(mp);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * As the mail system, bounce, defer delivery, or report success.
+ */
+ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
+ deliver_status = DEL_STAT_DEFER;
+ } else if (mail_copy_status != 0) {
+ vstring_sprintf_prepend(why->reason, "delivery failed to mailbox %s: ",
+ usr_attr.mailbox);
+ deliver_status =
+ (STR(why->status)[0] == '4' ?
+ defer_append : bounce_append)
+ (BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+ dsb_simple(why, "2.0.0", "delivered to mailbox");
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ }
+ return (deliver_status);
+}
+
+/* deliver_mailbox - deliver to recipient mailbox */
+
+int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
+{
+ const char *myname = "deliver_mailbox";
+ const char *mailbox_res;
+ const char *uid_res;
+ const char *gid_res;
+ DSN_BUF *why = state.msg_attr.why;
+ long n;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Sanity check.
+ */
+ if (*var_virt_mailbox_base != '/')
+ msg_fatal("do not specify relative pathname: %s = %s",
+ VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base);
+
+ /*
+ * Look up the mailbox location. Bounce if not found, defer in case of
+ * trouble.
+ */
+#define IGNORE_EXTENSION ((char **) 0)
+
+ mailbox_res = mail_addr_find(virtual_mailbox_maps, state.msg_attr.user,
+ IGNORE_EXTENSION);
+ if (mailbox_res == 0) {
+ if (virtual_mailbox_maps->error == 0)
+ return (NO);
+ msg_warn("table %s: lookup %s: %m", virtual_mailbox_maps->title,
+ state.msg_attr.user);
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ usr_attr.mailbox = concatenate(var_virt_mailbox_base, "/",
+ mailbox_res, (char *) 0);
+
+#define RETURN(res) { myfree(usr_attr.mailbox); return (res); }
+
+ /*
+ * Look up the mailbox owner rights. Defer in case of trouble.
+ */
+ uid_res = mail_addr_find(virtual_uid_maps, state.msg_attr.user,
+ IGNORE_EXTENSION);
+ if (uid_res == 0) {
+ msg_warn("recipient %s: not found in %s",
+ state.msg_attr.user, virtual_uid_maps->title);
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ RETURN(YES);
+ }
+ if ((n = atol(uid_res)) < var_virt_minimum_uid) {
+ msg_warn("recipient %s: bad uid %s in %s",
+ state.msg_attr.user, uid_res, virtual_uid_maps->title);
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ RETURN(YES);
+ }
+ usr_attr.uid = (uid_t) n;
+
+ /*
+ * Look up the mailbox group rights. Defer in case of trouble.
+ */
+ gid_res = mail_addr_find(virtual_gid_maps, state.msg_attr.user,
+ IGNORE_EXTENSION);
+ if (gid_res == 0) {
+ msg_warn("recipient %s: not found in %s",
+ state.msg_attr.user, virtual_gid_maps->title);
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ RETURN(YES);
+ }
+ if ((n = atol(gid_res)) <= 0) {
+ msg_warn("recipient %s: bad gid %s in %s",
+ state.msg_attr.user, gid_res, virtual_gid_maps->title);
+ dsb_simple(why, "4.3.5", "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ RETURN(YES);
+ }
+ usr_attr.gid = (gid_t) n;
+
+ if (msg_verbose)
+ msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u",
+ myname, state.level, usr_attr.mailbox,
+ (unsigned) usr_attr.uid, (unsigned) usr_attr.gid);
+
+ /*
+ * Deliver to mailbox or to maildir.
+ */
+#define LAST_CHAR(s) (s[strlen(s) - 1])
+
+ if (LAST_CHAR(usr_attr.mailbox) == '/')
+ *statusp = deliver_maildir(state, usr_attr);
+ else
+ *statusp = deliver_mailbox_file(state, usr_attr);
+
+ /*
+ * Cleanup.
+ */
+ RETURN(YES);
+}
diff --git a/src/virtual/maildir.c b/src/virtual/maildir.c
new file mode 100644
index 0000000..a677061
--- /dev/null
+++ b/src/virtual/maildir.c
@@ -0,0 +1,254 @@
+/*++
+/* NAME
+/* maildir 3
+/* SUMMARY
+/* delivery to maildir
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_maildir(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* DESCRIPTION
+/* deliver_maildir() delivers a message to a qmail-style maildir.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* .IP usr_attr
+/* Attributes describing user rights and environment information.
+/* DIAGNOSTICS
+/* deliver_maildir() always succeeds or it bounces the message.
+/* SEE ALSO
+/* bounce(3)
+/* 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>
+#include <sys/time.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <make_dirs.h>
+#include <set_eugid.h>
+#include <get_hostname.h>
+#include <sane_fsops.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <bounce.h>
+#include <defer.h>
+#include <sent.h>
+#include <mail_params.h>
+#include <mbox_open.h>
+#include <dsn_util.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_maildir - delivery to maildir-style mailbox */
+
+int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_maildir";
+ char *newdir;
+ char *tmpdir;
+ char *curdir;
+ char *tmpfile;
+ char *newfile;
+ DSN_BUF *why = state.msg_attr.why;
+ VSTRING *buf;
+ VSTREAM *dst;
+ int mail_copy_status;
+ int deliver_status;
+ int copy_flags;
+ struct stat st;
+ struct timeval starttime;
+
+ GETTIMEOFDAY(&starttime);
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Don't deliver trace-only requests.
+ */
+ if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
+ dsb_simple(why, "2.0.0", "delivers to maildir");
+ return (sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+ buf = vstring_alloc(100);
+
+ copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH
+ | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT;
+
+ newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0);
+ tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0);
+ curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0);
+
+ /*
+ * Create and write the file as the recipient, so that file quota work.
+ * Create any missing directories on the fly. The file name is chosen
+ * according to ftp://koobera.math.uic.edu/www/proto/maildir.html:
+ *
+ * "A unique name has three pieces, separated by dots. On the left is the
+ * result of time(). On the right is the result of gethostname(). In the
+ * middle is something that doesn't repeat within one second on a single
+ * host. I fork a new process for each delivery, so I just use the
+ * process ID. If you're delivering several messages from one process,
+ * use starttime.pid_count.host, where starttime is the time that your
+ * process started, and count is the number of messages you've
+ * delivered."
+ *
+ * Well, that stopped working on fast machines, and on operating systems
+ * that randomize process ID values. When creating a file in tmp/ we use
+ * the process ID because it still is an exclusive resource. When moving
+ * the file to new/ we use the device number and inode number. I do not
+ * care if this breaks on a remote AFS file system, because people should
+ * know better.
+ *
+ * On January 26, 2003, http://cr.yp.to/proto/maildir.html said:
+ *
+ * A unique name has three pieces, separated by dots. On the left is the
+ * result of time() or the second counter from gettimeofday(). On the
+ * right is the result of gethostname(). (To deal with invalid host
+ * names, replace / with \057 and : with \072.) In the middle is a
+ * delivery identifier, discussed below.
+ *
+ * [...]
+ *
+ * Modern delivery identifiers are created by concatenating enough of the
+ * following strings to guarantee uniqueness:
+ *
+ * [...]
+ *
+ * In, where n is (in hexadecimal) the UNIX inode number of this file.
+ * Unfortunately, inode numbers aren't always available through NFS.
+ *
+ * Vn, where n is (in hexadecimal) the UNIX device number of this file.
+ * Unfortunately, device numbers aren't always available through NFS.
+ * (Device numbers are also not helpful with the standard UNIX
+ * filesystem: a maildir has to be within a single UNIX device for link()
+ * and rename() to work.)
+ *
+ * Mn, where n is (in decimal) the microsecond counter from the same
+ * gettimeofday() used for the left part of the unique name.
+ *
+ * Pn, where n is (in decimal) the process ID.
+ *
+ * [...]
+ */
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ vstring_sprintf(buf, "%lu.P%d.%s",
+ (unsigned long) starttime.tv_sec, var_pid, get_hostname());
+ tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
+ newfile = 0;
+ if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
+ && (errno != ENOENT
+ || make_dirs(tmpdir, 0700) < 0
+ || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) {
+ dsb_simple(why, mbox_dsn(errno, "4.2.0"),
+ "create maildir file %s: %m", tmpfile);
+ } else if (fstat(vstream_fileno(dst), &st) < 0) {
+
+ /*
+ * Coverity 200604: file descriptor leak in code that never executes.
+ * Code replaced by msg_fatal(), as it is not worthwhile to continue
+ * after an impossible error condition.
+ */
+ msg_fatal("fstat %s: %m", tmpfile);
+ } else {
+ vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
+ (unsigned long) starttime.tv_sec,
+ (unsigned long) st.st_dev,
+ (unsigned long) st.st_ino,
+ (unsigned long) starttime.tv_usec,
+ get_hostname());
+ newfile = concatenate(newdir, STR(buf), (char *) 0);
+ if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
+ dst, copy_flags, "\n",
+ why)) == 0) {
+ if (sane_link(tmpfile, newfile) < 0
+ && (errno != ENOENT
+ || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0
+ || sane_link(tmpfile, newfile) < 0)) {
+ dsb_simple(why, mbox_dsn(errno, "4.2.0"),
+ "create maildir file %s: %m", newfile);
+ mail_copy_status = MAIL_COPY_STAT_WRITE;
+ }
+ }
+ if (unlink(tmpfile) < 0)
+ msg_warn("remove %s: %m", tmpfile);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * The maildir location is controlled by the mail administrator. If
+ * delivery fails, try again later. We would just bounce when the maildir
+ * location possibly under user control.
+ */
+ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
+ deliver_status = DEL_STAT_DEFER;
+ } else if (mail_copy_status != 0) {
+ if (errno == EACCES) {
+ msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",
+ (long) usr_attr.uid, (long) usr_attr.gid,
+ STR(why->reason));
+ msg_warn("perhaps you need to create the maildirs in advance");
+ }
+ vstring_sprintf_prepend(why->reason, "maildir delivery failed: ");
+ deliver_status =
+ (STR(why->status)[0] == '4' ?
+ defer_append : bounce_append)
+ (BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+ dsb_simple(why, "2.0.0", "delivered to maildir");
+ deliver_status = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ }
+ vstring_free(buf);
+ myfree(newdir);
+ myfree(tmpdir);
+ myfree(curdir);
+ myfree(tmpfile);
+ if (newfile)
+ myfree(newfile);
+ return (deliver_status);
+}
diff --git a/src/virtual/recipient.c b/src/virtual/recipient.c
new file mode 100644
index 0000000..fd151f2
--- /dev/null
+++ b/src/virtual/recipient.c
@@ -0,0 +1,94 @@
+/*++
+/* NAME
+/* recipient 3
+/* SUMMARY
+/* deliver to one local recipient
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_recipient(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR *usr_attr;
+/* DESCRIPTION
+/* deliver_recipient() delivers a message to a local recipient.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender, and more.
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* DIAGNOSTICS
+/* deliver_recipient() returns non-zero when delivery should be
+/* tried again.
+/* SEE ALSO
+/* mailbox(3) delivery to UNIX-style mailbox
+/* maildir(3) delivery to qmail-style maildir
+/* 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 <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <bounce.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_recipient - deliver one local recipient */
+
+int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_recipient";
+ VSTRING *folded;
+ int rcpt_stat;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Set up the recipient-specific attributes. The recipient's lookup
+ * handle is the full address.
+ */
+ if (state.msg_attr.delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ folded = vstring_alloc(100);
+ state.msg_attr.user = casefold(folded, state.msg_attr.rcpt.address);
+
+ /*
+ * Deliver
+ */
+ if (msg_verbose)
+ deliver_attr_dump(&state.msg_attr);
+
+ if (deliver_mailbox(state, usr_attr, &rcpt_stat) == 0)
+ rcpt_stat = deliver_unknown(state);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(folded);
+
+ return (rcpt_stat);
+}
diff --git a/src/virtual/unknown.c b/src/virtual/unknown.c
new file mode 100644
index 0000000..ad8d64a
--- /dev/null
+++ b/src/virtual/unknown.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* unknown 3
+/* SUMMARY
+/* delivery of unknown recipients
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_unknown(state)
+/* LOCAL_STATE state;
+/* DESCRIPTION
+/* deliver_unknown() delivers a message for unknown recipients.
+/* .PP
+/* Arguments:
+/* .IP state
+/* Message delivery attributes (sender, recipient etc.).
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* DIAGNOSTICS
+/* The result status is non-zero when delivery should be tried again.
+/* 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>
+
+/* Global library. */
+
+#include <bounce.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_unknown - delivery for unknown recipients */
+
+int deliver_unknown(LOCAL_STATE state)
+{
+ const char *myname = "deliver_unknown";
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ dsb_simple(state.msg_attr.why, "5.1.1",
+ "unknown user: \"%s\"", state.msg_attr.user);
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+}
diff --git a/src/virtual/virtual.c b/src/virtual/virtual.c
new file mode 100644
index 0000000..6fa9f1e
--- /dev/null
+++ b/src/virtual/virtual.c
@@ -0,0 +1,573 @@
+/*++
+/* NAME
+/* virtual 8
+/* SUMMARY
+/* Postfix virtual domain mail delivery agent
+/* SYNOPSIS
+/* \fBvirtual\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBvirtual\fR(8) delivery agent is designed for virtual mail
+/* hosting services. Originally based on the Postfix \fBlocal\fR(8)
+/* delivery
+/* agent, this agent looks up recipients with map lookups of their
+/* full recipient address, instead of using hard-coded unix password
+/* file lookups of the address local part only.
+/*
+/* This delivery agent only delivers mail. Other features such as
+/* mail forwarding, out-of-office notifications, etc., must be
+/* configured via virtual_alias maps or via similar lookup mechanisms.
+/* MAILBOX LOCATION
+/* .ad
+/* .fi
+/* The mailbox location is controlled by the \fBvirtual_mailbox_base\fR
+/* and \fBvirtual_mailbox_maps\fR configuration parameters (see below).
+/* The \fBvirtual_mailbox_maps\fR table is indexed by the recipient
+/* address as described under TABLE SEARCH ORDER below.
+/*
+/* The mailbox pathname is constructed as follows:
+/*
+/* .nf
+/* \fB$virtual_mailbox_base/$virtual_mailbox_maps(\fIrecipient\fB)\fR
+/* .fi
+/*
+/* where \fIrecipient\fR is the full recipient address.
+/* UNIX MAILBOX FORMAT
+/* .ad
+/* .fi
+/* When the mailbox location does not end in \fB/\fR, the message
+/* is delivered in UNIX mailbox format. This format stores multiple
+/* messages in one textfile.
+/*
+/* The \fBvirtual\fR(8) delivery agent prepends a "\fBFrom \fIsender
+/* time_stamp\fR" envelope header to each message, prepends a
+/* \fBDelivered-To:\fR message header with the envelope recipient
+/* address,
+/* prepends an \fBX-Original-To:\fR header with the recipient address as
+/* given to Postfix,
+/* prepends a \fBReturn-Path:\fR message header with the
+/* envelope sender address, prepends a \fB>\fR character to lines
+/* beginning with "\fBFrom \fR", and appends an empty line.
+/*
+/* The mailbox is locked for exclusive access while delivery is in
+/* progress. In case of problems, an attempt is made to truncate the
+/* mailbox to its original length.
+/* QMAIL MAILDIR FORMAT
+/* .ad
+/* .fi
+/* When the mailbox location ends in \fB/\fR, the message is delivered
+/* in qmail \fBmaildir\fR format. This format stores one message per file.
+/*
+/* The \fBvirtual\fR(8) delivery agent prepends a \fBDelivered-To:\fR
+/* message header with the final envelope recipient address,
+/* prepends an \fBX-Original-To:\fR header with the recipient address as
+/* given to Postfix, and prepends a
+/* \fBReturn-Path:\fR message header with the envelope sender address.
+/*
+/* By definition, \fBmaildir\fR format does not require application-level
+/* file locking during mail delivery or retrieval.
+/* MAILBOX OWNERSHIP
+/* .ad
+/* .fi
+/* Mailbox ownership is controlled by the \fBvirtual_uid_maps\fR
+/* and \fBvirtual_gid_maps\fR lookup tables, which are indexed
+/* with the full recipient address. Each table provides
+/* a string with the numerical user and group ID, respectively.
+/*
+/* The \fBvirtual_minimum_uid\fR parameter imposes a lower bound on
+/* numerical user ID values that may be specified in any
+/* \fBvirtual_uid_maps\fR.
+/* CASE FOLDING
+/* .ad
+/* .fi
+/* All delivery decisions are made using the full recipient
+/* address, folded to lower case. See also the next section
+/* for a few exceptions with optional address extensions.
+/* TABLE SEARCH ORDER
+/* .ad
+/* .fi
+/* Normally, a lookup table is specified as a text file that
+/* serves as input to the \fBpostmap\fR(1) command. The result, an
+/* indexed file in \fBdbm\fR or \fBdb\fR format, is used for fast
+/* searching by the mail system.
+/*
+/* The search order is as follows. The search stops
+/* upon the first successful lookup.
+/* .IP \(bu
+/* When the recipient has an optional address extension the
+/* \fIuser+extension@domain.tld\fR address is looked up first.
+/* .sp
+/* With Postfix versions before 2.1, the optional address extension
+/* is always ignored.
+/* .IP \(bu
+/* The \fIuser@domain.tld\fR address, without address extension,
+/* is looked up next.
+/* .IP \(bu
+/* Finally, the recipient \fI@domain\fR is looked up.
+/* .PP
+/* When the table is provided via other means such as NIS, LDAP
+/* or SQL, the same lookups are done as for ordinary indexed files.
+/*
+/* Alternatively, a table can be provided as a regular-expression
+/* map where patterns are given as regular expressions. In that case,
+/* only the full recipient address is given to the regular-expression
+/* map.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBvirtual\fR(8) delivery agent is not security sensitive, provided
+/* that the lookup tables with recipient user/group ID information are
+/* adequately protected. This program is not designed to run chrooted.
+/*
+/* The \fBvirtual\fR(8) delivery agent disallows regular expression
+/* substitution of $1 etc. in regular expression lookup tables,
+/* because that would open a security hole.
+/*
+/* The \fBvirtual\fR(8) delivery agent will silently ignore requests
+/* to use the \fBproxymap\fR(8) server. Instead it will open the
+/* table directly. Before Postfix version 2.2, the virtual
+/* delivery agent will terminate with a fatal error.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* DIAGNOSTICS
+/* Mail bounces when the recipient has no mailbox or when the
+/* recipient is over disk quota. In all other problem cases, mail for
+/* an existing recipient is deferred and a warning is logged.
+/*
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are marked so that the queue
+/* manager can move them to the \fBcorrupt\fR queue afterwards.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* BUGS
+/* This delivery agent supports address extensions in email
+/* addresses and in lookup table keys, but does not propagate
+/* address extension information to the result of table lookup.
+/*
+/* Postfix should have lookup tables that can return multiple result
+/* attributes. In order to avoid the inconvenience of maintaining
+/* three tables, use an LDAP or MYSQL database.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as
+/* \fBvirtual\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.
+/* MAILBOX DELIVERY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBvirtual_mailbox_base (empty)\fR"
+/* A prefix that the \fBvirtual\fR(8) delivery agent prepends to all pathname
+/* results from $virtual_mailbox_maps table lookups.
+/* .IP "\fBvirtual_mailbox_maps (empty)\fR"
+/* Optional lookup tables with all valid addresses in the domains that
+/* match $virtual_mailbox_domains.
+/* .IP "\fBvirtual_minimum_uid (100)\fR"
+/* The minimum user ID value that the \fBvirtual\fR(8) delivery agent accepts
+/* as a result from $virtual_uid_maps table lookup.
+/* .IP "\fBvirtual_uid_maps (empty)\fR"
+/* Lookup tables with the per-recipient user ID that the \fBvirtual\fR(8)
+/* delivery agent uses while writing to the recipient's mailbox.
+/* .IP "\fBvirtual_gid_maps (empty)\fR"
+/* Lookup tables with the per-recipient group ID for \fBvirtual\fR(8) mailbox
+/* delivery.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR"
+/* Postfix is the final destination for the specified list of domains;
+/* mail is delivered via the $virtual_transport mail delivery transport.
+/* .IP "\fBvirtual_transport (virtual)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* final delivery to domains listed with $virtual_mailbox_domains.
+/* .PP
+/* Available in Postfix version 2.5.3 and later:
+/* .IP "\fBstrict_mailbox_ownership (yes)\fR"
+/* Defer delivery when a mailbox file is not owned by its recipient.
+/* LOCKING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBvirtual_mailbox_lock (see 'postconf -d' output)\fR"
+/* How to lock a UNIX-style \fBvirtual\fR(8) mailbox before attempting
+/* delivery.
+/* .IP "\fBdeliver_lock_attempts (20)\fR"
+/* The maximal number of attempts to acquire an exclusive lock on a
+/* mailbox file or \fBbounce\fR(8) logfile.
+/* .IP "\fBdeliver_lock_delay (1s)\fR"
+/* The time between attempts to acquire an exclusive lock on a mailbox
+/* file or \fBbounce\fR(8) logfile.
+/* .IP "\fBstale_lock_time (500s)\fR"
+/* The time after which a stale exclusive mailbox lockfile is removed.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBvirtual_mailbox_limit (51200000)\fR"
+/* The maximal size in bytes of an individual \fBvirtual\fR(8) mailbox or
+/* maildir file, or zero (no limit).
+/* .PP
+/* Implemented in the qmgr(8) daemon:
+/* .IP "\fBvirtual_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* The maximal number of parallel deliveries to the same destination
+/* via the virtual message delivery transport.
+/* .IP "\fBvirtual_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* The maximal number of recipients per message for the virtual
+/* message delivery transport.
+/* 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 "\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 "\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 "\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 3.0 and later:
+/* .IP "\fBvirtual_delivery_status_filter ($default_delivery_status_filter)\fR"
+/* Optional filter for the \fBvirtual\fR(8) delivery agent to change the
+/* delivery status code or explanatory text of successful or unsuccessful
+/* deliveries.
+/* .PP
+/* Available in Postfix version 3.3 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).
+/* .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.).
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* postconf(5), configuration parameters
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README_FILES
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* VIRTUAL_README, domain hosting howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This delivery agent was originally based on the Postfix local delivery
+/* agent. Modifications mainly consisted of removing code that either
+/* was not applicable or that was not safe in this context: aliases,
+/* ~user/.forward files, delivery to "|command" or to /file/name.
+/*
+/* The \fBDelivered-To:\fR message header appears in the \fBqmail\fR
+/* system by Daniel Bernstein.
+/*
+/* The \fBmaildir\fR structure appears in the \fBqmail\fR system
+/* by Daniel Bernstein.
+/* 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
+/*
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney 2060, NSW, Australia
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#ifdef USE_PATHS_H
+#include <paths.h> /* XXX mail_spool_dir dependency */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <set_eugid.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <deliver_completed.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_addr_find.h>
+#include <flush_clnt.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+ /*
+ * Tunable parameters.
+ */
+char *var_virt_mailbox_maps;
+char *var_virt_uid_maps;
+char *var_virt_gid_maps;
+int var_virt_minimum_uid;
+char *var_virt_mailbox_base;
+char *var_virt_mailbox_lock;
+long var_virt_mailbox_limit;
+char *var_mail_spool_dir; /* XXX dependency fix */
+bool var_strict_mbox_owner;
+char *var_virt_dsn_filter;
+
+ /*
+ * Mappings.
+ */
+MAPS *virtual_mailbox_maps;
+MAPS *virtual_uid_maps;
+MAPS *virtual_gid_maps;
+
+ /*
+ * Bit masks.
+ */
+int virtual_mbox_lock_mask;
+
+/* local_deliver - deliver message with extreme prejudice */
+
+static int local_deliver(DELIVER_REQUEST *rqst, char *service)
+{
+ const char *myname = "local_deliver";
+ RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len;
+ RECIPIENT *rcpt;
+ int rcpt_stat;
+ int msg_stat;
+ LOCAL_STATE state;
+ USER_ATTR usr_attr;
+
+ if (msg_verbose)
+ msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender);
+
+ /*
+ * Initialize the delivery attributes that are not recipient specific.
+ */
+ state.level = 0;
+ deliver_attr_init(&state.msg_attr);
+ state.msg_attr.queue_name = rqst->queue_name;
+ state.msg_attr.queue_id = rqst->queue_id;
+ state.msg_attr.fp = rqst->fp;
+ state.msg_attr.offset = rqst->data_offset;
+ state.msg_attr.sender = rqst->sender;
+ state.msg_attr.dsn_envid = rqst->dsn_envid;
+ state.msg_attr.dsn_ret = rqst->dsn_ret;
+ state.msg_attr.relay = service;
+ state.msg_attr.msg_stats = rqst->msg_stats;
+ RESET_USER_ATTR(usr_attr, state.level);
+ state.request = rqst;
+
+ /*
+ * Iterate over each recipient named in the delivery request. When the
+ * mail delivery status for a given recipient is definite (i.e. bounced
+ * or delivered), update the message queue file and cross off the
+ * recipient. Update the per-message delivery status.
+ */
+ for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) {
+ state.msg_attr.rcpt = *rcpt;
+ rcpt_stat = deliver_recipient(state, usr_attr);
+ if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS))
+ deliver_completed(state.msg_attr.fp, rcpt->offset);
+ msg_stat |= rcpt_stat;
+ }
+
+ deliver_attr_free(&state.msg_attr);
+ return (msg_stat);
+}
+
+/* local_service - perform service for client */
+
+static void local_service(VSTREAM *stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * that is dedicated to local mail delivery service. What we see below is
+ * a little protocol to (1) tell the client that we are ready, (2) read a
+ * delivery request from the client, and (3) report the completion status
+ * of that request.
+ */
+ if ((request = deliver_request_read(stream)) != 0) {
+ status = local_deliver(request, service);
+ deliver_request_done(stream, request, status);
+ }
+}
+
+/* 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);
+ }
+}
+
+/* post_init - post-jail initialization */
+
+static void post_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Drop privileges most of the time.
+ */
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * No case folding needed: the recipient address is case folded.
+ */
+ virtual_mailbox_maps =
+ maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_PARANOID
+ | DICT_FLAG_UTF8_REQUEST);
+
+ virtual_uid_maps =
+ maps_create(VAR_VIRT_UID_MAPS, var_virt_uid_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_PARANOID
+ | DICT_FLAG_UTF8_REQUEST);
+
+ virtual_gid_maps =
+ maps_create(VAR_VIRT_GID_MAPS, var_virt_gid_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_PARANOID
+ | DICT_FLAG_UTF8_REQUEST);
+
+ virtual_mbox_lock_mask = mbox_lock_mask(var_virt_mailbox_lock);
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Reset the file size limit from the message size limit to the mailbox
+ * size limit.
+ *
+ * We can't have mailbox size limit smaller than the message size limit,
+ * because that prohibits the delivery agent from updating the queue
+ * file.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_virt_mailbox_limit)) {
+ if (!ENFORCING_SIZE_LIMIT(var_message_limit))
+ msg_fatal("configuration error: %s is limited but %s is "
+ "unlimited", VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ if (var_virt_mailbox_limit < var_message_limit)
+ msg_fatal("configuration error: %s is smaller than %s",
+ VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ set_file_limit(var_virt_mailbox_limit);
+ }
+
+ /*
+ * flush client.
+ */
+ flush_init();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_VIRT_MINUID, DEF_VIRT_MINUID, &var_virt_minimum_uid, 1, 0,
+ 0,
+ };
+ static const CONFIG_LONG_TABLE long_table[] = {
+ VAR_VIRT_MAILBOX_LIMIT, DEF_VIRT_MAILBOX_LIMIT, &var_virt_mailbox_limit, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_MAIL_SPOOL_DIR, DEF_MAIL_SPOOL_DIR, &var_mail_spool_dir, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_VIRT_UID_MAPS, DEF_VIRT_UID_MAPS, &var_virt_uid_maps, 0, 0,
+ VAR_VIRT_GID_MAPS, DEF_VIRT_GID_MAPS, &var_virt_gid_maps, 0, 0,
+ VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 1, 0,
+ VAR_VIRT_MAILBOX_LOCK, DEF_VIRT_MAILBOX_LOCK, &var_virt_mailbox_lock, 1, 0,
+ VAR_VIRT_DSN_FILTER, DEF_VIRT_DSN_FILTER, &var_virt_dsn_filter, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_STRICT_MBOX_OWNER, DEF_STRICT_MBOX_OWNER, &var_strict_mbox_owner,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, local_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_LONG_TABLE(long_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ CA_MAIL_SERVER_POST_INIT(post_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_PRIVILEGED,
+ CA_MAIL_SERVER_BOUNCE_INIT(VAR_VIRT_DSN_FILTER,
+ &var_virt_dsn_filter),
+ 0);
+}
diff --git a/src/virtual/virtual.h b/src/virtual/virtual.h
new file mode 100644
index 0000000..75dd6cd
--- /dev/null
+++ b/src/virtual/virtual.h
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* virtual 3h
+/* SUMMARY
+/* virtual mail delivery
+/* SYNOPSIS
+/* #include "virtual.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <maps.h>
+#include <mbox_conf.h>
+#include <dsn_buf.h>
+#include <dsn.h>
+
+ /*
+ * Mappings.
+ */
+extern MAPS *virtual_mailbox_maps;
+extern MAPS *virtual_uid_maps;
+extern MAPS *virtual_gid_maps;
+
+ /*
+ * User attributes: these control the privileges for delivery to external
+ * commands, external files, or mailboxes, and the initial environment of
+ * external commands.
+ */
+typedef struct USER_ATTR {
+ uid_t uid; /* file/command access */
+ gid_t gid; /* file/command access */
+ char *mailbox; /* mailbox file or directory */
+} USER_ATTR;
+
+ /*
+ * Critical macros. Not for obscurity, but to ensure consistency.
+ */
+#define RESET_USER_ATTR(usr_attr, level) { \
+ usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.mailbox = 0; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: reset user_attr", myname, level); \
+ }
+
+ /*
+ * The delivery attributes are inherited from files, from aliases, and from
+ * whatnot. Some of the information is changed on the fly. DELIVER_ATTR
+ * structures are therefore passed by value, so there is no need to undo
+ * changes.
+ */
+typedef struct DELIVER_ATTR {
+ int level; /* recursion level */
+ VSTREAM *fp; /* open queue file */
+ char *queue_name; /* mail queue id */
+ char *queue_id; /* mail queue id */
+ long offset; /* data offset */
+ const char *sender; /* taken from envelope */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN headers/full */
+ RECIPIENT rcpt; /* from delivery request */
+ char *user; /* recipient lookup handle */
+ const char *delivered; /* for loop detection */
+ char *relay; /* relay host */
+ MSG_STATS msg_stats; /* time profile */
+ DSN_BUF *why; /* delivery status */
+} DELIVER_ATTR;
+
+extern void deliver_attr_init(DELIVER_ATTR *);
+extern void deliver_attr_dump(DELIVER_ATTR *);
+extern void deliver_attr_free(DELIVER_ATTR *);
+
+#define FEATURE_NODELIVERED (1<<0) /* no delivered-to */
+
+ /*
+ * Rather than schlepping around dozens of arguments, here is one that has
+ * all. Well, almost. The user attributes are just a bit too sensitive, so
+ * they are passed around separately.
+ */
+typedef struct LOCAL_STATE {
+ int level; /* nesting level, for logging */
+ DELIVER_ATTR msg_attr; /* message/recipient attributes */
+ DELIVER_REQUEST *request; /* as from queue manager */
+} LOCAL_STATE;
+
+ /*
+ * Bundle up some often-user attributes.
+ */
+#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS((request)->flags)
+
+#define BOUNCE_ATTR(attr) \
+ attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \
+ DSN_FROM_DSN_BUF(attr.why)
+#define SENT_ATTR(attr) \
+ attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \
+ DSN_FROM_DSN_BUF(attr.why)
+#define COPY_ATTR(attr) \
+ attr.sender, attr.rcpt.orig_addr, attr.delivered, attr.fp
+
+#define MSG_LOG_STATE(m, p) \
+ msg_info("%s[%d]: recip %s deliver %s", m, \
+ p.level, \
+ p.msg_attr.rcpt.address ? p.msg_attr.rcpt.address : "", \
+ p.msg_attr.delivered ? p.msg_attr.delivered : "")
+
+ /*
+ * "inner" nodes of the delivery graph.
+ */
+extern int deliver_recipient(LOCAL_STATE, USER_ATTR);
+
+ /*
+ * "leaf" nodes of the delivery graph.
+ */
+extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *);
+extern int deliver_file(LOCAL_STATE, USER_ATTR, char *);
+extern int deliver_maildir(LOCAL_STATE, USER_ATTR);
+extern int deliver_unknown(LOCAL_STATE);
+
+ /*
+ * Mailbox lock protocol.
+ */
+extern int virtual_mbox_lock_mask;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+/* 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
+/*--*/
diff --git a/src/xsasl/.indent.pro b/src/xsasl/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/xsasl/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/xsasl/Makefile.in b/src/xsasl/Makefile.in
new file mode 100644
index 0000000..ad48302
--- /dev/null
+++ b/src/xsasl/Makefile.in
@@ -0,0 +1,165 @@
+SHELL = /bin/sh
+SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \
+ xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \
+ xsasl_dovecot_server.c
+OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \
+ xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \
+ xsasl_dovecot_server.o
+HDRS = xsasl.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = libxsasl.a
+TESTPROG=
+
+LIBS = ../../lib/lib$(LIB_PERFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PERFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(_AR) $(ARFL) $(LIB) $?
+ $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(_RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+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 $(LIB) *core $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+foo: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+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'
+xsasl_client.o: ../../include/argv.h
+xsasl_client.o: ../../include/check_arg.h
+xsasl_client.o: ../../include/msg.h
+xsasl_client.o: ../../include/mymalloc.h
+xsasl_client.o: ../../include/sys_defs.h
+xsasl_client.o: ../../include/vbuf.h
+xsasl_client.o: ../../include/vstream.h
+xsasl_client.o: ../../include/vstring.h
+xsasl_client.o: xsasl.h
+xsasl_client.o: xsasl_client.c
+xsasl_client.o: xsasl_cyrus.h
+xsasl_cyrus_client.o: ../../include/argv.h
+xsasl_cyrus_client.o: ../../include/check_arg.h
+xsasl_cyrus_client.o: ../../include/mail_params.h
+xsasl_cyrus_client.o: ../../include/msg.h
+xsasl_cyrus_client.o: ../../include/mymalloc.h
+xsasl_cyrus_client.o: ../../include/stringops.h
+xsasl_cyrus_client.o: ../../include/sys_defs.h
+xsasl_cyrus_client.o: ../../include/vbuf.h
+xsasl_cyrus_client.o: ../../include/vstream.h
+xsasl_cyrus_client.o: ../../include/vstring.h
+xsasl_cyrus_client.o: xsasl.h
+xsasl_cyrus_client.o: xsasl_cyrus.h
+xsasl_cyrus_client.o: xsasl_cyrus_client.c
+xsasl_cyrus_client.o: xsasl_cyrus_common.h
+xsasl_cyrus_log.o: ../../include/msg.h
+xsasl_cyrus_log.o: ../../include/sys_defs.h
+xsasl_cyrus_log.o: xsasl_cyrus_common.h
+xsasl_cyrus_log.o: xsasl_cyrus_log.c
+xsasl_cyrus_security.o: ../../include/check_arg.h
+xsasl_cyrus_security.o: ../../include/name_mask.h
+xsasl_cyrus_security.o: ../../include/sys_defs.h
+xsasl_cyrus_security.o: ../../include/vbuf.h
+xsasl_cyrus_security.o: ../../include/vstring.h
+xsasl_cyrus_security.o: xsasl_cyrus_common.h
+xsasl_cyrus_security.o: xsasl_cyrus_security.c
+xsasl_cyrus_server.o: ../../include/argv.h
+xsasl_cyrus_server.o: ../../include/check_arg.h
+xsasl_cyrus_server.o: ../../include/mail_params.h
+xsasl_cyrus_server.o: ../../include/msg.h
+xsasl_cyrus_server.o: ../../include/mymalloc.h
+xsasl_cyrus_server.o: ../../include/name_mask.h
+xsasl_cyrus_server.o: ../../include/stringops.h
+xsasl_cyrus_server.o: ../../include/sys_defs.h
+xsasl_cyrus_server.o: ../../include/vbuf.h
+xsasl_cyrus_server.o: ../../include/vstream.h
+xsasl_cyrus_server.o: ../../include/vstring.h
+xsasl_cyrus_server.o: xsasl.h
+xsasl_cyrus_server.o: xsasl_cyrus.h
+xsasl_cyrus_server.o: xsasl_cyrus_common.h
+xsasl_cyrus_server.o: xsasl_cyrus_server.c
+xsasl_dovecot_server.o: ../../include/argv.h
+xsasl_dovecot_server.o: ../../include/check_arg.h
+xsasl_dovecot_server.o: ../../include/connect.h
+xsasl_dovecot_server.o: ../../include/iostuff.h
+xsasl_dovecot_server.o: ../../include/mail_params.h
+xsasl_dovecot_server.o: ../../include/msg.h
+xsasl_dovecot_server.o: ../../include/myaddrinfo.h
+xsasl_dovecot_server.o: ../../include/mymalloc.h
+xsasl_dovecot_server.o: ../../include/name_mask.h
+xsasl_dovecot_server.o: ../../include/split_at.h
+xsasl_dovecot_server.o: ../../include/stringops.h
+xsasl_dovecot_server.o: ../../include/sys_defs.h
+xsasl_dovecot_server.o: ../../include/vbuf.h
+xsasl_dovecot_server.o: ../../include/vstream.h
+xsasl_dovecot_server.o: ../../include/vstring.h
+xsasl_dovecot_server.o: ../../include/vstring_vstream.h
+xsasl_dovecot_server.o: xsasl.h
+xsasl_dovecot_server.o: xsasl_dovecot.h
+xsasl_dovecot_server.o: xsasl_dovecot_server.c
+xsasl_server.o: ../../include/argv.h
+xsasl_server.o: ../../include/check_arg.h
+xsasl_server.o: ../../include/msg.h
+xsasl_server.o: ../../include/mymalloc.h
+xsasl_server.o: ../../include/sys_defs.h
+xsasl_server.o: ../../include/vbuf.h
+xsasl_server.o: ../../include/vstream.h
+xsasl_server.o: ../../include/vstring.h
+xsasl_server.o: xsasl.h
+xsasl_server.o: xsasl_cyrus.h
+xsasl_server.o: xsasl_dovecot.h
+xsasl_server.o: xsasl_server.c
diff --git a/src/xsasl/README b/src/xsasl/README
new file mode 100644
index 0000000..d1b2f5a
--- /dev/null
+++ b/src/xsasl/README
@@ -0,0 +1,109 @@
+Purpose of this document
+========================
+
+This document describes how to add your own SASL implementation to
+Postfix. You don't have to provide both the server and client side.
+You can provide just one and omit the other. The examples below
+assume you do both.
+
+The plug-in API is described in cyrus_server.c and cyrus_client.c.
+It was unavoidably contaminated^h^h^h^h^h^h^h^h^h^h^h^hinfluenced
+by Cyrus SASL and may need revision as other implementations are
+added.
+
+For an example of how the plug-in interface is implemented, have a
+look at the xsasl/xsasl_cyrus_client.c and xsasl/xsasl_cyrus_server.c.
+
+Configuration features
+======================
+
+There are two configuration parameters that allow you to pass
+information from main.cf into the plug_in:
+
+ smtpd_sasl_path, smtpd_sasl_security_options
+ smtp_sasl_path, smtp_sasl_security_options
+ lmtp_sasl_path, lmtp_sasl_security_options
+
+As usual, newline characters are removed from multi-line parameter
+values, and $name is expanded recursively. The parameter values
+are passed to the plug-in without any further processing. The
+following restrictions are imposed by the main.cf file parser:
+
+- parameter values never contain newlines,
+
+- parameter values never start or end with whitespace characters.
+
+The _path parameter value is passed only once during process
+initialization (i.e. it is a class variable). The path typically
+specifies the location of a configuration file or rendez-vous point.
+The _security_options parameter value is passed each time SASL is
+turned on for a connection (i.e. it is an instance variable). The
+options may depend on whether or not TLS encryption is turned on.
+Remember that one Postfix process may perform up to 100 mail
+transactions during its life time. Things that happen in one
+transaction must not affect later transactions.
+
+Adding Postfix support for your own SASL implementation
+=======================================================
+
+To add your own SASL implementation, say, FOOBAR:
+
+- Copy xsasl/xsasl_cyrus.h to xsasl/xsasl_foobar.h and replace
+ CYRUS by FOOBAR:
+
+ #if defined(USE_SASL_AUTH) && defined(USE_FOOBAR_SASL)
+ /*
+ * SASL protocol interface
+ */
+ #define XSASL_TYPE_FOOBAR "foobar"
+ extern XSASL_SERVER_IMPL *xsasl_foobar_server_init(const char *, const char *);
+ extern XSASL_CLIENT_IMPL *xsasl_foobar_client_init(const char *, const char *);
+ #endif
+
+- Edit xsasl/xsasl_server.c, add your #include <xsasl_foobar.h> line
+ under #include <xsasl_cyrus.h> at the top, and add your initialization
+ function in the table at the bottom as shown below:
+
+ static XSASL_SERVER_IMPL_INFO server_impl_info[] = {
+ #ifdef XSASL_TYPE_CYRUS
+ XSASL_TYPE_CYRUS, xsasl_cyrus_server_init,
+ #endif
+ #ifdef XSASL_TYPE_FOOBAR
+ XSASL_TYPE_FOOBAR, xsasl_foobar_server_init,
+ #endif
+ 0,
+ };
+
+- Repeat the (almost) same procedure for xsasl/xsasl_client.c.
+
+- Create your own xsasl/xsasl_foobar_{client,server}.c and support
+ files. Perhaps it's convenient to copy the cyrus files, rip out
+ the function bodies, and replace CYRUS by FOOBAR.
+
+- List your source files in Makefile.in. Don't forget to do "make
+ depend" after you do "make makefiles" in the step that follows
+ after this one.
+
+ SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \
+ xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \
+ xsasl_foobar_client.c xsasl_foobar_server.c
+ OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \
+ xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \
+ xsasl_foobar_client.o xsasl_foobar_server.o
+
+- Create the Postfix makefiles from the top-level directory:
+
+ % make makefiles CCARGS='-DUSE_SASL_AUTH -DUSE_FOOBAR_SASL \
+ -DDEF_CLIENT_SASL_TYPE=\"foobar\" -DDEF_SERVER_SASL_TYPE=\"foobar\" \
+ -I/some/where/include' AUXLIBS='-L/some/where/lib -lfoobar'
+
+ Yes, you can have different default SASL implementation types for
+ the client and server plug-ins.
+
+ Of course you don't have to override the default SASL implementation
+ type; it is shown here as an example.
+
+
+- Don't forget to do "make depend" in the xsasl directory.
+
+- Document your build and configuration with a README document.
diff --git a/src/xsasl/xsasl.h b/src/xsasl/xsasl.h
new file mode 100644
index 0000000..b494d7e
--- /dev/null
+++ b/src/xsasl/xsasl.h
@@ -0,0 +1,146 @@
+#ifndef _XSASL_H_INCLUDED_
+#define _XSASL_H_INCLUDED_
+
+/*++
+/* NAME
+/* xsasl 3h
+/* SUMMARY
+/* Postfix SASL plug-in interface
+/* SYNOPSIS
+/* #include <xsasl.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Generic server object. Specific instances extend this with their own
+ * private data.
+ */
+typedef struct XSASL_SERVER {
+ void (*free) (struct XSASL_SERVER *);
+ int (*first) (struct XSASL_SERVER *, const char *, const char *, VSTRING *);
+ int (*next) (struct XSASL_SERVER *, const char *, VSTRING *);
+ const char *(*get_mechanism_list) (struct XSASL_SERVER *);
+ const char *(*get_username) (struct XSASL_SERVER *);
+} XSASL_SERVER;
+
+#define xsasl_server_free(server) (server)->free(server)
+#define xsasl_server_first(server, method, init_resp, reply) \
+ (server)->first((server), (method), (init_resp), (reply))
+#define xsasl_server_next(server, request, reply) \
+ (server)->next((server), (request), (reply))
+#define xsasl_server_get_mechanism_list(server) \
+ (server)->get_mechanism_list((server))
+#define xsasl_server_get_username(server) \
+ (server)->get_username((server))
+
+ /*
+ * Generic server implementation. Specific instances extend this with their
+ * own private data.
+ */
+typedef struct XSASL_SERVER_CREATE_ARGS {
+ VSTREAM *stream;
+ int addr_family;
+ const char *server_addr;
+ const char *server_port;
+ const char *client_addr;
+ const char *client_port;
+ const char *service;
+ const char *user_realm;
+ const char *security_options;
+ int tls_flag;
+} XSASL_SERVER_CREATE_ARGS;
+
+typedef struct XSASL_SERVER_IMPL {
+ XSASL_SERVER *(*create) (struct XSASL_SERVER_IMPL *, XSASL_SERVER_CREATE_ARGS *);
+ void (*done) (struct XSASL_SERVER_IMPL *);
+} XSASL_SERVER_IMPL;
+
+extern XSASL_SERVER_IMPL *xsasl_server_init(const char *, const char *);
+extern ARGV *xsasl_server_types(void);
+
+#define xsasl_server_create(impl, args) \
+ (impl)->create((impl), (args))
+#define XSASL_SERVER_CREATE(impl, args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+ xsasl_server_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \
+ ((args)->a4), ((args)->a5), ((args)->a6), ((args)->a7), ((args)->a8), \
+ ((args)->a9), ((args)->a10), (args)))
+#define xsasl_server_done(impl) (impl)->done((impl));
+
+ /*
+ * Generic client object. Specific instances extend this with their own
+ * private data.
+ */
+typedef struct XSASL_CLIENT {
+ void (*free) (struct XSASL_CLIENT *);
+ int (*first) (struct XSASL_CLIENT *, const char *, const char *, const char *, const char **, VSTRING *);
+ int (*next) (struct XSASL_CLIENT *, const char *, VSTRING *);
+} XSASL_CLIENT;
+
+#define xsasl_client_free(client) (client)->free(client)
+#define xsasl_client_first(client, server, method, user, pass, init_resp) \
+ (client)->first((client), (server), (method), (user), (pass), (init_resp))
+#define xsasl_client_next(client, request, reply) \
+ (client)->next((client), (request), (reply))
+#define xsasl_client_set_password(client, user, pass) \
+ (client)->set_password((client), (user), (pass))
+
+ /*
+ * Generic client implementation. Specific instances extend this with their
+ * own private data.
+ */
+typedef struct XSASL_CLIENT_CREATE_ARGS {
+ VSTREAM *stream;
+ const char *service;
+ const char *server_name;
+ const char *security_options;
+} XSASL_CLIENT_CREATE_ARGS;
+
+typedef struct XSASL_CLIENT_IMPL {
+ XSASL_CLIENT *(*create) (struct XSASL_CLIENT_IMPL *, XSASL_CLIENT_CREATE_ARGS *);
+ void (*done) (struct XSASL_CLIENT_IMPL *);
+} XSASL_CLIENT_IMPL;
+
+extern XSASL_CLIENT_IMPL *xsasl_client_init(const char *, const char *);
+extern ARGV *xsasl_client_types(void);
+
+#define xsasl_client_create(impl, args) \
+ (impl)->create((impl), (args))
+#define XSASL_CLIENT_CREATE(impl, args, a1, a2, a3, a4) \
+ xsasl_client_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \
+ ((args)->a4), (args)))
+#define xsasl_client_done(impl) (impl)->done((impl));
+
+ /*
+ * Status codes.
+ */
+#define XSASL_AUTH_OK 1 /* Success */
+#define XSASL_AUTH_MORE 2 /* Need another c/s protocol exchange */
+#define XSASL_AUTH_DONE 3 /* Authentication completed */
+#define XSASL_AUTH_FORM 4 /* Cannot decode response */
+#define XSASL_AUTH_FAIL 5 /* Error */
+#define XSASL_AUTH_TEMP 6 /* Temporary error condition */
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/xsasl/xsasl_client.c b/src/xsasl/xsasl_client.c
new file mode 100644
index 0000000..0bddd41
--- /dev/null
+++ b/src/xsasl/xsasl_client.c
@@ -0,0 +1,240 @@
+/*++
+/* NAME
+/* xsasl_client 3
+/* SUMMARY
+/* Postfix SASL client plug-in interface
+/* SYNOPSIS
+/* #include <xsasl.h>
+/*
+/* XSASL_CLIENT_IMPL *xsasl_client_init(client_type, path_info)
+/* const char *client_type;
+/* const char *path_info;
+/*
+/* void xsasl_client_done(implementation)
+/* XSASL_CLIENT_IMPL *implementation;
+/*
+/* ARGV *xsasl_client_types()
+/*
+/* .in +4
+/* typedef struct XSASL_CLIENT_CREATE_ARGS {
+/* VSTREAM *stream;
+/* const char *service;
+/* const char *server_name;
+/* const char *security_options;
+/* } XSASL_CLIENT_CREATE_ARGS;
+/* .in -4
+/*
+/* XSASL_CLIENT *xsasl_client_create(implementation, create_args)
+/* XSASL_CLIENT_IMPL *implementation;
+/* XSASL_CLIENT_CREATE_ARGS *create_args;
+/*
+/* XSASL_CLIENT *XSASL_CLIENT_CREATE(implementation, create_args,
+/* stream = stream_val,
+/* ...,
+/* security_options = prop_val)
+/* XSASL_CLIENT_IMPL *implementation;
+/* XSASL_CLIENT_CREATE_ARGS *create_args;
+/*
+/* void xsasl_client_free(client)
+/* XSASL_CLIENT *client;
+/*
+/* int xsasl_client_first(client, stream, mech_list, username,
+/* password, auth_method, init_resp)
+/* XSASL_CLIENT *client;
+/* const char *mech_list;
+/* const char *username;
+/* const char *password;
+/* const char **auth_method;
+/* VSTRING *init_resp;
+/*
+/* int xsasl_client_next(client, server_reply, client_reply)
+/* XSASL_CLIENT *client;
+/* const char *server_reply;
+/* VSTRING *client_reply;
+/* DESCRIPTION
+/* The XSASL_CLIENT abstraction implements a generic interface
+/* to one or more SASL authentication implementations.
+/*
+/* xsasl_client_init() is called once during process initialization.
+/* It selects a SASL implementation by name, specifies the
+/* location of a configuration file or rendez-vous point, and
+/* returns an implementation handle that can be used to generate
+/* SASL client instances. This function is typically used to
+/* initialize the underlying implementation.
+/*
+/* xsasl_client_done() disposes of an implementation handle,
+/* and allows the underlying implementation to release resources.
+/*
+/* xsasl_client_types() lists the available implementation types.
+/* The result should be destroyed by the caller.
+/*
+/* xsasl_client_create() is called at the start of an SMTP
+/* session. It generates a Postfix SASL plug-in client instance
+/* for the specified service and server name, with the specified
+/* security properties. The stream handle is stored so that
+/* encryption can be turned on after successful negotiations.
+/*
+/* XSASL_CLIENT_CREATE() is a macro that provides an interface
+/* with named parameters. Named parameters do not have to
+/* appear in a fixed order. The parameter names correspond to
+/* the member names of the XSASL_CLIENT_CREATE_ARGS structure.
+/*
+/* xsasl_client_free() is called at the end of an SMTP session.
+/* It destroys a SASL client instance, and disables further
+/* read/write operations if encryption was turned on.
+/*
+/* xsasl_client_first() produces the client input for the AUTH
+/* command. The input is an authentication method list from
+/* an EHLO response, a username and a password. On return, the
+/* method argument specifies the authentication method; storage
+/* space is owned by the underlying implementation. The initial
+/* response and client non-error replies are BASE64 encoded.
+/* Client error replies are 7-bit ASCII text without control
+/* characters, and without BASE64 encoding. They are meant for
+/* the local application, not for transmission to the server.
+/* The client may negotiate encryption of the client-server
+/* connection.
+/*
+/* The result is one of the following:
+/* .IP XSASL_AUTH_OK
+/* Success.
+/* .IP XSASL_AUTH_FORM
+/* The server reply is incorrectly formatted. The client error
+/* reply explains why.
+/* .IP XSASL_AUTH_FAIL
+/* Other error. The client error reply explains why.
+/* .PP
+/* xsasl_client_next() supports the subsequent stages of the
+/* AUTH protocol. Both the client reply and client non-error
+/* responses are BASE64 encoded. See xsasl_client_first() for
+/* other details.
+/*
+/* Arguments:
+/* .IP client
+/* SASL plug-in client handle.
+/* .IP client_reply
+/* BASE64 encoded non-error client reply, or ASCII error
+/* description for the user.
+/* .IP client_type
+/* The name of a Postfix SASL client plug_in implementation.
+/* .IP client_types
+/* Null-terminated array of strings with SASL client plug-in
+/* implementation names.
+/* .IP init_resp
+/* The AUTH command initial response.
+/* .IP implementation
+/* Implementation handle that was obtained with xsasl_client_init().
+/* .IP mech_list
+/* List of SASL mechanisms as announced by the server.
+/* .IP auth_method
+/* The AUTH command authentication method.
+/* .IP password
+/* Information from the Postfix SASL password file or equivalent.
+/* .IP path_info
+/* The value of the smtp_sasl_path parameter or equivalent.
+/* This specifies the implementation-dependent location of a
+/* configuration file, rendez-vous point, etc., and is passed
+/* unchanged to the plug-in.
+/* .IP security_options
+/* The value of the smtp_sasl_security_options parameter or
+/* equivalent. This is passed unchanged to the plug-in.
+/* .IP server_name
+/* The remote server fully qualified hostname.
+/* .IP server_reply
+/* BASE64 encoded server reply without SMTP reply code or
+/* enhanced status code.
+/* .IP service
+/* The service that is implemented by the local client (typically,
+/* "lmtp" or "smtp").
+/* .IP stream
+/* The connection between client and server.
+/* When SASL encryption is negotiated, the plug-in will
+/* transparently intercept the socket read/write operations.
+/* .IP username
+/* Information from the Postfix SASL password file.
+/* SECURITY
+/* .ad
+/* .fi
+/* The caller does not sanitize the server reply. It is the
+/* responsibility of the underlying SASL client implementation
+/* to produce 7-bit ASCII without control characters as client
+/* non-error and error replies.
+/* DIAGNOSTICS
+/* In case of error, xsasl_client_init() and xsasl_client_create()
+/* log a warning and return a null pointer.
+/*
+/* Functions that normally return XSASL_AUTH_OK will log a warning
+/* and return an appropriate result value.
+/*
+/* Panic: interface violation.
+/*
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* cyrus_security(3) Cyrus SASL security features
+/* 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 <mymalloc.h>
+
+/* SASL implementations. */
+
+#include <xsasl.h>
+#include <xsasl_cyrus.h>
+
+ /*
+ * Lookup table for available SASL client implementations.
+ */
+typedef struct {
+ char *client_type;
+ struct XSASL_CLIENT_IMPL *(*client_init) (const char *, const char *);
+} XSASL_CLIENT_IMPL_INFO;
+
+static const XSASL_CLIENT_IMPL_INFO client_impl_info[] = {
+#ifdef XSASL_TYPE_CYRUS
+ XSASL_TYPE_CYRUS, xsasl_cyrus_client_init,
+#endif
+ 0,
+};
+
+/* xsasl_client_init - look up client implementation by name */
+
+XSASL_CLIENT_IMPL *xsasl_client_init(const char *client_type,
+ const char *path_info)
+{
+ const XSASL_CLIENT_IMPL_INFO *xp;
+
+ for (xp = client_impl_info; xp->client_type; xp++)
+ if (strcmp(client_type, xp->client_type) == 0)
+ return (xp->client_init(client_type, path_info));
+ msg_warn("unsupported SASL client implementation: %s", client_type);
+ return (0);
+}
+
+/* xsasl_client_types - report available implementation types */
+
+ARGV *xsasl_client_types(void)
+{
+ const XSASL_CLIENT_IMPL_INFO *xp;
+ ARGV *argv = argv_alloc(1);
+
+ for (xp = client_impl_info; xp->client_type; xp++)
+ argv_add(argv, xp->client_type, ARGV_END);
+ return (argv);
+}
diff --git a/src/xsasl/xsasl_cyrus.h b/src/xsasl/xsasl_cyrus.h
new file mode 100644
index 0000000..ad8557e
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus.h
@@ -0,0 +1,47 @@
+#ifndef _XSASL_CYRUS_H_INCLUDED_
+#define _XSASL_CYRUS_H_INCLUDED_
+
+/*++
+/* NAME
+/* xsasl_cyrus 3h
+/* SUMMARY
+/* Cyrus SASL plug-in
+/* SYNOPSIS
+/* #include <xsasl_cyrus.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * XSASL library.
+ */
+#include <xsasl.h>
+
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+ /*
+ * SASL protocol interface
+ */
+#define XSASL_TYPE_CYRUS "cyrus"
+
+extern XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *, const char *);
+extern XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *, const char *);
+
+ /*
+ * Internal definitions for client and server module.
+ */
+typedef int (*XSASL_CYRUS_CB) (void);
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/xsasl/xsasl_cyrus_client.c b/src/xsasl/xsasl_cyrus_client.c
new file mode 100644
index 0000000..fc799c9
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus_client.c
@@ -0,0 +1,585 @@
+/*++
+/* NAME
+/* xsasl_cyrus_client 3
+/* SUMMARY
+/* Cyrus SASL client-side plug-in
+/* SYNOPSIS
+/* #include <xsasl_cyrus_client.h>
+/*
+/* XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info)
+/* const char *client_type;
+/* DESCRIPTION
+/* This module implements the Cyrus SASL client-side authentication
+/* plug-in.
+/*
+/* xsasl_cyrus_client_init() initializes the Cyrus SASL library and
+/* returns an implementation handle that can be used to generate
+/* SASL client instances.
+/*
+/* Arguments:
+/* .IP client_type
+/* The plug-in SASL client type (cyrus). This argument is
+/* ignored, but it could be used when one implementation
+/* provides multiple variants.
+/* .IP path_info
+/* Implementation-specific information to specify the location
+/* of a configuration file, rendez-vous point, etc. This
+/* information is ignored by the Cyrus SASL client plug-in.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* Panic: interface violation.
+/*
+/* Other: the routines log a warning and return an error result
+/* as specified in xsasl_client(3).
+/* SEE ALSO
+/* xsasl_client(3) Client API
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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 <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+
+ /*
+ * Application-specific
+ */
+#include <xsasl.h>
+#include <xsasl_cyrus.h>
+#include <xsasl_cyrus_common.h>
+
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+#include <sasl.h>
+#include <saslutil.h>
+
+/*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+ /*
+ * Macros to handle API differences between SASLv1 and SASLv2. Specifics:
+ *
+ * The SASL_LOG_* constants were renamed in SASLv2.
+ *
+ * SASLv2's sasl_client_new takes two new parameters to specify local and
+ * remote IP addresses for auth mechs that use them.
+ *
+ * SASLv2's sasl_client_start function no longer takes the secret parameter.
+ *
+ * SASLv2's sasl_decode64 function takes an extra parameter for the length of
+ * the output buffer.
+ *
+ * The other major change is that SASLv2 now takes more responsibility for
+ * deallocating memory that it allocates internally. Thus, some of the
+ * function parameters are now 'const', to make sure we don't try to free
+ * them too. This is dealt with in the code later on.
+ */
+#if SASL_VERSION_MAJOR < 2
+/* SASL version 1.x */
+#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \
+ sasl_client_new(srv, fqdn, prompt, secflags, pconn)
+#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \
+ sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech)
+#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
+ sasl_decode64(in, inlen, out, outlen)
+typedef char *CLIENTOUT_TYPE;
+
+#endif
+
+#if SASL_VERSION_MAJOR >= 2
+/* SASL version > 2.x */
+#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \
+ sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn)
+#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \
+ sasl_client_start(conn, mechlst, prompt, clout, cllen, mech)
+#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
+ sasl_decode64(in, inlen, out, outmaxlen, outlen)
+typedef const char *CLIENTOUT_TYPE;
+
+#endif
+
+ /*
+ * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT
+ * object.
+ */
+typedef struct {
+ XSASL_CLIENT xsasl; /* generic members, must be first */
+ VSTREAM *stream; /* client-server connection */
+ sasl_conn_t *sasl_conn; /* SASL context */
+ VSTRING *decoded; /* decoded server challenge */
+ sasl_callback_t *callbacks; /* user/password lookup */
+ char *username;
+ char *password;
+} XSASL_CYRUS_CLIENT;
+
+ /*
+ * Forward declarations.
+ */
+static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *);
+static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *,
+ XSASL_CLIENT_CREATE_ARGS *);
+static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *);
+static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *,
+ const char *, const char **, VSTRING *);
+static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *);
+static void xsasl_cyrus_client_free(XSASL_CLIENT *);
+
+/* xsasl_cyrus_client_get_user - username lookup call-back routine */
+
+static int xsasl_cyrus_client_get_user(void *context, int unused_id,
+ const char **result,
+ unsigned *len)
+{
+ const char *myname = "xsasl_cyrus_client_get_user";
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, client->username);
+
+ /*
+ * Sanity check.
+ */
+ if (client->password == 0)
+ msg_panic("%s: no username looked up", myname);
+
+ *result = client->username;
+ if (len)
+ *len = strlen(client->username);
+ return (SASL_OK);
+}
+
+/* xsasl_cyrus_client_get_passwd - password lookup call-back routine */
+
+static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context,
+ int id, sasl_secret_t **psecret)
+{
+ const char *myname = "xsasl_cyrus_client_get_passwd";
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context;
+ int len;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, client->password);
+
+ /*
+ * Sanity check.
+ */
+ if (!conn || !psecret || id != SASL_CB_PASS)
+ return (SASL_BADPARAM);
+ if (client->password == 0)
+ msg_panic("%s: no password looked up", myname);
+
+ /*
+ * Convert the password into a counted string.
+ */
+ len = strlen(client->password);
+ if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0)
+ return (SASL_NOMEM);
+ (*psecret)->len = len;
+ memcpy((*psecret)->data, client->password, len + 1);
+
+ return (SASL_OK);
+}
+
+/* xsasl_cyrus_client_init - initialize Cyrus SASL library */
+
+XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type,
+ const char *unused_path_info)
+{
+ XSASL_CLIENT_IMPL *xp;
+ int sasl_status;
+
+ /*
+ * Global callbacks. These have no per-session context.
+ */
+ static sasl_callback_t callbacks[] = {
+ {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0},
+ {SASL_CB_LIST_END, 0, 0}
+ };
+
+#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \
+ || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19))
+ int sasl_major;
+ int sasl_minor;
+ int sasl_step;
+
+ /*
+ * DLL hell guard.
+ */
+ sasl_version_info((const char **) 0, (const char **) 0,
+ &sasl_major, &sasl_minor,
+ &sasl_step, (int *) 0);
+ if (sasl_major != SASL_VERSION_MAJOR
+#if 0
+ || sasl_minor != SASL_VERSION_MINOR
+ || sasl_step != SASL_VERSION_STEP
+#endif
+ ) {
+ msg_warn("incorrect SASL library version. "
+ "Postfix was built with include files from version %d.%d.%d, "
+ "but the run-time library version is %d.%d.%d",
+ SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
+ sasl_major, sasl_minor, sasl_step);
+ return (0);
+ }
+#endif
+
+ if (*var_cyrus_conf_path) {
+#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */
+ if (sasl_set_path(SASL_PATH_TYPE_CONFIG,
+ var_cyrus_conf_path) != SASL_OK)
+ msg_warn("failed to set Cyrus SASL configuration path: \"%s\"",
+ var_cyrus_conf_path);
+#else
+ msg_warn("%s is not empty, but setting the Cyrus SASL configuration "
+ "path is not supported with SASL library version %d.%d.%d",
+ VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR,
+ SASL_VERSION_MINOR, SASL_VERSION_STEP);
+#endif
+ }
+
+ /*
+ * Initialize the SASL library.
+ */
+ if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) {
+ msg_warn("SASL library initialization error: %s",
+ xsasl_cyrus_strerror(sasl_status));
+ return (0);
+ }
+
+ /*
+ * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it
+ * with our own methods or data.
+ */
+ xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp));
+ xp->create = xsasl_cyrus_client_create;
+ xp->done = xsasl_cyrus_client_done;
+ return (xp);
+}
+
+/* xsasl_cyrus_client_done - dispose of implementation */
+
+static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl)
+{
+ myfree((void *) impl);
+ sasl_done();
+}
+
+/* xsasl_cyrus_client_create - per-session SASL initialization */
+
+XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl,
+ XSASL_CLIENT_CREATE_ARGS *args)
+{
+ XSASL_CYRUS_CLIENT *client = 0;
+ static sasl_callback_t callbacks[] = {
+ {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0},
+ {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0},
+ {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0},
+ {SASL_CB_LIST_END, 0, 0}
+ };
+ sasl_conn_t *sasl_conn = 0;
+ sasl_callback_t *custom_callbacks = 0;
+ sasl_callback_t *cp;
+ int sasl_status;
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \
+ do { \
+ if (client) { \
+ xsasl_cyrus_client_free(&client->xsasl); \
+ } else { \
+ if (custom_callbacks) \
+ myfree((void *) custom_callbacks); \
+ if (sasl_conn) \
+ sasl_dispose(&sasl_conn); \
+ } \
+ return (x); \
+ } while (0)
+
+ /*
+ * Per-session initialization. Provide each session with its own callback
+ * context.
+ */
+#define NULL_SECFLAGS 0
+
+ custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks));
+ memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks));
+
+#define NULL_SERVER_ADDR ((char *) 0)
+#define NULL_CLIENT_ADDR ((char *) 0)
+
+ if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name,
+ NULL_CLIENT_ADDR, NULL_SERVER_ADDR,
+ var_cyrus_sasl_authzid ? custom_callbacks :
+ custom_callbacks + 1, NULL_SECFLAGS,
+ &sasl_conn)) != SASL_OK) {
+ msg_warn("per-session SASL client initialization: %s",
+ xsasl_cyrus_strerror(sasl_status));
+ XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0);
+ }
+
+ /*
+ * Extend the XSASL_CLIENT object with our own state. We use long-lived
+ * conversion buffers rather than local variables to avoid memory leaks
+ * in case of read/write timeout or I/O error.
+ *
+ * XXX If we enable SASL encryption, there needs to be a way to inform the
+ * application, so that they can turn off connection caching, refuse
+ * STARTTLS, etc.
+ */
+ client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client));
+ client->xsasl.free = xsasl_cyrus_client_free;
+ client->xsasl.first = xsasl_cyrus_client_first;
+ client->xsasl.next = xsasl_cyrus_client_next;
+ client->stream = args->stream;
+ client->sasl_conn = sasl_conn;
+ client->callbacks = custom_callbacks;
+ client->decoded = vstring_alloc(20);
+ client->username = 0;
+ client->password = 0;
+
+ for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++)
+ cp->context = (void *) client;
+
+ if (xsasl_cyrus_client_set_security(&client->xsasl,
+ args->security_options)
+ != XSASL_AUTH_OK)
+ XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0);
+
+ return (&client->xsasl);
+}
+
+/* xsasl_cyrus_client_set_security - set security properties */
+
+static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp,
+ const char *sasl_opts_val)
+{
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
+ sasl_security_properties_t sec_props;
+ int sasl_status;
+
+ /*
+ * Per-session security properties. XXX This routine is not sufficiently
+ * documented. What is the purpose of all this?
+ */
+ memset(&sec_props, 0, sizeof(sec_props));
+ sec_props.min_ssf = 0;
+ sec_props.max_ssf = 0; /* don't allow real SASL
+ * security layer */
+ if (*sasl_opts_val == 0) {
+ sec_props.security_flags = 0;
+ } else {
+ sec_props.security_flags =
+ xsasl_cyrus_security_parse_opts(sasl_opts_val);
+ if (sec_props.security_flags == 0) {
+ msg_warn("bad per-session SASL security properties");
+ return (XSASL_AUTH_FAIL);
+ }
+ }
+ sec_props.maxbufsize = 0;
+ sec_props.property_names = 0;
+ sec_props.property_values = 0;
+ if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS,
+ &sec_props)) != SASL_OK) {
+ msg_warn("set per-session SASL security properties: %s",
+ xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FAIL);
+ }
+ return (XSASL_AUTH_OK);
+}
+
+/* xsasl_cyrus_client_first - run authentication protocol */
+
+static int xsasl_cyrus_client_first(XSASL_CLIENT *xp,
+ const char *mechanism_list,
+ const char *username,
+ const char *password,
+ const char **mechanism,
+ VSTRING *init_resp)
+{
+ const char *myname = "xsasl_cyrus_client_first";
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
+ unsigned enc_length;
+ unsigned enc_length_out;
+ CLIENTOUT_TYPE clientout;
+ unsigned clientoutlen;
+ int sasl_status;
+
+#define NO_SASL_SECRET 0
+#define NO_SASL_INTERACTION 0
+
+ /*
+ * Save the username and password for the call-backs.
+ */
+ if (client->username)
+ myfree(client->username);
+ client->username = mystrdup(username);
+ if (client->password)
+ myfree(client->password);
+ client->password = mystrdup(password);
+
+ /*
+ * Start the client side authentication protocol.
+ */
+ sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn,
+ mechanism_list,
+ NO_SASL_SECRET, NO_SASL_INTERACTION,
+ &clientout, &clientoutlen, mechanism);
+ if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) {
+ vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FAIL);
+ }
+
+ /*
+ * Generate the AUTH command and the optional initial client response.
+ * sasl_encode64() produces four bytes for each complete or incomplete
+ * triple of input bytes. Allocate an extra byte for string termination.
+ */
+#define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4)
+
+ if (clientoutlen > 0) {
+ if (msg_verbose) {
+ escape(client->decoded, clientout, clientoutlen);
+ msg_info("%s: uncoded initial reply: %s",
+ myname, STR(client->decoded));
+ }
+ enc_length = ENCODE64_LENGTH(clientoutlen) + 1;
+ VSTRING_RESET(init_resp); /* Fix 200512 */
+ VSTRING_SPACE(init_resp, enc_length);
+ if ((sasl_status = sasl_encode64(clientout, clientoutlen,
+ STR(init_resp),
+ vstring_avail(init_resp),
+ &enc_length_out)) != SASL_OK)
+ msg_panic("%s: sasl_encode64 botch: %s",
+ myname, xsasl_cyrus_strerror(sasl_status));
+ vstring_set_payload_size(init_resp, enc_length_out);
+#if SASL_VERSION_MAJOR < 2
+ /* SASL version 1 doesn't free memory that it allocates. */
+ free(clientout);
+#endif
+ } else {
+ vstring_strcpy(init_resp, "");
+ }
+ return (XSASL_AUTH_OK);
+}
+
+/* xsasl_cyrus_client_next - continue authentication */
+
+static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply,
+ VSTRING *client_reply)
+{
+ const char *myname = "xsasl_cyrus_client_next";
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
+ unsigned enc_length;
+ unsigned enc_length_out;
+ CLIENTOUT_TYPE clientout;
+ unsigned clientoutlen;
+ unsigned serverinlen;
+ int sasl_status;
+
+ /*
+ * Process a server challenge.
+ */
+ serverinlen = strlen(server_reply);
+ VSTRING_RESET(client->decoded); /* Fix 200512 */
+ VSTRING_SPACE(client->decoded, serverinlen);
+ if ((sasl_status = SASL_DECODE64(server_reply, serverinlen,
+ STR(client->decoded),
+ vstring_avail(client->decoded),
+ &enc_length)) != SASL_OK) {
+ vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FORM);
+ }
+ if (msg_verbose)
+ msg_info("%s: decoded challenge: %.*s",
+ myname, (int) enc_length, STR(client->decoded));
+ sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded),
+ enc_length, NO_SASL_INTERACTION,
+ &clientout, &clientoutlen);
+ if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) {
+ vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FAIL);
+ }
+
+ /*
+ * Send a client response.
+ */
+ if (clientoutlen > 0) {
+ if (msg_verbose)
+ msg_info("%s: uncoded client response %.*s",
+ myname, (int) clientoutlen, clientout);
+ enc_length = ENCODE64_LENGTH(clientoutlen) + 1;
+ VSTRING_RESET(client_reply); /* Fix 200512 */
+ VSTRING_SPACE(client_reply, enc_length);
+ if ((sasl_status = sasl_encode64(clientout, clientoutlen,
+ STR(client_reply),
+ vstring_avail(client_reply),
+ &enc_length_out)) != SASL_OK)
+ msg_panic("%s: sasl_encode64 botch: %s",
+ myname, xsasl_cyrus_strerror(sasl_status));
+#if SASL_VERSION_MAJOR < 2
+ /* SASL version 1 doesn't free memory that it allocates. */
+ free(clientout);
+#endif
+ } else {
+ /* XXX Can't happen. */
+ vstring_strcpy(client_reply, "");
+ }
+ return (XSASL_AUTH_OK);
+}
+
+/* xsasl_cyrus_client_free - per-session cleanup */
+
+void xsasl_cyrus_client_free(XSASL_CLIENT *xp)
+{
+ XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp;
+
+ if (client->username)
+ myfree(client->username);
+ if (client->password)
+ myfree(client->password);
+ if (client->sasl_conn)
+ sasl_dispose(&client->sasl_conn);
+ myfree((void *) client->callbacks);
+ vstring_free(client->decoded);
+ myfree((void *) client);
+}
+
+#endif
diff --git a/src/xsasl/xsasl_cyrus_common.h b/src/xsasl/xsasl_cyrus_common.h
new file mode 100644
index 0000000..5447378
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus_common.h
@@ -0,0 +1,39 @@
+#ifndef _CYRUS_COMMON_H_INCLUDED_
+#define _CYRUS_COMMON_H_INCLUDED_
+
+/*++
+/* NAME
+/* cyrus_common 3h
+/* SUMMARY
+/* Cyrus SASL plug-in helpers
+/* SYNOPSIS
+/* #include <cyrus_common.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+#define NO_SASL_LANGLIST ((const char *) 0)
+#define NO_SASL_OUTLANG ((const char **) 0)
+#define xsasl_cyrus_strerror(status) \
+ sasl_errstring((status), NO_SASL_LANGLIST, NO_SASL_OUTLANG)
+extern int xsasl_cyrus_log(void *, int, const char *);
+extern int xsasl_cyrus_security_parse_opts(const char *);
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/xsasl/xsasl_cyrus_log.c b/src/xsasl/xsasl_cyrus_log.c
new file mode 100644
index 0000000..7bf25c3
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus_log.c
@@ -0,0 +1,104 @@
+/*++
+/* NAME
+/* xsasl_cyrus_log 3
+/* SUMMARY
+/* Cyrus SASL logging call-back routine
+/* SYNOPSIS
+/* #include <xsasl_cyrus_common.h>
+/*
+/* int xsasl_cyrus_log(context, priority, text)
+/* void *context;
+/* int priority;
+/* const char *text;
+/* DESCRIPTION
+/* xsasl_cyrus_log() logs a Cyrus message.
+/* DIAGNOSTICS:
+/* Fatal: out of memory.
+/* 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>
+
+/* Application-specific */
+
+#include <xsasl_cyrus_common.h>
+
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+#include <sasl.h>
+#include <saslutil.h>
+
+/* xsasl_cyrus_log - logging callback */
+
+int xsasl_cyrus_log(void *unused_context, int priority,
+ const char *message)
+{
+ switch (priority) {
+ case SASL_LOG_ERR: /* unusual errors */
+#ifdef SASL_LOG_WARN /* non-fatal warnings (Cyrus-SASL v2) */
+ case SASL_LOG_WARN:
+#endif
+#ifdef SASL_LOG_WARNING /* non-fatal warnings (Cyrus-SASL v1) */
+ case SASL_LOG_WARNING:
+#endif
+ msg_warn("SASL authentication problem: %s", message);
+ break;
+#ifdef SASL_LOG_INFO
+ case SASL_LOG_INFO: /* other info (Cyrus-SASL v1) */
+ if (msg_verbose)
+ msg_info("SASL authentication info: %s", message);
+ break;
+#endif
+#ifdef SASL_LOG_NOTE
+ case SASL_LOG_NOTE: /* other info (Cyrus-SASL v2) */
+ if (msg_verbose)
+ msg_info("SASL authentication info: %s", message);
+ break;
+#endif
+#ifdef SASL_LOG_FAIL
+ case SASL_LOG_FAIL: /* authentication failures
+ * (Cyrus-SASL v2) */
+ msg_warn("SASL authentication failure: %s", message);
+ break;
+#endif
+#ifdef SASL_LOG_DEBUG
+ case SASL_LOG_DEBUG: /* more verbose than LOG_NOTE
+ * (Cyrus-SASL v2) */
+ if (msg_verbose > 1)
+ msg_info("SASL authentication debug: %s", message);
+ break;
+#endif
+#ifdef SASL_LOG_TRACE
+ case SASL_LOG_TRACE: /* traces of internal
+ * protocols (Cyrus-SASL v2) */
+ if (msg_verbose > 1)
+ msg_info("SASL authentication trace: %s", message);
+ break;
+#endif
+#ifdef SASL_LOG_PASS
+ case SASL_LOG_PASS: /* traces of internal
+ * protocols, including
+ * passwords (Cyrus-SASL v2) */
+ if (msg_verbose > 1)
+ msg_info("SASL authentication pass: %s", message);
+ break;
+#endif
+ }
+ return (SASL_OK);
+}
+
+#endif
diff --git a/src/xsasl/xsasl_cyrus_security.c b/src/xsasl/xsasl_cyrus_security.c
new file mode 100644
index 0000000..7ca7216
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus_security.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* xsasl_cyrus_security 3
+/* SUMMARY
+/* convert Cyrus SASL security properties to bit mask
+/* SYNOPSIS
+/* #include <xsasl_cyrus_common.h>
+/*
+/* int xsasl_cyrus_security_parse_opts(properties)
+/* const char *properties;
+/* DESCRIPTION
+/* xsasl_cyrus_security_parse_opts() converts a list of security
+/* properties to a bit mask. The result is zero in case of error.
+/*
+/* Arguments:
+/* .IP properties
+/* A comma or space separated list of zero or more of the
+/* following:
+/* .RS
+/* .IP noplaintext
+/* Disallow authentication methods that use plaintext passwords.
+/* .IP noactive
+/* Disallow authentication methods that are vulnerable to
+/* non-dictionary active attacks.
+/* .IP nodictionary
+/* Disallow authentication methods that are vulnerable to
+/* passive dictionary attack.
+/* .IP forward_secrecy
+/* Require forward secrecy between sessions.
+/* .IP noanonymous
+/* Disallow anonymous logins.
+/* .RE
+/* DIAGNOSTICS:
+/* Warning: bad input.
+/* 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 <name_mask.h>
+
+/* Application-specific. */
+
+#include <xsasl_cyrus_common.h>
+
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+#include <sasl.h>
+
+ /*
+ * SASL Security options.
+ */
+static const NAME_MASK xsasl_cyrus_sec_mask[] = {
+ "noplaintext", SASL_SEC_NOPLAINTEXT,
+ "noactive", SASL_SEC_NOACTIVE,
+ "nodictionary", SASL_SEC_NODICTIONARY,
+#ifdef SASL_SEC_FORWARD_SECRECY
+ "forward_secrecy", SASL_SEC_FORWARD_SECRECY,
+#endif
+ "noanonymous", SASL_SEC_NOANONYMOUS,
+#if SASL_VERSION_MAJOR >= 2
+ "mutual_auth", SASL_SEC_MUTUAL_AUTH,
+#endif
+ 0,
+};
+
+/* xsasl_cyrus_security - parse security options */
+
+int xsasl_cyrus_security_parse_opts(const char *sasl_opts_val)
+{
+ return (name_mask_opt("SASL security options", xsasl_cyrus_sec_mask,
+ sasl_opts_val, NAME_MASK_RETURN));
+}
+
+#endif
diff --git a/src/xsasl/xsasl_cyrus_server.c b/src/xsasl/xsasl_cyrus_server.c
new file mode 100644
index 0000000..4bf2ed2
--- /dev/null
+++ b/src/xsasl/xsasl_cyrus_server.c
@@ -0,0 +1,640 @@
+/*++
+/* NAME
+/* xsasl_cyrus_server 3
+/* SUMMARY
+/* Cyrus SASL server-side plug-in
+/* SYNOPSIS
+/* #include <xsasl_cyrus_server.h>
+/*
+/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info)
+/* const char *server_type;
+/* const char *path_info;
+/* DESCRIPTION
+/* This module implements the Cyrus SASL server-side authentication
+/* plug-in.
+/*
+/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and
+/* returns an implementation handle that can be used to generate
+/* SASL server instances.
+/*
+/* Arguments:
+/* .IP server_type
+/* The server type (cyrus). This argument is ignored, but it
+/* could be used when one implementation provides multiple
+/* variants.
+/* .IP path_info
+/* The base name of the SASL server configuration file (example:
+/* smtpd becomes /usr/lib/sasl2/smtpd.conf).
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* Panic: interface violation.
+/*
+/* Other: the routines log a warning and return an error result
+/* as specified in xsasl_server(3).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* 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>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <xsasl.h>
+#include <xsasl_cyrus.h>
+#include <xsasl_cyrus_common.h>
+
+#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL)
+
+#include <sasl.h>
+#include <saslutil.h>
+
+/*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+ /*
+ * Macros to handle API differences between SASLv1 and SASLv2. Specifics:
+ *
+ * The SASL_LOG_* constants were renamed in SASLv2.
+ *
+ * SASLv2's sasl_server_new takes two new parameters to specify local and
+ * remote IP addresses for auth mechs that use them.
+ *
+ * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr
+ * parameter.
+ *
+ * SASLv2's sasl_decode64 function takes an extra parameter for the length of
+ * the output buffer.
+ *
+ * The other major change is that SASLv2 now takes more responsibility for
+ * deallocating memory that it allocates internally. Thus, some of the
+ * function parameters are now 'const', to make sure we don't try to free
+ * them too. This is dealt with in the code later on.
+ */
+
+#if SASL_VERSION_MAJOR < 2
+/* SASL version 1.x */
+#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \
+ sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn)
+#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \
+ sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err)
+#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \
+ sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err)
+#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
+ sasl_decode64(in, inlen, out, outlen)
+typedef char *MECHANISM_TYPE;
+typedef unsigned MECHANISM_COUNT_TYPE;
+typedef char *SERVEROUT_TYPE;
+typedef void *VOID_SERVEROUT_TYPE;
+
+#endif
+
+#if SASL_VERSION_MAJOR >= 2
+/* SASL version > 2.x */
+#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \
+ sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn)
+#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \
+ sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen)
+#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \
+ sasl_server_step(conn, clin, clinlen, srvout, srvoutlen)
+#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \
+ sasl_decode64(in, inlen, out, outmaxlen, outlen)
+typedef const char *MECHANISM_TYPE;
+typedef int MECHANISM_COUNT_TYPE;
+typedef const char *SERVEROUT_TYPE;
+typedef const void *VOID_SERVEROUT_TYPE;
+
+#endif
+
+#ifndef NO_IP_CYRUS_SASL_AUTH
+#define USE_IP_CYRUS_SASL_AUTH
+#endif
+
+ /*
+ * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER
+ * object.
+ */
+typedef struct {
+ XSASL_SERVER xsasl; /* generic members, must be first */
+ VSTREAM *stream; /* client-server connection */
+ sasl_conn_t *sasl_conn; /* SASL context */
+ VSTRING *decoded; /* decoded challenge or response */
+ char *username; /* authenticated user */
+ char *mechanism_list; /* applicable mechanisms */
+} XSASL_CYRUS_SERVER;
+
+ /*
+ * Forward declarations.
+ */
+static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *);
+static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *,
+ XSASL_SERVER_CREATE_ARGS *);
+static void xsasl_cyrus_server_free(XSASL_SERVER *);
+static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *,
+ const char *, VSTRING *);
+static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *);
+static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *);
+static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *);
+static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *);
+
+ /*
+ * SASL callback interface structure. These call-backs have no per-session
+ * context.
+ */
+#define NO_CALLBACK_CONTEXT 0
+
+static sasl_callback_t callbacks[] = {
+ {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT},
+ {SASL_CB_LIST_END, 0, 0}
+};
+
+/* xsasl_cyrus_server_init - create implementation handle */
+
+XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type,
+ const char *path_info)
+{
+ const char *myname = "xsasl_cyrus_server_init";
+ XSASL_SERVER_IMPL *xp;
+ int sasl_status;
+
+#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \
+ || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19))
+ int sasl_major;
+ int sasl_minor;
+ int sasl_step;
+
+ /*
+ * DLL hell guard.
+ */
+ sasl_version_info((const char **) 0, (const char **) 0,
+ &sasl_major, &sasl_minor,
+ &sasl_step, (int *) 0);
+ if (sasl_major != SASL_VERSION_MAJOR
+#if 0
+ || sasl_minor != SASL_VERSION_MINOR
+ || sasl_step != SASL_VERSION_STEP
+#endif
+ ) {
+ msg_warn("incorrect SASL library version. "
+ "Postfix was built with include files from version %d.%d.%d, "
+ "but the run-time library version is %d.%d.%d",
+ SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
+ sasl_major, sasl_minor, sasl_step);
+ return (0);
+ }
+#endif
+
+ if (*var_cyrus_conf_path) {
+#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */
+ if (sasl_set_path(SASL_PATH_TYPE_CONFIG,
+ var_cyrus_conf_path) != SASL_OK)
+ msg_warn("failed to set Cyrus SASL configuration path: \"%s\"",
+ var_cyrus_conf_path);
+#else
+ msg_warn("%s is not empty, but setting the Cyrus SASL configuration "
+ "path is not supported with SASL library version %d.%d.%d",
+ VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR,
+ SASL_VERSION_MINOR, SASL_VERSION_STEP);
+#endif
+ }
+
+ /*
+ * Initialize the library: load SASL plug-in routines, etc.
+ */
+ if (msg_verbose)
+ msg_info("%s: SASL config file is %s.conf", myname, path_info);
+ if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) {
+ msg_warn("SASL per-process initialization failed: %s",
+ xsasl_cyrus_strerror(sasl_status));
+ return (0);
+ }
+
+ /*
+ * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it
+ * with our own methods or data.
+ */
+ xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp));
+ xp->create = xsasl_cyrus_server_create;
+ xp->done = xsasl_cyrus_server_done;
+ return (xp);
+}
+
+/* xsasl_cyrus_server_done - dispose of implementation */
+
+static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl)
+{
+ myfree((void *) impl);
+ sasl_done();
+}
+
+/* xsasl_cyrus_server_create - create server instance */
+
+static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl,
+ XSASL_SERVER_CREATE_ARGS *args)
+{
+ const char *myname = "xsasl_cyrus_server_create";
+ char *server_addr_port = 0;
+ char *client_addr_port = 0;
+ sasl_conn_t *sasl_conn = 0;
+ XSASL_CYRUS_SERVER *server = 0;
+ int sasl_status;
+
+ if (msg_verbose)
+ msg_info("%s: SASL service=%s, realm=%s",
+ myname, args->service, args->user_realm ?
+ args->user_realm : "(null)");
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \
+ do { \
+ if (server) { \
+ xsasl_cyrus_server_free(&server->xsasl); \
+ } else { \
+ if (sasl_conn) \
+ sasl_dispose(&sasl_conn); \
+ } \
+ XSASL_CYRUS_SERVER_CREATE_RETURN(x); \
+ } while (0)
+
+#define XSASL_CYRUS_SERVER_CREATE_RETURN(x) \
+ do { \
+ if (server_addr_port) \
+ myfree(server_addr_port); \
+ if (client_addr_port) \
+ myfree(client_addr_port); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Set up a new server context.
+ */
+#define NO_SECURITY_LAYERS (0)
+#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0)
+#define NO_AUTH_REALM ((char *) 0)
+
+#if SASL_VERSION_MAJOR >= 2 && defined(USE_IP_CYRUS_SASL_AUTH)
+
+ /*
+ * Get IP address and port of local and remote endpoints for SASL. Some
+ * implementation supports "[ipv6addr]:port" and "ipv4addr:port" (e.g.,
+ * https://illumos.org/man/3sasl/sasl_server_new), They still support the
+ * historical "address;port" syntax, so we stick with that for now.
+ */
+ server_addr_port = (*args->server_addr && *args->server_port ?
+ concatenate(args->server_addr, ";",
+ args->server_port, (char *) 0) : 0);
+ client_addr_port = (*args->client_addr && *args->client_port ?
+ concatenate(args->client_addr, ";",
+ args->client_port, (char *) 0) : 0);
+#else
+
+ /*
+ * Don't give any IP address information to SASL.
+ */
+#endif
+
+ if ((sasl_status =
+ SASL_SERVER_NEW(args->service, var_myhostname,
+ args->user_realm ? args->user_realm : NO_AUTH_REALM,
+ server_addr_port, client_addr_port,
+ NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS,
+ &sasl_conn)) != SASL_OK) {
+ msg_warn("SASL per-connection server initialization: %s",
+ xsasl_cyrus_strerror(sasl_status));
+ XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0);
+ }
+
+ /*
+ * Extend the XSASL_SERVER object with our own data. We use long-lived
+ * conversion buffers rather than local variables to avoid memory leaks
+ * in case of read/write timeout or I/O error.
+ */
+ server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server));
+ server->xsasl.free = xsasl_cyrus_server_free;
+ server->xsasl.first = xsasl_cyrus_server_first;
+ server->xsasl.next = xsasl_cyrus_server_next;
+ server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list;
+ server->xsasl.get_username = xsasl_cyrus_server_get_username;
+ server->stream = args->stream;
+ server->sasl_conn = sasl_conn;
+ server->decoded = vstring_alloc(20);
+ server->username = 0;
+ server->mechanism_list = 0;
+
+ if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options)
+ != XSASL_AUTH_OK)
+ XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0);
+
+ XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl);
+}
+
+/* xsasl_cyrus_server_set_security - set security properties */
+
+static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp,
+ const char *sasl_opts_val)
+{
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+ sasl_security_properties_t sec_props;
+ int sasl_status;
+
+ /*
+ * Security options. Some information can be found in the sasl.h include
+ * file.
+ */
+ memset(&sec_props, 0, sizeof(sec_props));
+ sec_props.min_ssf = 0;
+ sec_props.max_ssf = 0; /* don't allow real SASL
+ * security layer */
+ if (*sasl_opts_val == 0) {
+ sec_props.security_flags = 0;
+ } else {
+ sec_props.security_flags =
+ xsasl_cyrus_security_parse_opts(sasl_opts_val);
+ if (sec_props.security_flags == 0) {
+ msg_warn("bad per-session SASL security properties");
+ return (XSASL_AUTH_FAIL);
+ }
+ }
+ sec_props.maxbufsize = 0;
+ sec_props.property_names = 0;
+ sec_props.property_values = 0;
+
+ if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS,
+ &sec_props)) != SASL_OK) {
+ msg_warn("SASL per-connection security setup; %s",
+ xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FAIL);
+ }
+ return (XSASL_AUTH_OK);
+}
+
+/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */
+
+static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp)
+{
+ const char *myname = "xsasl_cyrus_server_get_mechanism_list";
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+ MECHANISM_TYPE mechanism_list;
+ MECHANISM_COUNT_TYPE mechanism_count;
+ int sasl_status;
+
+ /*
+ * Get the list of authentication mechanisms.
+ */
+#define UNSUPPORTED_USER ((char *) 0)
+#define IGNORE_MECHANISM_LEN ((unsigned *) 0)
+
+ if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER,
+ "", " ", "",
+ &mechanism_list,
+ IGNORE_MECHANISM_LEN,
+ &mechanism_count)) != SASL_OK) {
+ msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status));
+ return (0);
+ }
+ if (mechanism_count <= 0) {
+ msg_warn("%s: no applicable SASL mechanisms", myname);
+ return (0);
+ }
+ server->mechanism_list = mystrdup(mechanism_list);
+#if SASL_VERSION_MAJOR < 2
+ /* SASL version 1 doesn't free memory that it allocates. */
+ free(mechanism_list);
+#endif
+ return (server->mechanism_list);
+}
+
+/* xsasl_cyrus_server_free - destroy server instance */
+
+static void xsasl_cyrus_server_free(XSASL_SERVER *xp)
+{
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+
+ sasl_dispose(&server->sasl_conn);
+ vstring_free(server->decoded);
+ if (server->username)
+ myfree(server->username);
+ if (server->mechanism_list)
+ myfree(server->mechanism_list);
+ myfree((void *) server);
+}
+
+/* xsasl_cyrus_server_auth_response - encode server first/next response */
+
+static int xsasl_cyrus_server_auth_response(int sasl_status,
+ SERVEROUT_TYPE serverout,
+ unsigned serveroutlen,
+ VSTRING *reply)
+{
+ const char *myname = "xsasl_cyrus_server_auth_response";
+ unsigned enc_length;
+ unsigned enc_length_out;
+
+ /*
+ * Encode the server first/next non-error response; otherwise return the
+ * unencoded error text that corresponds to the SASL error status.
+ *
+ * Regarding the hairy expression below: output from sasl_encode64() comes
+ * in multiples of four bytes for each triple of input bytes, plus four
+ * bytes for any incomplete last triple, plus one byte for the null
+ * terminator.
+ */
+ if (sasl_status == SASL_OK) {
+ vstring_strcpy(reply, "");
+ return (XSASL_AUTH_DONE);
+ } else if (sasl_status == SASL_CONTINUE) {
+ if (msg_verbose)
+ msg_info("%s: uncoded server challenge: %.*s",
+ myname, (int) serveroutlen, serverout);
+ enc_length = ((serveroutlen + 2) / 3) * 4 + 1;
+ VSTRING_RESET(reply); /* Fix 200512 */
+ VSTRING_SPACE(reply, enc_length);
+ if ((sasl_status = sasl_encode64(serverout, serveroutlen,
+ STR(reply), vstring_avail(reply),
+ &enc_length_out)) != SASL_OK)
+ msg_panic("%s: sasl_encode64 botch: %s",
+ myname, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_MORE);
+ } else {
+ if (sasl_status == SASL_NOUSER) /* privacy */
+ sasl_status = SASL_BADAUTH;
+ vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
+ switch (sasl_status) {
+ case SASL_FAIL:
+ case SASL_NOMEM:
+ case SASL_TRYAGAIN:
+ case SASL_UNAVAIL:
+ return XSASL_AUTH_TEMP;
+ default:
+ return (XSASL_AUTH_FAIL);
+ }
+ }
+}
+
+/* xsasl_cyrus_server_first - per-session authentication */
+
+int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method,
+ const char *init_response, VSTRING *reply)
+{
+ const char *myname = "xsasl_cyrus_server_first";
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+ char *dec_buffer;
+ unsigned dec_length;
+ unsigned reply_len;
+ unsigned serveroutlen;
+ int sasl_status;
+ SERVEROUT_TYPE serverout = 0;
+ int xsasl_status;
+
+#if SASL_VERSION_MAJOR < 2
+ const char *errstr = 0;
+
+#endif
+
+#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
+
+ if (msg_verbose)
+ msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
+ IFELSE(init_response, ", init_response ", ""),
+ IFELSE(init_response, init_response, ""));
+
+ /*
+ * SASL authentication protocol start-up. Process any initial client
+ * response that was sent along in the AUTH command.
+ */
+ if (init_response) {
+ reply_len = strlen(init_response);
+ VSTRING_RESET(server->decoded); /* Fix 200512 */
+ VSTRING_SPACE(server->decoded, reply_len);
+ if ((sasl_status = SASL_DECODE64(init_response, reply_len,
+ dec_buffer = STR(server->decoded),
+ vstring_avail(server->decoded),
+ &dec_length)) != SASL_OK) {
+ vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FORM);
+ }
+ if (msg_verbose)
+ msg_info("%s: decoded initial response %s", myname, dec_buffer);
+ } else {
+ dec_buffer = 0;
+ dec_length = 0;
+ }
+ sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer,
+ dec_length, &serverout,
+ &serveroutlen, &errstr);
+ xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout,
+ serveroutlen, reply);
+#if SASL_VERSION_MAJOR < 2
+ /* SASL version 1 doesn't free memory that it allocates. */
+ free(serverout);
+#endif
+ return (xsasl_status);
+}
+
+/* xsasl_cyrus_server_next - continue authentication */
+
+static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request,
+ VSTRING *reply)
+{
+ const char *myname = "xsasl_cyrus_server_next";
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+ unsigned dec_length;
+ unsigned request_len;
+ unsigned serveroutlen;
+ int sasl_status;
+ SERVEROUT_TYPE serverout = 0;
+ int xsasl_status;
+
+#if SASL_VERSION_MAJOR < 2
+ const char *errstr = 0;
+
+#endif
+
+ request_len = strlen(request);
+ VSTRING_RESET(server->decoded); /* Fix 200512 */
+ VSTRING_SPACE(server->decoded, request_len);
+ if ((sasl_status = SASL_DECODE64(request, request_len,
+ STR(server->decoded),
+ vstring_avail(server->decoded),
+ &dec_length)) != SASL_OK) {
+ vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status));
+ return (XSASL_AUTH_FORM);
+ }
+ if (msg_verbose)
+ msg_info("%s: decoded response: %.*s",
+ myname, (int) dec_length, STR(server->decoded));
+ sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded),
+ dec_length, &serverout,
+ &serveroutlen, &errstr);
+ xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout,
+ serveroutlen, reply);
+#if SASL_VERSION_MAJOR < 2
+ /* SASL version 1 doesn't free memory that it allocates. */
+ free(serverout);
+#endif
+ return (xsasl_status);
+}
+
+/* xsasl_cyrus_server_get_username - get authenticated username */
+
+static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp)
+{
+ const char *myname = "xsasl_cyrus_server_get_username";
+ XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp;
+ VOID_SERVEROUT_TYPE serverout = 0;
+ int sasl_status;
+
+ /*
+ * XXX Do not free(serverout).
+ */
+ if (server->username)
+ myfree(server->username);
+ sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout);
+ if (sasl_status != SASL_OK || serverout == 0) {
+ server->username = 0;
+ } else {
+ server->username = mystrdup(serverout);
+ printable(server->username, '?');
+ }
+ return (server->username);
+}
+
+#endif
diff --git a/src/xsasl/xsasl_dovecot.h b/src/xsasl/xsasl_dovecot.h
new file mode 100644
index 0000000..f99850e
--- /dev/null
+++ b/src/xsasl/xsasl_dovecot.h
@@ -0,0 +1,41 @@
+#ifndef _XSASL_DOVECOT_H_INCLUDED_
+#define _XSASL_DOVECOT_H_INCLUDED_
+
+/*++
+/* NAME
+/* xsasl_dovecot 3h
+/* SUMMARY
+/* Dovecot SASL plug-in
+/* SYNOPSIS
+/* #include <xsasl_dovecot.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * XSASL library.
+ */
+#include <xsasl.h>
+
+#if defined(USE_SASL_AUTH)
+
+ /*
+ * SASL protocol interface
+ */
+#define XSASL_TYPE_DOVECOT "dovecot"
+
+extern XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *, const char *);
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/xsasl/xsasl_dovecot_server.c b/src/xsasl/xsasl_dovecot_server.c
new file mode 100644
index 0000000..1d1c570
--- /dev/null
+++ b/src/xsasl/xsasl_dovecot_server.c
@@ -0,0 +1,747 @@
+/*++
+/* NAME
+/* xsasl_dovecot_server 3
+/* SUMMARY
+/* Dovecot SASL server-side plug-in
+/* SYNOPSIS
+/* XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name)
+/* const char *server_type;
+/* const char *appl_name;
+/* DESCRIPTION
+/* This module implements the Dovecot SASL server-side authentication
+/* plug-in.
+/*
+/* .IP server_type
+/* The plug-in type that was specified to xsasl_server_init().
+/* The argument is ignored, because the Dovecot plug-in
+/* implements only one plug-in type.
+/* .IP path_info
+/* The location of the Dovecot authentication server's UNIX-domain
+/* socket. Note: the Dovecot plug-in uses late binding, therefore
+/* all connect operations are done with Postfix privileges.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* Panic: interface violation.
+/*
+/* Other: the routines log a warning and return an error result
+/* as specified in xsasl_server(3).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Timo Sirainen
+/* Procontrol
+/* Finland
+/*
+/* Adopted by:
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <name_mask.h>
+#include <argv.h>
+#include <myaddrinfo.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <xsasl.h>
+#include <xsasl_dovecot.h>
+
+#ifdef USE_SASL_AUTH
+
+/* Major version changes are not backwards compatible,
+ minor version numbers can be ignored. */
+#define AUTH_PROTOCOL_MAJOR_VERSION 1
+#define AUTH_PROTOCOL_MINOR_VERSION 0
+
+ /*
+ * Enforce read/write time limits, so that we can produce accurate
+ * diagnostics instead of getting killed by the watchdog timer.
+ */
+#define AUTH_TIMEOUT 10
+
+ /*
+ * Security property bitmasks.
+ */
+#define SEC_PROPS_NOPLAINTEXT (1 << 0)
+#define SEC_PROPS_NOACTIVE (1 << 1)
+#define SEC_PROPS_NODICTIONARY (1 << 2)
+#define SEC_PROPS_NOANONYMOUS (1 << 3)
+#define SEC_PROPS_FWD_SECRECY (1 << 4)
+#define SEC_PROPS_MUTUAL_AUTH (1 << 5)
+#define SEC_PROPS_PRIVATE (1 << 6)
+
+#define SEC_PROPS_POS_MASK (SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY)
+#define SEC_PROPS_NEG_MASK (SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \
+ SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS)
+
+ /*
+ * Security properties as specified in the Postfix main.cf file.
+ */
+static const NAME_MASK xsasl_dovecot_conf_sec_props[] = {
+ "noplaintext", SEC_PROPS_NOPLAINTEXT,
+ "noactive", SEC_PROPS_NOACTIVE,
+ "nodictionary", SEC_PROPS_NODICTIONARY,
+ "noanonymous", SEC_PROPS_NOANONYMOUS,
+ "forward_secrecy", SEC_PROPS_FWD_SECRECY,
+ "mutual_auth", SEC_PROPS_MUTUAL_AUTH,
+ 0, 0,
+};
+
+ /*
+ * Security properties as specified in the Dovecot protocol. See
+ * http://wiki.dovecot.org/Authentication_Protocol.
+ */
+static const NAME_MASK xsasl_dovecot_serv_sec_props[] = {
+ "plaintext", SEC_PROPS_NOPLAINTEXT,
+ "active", SEC_PROPS_NOACTIVE,
+ "dictionary", SEC_PROPS_NODICTIONARY,
+ "anonymous", SEC_PROPS_NOANONYMOUS,
+ "forward-secrecy", SEC_PROPS_FWD_SECRECY,
+ "mutual-auth", SEC_PROPS_MUTUAL_AUTH,
+ "private", SEC_PROPS_PRIVATE,
+ 0, 0,
+};
+
+ /*
+ * Class variables.
+ */
+typedef struct XSASL_DCSRV_MECH {
+ char *mech_name; /* mechanism name */
+ int sec_props; /* mechanism properties */
+ struct XSASL_DCSRV_MECH *next;
+} XSASL_DCSRV_MECH;
+
+typedef struct {
+ XSASL_SERVER_IMPL xsasl;
+ VSTREAM *sasl_stream;
+ char *socket_path;
+ XSASL_DCSRV_MECH *mechanism_list; /* unfiltered mechanism list */
+ unsigned int request_id_counter;
+} XSASL_DOVECOT_SERVER_IMPL;
+
+ /*
+ * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER
+ * object.
+ */
+typedef struct {
+ XSASL_SERVER xsasl; /* generic members, must be first */
+ XSASL_DOVECOT_SERVER_IMPL *impl;
+ unsigned int last_request_id;
+ char *service;
+ char *username; /* authenticated user */
+ VSTRING *sasl_line;
+ unsigned int sec_props; /* Postfix mechanism filter */
+ int tls_flag; /* TLS enabled in this session */
+ char *mechanism_list; /* filtered mechanism list */
+ ARGV *mechanism_argv; /* ditto */
+ char *client_addr; /* remote IP address */
+ char *server_addr; /* remote IP address */
+} XSASL_DOVECOT_SERVER;
+
+ /*
+ * Forward declarations.
+ */
+static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *);
+static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *,
+ XSASL_SERVER_CREATE_ARGS *);
+static void xsasl_dovecot_server_free(XSASL_SERVER *);
+static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *,
+ const char *, VSTRING *);
+static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *);
+static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *);
+static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *);
+
+/* xsasl_dovecot_server_mech_append - append server mechanism entry */
+
+static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH **mech_list,
+ const char *mech_name, int sec_props)
+{
+ XSASL_DCSRV_MECH **mpp;
+ XSASL_DCSRV_MECH *mp;
+
+ for (mpp = mech_list; *mpp != 0; mpp = &mpp[0]->next)
+ /* void */ ;
+
+ mp = (XSASL_DCSRV_MECH *) mymalloc(sizeof(*mp));
+ mp->mech_name = mystrdup(mech_name);
+ mp->sec_props = sec_props;
+ mp->next = 0;
+ *mpp = mp;
+}
+
+/* xsasl_dovecot_server_mech_free - destroy server mechanism list */
+
+static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH *mech_list)
+{
+ XSASL_DCSRV_MECH *mp;
+ XSASL_DCSRV_MECH *next;
+
+ for (mp = mech_list; mp != 0; mp = next) {
+ myfree(mp->mech_name);
+ next = mp->next;
+ myfree((void *) mp);
+ }
+}
+
+/* xsasl_dovecot_server_mech_filter - filter server mechanism list */
+
+static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv,
+ XSASL_DCSRV_MECH *mechanism_list,
+ unsigned int conf_props)
+{
+ const char *myname = "xsasl_dovecot_server_mech_filter";
+ unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK);
+ unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK);
+ VSTRING *mechanisms_str = vstring_alloc(10);
+ XSASL_DCSRV_MECH *mp;
+
+ /*
+ * Match Postfix properties against Dovecot server properties.
+ */
+ for (mp = mechanism_list; mp != 0; mp = mp->next) {
+ if ((mp->sec_props & pos_conf_props) == pos_conf_props
+ && (mp->sec_props & neg_conf_props) == 0) {
+ if (VSTRING_LEN(mechanisms_str) > 0)
+ VSTRING_ADDCH(mechanisms_str, ' ');
+ vstring_strcat(mechanisms_str, mp->mech_name);
+ argv_add(mechanism_argv, mp->mech_name, (char *) 0);
+ if (msg_verbose)
+ msg_info("%s: keep mechanism: %s", myname, mp->mech_name);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: skip mechanism: %s", myname, mp->mech_name);
+ }
+ }
+ return (vstring_export(mechanisms_str));
+}
+
+/* xsasl_dovecot_server_connect - initial auth server handshake */
+
+static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp)
+{
+ const char *myname = "xsasl_dovecot_server_connect";
+ VSTRING *line_str;
+ VSTREAM *sasl_stream;
+ char *line, *cmd, *mech_name;
+ unsigned int major_version, minor_version;
+ int fd, success, have_mech_line;
+ int sec_props;
+ const char *path;
+
+ if (msg_verbose)
+ msg_info("%s: Connecting", myname);
+
+ /*
+ * Not documented, but necessary for testing.
+ */
+ path = xp->socket_path;
+ if (strncmp(path, "inet:", 5) == 0) {
+ fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT);
+ } else {
+ if (strncmp(path, "unix:", 5) == 0)
+ path += 5;
+ fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT);
+ }
+ if (fd < 0) {
+ msg_warn("SASL: Connect to Dovecot auth socket '%s' failed: %m",
+ xp->socket_path);
+ return (-1);
+ }
+ sasl_stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(sasl_stream,
+ CA_VSTREAM_CTL_PATH(xp->socket_path),
+ CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT),
+ CA_VSTREAM_CTL_END);
+
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(sasl_stream,
+ "VERSION\t%u\t%u\n"
+ "CPID\t%u\n",
+ AUTH_PROTOCOL_MAJOR_VERSION,
+ AUTH_PROTOCOL_MINOR_VERSION,
+ (unsigned int) getpid());
+ if (vstream_fflush(sasl_stream) == VSTREAM_EOF) {
+ msg_warn("SASL: Couldn't send handshake: %m");
+ return (-1);
+ }
+ success = 0;
+ have_mech_line = 0;
+ line_str = vstring_alloc(256);
+ /* XXX Encapsulate for logging. */
+ while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) {
+ line = vstring_str(line_str);
+
+ if (msg_verbose)
+ msg_info("%s: auth reply: %s", myname, line);
+
+ cmd = line;
+ line = split_at(line, '\t');
+
+ if (strcmp(cmd, "VERSION") == 0) {
+ if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) {
+ msg_warn("SASL: Protocol version error");
+ break;
+ }
+ if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) {
+ /* Major version is different from ours. */
+ msg_warn("SASL: Protocol version mismatch (%d vs. %d)",
+ major_version, AUTH_PROTOCOL_MAJOR_VERSION);
+ break;
+ }
+ } else if (strcmp(cmd, "MECH") == 0 && line != NULL) {
+ mech_name = line;
+ have_mech_line = 1;
+ line = split_at(line, '\t');
+ if (line != 0) {
+ sec_props =
+ name_mask_delim_opt(myname,
+ xsasl_dovecot_serv_sec_props,
+ line, "\t",
+ NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
+ if ((sec_props & SEC_PROPS_PRIVATE) != 0)
+ continue;
+ } else
+ sec_props = 0;
+ xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name,
+ sec_props);
+ } else if (strcmp(cmd, "SPID") == 0) {
+
+ /*
+ * Unfortunately the auth protocol handshake wasn't designed well
+ * to differentiate between auth-client/userdb/master.
+ * auth-userdb and auth-master send VERSION + SPID lines only and
+ * nothing afterwards, while auth-client sends VERSION + MECH +
+ * SPID + CUID + more. The simplest way that we can determine if
+ * we've connected to the correct socket is to see if MECH line
+ * exists or not (alternatively we'd have to have a small timeout
+ * after SPID to see if CUID is sent or not).
+ */
+ if (!have_mech_line) {
+ msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)");
+ break;
+ }
+ } else if (strcmp(cmd, "DONE") == 0) {
+ /* Handshake finished. */
+ success = 1;
+ break;
+ } else {
+ /* ignore any unknown commands */
+ }
+ }
+ vstring_free(line_str);
+
+ if (!success) {
+ /* handshake failed */
+ (void) vstream_fclose(sasl_stream);
+ return (-1);
+ }
+ xp->sasl_stream = sasl_stream;
+ return (0);
+}
+
+/* xsasl_dovecot_server_disconnect - dispose of server connection state */
+
+static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp)
+{
+ if (xp->sasl_stream) {
+ (void) vstream_fclose(xp->sasl_stream);
+ xp->sasl_stream = 0;
+ }
+ if (xp->mechanism_list) {
+ xsasl_dovecot_server_mech_free(xp->mechanism_list);
+ xp->mechanism_list = 0;
+ }
+}
+
+/* xsasl_dovecot_server_init - create implementation handle */
+
+XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type,
+ const char *path_info)
+{
+ XSASL_DOVECOT_SERVER_IMPL *xp;
+
+ xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp));
+ xp->xsasl.create = xsasl_dovecot_server_create;
+ xp->xsasl.done = xsasl_dovecot_server_done;
+ xp->socket_path = mystrdup(path_info);
+ xp->sasl_stream = 0;
+ xp->mechanism_list = 0;
+ xp->request_id_counter = 0;
+ return (&xp->xsasl);
+}
+
+/* xsasl_dovecot_server_done - dispose of implementation */
+
+static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl)
+{
+ XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl;
+
+ xsasl_dovecot_server_disconnect(xp);
+ myfree(xp->socket_path);
+ myfree((void *) impl);
+}
+
+/* xsasl_dovecot_server_create - create server instance */
+
+static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl,
+ XSASL_SERVER_CREATE_ARGS *args)
+{
+ const char *myname = "xsasl_dovecot_server_create";
+ XSASL_DOVECOT_SERVER *server;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE salen;
+ MAI_HOSTADDR_STR server_addr;
+
+ if (msg_verbose)
+ msg_info("%s: SASL service=%s, realm=%s",
+ myname, args->service, args->user_realm ?
+ args->user_realm : "(null)");
+
+ /*
+ * Extend the XSASL_SERVER_IMPL object with our own data. We use
+ * long-lived conversion buffers rather than local variables to avoid
+ * memory leaks in case of read/write timeout or I/O error.
+ */
+ server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server));
+ server->xsasl.free = xsasl_dovecot_server_free;
+ server->xsasl.first = xsasl_dovecot_server_first;
+ server->xsasl.next = xsasl_dovecot_server_next;
+ server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list;
+ server->xsasl.get_username = xsasl_dovecot_server_get_username;
+ server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl;
+ server->sasl_line = vstring_alloc(256);
+ server->username = 0;
+ server->service = mystrdup(args->service);
+ server->last_request_id = 0;
+ server->mechanism_list = 0;
+ server->mechanism_argv = 0;
+ server->tls_flag = args->tls_flag;
+ server->sec_props =
+ name_mask_opt(myname, xsasl_dovecot_conf_sec_props,
+ args->security_options,
+ NAME_MASK_ANY_CASE | NAME_MASK_FATAL);
+ server->client_addr = mystrdup(args->client_addr);
+
+ /*
+ * XXX Temporary code until smtpd_peer.c is updated.
+ */
+ if (args->server_addr && *args->server_addr) {
+ server->server_addr = mystrdup(args->server_addr);
+ } else {
+ salen = sizeof(ss);
+ if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0
+ || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0)
+ server_addr.buf[0] = 0;
+ server->server_addr = mystrdup(server_addr.buf);
+ }
+
+ return (&server->xsasl);
+}
+
+/* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */
+
+static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ if (!server->impl->sasl_stream) {
+ if (xsasl_dovecot_server_connect(server->impl) < 0)
+ return (0);
+ }
+ if (server->mechanism_list == 0) {
+ server->mechanism_argv = argv_alloc(2);
+ server->mechanism_list =
+ xsasl_dovecot_server_mech_filter(server->mechanism_argv,
+ server->impl->mechanism_list,
+ server->sec_props);
+ }
+ return (server->mechanism_list[0] ? server->mechanism_list : 0);
+}
+
+/* xsasl_dovecot_server_free - destroy server instance */
+
+static void xsasl_dovecot_server_free(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ vstring_free(server->sasl_line);
+ if (server->username)
+ myfree(server->username);
+ if (server->mechanism_list) {
+ myfree(server->mechanism_list);
+ argv_free(server->mechanism_argv);
+ }
+ myfree(server->service);
+ myfree(server->server_addr);
+ myfree(server->client_addr);
+ myfree((void *) server);
+}
+
+/* xsasl_dovecot_server_auth_response - encode server first/next response */
+
+static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line)
+{
+ char *id;
+
+ if (*line == NULL) {
+ msg_warn("SASL: Protocol error");
+ return -1;
+ }
+ id = *line;
+ *line = split_at(*line, '\t');
+
+ if (strtoul(id, NULL, 0) != server->last_request_id) {
+ /* reply to another request, shouldn't really happen.. */
+ return -1;
+ }
+ return 0;
+}
+
+static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server,
+ char *line, VSTRING *reply,
+ int success)
+{
+ char *next;
+
+ if (server->username) {
+ myfree(server->username);
+ server->username = 0;
+ }
+
+ /*
+ * Note: TAB is part of the Dovecot protocol and must not appear in
+ * legitimate Dovecot usernames, otherwise the protocol would break.
+ */
+ for (; line != NULL; line = next) {
+ next = split_at(line, '\t');
+ if (strncmp(line, "user=", 5) == 0) {
+ server->username = mystrdup(line + 5);
+ printable(server->username, '?');
+ } else if (strncmp(line, "reason=", 7) == 0) {
+ if (!success) {
+ printable(line + 7, '?');
+ vstring_strcpy(reply, line + 7);
+ }
+ }
+ }
+}
+
+/* xsasl_dovecot_handle_reply - receive and process auth reply */
+
+static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server,
+ VSTRING *reply)
+{
+ const char *myname = "xsasl_dovecot_handle_reply";
+ char *line, *cmd;
+
+ /* XXX Encapsulate for logging. */
+ while (vstring_get_nonl(server->sasl_line,
+ server->impl->sasl_stream) != VSTREAM_EOF) {
+ line = vstring_str(server->sasl_line);
+
+ if (msg_verbose)
+ msg_info("%s: auth reply: %s", myname, line);
+
+ cmd = line;
+ line = split_at(line, '\t');
+
+ if (strcmp(cmd, "OK") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ /* authentication successful */
+ xsasl_dovecot_parse_reply_args(server, line, reply, 1);
+ if (server->username == 0) {
+ msg_warn("missing Dovecot server %s username field", cmd);
+ vstring_strcpy(reply, "Authentication backend error");
+ return XSASL_AUTH_FAIL;
+ }
+ return XSASL_AUTH_DONE;
+ }
+ } else if (strcmp(cmd, "CONT") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ if (line == 0) {
+ msg_warn("missing Dovecot server %s reply field", cmd);
+ vstring_strcpy(reply, "Authentication backend error");
+ return XSASL_AUTH_FAIL;
+ }
+ vstring_strcpy(reply, line);
+ return XSASL_AUTH_MORE;
+ }
+ } else if (strcmp(cmd, "FAIL") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ /* authentication failure */
+ xsasl_dovecot_parse_reply_args(server, line, reply, 0);
+ return XSASL_AUTH_FAIL;
+ }
+ } else {
+ /* ignore */
+ }
+ }
+
+ vstring_strcpy(reply, "Connection lost to authentication server");
+ return XSASL_AUTH_TEMP;
+}
+
+/* is_valid_base64 - input sanitized */
+
+static int is_valid_base64(const char *data)
+{
+
+ /*
+ * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent).
+ */
+ for (; *data != '\0'; data++) {
+ if (!((*data >= '0' && *data <= '9') ||
+ (*data >= 'a' && *data <= 'z') ||
+ (*data >= 'A' && *data <= 'Z') ||
+ *data == '+' || *data == '/' || *data == '='))
+ return 0;
+ }
+ return 1;
+}
+
+/* xsasl_dovecot_server_first - per-session authentication */
+
+int xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method,
+ const char *init_response, VSTRING *reply)
+{
+ const char *myname = "xsasl_dovecot_server_first";
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+ int i;
+ char **cpp;
+
+#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
+
+ if (msg_verbose)
+ msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
+ IFELSE(init_response, ", init_response ", ""),
+ IFELSE(init_response, init_response, ""));
+
+ if (server->mechanism_argv == 0)
+ msg_panic("%s: no mechanism list", myname);
+
+ for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) {
+ if (*cpp == 0) {
+ vstring_strcpy(reply, "Invalid authentication mechanism");
+ return XSASL_AUTH_FAIL;
+ }
+ if (strcasecmp(sasl_method, *cpp) == 0)
+ break;
+ }
+ if (init_response)
+ if (!is_valid_base64(init_response)) {
+ vstring_strcpy(reply, "Invalid base64 data in initial response");
+ return XSASL_AUTH_FAIL;
+ }
+ for (i = 0; i < 2; i++) {
+ if (!server->impl->sasl_stream) {
+ if (xsasl_dovecot_server_connect(server->impl) < 0)
+ return XSASL_AUTH_TEMP;
+ }
+ /* send the request */
+ server->last_request_id = ++server->impl->request_id_counter;
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s",
+ server->last_request_id, sasl_method,
+ server->service, server->server_addr,
+ server->client_addr);
+ if (server->tls_flag)
+ /* XXX Encapsulate for logging. */
+ vstream_fputs("\tsecured", server->impl->sasl_stream);
+ if (init_response) {
+
+ /*
+ * initial response is already base64 encoded, so we can send it
+ * directly.
+ */
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "\tresp=%s", init_response);
+ }
+ /* XXX Encapsulate for logging. */
+ VSTREAM_PUTC('\n', server->impl->sasl_stream);
+
+ if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF)
+ break;
+
+ if (i == 1) {
+ vstring_strcpy(reply, "Can't connect to authentication server");
+ return XSASL_AUTH_TEMP;
+ }
+
+ /*
+ * Reconnect and try again.
+ */
+ xsasl_dovecot_server_disconnect(server->impl);
+ }
+
+ return xsasl_dovecot_handle_reply(server, reply);
+}
+
+/* xsasl_dovecot_server_next - continue authentication */
+
+static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request,
+ VSTRING *reply)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ if (!is_valid_base64(request)) {
+ vstring_strcpy(reply, "Invalid base64 data in continued response");
+ return XSASL_AUTH_FAIL;
+ }
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "CONT\t%u\t%s\n", server->last_request_id, request);
+ if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) {
+ vstring_strcpy(reply, "Connection lost to authentication server");
+ return XSASL_AUTH_TEMP;
+ }
+ return xsasl_dovecot_handle_reply(server, reply);
+}
+
+/* xsasl_dovecot_server_get_username - get authenticated username */
+
+static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ return (server->username);
+}
+
+#endif
diff --git a/src/xsasl/xsasl_server.c b/src/xsasl/xsasl_server.c
new file mode 100644
index 0000000..e8d7e16
--- /dev/null
+++ b/src/xsasl/xsasl_server.c
@@ -0,0 +1,270 @@
+/*++
+/* NAME
+/* xsasl-server 3
+/* SUMMARY
+/* Postfix SASL server plug-in interface
+/* SYNOPSIS
+/* #include <xsasl.h>
+/*
+/* XSASL_SERVER_IMPL *xsasl_server_init(server_type, path_info)
+/* const char *server_type;
+/* const char *path_info;
+/*
+/* void xsasl_server_done(implementation)
+/* XSASL_SERVER_IMPL *implementation;
+/*
+/* ARGV *xsasl_server_types()
+/*
+/* .in +4
+/* typedef struct XSASL_SERVER_CREATE_ARGS {
+/* VSTREAM *stream;
+/* const char *server_addr;
+/* const char *client_addr;
+/* const char *service;
+/* const char *user_realm;
+/* const char *security_options;
+/* int tls_flag;
+/* } XSASL_SERVER_CREATE_ARGS;
+/* .in -4
+/*
+/* XSASL_SERVER *xsasl_server_create(implementation, args)
+/* XSASL_SERVER_IMPL *implementation;
+/* XSASL_SERVER_CREATE_ARGS *args;
+/*
+/* XSASL_SERVER *XSASL_SERVER_CREATE(implementation, args,
+/* stream = stream_value,
+/* ...,
+/* tls_flag = tls_flag_value)
+/* XSASL_SERVER_IMPL *implementation;
+/* XSASL_SERVER_CREATE_ARGS *args;
+/*
+/* void xsasl_server_free(server)
+/* XSASL_SERVER *server;
+/*
+/* int xsasl_server_first(server, auth_method, init_resp, server_reply)
+/* XSASL_SERVER *server;
+/* const char *auth_method;
+/* const char *init_resp;
+/* VSTRING *server_reply;
+/*
+/* int xsasl_server_next(server, client_request, server_reply)
+/* XSASL_SERVER *server;
+/* const char *client_request;
+/* VSTRING *server_reply;
+/*
+/* const char *xsasl_server_get_mechanism_list(server)
+/* XSASL_SERVER *server;
+/*
+/* const char *xsasl_server_get_username(server)
+/* XSASL_SERVER *server;
+/* DESCRIPTION
+/* The XSASL_SERVER abstraction implements a generic interface
+/* to one or more SASL authentication implementations.
+/*
+/* xsasl_server_init() is called once during process initialization.
+/* It selects a SASL implementation by name, specifies the
+/* location of a configuration file or rendez-vous point, and
+/* returns an implementation handle that can be used to generate
+/* SASL server instances. This function is typically used to
+/* initialize the underlying implementation.
+/*
+/* xsasl_server_done() disposes of an implementation handle,
+/* and allows the underlying implementation to release resources.
+/*
+/* xsasl_server_types() lists the available implementation types.
+/* The result should be destroyed by the caller.
+/*
+/* xsasl_server_create() is called at the start of an SMTP
+/* session. It generates a Postfix SASL plug-in server instance
+/* for the specified service and authentication realm, and
+/* with the specified security properties. Specify a null
+/* pointer when no realm should be used. The stream handle is
+/* stored so that encryption can be turned on after successful
+/* negotiations. Specify zero-length strings when a client or
+/* server address is unavailable.
+/*
+/* XSASL_SERVER_CREATE() is a macro that provides an interface
+/* with named parameters. Named parameters do not have to
+/* appear in a fixed order. The parameter names correspond to
+/* the member names of the XSASL_SERVER_CREATE_ARGS structure.
+/*
+/* xsasl_server_free() is called at the end of an SMTP session.
+/* It destroys a SASL server instance, and disables further
+/* read/write operations if encryption was turned on.
+/*
+/* xsasl_server_first() produces the server response for the
+/* client AUTH command. The client input are an authentication
+/* method, and an optional initial response or null pointer.
+/* The initial response and server non-error replies are BASE64
+/* encoded. Server error replies are 7-bit ASCII text without
+/* control characters, without BASE64 encoding, and without
+/* SMTP reply code or enhanced status code.
+/*
+/* The result is one of the following:
+/* .IP XSASL_AUTH_MORE
+/* More client input is needed. The server reply specifies
+/* what.
+/* .IP XSASL_AUTH_DONE
+/* Authentication completed successfully.
+/* .IP XSASL_AUTH_FORM
+/* The client input is incorrectly formatted. The server error
+/* reply explains why.
+/* .IP XSASL_AUTH_FAIL
+/* Authentication failed. The server error reply explains why.
+/* .PP
+/* xsasl_server_next() supports the subsequent stages of the
+/* client-server AUTH protocol. Both the client input and
+/* server non-error responses are BASE64 encoded. See
+/* xsasl_server_first() for other details.
+/*
+/* xsasl_server_get_mechanism_list() returns the authentication
+/* mechanisms that match the security properties, as a white-space
+/* separated list. This is meant to be used in the SMTP EHLO
+/* reply.
+/*
+/* xsasl_server_get_username() returns the stored username
+/* after successful authentication.
+/*
+/* Arguments:
+/* .IP addr_family
+/* The network address family: AF_INET6 or AF_INET.
+/* .IP auth_method
+/* AUTH command authentication method.
+/* .IP client_addr
+/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix),
+/* or zero-length string if unavailable.
+/* .IP client_port
+/* TCP port or zero-length string if unavailable.
+/* .IP init_resp
+/* AUTH command initial response or null pointer.
+/* .IP implementation
+/* Implementation handle that was obtained with xsasl_server_init().
+/* .IP path_info
+/* The value of the smtpd_sasl_path parameter or equivalent.
+/* This specifies the implementation-dependent location of a
+/* configuration file, rendez-vous point, etc., and is passed
+/* unchanged to the plug-in.
+/* .IP security_options
+/* The value of the smtpd_security_options parameter or
+/* equivalent. This is passed unchanged to the plug-in.
+/* .IP server
+/* SASL plug-in server handle.
+/* .IP server_addr
+/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix),
+/* or zero-length string if unavailable.
+/* .IP server_port
+/* TCP port or zero-length string if unavailable.
+/* .IP server_reply
+/* BASE64 encoded server non-error reply (without SMTP reply
+/* code or enhanced status code), or ASCII error description.
+/* .IP server_type
+/* The name of a Postfix SASL server plug_in implementation.
+/* .IP server_types
+/* Null-terminated array of strings with SASL server plug-in
+/* implementation names.
+/* .IP service
+/* The service that is implemented by the local server, typically
+/* "smtp" or "lmtp".
+/* .IP stream
+/* The connection between client and server. When SASL
+/* encryption is negotiated, the plug-in will transparently
+/* intercept the socket read/write operations.
+/* .IP user_realm
+/* Authentication domain or null pointer.
+/* SECURITY
+/* .ad
+/* .fi
+/* The caller does not sanitize client input. It is the
+/* responsibility of the underlying SASL server implementation
+/* to produce 7-bit ASCII without control characters as server
+/* non-error and error replies, and as the result from
+/* xsasl_server_method() and xsasl_server_username().
+/* DIAGNOSTICS
+/* In case of failure, xsasl_server_init(), xsasl_server_create(),
+/* xsasl_server_get_mechanism_list() and xsasl_server_get_username()
+/* log a warning and return a null pointer.
+/*
+/* Functions that normally return XSASL_AUTH_OK will log a warning
+/* and return an appropriate result value.
+/*
+/* Fatal errors: out of memory.
+/*
+/* Panic: interface violations.
+/* SEE ALSO
+/* cyrus_security(3) Cyrus SASL security features
+/* 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 <mymalloc.h>
+
+/* SASL implementations. */
+
+#include <xsasl.h>
+#include <xsasl_cyrus.h>
+#include <xsasl_dovecot.h>
+
+ /*
+ * Lookup table for available SASL server implementations.
+ */
+typedef struct {
+ char *server_type;
+ struct XSASL_SERVER_IMPL *(*server_init) (const char *, const char *);
+} XSASL_SERVER_IMPL_INFO;
+
+static const XSASL_SERVER_IMPL_INFO server_impl_info[] = {
+#ifdef XSASL_TYPE_CYRUS
+ {XSASL_TYPE_CYRUS, xsasl_cyrus_server_init},
+#endif
+#ifdef XSASL_TYPE_DOVECOT
+ {XSASL_TYPE_DOVECOT, xsasl_dovecot_server_init},
+#endif
+ {0, 0}
+};
+
+/* xsasl_server_init - look up server implementation by name */
+
+XSASL_SERVER_IMPL *xsasl_server_init(const char *server_type,
+ const char *path_info)
+{
+ const XSASL_SERVER_IMPL_INFO *xp;
+
+ for (xp = server_impl_info; xp->server_type; xp++)
+ if (strcmp(server_type, xp->server_type) == 0)
+ return (xp->server_init(server_type, path_info));
+ msg_warn("unsupported SASL server implementation: %s", server_type);
+ return (0);
+}
+
+/* xsasl_server_types - report available implementation types */
+
+ARGV *xsasl_server_types(void)
+{
+ const XSASL_SERVER_IMPL_INFO *xp;
+ ARGV *argv = argv_alloc(1);
+
+ for (xp = server_impl_info; xp->server_type; xp++)
+ argv_add(argv, xp->server_type, ARGV_END);
+ return (argv);
+}